Coverage for /builds/kinetik161/ase/ase/md/logger.py: 76.36%
55 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1"""Logging for molecular dynamics."""
2import weakref
3from typing import IO, Any, Union
5from ase import Atoms, units
6from ase.parallel import world
7from ase.utils import IOContext
10class MDLogger(IOContext):
11 """Class for logging molecular dynamics simulations.
13 Parameters:
14 dyn: The dynamics. Only a weak reference is kept.
16 atoms: The atoms.
18 logfile: File name or open file, "-" meaning standard output.
20 stress=False: Include stress in log.
22 peratom=False: Write energies per atom.
24 mode="a": How the file is opened if logfile is a filename.
25 """
27 def __init__(
28 self,
29 dyn: Any, # not fully annotated so far to avoid a circular import
30 atoms: Atoms,
31 logfile: Union[IO, str],
32 header: bool = True,
33 stress: bool = False,
34 peratom: bool = False,
35 mode: str = "a",
36 ):
37 self.dyn = weakref.proxy(dyn) if hasattr(dyn, "get_time") else None
38 self.atoms = atoms
39 global_natoms = atoms.get_global_number_of_atoms()
40 self.logfile = self.openfile(logfile, comm=world, mode=mode)
41 self.stress = stress
42 self.peratom = peratom
43 if self.dyn is not None:
44 self.hdr = "%-9s " % ("Time[ps]",)
45 self.fmt = "%-10.4f "
46 else:
47 self.hdr = ""
48 self.fmt = ""
49 if self.peratom:
50 self.hdr += "%12s %12s %12s %6s" % ("Etot/N[eV]", "Epot/N[eV]",
51 "Ekin/N[eV]", "T[K]")
52 self.fmt += "%12.4f %12.4f %12.4f %6.1f"
53 else:
54 self.hdr += "%12s %12s %12s %6s" % ("Etot[eV]", "Epot[eV]",
55 "Ekin[eV]", "T[K]")
56 # Choose a sensible number of decimals
57 if global_natoms <= 100:
58 digits = 4
59 elif global_natoms <= 1000:
60 digits = 3
61 elif global_natoms <= 10000:
62 digits = 2
63 else:
64 digits = 1
65 self.fmt += 3 * ("%%12.%df " % (digits,)) + " %6.1f"
66 if self.stress:
67 self.hdr += (' ---------------------- stress [GPa] '
68 '-----------------------')
69 self.fmt += 6 * " %10.3f"
70 self.fmt += "\n"
71 if header:
72 self.logfile.write(self.hdr + "\n")
74 def __del__(self):
75 self.close()
77 def __call__(self):
78 epot = self.atoms.get_potential_energy()
79 ekin = self.atoms.get_kinetic_energy()
80 temp = self.atoms.get_temperature()
81 global_natoms = self.atoms.get_global_number_of_atoms()
82 if self.peratom:
83 epot /= global_natoms
84 ekin /= global_natoms
85 if self.dyn is not None:
86 t = self.dyn.get_time() / (1000 * units.fs)
87 dat = (t,)
88 else:
89 dat = ()
90 dat += (epot + ekin, epot, ekin, temp)
91 if self.stress:
92 dat += tuple(self.atoms.get_stress(
93 include_ideal_gas=True) / units.GPa)
94 self.logfile.write(self.fmt % dat)
95 self.logfile.flush()