Source code for ase.md.logger

"""Logging for molecular dynamics."""

import weakref
from typing import IO, Any

from ase import Atoms, units
from ase.parallel import world
from ase.utils import IOContext


[docs] class MDLogger(IOContext): """Class for logging molecular dynamics simulations. Parameters ---------- dyn : :class:`~ase.md.md.MolecularDynamics` ASE :class:`~ase.md.md.MolecularDynamics` object. Only a weak reference is kept. atoms : :class:`~ase.Atoms` ASE :class:`~ase.Atoms` object. logfile : IO | str File name or open file, "-" meaning standard output. stress : bool, default: :obj:`False` If :obj:`True`, the logger writes stress. peratom : bool, default: :obj:`False` If :obj:`True`, the logger writes energies per atom. mode : str, default: ``'a'`` How the file is opened if logfile is a filename. """ def __init__( self, dyn: Any, # not fully annotated so far to avoid a circular import atoms: Atoms, logfile: IO | str, header: bool = True, stress: bool = False, peratom: bool = False, mode: str = 'a', comm=world, ): self.dyn = weakref.proxy(dyn) if hasattr(dyn, 'get_time') else None self.atoms = atoms global_natoms = atoms.get_global_number_of_atoms() self.logfile = self.openfile(file=logfile, mode=mode, comm=comm) self.stress = stress self.peratom = peratom if self.dyn is not None: self.hdr = '%-9s ' % ('Time[ps]',) self.fmt = '%-10.4f ' else: self.hdr = '' self.fmt = '' if self.peratom: self.hdr += '%12s %12s %12s %6s' % ( 'Etot/N[eV]', 'Epot/N[eV]', 'Ekin/N[eV]', 'T[K]', ) self.fmt += '%12.4f %12.4f %12.4f %6.1f' else: self.hdr += '%12s %12s %12s %6s' % ( 'Etot[eV]', 'Epot[eV]', 'Ekin[eV]', 'T[K]', ) # Choose a sensible number of decimals if global_natoms <= 100: digits = 4 elif global_natoms <= 1000: digits = 3 elif global_natoms <= 10000: digits = 2 else: digits = 1 self.fmt += 3 * ('%%12.%df ' % (digits,)) + ' %6.1f' if self.stress: self.hdr += ( ' ---------------------- stress [GPa] ' '-----------------------' ) self.fmt += 6 * ' %10.3f' self.fmt += '\n' if header: self.logfile.write(self.hdr + '\n') def __del__(self): self.close() def __call__(self): epot = self._potential_energy() ekin = self.atoms.get_kinetic_energy() temp = self.atoms.get_temperature() global_natoms = self.atoms.get_global_number_of_atoms() if self.peratom: epot /= global_natoms ekin /= global_natoms if self.dyn is not None: t = self.dyn.get_time() / (1000 * units.fs) dat = (t,) else: dat = () dat += (epot + ekin, epot, ekin, temp) if self.stress: dat += tuple(self._stress() / units.GPa) self.logfile.write(self.fmt % dat) self.logfile.flush() def _potential_energy(self): return self.atoms.get_potential_energy() def _stress(self): return self.atoms.get_stress(include_ideal_gas=True)