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