Coverage for /builds/debichem-team/python-ase/ase/md/logger.py: 76.36%
55 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +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 comm=world,
37 ):
38 self.dyn = weakref.proxy(dyn) if hasattr(dyn, "get_time") else None
39 self.atoms = atoms
40 global_natoms = atoms.get_global_number_of_atoms()
41 self.logfile = self.openfile(file=logfile, mode=mode, comm=comm)
42 self.stress = stress
43 self.peratom = peratom
44 if self.dyn is not None:
45 self.hdr = "%-9s " % ("Time[ps]",)
46 self.fmt = "%-10.4f "
47 else:
48 self.hdr = ""
49 self.fmt = ""
50 if self.peratom:
51 self.hdr += "%12s %12s %12s %6s" % ("Etot/N[eV]", "Epot/N[eV]",
52 "Ekin/N[eV]", "T[K]")
53 self.fmt += "%12.4f %12.4f %12.4f %6.1f"
54 else:
55 self.hdr += "%12s %12s %12s %6s" % ("Etot[eV]", "Epot[eV]",
56 "Ekin[eV]", "T[K]")
57 # Choose a sensible number of decimals
58 if global_natoms <= 100:
59 digits = 4
60 elif global_natoms <= 1000:
61 digits = 3
62 elif global_natoms <= 10000:
63 digits = 2
64 else:
65 digits = 1
66 self.fmt += 3 * ("%%12.%df " % (digits,)) + " %6.1f"
67 if self.stress:
68 self.hdr += (' ---------------------- stress [GPa] '
69 '-----------------------')
70 self.fmt += 6 * " %10.3f"
71 self.fmt += "\n"
72 if header:
73 self.logfile.write(self.hdr + "\n")
75 def __del__(self):
76 self.close()
78 def __call__(self):
79 epot = self.atoms.get_potential_energy()
80 ekin = self.atoms.get_kinetic_energy()
81 temp = self.atoms.get_temperature()
82 global_natoms = self.atoms.get_global_number_of_atoms()
83 if self.peratom:
84 epot /= global_natoms
85 ekin /= global_natoms
86 if self.dyn is not None:
87 t = self.dyn.get_time() / (1000 * units.fs)
88 dat = (t,)
89 else:
90 dat = ()
91 dat += (epot + ekin, epot, ekin, temp)
92 if self.stress:
93 dat += tuple(self.atoms.get_stress(
94 include_ideal_gas=True) / units.GPa)
95 self.logfile.write(self.fmt % dat)
96 self.logfile.flush()