Coverage for /builds/kinetik161/ase/ase/md/nvtberendsen.py: 83.02%
53 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"""Berendsen NVT dynamics class."""
2from typing import IO, Optional, Union
4import numpy as np
6from ase import Atoms
7from ase.md.md import MolecularDynamics
8from ase.parallel import world
11class NVTBerendsen(MolecularDynamics):
12 def __init__(
13 self,
14 atoms: Atoms,
15 timestep: float,
16 temperature: Optional[float] = None,
17 taut: Optional[float] = None,
18 fixcm: bool = True,
19 *,
20 temperature_K: Optional[float] = None,
21 trajectory: Optional[str] = None,
22 logfile: Optional[Union[IO, str]] = None,
23 loginterval: int = 1,
24 communicator=world,
25 append_trajectory: bool = False,
26 ):
27 """Berendsen (constant N, V, T) molecular dynamics.
29 Parameters:
31 atoms: Atoms object
32 The list of atoms.
34 timestep: float
35 The time step in ASE time units.
37 temperature: float
38 The desired temperature, in Kelvin.
40 temperature_K: float
41 Alias for *temperature*
43 taut: float
44 Time constant for Berendsen temperature coupling in ASE
45 time units.
47 fixcm: bool (optional)
48 If True, the position and momentum of the center of mass is
49 kept unperturbed. Default: True.
51 trajectory: Trajectory object or str (optional)
52 Attach trajectory object. If *trajectory* is a string a
53 Trajectory will be constructed. Use *None* for no
54 trajectory.
56 logfile: file object or str (optional)
57 If *logfile* is a string, a file with that name will be opened.
58 Use '-' for stdout.
60 loginterval: int (optional)
61 Only write a log line for every *loginterval* time steps.
62 Default: 1
64 append_trajectory: boolean (optional)
65 Defaults to False, which causes the trajectory file to be
66 overwriten each time the dynamics is restarted from scratch.
67 If True, the new structures are appended to the trajectory
68 file instead.
70 """
72 MolecularDynamics.__init__(self, atoms, timestep, trajectory,
73 logfile, loginterval,
74 append_trajectory=append_trajectory)
75 if taut is None:
76 raise TypeError("Missing 'taut' argument.")
77 self.taut = taut
78 self.temperature = self._process_temperature(temperature,
79 temperature_K, 'K')
81 self.fix_com = fixcm # will the center of mass be held fixed?
82 self.communicator = communicator
84 def set_taut(self, taut):
85 self.taut = taut
87 def get_taut(self):
88 return self.taut
90 def set_temperature(self, temperature=None, *, temperature_K=None):
91 self.temperature = self._process_temperature(temperature,
92 temperature_K, 'K')
94 def get_temperature(self):
95 return self.temperature
97 def set_timestep(self, timestep):
98 self.dt = timestep
100 def get_timestep(self):
101 return self.dt
103 def scale_velocities(self):
104 """ Do the NVT Berendsen velocity scaling """
105 tautscl = self.dt / self.taut
106 old_temperature = self.atoms.get_temperature()
108 scl_temperature = np.sqrt(1.0 +
109 (self.temperature / old_temperature - 1.0) *
110 tautscl)
111 # Limit the velocity scaling to reasonable values
112 if scl_temperature > 1.1:
113 scl_temperature = 1.1
114 if scl_temperature < 0.9:
115 scl_temperature = 0.9
117 p = self.atoms.get_momenta()
118 p = scl_temperature * p
119 self.atoms.set_momenta(p)
120 return
122 def step(self, forces=None):
123 """Move one timestep forward using Berenden NVT molecular dynamics."""
124 self.scale_velocities()
126 # one step velocity verlet
127 atoms = self.atoms
129 if forces is None:
130 forces = atoms.get_forces(md=True)
132 p = self.atoms.get_momenta()
133 p += 0.5 * self.dt * forces
135 if self.fix_com:
136 # calculate the center of mass
137 # momentum and subtract it
138 psum = p.sum(axis=0) / float(len(p))
139 p = p - psum
141 self.atoms.set_positions(
142 self.atoms.get_positions() +
143 self.dt * p / self.atoms.get_masses()[:, np.newaxis])
145 # We need to store the momenta on the atoms before calculating
146 # the forces, as in a parallel Asap calculation atoms may
147 # migrate during force calculations, and the momenta need to
148 # migrate along with the atoms. For the same reason, we
149 # cannot use self.masses in the line above.
151 self.atoms.set_momenta(p)
152 forces = self.atoms.get_forces(md=True)
153 atoms.set_momenta(self.atoms.get_momenta() + 0.5 * self.dt * forces)
155 return forces