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