Source code for openxps.system

"""
.. module:: openxps.system
   :platform: Linux, MacOS, Windows
   :synopsis: System for extended phase-space simulations with OpenMM.

.. classauthor:: Charlles Abreu <craabreu@gmail.com>

"""

import openmm as mm

from .couplings import Coupling
from .dynamical_variable import DynamicalVariable


[docs] class ExtendedSpaceSystem(mm.System): """An :OpenMM:`System` object that includes extra dynamical variables (DVs) and allows for extended phase-space (XPS) simulations. **Note**: The system provided as argument is modified in place. Parameters ---------- system The :OpenMM:`System` to be used in the XPS simulation. coupling A :class:`Coupling` object, required to couple the physical and extended phase-space systems. The dynamical variables are obtained from this coupling. Its forces are moved into ``system``, so a coupling can be part of only one system. To reuse it, pass a deep copy (``copy.deepcopy(coupling)``). Example ------- >>> import openxps as xps >>> from math import pi >>> import openmm >>> import cvpack >>> from openmm import unit >>> from openmmtools import testsystems >>> model = testsystems.AlanineDipeptideVacuum() >>> mass = 3 * unit.dalton*(unit.nanometer/unit.radian)**2 >>> phi0 = xps.DynamicalVariable("phi0", unit.radian, mass, xps.CircularBounds()) >>> harmonic_force = xps.HarmonicCoupling( ... cvpack.Torsion(6, 8, 14, 16, name="phi"), ... phi0, ... 1000 * unit.kilojoules_per_mole / unit.radian**2, ... ) >>> system = xps.ExtendedSpaceSystem(model.system, harmonic_force) >>> system.getDynamicalVariables() (DynamicalVariable(name='phi0', unit=rad, mass=3 nm**2 Da/(rad**2), bounds=...),) >>> system.getExtensionSystem().getNumParticles() 1 """ def __init__( self, system: mm.System, coupling: Coupling, ) -> None: if not isinstance(coupling, Coupling): raise TypeError("The coupling must be an instance of Coupling.") if not all(force.thisown for force in coupling.getForces()): raise RuntimeError( "The coupling is already part of another system. Create a new " "coupling or pass a deep copy of this one (copy.deepcopy)." ) self._coupling = coupling self._extension_system = mm.System() for dv in coupling.getDynamicalVariables(): self._extension_system.addParticle(dv.mass / dv.mass.unit) coupling.addToExtensionSystem(self._extension_system) self._checkOtherForces(system) coupling.addToPhysicalSystem(system) self.this = system.this def _checkOtherForces(self, system: mm.System) -> None: """Reject forces outside the coupling that depend on its parameters. The coupling sets these parameters from the dynamical variables, but the extension system only feels the forces of the coupling itself, so a force outside it would act on the physical system without a counterpart. """ protected = self._coupling.getProtectedParameters() for force in system.getForces(): if not hasattr(force, "getNumGlobalParameters"): continue names = { force.getGlobalParameterName(i) for i in range(force.getNumGlobalParameters()) } if shared := names & protected: raise ValueError( f"Force {force.getName()} is not part of the coupling but depends " "on parameters controlled by it: " + ", ".join(sorted(shared)) + ". Include this force in the coupling." )
[docs] def getDynamicalVariables(self) -> tuple[DynamicalVariable]: """ Get the dynamical variables included in the extended phase-space system. Returns ------- t.Tuple[DynamicalVariable] The dynamical variables, converted to MD units. Their order is the one expected by the methods of :class:`ExtendedSpaceContext` that get or set the values and velocities of dynamical variables. """ return tuple(self._coupling.getDynamicalVariables())
[docs] def getCoupling(self) -> Coupling: """ Get the coupling included in the extended phase-space system. Returns ------- Coupling The coupling. """ return self._coupling
[docs] def getExtensionSystem(self) -> mm.System: """ Get the extension system included in the extended phase-space system. Returns ------- mm.System The extension system. """ return self._extension_system