Coverage for /builds/debichem-team/python-ase/ase/md/bussi.py: 100.00%
31 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"""Bussi NVT dynamics class."""
3import math
5import numpy as np
7from ase import units
8from ase.md.verlet import VelocityVerlet
11class Bussi(VelocityVerlet):
12 """Bussi stochastic velocity rescaling (NVT) molecular dynamics.
13 Based on the paper from Bussi et al. (https://arxiv.org/abs/0803.4060)
15 Parameters
16 ----------
17 atoms : Atoms
18 The atoms object.
19 timestep : float
20 The time step in ASE time units.
21 temperature_K : float
22 The desired temperature, in Kelvin.
23 taut : float
24 Time constant for Bussi temperature coupling in ASE time units.
25 rng : numpy.random, optional
26 Random number generator.
27 **md_kwargs : dict, optional
28 Additional arguments passed to :class:~ase.md.md.MolecularDynamics
29 base class.
30 """
32 def __init__(
33 self,
34 atoms,
35 timestep,
36 temperature_K,
37 taut,
38 rng=np.random,
39 **md_kwargs,
40 ):
41 super().__init__(atoms, timestep, **md_kwargs)
43 self.temp = temperature_K * units.kB
44 self.taut = taut
45 self.rng = rng
47 self.ndof = self.atoms.get_number_of_degrees_of_freedom()
49 self.target_kinetic_energy = 0.5 * self.temp * self.ndof
51 if np.isclose(
52 self.atoms.get_kinetic_energy(), 0.0, rtol=0, atol=1e-12
53 ):
54 raise ValueError(
55 "Initial kinetic energy is zero. "
56 "Please set the initial velocities before running Bussi NVT."
57 )
59 self._exp_term = math.exp(-self.dt / self.taut)
60 self._masses = self.atoms.get_masses()[:, np.newaxis]
62 self.transferred_energy = 0.0
64 def scale_velocities(self):
65 """Do the NVT Bussi stochastic velocity scaling."""
66 kinetic_energy = self.atoms.get_kinetic_energy()
67 alpha = self.calculate_alpha(kinetic_energy)
69 momenta = self.atoms.get_momenta()
70 self.atoms.set_momenta(alpha * momenta)
72 self.transferred_energy += (alpha**2 - 1.0) * kinetic_energy
74 def calculate_alpha(self, kinetic_energy):
75 """Calculate the scaling factor alpha using equation (A7)
76 from the Bussi paper."""
78 energy_scaling_term = (
79 (1 - self._exp_term)
80 * self.target_kinetic_energy
81 / kinetic_energy
82 / self.ndof
83 )
85 # R1 in Eq. (A7)
86 normal_noise = self.rng.standard_normal()
87 # \sum_{i=2}^{Nf} R_i^2 in Eq. (A7)
88 # 2 * standard_gamma(n / 2) is equal to chisquare(n)
89 sum_of_noises = 2.0 * self.rng.standard_gamma(0.5 * (self.ndof - 1))
91 return math.sqrt(
92 self._exp_term
93 + energy_scaling_term * (sum_of_noises + normal_noise**2)
94 + 2
95 * normal_noise
96 * math.sqrt(self._exp_term * energy_scaling_term)
97 )
99 def step(self, forces=None):
100 """Move one timestep forward using Bussi NVT molecular dynamics."""
101 self.scale_velocities()
102 return super().step(forces)