Coverage for openxps/metadynamics.py: 88%
48 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.metadynamics
3 :platform: Linux, Windows, macOS
4 :synopsis: Bias potentials applied to dynamical variables.
6.. moduleauthor:: Charlles Abreu <craabreu@gmail.com>
8"""
10import typing as t
11from dataclasses import dataclass, field
13from cvpack.serialization import Serializable
14from openmm import app as mmapp
15from openmm import unit as mmunit
17from .bounds import NoBounds
18from .dynamical_variable import DynamicalVariable
19from .system import ExtendedSpaceSystem
20from .utils import preprocess_args
23class _SimulationWrapper:
24 def __init__(self, simulation: mmapp.Simulation):
25 self._simulation = simulation
26 self.context = simulation.context.getExtensionContext()
27 self.step = simulation.step
29 @property
30 def currentStep(self):
31 return self._simulation.currentStep
34@dataclass(frozen=True)
35class ExtendedSpaceBiasVariable(Serializable):
36 """
37 A dynamical variable augmented with a Gaussian bias kernel.
39 Parameters
40 ----------
41 dynamical_variable
42 The dynamical variable to be augmented.
43 sigma
44 The bandwidth (standard deviation) of the Gaussian bias kernel.
45 grid_width
46 The width of the grid for the bias kernel. If ``None``, the grid width is
47 automatically determined based on the dynamical variable's bounds and the
48 bandwidth.
50 Example
51 -------
52 >>> import openxps as xps
53 >>> from openmm import unit
54 >>> dv = xps.DynamicalVariable(
55 ... "phi",
56 ... unit.radian,
57 ... 3 * unit.dalton*(unit.nanometer/unit.radian)**2,
58 ... xps.PeriodicBounds(-180, 180, unit.degree)
59 ... )
60 >>> bias_variable = xps.ExtendedSpaceBiasVariable(dv, 18 * unit.degree)
61 >>> bias_variable.dynamical_variable
62 DynamicalVariable(name='phi', unit=rad, mass=3 nm**2 Da/(rad**2), bounds=...)
63 """
65 dynamical_variable: DynamicalVariable
66 sigma: mmunit.Quantity
67 grid_width: t.Optional[int] = field(default_factory=lambda: None)
69 def __post_init__(self) -> None:
70 if not mmunit.is_quantity(self.sigma):
71 raise TypeError("sigma must be a quantity")
72 if not self.sigma.unit.is_compatible(self.dynamical_variable.unit):
73 raise ValueError(
74 "sigma must be compatible with the dynamical variable's unit"
75 )
76 if not (self.grid_width is None or isinstance(self.grid_width, int)):
77 raise TypeError("grid_width must be an integer or None")
80ExtendedSpaceBiasVariable.__init__ = preprocess_args(ExtendedSpaceBiasVariable.__init__)
82ExtendedSpaceBiasVariable.registerTag("!openxps.metadynamics.ExtendedSpaceBiasVariable")
85class ExtendedSpaceMetadynamics(mmapp.Metadynamics):
86 """
87 Performs Extended Phase-Space (XPS) Metadynamics simulations, in which a bias
88 potential is applied to dynamical variables (DVs) to enhance sampling of the
89 physical coordinates.
91 Parameters
92 ----------
93 system
94 The :class:`ExtendedSpaceSystem` to be used in the XPS simulation.
95 variables
96 A sequence of :class:`ExtendedSpaceBiasVariable` objects to specify the biased
97 dynamical variables and their corresponding Gaussian kernels.
98 temperature
99 The temperature at which the simulation is being run. This is used in computing
100 the free energy.
101 biasFactor
102 Used in scaling the height of the Gaussians added to the bias. The dynamical
103 variables are sampled as if the effective temperature of the simulation were
104 temperature*biasFactor.
105 height
106 The initial height of the Gaussians to add (in units of energy).
107 frequency
108 The interval in time steps at which Gaussians should be added to the bias
109 potential.
110 saveFrequency
111 The interval in time steps at which to write out the current biases to disk. At
112 the same time it writes biases, it also checks for updated biases written by
113 other processes and loads them in. This must be a multiple of frequency.
114 biasDir
115 The directory to which biases should be written, and from which biases written
116 by other processes should be loaded.
118 Example
119 -------
120 >>> import openxps as xps
121 >>> from openmm import unit
122 >>> import cvpack
123 >>> from math import pi
124 >>> import openmm
125 >>> from openmmtools import testsystems
126 >>> model = testsystems.AlanineDipeptideVacuum()
127 >>> phi = cvpack.Torsion(6, 8, 14, 16, name="phi")
128 >>> mass = 3 * unit.dalton*(unit.nanometer/unit.radian)**2
129 >>> phi0 = xps.DynamicalVariable("phi0", unit.radian, mass, xps.CircularBounds())
130 >>> phi = cvpack.Torsion(6, 8, 14, 16, name="phi")
131 >>> kappa = 1000 * unit.kilojoules_per_mole / unit.radian**2
132 >>> harmonic_force = xps.HarmonicCoupling(phi, phi0, kappa)
133 >>> system = xps.ExtendedSpaceSystem(model.system, harmonic_force)
134 >>> bias_variable = xps.ExtendedSpaceBiasVariable(phi0, 18 * unit.degrees)
135 >>> temperature = 300 * unit.kelvin
136 >>> metadynamics = xps.ExtendedSpaceMetadynamics(
137 ... system=system,
138 ... variables=[bias_variable],
139 ... temperature=temperature,
140 ... biasFactor=5,
141 ... height=2 * unit.kilojoule_per_mole,
142 ... frequency=100,
143 ... )
144 >>> simulation = xps.ExtendedSpaceSimulation(
145 ... model.topology,
146 ... system,
147 ... xps.LockstepIntegrator(
148 ... openmm.LangevinMiddleIntegrator(
149 ... temperature, 1 / unit.picosecond, 4 * unit.femtosecond
150 ... )
151 ... ),
152 ... openmm.Platform.getPlatformByName("Reference"),
153 ... )
154 >>> context = simulation.context
155 >>> context.setPositions(model.positions)
156 >>> context.setVelocitiesToTemperature(temperature, 1234)
157 >>> context.setDynamicalVariableValues([180 * unit.degree])
158 >>> context.setDynamicalVariableVelocitiesToTemperature(temperature, 1234)
159 >>> metadynamics.step(simulation, 100)
160 >>> metadynamics.getFreeEnergy()
161 [... ... ...] kJ/mol
162 """
164 def __init__( # noqa: PLR0913
165 self,
166 system: ExtendedSpaceSystem,
167 variables: t.Sequence[ExtendedSpaceBiasVariable],
168 temperature: mmunit.Quantity,
169 biasFactor: float,
170 height: mmunit.Quantity,
171 frequency: int,
172 saveFrequency: t.Optional[int] = None,
173 biasDir: t.Optional[str] = None,
174 ) -> None:
175 system_dvs = system.getDynamicalVariables()
176 bias_variables = []
177 for variable in variables:
178 dv = variable.dynamical_variable.in_md_units()
179 if dv not in system_dvs:
180 raise ValueError(f"Dynamical variable {dv.name} not found in system")
181 if isinstance(dv.bounds, NoBounds):
182 raise ValueError(f"Dynamical variable {dv.name} has no bounds")
183 index = system_dvs.index(dv)
184 bias_variables.append(
185 mmapp.BiasVariable(
186 dv.createCollectiveVariable(index),
187 dv.bounds.lower * dv.bounds.unit,
188 dv.bounds.upper * dv.bounds.unit,
189 variable.sigma,
190 dv.isPeriodic(),
191 variable.grid_width,
192 )
193 )
194 super().__init__(
195 system.getExtensionSystem(),
196 bias_variables,
197 temperature,
198 biasFactor,
199 height,
200 frequency,
201 saveFrequency,
202 biasDir,
203 )
205 def step(self, simulation, steps):
206 super().step(_SimulationWrapper(simulation), steps)
208 def getCollectiveVariables(self, simulation):
209 return self._force.getCollectiveVariableValues(
210 simulation.context.getExtensionContext()
211 )