Coverage for openxps/integrators/massive_ggmt.py: 81%
80 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-13 22:08 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-13 22:08 +0000
1"""
2.. module:: openxps.integrators.massive_ggmt
3 :platform: Linux, MacOS, Windows
4 :synopsis: Massive Generalized Gaussian Moment Thermostat (GGMT) integrator.
6.. classauthor:: Charlles Abreu <craabreu@gmail.com>
8"""
10import numpy as np
11import openmm as mm
12from openmm import unit as mmunit
14from openxps.integrators.utils import IntegratorMixin, add_property
15from openxps.utils import preprocess_args
18@add_property("temperature", mmunit.kelvin)
19@add_property("time constant", 1 / mmunit.picosecond)
20@add_property("friction coefficient", 1 / mmunit.picosecond)
21class MassiveGGMTIntegrator(IntegratorMixin, mm.CustomIntegrator):
22 r"""A massive, second- and fourth-moment GGMT integrator :cite:`Liu2000`.
24 The massive GGMT integrator is effective at maintaining temperature control under
25 steady-state conditions, such as in d-AFED/TAMD :cite:`Maragliano2006,Abrams2008`
26 or UFED :cite:`Chen2012` simulations.
28 This implementation optionally introduces stochasticity by applying Langevin-type
29 noise to the variables controlling the second- and fourth-moment of the velocity
30 distribution.
32 .. note::
33 If stochasticity is enabled, the friction coefficient is initially set to the
34 inverse of the time constant, but this value can be customized via
35 :meth:`setFrictionCoefficient`.
37 .. warning::
38 This integrator is incompatible with systems subject to constraints.
40 Parameters
41 ----------
42 temperature
43 The temperature of the heat bath.
44 timeConstant
45 The time constant (a.k.a. coupling/damping/relaxation time) of the thermostat.
46 stepSize
47 The integration step size.
49 Keyword Arguments
50 -----------------
51 stochastic
52 If True, the integrator will apply stochasticity to the variables controlling
53 the second- and fourth-moment of the velocity distribution.
54 forceFirst
55 If True, the integrator will apply a force-first scheme. Otherwise, it will
56 apply a symmetric operator splitting scheme.
58 Example
59 -------
60 >>> import openxps as xps
61 >>> from openmm import unit
62 >>> from openmmtools import testsystems
64 Symmetric scheme (default)
66 >>> integrator = xps.integrators.MassiveGGMTIntegrator(
67 ... temperature=300 * unit.kelvin,
68 ... timeConstant=100 * unit.femtoseconds,
69 ... stepSize=2 * unit.femtoseconds,
70 ... )
71 >>> integrator
72 Per-dof variables:
73 v1, v2, v2lb
74 Global variables:
75 kT = 2.494338...
76 invQ = 40.090...
77 invQ2 = 2.416...
78 Computation steps:
79 0: allow forces to update the context state
80 1: v <- v + 0.5*dt*f/m
81 2: x <- x + 0.5*dt*v
82 3: v1 <- v1 + 0.5*dt*(m*v^2 - kT)*invQ
83 4: v2lb <- -sqrt(v2*v2 + (mvv + e*e/mvv - 2*e)*invQ2); mvv = m*v*v; e=sqrt(3)*kT
84 5: v2 <- select(step(new_v2 - v2lb), new_v2, 2*v2lb - new_v2); new_v2 = ...
85 6: v <- v/sqrt(1 + 0.33...*dt*m*v^2*v2)
86 7: v2 <- select(step(new_v2 - v2lb), new_v2, 2*v2lb - new_v2); new_v2 = ...
87 8: v <- v*exp(-1*dt*(v1 + kT*v2))
88 9: v2lb <- -sqrt(v2*v2 + (mvv + e*e/mvv - 2*e)*invQ2); mvv = m*v*v; e=sqrt(3)*kT
89 10: v2 <- select(step(new_v2 - v2lb), new_v2, 2*v2lb - new_v2); new_v2 = ...
90 11: v <- v/sqrt(1 + 0.33...*dt*m*v^2*v2)
91 12: v2 <- select(step(new_v2 - v2lb), new_v2, 2*v2lb - new_v2); new_v2 = ...
92 13: v1 <- v1 + 0.5*dt*(m*v^2 - kT)*invQ
93 14: x <- x + 0.5*dt*v
94 15: v <- v + 0.5*dt*f/m
96 Force-first scheme
98 >>> integrator_ff = xps.integrators.MassiveGGMTIntegrator(
99 ... temperature=300 * unit.kelvin,
100 ... timeConstant=40 * unit.femtoseconds,
101 ... stepSize=2 * unit.femtoseconds,
102 ... forceFirst=True
103 ... )
104 >>> model = testsystems.AlanineDipeptideVacuum()
105 >>> try:
106 ... integrator_ff.registerWithSystem(model.system, False)
107 ... except ValueError as e:
108 ... print(e)
109 Massive GGMT integrators do not support constraints.
110 """
112 @preprocess_args
113 def __init__(
114 self,
115 temperature: mmunit.Quantity,
116 timeConstant: mmunit.Quantity,
117 stepSize: mmunit.Quantity,
118 *,
119 stochastic: bool = False,
120 forceFirst: bool = False,
121 ) -> None:
122 super().__init__(stepSize)
123 self._forceFirst = forceFirst
124 self._init_temperature(temperature)
125 self._init_time_constant(timeConstant)
126 self._init_friction_coefficient(1 / timeConstant if stochastic else 0.0)
127 self._stochastic = stochastic
128 self._add_variables()
129 self.addUpdateContextState()
130 self._add_boost(1 if forceFirst else 0.5)
131 self._add_translation(0.5)
132 self._add_thermostat(1)
133 self._add_translation(0.5)
134 if not forceFirst:
135 self._add_boost(0.5)
137 def _add_variables(self) -> None:
138 self.addGlobalVariable("kT", 0)
139 self.addGlobalVariable("invQ", 0)
140 self.addGlobalVariable("invQ2", 0)
141 self.addPerDofVariable("v1", 0)
142 self.addPerDofVariable("v2", 0)
143 self.addPerDofVariable("v2lb", 0)
144 if self._stochastic:
145 self.addGlobalVariable("a", 0)
146 self.addGlobalVariable("b1", 0)
147 self.addGlobalVariable("b2", 0)
148 self._update_global_variables()
150 def _update_global_variables(self) -> None:
151 tau = self.getTimeConstant()
152 kt = mmunit.MOLAR_GAS_CONSTANT_R * self.getTemperature()
153 self.setGlobalVariableByName("kT", kt)
154 self.setGlobalVariableByName("invQ", 1 / (kt * tau**2))
155 self.setGlobalVariableByName("invQ2", 3 / (8 * kt**3 * tau**2))
156 if self._stochastic:
157 friction = self.getFrictionCoefficient()
158 dt = self.getStepSize()
159 a = np.exp(-friction * dt)
160 b1 = np.sqrt(1 - a**2) / tau
161 b2 = b1 * np.sqrt(3 / 8) / kt
162 self.setGlobalVariableByName("a", a)
163 self.setGlobalVariableByName("b1", b1)
164 self.setGlobalVariableByName("b2", b2)
166 def _add_translation(self, fraction: float) -> None:
167 self.addComputePerDof("x", f"x + {fraction}*dt*v")
169 def _add_boost(self, fraction: float) -> None:
170 self.addComputePerDof("v", f"v + {fraction}*dt*f/m")
172 def _add_v1_boost(self, fraction: float) -> None:
173 self.addComputePerDof("v1", f"v1 + {fraction}*dt*(m*v^2 - kT)*invQ")
175 def _add_v2_boost(self, fraction: float) -> None:
176 self.addComputePerDof(
177 "v2lb",
178 "-sqrt(v2*v2 + (mvv + e*e/mvv - 2*e)*invQ2); mvv = m*v*v; e=sqrt(3)*kT",
179 )
180 expression = (
181 "select(step(new_v2 - v2lb), new_v2, 2*v2lb - new_v2)"
182 f"; new_v2 = v2 + {fraction / 2}*dt*((m*v^2)^2/3 - kT^2)*invQ2"
183 )
184 self.addComputePerDof("v2", expression)
185 self.addComputePerDof("v", f"v/sqrt(1 + {(2 * fraction) / 3}*dt*m*v^2*v2)")
186 self.addComputePerDof("v2", expression)
188 def _add_v_scaling(self, fraction: float) -> None:
189 self.addComputePerDof("v", f"v*exp(-{fraction}*dt*(v1 + kT*v2))")
191 def _add_thermostat(self, fraction: float) -> None:
192 self._add_v1_boost(0.5 * fraction)
193 self._add_v2_boost(0.5 * fraction)
194 if self._stochastic:
195 self._add_v_scaling(0.5 * fraction)
196 self.addComputePerDof("v1", "a*v1 + b1*gaussian")
197 self.addComputePerDof("v2", "a*v2 + b2*gaussian")
198 self._add_v_scaling(0.5 * fraction)
199 else:
200 self._add_v_scaling(fraction)
201 self._add_v2_boost(0.5 * fraction)
202 self._add_v1_boost(0.5 * fraction)
204 def registerWithSystem(self, system: mm.System, isExtension: bool) -> None:
205 if system.getNumConstraints() > 0:
206 raise ValueError("Massive GGMT integrators do not support constraints.")