Skip to content

Commit 8d20cee

Browse files
author
Charlles Abreu
committed
Included Python test
1 parent 3b3678c commit 8d20cee

File tree

1 file changed

+130
-5
lines changed

1 file changed

+130
-5
lines changed

python/openmmcppforces/tests/test_concerted_rmsd_force.py

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import math
21

3-
import openmmcppforces
2+
import typing as t
3+
4+
import openmmcppforces as mmcpp
45
import numpy as np
56
import openmm as mm
6-
import pytest
77
from openmm import unit
88

99

@@ -34,5 +34,130 @@ def assert_forces_and_energy(context, tol):
3434
ASSERT_EQUAL_TOL(state0.getPotentialEnergy(), state1.getPotentialEnergy(), tol)
3535

3636

37-
def test_place_holder():
38-
pass
37+
def estimate_rmsd(
38+
positions: t.Sequence[mm.Vec3],
39+
referencePos: t.Sequence[mm.Vec3],
40+
particles: t.Sequence[int],
41+
) -> float:
42+
# Estimate the RMSD. For simplicity we omit the orientation alignment, but they
43+
# should already be almost perfectly aligned.
44+
45+
center1 = mm.Vec3(0, 0, 0)
46+
center2 = mm.Vec3(0, 0, 0)
47+
for i in particles:
48+
center1 += referencePos[i]
49+
center2 += positions[i]
50+
center1 /= len(particles)
51+
center2 /= len(particles)
52+
estimate = 0.0
53+
for i in particles:
54+
delta = (referencePos[i] - center1) - (positions[i] - center2)
55+
estimate += delta.x * delta.x + delta.y * delta.y + delta.z * delta.z
56+
return np.sqrt(estimate/len(particles))
57+
58+
59+
def test_single_group_rmsd():
60+
numParticles = 20
61+
system = mm.System()
62+
random = np.random.default_rng(0)
63+
64+
def random_vec3():
65+
return mm.Vec3(random.random(), random.random(), random.random())
66+
67+
referencePos = []
68+
positions = []
69+
particles = []
70+
for i in range(numParticles):
71+
system.addParticle(1.0)
72+
refpos = 10 * random_vec3()
73+
referencePos.append(refpos)
74+
positions.append(refpos + 0.2 * random_vec3())
75+
if i % 5:
76+
particles.append(i)
77+
force = mmcpp.ConcertedRMSDForce(referencePos)
78+
79+
force.addGroup(particles)
80+
system.addForce(force)
81+
integrator = mm.VerletIntegrator(0.001)
82+
platform = mm.Platform.getPlatformByName("Reference")
83+
context = mm.Context(system, integrator, platform)
84+
context.setPositions(positions)
85+
estimate = estimate_rmsd(positions, referencePos, particles)
86+
87+
# Have the force compute the RMSD. It should be very slightly less than
88+
# what we calculated above (since that omitted the rotation).
89+
90+
state1 = context.getState(getEnergy=True)
91+
rmsd = state1.getPotentialEnergy().value_in_unit(unit.kilojoule_per_mole)
92+
ASSERT(rmsd <= estimate)
93+
ASSERT(rmsd > 0.9*estimate)
94+
95+
# Translate and rotate all the particles. This should have no effect on the RMSD.
96+
97+
transformedPos = []
98+
cs = np.cos(1.1)
99+
sn = np.sin(1.1)
100+
for i in range(numParticles):
101+
p = positions[i]
102+
transformedPos.append(
103+
mm.Vec3(cs*p[0] + sn*p[1] + 0.1, -sn*p[0] + cs*p[1] - 11.3, p[2] + 1.5)
104+
)
105+
context.setPositions(transformedPos)
106+
state1 = context.getState(getEnergy=True, getForces=True)
107+
ASSERT_EQUAL_TOL(rmsd, state1.getPotentialEnergy(), 1e-4)
108+
109+
# Take a small step in the direction of the energy gradient and see whether the
110+
# potential energy changes by the expected amount.
111+
112+
forces = state1.getForces().value_in_unit(unit.kilojoule_per_mole/unit.nanometer)
113+
norm = 0.0
114+
for f in forces:
115+
norm += f.x * f.x + f.y * f.y + f.z * f.z
116+
norm = np.sqrt(norm)
117+
stepSize = 0.1
118+
step = 0.5 * stepSize / norm
119+
positions2 = []
120+
positions3 = []
121+
for i in range(len(positions)):
122+
p = transformedPos[i]
123+
f = forces[i]
124+
positions2.append(
125+
mm.Vec3(p[0] - f[0] * step, p[1] - f[1] * step, p[2] - f[2] * step)
126+
)
127+
positions3.append(
128+
mm.Vec3(p[0] + f[0] * step, p[1] + f[1] * step, p[2] + f[2] * step)
129+
)
130+
context.setPositions(positions2)
131+
state2 = context.getState(getEnergy=True)
132+
rmsd2 = state2.getPotentialEnergy()
133+
context.setPositions(positions3)
134+
state3 = context.getState(getEnergy=True)
135+
rmsd3 = state3.getPotentialEnergy()
136+
ASSERT_EQUAL_TOL(norm, (rmsd2 - rmsd3)/stepSize, 1e-3)
137+
138+
# Check that updateParametersInContext() works correctly.
139+
140+
context.setPositions(transformedPos)
141+
force.setReferencePositions(transformedPos)
142+
force.updateParametersInContext(context)
143+
ASSERT_EQUAL_TOL(0.0, context.getState(getEnergy=True).getPotentialEnergy(), 1e-2)
144+
context.setPositions(referencePos)
145+
ASSERT_EQUAL_TOL(rmsd, context.getState(getEnergy=True).getPotentialEnergy(), 1e-4)
146+
147+
# Verify that giving an empty list of particles is interpreted to mean all
148+
# particles.
149+
150+
allParticles = list(range(numParticles))
151+
estimate = estimate_rmsd(positions, referencePos, allParticles)
152+
force.setGroup(0, allParticles)
153+
force.setReferencePositions(referencePos)
154+
force.updateParametersInContext(context)
155+
context.setPositions(positions);
156+
rmsd1 = context.getState(getEnergy=True).getPotentialEnergy()
157+
force.setGroup(0, [])
158+
force.updateParametersInContext(context)
159+
rmsd2 = context.getState(getEnergy=True).getPotentialEnergy()
160+
ASSERT_EQUAL_TOL(rmsd1, rmsd2, 1e-4)
161+
rmsd1 = rmsd1 / unit.kilojoule_per_mole
162+
ASSERT(rmsd1 <= estimate)
163+
ASSERT(rmsd1 > 0.9*estimate)

0 commit comments

Comments
 (0)