Commit ffe0512f authored by peastman's avatar peastman
Browse files

addExtraParticles() works with patches

parent a752d258
...@@ -553,20 +553,30 @@ class ForceField(object): ...@@ -553,20 +553,30 @@ class ForceField(object):
class _SystemData(object): class _SystemData(object):
"""Inner class used to encapsulate data about the system being created.""" """Inner class used to encapsulate data about the system being created."""
def __init__(self): def __init__(self, topology):
self.atomType = {} self.atomType = {}
self.atomParameters = {} self.atomParameters = {}
self.atomTemplateIndexes = {} self.atomTemplateIndexes = {}
self.atoms = [] self.atoms = list(topology.atoms())
self.excludeAtomWith = [] self.excludeAtomWith = [[] for a in self.atoms]
self.virtualSites = {} self.virtualSites = {}
self.bonds = [] self.bonds = [ForceField._BondData(bond[0].index, bond[1].index) for bond in topology.bonds()]
self.angles = [] self.angles = []
self.propers = [] self.propers = []
self.impropers = [] self.impropers = []
self.atomBonds = [] self.atomBonds = [[] for a in self.atoms]
self.isAngleConstrained = [] self.isAngleConstrained = []
self.constraints = {} self.constraints = {}
self.bondedToAtom = [set() for a in self.atoms]
# Record which atoms are bonded to each other atom
for i in range(len(self.bonds)):
bond = self.bonds[i]
self.bondedToAtom[bond.atom1].add(bond.atom2)
self.bondedToAtom[bond.atom2].add(bond.atom1)
self.atomBonds[bond.atom1].append(i)
self.atomBonds[bond.atom2].append(i)
def addConstraint(self, system, atom1, atom2, distance): def addConstraint(self, system, atom1, atom2, distance):
"""Add a constraint to the system, avoiding duplicate constraints.""" """Add a constraint to the system, avoiding duplicate constraints."""
...@@ -883,8 +893,8 @@ class ForceField(object): ...@@ -883,8 +893,8 @@ class ForceField(object):
raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t)) raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t))
def _getResidueTemplateMatches(self, res, bondedToAtom, templateSignatures=None, ignoreExternalBonds=False): def _getResidueTemplateMatches(self, res, bondedToAtom, templateSignatures=None, ignoreExternalBonds=False, ignoreExtraParticles=False):
"""Return the residue template matches, or None if none are found. """Return the templates that match a residue, or None if none are found.
Parameters Parameters
---------- ----------
...@@ -910,7 +920,7 @@ class ForceField(object): ...@@ -910,7 +920,7 @@ class ForceField(object):
if signature in templateSignatures: if signature in templateSignatures:
allMatches = [] allMatches = []
for t in templateSignatures[signature]: for t in templateSignatures[signature]:
match = compiled.matchResidueToTemplate(res, t, bondedToAtom, ignoreExternalBonds) match = compiled.matchResidueToTemplate(res, t, bondedToAtom, ignoreExternalBonds, ignoreExtraParticles)
if match is not None: if match is not None:
allMatches.append((t, match)) allMatches.append((t, match))
if len(allMatches) == 1: if len(allMatches) == 1:
...@@ -1112,83 +1122,20 @@ class ForceField(object): ...@@ -1112,83 +1122,20 @@ class ForceField(object):
""" """
args['switchDistance'] = switchDistance args['switchDistance'] = switchDistance
args['flexibleConstraints'] = flexibleConstraints args['flexibleConstraints'] = flexibleConstraints
data = ForceField._SystemData() data = ForceField._SystemData(topology)
data.atoms = list(topology.atoms())
for atom in data.atoms:
data.excludeAtomWith.append([])
rigidResidue = [False]*topology.getNumResidues() rigidResidue = [False]*topology.getNumResidues()
# Make a list of all bonds
for bond in topology.bonds():
data.bonds.append(ForceField._BondData(bond[0].index, bond[1].index))
# Record which atoms are bonded to each other atom
bondedToAtom = []
for i in range(len(data.atoms)):
bondedToAtom.append(set())
data.atomBonds.append([])
for i in range(len(data.bonds)):
bond = data.bonds[i]
bondedToAtom[bond.atom1].add(bond.atom2)
bondedToAtom[bond.atom2].add(bond.atom1)
data.atomBonds[bond.atom1].append(i)
data.atomBonds[bond.atom2].append(i)
# Find the template matching each residue and assign atom types. # Find the template matching each residue and assign atom types.
unmatchedResidues = [] templateForResidue = self._matchAllResiduesToTemplates(data, topology, residueTemplates, ignoreExternalBonds)
for chain in topology.chains(): for res in topology.residues():
for res in chain.residues(): if res.name == 'HOH':
if res in residueTemplates: # Determine whether this should be a rigid water.
tname = residueTemplates[res]
template = self._templates[tname] if rigidWater is None:
matches = compiled.matchResidueToTemplate(res, template, bondedToAtom, ignoreExternalBonds) rigidResidue[res.index] = templateForResidue[res.index].rigidWater
if matches is None: elif rigidWater:
raise Exception('User-supplied template %s does not match the residue %d (%s)' % (tname, res.index+1, res.name)) rigidResidue[res.index] = True
else:
# Attempt to match one of the existing templates.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
if matches is None:
unmatchedResidues.append(res)
else:
data.recordMatchedAtomParameters(res, template, matches)
if res.name == 'HOH':
# Determine whether this should be a rigid water.
if rigidWater is None and template is not None:
rigidResidue[res.index] = template.rigidWater
elif rigidWater:
rigidResidue[res.index] = True
# Try to apply patches to find matches for any unmatched residues.
if len(unmatchedResidues) > 0:
unmatchedResidues = _applyPatchesToMatchResidues(self, data, unmatchedResidues, bondedToAtom, ignoreExternalBonds)
# If we still haven't found a match for a residue, attempt to use residue template generators to create
# new templates (and potentially atom types/parameters).
for res in unmatchedResidues:
# A template might have been generated on an earlier iteration of this loop.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
if matches is None:
# Try all generators.
for generator in self._templateGenerators:
if generator(self, res):
# This generator has registered a new residue template that should match.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
if matches is None:
# Something went wrong because the generated template does not match the residue signature.
raise Exception('The residue handler %s indicated it had correctly parameterized residue %s, but the generated template did not match the residue signature.' % (generator.__class__.__name__, str(res)))
else:
# We successfully generated a residue template. Break out of the for loop.
break
if matches is None:
raise ValueError('No template found for residue %d (%s). %s' % (res.index+1, res.name, _findMatchErrors(self, res)))
else:
data.recordMatchedAtomParameters(res, template, matches)
# Create the System and add atoms # Create the System and add atoms
...@@ -1234,13 +1181,13 @@ class ForceField(object): ...@@ -1234,13 +1181,13 @@ class ForceField(object):
uniqueAngles = set() uniqueAngles = set()
for bond in data.bonds: for bond in data.bonds:
for atom in bondedToAtom[bond.atom1]: for atom in data.bondedToAtom[bond.atom1]:
if atom != bond.atom2: if atom != bond.atom2:
if atom < bond.atom2: if atom < bond.atom2:
uniqueAngles.add((atom, bond.atom1, bond.atom2)) uniqueAngles.add((atom, bond.atom1, bond.atom2))
else: else:
uniqueAngles.add((bond.atom2, bond.atom1, atom)) uniqueAngles.add((bond.atom2, bond.atom1, atom))
for atom in bondedToAtom[bond.atom2]: for atom in data.bondedToAtom[bond.atom2]:
if atom != bond.atom1: if atom != bond.atom1:
if atom > bond.atom1: if atom > bond.atom1:
uniqueAngles.add((bond.atom1, bond.atom2, atom)) uniqueAngles.add((bond.atom1, bond.atom2, atom))
...@@ -1252,13 +1199,13 @@ class ForceField(object): ...@@ -1252,13 +1199,13 @@ class ForceField(object):
uniquePropers = set() uniquePropers = set()
for angle in data.angles: for angle in data.angles:
for atom in bondedToAtom[angle[0]]: for atom in data.bondedToAtom[angle[0]]:
if atom not in angle: if atom not in angle:
if atom < angle[2]: if atom < angle[2]:
uniquePropers.add((atom, angle[0], angle[1], angle[2])) uniquePropers.add((atom, angle[0], angle[1], angle[2]))
else: else:
uniquePropers.add((angle[2], angle[1], angle[0], atom)) uniquePropers.add((angle[2], angle[1], angle[0], atom))
for atom in bondedToAtom[angle[2]]: for atom in data.bondedToAtom[angle[2]]:
if atom not in angle: if atom not in angle:
if atom > angle[0]: if atom > angle[0]:
uniquePropers.add((angle[0], angle[1], angle[2], atom)) uniquePropers.add((angle[0], angle[1], angle[2], atom))
...@@ -1268,8 +1215,8 @@ class ForceField(object): ...@@ -1268,8 +1215,8 @@ class ForceField(object):
# Make a list of all unique improper torsions # Make a list of all unique improper torsions
for atom in range(len(bondedToAtom)): for atom in range(len(data.bondedToAtom)):
bondedTo = bondedToAtom[atom] bondedTo = data.bondedToAtom[atom]
if len(bondedTo) > 2: if len(bondedTo) > 2:
for subset in itertools.combinations(bondedTo, 3): for subset in itertools.combinations(bondedTo, 3):
data.impropers.append((atom, subset[0], subset[1], subset[2])) data.impropers.append((atom, subset[0], subset[1], subset[2]))
...@@ -1348,6 +1295,58 @@ class ForceField(object): ...@@ -1348,6 +1295,58 @@ class ForceField(object):
return sys return sys
def _matchAllResiduesToTemplates(self, data, topology, residueTemplates, ignoreExternalBonds, ignoreExtraParticles=False):
"""Return a list of which template matches each residue in the topology, and assign atom types."""
templateForResidue = [None]*topology.getNumResidues()
unmatchedResidues = []
for chain in topology.chains():
for res in chain.residues():
if res in residueTemplates:
tname = residueTemplates[res]
template = self._templates[tname]
matches = compiled.matchResidueToTemplate(res, template, data.bondedToAtom, ignoreExternalBonds, ignoreExtraParticles)
if matches is None:
raise Exception('User-supplied template %s does not match the residue %d (%s)' % (tname, res.index+1, res.name))
else:
# Attempt to match one of the existing templates.
[template, matches] = self._getResidueTemplateMatches(res, data.bondedToAtom, ignoreExternalBonds=ignoreExternalBonds, ignoreExtraParticles=ignoreExtraParticles)
if matches is None:
unmatchedResidues.append(res)
else:
data.recordMatchedAtomParameters(res, template, matches)
templateForResidue[res.index] = template
# Try to apply patches to find matches for any unmatched residues.
if len(unmatchedResidues) > 0:
unmatchedResidues = _applyPatchesToMatchResidues(self, data, unmatchedResidues, templateForResidue, data.bondedToAtom, ignoreExternalBonds, ignoreExtraParticles)
# If we still haven't found a match for a residue, attempt to use residue template generators to create
# new templates (and potentially atom types/parameters).
for res in unmatchedResidues:
# A template might have been generated on an earlier iteration of this loop.
[template, matches] = self._getResidueTemplateMatches(res, data.bondedToAtom, ignoreExternalBonds=ignoreExternalBonds, ignoreExtraParticles=ignoreExtraParticles)
if matches is None:
# Try all generators.
for generator in self._templateGenerators:
if generator(self, res):
# This generator has registered a new residue template that should match.
[template, matches] = self._getResidueTemplateMatches(res, data.bondedToAtom, ignoreExternalBonds=ignoreExternalBonds, ignoreExtraParticles=ignoreExtraParticles)
if matches is None:
# Something went wrong because the generated template does not match the residue signature.
raise Exception('The residue handler %s indicated it had correctly parameterized residue %s, but the generated template did not match the residue signature.' % (generator.__class__.__name__, str(res)))
else:
# We successfully generated a residue template. Break out of the for loop.
break
if matches is None:
raise ValueError('No template found for residue %d (%s). %s' % (res.index+1, res.name, _findMatchErrors(self, res)))
else:
data.recordMatchedAtomParameters(res, template, matches)
templateForResidue[res.index] = template
return templateForResidue
def _findBondsForExclusions(data, sys): def _findBondsForExclusions(data, sys):
"""Create a list of bonds to use when identifying exclusions.""" """Create a list of bonds to use when identifying exclusions."""
bondIndices = [] bondIndices = []
...@@ -1469,7 +1468,7 @@ def _createResidueSignature(elements): ...@@ -1469,7 +1468,7 @@ def _createResidueSignature(elements):
return s return s
def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignoreExternalBonds): def _applyPatchesToMatchResidues(forcefield, data, residues, templateForResidue, bondedToAtom, ignoreExternalBonds, ignoreExtraParticles):
"""Try to apply patches to find matches for residues.""" """Try to apply patches to find matches for residues."""
# Start by creating all templates than can be created by applying a combination of one-residue patches # Start by creating all templates than can be created by applying a combination of one-residue patches
# to a single template. The number of these is usually not too large, and they often cover a large fraction # to a single template. The number of these is usually not too large, and they often cover a large fraction
...@@ -1495,11 +1494,12 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignor ...@@ -1495,11 +1494,12 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignor
unmatchedResidues = [] unmatchedResidues = []
for res in residues: for res in residues:
[template, matches] = forcefield._getResidueTemplateMatches(res, bondedToAtom, patchedTemplateSignatures, ignoreExternalBonds) [template, matches] = forcefield._getResidueTemplateMatches(res, bondedToAtom, patchedTemplateSignatures, ignoreExternalBonds, ignoreExtraParticles)
if matches is None: if matches is None:
unmatchedResidues.append(res) unmatchedResidues.append(res)
else: else:
data.recordMatchedAtomParameters(res, template, matches) data.recordMatchedAtomParameters(res, template, matches)
templateForResidue[res.index] = template
if len(unmatchedResidues) == 0: if len(unmatchedResidues) == 0:
return [] return []
...@@ -1547,7 +1547,7 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignor ...@@ -1547,7 +1547,7 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignor
for patchName in patches: for patchName in patches:
patch = forcefield._patches[patchName] patch = forcefield._patches[patchName]
if patch.numResidues == clusterSize: if patch.numResidues == clusterSize:
matchedClusters = _matchToMultiResiduePatchedTemplates(data, clusters, patch, patches[patchName], bondedToAtom, ignoreExternalBonds) matchedClusters = _matchToMultiResiduePatchedTemplates(data, clusters, patch, patches[patchName], bondedToAtom, ignoreExternalBonds, ignoreExtraParticles)
for cluster in matchedClusters: for cluster in matchedClusters:
for residue in cluster: for residue in cluster:
unmatchedResidues.remove(residue) unmatchedResidues.remove(residue)
...@@ -1597,21 +1597,21 @@ def _generatePatchedSingleResidueTemplates(template, patches, index, newTemplate ...@@ -1597,21 +1597,21 @@ def _generatePatchedSingleResidueTemplates(template, patches, index, newTemplate
_generatePatchedSingleResidueTemplates(patchedTemplate, patches, index+1, newTemplates, newAlteredAtoms) _generatePatchedSingleResidueTemplates(patchedTemplate, patches, index+1, newTemplates, newAlteredAtoms)
def _matchToMultiResiduePatchedTemplates(data, clusters, patch, residueTemplates, bondedToAtom, ignoreExternalBonds): def _matchToMultiResiduePatchedTemplates(data, clusters, patch, residueTemplates, bondedToAtom, ignoreExternalBonds, ignoreExtraParticles):
"""Apply a multi-residue patch to templates, then try to match them against clusters of residues.""" """Apply a multi-residue patch to templates, then try to match them against clusters of residues."""
matchedClusters = [] matchedClusters = []
selectedTemplates = [None]*patch.numResidues selectedTemplates = [None]*patch.numResidues
_applyMultiResiduePatch(data, clusters, patch, residueTemplates, selectedTemplates, 0, matchedClusters, bondedToAtom, ignoreExternalBonds) _applyMultiResiduePatch(data, clusters, patch, residueTemplates, selectedTemplates, 0, matchedClusters, bondedToAtom, ignoreExternalBonds, ignoreExtraParticles)
return matchedClusters return matchedClusters
def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index, matchedClusters, bondedToAtom, ignoreExternalBonds): def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index, matchedClusters, bondedToAtom, ignoreExternalBonds, ignoreExtraParticles):
"""This is called recursively to apply a multi-residue patch to all possible combinations of templates.""" """This is called recursively to apply a multi-residue patch to all possible combinations of templates."""
if index < patch.numResidues: if index < patch.numResidues:
for template in candidateTemplates[index]: for template in candidateTemplates[index]:
selectedTemplates[index] = template selectedTemplates[index] = template
_applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index+1, matchedClusters, bondedToAtom, ignoreExternalBonds) _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index+1, matchedClusters, bondedToAtom, ignoreExternalBonds, ignoreExtraParticles)
else: else:
# We're at the deepest level of the recursion. We've selected a template for each residue, so apply the patch, # We're at the deepest level of the recursion. We've selected a template for each residue, so apply the patch,
# then try to match it against clusters. # then try to match it against clusters.
...@@ -1627,7 +1627,7 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT ...@@ -1627,7 +1627,7 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT
for residues in itertools.permutations(cluster): for residues in itertools.permutations(cluster):
residueMatches = [] residueMatches = []
for residue, template in zip(residues, patchedTemplates): for residue, template in zip(residues, patchedTemplates):
matches = compiled.matchResidueToTemplate(residue, template, bondedToAtom, ignoreExternalBonds) matches = compiled.matchResidueToTemplate(residue, template, bondedToAtom, ignoreExternalBonds, ignoreExtraParticles)
if matches is None: if matches is None:
residueMatches = None residueMatches = None
break break
...@@ -2431,12 +2431,12 @@ class LennardJonesGenerator(object): ...@@ -2431,12 +2431,12 @@ class LennardJonesGenerator(object):
def registerNBFIX(self, parameters): def registerNBFIX(self, parameters):
types = self.ff._findAtomTypes(parameters, 2) types = self.ff._findAtomTypes(parameters, 2)
if None not in types: if None not in types:
type1 = types[0][0] for type1 in types[0]:
type2 = types[1][0] for type2 in types[1]:
epsilon = _convertParameterToNumber(parameters['epsilon']) epsilon = _convertParameterToNumber(parameters['epsilon'])
sigma = _convertParameterToNumber(parameters['sigma']) sigma = _convertParameterToNumber(parameters['sigma'])
self.nbfixTypes[(type1, type2)] = [sigma, epsilon] self.nbfixTypes[(type1, type2)] = [sigma, epsilon]
self.nbfixTypes[(type2, type1)] = [sigma, epsilon] self.nbfixTypes[(type2, type1)] = [sigma, epsilon]
def registerLennardJones(self, parameters): def registerLennardJones(self, parameters):
self.ljTypes.registerAtom(parameters) self.ljTypes.registerAtom(parameters)
...@@ -5570,11 +5570,6 @@ class AmoebaUreyBradleyGenerator(object): ...@@ -5570,11 +5570,6 @@ class AmoebaUreyBradleyGenerator(object):
generator.length.append(float(bond.attrib['d'])) generator.length.append(float(bond.attrib['d']))
generator.k.append(float(bond.attrib['k'])) generator.k.append(float(bond.attrib['k']))
else:
outputString = "AmoebaUreyBradleyGenerator: error getting types: %s %s %s" % (
bond.attrib['class1'], bond.attrib['class2'], bond.attrib['class3'])
raise ValueError(outputString)
#============================================================================================= #=============================================================================================
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
......
...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of ...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org. Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2018 Stanford University and the Authors. Portions copyright (c) 2018-2020 Stanford University and the Authors.
Authors: Peter Eastman Authors: Peter Eastman
Contributors: Contributors:
...@@ -68,7 +68,7 @@ cdef class periodicDistance: ...@@ -68,7 +68,7 @@ cdef class periodicDistance:
return sqrt(dx*dx + dy*dy + dz*dz) return sqrt(dx*dx + dy*dy + dz*dz)
def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds=False): def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds=False, bint ignoreExtraParticles=False):
"""Determine whether a residue matches a template and return a list of corresponding atoms. """Determine whether a residue matches a template and return a list of corresponding atoms.
This is used heavily in ForceField. This is used heavily in ForceField.
...@@ -82,6 +82,8 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds ...@@ -82,6 +82,8 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds
Enumerates which other atoms each atom is bonded to Enumerates which other atoms each atom is bonded to
ignoreExternalBonds : bool ignoreExternalBonds : bool
If true, ignore external bonds when matching templates If true, ignore external bonds when matching templates
ignoreExtraParticles : bool
If true, ignore extra particles (ones whose element is None) when matching templates
Returns Returns
------- -------
...@@ -91,8 +93,18 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds ...@@ -91,8 +93,18 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds
""" """
cdef int numAtoms, i, j cdef int numAtoms, i, j
atoms = list(res.atoms()) atoms = list(res.atoms())
if ignoreExtraParticles:
atoms = [a for a in atoms if a.element is not None]
templateAtoms = [a for a in template.atoms if a.element is not None]
templateBondedTo = {}
for i, atom in enumerate(template.atoms):
if atom.element is not None:
templateBondedTo[atom] = [j for j in atom.bondedTo if template.atoms[j].element is not None]
else:
templateAtoms = template.atoms
templateBondedTo = dict((atom, atom.bondedTo) for atom in template.atoms)
numAtoms = len(atoms) numAtoms = len(atoms)
if numAtoms != len(template.atoms): if numAtoms != len(templateAtoms):
return None return None
# Translate from global to local atom indices, and record the bonds for each atom. # Translate from global to local atom indices, and record the bonds for each atom.
...@@ -117,8 +129,8 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds ...@@ -117,8 +129,8 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds
residueTypeCount[key] = 1 residueTypeCount[key] = 1
residueTypeCount[key] += 1 residueTypeCount[key] += 1
templateTypeCount = {} templateTypeCount = {}
for i, atom in enumerate(template.atoms): for i, atom in enumerate(templateAtoms):
key = (atom.element, len(atom.bondedTo), 0 if ignoreExternalBonds else atom.externalBonds) key = (atom.element, len(templateBondedTo[atom]), 0 if ignoreExternalBonds else atom.externalBonds)
if key not in templateTypeCount: if key not in templateTypeCount:
templateTypeCount[key] = 1 templateTypeCount[key] = 1
templateTypeCount[key] += 1 templateTypeCount[key] += 1
...@@ -130,11 +142,11 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds ...@@ -130,11 +142,11 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds
candidates = [[] for i in range(numAtoms)] candidates = [[] for i in range(numAtoms)]
cdef bint exactNameMatch cdef bint exactNameMatch
for i in range(numAtoms): for i in range(numAtoms):
exactNameMatch = (atoms[i].element is None and any(atom.element is None and atom.name == atoms[i].name for atom in template.atoms)) exactNameMatch = (atoms[i].element is None and any(atom.element is None and atom.name == atoms[i].name for atom in templateAtoms))
for j, atom in enumerate(template.atoms): for j, atom in enumerate(templateAtoms):
if (atom.element is not None and atom.element != atoms[i].element) or (exactNameMatch and atom.name != atoms[i].name): if (atom.element is not None and atom.element != atoms[i].element) or (exactNameMatch and atom.name != atoms[i].name):
continue continue
if len(atom.bondedTo) != len(bondedTo[i]): if len(templateBondedTo[atom]) != len(bondedTo[i]):
continue continue
if not ignoreExternalBonds and atom.externalBonds != externalBonds[i]: if not ignoreExternalBonds and atom.externalBonds != externalBonds[i]:
continue continue
...@@ -174,38 +186,38 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds ...@@ -174,38 +186,38 @@ def matchResidueToTemplate(res, template, bondedToAtom, bint ignoreExternalBonds
matches = numAtoms*[0] matches = numAtoms*[0]
hasMatch = numAtoms*[False] hasMatch = numAtoms*[False]
if _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, 0): if _findAtomMatches(templateAtoms, bondedTo, templateBondedTo, matches, hasMatch, candidates, 0):
return [matches[inverseSearchOrder[i]] for i in range(numAtoms)] return [matches[inverseSearchOrder[i]] for i in range(numAtoms)]
return None return None
def _getAtomMatchCandidates(template, bondedTo, matches, candidates, position): def _getAtomMatchCandidates(templateAtoms, bondedTo, matches, candidates, position):
"""Get a list of template atoms that are potential matches for the next atom.""" """Get a list of template atoms that are potential matches for the next atom."""
for bonded in bondedTo[position]: for bonded in bondedTo[position]:
if bonded < position: if bonded < position:
# This atom is bonded to another one for which we already have a match, so only consider # This atom is bonded to another one for which we already have a match, so only consider
# template atoms that *that* one is bonded to. # template atoms that *that* one is bonded to.
return template.atoms[matches[bonded]].bondedTo return templateAtoms[matches[bonded]].bondedTo
return candidates[position] return candidates[position]
def _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, int position): def _findAtomMatches(templateAtoms, bondedTo, templateBondedTo, matches, hasMatch, candidates, int position):
"""This is called recursively from inside matchResidueToTemplate() to identify matching atoms.""" """This is called recursively from inside matchResidueToTemplate() to identify matching atoms."""
if position == len(matches): if position == len(matches):
return True return True
cdef int i cdef int i
for i in _getAtomMatchCandidates(template, bondedTo, matches, candidates, position): for i in _getAtomMatchCandidates(templateAtoms, bondedTo, matches, candidates, position):
atom = template.atoms[i] atom = templateAtoms[i]
if not hasMatch[i] and i in candidates[position]: if not hasMatch[i] and i in candidates[position]:
# See if the bonds for this identification are consistent # See if the bonds for this identification are consistent
allBondsMatch = all((bonded > position or matches[bonded] in atom.bondedTo for bonded in bondedTo[position])) allBondsMatch = all((bonded > position or matches[bonded] in templateBondedTo[atom] for bonded in bondedTo[position]))
if allBondsMatch: if allBondsMatch:
# This is a possible match, so try matching the rest of the residue. # This is a possible match, so try matching the rest of the residue.
matches[position] = i matches[position] = i
hasMatch[i] = True hasMatch[i] = True
if _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, position+1): if _findAtomMatches(templateAtoms, bondedTo, templateBondedTo, matches, hasMatch, candidates, position+1):
return True return True
hasMatch[i] = False hasMatch[i] = False
return False return False
...@@ -1028,44 +1028,14 @@ class Modeller(object): ...@@ -1028,44 +1028,14 @@ class Modeller(object):
This is useful when the Topology represents one piece of a larger This is useful when the Topology represents one piece of a larger
molecule, so chains are not terminated properly. molecule, so chains are not terminated properly.
""" """
# Create copies of all residue templates that have had all extra points removed. # Record which atoms are bonded to each other atom.
templatesNoEP = {}
for resName, template in forcefield._templates.items():
if any(atom.element is None for atom in template.atoms):
index = 0
newIndex = {}
newTemplate = ForceField._TemplateData(resName)
for i, atom in enumerate(template.atoms):
if atom.element is not None:
newIndex[i] = index
index += 1
newAtom = ForceField._TemplateAtomData(atom.name, atom.type, atom.element)
newAtom.externalBonds = atom.externalBonds
newTemplate.atoms.append(newAtom)
for b1, b2 in template.bonds:
if b1 in newIndex and b2 in newIndex:
newTemplate.bonds.append((newIndex[b1], newIndex[b2]))
newTemplate.atoms[newIndex[b1]].bondedTo.append(newIndex[b2])
newTemplate.atoms[newIndex[b2]].bondedTo.append(newIndex[b1])
for b in template.externalBonds:
if b in newIndex:
newTemplate.externalBonds.append(newIndex[b])
templatesNoEP[template] = newTemplate
# Record which atoms are bonded to each other atom, with and without extra particles.
bondedToAtom = [] bondedToAtom = []
bondedToAtomNoEP = []
for atom in self.topology.atoms(): for atom in self.topology.atoms():
bondedToAtom.append(set()) bondedToAtom.append(set())
bondedToAtomNoEP.append(set())
for atom1, atom2 in self.topology.bonds(): for atom1, atom2 in self.topology.bonds():
bondedToAtom[atom1.index].add(atom2.index) bondedToAtom[atom1.index].add(atom2.index)
bondedToAtom[atom2.index].add(atom1.index) bondedToAtom[atom2.index].add(atom1.index)
if atom1.element is not None and atom2.element is not None:
bondedToAtomNoEP[atom1.index].add(atom2.index)
bondedToAtomNoEP[atom2.index].add(atom1.index)
# If the force field has a DrudeForce, record the types of Drude particles and their parents since we'll # If the force field has a DrudeForce, record the types of Drude particles and their parents since we'll
# need them for picking particle positions. # need them for picking particle positions.
...@@ -1076,6 +1046,10 @@ class Modeller(object): ...@@ -1076,6 +1046,10 @@ class Modeller(object):
for type in force.typeMap: for type in force.typeMap:
drudeTypeMap[type] = force.typeMap[type][0] drudeTypeMap[type] = force.typeMap[type][0]
# Identify the template to use for each residue.
templates = forcefield._matchAllResiduesToTemplates(ForceField._SystemData(self.topology), self.topology, {}, False, True)
# Create the new Topology. # Create the new Topology.
newTopology = Topology() newTopology = Topology()
...@@ -1087,16 +1061,8 @@ class Modeller(object): ...@@ -1087,16 +1061,8 @@ class Modeller(object):
newChain = newTopology.addChain(chain.id) newChain = newTopology.addChain(chain.id)
for residue in chain.residues(): for residue in chain.residues():
newResidue = newTopology.addResidue(residue.name, newChain, residue.id, residue.insertionCode) newResidue = newTopology.addResidue(residue.name, newChain, residue.id, residue.insertionCode)
template = templates[residue.index]
# Look for a matching template. if len(template.atoms) == len(list(residue.atoms())):
matchFound = False
signature = _createResidueSignature([atom.element for atom in residue.atoms()])
if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]:
if compiled.matchResidueToTemplate(residue, t, bondedToAtom, ignoreExternalBonds) is not None:
matchFound = True
if matchFound:
# Just copy the residue over. # Just copy the residue over.
for atom in residue.atoms(): for atom in residue.atoms():
...@@ -1104,28 +1070,17 @@ class Modeller(object): ...@@ -1104,28 +1070,17 @@ class Modeller(object):
newAtoms[atom] = newAtom newAtoms[atom] = newAtom
newPositions.append(deepcopy(self.positions[atom.index])) newPositions.append(deepcopy(self.positions[atom.index]))
else: else:
# There's no matching template. Try to find one that matches based on everything except # Record the corresponding atoms.
# extra points.
matches = compiled.matchResidueToTemplate(residue, template, bondedToAtom, ignoreExternalBonds, True)
template = None atomsNoEP = [a for a in residue.atoms() if a.element is not None]
residueNoEP = Residue(residue.name, residue.index, residue.chain, residue.id, residue.insertionCode) templateAtomsNoEP = [a for a in template.atoms if a.element is not None]
residueNoEP._atoms = [atom for atom in residue.atoms() if atom.element is not None] matchingAtoms = {}
if signature in forcefield._templateSignatures: for atom, match in zip(atomsNoEP, matches):
for t in forcefield._templateSignatures[signature]: templateAtomName = templateAtomsNoEP[match].name
if t in templatesNoEP: for templateAtom in template.atoms:
matches = compiled.matchResidueToTemplate(residueNoEP, templatesNoEP[t], bondedToAtomNoEP, ignoreExternalBonds) if templateAtom.name == templateAtomName:
if matches is not None: matchingAtoms[templateAtom] = atom
template = t;
# Record the corresponding atoms.
matchingAtoms = {}
for atom, match in zip(residueNoEP.atoms(), matches):
templateAtomName = templatesNoEP[t].atoms[match].name
for templateAtom in template.atoms:
if templateAtom.name == templateAtomName:
matchingAtoms[templateAtom] = atom
break
if template is None:
raise ValueError('Residue %d (%s) does not match any template defined by the ForceField.' % (residue.index+1, residue.name))
# Add the regular atoms. # Add the regular atoms.
......
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