Coverage for /builds/alexhroom/ase/ase/calculators/aims.py: 42.28%
123 statements
« prev ^ index » next coverage.py v7.5.3, created at 2024-08-05 14:37 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2024-08-05 14:37 +0000
1"""This module defines an ASE interface to FHI-aims.
3Felix Hanke hanke@liverpool.ac.uk
4Jonas Bjork j.bjork@liverpool.ac.uk
5Simon P. Rittmeyer simon.rittmeyer@tum.de
7Edits on (24.11.2021) by Thomas A. R. Purcell purcell@fhi-berlin.mpg.de
8"""
10import os
11import re
13import numpy as np
15from ase.calculators.genericfileio import (BaseProfile, CalculatorTemplate,
16 GenericFileIOCalculator,
17 read_stdout)
18from ase.io.aims import write_aims, write_control
21def get_aims_version(string):
22 match = re.search(r'\s*FHI-aims version\s*:\s*(\S+)', string, re.M)
23 return match.group(1)
26class AimsProfile(BaseProfile):
27 configvars = {'default_species_directory'}
29 def __init__(self, command, default_species_directory=None, **kwargs):
30 super().__init__(command, **kwargs)
31 self.default_species_directory = default_species_directory
33 def get_calculator_command(self, inputfile):
34 return []
36 def version(self):
37 return get_aims_version(read_stdout(self._split_command))
40class AimsTemplate(CalculatorTemplate):
41 _label = 'aims'
43 def __init__(self):
44 super().__init__(
45 'aims',
46 [
47 'energy',
48 'free_energy',
49 'forces',
50 'stress',
51 'stresses',
52 'dipole',
53 'magmom',
54 ],
55 )
57 self.outputname = f'{self._label}.out'
58 self.errorname = f'{self._label}.err'
60 def update_parameters(self, properties, parameters):
61 """Check and update the parameters to match the desired calculation
63 Parameters
64 ----------
65 properties: list of str
66 The list of properties to calculate
67 parameters: dict
68 The parameters used to perform the calculation.
70 Returns
71 -------
72 dict
73 The updated parameters object
74 """
75 parameters = dict(parameters)
76 property_flags = {
77 'forces': 'compute_forces',
78 'stress': 'compute_analytical_stress',
79 'stresses': 'compute_heat_flux',
80 }
81 # Ensure FHI-aims will calculate all desired properties
82 for property in properties:
83 aims_name = property_flags.get(property, None)
84 if aims_name is not None:
85 parameters[aims_name] = True
87 if 'dipole' in properties:
88 if 'output' in parameters and 'dipole' not in parameters['output']:
89 parameters['output'] = list(parameters['output'])
90 parameters['output'].append('dipole')
91 elif 'output' not in parameters:
92 parameters['output'] = ['dipole']
94 return parameters
96 def write_input(self, profile, directory, atoms, parameters, properties):
97 """Write the geometry.in and control.in files for the calculation
99 Parameters
100 ----------
101 directory : Path
102 The working directory to store the input files.
103 atoms : atoms.Atoms
104 The atoms object to perform the calculation on.
105 parameters: dict
106 The parameters used to perform the calculation.
107 properties: list of str
108 The list of properties to calculate
109 """
110 parameters = self.update_parameters(properties, parameters)
112 ghosts = parameters.pop('ghosts', None)
113 geo_constrain = parameters.pop('geo_constrain', None)
114 scaled = parameters.pop('scaled', None)
115 write_velocities = parameters.pop('write_velocities', None)
117 if scaled is None:
118 scaled = np.all(atoms.pbc)
119 if write_velocities is None:
120 write_velocities = atoms.has('momenta')
122 if geo_constrain is None:
123 geo_constrain = scaled and 'relax_geometry' in parameters
125 have_lattice_vectors = atoms.pbc.any()
126 have_k_grid = (
127 'k_grid' in parameters
128 or 'kpts' in parameters
129 or 'k_grid_density' in parameters
130 )
131 if have_lattice_vectors and not have_k_grid:
132 raise RuntimeError('Found lattice vectors but no k-grid!')
133 if not have_lattice_vectors and have_k_grid:
134 raise RuntimeError('Found k-grid but no lattice vectors!')
136 geometry_in = directory / 'geometry.in'
138 write_aims(
139 geometry_in,
140 atoms,
141 scaled,
142 geo_constrain,
143 write_velocities=write_velocities,
144 ghosts=ghosts,
145 )
147 control = directory / 'control.in'
149 if (
150 'species_dir' not in parameters
151 and profile.default_species_directory is not None
152 ):
153 parameters['species_dir'] = profile.default_species_directory
155 write_control(control, atoms, parameters)
157 def execute(self, directory, profile):
158 profile.run(directory, None, self.outputname,
159 errorfile=self.errorname)
161 def read_results(self, directory):
162 from ase.io.aims import read_aims_results
164 dst = directory / self.outputname
165 return read_aims_results(dst, index=-1)
167 def load_profile(self, cfg, **kwargs):
168 return AimsProfile.from_config(cfg, self.name, **kwargs)
170 def socketio_argv(self, profile, unixsocket, port):
171 return [profile.command]
173 def socketio_parameters(self, unixsocket, port):
174 if port:
175 use_pimd_wrapper = ('localhost', port)
176 else:
177 # (INET port number should be unused.)
178 use_pimd_wrapper = (f'UNIX:{unixsocket}', 31415)
180 return dict(use_pimd_wrapper=use_pimd_wrapper, compute_forces=True)
183class Aims(GenericFileIOCalculator):
184 def __init__(
185 self,
186 profile=None,
187 directory='.',
188 **kwargs,
189 ):
190 """Construct the FHI-aims calculator.
192 The keyword arguments (kwargs) can be one of the ASE standard
193 keywords: 'xc', 'kpts' and 'smearing' or any of FHI-aims'
194 native keywords.
197 Arguments:
199 cubes: AimsCube object
200 Cube file specification.
202 tier: int or array of ints
203 Set basis set tier for all atomic species.
205 plus_u : dict
206 For DFT+U. Adds a +U term to one specific shell of the species.
208 kwargs : dict
209 Any of the base class arguments.
211 """
213 super().__init__(
214 template=AimsTemplate(),
215 profile=profile,
216 parameters=kwargs,
217 directory=directory,
218 )
221class AimsCube:
222 'Object to ensure the output of cube files, can be attached to Aims object'
224 def __init__(
225 self,
226 origin=(0, 0, 0),
227 edges=[(0.1, 0.0, 0.0), (0.0, 0.1, 0.0), (0.0, 0.0, 0.1)],
228 points=(50, 50, 50),
229 plots=(),
230 ):
231 """parameters:
233 origin, edges, points:
234 Same as in the FHI-aims output
235 plots:
236 what to print, same names as in FHI-aims"""
238 self.name = 'AimsCube'
239 self.origin = origin
240 self.edges = edges
241 self.points = points
242 self.plots = plots
244 def ncubes(self):
245 """returns the number of cube files to output"""
246 return len(self.plots)
248 def move_to_base_name(self, basename):
249 """when output tracking is on or the base namem is not standard,
250 this routine will rename add the base to the cube file output for
251 easier tracking"""
252 for plot in self.plots:
253 found = False
254 cube = plot.split()
255 if (
256 cube[0] == 'total_density'
257 or cube[0] == 'spin_density'
258 or cube[0] == 'delta_density'
259 ):
260 found = True
261 old_name = cube[0] + '.cube'
262 new_name = basename + '.' + old_name
263 if cube[0] == 'eigenstate' or cube[0] == 'eigenstate_density':
264 found = True
265 state = int(cube[1])
266 s_state = cube[1]
267 for i in [10, 100, 1000, 10000]:
268 if state < i:
269 s_state = '0' + s_state
270 old_name = cube[0] + '_' + s_state + '_spin_1.cube'
271 new_name = basename + '.' + old_name
272 if found:
273 # XXX Should not use platform dependent commands!
274 os.system('mv ' + old_name + ' ' + new_name)
276 def add_plot(self, name):
277 """in case you forgot one ..."""
278 self.plots += [name]
280 def write(self, file):
281 """write the necessary output to the already opened control.in"""
282 file.write('output cube ' + self.plots[0] + '\n')
283 file.write(' cube origin ')
284 for ival in self.origin:
285 file.write(str(ival) + ' ')
286 file.write('\n')
287 for i in range(3):
288 file.write(' cube edge ' + str(self.points[i]) + ' ')
289 for ival in self.edges[i]:
290 file.write(str(ival) + ' ')
291 file.write('\n')
292 if self.ncubes() > 1:
293 for i in range(self.ncubes() - 1):
294 file.write('output cube ' + self.plots[i + 1] + '\n')