Coverage for cvpack/reporting/cv_writer.py: 93%
30 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 00:10 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 00:10 +0000
1"""
2.. class:: CVWriter
3 :platform: Linux, MacOS, Windows
4 :synopsis: A custom writer for reporting collective variable data
6.. moduleauthor:: Charlles Abreu <craabreu@gmail.com>
8"""
10import openmm as mm
12from ..collective_variable import CollectiveVariable
13from .custom_writer import CustomWriter
16class CVWriter(CustomWriter):
17 """
18 A custom writer for reporting collective variable data.
20 Parameters
21 ----------
22 variable
23 The collective variable whose values and/or effective masses will be reported.
24 value
25 If `True`, report the values of the collective variable.
26 emass
27 If `True`, report the effective masses of the collective variable.
28 allowReinitialization
29 Whether to allow context reinitialization when the collective variable shares
30 its force group with other forces (see :meth:`CollectiveVariable.getValue`).
32 Examples
33 --------
34 >>> import cvpack
35 >>> import openmm
36 >>> from cvpack.reporting import StateDataReporter, CVWriter
37 >>> from math import pi
38 >>> from openmm import app, unit
39 >>> from sys import stdout
40 >>> from openmmtools import testsystems
41 >>> model = testsystems.AlanineDipeptideVacuum()
42 >>> phi = cvpack.Torsion(4, 6, 8, 14, name="phi")
43 >>> phi.addToSystem(model.system)
44 >>> psi = cvpack.Torsion(6, 8, 14, 16, name="psi")
45 >>> psi.addToSystem(model.system)
46 >>> reporter = StateDataReporter(
47 ... stdout,
48 ... 100,
49 ... writers=[
50 ... CVWriter(phi, value=True, emass=True),
51 ... CVWriter(psi, value=True, emass=True),
52 ... ],
53 ... step=True,
54 ... )
55 >>> integrator = openmm.LangevinIntegrator(
56 ... 300 * unit.kelvin,
57 ... 1 / unit.picosecond,
58 ... 2 * unit.femtosecond,
59 ... )
60 >>> integrator.setRandomNumberSeed(1234)
61 >>> # Integrate only the physical forces, so phi and psi do not bias the dynamics
62 >>> integrator.setIntegrationForceGroups({0})
63 >>> simulation = app.Simulation(model.topology, model.system, integrator)
64 >>> simulation.context.setPositions(model.positions)
65 >>> simulation.context.setVelocitiesToTemperature(300 * unit.kelvin, 5678)
66 >>> simulation.reporters.append(reporter)
67 >>> simulation.step(1000) # doctest: +SKIP
68 #"Step","phi (rad)",...,"emass[psi] (nm**2 Da/(rad**2))"
69 100,-2.4309...,0.0404...,2.7368...,0.0497...
70 200,-2.0470...,0.0386...,2.2298...,0.0456...
71 300,-2.4721...,0.0424...,2.2528...,0.0422...
72 400,-2.4032...,0.0406...,-3.1341...,0.0546...
73 500,-2.8165...,0.0460...,-2.5845...,0.0458...
74 600,-2.6256...,0.0401...,3.1382...,0.0509...
75 700,-2.4156...,0.0463...,2.1037...,0.0406...
76 800,-2.3871...,0.0372...,1.8683...,0.0408...
77 900,-1.8631...,0.0322...,2.0393...,0.0374...
78 1000,-1.0804...,0.0240...,1.8363...,0.0354...
79 """
81 def __init__(
82 self,
83 variable: CollectiveVariable,
84 value: bool = False,
85 emass: bool = False,
86 allowReinitialization: bool = False,
87 ) -> None:
88 if not isinstance(variable, CollectiveVariable):
89 raise TypeError("variable must be a CollectiveVariable object")
90 if not (value or emass):
91 raise ValueError("At least one of value or emass must be True")
92 self._cv = variable
93 self._value = value
94 self._emass = emass
95 self._allow_reinitialization = allowReinitialization
97 def getHeaders(self) -> list[str]:
98 headers = []
99 if self._value:
100 headers.append(f"{self._cv.getName()} ({self._cv.getUnit().get_symbol()})")
101 if self._emass:
102 headers.append(
103 f"emass[{self._cv.getName()}] ({self._cv.getMassUnit().get_symbol()})"
104 )
105 return headers
107 def getValues(self, simulation: mm.app.Simulation) -> list[float]:
108 context = simulation.context
109 values = []
110 if self._value:
111 value = self._cv.getValue(context, self._allow_reinitialization)
112 values.append(value / self._cv.getUnit())
113 if self._emass:
114 mass = self._cv.getEffectiveMass(context, self._allow_reinitialization)
115 values.append(mass / self._cv.getMassUnit())
116 return values