Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import time
2import warnings
4from ase.units import Ang, fs
5from ase.utils import reader, writer
8v_unit = Ang / (1000.0 * fs)
11@reader
12def read_aims(fd, apply_constraints=True):
13 """Import FHI-aims geometry type files.
15 Reads unitcell, atom positions and constraints from
16 a geometry.in file.
18 If geometric constraint (symmetry parameters) are in the file
19 include that information in atoms.info["symmetry_block"]
20 """
22 lines = fd.readlines()
23 return parse_geometry_lines(lines, apply_constraints=apply_constraints)
26def parse_geometry_lines(lines, apply_constraints=True):
28 from ase import Atoms
29 from ase.constraints import (
30 FixAtoms,
31 FixCartesian,
32 FixScaledParametricRelations,
33 FixCartesianParametricRelations,
34 )
35 import numpy as np
37 atoms = Atoms()
39 positions = []
40 cell = []
41 symbols = []
42 velocities = []
43 magmoms = []
44 symmetry_block = []
45 charges = []
46 fix = []
47 fix_cart = []
48 xyz = np.array([0, 0, 0])
49 i = -1
50 n_periodic = -1
51 periodic = np.array([False, False, False])
52 cart_positions, scaled_positions = False, False
53 for line in lines:
54 inp = line.split()
55 if inp == []:
56 continue
57 if inp[0] in ["atom", "atom_frac"]:
59 if inp[0] == "atom":
60 cart_positions = True
61 else:
62 scaled_positions = True
64 if xyz.all():
65 fix.append(i)
66 elif xyz.any():
67 fix_cart.append(FixCartesian(i, xyz))
68 floatvect = float(inp[1]), float(inp[2]), float(inp[3])
69 positions.append(floatvect)
70 symbols.append(inp[4])
71 magmoms.append(0.0)
72 charges.append(0.0)
73 xyz = np.array([0, 0, 0])
74 i += 1
76 elif inp[0] == "lattice_vector":
77 floatvect = float(inp[1]), float(inp[2]), float(inp[3])
78 cell.append(floatvect)
79 n_periodic = n_periodic + 1
80 periodic[n_periodic] = True
82 elif inp[0] == "initial_moment":
83 magmoms[-1] = float(inp[1])
85 elif inp[0] == "initial_charge":
86 charges[-1] = float(inp[1])
88 elif inp[0] == "constrain_relaxation":
89 if inp[1] == ".true.":
90 fix.append(i)
91 elif inp[1] == "x":
92 xyz[0] = 1
93 elif inp[1] == "y":
94 xyz[1] = 1
95 elif inp[1] == "z":
96 xyz[2] = 1
98 elif inp[0] == "velocity":
99 floatvect = [v_unit * float(l) for l in inp[1:4]]
100 velocities.append(floatvect)
102 elif inp[0] in [
103 "symmetry_n_params",
104 "symmetry_params",
105 "symmetry_lv",
106 "symmetry_frac",
107 ]:
108 symmetry_block.append(" ".join(inp))
110 if xyz.all():
111 fix.append(i)
112 elif xyz.any():
113 fix_cart.append(FixCartesian(i, xyz))
115 if cart_positions and scaled_positions:
116 raise Exception(
117 "Can't specify atom positions with mixture of "
118 "Cartesian and fractional coordinates"
119 )
120 elif scaled_positions and periodic.any():
121 atoms = Atoms(
122 symbols, scaled_positions=positions, cell=cell, pbc=periodic
123 )
124 else:
125 atoms = Atoms(symbols, positions)
127 if len(velocities) > 0:
128 if len(velocities) != len(positions):
129 raise Exception(
130 "Number of positions and velocities have to coincide."
131 )
132 atoms.set_velocities(velocities)
134 fix_params = []
136 if len(symmetry_block) > 5:
137 params = symmetry_block[1].split()[1:]
139 lattice_expressions = []
140 lattice_params = []
142 atomic_expressions = []
143 atomic_params = []
145 n_lat_param = int(symmetry_block[0].split(" ")[2])
147 lattice_params = params[:n_lat_param]
148 atomic_params = params[n_lat_param:]
150 for ll, line in enumerate(symmetry_block[2:]):
151 expression = " ".join(line.split(" ")[1:])
152 if ll < 3:
153 lattice_expressions += expression.split(",")
154 else:
155 atomic_expressions += expression.split(",")
157 fix_params.append(
158 FixCartesianParametricRelations.from_expressions(
159 list(range(3)),
160 lattice_params,
161 lattice_expressions,
162 use_cell=True,
163 )
164 )
166 fix_params.append(
167 FixScaledParametricRelations.from_expressions(
168 list(range(len(atoms))), atomic_params, atomic_expressions
169 )
170 )
172 if any(magmoms):
173 atoms.set_initial_magnetic_moments(magmoms)
174 if any(charges):
175 atoms.set_initial_charges(charges)
177 if periodic.any():
178 atoms.set_cell(cell)
179 atoms.set_pbc(periodic)
180 if len(fix):
181 atoms.set_constraint([FixAtoms(indices=fix)] + fix_cart + fix_params)
182 else:
183 atoms.set_constraint(fix_cart + fix_params)
185 if fix_params and apply_constraints:
186 atoms.set_positions(atoms.get_positions())
187 return atoms
190@writer
191def write_aims(
192 fd,
193 atoms,
194 scaled=False,
195 geo_constrain=False,
196 velocities=False,
197 ghosts=None,
198 info_str=None,
199 wrap=False,
200):
201 """Method to write FHI-aims geometry files.
203 Writes the atoms positions and constraints (only FixAtoms is
204 supported at the moment).
206 Args:
207 fd: file object
208 File to output structure to
209 atoms: ase.atoms.Atoms
210 structure to output to the file
211 scaled: bool
212 If True use fractional coordinates instead of Cartesian coordinates
213 symmetry_block: list of str
214 List of geometric constraints as defined in:
215 https://arxiv.org/abs/1908.01610
216 velocities: bool
217 If True add the atomic velocity vectors to the file
218 ghosts: list of Atoms
219 A list of ghost atoms for the system
220 info_str: str
221 A string to be added to the header of the file
222 wrap: bool
223 Wrap atom positions to cell before writing
224 """
226 from ase.constraints import FixAtoms, FixCartesian
228 import numpy as np
230 if geo_constrain:
231 if not scaled:
232 warnings.warn(
233 "Setting scaled to True because a symmetry_block is detected."
234 )
235 scaled = True
237 fd.write("#=======================================================\n")
238 if hasattr(fd, 'name'):
239 fd.write("# FHI-aims file: " + fd.name + "\n")
240 fd.write("# Created using the Atomic Simulation Environment (ASE)\n")
241 fd.write("# " + time.asctime() + "\n")
243 # If writing additional information is requested via info_str:
244 if info_str is not None:
245 fd.write("\n# Additional information:\n")
246 if isinstance(info_str, list):
247 fd.write("\n".join(["# {}".format(s) for s in info_str]))
248 else:
249 fd.write("# {}".format(info_str))
250 fd.write("\n")
252 fd.write("#=======================================================\n")
254 i = 0
255 if atoms.get_pbc().any():
256 for n, vector in enumerate(atoms.get_cell()):
257 fd.write("lattice_vector ")
258 for i in range(3):
259 fd.write("%16.16f " % vector[i])
260 fd.write("\n")
261 fix_cart = np.zeros([len(atoms), 3])
263 # else aims crashes anyways
264 # better be more explicit
265 # write_magmoms = np.any([a.magmom for a in atoms])
267 if atoms.constraints:
268 for constr in atoms.constraints:
269 if isinstance(constr, FixAtoms):
270 fix_cart[constr.index] = [1, 1, 1]
271 elif isinstance(constr, FixCartesian):
272 fix_cart[constr.a] = -constr.mask + 1
274 if ghosts is None:
275 ghosts = np.zeros(len(atoms))
276 else:
277 assert len(ghosts) == len(atoms)
279 if geo_constrain:
280 wrap = False
281 scaled_positions = atoms.get_scaled_positions(wrap=wrap)
283 for i, atom in enumerate(atoms):
284 if ghosts[i] == 1:
285 atomstring = "empty "
286 elif scaled:
287 atomstring = "atom_frac "
288 else:
289 atomstring = "atom "
290 fd.write(atomstring)
291 if scaled:
292 for pos in scaled_positions[i]:
293 fd.write("%16.16f " % pos)
294 else:
295 for pos in atom.position:
296 fd.write("%16.16f " % pos)
297 fd.write(atom.symbol)
298 fd.write("\n")
299 # (1) all coords are constrained:
300 if fix_cart[i].all():
301 fd.write(" constrain_relaxation .true.\n")
302 # (2) some coords are constrained:
303 elif fix_cart[i].any():
304 xyz = fix_cart[i]
305 for n in range(3):
306 if xyz[n]:
307 fd.write(" constrain_relaxation %s\n" % "xyz"[n])
308 if atom.charge:
309 fd.write(" initial_charge %16.6f\n" % atom.charge)
310 if atom.magmom:
311 fd.write(" initial_moment %16.6f\n" % atom.magmom)
313 # Write velocities if this is wanted
314 if velocities and atoms.get_velocities() is not None:
315 fd.write(
316 " velocity {:.16f} {:.16f} {:.16f}\n".format(
317 *atoms.get_velocities()[i] / v_unit
318 )
319 )
321 if geo_constrain:
322 for line in get_sym_block(atoms):
323 fd.write(line)
326def get_sym_block(atoms):
327 """Get symmetry block for Parametric constraints in atoms.constraints"""
328 import numpy as np
330 from ase.constraints import (
331 FixScaledParametricRelations,
332 FixCartesianParametricRelations,
333 )
335 # Initialize param/expressions lists
336 atomic_sym_params = []
337 lv_sym_params = []
338 atomic_param_constr = np.zeros((len(atoms),), dtype="<U100")
339 lv_param_constr = np.zeros((3,), dtype="<U100")
341 # Populate param/expressions list
342 for constr in atoms.constraints:
343 if isinstance(constr, FixScaledParametricRelations):
344 atomic_sym_params += constr.params
346 if np.any(atomic_param_constr[constr.indices] != ""):
347 warnings.warn(
348 "multiple parametric constraints defined for the same "
349 "atom, using the last one defined"
350 )
352 atomic_param_constr[constr.indices] = [
353 ", ".join(expression) for expression in constr.expressions
354 ]
355 elif isinstance(constr, FixCartesianParametricRelations):
356 lv_sym_params += constr.params
358 if np.any(lv_param_constr[constr.indices] != ""):
359 warnings.warn(
360 "multiple parametric constraints defined for the same "
361 "lattice vector, using the last one defined"
362 )
364 lv_param_constr[constr.indices] = [
365 ", ".join(expression) for expression in constr.expressions
366 ]
368 if np.all(atomic_param_constr == "") and np.all(lv_param_constr == ""):
369 return []
371 # Check Constraint Parameters
372 if len(atomic_sym_params) != len(np.unique(atomic_sym_params)):
373 warnings.warn(
374 "Some parameters were used across constraints, they will be "
375 "combined in the aims calculations"
376 )
377 atomic_sym_params = np.unique(atomic_sym_params)
379 if len(lv_sym_params) != len(np.unique(lv_sym_params)):
380 warnings.warn(
381 "Some parameters were used across constraints, they will be "
382 "combined in the aims calculations"
383 )
384 lv_sym_params = np.unique(lv_sym_params)
386 if np.any(atomic_param_constr == ""):
387 raise IOError(
388 "FHI-aims input files require all atoms have defined parametric "
389 "constraints"
390 )
392 cell_inds = np.where(lv_param_constr == "")[0]
393 for ind in cell_inds:
394 lv_param_constr[ind] = "{:.16f}, {:.16f}, {:.16f}".format(
395 *atoms.cell[ind]
396 )
398 n_atomic_params = len(atomic_sym_params)
399 n_lv_params = len(lv_sym_params)
400 n_total_params = n_atomic_params + n_lv_params
402 sym_block = []
403 if n_total_params > 0:
404 sym_block.append("#" + "="*55 + "\n")
405 sym_block.append("# Parametric constraints\n")
406 sym_block.append("#" + "="*55 + "\n")
407 sym_block.append(
408 "symmetry_n_params {:d} {:d} {:d}\n".format(
409 n_total_params, n_lv_params, n_atomic_params
410 )
411 )
412 sym_block.append(
413 "symmetry_params %s\n"
414 % " ".join(lv_sym_params + atomic_sym_params)
415 )
417 for constr in lv_param_constr:
418 sym_block.append("symmetry_lv {:s}\n".format(constr))
420 for constr in atomic_param_constr:
421 sym_block.append("symmetry_frac {:s}\n".format(constr))
422 return sym_block
425def _parse_atoms(fd, n_atoms, molecular_dynamics=False):
426 """parse structure information from aims output to Atoms object"""
427 from ase import Atoms, Atom
429 next(fd)
430 atoms = Atoms()
431 for i in range(n_atoms):
432 inp = next(fd).split()
433 if "lattice_vector" in inp[0]:
434 cell = []
435 for i in range(3):
436 cell += [[float(inp[1]), float(inp[2]), float(inp[3])]]
437 inp = next(fd).split()
438 atoms.set_cell(cell)
439 inp = next(fd).split()
440 atoms.append(Atom(inp[4], (inp[1], inp[2], inp[3])))
441 if molecular_dynamics:
442 inp = next(fd).split()
444 return atoms
447@reader
448def read_aims_output(fd, index=-1):
449 """Import FHI-aims output files with all data available, i.e.
450 relaxations, MD information, force information etc etc etc."""
451 from ase import Atoms, Atom
452 from ase.calculators.singlepoint import SinglePointCalculator
453 from ase.constraints import FixAtoms, FixCartesian
455 molecular_dynamics = False
456 cell = []
457 images = []
458 fix = []
459 fix_cart = []
460 f = None
461 pbc = False
462 found_aims_calculator = False
463 stress = None
464 for line in fd:
465 # if "List of parameters used to initialize the calculator:" in line:
466 # next(fd)
467 # calc = read_aims_calculator(fd)
468 # calc.out = filename
469 # found_aims_calculator = True
470 if "| Number of atoms :" in line:
471 inp = line.split()
472 n_atoms = int(inp[5])
473 if "| Unit cell:" in line:
474 if not pbc:
475 pbc = True
476 for i in range(3):
477 inp = next(fd).split()
478 cell.append([inp[1], inp[2], inp[3]])
479 if "Found relaxation constraint for atom" in line:
480 xyz = [0, 0, 0]
481 ind = int(line.split()[5][:-1]) - 1
482 if "All coordinates fixed" in line:
483 if ind not in fix:
484 fix.append(ind)
485 if "coordinate fixed" in line:
486 coord = line.split()[6]
487 if coord == "x":
488 xyz[0] = 1
489 elif coord == "y":
490 xyz[1] = 1
491 elif coord == "z":
492 xyz[2] = 1
493 keep = True
494 for n, c in enumerate(fix_cart):
495 if ind == c.a:
496 keep = False
497 if keep:
498 fix_cart.append(FixCartesian(ind, xyz))
499 else:
500 fix_cart[n].mask[xyz.index(1)] = 0
502 if "Atomic structure:" in line and not molecular_dynamics:
503 next(fd)
504 atoms = Atoms()
505 for _ in range(n_atoms):
506 inp = next(fd).split()
507 atoms.append(Atom(inp[3], (inp[4], inp[5], inp[6])))
509 if "Complete information for previous time-step:" in line:
510 molecular_dynamics = True
512 if "Updated atomic structure:" in line and not molecular_dynamics:
513 atoms = _parse_atoms(fd, n_atoms=n_atoms)
514 elif "Atomic structure (and velocities)" in line:
515 next(fd)
516 atoms = Atoms()
517 velocities = []
518 for i in range(n_atoms):
519 inp = next(fd).split()
520 atoms.append(Atom(inp[4], (inp[1], inp[2], inp[3])))
521 inp = next(fd).split()
522 floatvect = [v_unit * float(l) for l in inp[1:4]]
523 velocities.append(floatvect)
524 atoms.set_velocities(velocities)
525 if len(fix):
526 atoms.set_constraint([FixAtoms(indices=fix)] + fix_cart)
527 else:
528 atoms.set_constraint(fix_cart)
529 images.append(atoms)
531 # if we enter here, the SocketIO/PIMD Wrapper was used
532 elif (
533 "Atomic structure that "
534 "was used in the preceding time step of the wrapper"
535 ) in line:
536 # parse atoms and add calculator information, i.e., the energies
537 # and forces that were already collected
538 atoms = _parse_atoms(fd, n_atoms=n_atoms)
539 results = images[-1].calc.results
540 atoms.calc = SinglePointCalculator(atoms, **results)
542 # replace last image with updated atoms
543 images[-1] = atoms
545 # make sure `atoms` does not point to `images[-1` later on
546 atoms = atoms.copy()
548 # FlK: add analytical stress and replace stress=None
549 if "Analytical stress tensor - Symmetrized" in line:
550 # scroll to significant lines
551 for _ in range(4):
552 next(fd)
553 stress = []
554 for _ in range(3):
555 inp = next(fd)
556 stress.append([float(i) for i in inp.split()[2:5]])
558 if "Total atomic forces" in line:
559 f = []
560 for i in range(n_atoms):
561 inp = next(fd).split()
562 # FlK: use inp[-3:] instead of inp[1:4] to make sure this works
563 # when atom number is not preceded by a space.
564 f.append([float(i) for i in inp[-3:]])
565 if not found_aims_calculator:
566 e = images[-1].get_potential_energy()
567 # FlK: Add the stress if it has been computed
568 if stress is None:
569 calc = SinglePointCalculator(atoms, energy=e, forces=f)
570 else:
571 calc = SinglePointCalculator(
572 atoms, energy=e, forces=f, stress=stress
573 )
574 images[-1].calc = calc
575 e = None
576 f = None
578 if "Total energy corrected" in line:
579 e = float(line.split()[5])
580 if pbc:
581 atoms.set_cell(cell)
582 atoms.pbc = True
583 if not found_aims_calculator:
584 atoms.calc = SinglePointCalculator(atoms, energy=e)
585 if not molecular_dynamics:
586 if len(fix):
587 atoms.set_constraint([FixAtoms(indices=fix)] + fix_cart)
588 else:
589 atoms.set_constraint(fix_cart)
590 images.append(atoms)
591 e = None
592 # if found_aims_calculator:
593 # calc.set_results(images[-1])
594 # images[-1].calc = calc
596 # FlK: add stress per atom
597 if "Per atom stress (eV) used for heat flux calculation" in line:
598 # scroll to boundary
599 next(l for l in fd if "-------------" in l)
601 stresses = []
602 for l in [next(fd) for _ in range(n_atoms)]:
603 # Read stresses
604 xx, yy, zz, xy, xz, yz = [float(d) for d in l.split()[2:8]]
605 stresses.append([[xx, xy, xz], [xy, yy, yz], [xz, yz, zz]])
607 if not found_aims_calculator:
608 e = images[-1].get_potential_energy()
609 f = images[-1].get_forces()
610 stress = images[-1].get_stress(voigt=False)
612 calc = SinglePointCalculator(
613 atoms, energy=e, forces=f, stress=stress, stresses=stresses
614 )
615 images[-1].calc = calc
617 fd.close()
618 if molecular_dynamics:
619 images = images[1:]
621 # return requested images, code borrowed from ase/io/trajectory.py
622 if isinstance(index, int):
623 return images[index]
624 else:
625 step = index.step or 1
626 if step > 0:
627 start = index.start or 0
628 if start < 0:
629 start += len(images)
630 stop = index.stop or len(images)
631 if stop < 0:
632 stop += len(images)
633 else:
634 if index.start is None:
635 start = len(images) - 1
636 else:
637 start = index.start
638 if start < 0:
639 start += len(images)
640 if index.stop is None:
641 stop = -1
642 else:
643 stop = index.stop
644 if stop < 0:
645 stop += len(images)
646 return [images[i] for i in range(start, stop, step)]