Coverage for cvpack/torsion_similarity.py: 100%
20 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:: TorsionSimilarity
3 :platform: Linux, MacOS, Windows
4 :synopsis: The degree of similarity between pairs of torsion angles.
6.. classauthor:: Charlles Abreu <craabreu@gmail.com>
8"""
10from __future__ import annotations
12import numpy as np
13import openmm
14from numpy.typing import ArrayLike
15from openmm import unit as mmunit
17from .collective_variable import CollectiveVariable
20class TorsionSimilarity(CollectiveVariable, openmm.CustomCompoundBondForce):
21 r"""
22 The degree of similarity between `n` pairs of torsion angles:
24 .. math::
26 s({\bf r}) = \frac{n}{2} + \frac{1}{2} \sum_{i=1}^n \cos\Big(
27 \phi^{\rm 1st}_i({\bf r}) - \phi^{\rm 2nd}_i({\bf r})
28 \Big)
30 where :math:`\phi^{\rm kth}_i` is the torsion angle at position :math:`i` in the
31 :math:`k`-th list.
33 .. note::
35 In `PLUMED <https://www.plumed.org/doc-v2.8/user-doc/html/_colvar.html>`_, this
36 collective variable is called ``DIHCOR``.
38 Parameters
39 ----------
40 firstList
41 A list of :math:`n` tuples of four atom indices defining the first torsion
42 angle in each pair.
43 secondList
44 A list of :math:`n` tuples of four atom indices defining the second torsion
45 angle in each pair.
46 pbc
47 Whether to use periodic boundary conditions in distance calculations.
48 name
49 The name of the collective variable.
51 Raises
52 ------
53 ValueError
54 If firstList and secondList have different lengths
56 Example
57 -------
58 >>> import cvpack
59 >>> import mdtraj
60 >>> import openmm
61 >>> import openmm.unit as mmunit
62 >>> from openmmtools import testsystems
63 >>> model = testsystems.LysozymeImplicit()
64 >>> traj = mdtraj.Trajectory(
65 ... model.positions, mdtraj.Topology.from_openmm(model.topology)
66 ... )
67 >>> phi_atoms, _ = mdtraj.compute_phi(traj)
68 >>> valid_atoms = traj.top.select("resid 59 to 79 and backbone")
69 >>> phi_atoms = [
70 ... phi
71 ... for phi in phi_atoms
72 ... if all(atom in valid_atoms for atom in phi)
73 ... ]
74 >>> torsion_similarity = cvpack.TorsionSimilarity(
75 ... phi_atoms[1:], phi_atoms[:-1]
76 ... )
77 >>> torsion_similarity.addToSystem(model.system)
78 >>> integrator = openmm.VerletIntegrator(0)
79 >>> platform = openmm.Platform.getPlatformByName("Reference")
80 >>> context = openmm.Context(model.system, integrator, platform)
81 >>> context.setPositions(model.positions)
82 >>> torsion_similarity.getValue(context)
83 18.659... dimensionless
84 """
86 def __init__(
87 self,
88 firstList: ArrayLike,
89 secondList: ArrayLike,
90 pbc: bool = False,
91 name: str = "torsion_similarity",
92 ) -> None:
93 firstList = [list(map(int, first)) for first in firstList]
94 secondList = [list(map(int, second)) for second in secondList]
95 if len(firstList) != len(secondList):
96 raise ValueError("firstList and secondList must have the same length")
97 function = f"0.5*(1 + cos(min(delta, {2 * np.pi} - delta)))"
98 definition = "delta = dihedral(p1, p2, p3, p4) - dihedral(p5, p6, p7, p8)"
99 super().__init__(8, f"{function}; {definition}")
100 for first, second in zip(firstList, secondList):
101 self.addBond([*first, *second], [])
102 self.setUsesPeriodicBoundaryConditions(pbc)
103 self._registerCV(
104 name,
105 mmunit.dimensionless,
106 firstList=firstList,
107 secondList=secondList,
108 pbc=pbc,
109 )
112TorsionSimilarity.registerTag("!cvpack.TorsionSimilarity")