@@ -128,9 +128,9 @@ def _registerCV(
128128 """
129129 cls = self .__class__
130130 self .setName (cls .__name__ )
131- self .setUnit ( unit )
131+ self ._unit = unit
132132 self ._mass_unit = mmunit .dalton * (mmunit .nanometers / self .getUnit ()) ** 2
133- arguments , _ = self .getArguments ()
133+ arguments , _ = self ._getArguments ()
134134 self ._args = dict (zip (arguments , args ))
135135 self ._args .update (kwargs )
136136
@@ -149,21 +149,22 @@ def _registerPeriod(self, period: float) -> None:
149149 self ._period = period
150150
151151 @classmethod
152- def getArguments (cls ) -> t .Tuple [collections .OrderedDict , collections .OrderedDict ]:
152+ def _getArguments (cls ) -> t .Tuple [collections .OrderedDict , collections .OrderedDict ]:
153153 """
154154 Inspect the arguments needed for constructing an instance of this collective
155155 variable.
156156
157157 Returns
158158 -------
159+ OrderedDict
159160 A dictionary with the type annotations of all arguments
160-
161+ OrderedDict
161162 A dictionary with the default values of optional arguments
162163
163164 Example
164165 -------
165166 >>> import cvpack
166- >>> args, defaults = cvpack.RadiusOfGyration.getArguments ()
167+ >>> args, defaults = cvpack.RadiusOfGyration._getArguments ()
167168 >>> for name, annotation in args.items():
168169 ... print(f"{name}: {annotation}")
169170 group: typing.Iterable[int]
@@ -180,16 +181,32 @@ def getArguments(cls) -> t.Tuple[collections.OrderedDict, collections.OrderedDic
180181 defaults [name ] = parameter .default
181182 return arguments , defaults
182183
183- def setUnit (self , unit : mmunit . Unit ) -> None :
184+ def _setUnusedForceGroup (self , system : openmm . System ) -> None :
184185 """
185- Set the unit of measurement of this collective variable.
186+ Set the force group of this collective variable to the one at a given position
187+ in the ascending ordered list of unused force groups in an :OpenMM:`System`.
188+
189+ .. note::
190+
191+ Evaluating a collective variable (see :meth:`getValue`) or computing its
192+ effective mass (see :meth:`getEffectiveMass`) is more efficient when the
193+ collective variable is the only force in its own force group.
186194
187195 Parameters
188196 ----------
189- unit
190- The unit of measurement of this collective variable
197+ system
198+ The system to search for unused force groups
199+
200+ Raises
201+ ------
202+ RuntimeError
203+ If all force groups are already in use
191204 """
192- self ._unit = unit
205+ used_groups = {force .getForceGroup () for force in system .getForces ()}
206+ new_group = next (filter (lambda i : i not in used_groups , range (32 )), None )
207+ if new_group is None :
208+ raise RuntimeError ("All force groups are already in use." )
209+ self .setForceGroup (new_group )
193210
194211 def getUnit (self ) -> mmunit .Unit :
195212 """
@@ -209,42 +226,23 @@ def getPeriod(self) -> t.Optional[mmunit.SerializableQuantity]:
209226 return None
210227 return mmunit .SerializableQuantity (self ._period , self .getUnit ())
211228
212- def setUnusedForceGroup (self , position : int , system : openmm .System ) -> int :
229+ def addToSystem (
230+ self , system : openmm .System , setUnusedForceGroup : bool = True
231+ ) -> None :
213232 """
214- Set the force group of this collective variable to the one at a given position
215- in the ascending ordered list of unused force groups in an :OpenMM:`System`.
216-
217- .. note::
218-
219- Evaluating a collective variable (see :meth:`getValue`) or computing its
220- effective mass (see :meth:`getEffectiveMass`) is more efficient when the
221- collective variable is the only force in its own force group.
233+ Add this collective variable to an :OpenMM:`System`.
222234
223235 Parameters
224236 ----------
225- position
226- The position of the force group in the ascending ordered list of unused
227- force groups in the system
228- system
229- The system to search for unused force groups
230-
231- Returns
232- -------
233- The index of the force group that was set
234-
235- Raises
236- ------
237- RuntimeError
238- If all force groups are already in use
237+ system
238+ The system to which this collective variable should be added
239+ setUnusedForceGroup
240+ If True, the force group of this collective variable will be set to the
241+ first available force group in the system
239242 """
240- free_groups = sorted (
241- set (range (32 )) - {force .getForceGroup () for force in system .getForces ()}
242- )
243- if not free_groups :
244- raise RuntimeError ("All force groups are already in use." )
245- new_group = free_groups [position ]
246- self .setForceGroup (new_group )
247- return new_group
243+ if setUnusedForceGroup :
244+ self ._setUnusedForceGroup (system )
245+ system .addForce (self )
248246
249247 def getValue (self , context : openmm .Context ) -> mmunit .Quantity :
250248 """
@@ -257,12 +255,43 @@ def getValue(self, context: openmm.Context) -> mmunit.Quantity:
257255
258256 Parameters
259257 ----------
260- context
261- The context at which this collective variable should be evaluated
258+ context
259+ The context at which this collective variable should be evaluated
262260
263261 Returns
264262 -------
263+ unit.Quantity
265264 The value of this collective variable at the given context
265+
266+
267+ Example
268+ -------
269+ In this example, we compute the values of the backbone dihedral angles and
270+ the radius of gyration of an alanine dipeptide molecule in water:
271+
272+ >>> import cvpack
273+ >>> import openmm
274+ >>> from openmmtools import testsystems
275+ >>> model = testsystems.AlanineDipeptideExplicit()
276+ >>> top = model.mdtraj_topology
277+ >>> backbone_atoms = top.select("name N C CA and resid 1 2")
278+ >>> phi = cvpack.Torsion(*backbone_atoms[0:4])
279+ >>> psi = cvpack.Torsion(*backbone_atoms[1:5])
280+ >>> radius_of_gyration = cvpack.RadiusOfGyration(
281+ ... top.select('not water')
282+ ... )
283+ >>> for cv in [phi, psi, radius_of_gyration]:
284+ ... cv.addToSystem(model.system)
285+ >>> context = openmm.Context(
286+ ... model.system, openmm.VerletIntegrator(0)
287+ ... )
288+ >>> context.setPositions(model.positions)
289+ >>> print(phi.getValue(context))
290+ 3.1415... rad
291+ >>> print(psi.getValue(context))
292+ 3.1415... rad
293+ >>> print(radius_of_gyration.getValue(context))
294+ 0.29514... nm
266295 """
267296 state = get_single_force_state (self , context , getEnergy = True )
268297 value = value_in_md_units (state .getPotentialEnergy ())
@@ -291,36 +320,41 @@ def getEffectiveMass(self, context: openmm.Context) -> mmunit.Quantity:
291320
292321 Parameters
293322 ----------
294- context
295- The context at which this collective variable's effective mass should be
296- evaluated
323+ context
324+ The context at which this collective variable's effective mass should be
325+ evaluated
297326
298327 Returns
299328 -------
329+ unit.Quantity
300330 The effective mass of this collective variable at the given context
301331
302332 Example
303333 -------
334+ In this example, we compute the effective masses of the backbone dihedral
335+ angles and the radius of gyration of an alanine dipeptide molecule in water:
336+
304337 >>> import cvpack
305338 >>> import openmm
306339 >>> from openmmtools import testsystems
307- >>> model = testsystems.AlanineDipeptideImplicit()
308- >>> peptide = [
309- ... a.index
310- ... for a in model.topology.atoms()
311- ... if a.residue.name != 'HOH'
312- ... ]
313- >>> radius_of_gyration = cvpack.RadiusOfGyration(peptide)
314- >>> radius_of_gyration.setForceGroup(1)
315- >>> radius_of_gyration.setUnusedForceGroup(0, model.system)
316- 1
317- >>> model.system.addForce(radius_of_gyration)
318- 6
319- >>> platform = openmm.Platform.getPlatformByName('Reference')
340+ >>> model = testsystems.AlanineDipeptideExplicit()
341+ >>> top = model.mdtraj_topology
342+ >>> backbone_atoms = top.select("name N C CA and resid 1 2")
343+ >>> phi = cvpack.Torsion(*backbone_atoms[0:4])
344+ >>> psi = cvpack.Torsion(*backbone_atoms[1:5])
345+ >>> radius_of_gyration = cvpack.RadiusOfGyration(
346+ ... top.select('not water')
347+ ... )
348+ >>> for cv in [phi, psi, radius_of_gyration]:
349+ ... cv.addToSystem(model.system)
320350 >>> context = openmm.Context(
321- ... model.system,openmm.VerletIntegrator(0), platform
351+ ... model.system, openmm.VerletIntegrator(0)
322352 ... )
323353 >>> context.setPositions(model.positions)
354+ >>> print(phi.getEffectiveMass(context))
355+ 0.05119... nm**2 Da/(rad**2)
356+ >>> print(psi.getEffectiveMass(context))
357+ 0.05186... nm**2 Da/(rad**2)
324358 >>> print(radius_of_gyration.getEffectiveMass(context))
325359 30.946... Da
326360 """
0 commit comments