Commit c3ca5fc0 authored by Peter Eastman's avatar Peter Eastman
Browse files

Finished implementing addHydrogens()

parent c7d32e9c
...@@ -7,8 +7,8 @@ __version__ = "1.0" ...@@ -7,8 +7,8 @@ __version__ = "1.0"
from simtk.openmm.app import Topology, PDBFile from simtk.openmm.app import Topology, PDBFile
from simtk.openmm.app.forcefield import HAngles from simtk.openmm.app.forcefield import HAngles
from simtk.openmm.vec3 import Vec3 from simtk.openmm.vec3 import Vec3
from simtk.openmm import System, Context, NonbondedForce, VerletIntegrator from simtk.openmm import System, Context, NonbondedForce, VerletIntegrator, LocalEnergyMinimizer
from simtk.unit import nanometer, molar, elementary_charge, amu, gram, liter, sqrt, is_quantity from simtk.unit import nanometer, molar, elementary_charge, amu, gram, liter, degree, sqrt, acos, is_quantity, dot, norm
import element as elem import element as elem
import os import os
import random import random
...@@ -264,7 +264,7 @@ class Modeller(object): ...@@ -264,7 +264,7 @@ class Modeller(object):
def periodicDistance(pos1, pos2): def periodicDistance(pos1, pos2):
delta = pos1-pos2 delta = pos1-pos2
delta = [delta[i]-floor(delta[i]*invBox[i]+0.5)*box[i] for i in range(3)] delta = [delta[i]-floor(delta[i]*invBox[i]+0.5)*box[i] for i in range(3)]
return sqrt(delta[0]*delta[0]+delta[1]*delta[1]+delta[2]*delta[2]) return norm(delta)
# Find the list of water molecules to add. # Find the list of water molecules to add.
...@@ -451,14 +451,23 @@ class Modeller(object): ...@@ -451,14 +451,23 @@ class Modeller(object):
# Make a list of atoms bonded to each atom. # Make a list of atoms bonded to each atom.
bonded = {} bonded = {}
for atom in self.topology.atoms():
bonded[atom] = []
for atom1, atom2 in self.topology.bonds(): for atom1, atom2 in self.topology.bonds():
if atom1 not in bonded:
bonded[atom1] = []
if atom2 not in bonded:
bonded[atom2] = []
bonded[atom1].append(atom2) bonded[atom1].append(atom2)
bonded[atom2].append(atom1) bonded[atom2].append(atom1)
# Define a function that decides whether a set of atoms form a hydrogen bond, using fairly tolerant criteria.
def isHbond(d, h, a):
if norm(d-a) > 0.35*nanometer:
return False
deltaDH = h-d
deltaHA = a-h
deltaDH /= norm(deltaDH)
deltaHA /= norm(deltaHA)
return acos(dot(deltaDH, deltaHA)) < 50*degree
# Loop over residues. # Loop over residues.
newTopology = Topology() newTopology = Topology()
...@@ -486,7 +495,54 @@ class Modeller(object): ...@@ -486,7 +495,54 @@ class Modeller(object):
if len(sulfur) == 1 and any((atom.residue != residue for atom in bonded[sulfur[0]])): if len(sulfur) == 1 and any((atom.residue != residue for atom in bonded[sulfur[0]])):
variant = 'CYX' variant = 'CYX'
if residue.name == 'HIS' and pH > 6.5: if residue.name == 'HIS' and pH > 6.5:
variant = 'HID' # Fix this!!!!!!!!!!!!!!!!!!! # See if either nitrogen already has a hydrogen attached.
nd1 = [atom for atom in residue.atoms() if atom.name == 'ND1']
ne2 = [atom for atom in residue.atoms() if atom.name == 'NE2']
if len(nd1) != 1 or len(ne2) != 1:
raise ValueError('HIS residue (%d) has the wrong set of atoms' % residue.index)
nd1 = nd1[0]
ne2 = ne2[0]
nd1HasHydrogen = any((atom.element == elem.hydrogen for atom in bonded[nd1]))
ne2HasHydrogen = any((atom.element == elem.hydrogen for atom in bonded[ne2]))
if nd1HasHydrogen and ne2HasHydrogen:
variant = 'HIP'
elif nd1HasHydrogen:
variant = 'HID'
elif ne2HasHydrogen:
variant = 'HIE'
else:
# Estimate the hydrogen positions.
nd1Pos = self.positions[nd1.index]
ne2Pos = self.positions[ne2.index]
hd1Delta = Vec3(0, 0, 0)*nanometer
for other in bonded[nd1]:
hd1Delta += nd1Pos-self.positions[other.index]
hd1Delta *= 0.1*nanometer/norm(hd1Delta)
hd1Pos = nd1Pos+hd1Delta
he2Delta = Vec3(0, 0, 0)*nanometer
for other in bonded[ne2]:
he2Delta += ne2Pos-self.positions[other.index]
he2Delta *= 0.1*nanometer/norm(he2Delta)
he2Pos = ne2Pos+he2Delta
# See whether either hydrogen would form a hydrogen bond.
nd1IsBonded = False
ne2IsBonded = False
for acceptor in acceptors:
if acceptor.residue != residue:
acceptorPos = self.positions[acceptor.index]
if isHbond(nd1Pos, hd1Pos, acceptorPos):
nd1IsBonded = True
break
if isHbond(ne2Pos, he2Pos, acceptorPos):
ne2IsBonded = True
if ne2IsBonded and not nd1IsBonded:
variant = 'HIE'
else:
variant = 'HID'
elif residue.name == 'HIS': elif residue.name == 'HIS':
variant = 'HIP' variant = 'HIP'
if variant is not None and variant not in spec.variants: if variant is not None and variant not in spec.variants:
...@@ -537,13 +593,16 @@ class Modeller(object): ...@@ -537,13 +593,16 @@ class Modeller(object):
for h in expected: for h in expected:
newH = newTopology.addAtom(h.name, elem.hydrogen, newResidue) newH = newTopology.addAtom(h.name, elem.hydrogen, newResidue)
newIndices.append(newH.index) newIndices.append(newH.index)
if len(expected) == 1: delta = Vec3(0, 0, 0)*nanometer
delta = Vec3(0, 0, 0)*nanometer if len(bonded[parent]) > 0:
for other in bonded[parent]: for other in bonded[parent]:
delta += self.positions[parent.index]-self.positions[other.index] delta += self.positions[parent.index]-self.positions[other.index]
else: else:
delta = Vec3(random.random(), random.random(), random.random())*nanometer delta = Vec3(random.random(), random.random(), random.random())*nanometer
delta *= 0.1*nanometer/sqrt(delta[0]*delta[0]+delta[1]*delta[1]+delta[2]*delta[2]) delta *= 0.1*nanometer/norm(delta)
if len(expected) > 1:
delta += 0.05*Vec3(random.random(), random.random(), random.random())*nanometer
delta *= 0.1*nanometer/norm(delta)
newPositions.append(self.positions[parent.index]+delta) newPositions.append(self.positions[parent.index]+delta)
newTopology.addBond(newAtom, newH) newTopology.addBond(newAtom, newH)
else: else:
...@@ -559,17 +618,14 @@ class Modeller(object): ...@@ -559,17 +618,14 @@ class Modeller(object):
# The hydrogens were added at random positions. Now use the ForceField to fix them up. # The hydrogens were added at random positions. Now use the ForceField to fix them up.
system = forcefield.createSystem(newTopology, constraints=HAngles) system = forcefield.createSystem(newTopology, rigidWater=False)
system2 = System()
for i in range(system.getNumParticles()):
system2.addParticle(system.getParticleMass(i))
atoms = list(newTopology.atoms()) atoms = list(newTopology.atoms())
for i in range(system.getNumConstraints()): for i in range(system.getNumParticles()):
p1, p2, distance = system.getConstraintParameters(i) if atoms[i].element != elem.hydrogen:
if atoms[p1].element == elem.hydrogen or atoms[p2].element == elem.hydrogen: # This is a heavy atom, so make it immobile.
system2.addConstraint(p1, p2, distance) system.setParticleMass(i, 0)
context = Context(system2, VerletIntegrator(0.0)) context = Context(system, VerletIntegrator(0.0))
context.setPositions(newPositions) context.setPositions(newPositions)
context.applyConstraints(0.0001) LocalEnergyMinimizer.minimize(context)
self.topology = newTopology self.topology = newTopology
self.positions = context.getState(getPositions=True).getPositions() self.positions = context.getState(getPositions=True).getPositions()
...@@ -116,6 +116,27 @@ def sqrt(val): ...@@ -116,6 +116,27 @@ def sqrt(val):
except AttributeError: except AttributeError:
return math.sqrt(val) return math.sqrt(val)
###################
### VECTOR MATH ###
###################
def dot(x, y):
"""
>>> dot((2, 3)*meter, (4, 5)*meter)
Quantity(value=23, unit=meter**2)
"""
sum = x[0]*y[0]
for i in range(1, len(x)):
sum += x[i]*y[i]
return sum
def norm(x):
"""
>>>norm((3, 4)*meter)
Quantity(value=5, unit=meter*)
"""
return sqrt(dot(x, x))
# run module directly for testing # run module directly for testing
if __name__=='__main__': if __name__=='__main__':
# Test the examples in the docstrings # Test the examples in the docstrings
......
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