Coverage for cvpack/residue_coordination.py: 97%

35 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-09 16:14 +0000

1""" 

2.. class:: Distance 

3 :platform: Linux, MacOS, Windows 

4 :synopsis: The number of contacts between two groups of residues 

5 

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

7 

8""" 

9 

10import typing as t 

11 

12import openmm 

13from openmm import unit as mmunit 

14from openmm.app.element import hydrogen 

15from openmm.app.topology import Residue 

16 

17from .collective_variable import CollectiveVariable 

18from .units import Quantity, ScalarQuantity, value_in_md_units 

19 

20 

21class ResidueCoordination(CollectiveVariable, openmm.CustomCentroidBondForce): 

22 r""" 

23 The number of contacts between two disjoint groups of residues: 

24 

25 .. math:: 

26 N({\bf r}) = \sum_{i \in {\bf G}_1} \sum_{j \in {\bf G}_2} S\left( 

27 \frac{\|{\bf R}_j({\bf r}) - {\bf R}_i({\bf r})\|}{r_0} 

28 \right) 

29 

30 where :math:`{\bf G}_1` and :math:`{\bf G}_2` are the two groups of residues, 

31 :math:`{\bf R}_i` is the centroid of the residue :math:`i`, :math:`r_0` is the 

32 threshold distance for defining a contact and :math:`S(x)` is a step function equal 

33 to :math:`1` if a contact is made or equal to :math:`0` otherwise. For trajectory 

34 analysis, it is fine to make :math:`S(x) = H(1-x)`, where `H` is the `Heaviside step 

35 function <https://en.wikipedia.org/wiki/Heaviside_step_function>`_. For molecular 

36 dynamics, however, :math:`S(x)` should be a continuous approximation of 

37 :math:`H(1-x)` for :math:`x \geq 0`. By default :cite:`Iannuzzi_2003`, the 

38 following function is used: 

39 

40 .. math:: 

41 

42 S(x) = \frac{1-x^6}{1-x^{12}} = \frac{1}{1+x^6} 

43 

44 Parameters 

45 ---------- 

46 residueGroup1 

47 The residues in the first group. 

48 residueGroup2 

49 The residues in the second group. 

50 pbc 

51 Whether the system has periodic boundary conditions. 

52 stepFunction 

53 The function "step(1-x)" (for analysis only) or a continuous approximation 

54 thereof. 

55 thresholdDistance 

56 The threshold distance (:math:`r_0`) for considering two residues as being 

57 in contact. 

58 normalize 

59 Whether the number of contacts should be normalized by the total number of 

60 possible contacts. 

61 weighByMass 

62 Whether the centroid of each residue should be weighted by the mass of the 

63 atoms in the residue. 

64 includeHydrogens 

65 Whether hydrogen atoms should be included in the centroid calculations. 

66 name 

67 The name of the collective variable. 

68 

69 Raises 

70 ------ 

71 ValueError 

72 If the two groups of residues are not disjoint 

73 

74 Example 

75 ------- 

76 >>> import cvpack 

77 >>> import itertools as it 

78 >>> import openmm 

79 >>> from openmm import app, unit 

80 >>> from openmmtools import testsystems 

81 >>> model = testsystems.LysozymeImplicit() 

82 >>> group1 = list(it.islice(model.topology.residues(), 125, 142)) 

83 >>> print(*[r.name for r in group1]) # doctest: +ELLIPSIS 

84 TRP ASP GLU ... ASN GLN THR 

85 >>> group2 = list(it.islice(model.topology.residues(), 142, 156)) 

86 >>> print(*[r.name for r in group2]) # doctest: +ELLIPSIS 

87 PRO ASN ARG ... ARG THR GLY 

88 >>> residue_coordination = cvpack.ResidueCoordination( 

89 ... group1, 

90 ... group2, 

91 ... stepFunction="step(1-x)", 

92 ... thresholdDistance=0.6*unit.nanometers, 

93 ... ) 

94 >>> residue_coordination.addToSystem(model.system) 

95 >>> platform = openmm.Platform.getPlatformByName('Reference') 

96 >>> context = openmm.Context( 

97 ... model.system, openmm.CustomIntegrator(0), platform 

98 ... ) 

99 >>> context.setPositions(model.positions) 

100 >>> residue_coordination.getValue(context) 

101 26.0 dimensionless 

102 >>> residue_coordination.setReferenceValue(26 * unit.dimensionless) 

103 >>> context.reinitialize(preserveState=True) 

104 >>> residue_coordination.getValue(context) 

105 0.99999... dimensionless 

106 """ 

107 

108 def __init__( 

109 self, 

110 residueGroup1: t.Iterable[Residue], 

111 residueGroup2: t.Iterable[Residue], 

112 pbc: bool = True, 

113 stepFunction: str = "1/(1+x^6)", 

114 thresholdDistance: ScalarQuantity = Quantity(1.0 * mmunit.nanometers), 

115 normalize: bool = False, 

116 weighByMass: bool = True, 

117 includeHydrogens: bool = True, 

118 name: str = "residue_coordination", 

119 ) -> None: 

120 residueGroup1 = list(residueGroup1) 

121 residueGroup2 = list(residueGroup2) 

122 nr1 = len(residueGroup1) 

123 nr2 = len(residueGroup2) 

124 self._ref_val = nr1 * nr2 if normalize else 1.0 

125 threshold = thresholdDistance 

126 if mmunit.is_quantity(threshold): 

127 threshold = threshold.value_in_unit(mmunit.nanometers) 

128 expression = ( 

129 f"({stepFunction})/refval" 

130 f"; x=distance(g1,g2)/{threshold}" 

131 f"; refval={self._ref_val}" 

132 ) 

133 super().__init__(2, expression) 

134 self.setUsesPeriodicBoundaryConditions(pbc) 

135 for res in residueGroup1 + residueGroup2: 

136 atoms = [ 

137 atom.index 

138 for atom in res.atoms() 

139 if includeHydrogens or atom.element is not hydrogen 

140 ] 

141 self.addGroup(atoms, *([] if weighByMass else [[1] * len(atoms)])) 

142 for idx1 in range(nr1): 

143 for idx2 in range(nr1, nr1 + nr2): 

144 self.addBond([idx1, idx2], []) 

145 self._registerCV( 

146 name, 

147 mmunit.dimensionless, 

148 residueGroup1=residueGroup1, 

149 residueGroup2=residueGroup2, 

150 pbc=pbc, 

151 stepFunction=stepFunction, 

152 thresholdDistance=thresholdDistance, 

153 normalize=normalize, 

154 weighByMass=weighByMass, 

155 includeHydrogens=includeHydrogens, 

156 ) 

157 

158 def getReferenceValue(self) -> mmunit.Quantity: 

159 """ 

160 Get the reference value used for normalizing the residue coordination. 

161 

162 Returns 

163 ------- 

164 mmunit.Quantity 

165 The reference value. 

166 """ 

167 return self._ref_val * self.getUnit() 

168 

169 def setReferenceValue(self, value: ScalarQuantity) -> None: 

170 """ 

171 Set the reference value used for normalizing the residue coordination. 

172 

173 Parameters 

174 ---------- 

175 value 

176 The reference value. 

177 """ 

178 expression = self.getEnergyFunction() 

179 value = value_in_md_units(value) 

180 self.setEnergyFunction( 

181 expression.replace(f"refval={self._ref_val}", f"refval={value}") 

182 ) 

183 self._ref_val = value 

184 

185 

186ResidueCoordination.registerTag("!cvpack.ResidueCoordination")