Coverage for /builds/debichem-team/python-ase/ase/calculators/acemolecule.py: 72.92%
96 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
1import os
2from copy import deepcopy
4from ase.calculators.calculator import FileIOCalculator, ReadError
5from ase.io import read
8class ACE(FileIOCalculator):
9 '''
10 ACE-Molecule logfile reader
11 It has default parameters of each input section
12 And parameters' type = list of dictionaries
13 '''
14 name = 'ace'
15 implemented_properties = ['energy', 'forces', 'excitation-energy']
16 basic_list = [{
17 'Type': 'Scaling', 'Scaling': '0.35', 'Basis': 'Sinc',
18 'Grid': 'Sphere',
19 'KineticMatrix': 'Finite_Difference', 'DerivativesOrder': '7',
20 'GeometryFilename': None, 'NumElectrons': None}
21 ]
22 scf_list = [{
23 'ExchangeCorrelation': {'XFunctional': 'GGA_X_PBE',
24 'CFunctional': 'GGA_C_PBE'},
25 'NumberOfEigenvalues': None,
26 }]
28 force_list = [{'ForceDerivative': 'Potential'}]
29 tddft_list = [{
30 'SortOrbital': 'Order', 'MaximumOrder': '10',
31 'ExchangeCorrelation': {'XFunctional': 'GGA_X_PBE',
32 'CFunctional': 'GGA_C_PBE'},
33 }]
35 order_list = ['BasicInformation', 'Guess', 'Scf']
36 guess_list = [{}] # type: ignore[var-annotated]
37 default_parameters = {'BasicInformation': basic_list, 'Guess': guess_list,
38 'Scf': scf_list, 'Force': force_list,
39 'TDDFT': tddft_list, 'order': order_list}
41 def __init__(
42 self, restart=None,
43 ignore_bad_restart_file=FileIOCalculator._deprecated,
44 label='ace', atoms=None, command=None,
45 basisfile=None, **kwargs):
46 FileIOCalculator.__init__(self, restart, ignore_bad_restart_file,
47 label, atoms, command=command, **kwargs)
49 def set(self, **kwargs):
50 '''Update parameters self.parameter member variable.
51 1. Add default values for repeated parameter sections with
52 self.default_parameters using order.
53 2. Also add empty dictionary as an indicator for section existence
54 if no relevant default_parameters exist.
55 3. Update parameters from arguments.
57 Returns
58 =======
59 Updated parameter
60 '''
61 new_parameters = deepcopy(self.parameters)
63 changed_parameters = FileIOCalculator.set(self, **kwargs)
65 # Add default values for repeated parameter sections with
66 # self.default_parameters using order. Also add empty
67 # dictionary as an indicator for section existence if no
68 # relevant default_parameters exist.
69 if 'order' in kwargs:
70 new_parameters['order'] = kwargs['order']
71 section_sets = set(kwargs['order'])
72 for section_name in section_sets:
73 repeat = kwargs['order'].count(section_name)
74 if section_name in self.default_parameters.keys():
75 for _ in range(repeat - 1):
76 new_parameters[section_name] += deepcopy(
77 self.default_parameters[section_name])
78 else:
79 new_parameters[section_name] = []
80 for _ in range(repeat):
81 new_parameters[section_name].append({})
83 # Update parameters
84 for section in new_parameters['order']:
85 if section in kwargs:
86 if isinstance(kwargs[section], dict):
87 kwargs[section] = [kwargs[section]]
89 for i, section_param in enumerate(kwargs[section]):
90 new_parameters[section][i] = update_parameter(
91 new_parameters[section][i], section_param)
92 self.parameters = new_parameters
93 return changed_parameters
95 def read(self, label):
96 FileIOCalculator.read(self, label)
97 filename = self.label + ".log"
99 with open(filename) as fd:
100 lines = fd.readlines()
101 if 'WARNING' in lines:
102 raise ReadError(
103 f"Not convergy energy in log file {filename}.")
104 if '! total energy' not in lines:
105 raise ReadError(f"Wrong ACE-Molecule log file {filename}.")
107 if not os.path.isfile(filename):
108 raise ReadError(
109 f"Wrong ACE-Molecule input file {filename}.")
111 self.read_results()
113 def write_input(self, atoms, properties=None, system_changes=None):
114 '''Initializes input parameters and xyz files. If force calculation is
115 requested, add Force section to parameters if not exists.
117 Parameters
118 ==========
119 atoms: ASE atoms object.
120 properties: List of properties to be calculated. Should be element
121 of self.implemented_properties.
122 system_chages: Ignored.
124 '''
125 FileIOCalculator.write_input(self, atoms, properties, system_changes)
126 with open(self.label + '.inp', 'w') as inputfile:
127 xyz_name = f"{self.label}.xyz"
128 atoms.write(xyz_name)
130 run_parameters = self.prepare_input(xyz_name, properties)
131 self.write_acemolecule_input(inputfile, run_parameters)
133 def prepare_input(self, geometry_filename, properties):
134 '''Initialize parameters dictionary based on geometry filename and
135 calculated properties.
137 Parameters
138 ==========
139 geometry_filename: Geometry (XYZ format) file path.
140 properties: Properties to be calculated.
142 Returns
143 =======
144 Updated version of self.parameters; geometry file and
145 optionally Force section are updated.
147 '''
148 copied_parameters = deepcopy(self.parameters)
149 if (properties is not None and "forces" in properties
150 and 'Force' not in copied_parameters['order']):
151 copied_parameters['order'].append('Force')
152 copied_parameters["BasicInformation"][0]["GeometryFilename"] = \
153 f"{self.label}.xyz"
154 copied_parameters["BasicInformation"][0]["GeometryFormat"] = "xyz"
155 return copied_parameters
157 def read_results(self):
158 '''Read calculation results, speficied by 'quantities' variable, from
159 the log file.
161 quantities
162 =======
163 energy : obtaing single point energy(eV) from log file
164 forces : obtaing force of each atom form log file
165 excitation-energy : it able to calculate TDDFT.
166 Return value is None. Result is not used.
167 atoms : ASE atoms object
169 '''
170 filename = self.label + '.log'
171 self.results = read(filename, format='acemolecule-out')
173 def write_acemolecule_section(self, fpt, section, depth=0):
174 '''Write parameters in each section of input
176 Parameters
177 ==========
178 fpt: ACE-Moleucle input file object. Should be write mode.
179 section: Dictionary of a parameter section.
180 depth: Nested input depth.
181 '''
182 for section, section_param in section.items():
183 if isinstance(section_param, (str, int, float)):
184 fpt.write(
185 ' ' *
186 depth +
187 str(section) +
188 " " +
189 str(section_param) +
190 "\n")
191 else:
192 if isinstance(section_param, dict):
193 fpt.write(' ' * depth + "%% " + str(section) + "\n")
194 self.write_acemolecule_section(
195 fpt, section_param, depth + 1)
196 fpt.write(' ' * depth + "%% End\n")
197 if isinstance(section_param, list):
198 for val in section_param:
199 fpt.write(
200 ' ' *
201 depth +
202 str(section) +
203 " " +
204 str(val) +
205 "\n")
207 def write_acemolecule_input(self, fpt, param, depth=0):
208 '''Write ACE-Molecule input
210 ACE-Molecule input examples (not minimal)
211 %% BasicInformation
212 Type Scaling
213 Scaling 0.4
214 Basis Sinc
215 Cell 10.0
216 Grid Sphere
217 GeometryFormat xyz
218 SpinMultiplicity 3.0
219 Polarize 1
220 Centered 0
221 %% Pseudopotential
222 Pseudopotential 1
223 UsingDoubleGrid 0
224 FilterType Sinc
225 Format upf
226 PSFilePath /PATH/TO/UPF
227 PSFileSuffix .pbe-theos.UPF
228 %% End
229 GeometryFilename xyz/C.xyz
230 %% End
231 %% Guess
232 InitialGuess 3
233 InitialFilenames 001.cube
234 InitialFilenames 002.cube
235 %% End
236 %% Scf
237 IterateMaxCycle 150
238 ConvergenceType Energy
239 ConvergenceTolerance 0.00001
240 EnergyDecomposition 1
241 ComputeInitialEnergy 1
242 %% Diagonalize
243 Tolerance 0.000001
244 %% End
245 %% ExchangeCorrelation
246 XFunctional GGA_X_PBE
247 CFunctional GGA_C_PBE
248 %% End
249 %% Mixing
250 MixingMethod 1
251 MixingType Density
252 MixingParameter 0.5
253 PulayMixingParameter 0.1
254 %% End
255 %% End
257 Parameters
258 ==========
259 fpt: File object, should be write mode.
260 param: Dictionary of parameters. Also should contain
261 special 'order' section_name for parameter section ordering.
262 depth: Nested input depth.
264 Notes
265 =====
266 - Order of parameter section
267 (denoted using %% -- %% BasicInformation, %% Guess, etc.)
268 is important, because it determines calculation order.
269 For example, if Guess section comes after Scf section,
270 calculation will not run because Scf will tries to run
271 without initial Hamiltonian.
272 - Order of each parameter section-section_name pair is
273 not important unless their keys are the same.
274 - Indentation unimportant and capital letters are important.
275 '''
276 prefix = " " * depth
278 for i in range(len(param['order'])):
279 fpt.write(prefix + "%% " + param['order'][i] + "\n")
280 section_list = param[param['order'][i]]
281 if len(section_list) > 0:
282 section = section_list.pop(0)
283 self.write_acemolecule_section(fpt, section, 1)
284 fpt.write("%% End\n")
285 return
288def update_parameter(oldpar, newpar):
289 '''Update each section of parameter (oldpar) using newpar keys and values.
290 If section of newpar exist in oldpar,
291 - Replace the section_name with newpar's section_name if
292 oldvar section_name type is not dict.
293 - Append the section_name with newpar's section_name
294 if oldvar section_name type is list.
295 - If oldpar section_name type is dict, it is subsection.
296 So call update_parameter again.
297 otherwise, add the parameter section and section_name from newpar.
299 Parameters
300 ==========
301 oldpar: dictionary of original parameters to be updated.
302 newpar: dictionary containing parameter section and values to update.
304 Return
305 ======
306 Updated parameter dictionary.
307 '''
308 for section, section_param in newpar.items():
309 if section in oldpar:
310 if isinstance(section_param, dict):
311 oldpar[section] = update_parameter(
312 oldpar[section], section_param)
313 else:
314 oldpar[section] = section_param
315 else:
316 oldpar[section] = section_param
317 return oldpar