"...ssh:/git@developer.sourcefind.cn:2222/tsoc/openmm.git" did not exist on "dd320bcf06b4fff6db0f00798e0fce3a2f1e1f7e"
Commit ed57e864 authored by João Rodrigues's avatar João Rodrigues
Browse files

Fix position of existing atoms on addHydrogens()

parent c7441b96
...@@ -712,6 +712,8 @@ class Modeller(object): ...@@ -712,6 +712,8 @@ class Modeller(object):
automatically, this function will only add hydrogens. It will never remove ones that are already present in the automatically, this function will only add hydrogens. It will never remove ones that are already present in the
model, regardless of the specified pH. model, regardless of the specified pH.
In all cases, the positions of existing atoms (including existing hydrogens) are not modified.
Definitions for standard amino acids and nucleotides are built in. You can call loadHydrogenDefinitions() to load Definitions for standard amino acids and nucleotides are built in. You can call loadHydrogenDefinitions() to load
additional definitions for other residue types. additional definitions for other residue types.
...@@ -930,14 +932,16 @@ class Modeller(object): ...@@ -930,14 +932,16 @@ class Modeller(object):
# The hydrogens were added at random positions. Now perform an energy minimization to fix them up. # The hydrogens were added at random positions. Now perform an energy minimization to fix them up.
addedH = set(newIndices) # keep track of Hs added
if forcefield is not None: if forcefield is not None:
# Use the ForceField the user specified. # Use the ForceField the user specified.
system = forcefield.createSystem(newTopology, rigidWater=False, nonbondedMethod=CutoffNonPeriodic) system = forcefield.createSystem(newTopology, rigidWater=False, nonbondedMethod=CutoffNonPeriodic)
atoms = list(newTopology.atoms()) atoms = list(newTopology.atoms())
for i in range(system.getNumParticles()): for i in range(system.getNumParticles()):
if atoms[i].element != elem.hydrogen: if i not in addedH:
# This is a heavy atom, so make it immobile. # Existing atom, make it immobile.
system.setParticleMass(i, 0) system.setParticleMass(i, 0)
else: else:
# Create a System that restrains the distance of each hydrogen from its parent atom # Create a System that restrains the distance of each hydrogen from its parent atom
...@@ -955,7 +959,7 @@ class Modeller(object): ...@@ -955,7 +959,7 @@ class Modeller(object):
bondedTo = [] bondedTo = []
for atom in newTopology.atoms(): for atom in newTopology.atoms():
nonbonded.addParticle([]) nonbonded.addParticle([])
if atom.element != elem.hydrogen: if atom.index not in addedH: # make immobile
system.addParticle(0.0) system.addParticle(0.0)
else: else:
system.addParticle(1.0) system.addParticle(1.0)
...@@ -1221,33 +1225,33 @@ class Modeller(object): ...@@ -1221,33 +1225,33 @@ class Modeller(object):
def addMembrane(self, forcefield, lipidType='POPC', membraneCenterZ=0*nanometer, minimumPadding=1*nanometer, positiveIon='Na+', negativeIon='Cl-', ionicStrength=0*molar, neutralize=True): def addMembrane(self, forcefield, lipidType='POPC', membraneCenterZ=0*nanometer, minimumPadding=1*nanometer, positiveIon='Na+', negativeIon='Cl-', ionicStrength=0*molar, neutralize=True):
"""Add a lipid membrane to the model. """Add a lipid membrane to the model.
This method actually adds both a membrane and a water box. It is best to build them together, This method actually adds both a membrane and a water box. It is best to build them together,
both to avoid adding waters inside the membrane and to ensure that lipid head groups are properly both to avoid adding waters inside the membrane and to ensure that lipid head groups are properly
solvated. For that reason, this method includes many of the same arguments as addSolvent(). solvated. For that reason, this method includes many of the same arguments as addSolvent().
The membrane is added in the XY plane, and the existing protein is assumed to already be oriented The membrane is added in the XY plane, and the existing protein is assumed to already be oriented
and positioned correctly. When possible, it is recommended to start with a model from the and positioned correctly. When possible, it is recommended to start with a model from the
Orientations of Proteins in Membranes (OPM) database at http://opm.phar.umich.edu. Otherwise, it Orientations of Proteins in Membranes (OPM) database at http://opm.phar.umich.edu. Otherwise, it
is up to you to select the protein position yourself. is up to you to select the protein position yourself.
The algorithm is based on the one described in Wolf et al., J. Comp. Chem. 31, pp. 2169-2174 (2010). The algorithm is based on the one described in Wolf et al., J. Comp. Chem. 31, pp. 2169-2174 (2010).
It begins by tiling copies of a pre-equilibrated membrane patch to create a membrane of the desired It begins by tiling copies of a pre-equilibrated membrane patch to create a membrane of the desired
size. Next it scales down the protein by 50% along the X and Y axes. Any lipid within a cutoff size. Next it scales down the protein by 50% along the X and Y axes. Any lipid within a cutoff
distance of the scaled protein is removed. It also ensures that equal numbers of lipids are removed distance of the scaled protein is removed. It also ensures that equal numbers of lipids are removed
from each leaf of the membrane. Finally, 1000 steps of molecular dynamics are performed to let from each leaf of the membrane. Finally, 1000 steps of molecular dynamics are performed to let
the membrane relax while the protein is gradually scaled back up to its original size. the membrane relax while the protein is gradually scaled back up to its original size.
The size of the membrane and water box are determined by the minimumPadding argument. All The size of the membrane and water box are determined by the minimumPadding argument. All
pre-existing atoms are guaranteed to be at least this far from any edge of the periodic box. It pre-existing atoms are guaranteed to be at least this far from any edge of the periodic box. It
is also possible for the periodic box to have more padding than requested. In particular, it only is also possible for the periodic box to have more padding than requested. In particular, it only
adds whole copies of the pre-equilibrated membrane patch, so the box dimensions will always be adds whole copies of the pre-equilibrated membrane patch, so the box dimensions will always be
integer multiples of the patch size. That may lead to a larger membrane than what you requested. integer multiples of the patch size. That may lead to a larger membrane than what you requested.
This method has built in support for POPC and POPE lipids. You can also build other types of This method has built in support for POPC and POPE lipids. You can also build other types of
membranes by providing a pre-equilibrated, solvated membrane patch that can be tiled in the XY membranes by providing a pre-equilibrated, solvated membrane patch that can be tiled in the XY
plane to form the membrane. plane to form the membrane.
Parameters Parameters
---------- ----------
forcefield : ForceField forcefield : ForceField
...@@ -1282,8 +1286,8 @@ class Modeller(object): ...@@ -1282,8 +1286,8 @@ class Modeller(object):
membraneCenterZ = membraneCenterZ.value_in_unit(nanometer) membraneCenterZ = membraneCenterZ.value_in_unit(nanometer)
if is_quantity(minimumPadding): if is_quantity(minimumPadding):
minimumPadding = minimumPadding.value_in_unit(nanometer) minimumPadding = minimumPadding.value_in_unit(nanometer)
# Figure out how many copies of the membrane patch we need in each direction. # Figure out how many copies of the membrane patch we need in each direction.
proteinPos = self.positions.value_in_unit(nanometer) proteinPos = self.positions.value_in_unit(nanometer)
proteinMinPos = Vec3(*[min((p[i] for p in proteinPos)) for i in range(3)]) proteinMinPos = Vec3(*[min((p[i] for p in proteinPos)) for i in range(3)])
...@@ -1298,15 +1302,15 @@ class Modeller(object): ...@@ -1298,15 +1302,15 @@ class Modeller(object):
patchCenterPos = (patchMinPos+patchMaxPos)/2 patchCenterPos = (patchMinPos+patchMaxPos)/2
nx = int(ceil((proteinSize[0]+2*minimumPadding)/patchSize[0])) nx = int(ceil((proteinSize[0]+2*minimumPadding)/patchSize[0]))
ny = int(ceil((proteinSize[1]+2*minimumPadding)/patchSize[1])) ny = int(ceil((proteinSize[1]+2*minimumPadding)/patchSize[1]))
# Record the bonds for each residue. # Record the bonds for each residue.
resBonds = defaultdict(list) resBonds = defaultdict(list)
for bond in patch.topology.bonds(): for bond in patch.topology.bonds():
resBonds[bond[0].residue].append(bond) resBonds[bond[0].residue].append(bond)
# Identify which leaf of the membrane each lipid is in. # Identify which leaf of the membrane each lipid is in.
numLipidAtoms = 0 numLipidAtoms = 0
resMeanZ = {} resMeanZ = {}
membraneMeanZ = 0.0 membraneMeanZ = 0.0
...@@ -1322,17 +1326,17 @@ class Modeller(object): ...@@ -1322,17 +1326,17 @@ class Modeller(object):
resMeanZ[res] = sumZ/numResAtoms resMeanZ[res] = sumZ/numResAtoms
membraneMeanZ /= numLipidAtoms membraneMeanZ /= numLipidAtoms
lipidLeaf = dict((res, 0 if resMeanZ[res] < membraneMeanZ else 1) for res in resMeanZ) lipidLeaf = dict((res, 0 if resMeanZ[res] < membraneMeanZ else 1) for res in resMeanZ)
# Compute scaled positions for the protein. # Compute scaled positions for the protein.
scaledProteinPos = [None]*len(proteinPos) scaledProteinPos = [None]*len(proteinPos)
for i, p in enumerate(proteinPos): for i, p in enumerate(proteinPos):
p = p-proteinCenterPos p = p-proteinCenterPos
p = Vec3(0.5*p[0], 0.5*p[1], p[2]) p = Vec3(0.5*p[0], 0.5*p[1], p[2])
scaledProteinPos[i] = p+proteinCenterPos scaledProteinPos[i] = p+proteinCenterPos
# Create a new Topology for the membrane. # Create a new Topology for the membrane.
membraneTopology = Topology() membraneTopology = Topology()
membranePos = [] membranePos = []
boxSizeZ = patchSize[2] boxSizeZ = patchSize[2]
...@@ -1341,12 +1345,12 @@ class Modeller(object): ...@@ -1341,12 +1345,12 @@ class Modeller(object):
else: else:
boxSizeZ = max(boxSizeZ, proteinSize[2]+2*minimumPadding) boxSizeZ = max(boxSizeZ, proteinSize[2]+2*minimumPadding)
membraneTopology.setUnitCellDimensions((nx*patchSize[0], ny*patchSize[1], boxSizeZ)) membraneTopology.setUnitCellDimensions((nx*patchSize[0], ny*patchSize[1], boxSizeZ))
# Add membrane patches. We exclude any water that is within a cutoff distance of either the actual or scaled # Add membrane patches. We exclude any water that is within a cutoff distance of either the actual or scaled
# protein, and any lipid that is within a cutoff distance of the scaled protein. We also keep track of how # protein, and any lipid that is within a cutoff distance of the scaled protein. We also keep track of how
# many lipids have been excluded from each leaf of the membrane, so we can make sure exactly the same # many lipids have been excluded from each leaf of the membrane, so we can make sure exactly the same
# number get removed from each leaf. # number get removed from each leaf.
overlapCutoff = 0.22 overlapCutoff = 0.22
chain = membraneTopology.addChain() chain = membraneTopology.addChain()
addedWater = [] addedWater = []
...@@ -1395,9 +1399,9 @@ class Modeller(object): ...@@ -1395,9 +1399,9 @@ class Modeller(object):
del cellLists del cellLists
del cells del cells
del proteinCells del proteinCells
# Add the lipids. # Add the lipids.
newAtoms = {} newAtoms = {}
lipidChain = membraneTopology.addChain() lipidChain = membraneTopology.addChain()
lipidResNum = 1 # renumber lipid residues to handle large patches lipidResNum = 1 # renumber lipid residues to handle large patches
...@@ -1408,7 +1412,7 @@ class Modeller(object): ...@@ -1408,7 +1412,7 @@ class Modeller(object):
else: else:
newResidue = membraneTopology.addResidue(residue.name, lipidChain, lipidResNum, residue.insertionCode) newResidue = membraneTopology.addResidue(residue.name, lipidChain, lipidResNum, residue.insertionCode)
lipidResNum += 1 lipidResNum += 1
for atom in residue.atoms(): for atom in residue.atoms():
newAtom = membraneTopology.addAtom(atom.name, atom.element, newResidue, atom.id) newAtom = membraneTopology.addAtom(atom.name, atom.element, newResidue, atom.id)
newAtoms[atom] = newAtom newAtoms[atom] = newAtom
...@@ -1418,9 +1422,9 @@ class Modeller(object): ...@@ -1418,9 +1422,9 @@ class Modeller(object):
del lipidLeaf del lipidLeaf
del addedLipids del addedLipids
# Add the solvent. # Add the solvent.
solventChain = membraneTopology.addChain() solventChain = membraneTopology.addChain()
for (residue, pos) in addedWater: for (residue, pos) in addedWater:
newResidue = membraneTopology.addResidue(residue.name, solventChain, residue.id, residue.insertionCode) newResidue = membraneTopology.addResidue(residue.name, solventChain, residue.id, residue.insertionCode)
...@@ -1436,7 +1440,7 @@ class Modeller(object): ...@@ -1436,7 +1440,7 @@ class Modeller(object):
gc.collect() gc.collect()
# Create a System for the lipids, then add in the protein as stationary particles. # Create a System for the lipids, then add in the protein as stationary particles.
system = forcefield.createSystem(membraneTopology, nonbondedMethod=CutoffPeriodic) system = forcefield.createSystem(membraneTopology, nonbondedMethod=CutoffPeriodic)
proteinSystem = forcefield.createSystem(self.topology, nonbondedMethod=CutoffNonPeriodic) proteinSystem = forcefield.createSystem(self.topology, nonbondedMethod=CutoffNonPeriodic)
numMembraneParticles = system.getNumParticles() numMembraneParticles = system.getNumParticles()
...@@ -1458,9 +1462,9 @@ class Modeller(object): ...@@ -1458,9 +1462,9 @@ class Modeller(object):
del membranePos del membranePos
del scaledProteinCells del scaledProteinCells
gc.collect() gc.collect()
# Run a simulation while slowly scaling up the protein so the membrane can relax. # Run a simulation while slowly scaling up the protein so the membrane can relax.
integrator = LangevinIntegrator(10.0, 50.0, 0.001) integrator = LangevinIntegrator(10.0, 50.0, 0.001)
context = Context(system, integrator) context = Context(system, integrator)
context.setPositions(mergedPositions) context.setPositions(mergedPositions)
...@@ -1483,24 +1487,24 @@ class Modeller(object): ...@@ -1483,24 +1487,24 @@ class Modeller(object):
mergedPositions[j+numMembraneParticles] = (weight1*proteinPos[j] + weight2*scaledProteinPos[j]) mergedPositions[j+numMembraneParticles] = (weight1*proteinPos[j] + weight2*scaledProteinPos[j])
context.setPositions(mergedPositions) context.setPositions(mergedPositions)
integrator.step(20) integrator.step(20)
# Add the membrane to the protein. # Add the membrane to the protein.
modeller = Modeller(self.topology, self.positions) modeller = Modeller(self.topology, self.positions)
modeller.add(membraneTopology, context.getState(getPositions=True).getPositions()[:numMembraneParticles]) modeller.add(membraneTopology, context.getState(getPositions=True).getPositions()[:numMembraneParticles])
modeller.topology.setPeriodicBoxVectors(membraneTopology.getPeriodicBoxVectors()) modeller.topology.setPeriodicBoxVectors(membraneTopology.getPeriodicBoxVectors())
del context del context
del system del system
del integrator del integrator
# Depending on the box size, we may need to add more water beyond what was included with the membrane patch. # Depending on the box size, we may need to add more water beyond what was included with the membrane patch.
needExtraWater = (boxSizeZ > patchSize[2]) needExtraWater = (boxSizeZ > patchSize[2])
if needExtraWater: if needExtraWater:
modeller.addSolvent(forcefield, neutralize=False) modeller.addSolvent(forcefield, neutralize=False)
# Record the positions of all waters that have been added. # Record the positions of all waters that have been added.
waterPos = {} waterPos = {}
for chain in list(modeller.topology.chains())[-2:]: for chain in list(modeller.topology.chains())[-2:]:
for residue in chain.residues(): for residue in chain.residues():
...@@ -1564,7 +1568,7 @@ class Modeller(object): ...@@ -1564,7 +1568,7 @@ class Modeller(object):
class _CellList(object): class _CellList(object):
"""This class organizes atom positions into cells, so the neighbors of a point can be quickly retrieved""" """This class organizes atom positions into cells, so the neighbors of a point can be quickly retrieved"""
def __init__(self, positions, maxCutoff, vectors, periodic): def __init__(self, positions, maxCutoff, vectors, periodic):
self.positions = positions[:] self.positions = positions[:]
self.cells = {} self.cells = {}
......
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