Coverage for openxps/system.py: 71%

35 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-13 22:08 +0000

1""" 

2.. module:: openxps.system 

3 :platform: Linux, MacOS, Windows 

4 :synopsis: System for extended phase-space simulations with OpenMM. 

5 

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

7 

8""" 

9 

10import typing as t 

11 

12import numpy as np 

13import openmm as mm 

14from openmm import unit as mmunit 

15 

16from .couplings import Coupling 

17from .dynamical_variable import DynamicalVariable 

18 

19 

20class ExtendedSpaceSystem(mm.System): 

21 """An :OpenMM:`System` object that includes extra dynamical variables (DVs) and 

22 allows for extended phase-space (XPS) simulations. 

23 

24 Parameters 

25 ---------- 

26 system 

27 The :OpenMM:`System` to be used in the XPS simulation. 

28 coupling 

29 A :class:`Coupling` object, required to couple the physical and extended 

30 phase-space systems. The dynamical variables are obtained from this coupling. 

31 

32 Keyword Arguments 

33 ------------------ 

34 tether_period 

35 The period of oscillation of a harmonic potential that tethers the y and z 

36 coordinates of the extension system particles to the origin in the yz-plane. 

37 This is not necessary in typical XPS simulations. 

38 

39 Example 

40 ------- 

41 >>> import openxps as xps 

42 >>> from math import pi 

43 >>> import openmm 

44 >>> import cvpack 

45 >>> from openmm import unit 

46 >>> from openmmtools import testsystems 

47 >>> model = testsystems.AlanineDipeptideVacuum() 

48 >>> mass = 3 * unit.dalton*(unit.nanometer/unit.radian)**2 

49 >>> phi0 = xps.DynamicalVariable("phi0", unit.radian, mass, xps.CircularBounds()) 

50 >>> harmonic_force = xps.HarmonicCoupling( 

51 ... cvpack.Torsion(6, 8, 14, 16, name="phi"), 

52 ... phi0, 

53 ... 1000 * unit.kilojoules_per_mole / unit.radian**2, 

54 ... ) 

55 >>> system = xps.ExtendedSpaceSystem(model.system, harmonic_force) 

56 >>> system.getDynamicalVariables() 

57 (DynamicalVariable(name='phi0', unit=rad, mass=3 nm**2 Da/(rad**2), bounds=...),) 

58 >>> system.getExtensionSystem().getNumParticles() 

59 1 

60 """ 

61 

62 def __init__( 

63 self, 

64 system: mm.System, 

65 coupling: Coupling, 

66 *, 

67 tether_period: t.Optional[mmunit.Quantity] = None, 

68 ) -> None: 

69 self._coupling = coupling 

70 coupling.addToPhysicalSystem(system) 

71 self.this = system.this 

72 self._extension_system = mm.System() 

73 for dv in coupling.getDynamicalVariables(): 

74 self._extension_system.addParticle(dv.mass / dv.mass.unit) 

75 coupling.addToExtensionSystem(self._extension_system) 

76 if tether_period is not None: 

77 self._tethering_force = self._create_tethering_force(tether_period) 

78 self._extension_system.addForce(self._tethering_force) 

79 else: 

80 self._tethering_force = None 

81 

82 def _create_tethering_force( 

83 self, tether_period: mmunit.Quantity 

84 ) -> mm.CustomExternalForce: 

85 """Create a force tethering all particles to the origin in the yz-plane.""" 

86 tethering_force = mm.CustomExternalForce( 

87 "0.5*kappa*(y^2 + z^2); kappa=mass_4_pi_sq/tether_period^2" 

88 ) 

89 tethering_force.setName("Tethering") 

90 tethering_force.addGlobalParameter("tether_period", tether_period) 

91 tethering_force.addPerParticleParameter("mass_4_pi_sq") 

92 for index, dv in enumerate(self._coupling.getDynamicalVariables()): 

93 tethering_force.addParticle(index, [4 * np.pi**2 * dv.mass / dv.mass.unit]) 

94 return tethering_force 

95 

96 def getDynamicalVariables(self) -> tuple[DynamicalVariable]: 

97 """ 

98 Get the dynamical variables included in the extended phase-space system. 

99 

100 Returns 

101 ------- 

102 t.Tuple[DynamicalVariable] 

103 A tuple containing the dynamical variables. 

104 """ 

105 return tuple(self._coupling.getDynamicalVariables()) 

106 

107 def getCoupling(self) -> Coupling: 

108 """ 

109 Get the coupling included in the extended phase-space system. 

110 

111 Returns 

112 ------- 

113 Coupling 

114 The coupling. 

115 """ 

116 return self._coupling 

117 

118 def getExtensionSystem(self) -> mm.System: 

119 """ 

120 Get the extension system included in the extended phase-space system. 

121 

122 Returns 

123 ------- 

124 mm.System 

125 The extension system. 

126 """ 

127 return self._extension_system 

128 

129 def getTetheringForce(self) -> t.Optional[mm.CustomExternalForce]: 

130 """ 

131 Get the tethering force included in the extended phase-space system. 

132 

133 If no tethering force has been added, returns None. 

134 

135 Returns 

136 ------- 

137 t.Optional[mm.CustomExternalForce] 

138 The tethering force. 

139 """ 

140 return self._tethering_force