Commit 3b6925ae authored by Andy Simmonett's avatar Andy Simmonett Committed by GitHub
Browse files

Merge pull request #1 from peastman/ljpme

Cleanup to LJ PME code
parents 5a8a8aa9 f7a102fb
"""
Creates a subclass for all classes intended to be a singleton. This
maintains the correctness of instance is instance even following
pickling/unpickling
"""
class Singleton(object):
_inst = None
def __new__(cls):
if cls._inst is None:
cls._inst = super(Singleton, cls).__new__(cls)
return cls._inst
def __reduce__(self):
return repr(self)
......@@ -186,8 +186,7 @@ class Modeller(object):
def convertWater(self, model='tip3p'):
"""Convert all water molecules to a different water model.
@deprecated Use addExtraParticles() instead. It performs the same
function but in a more general way.
@deprecated Use addExtraParticles() instead. It performs the same function but in a more general way.
Parameters
----------
......@@ -871,7 +870,7 @@ class Modeller(object):
# and causes hydrogens to spread out evenly.
system = System()
nonbonded = CustomNonbondedForce('100/((r/0.1)^4+1)')
nonbonded = CustomNonbondedForce('100/(r/0.1)^4')
nonbonded.setNonbondedMethod(CustomNonbondedForce.CutoffNonPeriodic);
nonbonded.setCutoffDistance(1*nanometer)
bonds = HarmonicBondForce()
......@@ -1006,7 +1005,7 @@ class Modeller(object):
signature = _createResidueSignature([atom.element for atom in residue.atoms()])
if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]:
if _matchResidue(residue, t, bondedToAtom) is not None:
if _matchResidue(residue, t, bondedToAtom, False) is not None:
matchFound = True
if matchFound:
# Just copy the residue over.
......@@ -1025,7 +1024,7 @@ class Modeller(object):
if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]:
if t in templatesNoEP:
matches = _matchResidue(residueNoEP, templatesNoEP[t], bondedToAtomNoEP)
matches = _matchResidue(residueNoEP, templatesNoEP[t], bondedToAtomNoEP, False)
if matches is not None:
template = t;
# Record the corresponding atoms.
......
......@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2012-2015 Stanford University and the Authors.
Portions copyright (c) 2012-2016 Stanford University and the Authors.
Authors: Peter Eastman
Contributors:
......@@ -32,12 +32,41 @@ from __future__ import absolute_import
__author__ = "Peter Eastman"
__version__ = "1.0"
from collections import namedtuple
import os
import xml.etree.ElementTree as etree
from simtk.openmm.vec3 import Vec3
from simtk.openmm.app.internal.singleton import Singleton
from simtk.unit import nanometers, sqrt, is_quantity
from copy import deepcopy
# Enumerated values for bond type
class Single(Singleton):
def __repr__(self):
return 'Single'
Single = Single()
class Double(Singleton):
def __repr__(self):
return 'Double'
Double = Double()
class Triple(Singleton):
def __repr__(self):
return 'Triple'
Triple = Triple()
class Aromatic(Singleton):
def __repr__(self):
return 'Aromatic'
Aromatic = Aromatic()
class Amide(Singleton):
def __repr__(self):
return 'Amide'
Amide = Amide()
class Topology(object):
"""Topology stores the topological information about a system.
......@@ -155,7 +184,7 @@ class Topology(object):
residue._atoms.append(atom)
return atom
def addBond(self, atom1, atom2):
def addBond(self, atom1, atom2, type=None, order=None):
"""Create a new bond and add it to the Topology.
Parameters
......@@ -164,8 +193,13 @@ class Topology(object):
The first Atom connected by the bond
atom2 : Atom
The second Atom connected by the bond
type : object=None
The type of bond to add. Allowed values are None, Single, Double, Triple,
Aromatic, or Amide.
order : int=None
The bond order, or None if it is not specified
"""
self._bonds.append((atom1, atom2))
self._bonds.append(Bond(atom1, atom2, type, order))
def chains(self):
"""Iterate over all Chains in the Topology."""
......@@ -387,7 +421,7 @@ class Residue(object):
return "<Residue %d (%s) of chain %d>" % (self.index, self.name, self.chain.index)
class Atom(object):
"""An Atom object represents a residue within a Topology."""
"""An Atom object represents an atom within a Topology."""
def __init__(self, name, element, index, residue, id):
"""Construct a new Atom. You should call addAtom() on the Topology instead of calling this directly."""
......@@ -404,3 +438,32 @@ class Atom(object):
def __repr__(self):
return "<Atom %d (%s) of chain %d residue %d (%s)>" % (self.index, self.name, self.residue.chain.index, self.residue.index, self.residue.name)
class Bond(namedtuple('Bond', ['atom1', 'atom2'])):
"""A Bond object represents a bond between two Atoms within a Topology.
This class extends tuple, and may be interpreted as a 2 element tuple of Atom objects.
It also has fields that can optionally be used to describe the bond order and type of bond."""
def __new__(cls, atom1, atom2, type=None, order=None):
"""Create a new Bond. You should call addBond() on the Topology instead of calling this directly."""
bond = super(Bond, cls).__new__(cls, atom1, atom2)
bond.type = type
bond.order = order
return bond
def __getnewargs__(self):
"Support for pickle protocol 2: http://docs.python.org/2/library/pickle.html#pickling-and-unpickling-normal-class-instances"
return self[0], self[1], self.type, self.order
def __deepcopy__(self, memo):
return Bond(self[0], self[1], self.type, self.order)
def __repr__(self):
s = "Bond(%s, %s" % (self[0], self[1])
if self.type is not None:
s = "%s, type=%s" % (s, self.type)
if self.order is not None:
s = "%s, order=%d" % (s, self.order)
s += ")"
return s
......@@ -98,10 +98,11 @@ class MTSIntegrator(CustomIntegrator):
for i in range(stepsPerParentStep):
self.addComputePerDof("v", "v+0.5*(dt/"+str(substeps)+")*f"+str(group)+"/m")
if len(groups) == 1:
self.addComputePerDof("x1", "x")
self.addComputePerDof("x", "x+(dt/"+str(substeps)+")*v")
self.addComputePerDof("x1", "x")
self.addConstrainPositions();
self.addComputePerDof("v", "(x-x1)/(dt/"+str(substeps)+")");
self.addComputePerDof("v", "v+(x-x1)/(dt/"+str(substeps)+")");
self.addConstrainVelocities()
else:
self._createSubsteps(substeps, groups[1:])
self.addComputePerDof("v", "v+0.5*(dt/"+str(substeps)+")*f"+str(group)+"/m")
......@@ -74,6 +74,11 @@ def getNodeText(node):
s = "%s%s\n\n" % (s, getNodeText(n))
elif n.tag == "ref":
s = "%s%s" % (s, getNodeText(n))
elif n.tag == "xrefsect":
title = n.find("xreftitle")
description = n.find("xrefdescription")
if title is not None and description is not None and getNodeText(title).lower() == "deprecated":
s = "%s\n@deprecated %s\n\n" % (s, getNodeText(description))
else:
if n.tag in docTags:
tag = docTags[n.tag]
......@@ -242,6 +247,7 @@ class SwigInputBuilder:
self.fOut.write("\n/* Declare factories */\n\n")
forceSubclassList = []
integratorSubclassList = []
tabulatedFunctionSubclassList = []
for classNode in findNodes(self.doc.getroot(), "compounddef", kind="class", prot="public"):
className = getText("compoundname", classNode)
shortClassName=stripOpenmmPrefix(className)
......@@ -256,6 +262,8 @@ class SwigInputBuilder:
forceSubclassList.append(shortClassName)
elif baseName == 'OpenMM::Integrator':
integratorSubclassList.append(shortClassName)
elif baseName == 'OpenMM::TabulatedFunction':
tabulatedFunctionSubclassList.append(shortClassName)
self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce")
for name in sorted(forceSubclassList):
......@@ -292,6 +300,16 @@ class SwigInputBuilder:
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::TabulatedFunction* OpenMM::TabulatedFunction::__copy__")
for name in sorted(tabulatedFunctionSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::TabulatedFunction* OpenMM_XmlSerializer__deserializeTabulatedFunction")
for name in sorted(tabulatedFunctionSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::VirtualSite& OpenMM::System::getVirtualSite, OpenMM::TwoParticleAverageSite, OpenMM::ThreeParticleAverageSite, OpenMM::OutOfPlaneSite, OpenMM::LocalCoordinatesSite);\n\n")
self.fOut.write("\n")
......
......@@ -28,6 +28,7 @@
int getForces,
int getEnergy,
int getParameters,
int getParameterDerivatives,
int enforcePeriodic,
bitmask32t groups) {
State state;
......@@ -38,6 +39,7 @@
if (getForces) types |= State::Forces;
if (getEnergy) types |= State::Energy;
if (getParameters) types |= State::Parameters;
if (getParameterDerivatives) types |= State::ParameterDerivatives;
try {
state = self->getState(types, enforcePeriodic, groups);
}
......@@ -53,7 +55,7 @@
%pythoncode %{
def getState(self, getPositions=False, getVelocities=False,
getForces=False, getEnergy=False, getParameters=False,
enforcePeriodicBox=False, groups=-1):
getParameterDerivatives=False, enforcePeriodicBox=False, groups=-1):
"""Get a State object recording the current state information stored in this context.
Parameters
......@@ -66,8 +68,10 @@
whether to store the forces acting on particles in the State
getEnergy : bool=False
whether to store potential and kinetic energy in the State
getParameter : bool=False
getParameters : bool=False
whether to store context parameters in the State
getParameterDerivatives : bool=False
whether to store parameter derivatives in the State
enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position
is stored in the Context, regardless of periodic boundary conditions.
......@@ -79,9 +83,9 @@
can also be passed as an unsigned integer interpreted as a bitmask,
in which case group i will be included if (groups&(1<<i)) != 0.
"""
getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
getP, getV, getF, getE, getPa, getPd, enforcePeriodic = map(bool,
(getPositions, getVelocities, getForces, getEnergy, getParameters,
enforcePeriodicBox))
getParameterDerivatives, enforcePeriodicBox))
try:
# is the input integer-like?
......@@ -95,8 +99,8 @@
raise TypeError('%s is neither an int nor set' % groups)
(simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \
self._getStateAsLists(getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
forceList, paramMap, paramDerivMap) = \
self._getStateAsLists(getP, getV, getF, getE, getPa, getPd, enforcePeriodic, groups_mask)
state = State(simTime=simTime,
energy=energy,
......@@ -104,7 +108,8 @@
velList=velList,
forceList=forceList,
periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap)
paramMap=paramMap,
paramDerivMap=paramDerivMap)
return state
def setState(self, state):
......@@ -176,6 +181,7 @@ Parameters:
int getForces,
int getEnergy,
int getParameters,
int getParameterDerivatives,
int enforcePeriodic,
int groups) {
State state;
......@@ -186,6 +192,7 @@ Parameters:
if (getForces) types |= State::Forces;
if (getEnergy) types |= State::Energy;
if (getParameters) types |= State::Parameters;
if (getParameterDerivatives) types |= State::ParameterDerivatives;
try {
state = self->getState(copy, types, enforcePeriodic, groups);
}
......@@ -206,6 +213,7 @@ Parameters:
getForces=False,
getEnergy=False,
getParameters=False,
getParameterDerivatives=False,
enforcePeriodicBox=False,
groups=-1):
"""Get a State object recording the current state information about one copy of the system.
......@@ -222,8 +230,10 @@ Parameters:
whether to store the forces acting on particles in the State
getEnergy : bool=False
whether to store potential and kinetic energy in the State
getParameter : bool=False
getParameters : bool=False
whether to store context parameters in the State
getParameterDerivatives : bool=False
whether to store parameter derivatives in the State
enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position
is stored in the Context, regardless of periodic boundary conditions.
......@@ -235,9 +245,9 @@ Parameters:
can also be passed as an unsigned integer interpreted as a bitmask,
in which case group i will be included if (groups&(1<<i)) != 0.
"""
getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
getP, getV, getF, getE, getPa, getPd, enforcePeriodic = map(bool,
(getPositions, getVelocities, getForces, getEnergy, getParameters,
enforcePeriodicBox))
getParameterDerivatives, enforcePeriodicBox))
try:
# is the input integer-like?
......@@ -250,8 +260,8 @@ Parameters:
raise TypeError('%s is neither an int nor set' % groups)
(simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \
self._getStateAsLists(copy, getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
forceList, paramMap, paramDerivMap) = \
self._getStateAsLists(getP, getV, getF, getE, getPa, getPd, enforcePeriodic, groups_mask)
state = State(simTime=simTime,
energy=energy,
......@@ -259,7 +269,8 @@ Parameters:
velList=velList,
forceList=forceList,
periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap)
paramMap=paramMap,
paramDerivMap=paramDerivMap)
return state
%}
}
......@@ -311,14 +322,14 @@ Parameters:
}
%extend OpenMM::XmlSerializer {
%feature(docstring, "This method exists only for backward compatibility. @deprecated Use serialize() instead.") serializeSystem;
%feature(docstring, "This method exists only for backward compatibility.\n@deprecated Use serialize() instead.") serializeSystem;
static std::string serializeSystem(const OpenMM::System* object) {
std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::System>(object, "System", ss);
return ss.str();
}
%feature(docstring, "This method exists only for backward compatibility. @deprecated Use deserialize() instead.") deserializeSystem;
%feature(docstring, "This method exists only for backward compatibility.\n@deprecated Use deserialize() instead.") deserializeSystem;
%newobject deserializeSystem;
static OpenMM::System* deserializeSystem(const char* inputString) {
std::stringstream ss;
......@@ -352,6 +363,19 @@ Parameters:
return OpenMM::XmlSerializer::deserialize<OpenMM::Integrator>(ss);
}
static std::string _serializeTabulatedFunction(const OpenMM::TabulatedFunction* object) {
std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::TabulatedFunction>(object, "TabulatedFunction", ss);
return ss.str();
}
%newobject _deserializeTabulatedFunction;
static OpenMM::TabulatedFunction* _deserializeTabulatedFunction(const char* inputString) {
std::stringstream ss;
ss << inputString;
return OpenMM::XmlSerializer::deserialize<OpenMM::TabulatedFunction>(ss);
}
static std::string _serializeStateAsLists(
const std::vector<Vec3>& pos,
const std::vector<Vec3>& vel,
......@@ -361,8 +385,9 @@ Parameters:
double time,
const std::vector<Vec3>& boxVectors,
const std::map<string, double>& params,
const std::map<string, double>& paramDerivs,
int types) {
OpenMM::State myState = _convertListsToState(pos,vel,forces,kineticEnergy,potentialEnergy,time,boxVectors,params,types);
OpenMM::State myState = _convertListsToState(pos,vel,forces,kineticEnergy,potentialEnergy,time,boxVectors,params,paramDerivs,types);
std::stringstream buffer;
OpenMM::XmlSerializer::serialize<OpenMM::State>(&myState, "State", buffer);
return buffer.str();
......@@ -386,6 +411,7 @@ Parameters:
kineticEnergy = 0.0
potentialEnergy = 0.0
params = {}
paramDerivs = {}
types = 0
try:
positions = pythonState.getPositions().value_in_unit(unit.nanometers)
......@@ -413,16 +439,21 @@ Parameters:
types |= 16
except:
pass
try:
params = pythonState.getEnergyParameterDerivatives()
types |= 32
except:
pass
time = pythonState.getTime().value_in_unit(unit.picoseconds)
boxVectors = pythonState.getPeriodicBoxVectors().value_in_unit(unit.nanometers)
string = XmlSerializer._serializeStateAsLists(positions, velocities, forces, kineticEnergy, potentialEnergy, time, boxVectors, params, types)
string = XmlSerializer._serializeStateAsLists(positions, velocities, forces, kineticEnergy, potentialEnergy, time, boxVectors, params, paramDerivs, types)
return string
@staticmethod
def _deserializeState(pythonString):
(simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = XmlSerializer._deserializeStringIntoLists(pythonString)
forceList, paramMap, paramDerivMap) = XmlSerializer._deserializeStringIntoLists(pythonString)
state = State(simTime=simTime,
energy=energy,
......@@ -430,7 +461,8 @@ Parameters:
velList=velList,
forceList=forceList,
periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap)
paramMap=paramMap,
paramDerivMap=paramDerivMap)
return state
@staticmethod
......@@ -444,6 +476,8 @@ Parameters:
return XmlSerializer._serializeIntegrator(object)
elif isinstance(object, State):
return XmlSerializer._serializeState(object)
elif isinstance(object, TabulatedFunction):
return XmlSerializer._serializeTabulatedFunction(object)
raise ValueError("Unsupported object type")
@staticmethod
......@@ -462,6 +496,8 @@ Parameters:
return XmlSerializer._deserializeIntegrator(inputString)
if type == "State":
return XmlSerializer._deserializeState(inputString)
if type == "TabulatedFunction":
return XmlSerializer._deserializeTabulatedFunction(inputString)
raise ValueError("Unsupported object type")
%}
}
......@@ -511,3 +547,22 @@ Parameters:
return OpenMM::XmlSerializer::clone<OpenMM::Integrator>(*self);
}
}
%extend OpenMM::TabulatedFunction {
%pythoncode %{
def __getstate__(self):
serializationString = XmlSerializer.serialize(self)
return serializationString
def __setstate__(self, serializationString):
system = XmlSerializer.deserialize(serializationString)
self.this = system.this
def __deepcopy__(self, memo):
return self.__copy__()
%}
%newobject __copy__;
OpenMM::TabulatedFunction* __copy__() {
return OpenMM::XmlSerializer::clone<OpenMM::TabulatedFunction>(*self);
}
}
......@@ -28,6 +28,7 @@ State _convertListsToState( const std::vector<Vec3> &pos,
double time,
const std::vector<Vec3> &boxVectors,
const std::map<std::string, double> &params,
const std::map<std::string, double> &paramDerivs,
int types ) {
State::StateBuilder sb(time);
if(types & State::Positions)
......@@ -40,6 +41,8 @@ State _convertListsToState( const std::vector<Vec3> &pos,
sb.setEnergy(kineticEnergy, potentialEnergy);
if(types & State::Parameters)
sb.setParameters(params);
if(types & State::ParameterDerivatives)
sb.setEnergyParameterDerivatives(paramDerivs);
sb.setPeriodicBoxVectors(boxVectors[0], boxVectors[1], boxVectors[2]);
return sb.getState();
}
......@@ -53,6 +56,7 @@ PyObject *_convertStateToLists(const State& state) {
PyObject *pForces;
PyObject *pyTuple;
PyObject *pParameters;
PyObject *pParameterDerivs;
simTime=state.getTime();
OpenMM::Vec3 myVecA;
......@@ -112,11 +116,21 @@ PyObject *_convertStateToLists(const State& state) {
pParameters = Py_None;
Py_INCREF(Py_None);
}
try {
pParameterDerivs = PyDict_New();
const std::map<std::string, double>& params = state.getEnergyParameterDerivatives();
for (std::map<std::string, double>::const_iterator iter = params.begin(); iter != params.end(); ++iter)
PyDict_SetItemString(pParameterDerivs, iter->first.c_str(), Py_BuildValue("d", iter->second));
}
catch (std::exception& ex) {
pParameterDerivs = Py_None;
Py_INCREF(Py_None);
}
pyTuple=Py_BuildValue("(d,N,N,N,N,N,N)",
pyTuple=Py_BuildValue("(d,N,N,N,N,N,N,N)",
simTime, pPeriodicBoxVectorsList, pEnergy,
pPositions, pVelocities,
pForces, pParameters);
pForces, pParameters, pParameterDerivs);
return pyTuple;
}
......
......@@ -63,7 +63,8 @@ class State(_object):
velList=None,
forceList=None,
periodicBoxVectorsList=None,
paramMap=None):
paramMap=None,
paramDerivMap=None):
self._simTime=simTime
self._periodicBoxVectorsList=periodicBoxVectorsList
self._periodicBoxVectorsListNumpy=None
......@@ -80,6 +81,7 @@ class State(_object):
self._forceList=forceList
self._forceListNumpy=None
self._paramMap=paramMap
self._paramDerivMap=paramDerivMap
def __getstate__(self):
serializationString = XmlSerializer.serialize(self)
......@@ -221,6 +223,18 @@ class State(_object):
raise TypeError('Parameters were not requested in getState() call, so are not available.')
return self._paramMap
def getEnergyParameterDerivatives(self):
"""Get a map containing derivatives of the potential energy with respect to context parameters.
In most cases derivatives are only calculated if the corresponding Force objects have been
specifically told to compute them. Otherwise, the values in the map will be zero. Likewise,
if multiple Forces depend on the same parameter but only some have been told to compute
derivatives with respect to it, the returned value will include only the contributions from
the Forces that were told to compute it."""
if self._paramDerivMap is None:
raise TypeError('Parameter derivatives were not requested in getState() call, so are not available.')
return self._paramDerivMap
%}
%pythonappend OpenMM::Context::Context %{
......
......@@ -17,6 +17,17 @@ class TestDCDFile(unittest.TestCase):
for i in range(5):
dcd.writeModel([mm.Vec3(random(), random(), random()) for j in range(natom)]*unit.angstroms)
os.remove(fname)
def testLongTrajectory(self):
"""Test writing a trajectory that has more than 2^31 steps."""
fname = tempfile.mktemp(suffix='.dcd')
pdbfile = app.PDBFile('systems/alanine-dipeptide-implicit.pdb')
natom = len(list(pdbfile.topology.atoms()))
with open(fname, 'wb') as f:
dcd = app.DCDFile(f, pdbfile.topology, 0.001, interval=1000000000)
for i in range(5):
dcd.writeModel([mm.Vec3(random(), random(), random()) for j in range(natom)]*unit.angstroms)
os.remove(fname)
if __name__ == '__main__':
unittest.main()
......@@ -748,6 +748,19 @@ class TestForceField(unittest.TestCase):
expected = 0.3*ljEnergy(2.5, 1.1, 3) + 0.3*ljEnergy(3.5, sqrt(0.1), 3) + ljEnergy(3.5, 1.5, 4)
self.assertAlmostEqual(expected, context.getState(getEnergy=True).getPotentialEnergy().value_in_unit(kilojoules_per_mole))
def test_IgnoreExternalBonds(self):
"""Test the ignoreExternalBonds option"""
modeller = Modeller(self.pdb2.topology, self.pdb2.positions)
modeller.delete([next(modeller.topology.residues())])
self.assertRaises(Exception, lambda: self.forcefield2.createSystem(modeller.topology))
system = self.forcefield2.createSystem(modeller.topology, ignoreExternalBonds=True)
templates = self.forcefield2.getMatchingTemplates(modeller.topology, ignoreExternalBonds=True)
self.assertEqual(2, len(templates))
self.assertEqual('ALA', templates[0].name)
self.assertEqual('NME', templates[1].name)
class AmoebaTestForceField(unittest.TestCase):
"""Test the ForceField.createSystem() method with the AMOEBA forcefield."""
......
import unittest
from validateConstraints import *
from simtk.openmm.app import *
from simtk.openmm import *
from simtk.unit import *
import simtk.openmm.app.element as elem
import simtk.openmm.app.forcefield as forcefield
import math
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import os
import warnings
class TestGenerators(unittest.TestCase):
"""Test the various generators found in forcefield.py."""
def setUp(self):
# alanine dipeptide with implicit water
self.pdb1 = PDBFile('systems/alanine-dipeptide-implicit.pdb')
def test_CustomHbondGenerator(self):
"""Test the generator for CustomHbondForce."""
for bondCutoff in range(4):
xml = """
<ForceField>
<CustomHbondForce energy="a*b*distance(a1,d1)" particlesPerDonor="3" particlesPerAcceptor="2" bondCutoff="%d">
<PerDonorParameter name="a"/>
<PerAcceptorParameter name="b"/>
<Donor class1="C" class2="N" class3="H" a="3"/>
<Acceptor class1="C" class2="O" b="2"/>
<Function name="test" min="1" max="2" type="Continuous1D">
0 1 2 3 4 5
</Function>
</CustomHbondForce>
</ForceField>""" % bondCutoff
ff = ForceField('amber99sb.xml', StringIO(xml))
system = ff.createSystem(self.pdb1.topology)
hbond = [f for f in system.getForces() if isinstance(f, CustomHbondForce)][0]
self.assertEqual(1, hbond.getNumPerDonorParameters())
self.assertEqual(1, hbond.getNumPerAcceptorParameters())
self.assertEqual('a', hbond.getPerDonorParameterName(0))
self.assertEqual('b', hbond.getPerAcceptorParameterName(0))
self.assertEqual(1, hbond.getNumTabulatedFunctions())
expectedDonors = [(4,6,7), (14,16,17)]
expectedAcceptors = [(4,5,-1), (14,15,-1)]
self.assertEqual(len(expectedDonors), hbond.getNumDonors())
self.assertEqual(len(expectedAcceptors), hbond.getNumAcceptors())
for i in range(hbond.getNumDonors()):
atom1, atom2, atom3, params = hbond.getDonorParameters(i)
self.assertTrue((atom1, atom2, atom3) in expectedDonors)
self.assertEqual((3.0,), params)
for i in range(hbond.getNumAcceptors()):
atom1, atom2, atom3, params = hbond.getAcceptorParameters(i)
self.assertTrue((atom1, atom2, atom3) in expectedAcceptors)
self.assertEqual((2.0,), params)
expectedExclusions = [(0,0), (1,1)]
if bondCutoff >= 2:
expectedExclusions.append((0,1))
if bondCutoff >= 3:
expectedExclusions.append((1,0))
self.assertEqual(len(expectedExclusions), hbond.getNumExclusions())
for i in range(hbond.getNumExclusions()):
self.assertTrue(tuple(hbond.getExclusionParticles(i)) in expectedExclusions)
def test_CustomHbondGenerator2(self):
"""Test the generator for CustomHbondForce with different parameters."""
xml = """
<ForceField>
<CustomHbondForce energy="a*b*distance(a1,d1)" particlesPerDonor="2" particlesPerAcceptor="1" bondCutoff="0">
<PerDonorParameter name="a"/>
<PerAcceptorParameter name="b"/>
<Donor class1="N" class2="H" a="3"/>
<Acceptor class1="O" b="2"/>
</CustomHbondForce>
</ForceField>"""
ff = ForceField('amber99sb.xml', StringIO(xml))
system = ff.createSystem(self.pdb1.topology)
hbond = [f for f in system.getForces() if isinstance(f, CustomHbondForce)][0]
self.assertEqual(1, hbond.getNumPerDonorParameters())
self.assertEqual(1, hbond.getNumPerAcceptorParameters())
self.assertEqual('a', hbond.getPerDonorParameterName(0))
self.assertEqual('b', hbond.getPerAcceptorParameterName(0))
expectedDonors = [(6,7,-1), (16,17,-1)]
expectedAcceptors = [(5,-1,-1), (15,-1,-1)]
self.assertEqual(len(expectedDonors), hbond.getNumDonors())
self.assertEqual(len(expectedAcceptors), hbond.getNumAcceptors())
for i in range(hbond.getNumDonors()):
atom1, atom2, atom3, params = hbond.getDonorParameters(i)
self.assertTrue((atom1, atom2, atom3) in expectedDonors)
self.assertEqual((3.0,), params)
for i in range(hbond.getNumAcceptors()):
atom1, atom2, atom3, params = hbond.getAcceptorParameters(i)
self.assertTrue((atom1, atom2, atom3) in expectedAcceptors)
self.assertEqual((2.0,), params)
if __name__ == '__main__':
unittest.main()
import unittest
import tempfile
from datetime import datetime, timedelta
from simtk.openmm import *
from simtk.openmm.app import *
from simtk.unit import *
import math, random
class TestIntegrators(unittest.TestCase):
"""Test Python Integrator classes"""
def testMTSIntegratorExplicit(self):
"""Test the MTS integrator on an explicit solvent system"""
# Create a periodic solvated system with PME
pdb = PDBFile('systems/alanine-dipeptide-explicit.pdb')
ff = ForceField('amber99sbildn.xml', 'tip3p.xml')
system = ff.createSystem(pdb.topology, cutoffMethod=PME)
# Split forces into groups
for force in system.getForces():
if force.__class__.__name__ == 'NonbondedForce':
force.setForceGroup(1)
force.setReciprocalSpaceForceGroup(2)
else:
force.setForceGroup(0)
# Create an integrator
integrator = MTSIntegrator(4*femtoseconds, [(2,1), (1,2), (0,8)])
# Run a few steps of dynamics
context = Context(system, integrator)
context.setPositions(pdb.positions)
integrator.step(10)
# Ensure energy is well-behaved.
state = context.getState(getEnergy=True)
if not (state.getPotentialEnergy() / kilojoules_per_mole < 0.0):
raise Exception('Potential energy of alanine dipeptide system with MTS integrator is blowing up: %s' % str(state.getPotentialEnergy()))
def testMTSIntegratorConstraints(self):
"""Test the MTS integrator energy conservation on a system of constrained particles with no inner force (just constraints)"""
# Create a constrained test system
numParticles = 8
numConstraints = 5
system = System()
force = NonbondedForce()
for i in range(numParticles):
system.addParticle(5.0 if i%2==0 else 10.0)
force.addParticle((0.2 if i%2==0 else -0.2), 0.5, 5.0);
system.addConstraint(0, 1, 1.0);
system.addConstraint(1, 2, 1.0);
system.addConstraint(2, 3, 1.0);
system.addConstraint(4, 5, 1.0);
system.addConstraint(6, 7, 1.0);
system.addForce(force)
# Create integrator where inner timestep just evaluates constraints
integrator = MTSIntegrator(1*femtoseconds, [(1,1), (0,4)])
integrator.setConstraintTolerance(1e-5);
positions = [ (i/2., (i+1)/2., 0.) for i in range(numParticles) ]
velocities = [ (random.random()-0.5, random.random()-0.5, random.random()-0.5) for i in range(numParticles) ]
# Create Context
platform = Platform.getPlatformByName('Reference')
context = Context(system, integrator, platform)
context.setPositions(positions)
context.setVelocities(velocities)
context.applyConstraints(1e-5)
# Simulate it and see whether the constraints remain satisfied.
CONSTRAINT_RELATIVE_TOLERANCE = 1.e-4 # relative constraint violation tolerance
ENERGY_RELATIVE_TOLERANCE = 1.e-2 # relative energy violation tolerance
for i in range(1000):
state = context.getState(getPositions=True, getEnergy=True)
positions = state.getPositions()
for j in range(numConstraints):
[particle1, particle2, constraint_distance] = system.getConstraintParameters(j)
current_distance = 0.0 * nanometers**2
for k in range(3):
current_distance += (positions[particle1][k] - positions[particle2][k])**2
current_distance = sqrt(current_distance)
# Fail test if outside of relative tolerance
relative_violation = (current_distance - constraint_distance) / constraint_distance
if (relative_violation > CONSTRAINT_RELATIVE_TOLERANCE):
raise Exception('Constrained distance is violated by relative tolerance of %f (constraint %s actual %s)' % (relative_violation, str(constraint_distance), str(current_distance)))
# Check total energy
total_energy = state.getPotentialEnergy() + state.getKineticEnergy()
if (i == 1):
initial_energy = total_energy
elif (i > 1):
relative_violation = abs((total_energy - initial_energy) / initial_energy)
if (relative_violation > ENERGY_RELATIVE_TOLERANCE):
raise Exception('Total energy is violated by relative tolerance of %f on step %d (initial %s final %s)' % (relative_violation, i, str(initial_energy), str(total_energy)))
# Take a step
integrator.step(1)
if __name__ == '__main__':
unittest.main()
import pickle
import sys
import unittest
from simtk.openmm.app import *
......@@ -26,6 +27,14 @@ class TestTopology(unittest.TestCase):
"""Test getters for number of atoms, residues, chains."""
self.check_pdbfile('systems/1T2Y.pdb', 271, 25, 1)
def test_bondtype_singleton(self):
""" Tests that the bond types are really singletons """
self.assertIs(Single, pickle.loads(pickle.dumps(Single)))
self.assertIs(Double, pickle.loads(pickle.dumps(Double)))
self.assertIs(Triple, pickle.loads(pickle.dumps(Triple)))
self.assertIs(Aromatic, pickle.loads(pickle.dumps(Aromatic)))
self.assertIs(Amide, pickle.loads(pickle.dumps(Amide)))
def test_residue_bonds(self):
"""Test retrieving bonds for a residue produces expected results."""
# Create a test topology
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment