root / ase / calculators / aims.py @ 10
Historique | Voir | Annoter | Télécharger (17,02 ko)
1 |
"""This module defines an ASE interface to FHI-aims.
|
---|---|
2 |
|
3 |
Felix Hanke hanke@liverpool.ac.uk
|
4 |
Jonas Bjork j.bjork@liverpool.ac.uk
|
5 |
"""
|
6 |
|
7 |
from general import Calculator |
8 |
import os |
9 |
import sys |
10 |
from os.path import join, isfile, islink |
11 |
|
12 |
import numpy as np |
13 |
|
14 |
import ase |
15 |
|
16 |
float_keys = [ |
17 |
'charge',
|
18 |
'charge_mix_param',
|
19 |
'hartree_convergence_parameter',
|
20 |
'ini_linear_mix_param',
|
21 |
'ini_spin_mix_parma',
|
22 |
'initial_moment',
|
23 |
'MD_MB_init',
|
24 |
'MD_time_step',
|
25 |
'prec_mix_param',
|
26 |
'set_vacuum_level',
|
27 |
'spin_mix_param',
|
28 |
] |
29 |
|
30 |
exp_keys = [ |
31 |
'sc_accuracy_eev',
|
32 |
'sc_accuracy_etot',
|
33 |
'sc_accuracy_forces',
|
34 |
'sc_accuracy_rho',
|
35 |
] |
36 |
|
37 |
string_keys = [ |
38 |
'communication_type',
|
39 |
'KS_method',
|
40 |
'mixer',
|
41 |
'output_level',
|
42 |
'packed_matrix_format',
|
43 |
'restart',
|
44 |
'restart_read_only',
|
45 |
'restart_write_only',
|
46 |
'spin',
|
47 |
'total_energy_method',
|
48 |
'qpe_calc',
|
49 |
'xc',
|
50 |
] |
51 |
|
52 |
int_keys = [ |
53 |
'empty_states',
|
54 |
'ini_linear_mixing',
|
55 |
'max_relaxation_steps',
|
56 |
'n_max_pulay',
|
57 |
'sc_iter_limit',
|
58 |
'walltime',
|
59 |
] |
60 |
|
61 |
bool_keys = [ |
62 |
'collect_eigenvectors',
|
63 |
'compute_forces',
|
64 |
'compute_kinetic',
|
65 |
'distributed_spline_storage',
|
66 |
'evaluate_work_function',
|
67 |
'final_forces_cleaned',
|
68 |
'hessian_to_restart_geometry',
|
69 |
'MD_clean_rotations',
|
70 |
'restart_relaxations',
|
71 |
'squeeze_memory',
|
72 |
'use_density_matrix',
|
73 |
'use_dipole_correction',
|
74 |
'use_local_index',
|
75 |
'vdw_correction_hirshfeld',
|
76 |
] |
77 |
|
78 |
list_keys = [ |
79 |
'k_grid',
|
80 |
'k_offset',
|
81 |
'MD_run',
|
82 |
'MD_schedule',
|
83 |
'MD_segment',
|
84 |
'mixer_threshold',
|
85 |
'occupation_type',
|
86 |
'output',
|
87 |
'preconditioner',
|
88 |
'relativistic',
|
89 |
'relax_geometry',
|
90 |
] |
91 |
|
92 |
input_keys = [ |
93 |
'run_command',
|
94 |
'run_dir',
|
95 |
'species_dir',
|
96 |
'cubes',
|
97 |
'output_template',
|
98 |
'track_output',
|
99 |
] |
100 |
|
101 |
input_parameters_default = {'run_command':None, |
102 |
'run_dir':None, |
103 |
'species_dir':None, |
104 |
'cubes':None, |
105 |
'output_template':'aims', |
106 |
'track_output':False} |
107 |
|
108 |
class Aims(Calculator): |
109 |
def __init__(self, **kwargs): |
110 |
self.name = 'Aims' |
111 |
self.float_params = {}
|
112 |
self.exp_params = {}
|
113 |
self.string_params = {}
|
114 |
self.int_params = {}
|
115 |
self.bool_params = {}
|
116 |
self.list_params = {}
|
117 |
self.input_parameters = {}
|
118 |
for key in float_keys: |
119 |
self.float_params[key] = None |
120 |
for key in exp_keys: |
121 |
self.exp_params[key] = None |
122 |
for key in string_keys: |
123 |
self.string_params[key] = None |
124 |
for key in int_keys: |
125 |
self.int_params[key] = None |
126 |
for key in bool_keys: |
127 |
self.bool_params[key] = None |
128 |
for key in list_keys: |
129 |
self.list_params[key] = None |
130 |
for key in input_keys: |
131 |
self.input_parameters[key] = input_parameters_default[key]
|
132 |
if os.environ.has_key('AIMS_SPECIES_DIR'): |
133 |
self.input_parameters['species_dir'] = os.environ['AIMS_SPECIES_DIR'] |
134 |
if os.environ.has_key('AIMS_COMMAND'): |
135 |
self.input_parameters['run_command'] = os.environ['AIMS_COMMAND'] |
136 |
|
137 |
self.positions = None |
138 |
self.atoms = None |
139 |
self.run_counts = 0 |
140 |
self.set(**kwargs)
|
141 |
|
142 |
def set(self, **kwargs): |
143 |
if 'control' in kwargs: |
144 |
fname = kwargs['control']
|
145 |
from ase.io.aims import read_aims_calculator |
146 |
file = open(fname, 'r') |
147 |
calc_temp = None
|
148 |
while True: |
149 |
line = file.readline()
|
150 |
if "List of parameters used to initialize the calculator:" in line: |
151 |
file.readline()
|
152 |
calc_temp = read_aims_calculator(file)
|
153 |
break
|
154 |
if calc_temp is not None: |
155 |
self.float_params = calc_temp.float_params
|
156 |
self.exp_params = calc_temp.exp_params
|
157 |
self.string_params = calc_temp.string_params
|
158 |
self.int_params = calc_temp.int_params
|
159 |
self.bool_params = calc_temp.bool_params
|
160 |
self.list_params = calc_temp.list_params
|
161 |
self.input_parameters = calc_temp.input_parameters
|
162 |
else:
|
163 |
raise TypeError("Control file to be imported can not be read by ASE = " + fname) |
164 |
for key in kwargs: |
165 |
if self.float_params.has_key(key): |
166 |
self.float_params[key] = kwargs[key]
|
167 |
elif self.exp_params.has_key(key): |
168 |
self.exp_params[key] = kwargs[key]
|
169 |
elif self.string_params.has_key(key): |
170 |
self.string_params[key] = kwargs[key]
|
171 |
elif self.int_params.has_key(key): |
172 |
self.int_params[key] = kwargs[key]
|
173 |
elif self.bool_params.has_key(key): |
174 |
self.bool_params[key] = kwargs[key]
|
175 |
elif self.list_params.has_key(key): |
176 |
self.list_params[key] = kwargs[key]
|
177 |
elif self.input_parameters.has_key(key): |
178 |
self.input_parameters[key] = kwargs[key]
|
179 |
elif key is not 'control': |
180 |
raise TypeError('Parameter not defined: ' + key) |
181 |
|
182 |
def update(self, atoms): |
183 |
if self.calculation_required(atoms,[]): |
184 |
self.calculate(atoms)
|
185 |
|
186 |
def calculation_required(self, atoms,quantities): |
187 |
if (self.positions is None or |
188 |
(self.atoms != atoms) or |
189 |
(self.atoms != self.old_atoms) or |
190 |
(self.float_params != self.old_float_params) or |
191 |
(self.exp_params != self.old_exp_params) or |
192 |
(self.string_params != self.old_string_params) or |
193 |
(self.int_params != self.old_int_params) or |
194 |
(self.bool_params != self.old_bool_params) or |
195 |
(self.list_params != self.old_list_params) or |
196 |
(self.input_parameters != self.old_input_parameters)): |
197 |
return True |
198 |
else:
|
199 |
return False |
200 |
|
201 |
def calculate(self, atoms): |
202 |
"""Generate necessary files in the working directory.
|
203 |
|
204 |
If the directory does not exist it will be created.
|
205 |
|
206 |
"""
|
207 |
positions = atoms.get_positions() |
208 |
have_lattice_vectors = atoms.get_pbc().any() |
209 |
have_k_grid = self.list_params['k_grid'] |
210 |
if have_lattice_vectors and not have_k_grid: |
211 |
raise RuntimeError("Found lattice vectors but no k-grid!") |
212 |
if not have_lattice_vectors and have_k_grid: |
213 |
raise RuntimeError("Found k-grid but no lattice vectors!") |
214 |
from ase.io.aims import write_aims |
215 |
write_aims('geometry.in', atoms)
|
216 |
self.write_control()
|
217 |
self.write_species()
|
218 |
self.run()
|
219 |
self.converged = self.read_convergence() |
220 |
if not self.converged: |
221 |
os.system("tail -20 "+self.out) |
222 |
raise RuntimeError("FHI-aims did not converge!\n"+ |
223 |
"The last lines of output are printed above "+
|
224 |
"and should give an indication why.")
|
225 |
|
226 |
self.set_results(atoms)
|
227 |
|
228 |
def set_results(self,atoms): |
229 |
self.read(atoms)
|
230 |
self.old_float_params = self.float_params.copy() |
231 |
self.old_exp_params = self.exp_params.copy() |
232 |
self.old_string_params = self.string_params.copy() |
233 |
self.old_int_params = self.int_params.copy() |
234 |
self.old_input_parameters = self.input_parameters.copy() |
235 |
self.old_bool_params = self.bool_params.copy() |
236 |
self.old_list_params = self.list_params.copy() |
237 |
self.old_atoms = self.atoms.copy() |
238 |
|
239 |
def run(self): |
240 |
if self.input_parameters['track_output']: |
241 |
self.out = self.input_parameters['output_template']+str(self.run_counts)+'.out' |
242 |
self.run_counts += 1 |
243 |
else:
|
244 |
self.out = self.input_parameters['output_template']+'.out' |
245 |
if self.input_parameters['run_command']: |
246 |
aims_command = self.input_parameters['run_command'] |
247 |
elif os.environ.has_key('AIMS_COMMAND'): |
248 |
aims_command = os.environ['AIMS_COMMAND']
|
249 |
else:
|
250 |
raise RuntimeError("No specification for running FHI-aims. Aborting!") |
251 |
aims_command = aims_command + ' >> '
|
252 |
if self.input_parameters['run_dir']: |
253 |
aims_command = aims_command + self.input_parameters['run_dir'] + '/' |
254 |
aims_command = aims_command + self.out
|
255 |
self.write_parameters('#',self.out) |
256 |
exitcode = os.system(aims_command) |
257 |
if exitcode != 0: |
258 |
raise RuntimeError('FHI-aims exited with exit code: %d. ' % exitcode) |
259 |
if self.input_parameters['cubes'] and self.input_parameters['track_output']: |
260 |
self.input_parameters['cubes'].move_to_base_name(self.input_parameters['output_template']+str(self.run_counts-1)) |
261 |
|
262 |
def write_parameters(self,prefix,filename): |
263 |
output = open(filename,'w') |
264 |
output.write(prefix+'=======================================================\n')
|
265 |
output.write(prefix+'FHI-aims file: '+filename+'\n') |
266 |
output.write(prefix+'Created using the Atomic Simulation Environment (ASE)\n'+prefix+'\n') |
267 |
output.write(prefix+'List of parameters used to initialize the calculator:\n')
|
268 |
output.write(prefix+'=======================================================\n')
|
269 |
for key, val in self.float_params.items(): |
270 |
if val is not None: |
271 |
output.write('%-35s%5.6f\n' % (key, val))
|
272 |
for key, val in self.exp_params.items(): |
273 |
if val is not None: |
274 |
output.write('%-35s%5.2e\n' % (key, val))
|
275 |
for key, val in self.string_params.items(): |
276 |
if val is not None: |
277 |
output.write('%-35s%s\n' % (key, val))
|
278 |
for key, val in self.int_params.items(): |
279 |
if val is not None: |
280 |
output.write('%-35s%d\n' % (key, val))
|
281 |
for key, val in self.bool_params.items(): |
282 |
if val is not None: |
283 |
if key == 'vdw_correction_hirshfeld' and val: |
284 |
output.write('%-35s\n' % (key))
|
285 |
elif val:
|
286 |
output.write('%-35s.true.\n' % (key))
|
287 |
elif key != 'vdw_correction_hirshfeld': |
288 |
output.write('%-35s.false.\n' % (key))
|
289 |
for key, val in self.list_params.items(): |
290 |
if val is not None: |
291 |
if key == 'output': |
292 |
if not isinstance(val,(list,tuple)): |
293 |
val = [val] |
294 |
for output_type in val: |
295 |
output.write('%-35s%s\n' % (key,str(output_type))) |
296 |
else:
|
297 |
output.write('%-35s' % (key))
|
298 |
if isinstance(val,str): |
299 |
output.write(val) |
300 |
else:
|
301 |
for sub_value in val: |
302 |
output.write(str(sub_value)+' ') |
303 |
output.write('\n')
|
304 |
for key, val in self.input_parameters.items(): |
305 |
if key is 'cubes': |
306 |
if val:
|
307 |
val.write(output) |
308 |
elif val and val != input_parameters_default[key]: |
309 |
output.write(prefix+'%-34s%s\n' % (key,val))
|
310 |
output.write(prefix+'=======================================================\n\n')
|
311 |
output.close() |
312 |
|
313 |
def write_control(self, file = 'control.in'): |
314 |
"""Writes the control.in file."""
|
315 |
self.write_parameters('#',file) |
316 |
|
317 |
def write_species(self, file = 'control.in'): |
318 |
from ase.data import atomic_numbers |
319 |
|
320 |
if not self.input_parameters['species_dir']: |
321 |
raise RuntimeError('Missing species directory, THIS MUST BE SPECIFIED!') |
322 |
|
323 |
control = open(file, 'a') |
324 |
species_path = self.input_parameters['species_dir'] |
325 |
symbols = self.atoms.get_chemical_symbols()
|
326 |
symbols2 = [] |
327 |
for n, symbol in enumerate(symbols): |
328 |
if symbol not in symbols2: |
329 |
symbols2.append(symbol) |
330 |
for symbol in symbols2: |
331 |
fd = join(species_path, '%02i_%s_default' % (atomic_numbers[symbol], symbol))
|
332 |
for line in open(fd, 'r'): |
333 |
control.write(line) |
334 |
control.close() |
335 |
|
336 |
def get_dipole_moment(self, atoms): |
337 |
if self.list_params['output'] is None or 'dipole' not in self.list_params['output']: |
338 |
raise RuntimeError('output=[\'dipole\'] has to be set.') |
339 |
elif atoms.get_pbc().any():
|
340 |
raise RuntimeError('FHI-aims does not allow this for systems with periodic boundary conditions.') |
341 |
self.update(atoms)
|
342 |
return self.dipole |
343 |
|
344 |
def read_dipole(self): |
345 |
"""Method that reads the electric dipole moment from the output file."""
|
346 |
|
347 |
dipolemoment=np.zeros([1,3]) |
348 |
for line in open(self.out, 'r'): |
349 |
if line.rfind('Total dipole moment [eAng]') > -1: |
350 |
dipolemoment=np.array([float(f) for f in line.split()[6:10]]) |
351 |
return dipolemoment
|
352 |
|
353 |
def read_energy(self, all=None): |
354 |
for line in open(self.out, 'r'): |
355 |
if line.rfind('Total energy corrected') > -1: |
356 |
E0 = float(line.split()[-2]) |
357 |
elif line.rfind('Total energy uncorrected') > -1: |
358 |
F = float(line.split()[-2]) |
359 |
energy_free, energy_zero = F, E0 |
360 |
return [energy_free, energy_zero]
|
361 |
|
362 |
def read_forces(self, atoms, all=False): |
363 |
"""Method that reads forces from the output file.
|
364 |
|
365 |
If 'all' is switched on, the forces for all ionic steps
|
366 |
in the output file will be returned, in other case only the
|
367 |
forces for the last ionic configuration are returned."""
|
368 |
lines = open(self.out, 'r').readlines() |
369 |
forces = np.zeros([len(atoms), 3]) |
370 |
for n, line in enumerate(lines): |
371 |
if line.rfind('Total atomic forces') > -1: |
372 |
for iatom in range(len(atoms)): |
373 |
data = lines[n+iatom+1].split()
|
374 |
for iforce in range(3): |
375 |
forces[iatom, iforce] = float(data[2+iforce]) |
376 |
return forces
|
377 |
|
378 |
def get_stress(self, atoms): |
379 |
raise NotImplementedError('Stresses are not currently available in FHI-aims, sorry. ') |
380 |
|
381 |
# methods that should be quickly implemented some time, haven't had time yet:
|
382 |
def read_fermi(self): |
383 |
"""Method that reads Fermi energy from output file"""
|
384 |
return
|
385 |
|
386 |
def read_magnetic_moment(self): |
387 |
return
|
388 |
|
389 |
def read_convergence(self): |
390 |
converged = False
|
391 |
lines = open(self.out, 'r').readlines() |
392 |
for n, line in enumerate(lines): |
393 |
if line.rfind('Have a nice day') > -1: |
394 |
converged = True
|
395 |
return converged
|
396 |
|
397 |
def read_eigenvalues(self, kpt=0, spin=0): |
398 |
return
|
399 |
|
400 |
class AimsCube: |
401 |
""" object to ensure the output of cube files, can be attached to Aims object"""
|
402 |
def __init__(self,origin=(0,0,0), |
403 |
edges=[(0.1,0.0,0.0),(0.0,0.1,0.0),(0.0,0.0,0.1)], |
404 |
points=(50,50,50),plots=None): |
405 |
""" parameters:
|
406 |
origin, edges, points = same as in the FHI-aims output
|
407 |
plots: what to print, same names as in FHI-aims """
|
408 |
|
409 |
self.name = 'AimsCube' |
410 |
self.origin = origin
|
411 |
self.edges = edges
|
412 |
self.points = points
|
413 |
self.plots = plots
|
414 |
|
415 |
def ncubes(self): |
416 |
"""returns the number of cube files to output """
|
417 |
if self.plots: |
418 |
number = len(self.plots) |
419 |
else:
|
420 |
number = 0
|
421 |
return number
|
422 |
|
423 |
def set(self,**kwargs): |
424 |
""" set any of the parameters ... """
|
425 |
# NOT IMPLEMENTED AT THE MOMENT!
|
426 |
|
427 |
def move_to_base_name(self,basename): |
428 |
""" when output tracking is on or the base namem is not standard,
|
429 |
this routine will rename add the base to the cube file output for
|
430 |
easier tracking """
|
431 |
for plot in self.plots: |
432 |
found = False
|
433 |
cube = plot.split() |
434 |
if cube[0] == 'total_density' or cube[0] == 'spin_density' or cube[0] == 'delta_density': |
435 |
found = True
|
436 |
old_name = cube[0]+'.cube' |
437 |
new_name = basename+'.'+old_name
|
438 |
if cube[0] == 'eigenstate' or cube[0] == 'eigenstate_density': |
439 |
found = True
|
440 |
state = int(cube[1]) |
441 |
s_state = cube[1]
|
442 |
for i in [10,100,1000,10000]: |
443 |
if state < i:
|
444 |
s_state = '0'+s_state
|
445 |
old_name = cube[0]+'_'+s_state+'_spin_1.cube' |
446 |
new_name = basename+'.'+old_name
|
447 |
if found:
|
448 |
os.system("mv "+old_name+" "+new_name) |
449 |
|
450 |
def add_plot(self,name): |
451 |
""" in case you forgot one ... """
|
452 |
plots += [name] |
453 |
|
454 |
def write(self,file): |
455 |
""" write the necessary output to the already opened control.in """
|
456 |
file.write('output cube '+self.plots[0]+'\n') |
457 |
file.write(' cube origin ') |
458 |
for ival in self.origin: |
459 |
file.write(str(ival)+' ') |
460 |
file.write('\n') |
461 |
for i in range(3): |
462 |
file.write(' cube edge '+str(self.points[i])+' ') |
463 |
for ival in self.edges[i]: |
464 |
file.write(str(ival)+' ') |
465 |
file.write('\n') |
466 |
if self.ncubes() > 1: |
467 |
for i in range(self.ncubes()-1): |
468 |
file.write('output cube '+self.plots[i+1]+'\n') |
469 |
|
470 |
|
471 |
|