Commit 58f5168e authored by peastman's avatar peastman
Browse files

Merge pull request #1240 from peastman/extraparticles

addExtraParticles() can use bonds to select positions
parents 0e346232 eb1c5858
...@@ -35,7 +35,7 @@ __author__ = "Peter Eastman" ...@@ -35,7 +35,7 @@ __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
from simtk.openmm.app import Topology, PDBFile, ForceField from simtk.openmm.app import Topology, PDBFile, ForceField
from simtk.openmm.app.forcefield import HAngles, _createResidueSignature, _matchResidue, DrudeGenerator from simtk.openmm.app.forcefield import HAngles, AllBonds, _createResidueSignature, _matchResidue, DrudeGenerator
from simtk.openmm.app.topology import Residue from simtk.openmm.app.topology import Residue
from simtk.openmm.vec3 import Vec3 from simtk.openmm.vec3 import Vec3
from simtk.openmm import System, Context, NonbondedForce, CustomNonbondedForce, HarmonicBondForce, HarmonicAngleForce, VerletIntegrator, LocalEnergyMinimizer from simtk.openmm import System, Context, NonbondedForce, CustomNonbondedForce, HarmonicBondForce, HarmonicAngleForce, VerletIntegrator, LocalEnergyMinimizer
...@@ -931,6 +931,7 @@ class Modeller(object): ...@@ -931,6 +931,7 @@ class Modeller(object):
newTopology.setPeriodicBoxVectors(self.topology.getPeriodicBoxVectors()) newTopology.setPeriodicBoxVectors(self.topology.getPeriodicBoxVectors())
newAtoms = {} newAtoms = {}
newPositions = []*nanometer newPositions = []*nanometer
missingPositions = set()
for chain in self.topology.chains(): for chain in self.topology.chains():
newChain = newTopology.addChain(chain.id) newChain = newTopology.addChain(chain.id)
for residue in chain.residues(): for residue in chain.residues():
...@@ -988,9 +989,10 @@ class Modeller(object): ...@@ -988,9 +989,10 @@ class Modeller(object):
for index, atom in enumerate(template.atoms): for index, atom in enumerate(template.atoms):
if atom in matchingAtoms: if atom in matchingAtoms:
templateAtomPositions[index] = self.positions[matchingAtoms[atom].index].value_in_unit(nanometer) templateAtomPositions[index] = self.positions[matchingAtoms[atom].index].value_in_unit(nanometer)
newExtraPoints = {}
for index, atom in enumerate(template.atoms): for index, atom in enumerate(template.atoms):
if atom.element is None: if atom.element is None:
newTopology.addAtom(atom.name, None, newResidue) newExtraPoints[atom] = newTopology.addAtom(atom.name, None, newResidue)
position = None position = None
for site in template.virtualSites: for site in template.virtualSites:
if site.index == index: if site.index == index:
...@@ -1012,14 +1014,61 @@ class Modeller(object): ...@@ -1012,14 +1014,61 @@ class Modeller(object):
if atom2.type in drudeTypeMap[atom.type]: if atom2.type in drudeTypeMap[atom.type]:
position = deepcopy(pos) position = deepcopy(pos)
if position is None: if position is None:
# We couldn't figure out the correct position. As a wild guess, just put it at the center of the residue # We couldn't figure out the correct position. Put it at a random position near the center of the residue,
# and hope that energy minimization will fix it. # and we'll try to fix it later based on bonds.
knownPositions = [x for x in templateAtomPositions if x is not None] knownPositions = [x for x in templateAtomPositions if x is not None]
position = unit.sum(knownPositions)/len(knownPositions) position = Vec3(random.gauss(0, 1), random.gauss(0, 1), random.gauss(0, 1))+(unit.sum(knownPositions)/len(knownPositions))
missingPositions.add(len(newPositions))
newPositions.append(position*nanometer) newPositions.append(position*nanometer)
# Add bonds involving the extra points.
for atom1, atom2 in template.bonds:
atom1 = template.atoms[atom1]
atom2 = template.atoms[atom2]
if atom1 in newExtraPoints or atom2 in newExtraPoints:
if atom1 in newExtraPoints:
a1 = newExtraPoints[atom1]
else:
a1 = newAtoms[matchingAtoms[atom1]]
if atom2 in newExtraPoints:
a2 = newExtraPoints[atom2]
else:
a2 = newAtoms[matchingAtoms[atom2]]
newTopology.addBond(a1, a2)
for bond in self.topology.bonds(): for bond in self.topology.bonds():
if bond[0] in newAtoms and bond[1] in newAtoms: if bond[0] in newAtoms and bond[1] in newAtoms:
newTopology.addBond(newAtoms[bond[0]], newAtoms[bond[1]]) newTopology.addBond(newAtoms[bond[0]], newAtoms[bond[1]])
if len(missingPositions) > 0:
# There were particles whose position we couldn't identify before, since they were neither virtual sites nor Drude particles.
# Try to figure them out based on bonds. First, use the ForceField to create a list of every bond involving one of them.
system = forcefield.createSystem(newTopology, constraints=AllBonds)
bonds = []
for i in range(system.getNumConstraints()):
bond = system.getConstraintParameters(i)
if bond[0] in missingPositions or bond[1] in missingPositions:
bonds.append(bond)
# Now run a few iterations of SHAKE to try to select reasonable positions.
for iteration in range(10):
for atom1, atom2, distance in bonds:
if atom1 in missingPositions:
if atom2 in missingPositions:
weights = (0.5, 0.5)
else:
weights = (1.0, 0.0)
else:
weights = (0.0, 1.0)
delta = newPositions[atom2]-newPositions[atom1]
length = norm(delta)
delta *= (distance-length)/length
newPositions[atom1] -= weights[0]*delta
newPositions[atom2] += weights[1]*delta
self.topology = newTopology self.topology = newTopology
self.positions = newPositions self.positions = newPositions
...@@ -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. """
...@@ -919,6 +923,72 @@ class TestModeller(unittest.TestCase): ...@@ -919,6 +923,72 @@ class TestModeller(unittest.TestCase):
self.assertEqual(1, len(ep)) self.assertEqual(1, len(ep))
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