Commit 51b7f9e2 authored by Robert McGibbon's avatar Robert McGibbon
Browse files

Merge master

parents 85bfd73c be0387b6
...@@ -141,6 +141,7 @@ STEAL_OWNERSHIP = {("Platform", "registerPlatform") : [0], ...@@ -141,6 +141,7 @@ STEAL_OWNERSHIP = {("Platform", "registerPlatform") : [0],
("CustomHbondForce", "addTabulatedFunction") : [1], ("CustomHbondForce", "addTabulatedFunction") : [1],
("CustomCompoundBondForce", "addTabulatedFunction") : [1], ("CustomCompoundBondForce", "addTabulatedFunction") : [1],
("CustomManyParticleForce", "addTabulatedFunction") : [1], ("CustomManyParticleForce", "addTabulatedFunction") : [1],
("CompoundIntegrator", "addIntegrator") : [0],
} }
# This is a list of units to attach to return values and method args. # This is a list of units to attach to return values and method args.
...@@ -308,6 +309,7 @@ UNITS = { ...@@ -308,6 +309,7 @@ UNITS = {
("AmoebaWcaDispersionForce", "getShctd") : ( None, ()), ("AmoebaWcaDispersionForce", "getShctd") : ( None, ()),
("Context", "getParameter") : (None, ()), ("Context", "getParameter") : (None, ()),
("Context", "getParameters") : (None, ()),
("Context", "getMolecules") : (None, ()), ("Context", "getMolecules") : (None, ()),
("CMAPTorsionForce", "getMapParameters") : (None, (None, 'unit.kilojoule_per_mole')), ("CMAPTorsionForce", "getMapParameters") : (None, (None, 'unit.kilojoule_per_mole')),
("CMAPTorsionForce", "getTorsionParameters") : (None, ()), ("CMAPTorsionForce", "getTorsionParameters") : (None, ()),
......
...@@ -51,33 +51,33 @@ ...@@ -51,33 +51,33 @@
%pythoncode %{ %pythoncode %{
def getState(self, def getState(self, getPositions=False, getVelocities=False,
getPositions=False, getForces=False, getEnergy=False, getParameters=False,
getVelocities=False, enforcePeriodicBox=False, groups=-1):
getForces=False, """Get a State object recording the current state information stored in this context.
getEnergy=False,
getParameters=False, Parameters
enforcePeriodicBox=False, ----------
groups=-1): getPositions : bool=False
""" whether to store particle positions in the State
getState(self, getPositions=False, getVelocities=False, getForces=False, getVelocities : bool=False
getEnergy=False, getParameters=False, enforcePeriodicBox=False, whether to store particle velocities in the State
groups=-1) -> State getForces : bool=False
whether to store the forces acting on particles in the State
Get a State object recording the current state information stored in this context. getEnergy : bool=False
whether to store potential and kinetic energy in the State
Parameters: getParameter : bool=False
- getPositions (bool=False) whether to store particle positions in the State whether to store context parameters in the State
- getVelocities (bool=False) whether to store particle velocities in the State enforcePeriodicBox : bool=False
- getForces (bool=False) whether to store the forces acting on particles in the State if false, the position of each particle will be whatever position
- getEnergy (bool=False) whether to store potential and kinetic energy in the State is stored in the Context, regardless of periodic boundary conditions.
- getParameter (bool=False) whether to store context parameters in the State If true, particle positions will be translated so the center of
- enforcePeriodicBox (bool=False) if false, the position of each particle will be whatever position is stored in the Context, regardless of periodic boundary conditions. If true, particle positions will be translated so the center of every molecule lies in the same periodic box. every molecule lies in the same periodic box.
- groups (set={0,1,2,...,31}) a set of indices for which force groups groups : set={0,1,2,...,31}
to include when computing forces and energies. The default value a set of indices for which force groups to include when computing
includes all groups. groups can also be passed as an unsigned integer forces and energies. The default value includes all groups. groups
interpreted as a bitmask, in which case group i will be included if can also be passed as an unsigned integer interpreted as a bitmask,
(groups&(1<<i)) != 0. 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, enforcePeriodic = map(bool,
(getPositions, getVelocities, getForces, getEnergy, getParameters, (getPositions, getVelocities, getForces, getEnergy, getParameters,
...@@ -208,33 +208,32 @@ Parameters: ...@@ -208,33 +208,32 @@ Parameters:
getParameters=False, getParameters=False,
enforcePeriodicBox=False, enforcePeriodicBox=False,
groups=-1): groups=-1):
""" """Get a State object recording the current state information about one copy of the system.
getState(self,
copy, Parameters
getPositions = False, ----------
getVelocities = False, copy : int
getForces = False, the index of the copy for which to retrieve state information
getEnergy = False, getPositions : bool=False
getParameters = False, whether to store particle positions in the State
enforcePeriodicBox = False, getVelocities : bool=False
groups = -1) whether to store particle velocities in the State
-> State getForces : bool=False
whether to store the forces acting on particles in the State
Get a State object recording the current state information about one copy of the system. getEnergy : bool=False
whether to store potential and kinetic energy in the State
Parameters: getParameter : bool=False
- copy (int) the index of the copy for which to retrieve state information whether to store context parameters in the State
- getPositions (bool=False) whether to store particle positions in the State enforcePeriodicBox : bool=False
- getVelocities (bool=False) whether to store particle velocities in the State if false, the position of each particle will be whatever position
- getForces (bool=False) whether to store the forces acting on particles in the State is stored in the Context, regardless of periodic boundary conditions.
- getEnergy (bool=False) whether to store potential and kinetic energy in the State If true, particle positions will be translated so the center of
- getParameter (bool=False) whether to store context parameters in the State every molecule lies in the same periodic box.
- enforcePeriodicBox (bool=False) if false, the position of each particle will be whatever position is stored in the Context, regardless of periodic boundary conditions. If true, particle positions will be translated so the center of every molecule lies in the same periodic box. groups : set={0,1,2,...,31}
- groups (set={0,1,2,...,31}) a set of indices for which force groups a set of indices for which force groups to include when computing
to include when computing forces and energies. The default value forces and energies. The default value includes all groups. groups
includes all groups. groups can also be passed as an unsigned integer can also be passed as an unsigned integer interpreted as a bitmask,
interpreted as a bitmask, in which case group i will be included if in which case group i will be included if (groups&(1<<i)) != 0.
(groups&(1<<i)) != 0.
""" """
getP, getV, getF, getE, getPa, enforcePeriodic = map(bool, getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
(getPositions, getVelocities, getForces, getEnergy, getParameters, (getPositions, getVelocities, getForces, getEnergy, getParameters,
......
...@@ -3,6 +3,10 @@ from validateModeller import * ...@@ -3,6 +3,10 @@ from validateModeller import *
from simtk.openmm.app import * from simtk.openmm.app import *
from simtk.openmm import * from simtk.openmm import *
from simtk.unit import * from simtk.unit import *
if sys.version_info >= (3, 0):
from io import StringIO
else:
from cStringIO import StringIO
class TestModeller(unittest.TestCase): class TestModeller(unittest.TestCase):
""" Test the Modeller class. """ """ Test the Modeller class. """
...@@ -397,44 +401,46 @@ class TestModeller(unittest.TestCase): ...@@ -397,44 +401,46 @@ class TestModeller(unittest.TestCase):
def test_addSolventNegativeSolvent(self): def test_addSolventNegativeSolvent(self):
""" Test the addSolvent() method; test adding ions to a negatively charged solvent. """ """ Test the addSolvent() method; test adding ions to a negatively charged solvent. """
topology_start = self.pdb.topology topology_start = self.pdb.topology
topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers) topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers)
# set up modeller with no solvent
modeller = Modeller(topology_start, self.positions)
modeller.deleteWater()
# add 5 Cl- ions to the original topology for neutralize in (True, False):
topology_toAdd = Topology() # set up modeller with no solvent
newChain = topology_toAdd.addChain() modeller = Modeller(topology_start, self.positions)
for i in range(5): modeller.deleteWater()
topology_toAdd.addResidue('CL', newChain)
residues = [residue for residue in topology_toAdd.residues()]
for i in range(5):
topology_toAdd.addAtom('Cl',Element.getBySymbol('Cl'), residues[i])
positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0),
Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers
modeller.add(topology_toAdd, positions_toAdd)
modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar)
topology_after = modeller.getTopology()
water_count = 0 # add 5 Cl- ions to the original topology
sodium_count = 0 topology_toAdd = Topology()
chlorine_count = 0 newChain = topology_toAdd.addChain()
for residue in topology_after.residues(): for i in range(5):
if residue.name=='HOH': topology_toAdd.addResidue('CL', newChain)
water_count += 1 residues = [residue for residue in topology_toAdd.residues()]
elif residue.name=='NA': for i in range(5):
sodium_count += 1 topology_toAdd.addAtom('Cl',Element.getBySymbol('Cl'), residues[i])
elif residue.name=='CL': positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0),
chlorine_count += 1 Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers
modeller.add(topology_toAdd, positions_toAdd)
modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar, neutralize=neutralize)
topology_after = modeller.getTopology()
total_water_ions = water_count+sodium_count+chlorine_count water_count = 0
expected_ion_fraction = 1.0*molar/(55.4*molar) sodium_count = 0
expected_ions = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5 chlorine_count = 0
self.assertEqual(sodium_count, expected_ions) for residue in topology_after.residues():
self.assertEqual(chlorine_count, expected_ions) if residue.name=='HOH':
water_count += 1
elif residue.name=='NA':
sodium_count += 1
elif residue.name=='CL':
chlorine_count += 1
total_water_ions = water_count+sodium_count+chlorine_count
expected_ion_fraction = 1.0*molar/(55.4*molar)
expected_chlorine = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5
expected_sodium = expected_chlorine if neutralize else expected_chlorine-5
self.assertEqual(sodium_count, expected_sodium)
self.assertEqual(chlorine_count, expected_chlorine)
def test_addSolventPositiveSolvent(self): def test_addSolventPositiveSolvent(self):
""" Test the addSolvent() method; test adding ions to a positively charged solvent. """ """ Test the addSolvent() method; test adding ions to a positively charged solvent. """
...@@ -442,42 +448,44 @@ class TestModeller(unittest.TestCase): ...@@ -442,42 +448,44 @@ class TestModeller(unittest.TestCase):
topology_start = self.pdb.topology topology_start = self.pdb.topology
topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers) topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers)
# set up modeller with no solvent for neutralize in (True, False):
modeller = Modeller(topology_start, self.positions) # set up modeller with no solvent
modeller.deleteWater() modeller = Modeller(topology_start, self.positions)
modeller.deleteWater()
# add 5 Na+ ions to the original topology # add 5 Na+ ions to the original topology
topology_toAdd = Topology() topology_toAdd = Topology()
newChain = topology_toAdd.addChain() newChain = topology_toAdd.addChain()
for i in range(5): for i in range(5):
topology_toAdd.addResidue('NA', newChain) topology_toAdd.addResidue('NA', newChain)
residues = [residue for residue in topology_toAdd.residues()] residues = [residue for residue in topology_toAdd.residues()]
for i in range(5): for i in range(5):
topology_toAdd.addAtom('Na',Element.getBySymbol('Na'), residues[i]) topology_toAdd.addAtom('Na',Element.getBySymbol('Na'), residues[i])
positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0), positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0),
Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers
# positions_toAdd doesn't need to change # positions_toAdd doesn't need to change
modeller.add(topology_toAdd, positions_toAdd) modeller.add(topology_toAdd, positions_toAdd)
modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar) modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar, neutralize=neutralize)
topology_after = modeller.getTopology() topology_after = modeller.getTopology()
water_count = 0 water_count = 0
sodium_count = 0 sodium_count = 0
chlorine_count = 0 chlorine_count = 0
for residue in topology_after.residues(): for residue in topology_after.residues():
if residue.name=='HOH': if residue.name=='HOH':
water_count += 1 water_count += 1
elif residue.name=='NA': elif residue.name=='NA':
sodium_count += 1 sodium_count += 1
elif residue.name=='CL': elif residue.name=='CL':
chlorine_count += 1 chlorine_count += 1
total_water_ions = water_count+sodium_count+chlorine_count total_water_ions = water_count+sodium_count+chlorine_count
expected_ion_fraction = 1.0*molar/(55.4*molar) expected_ion_fraction = 1.0*molar/(55.4*molar)
expected_ions = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5 expected_sodium = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5
self.assertEqual(sodium_count, expected_ions) expected_chlorine = expected_sodium if neutralize else expected_sodium-5
self.assertEqual(chlorine_count, expected_ions) self.assertEqual(sodium_count, expected_sodium)
self.assertEqual(chlorine_count, expected_chlorine)
def test_addSolventIons(self): def test_addSolventIons(self):
""" Test the addSolvent() method with all possible choices for positive and negative ions. """ """ Test the addSolvent() method with all possible choices for positive and negative ions. """
...@@ -915,6 +923,135 @@ class TestModeller(unittest.TestCase): ...@@ -915,6 +923,135 @@ class TestModeller(unittest.TestCase):
self.assertEqual(1, len(ep)) self.assertEqual(1, len(ep))
def test_addVirtualSites(self):
"""Test adding extra particles defined by virtual sites."""
xml = """
<ForceField>
<AtomTypes>
<Type name="C" class="C" element="C" mass="10"/>
<Type name="N" class="N" element="N" mass="10"/>
<Type name="O" class="O" element="O" mass="10"/>
<Type name="V" class="V" mass="0.0"/>
</AtomTypes>
<Residues>
<Residue name="Test">
<Atom name="C" type="C"/>
<Atom name="N" type="N"/>
<Atom name="O" type="O"/>
<Atom name="V1" type="V"/>
<Atom name="V2" type="V"/>
<Atom name="V3" type="V"/>
<Atom name="V4" type="V"/>
<VirtualSite type="average2" index="3" atom1="0" atom2="1" weight1="0.7" weight2="0.3"/>
<VirtualSite type="average3" index="4" atom1="0" atom2="1" atom3="2" weight1="0.2" weight2="0.3" weight3="0.5"/>
<VirtualSite type="outOfPlane" index="5" atom1="0" atom2="1" atom3="2" weight12="0.1" weight13="-0.2" weightCross="0.8"/>
<VirtualSite type="localCoords" index="6" atom1="0" atom2="1" atom3="2" wo1="0.1" wo2="0.5" wo3="0.4" wx1="1" wx2="-0.6" wx3="-0.4" wy1="0.1" wy2="0.9" wy3="-1" p1="-0.5" p2="0.4" p3="1.1"/>
</Residue>
</Residues>
</ForceField>"""
ff = ForceField(StringIO(xml))
# Create the three real atoms.
topology = Topology()
chain = topology.addChain()
residue = topology.addResidue('Test', chain)
topology.addAtom('C', element.carbon, residue)
topology.addAtom('N', element.nitrogen, residue)
topology.addAtom('V', element.oxygen, residue)
# Add the virtual sites.
modeller = Modeller(topology, [Vec3(0.1, 0.2, 0.3), Vec3(1.0, 0.9, 0.8), Vec3(1.5, 1.1, 0.7)]*nanometers)
modeller.addExtraParticles(ff)
top = modeller.topology
pos = modeller.positions
# Check that the correct particles were added.
self.assertEqual(len(pos), 7)
for atom, elem in zip(top.atoms(), [element.carbon, element.nitrogen, element.oxygen, None, None, None, None]):
self.assertEqual(elem, atom.element)
# Check that the positions were calculated correctly.
system = ff.createSystem(top)
integ = VerletIntegrator(1.0)
context = Context(system, integ)
context.setPositions(pos)
context.computeVirtualSites()
state = context.getState(getPositions=True)
for p1, p2 in zip (pos, state.getPositions()):
self.assertVecAlmostEqual(p1.value_in_unit(nanometers), p2.value_in_unit(nanometers), 1e-6)
def test_multiSiteIon(self):
"""Test adding extra particles whose positions are determined based on bonds."""
xml = """
<ForceField>
<AtomTypes>
<Type name="Zn" class="Zn" element="Zn" mass="53.380"/>
<Type name="DA" class="DA" mass="3.0"/>
</AtomTypes>
<Residues>
<Residue name="ZN">
<Atom name="ZN" type="Zn"/>
<Atom name="D1" type="DA"/>
<Atom name="D2" type="DA"/>
<Atom name="D3" type="DA"/>
<Atom name="D4" type="DA"/>
<Bond from="0" to="2"/>
<Bond from="0" to="1"/>
<Bond from="0" to="3"/>
<Bond from="0" to="4"/>
<Bond from="1" to="2"/>
<Bond from="1" to="3"/>
<Bond from="1" to="4"/>
<Bond from="2" to="4"/>
<Bond from="2" to="3"/>
<Bond from="3" to="4"/>
</Residue>
</Residues>
<HarmonicBondForce>
<Bond class1="DA" class2="Zn" length="0.09" k="535552.0"/>
<Bond class1="DA" class2="DA" length="0.147" k="535552.0"/>
</HarmonicBondForce>
</ForceField>"""
ff = ForceField(StringIO(xml))
# Create two zinc atoms.
topology = Topology()
chain = topology.addChain()
residue = topology.addResidue('ZN', chain)
topology.addAtom('ZN', element.zinc, residue)
residue = topology.addResidue('ZN', chain)
topology.addAtom('ZN', element.zinc, residue)
# Add the extra particles.
modeller = Modeller(topology, [Vec3(0.5, 1.0, 1.5), Vec3(2.0, 2.0, 0.0)]*nanometers)
modeller.addExtraParticles(ff)
top = modeller.topology
pos = modeller.positions
# Check that the correct particles were added.
self.assertEqual(len(pos), 10)
for i, atom in enumerate(top.atoms()):
self.assertEqual(element.zinc if i in (0,5) else None, atom.element)
# Check that the positions in the first residue are reasonable.
center = Vec3(0.5, 1.0, 1.5)*nanometers
self.assertEqual(center, modeller.positions[0])
for i in range(1, 5):
for j in range(i):
dist = norm(pos[i]-pos[j])
expectedDist = 0.09 if j == 0 else 0.147
self.assertTrue(dist > (expectedDist-0.01)*nanometers and dist < (expectedDist+0.01)*nanometers)
def assertVecAlmostEqual(self, p1, p2, tol=1e-7): def assertVecAlmostEqual(self, p1, p2, tol=1e-7):
scale = max(1.0, norm(p1),) scale = max(1.0, norm(p1),)
for i in range(3): for i in range(3):
......
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