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

1"""Logging for molecular dynamics.""" 

2import weakref 

3from typing import IO, Any, Union 

4 

5from ase import Atoms, units 

6from ase.parallel import world 

7from ase.utils import IOContext 

8 

9 

10class MDLogger(IOContext): 

11 """Class for logging molecular dynamics simulations. 

12 

13 Parameters: 

14 dyn: The dynamics. Only a weak reference is kept. 

15 

16 atoms: The atoms. 

17 

18 logfile: File name or open file, "-" meaning standard output. 

19 

20 stress=False: Include stress in log. 

21 

22 peratom=False: Write energies per atom. 

23 

24 mode="a": How the file is opened if logfile is a filename. 

25 """ 

26 

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") 

73 

74 def __del__(self): 

75 self.close() 

76 

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()