1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
"""ONETEP interface for the Atomic Simulation Environment (ASE) package
T. Demeyere, T.Demeyere@soton.ac.uk (2023)
https://onetep.org"""
from copy import deepcopy
from ase.calculators.genericfileio import (
BaseProfile,
CalculatorTemplate,
GenericFileIOCalculator,
read_stdout,
)
from ase.io import read, write
class OnetepProfile(BaseProfile):
"""
ONETEP profile class.
"""
configvars = {'pseudo_path'}
def __init__(self, command, pseudo_path, **kwargs):
"""
Parameters
----------
command: str
The onetep command (not including inputfile).
**kwargs: dict
Additional kwargs are passed to the BaseProfile
class.
"""
super().__init__(command, **kwargs)
self.pseudo_path = pseudo_path
def version(self):
lines = read_stdout(self._split_command)
return self.parse_version(lines)
def parse_version(lines):
return '1.0.0'
def get_calculator_command(self, inputfile):
return [str(inputfile)]
class OnetepTemplate(CalculatorTemplate):
_label = 'onetep'
def __init__(self, append):
super().__init__(
'ONETEP',
implemented_properties=[
'energy',
'free_energy',
'forces',
'stress'])
self.inputname = f'{self._label}.dat'
self.outputname = f'{self._label}.out'
self.errorname = f'{self._label}.err'
self.append = append
def execute(self, directory, profile):
profile.run(directory, self.inputname, self.outputname,
self.errorname, append=self.append)
def read_results(self, directory):
output_path = directory / self.outputname
atoms = read(output_path, format='onetep-out')
return dict(atoms.calc.properties())
def write_input(self, profile, directory, atoms, parameters, properties):
input_path = directory / self.inputname
parameters = deepcopy(parameters)
keywords = parameters.get('keywords', {})
keywords.setdefault('pseudo_path', profile.pseudo_path)
parameters['keywords'] = keywords
write(input_path, atoms, format='onetep-in',
properties=properties, **parameters)
def load_profile(self, cfg, **kwargs):
return OnetepProfile.from_config(cfg, self.name, **kwargs)
class Onetep(GenericFileIOCalculator):
"""
Class for the ONETEP calculator, uses ase/io/onetep.py.
Need the env variable "ASE_ONETEP_COMMAND" defined to
properly work. All other options are passed in kwargs.
Parameters
----------
autorestart : Bool
When activated, manages restart keywords automatically.
append: Bool
Append to output instead of overwriting.
directory: str
Directory where to run the calculation(s).
keywords: dict
Dictionary with ONETEP keywords to write,
keywords with lists as values will be
treated like blocks, with each element
of list being a different line.
xc: str
DFT xc to use e.g (PBE, RPBE, ...).
ngwfs_count: int|list|dict
Behaviour depends on the type:
int: every species will have this amount
of ngwfs.
list: list of int, will be attributed
alphabetically to species:
dict: keys are species name(s),
value are their number:
ngwfs_radius: int|list|dict
Behaviour depends on the type:
float: every species will have this radius.
list: list of float, will be attributed
alphabetically to species:
[10.0, 9.0]
dict: keys are species name(s),
value are their radius:
{'Na': 9.0, 'Cl': 10.0}
pseudopotentials: list|dict
Behaviour depends on the type:
list: list of string(s), will be attributed
alphabetically to specie(s):
['Cl.usp', 'Na.usp']
dict: keys are species name(s) their
value are the pseudopotential file to use:
{'Na': 'Na.usp', 'Cl': 'Cl.usp'}
pseudo_path: str
Where to look for pseudopotential, correspond
to the pseudo_path keyword of ONETEP.
.. note::
write_forces is always turned on by default
when using this interface.
.. note::
Little to no check is performed on the keywords provided by the user
via the keyword dictionary, it is the user responsibility that they
are valid ONETEP keywords.
"""
def __init__(
self,
*,
profile=None,
directory='.',
**kwargs):
self.keywords = kwargs.get('keywords', None)
self.template = OnetepTemplate(
append=kwargs.pop('append', False)
)
super().__init__(profile=profile, template=self.template,
directory=directory,
parameters=kwargs)
|