mirror of
https://github.com/openmm/openmm
synced 2026-06-03 06:39:48 +09:00
198 lines
7.9 KiB
Python
198 lines
7.9 KiB
Python
import unittest
|
|
from openmm import *
|
|
from openmm.unit import *
|
|
import numpy as np
|
|
import copy
|
|
|
|
def compute(state):
|
|
"""This is a computation function used by the test cases."""
|
|
pos = state.getPositions(asNumpy=True).value_in_unit(nanometer)
|
|
k = state.getParameters()['k']
|
|
energy = k*np.sum(pos*pos)
|
|
force = -0.5*k*pos
|
|
return energy*kilojoules_per_mole, force*kilojoules_per_mole/nanometer
|
|
|
|
class TestPythonForce(unittest.TestCase):
|
|
"""Test the PythonForce class"""
|
|
|
|
def testComputeForce(self):
|
|
"""Test using PythonForce to compute forces."""
|
|
system = System()
|
|
for i in range(5):
|
|
system.addParticle(1.0)
|
|
force = PythonForce(compute, {'k':2.5})
|
|
system.addForce(force)
|
|
positions = np.random.rand(5, 3)
|
|
for i in range(Platform.getNumPlatforms()):
|
|
integrator = VerletIntegrator(0.001)
|
|
try:
|
|
context = Context(system, integrator, Platform.getPlatform(i))
|
|
except OpenMMException:
|
|
if i == 0:
|
|
raise
|
|
else:
|
|
# This happens on CI when no GPU is available.
|
|
continue
|
|
context.setPositions(positions)
|
|
state = context.getState(energy=True, forces=True)
|
|
self.assertAlmostEqual(2.5*np.sum(positions*positions), state.getPotentialEnergy().value_in_unit(kilojoules_per_mole), places=5)
|
|
self.assertTrue(np.allclose(-1.25*positions, state.getForces(asNumpy=True).value_in_unit(kilojoules_per_mole/nanometer)))
|
|
|
|
def testParticleSubset(self):
|
|
"""Test a PythonForce appled to a subset of particles."""
|
|
system = System()
|
|
for i in range(10):
|
|
system.addParticle(1.0)
|
|
force = PythonForce(compute, {'k':2.5})
|
|
particles = [1,3,5,7,9]
|
|
force.setParticles(particles)
|
|
system.addForce(force)
|
|
positions = np.random.rand(10, 3)
|
|
for i in range(Platform.getNumPlatforms()):
|
|
integrator = VerletIntegrator(0.001)
|
|
try:
|
|
context = Context(system, integrator, Platform.getPlatform(i))
|
|
except OpenMMException:
|
|
if i == 0:
|
|
raise
|
|
else:
|
|
# This happens on CI when no GPU is available.
|
|
continue
|
|
context.setPositions(positions)
|
|
state = context.getState(energy=True, forces=True)
|
|
filtered = np.zeros(positions.shape)
|
|
filtered[particles] = positions[particles]
|
|
self.assertAlmostEqual(2.5*np.sum(filtered*filtered), state.getPotentialEnergy().value_in_unit(kilojoules_per_mole), places=5)
|
|
self.assertTrue(np.allclose(-1.25*filtered, state.getForces(asNumpy=True).value_in_unit(kilojoules_per_mole/nanometer)))
|
|
|
|
def testExceptions(self):
|
|
"""Test that PythonForce handles exceptions correctly."""
|
|
def compute2(state):
|
|
raise ValueError('This should fail')
|
|
|
|
system = System()
|
|
system.addParticle(1.0)
|
|
force = PythonForce(compute2)
|
|
system.addForce(force)
|
|
positions = np.random.rand(1, 3)
|
|
for i in range(Platform.getNumPlatforms()):
|
|
integrator = VerletIntegrator(0.001)
|
|
try:
|
|
context = Context(system, integrator, Platform.getPlatform(i))
|
|
except OpenMMException:
|
|
if i == 0:
|
|
raise
|
|
else:
|
|
# This happens on CI when no GPU is available.
|
|
continue
|
|
context.setPositions(positions)
|
|
with self.assertRaises(OpenMMException) as cm:
|
|
context.getState(energy=True)
|
|
self.assertEqual('This should fail', str(cm.exception))
|
|
|
|
def testSerialize(self):
|
|
"""Test that PythonForce can be serialized."""
|
|
force1 = PythonForce(compute, {'k':2.5})
|
|
force1.setUsesPeriodicBoundaryConditions(True)
|
|
force1.setParticles([1,3,5])
|
|
|
|
# Make a copy by serializing and the deserializing it.
|
|
|
|
force2 = copy.copy(force1)
|
|
|
|
# They should be identical.
|
|
|
|
self.assertEqual(XmlSerializer.serialize(force1), XmlSerializer.serialize(force2))
|
|
self.assertEqual(dict(force2.getGlobalParameters()), {'k':2.5})
|
|
self.assertEqual(force1.getParticles(), force2.getParticles())
|
|
self.assertTrue(force2.usesPeriodicBoundaryConditions())
|
|
|
|
# A locally defined function cannot be pickled. We should not be able to serialize a force
|
|
# that uses it.
|
|
|
|
def compute2(state):
|
|
return 1.0, np.zeros(len(state.getPositions()), 3)
|
|
|
|
force3 = PythonForce(compute2)
|
|
with self.assertRaises(OpenMMException):
|
|
XmlSerializer.serialize(force3)
|
|
|
|
def testMinimization(self):
|
|
"""Test that PythonForce works correctly with the minimizer."""
|
|
system = System()
|
|
for i in range(5):
|
|
system.addParticle(1.0)
|
|
force = PythonForce(compute, {'k':2.5})
|
|
system.addForce(force)
|
|
positions = np.random.rand(5, 3)
|
|
integrator = VerletIntegrator(0.001)
|
|
context = Context(system, integrator, Platform.getPlatform('Reference'))
|
|
context.setPositions(positions)
|
|
|
|
# The PythonForce and the MinimizationReporter both involve calling back into Python code,
|
|
# possibly from different threads. Make sure it doesn't cause any problems.
|
|
|
|
class Reporter(MinimizationReporter):
|
|
count = 0
|
|
def report(self, iteration, x, grad, args):
|
|
self.count += 1
|
|
return False
|
|
|
|
reporter = Reporter()
|
|
LocalEnergyMinimizer.minimize(context, tolerance=1e-3, reporter=reporter)
|
|
self.assertTrue(reporter.count > 0)
|
|
state = context.getState(energy=True, positions=True)
|
|
self.assertAlmostEqual(0.0, state.getPotentialEnergy().value_in_unit(kilojoules_per_mole))
|
|
|
|
def testMemory(self):
|
|
"""Test for memory leaks in the Python/C++ interface."""
|
|
try:
|
|
import resource
|
|
except:
|
|
# The resource module is not available on Windows.
|
|
return
|
|
system = System()
|
|
for i in range(1000):
|
|
system.addParticle(1.0)
|
|
force = PythonForce(compute, {'k':2.5})
|
|
system.addForce(force)
|
|
positions = np.random.rand(1000, 3)
|
|
integrator = VerletIntegrator(0.001)
|
|
context = Context(system, integrator, Platform.getPlatform('Reference'))
|
|
context.setPositions(positions)
|
|
integrator.step(5000)
|
|
memory1 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
integrator.step(5000)
|
|
memory2 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
|
self.assertTrue(memory2 < 1.05*memory1)
|
|
|
|
def testDtypes(self):
|
|
"""Test returning forces with different types."""
|
|
for dtype in [np.float32, np.float64, int]:
|
|
def compute2(state):
|
|
return 0, np.array([[1,2,3],[4,5,6]], dtype=dtype)
|
|
|
|
system = System()
|
|
system.addParticle(1.0)
|
|
system.addParticle(1.0)
|
|
force = PythonForce(compute2)
|
|
system.addForce(force)
|
|
positions = np.random.rand(2, 3)
|
|
for i in range(Platform.getNumPlatforms()):
|
|
integrator = VerletIntegrator(0.001)
|
|
try:
|
|
context = Context(system, integrator, Platform.getPlatform(i))
|
|
except OpenMMException:
|
|
if i == 0:
|
|
raise
|
|
else:
|
|
# This happens on CI when no GPU is available.
|
|
continue
|
|
context.setPositions(positions)
|
|
forces = context.getState(forces=True).getForces().value_in_unit(kilojoules_per_mole/nanometer)
|
|
self.assertEqual(Vec3(1,2,3), forces[0])
|
|
self.assertEqual(Vec3(4,5,6), forces[1])
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|