Commit 97136edd authored by peastman's avatar peastman
Browse files

Merge pull request #1350 from jchodera/forcefield

Add methods to retrieve residues without matching forcefield residue templates
parents fbf0a0a2 a2289e84
...@@ -723,7 +723,7 @@ For the main force field, OpenMM provides the following options: ...@@ -723,7 +723,7 @@ For the main force field, OpenMM provides the following options:
.. tabularcolumns:: |l|L| .. tabularcolumns:: |l|L|
============================= ================================================================================ ============================= ================================================================================
File Force Field File Force Field
============================= ================================================================================ ============================= ================================================================================
:code:`amber96.xml` Amber96\ :cite:`Kollman1997` :code:`amber96.xml` Amber96\ :cite:`Kollman1997`
:code:`amber99sb.xml` Amber99\ :cite:`Wang2000` with modified backbone torsions\ :cite:`Hornak2006` :code:`amber99sb.xml` Amber99\ :cite:`Wang2000` with modified backbone torsions\ :cite:`Hornak2006`
...@@ -731,7 +731,7 @@ File Force Field ...@@ -731,7 +731,7 @@ File Force Field
:code:`amber99sbnmr.xml` Amber99SB with modifications to fit NMR data\ :cite:`Li2010` :code:`amber99sbnmr.xml` Amber99SB with modifications to fit NMR data\ :cite:`Li2010`
:code:`amber03.xml` Amber03\ :cite:`Duan2003` :code:`amber03.xml` Amber03\ :cite:`Duan2003`
:code:`amber10.xml` Amber10 (documented in the AmberTools_ manual as `ff10`) :code:`amber10.xml` Amber10 (documented in the AmberTools_ manual as `ff10`)
:code:`amoeba2009.xml` AMOEBA 2009\ :cite:`Ren2002`. This force field is deprecated. It is :code:`amoeba2009.xml` AMOEBA 2009\ :cite:`Ren2002`. This force field is deprecated. It is
recommended to use AMOEBA 2013 instead. recommended to use AMOEBA 2013 instead.
:code:`amoeba2013.xml` AMOEBA 2013\ :cite:`Shi2013` :code:`amoeba2013.xml` AMOEBA 2013\ :cite:`Shi2013`
:code:`charmm_polar_2013.xml` CHARMM 2013 polarizable force field\ :cite:`Lopes2013` :code:`charmm_polar_2013.xml` CHARMM 2013 polarizable force field\ :cite:`Lopes2013`
...@@ -746,14 +746,14 @@ files: ...@@ -746,14 +746,14 @@ files:
.. tabularcolumns:: |l|L| .. tabularcolumns:: |l|L|
=================== ============================================ =================== ============================================
File Water Model File Water Model
=================== ============================================ =================== ============================================
:code:`tip3p.xml` TIP3P water model\ :cite:`Jorgensen1983` :code:`tip3p.xml` TIP3P water model\ :cite:`Jorgensen1983`
:code:`tip3pfb.xml` TIP3P-FB water model\ :cite:`Wang2014` :code:`tip3pfb.xml` TIP3P-FB water model\ :cite:`Wang2014`
:code:`tip4pew.xml` TIP4P-Ew water model\ :cite:`Horn2004` :code:`tip4pew.xml` TIP4P-Ew water model\ :cite:`Horn2004`
:code:`tip4pfb.xml` TIP4P-FB water model\ :cite:`Wang2014` :code:`tip4pfb.xml` TIP4P-FB water model\ :cite:`Wang2014`
:code:`tip5p.xml` TIP5P water model\ :cite:`Mahoney2000` :code:`tip5p.xml` TIP5P water model\ :cite:`Mahoney2000`
:code:`spce.xml` SPC/E water model\ :cite:`Berendsen1987` :code:`spce.xml` SPC/E water model\ :cite:`Berendsen1987`
:code:`swm4ndp.xml` SWM4-NDP water model\ :cite:`Lamoureux2006` :code:`swm4ndp.xml` SWM4-NDP water model\ :cite:`Lamoureux2006`
=================== ============================================ =================== ============================================
...@@ -769,7 +769,7 @@ the following files: ...@@ -769,7 +769,7 @@ the following files:
.. tabularcolumns:: |l|L| .. tabularcolumns:: |l|L|
========================= ================================================================================================= ========================= =================================================================================================
File Implicit Solvation Model File Implicit Solvation Model
========================= ================================================================================================= ========================= =================================================================================================
:code:`amber96_obc.xml` GBSA-OBC solvation model\ :cite:`Onufriev2004` for use with Amber96 force field :code:`amber96_obc.xml` GBSA-OBC solvation model\ :cite:`Onufriev2004` for use with Amber96 force field
:code:`amber99_obc.xml` GBSA-OBC solvation model for use with Amber99 force fields :code:`amber99_obc.xml` GBSA-OBC solvation model for use with Amber99 force fields
...@@ -821,15 +821,15 @@ allowed values for :code:`implicitSolvent`\ : ...@@ -821,15 +821,15 @@ allowed values for :code:`implicitSolvent`\ :
.. tabularcolumns:: |l|L| .. tabularcolumns:: |l|L|
============= ================================================================================================================================== ============= ==================================================================================================================================
Value Meaning Value Meaning
============= ================================================================================================================================== ============= ==================================================================================================================================
:code:`None` No implicit solvent is used. :code:`None` No implicit solvent is used.
:code:`HCT` Hawkins-Cramer-Truhlar GBSA model\ :cite:`Hawkins1995` (corresponds to igb=1 in AMBER) :code:`HCT` Hawkins-Cramer-Truhlar GBSA model\ :cite:`Hawkins1995` (corresponds to igb=1 in AMBER)
:code:`OBC1` Onufriev-Bashford-Case GBSA model\ :cite:`Onufriev2004` using the GB\ :sup:`OBC`\ I parameters (corresponds to igb=2 in AMBER). :code:`OBC1` Onufriev-Bashford-Case GBSA model\ :cite:`Onufriev2004` using the GB\ :sup:`OBC`\ I parameters (corresponds to igb=2 in AMBER).
:code:`OBC2` Onufriev-Bashford-Case GBSA model\ :cite:`Onufriev2004` using the GB\ :sup:`OBC`\ II parameters (corresponds to igb=5 in AMBER). :code:`OBC2` Onufriev-Bashford-Case GBSA model\ :cite:`Onufriev2004` using the GB\ :sup:`OBC`\ II parameters (corresponds to igb=5 in AMBER).
This is the same model used by the GBSA-OBC files described in Section :ref:`force-fields`. This is the same model used by the GBSA-OBC files described in Section :ref:`force-fields`.
:code:`GBn` GBn solvation model\ :cite:`Mongan2007` (corresponds to igb=7 in AMBER). :code:`GBn` GBn solvation model\ :cite:`Mongan2007` (corresponds to igb=7 in AMBER).
:code:`GBn2` GBn2 solvation model\ :cite:`Nguyen2013` (corresponds to igb=8 in AMBER). :code:`GBn2` GBn2 solvation model\ :cite:`Nguyen2013` (corresponds to igb=8 in AMBER).
============= ================================================================================================================================== ============= ==================================================================================================================================
...@@ -867,13 +867,13 @@ The :code:`nonbondedMethod` parameter can have any of the following values: ...@@ -867,13 +867,13 @@ The :code:`nonbondedMethod` parameter can have any of the following values:
.. tabularcolumns:: |l|L| .. tabularcolumns:: |l|L|
========================= =========================================================================================================================================================================================================================================== ========================= ===========================================================================================================================================================================================================================================
Value Meaning Value Meaning
========================= =========================================================================================================================================================================================================================================== ========================= ===========================================================================================================================================================================================================================================
:code:`NoCutoff` No cutoff is applied. :code:`NoCutoff` No cutoff is applied.
:code:`CutoffNonPeriodic` The reaction field method is used to eliminate all interactions beyond a cutoff distance. Not valid for AMOEBA. :code:`CutoffNonPeriodic` The reaction field method is used to eliminate all interactions beyond a cutoff distance. Not valid for AMOEBA.
:code:`CutoffPeriodic` The reaction field method is used to eliminate all interactions beyond a cutoff distance. Periodic boundary conditions are applied, so each atom interacts only with the nearest periodic copy of every other atom. Not valid for AMOEBA. :code:`CutoffPeriodic` The reaction field method is used to eliminate all interactions beyond a cutoff distance. Periodic boundary conditions are applied, so each atom interacts only with the nearest periodic copy of every other atom. Not valid for AMOEBA.
:code:`Ewald` Periodic boundary conditions are applied. Ewald summation is used to compute long range interactions. (This option is rarely used, since PME is much faster for all but the smallest systems.) Not valid for AMOEBA. :code:`Ewald` Periodic boundary conditions are applied. Ewald summation is used to compute long range interactions. (This option is rarely used, since PME is much faster for all but the smallest systems.) Not valid for AMOEBA.
:code:`PME` Periodic boundary conditions are applied. The Particle Mesh Ewald method is used to compute long range interactions. :code:`PME` Periodic boundary conditions are applied. The Particle Mesh Ewald method is used to compute long range interactions.
========================= =========================================================================================================================================================================================================================================== ========================= ===========================================================================================================================================================================================================================================
...@@ -988,11 +988,11 @@ The :code:`constraints` parameter can have any of the following values: ...@@ -988,11 +988,11 @@ The :code:`constraints` parameter can have any of the following values:
.. tabularcolumns:: |l|L| .. tabularcolumns:: |l|L|
================ ============================================================================================================================================= ================ =============================================================================================================================================
Value Meaning Value Meaning
================ ============================================================================================================================================= ================ =============================================================================================================================================
:code:`None` No constraints are applied. This is the default value. :code:`None` No constraints are applied. This is the default value.
:code:`HBonds` The lengths of all bonds that involve a hydrogen atom are constrained. :code:`HBonds` The lengths of all bonds that involve a hydrogen atom are constrained.
:code:`AllBonds` The lengths of all bonds are constrained. :code:`AllBonds` The lengths of all bonds are constrained.
:code:`HAngles` The lengths of all bonds are constrained. In addition, all angles of the form H-X-H or H-O-X (where X is an arbitrary atom) are constrained. :code:`HAngles` The lengths of all bonds are constrained. In addition, all angles of the form H-X-H or H-O-X (where X is an arbitrary atom) are constrained.
================ ============================================================================================================================================= ================ =============================================================================================================================================
...@@ -1678,19 +1678,19 @@ Here is the definition of the :class:`ForceReporter` class: ...@@ -1678,19 +1678,19 @@ Here is the definition of the :class:`ForceReporter` class:
.. samepage:: .. samepage::
:: ::
class ForceReporter(object): class ForceReporter(object):
def __init__(self, file, reportInterval): def __init__(self, file, reportInterval):
self._out = open(file, 'w') self._out = open(file, 'w')
self._reportInterval = reportInterval self._reportInterval = reportInterval
def __del__(self): def __del__(self):
self._out.close() self._out.close()
def describeNextReport(self, simulation): def describeNextReport(self, simulation):
steps = self._reportInterval - simulation.currentStep%self._reportInterval steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, False, False, True, False) return (steps, False, False, True, False)
def report(self, simulation, state): def report(self, simulation, state):
forces = state.getForces().value_in_unit(kilojoules/mole/nanometer) forces = state.getForces().value_in_unit(kilojoules/mole/nanometer)
for f in forces: for f in forces:
...@@ -1961,6 +1961,38 @@ are :code:`wo1`\ , :code:`wo2`\ , :code:`wo3`\ , :code:`wx1`\ , :code:`wx2`\ , ...@@ -1961,6 +1961,38 @@ are :code:`wo1`\ , :code:`wo2`\ , :code:`wo3`\ , :code:`wx1`\ , :code:`wx2`\ ,
:code:`wx3`\ , :code:`wy1`\ , :code:`wy2`\ , :code:`wy3`\ , :code:`p1`\ , :code:`wx3`\ , :code:`wy1`\ , :code:`wy2`\ , :code:`wy3`\ , :code:`p1`\ ,
:code:`p2`\ , and :code:`p3`\ . :code:`p2`\ , and :code:`p3`\ .
Missing residue templates
=========================
.. CAUTION::
These features are experimental, and its API is subject to change.
You can use the :method:`getUnmatchedResidues()` method to get a list of residues
in the provided :code:`topology` object that do not currently have a matching
residue template defined in the :class:`ForceField`.
::
pdb = PDBFile('input.pdb')
forcefield = ForceField('amber99sb.xml', 'tip3p.xml')
unmatched_residues = forcefield.getUnmatchedResidues(topology)
This is useful for idenfitying issues with prepared systems, debugging issues
with residue template definitions, or identifying which additional residues need
to be parameterized.
As a convenience for parameterizing new residues, you can also get a list of
residues and empty residue templates using :method:`generateTemplatesForUnmatchedResidues`
::
pdb = PDBFile('input.pdb')
forcefield = ForceField('amber99sb.xml', 'tip3p.xml')
[templates, residues] = forcefield.generateTemplatesForUnmatchedResidues(topology)
# Se the atom types
for template in templates:
for atom in template.atoms:
atom.type = ... # set the atom types here
# Register the template with the forcefield.
forcefield.registerResidueTemplate(template)
<HarmonicBondForce> <HarmonicBondForce>
=================== ===================
......
...@@ -573,6 +573,104 @@ class ForceField(object): ...@@ -573,6 +573,104 @@ class ForceField(object):
break break
return [template, matches] return [template, matches]
def _buildBondedToAtomList(self, topology):
"""Build a list of which atom indices are bonded to each atom.
Parameters
----------
topology : Topology
The Topology whose bonds are to be indexed.
Returns
-------
bondedToAtom : list of set of int
bondedToAtom[index] is the set of atom indices bonded to atom `index`
"""
bondedToAtom = []
for atom in topology.atoms():
bondedToAtom.append(set())
for (atom1, atom2) in topology.bonds():
bondedToAtom[atom1.index].add(atom2.index)
bondedToAtom[atom2.index].add(atom1.index)
return bondedToAtom
def getUnmatchedResidues(self, topology):
"""Return a list of Residue objects from specified topology for which no forcefield templates are available.
.. CAUTION:: This method is experimental, and its API is subject to change.
Parameters
----------
topology : Topology
The Topology whose residues are to be checked against the forcefield residue templates.
Returns
-------
unmatched_residues : list of Residue
List of Residue objects from `topology` for which no forcefield residue templates are available.
Note that multiple instances of the same residue appearing at different points in the topology may be returned.
This method may be of use in generating missing residue templates or diagnosing parameterization failures.
"""
# Find the template matching each residue, compiling a list of residues for which no templates are available.
bondedToAtom = self._buildBondedToAtomList(topology)
unmatched_residues = list() # list of unmatched residues
for chain in topology.chains():
for res in chain.residues():
# Attempt to match one of the existing templates.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom)
if matches is None:
# No existing templates match.
unmatched_residues.append(res)
return unmatched_residues
def generateTemplatesForUnmatchedResidues(self, topology):
"""Generate forcefield residue templates for residues in specified topology for which no forcefield templates are available.
.. CAUTION:: This method is experimental, and its API is subject to change.
Parameters
----------
topology : Topology
The Topology whose residues are to be checked against the forcefield residue templates.
Returns
-------
templates : list of _TemplateData
List of forcefield residue templates corresponding to residues in `topology` for which no forcefield templates are currently available.
Atom types will be set to `None`, but template name, atom names, elements, and connectivity will be taken from corresponding Residue objects.
residues : list of Residue
List of Residue objects that were used to generate the templates.
`residues[index]` is the Residue that was used to generate the template `templates[index]`
"""
# Get a non-unique list of unmatched residues.
unmatched_residues = self.getUnmatchedResidues(topology)
# Generate a unique list of unmatched residues by comparing fingerprints.
bondedToAtom = self._buildBondedToAtomList(topology)
unique_unmatched_residues = list() # list of unique unmatched Residue objects from topology
templates = list() # corresponding _TemplateData templates
signatures = set()
for residue in unmatched_residues:
signature = _createResidueSignature([ atom.element for atom in residue.atoms() ])
template = _createResidueTemplate(residue)
is_unique = True
if signature in signatures:
# Signature is the same as an existing residue; check connectivity.
for check_residue in unique_unmatched_residues:
matches = _matchResidue(check_residue, template, bondedToAtom)
if matches is not None:
is_unique = False
if is_unique:
# Residue is unique.
unique_unmatched_residues.append(residue)
signatures.add(signature)
templates.append(template)
return [templates, unique_unmatched_residues]
def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer, def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, **args): constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, **args):
"""Construct an OpenMM System representing a Topology with this force field. """Construct an OpenMM System representing a Topology with this force field.
...@@ -853,7 +951,6 @@ def _createResidueSignature(elements): ...@@ -853,7 +951,6 @@ def _createResidueSignature(elements):
s += element.symbol+str(count) s += element.symbol+str(count)
return s return s
def _matchResidue(res, template, bondedToAtom): def _matchResidue(res, template, bondedToAtom):
"""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.
...@@ -998,6 +1095,34 @@ def _findMatchErrors(forcefield, res): ...@@ -998,6 +1095,34 @@ def _findMatchErrors(forcefield, res):
return 'The set of atoms is similar to %s, but it is missing %d atoms.' % (bestMatchName, numBestMatchAtoms-numResidueAtoms) return 'The set of atoms is similar to %s, but it is missing %d atoms.' % (bestMatchName, numBestMatchAtoms-numResidueAtoms)
return 'This might mean your input topology is missing some atoms or bonds, or possibly that you are using the wrong force field.' return 'This might mean your input topology is missing some atoms or bonds, or possibly that you are using the wrong force field.'
def _createResidueTemplate(residue):
"""Create a _TemplateData template from a Residue object.
Parameters
----------
residue : Residue
The Residue from which the template is to be constructed.
Returns
-------
template : _TemplateData
The residue template, with atom types set to None.
This method may be useful in creating new residue templates for residues without templates defined by the ForceField.
"""
template = ForceField._TemplateData(residue.name)
for atom in residue.atoms():
template.atoms.append(ForceField._TemplateAtomData(atom.name, None, atom.element))
for (atom1,atom2) in residue.internal_bonds():
template.addBondByName(atom1.name, atom2.name)
residue_atoms = [ atom for atom in residue.atoms() ]
for (atom1,atom2) in residue.external_bonds():
if atom1 in residue_atoms:
template.addExternalBondByName(atom1.name)
elif atom2 in residue_atoms:
template.addExternalBondByName(atom2.name)
return template
# The following classes are generators that know how to create Force subclasses and add them to a System that is being # The following classes are generators that know how to create Force subclasses and add them to a System that is being
# created. Each generator class must define two methods: 1) a static method that takes an etree Element and a ForceField, # created. Each generator class must define two methods: 1) a static method that takes an etree Element and a ForceField,
...@@ -4558,3 +4683,5 @@ class DrudeGenerator: ...@@ -4558,3 +4683,5 @@ class DrudeGenerator:
drude.addScreenedPair(drude1, drude2, thole1+thole2) drude.addScreenedPair(drude1, drude2, thole1+thole2)
parsers["DrudeForce"] = DrudeGenerator.parseElement parsers["DrudeForce"] = DrudeGenerator.parseElement
#=============================================================================================
...@@ -13,9 +13,9 @@ else: ...@@ -13,9 +13,9 @@ else:
class TestForceField(unittest.TestCase): class TestForceField(unittest.TestCase):
"""Test the ForceField.createSystem() method.""" """Test the ForceField.createSystem() method."""
def setUp(self): def setUp(self):
"""Set up the tests by loading the input pdb files and force field """Set up the tests by loading the input pdb files and force field
xml files. xml files.
""" """
...@@ -33,16 +33,16 @@ class TestForceField(unittest.TestCase): ...@@ -33,16 +33,16 @@ class TestForceField(unittest.TestCase):
def test_NonbondedMethod(self): def test_NonbondedMethod(self):
"""Test all five options for the nonbondedMethod parameter.""" """Test all five options for the nonbondedMethod parameter."""
methodMap = {NoCutoff:NonbondedForce.NoCutoff, methodMap = {NoCutoff:NonbondedForce.NoCutoff,
CutoffNonPeriodic:NonbondedForce.CutoffNonPeriodic, CutoffNonPeriodic:NonbondedForce.CutoffNonPeriodic,
CutoffPeriodic:NonbondedForce.CutoffPeriodic, CutoffPeriodic:NonbondedForce.CutoffPeriodic,
Ewald:NonbondedForce.Ewald, PME: NonbondedForce.PME} Ewald:NonbondedForce.Ewald, PME: NonbondedForce.PME}
for method in methodMap: for method in methodMap:
system = self.forcefield1.createSystem(self.pdb1.topology, system = self.forcefield1.createSystem(self.pdb1.topology,
nonbondedMethod=method) nonbondedMethod=method)
forces = system.getForces() forces = system.getForces()
self.assertTrue(any(isinstance(f, NonbondedForce) and self.assertTrue(any(isinstance(f, NonbondedForce) and
f.getNonbondedMethod()==methodMap[method] f.getNonbondedMethod()==methodMap[method]
for f in forces)) for f in forces))
def test_DispersionCorrection(self): def test_DispersionCorrection(self):
...@@ -50,7 +50,7 @@ class TestForceField(unittest.TestCase): ...@@ -50,7 +50,7 @@ class TestForceField(unittest.TestCase):
for useDispersionCorrection in [True, False]: for useDispersionCorrection in [True, False]:
system = self.forcefield1.createSystem(self.pdb1.topology, system = self.forcefield1.createSystem(self.pdb1.topology,
nonbondedCutoff=2*nanometer, nonbondedCutoff=2*nanometer,
useDispersionCorrection=useDispersionCorrection) useDispersionCorrection=useDispersionCorrection)
for force in system.getForces(): for force in system.getForces():
...@@ -62,8 +62,8 @@ class TestForceField(unittest.TestCase): ...@@ -62,8 +62,8 @@ class TestForceField(unittest.TestCase):
for method in [CutoffNonPeriodic, CutoffPeriodic, Ewald, PME]: for method in [CutoffNonPeriodic, CutoffPeriodic, Ewald, PME]:
system = self.forcefield1.createSystem(self.pdb1.topology, system = self.forcefield1.createSystem(self.pdb1.topology,
nonbondedMethod=method, nonbondedMethod=method,
nonbondedCutoff=2*nanometer, nonbondedCutoff=2*nanometer,
constraints=HBonds) constraints=HBonds)
cutoff_distance = 0.0*nanometer cutoff_distance = 0.0*nanometer
cutoff_check = 2.0*nanometer cutoff_check = 2.0*nanometer
...@@ -81,29 +81,29 @@ class TestForceField(unittest.TestCase): ...@@ -81,29 +81,29 @@ class TestForceField(unittest.TestCase):
def test_RigidWaterAndConstraints(self): def test_RigidWaterAndConstraints(self):
"""Test all eight options for the constraints and rigidWater parameters.""" """Test all eight options for the constraints and rigidWater parameters."""
topology = self.pdb1.topology topology = self.pdb1.topology
for constraints_value in [None, HBonds, AllBonds, HAngles]: for constraints_value in [None, HBonds, AllBonds, HAngles]:
for rigidWater_value in [True, False]: for rigidWater_value in [True, False]:
system = self.forcefield1.createSystem(topology, system = self.forcefield1.createSystem(topology,
constraints=constraints_value, constraints=constraints_value,
rigidWater=rigidWater_value) rigidWater=rigidWater_value)
validateConstraints(self, topology, system, validateConstraints(self, topology, system,
constraints_value, rigidWater_value) constraints_value, rigidWater_value)
def test_ImplicitSolvent(self): def test_ImplicitSolvent(self):
"""Test the four types of implicit solvents using the implicitSolvent """Test the four types of implicit solvents using the implicitSolvent
parameter. parameter.
""" """
topology = self.pdb2.topology topology = self.pdb2.topology
system = self.forcefield2.createSystem(topology) system = self.forcefield2.createSystem(topology)
forces = system.getForces() forces = system.getForces()
self.assertTrue(any(isinstance(f, GBSAOBCForce) for f in forces)) self.assertTrue(any(isinstance(f, GBSAOBCForce) for f in forces))
def test_ImplicitSolventParameters(self): def test_ImplicitSolventParameters(self):
"""Test that solventDielectric and soluteDielectric are passed correctly """Test that solventDielectric and soluteDielectric are passed correctly
for the different types of implicit solvent. for the different types of implicit solvent.
""" """
...@@ -121,12 +121,12 @@ class TestForceField(unittest.TestCase): ...@@ -121,12 +121,12 @@ class TestForceField(unittest.TestCase):
found_matching_solute_dielectric = True found_matching_solute_dielectric = True
if isinstance(force, NonbondedForce): if isinstance(force, NonbondedForce):
self.assertEqual(force.getReactionFieldDielectric(), 1.0) self.assertEqual(force.getReactionFieldDielectric(), 1.0)
self.assertTrue(found_matching_solvent_dielectric and self.assertTrue(found_matching_solvent_dielectric and
found_matching_solute_dielectric) found_matching_solute_dielectric)
def test_HydrogenMass(self): def test_HydrogenMass(self):
"""Test that altering the mass of hydrogens works correctly.""" """Test that altering the mass of hydrogens works correctly."""
topology = self.pdb1.topology topology = self.pdb1.topology
hydrogenMass = 4*amu hydrogenMass = 4*amu
system1 = self.forcefield1.createSystem(topology) system1 = self.forcefield1.createSystem(topology)
...@@ -138,10 +138,10 @@ class TestForceField(unittest.TestCase): ...@@ -138,10 +138,10 @@ class TestForceField(unittest.TestCase):
totalMass1 = sum([system1.getParticleMass(i) for i in range(system1.getNumParticles())]).value_in_unit(amu) totalMass1 = sum([system1.getParticleMass(i) for i in range(system1.getNumParticles())]).value_in_unit(amu)
totalMass2 = sum([system2.getParticleMass(i) for i in range(system2.getNumParticles())]).value_in_unit(amu) totalMass2 = sum([system2.getParticleMass(i) for i in range(system2.getNumParticles())]).value_in_unit(amu)
self.assertAlmostEqual(totalMass1, totalMass2) self.assertAlmostEqual(totalMass1, totalMass2)
def test_Forces(self): def test_Forces(self):
"""Compute forces and compare them to ones generated with a previous version of OpenMM to ensure they haven't changed.""" """Compute forces and compare them to ones generated with a previous version of OpenMM to ensure they haven't changed."""
pdb = PDBFile('systems/lysozyme-implicit.pdb') pdb = PDBFile('systems/lysozyme-implicit.pdb')
system = self.forcefield2.createSystem(pdb.topology) system = self.forcefield2.createSystem(pdb.topology)
integrator = VerletIntegrator(0.001) integrator = VerletIntegrator(0.001)
...@@ -156,10 +156,10 @@ class TestForceField(unittest.TestCase): ...@@ -156,10 +156,10 @@ class TestForceField(unittest.TestCase):
if diff > 0.1 and diff/norm(f1) > 1e-3: if diff > 0.1 and diff/norm(f1) > 1e-3:
numDifferences += 1 numDifferences += 1
self.assertTrue(numDifferences < system.getNumParticles()/20) # Tolerate occasional differences from numerical error self.assertTrue(numDifferences < system.getNumParticles()/20) # Tolerate occasional differences from numerical error
def test_ProgrammaticForceField(self): def test_ProgrammaticForceField(self):
"""Test building a ForceField programmatically.""" """Test building a ForceField programmatically."""
# Build the ForceField for TIP3P programmatically. # Build the ForceField for TIP3P programmatically.
ff = ForceField() ff = ForceField()
ff.registerAtomType({'name':'tip3p-O', 'class':'OW', 'mass':15.99943*daltons, 'element':elem.oxygen}) ff.registerAtomType({'name':'tip3p-O', 'class':'OW', 'mass':15.99943*daltons, 'element':elem.oxygen})
...@@ -181,11 +181,11 @@ class TestForceField(unittest.TestCase): ...@@ -181,11 +181,11 @@ class TestForceField(unittest.TestCase):
nonbonded.registerAtom({'type':'tip3p-O', 'charge':-0.834, 'sigma':0.31507524065751241*nanometers, 'epsilon':0.635968*kilojoules_per_mole}) nonbonded.registerAtom({'type':'tip3p-O', 'charge':-0.834, 'sigma':0.31507524065751241*nanometers, 'epsilon':0.635968*kilojoules_per_mole})
nonbonded.registerAtom({'type':'tip3p-H', 'charge':0.417, 'sigma':1*nanometers, 'epsilon':0*kilojoules_per_mole}) nonbonded.registerAtom({'type':'tip3p-H', 'charge':0.417, 'sigma':1*nanometers, 'epsilon':0*kilojoules_per_mole})
ff.registerGenerator(nonbonded) ff.registerGenerator(nonbonded)
# Build a water box. # Build a water box.
modeller = Modeller(Topology(), []) modeller = Modeller(Topology(), [])
modeller.addSolvent(ff, boxSize=Vec3(3, 3, 3)*nanometers) modeller.addSolvent(ff, boxSize=Vec3(3, 3, 3)*nanometers)
# Create a system using the programmatic force field as well as one from an XML file. # Create a system using the programmatic force field as well as one from an XML file.
system1 = ff.createSystem(modeller.topology) system1 = ff.createSystem(modeller.topology)
ff2 = ForceField('tip3p.xml') ff2 = ForceField('tip3p.xml')
...@@ -261,19 +261,11 @@ class TestForceField(unittest.TestCase): ...@@ -261,19 +261,11 @@ class TestForceField(unittest.TestCase):
from uuid import uuid4 from uuid import uuid4
template_name = uuid4() template_name = uuid4()
# Create residue template. # Create residue template.
template = ForceField._TemplateData(template_name) from simtk.openmm.app.forcefield import _createResidueTemplate
for atom in residue.atoms(): template = _createResidueTemplate(residue) # use helper function
typename = 'XXX' template.name = template_name # replace template name
atom_template = ForceField._TemplateAtomData(atom.name, typename, atom.element) for (template_atom, residue_atom) in zip(template.atoms, residue.atoms()):
template.atoms.append(atom_template) template_atom.type = 'XXX' # replace atom type
for (atom1,atom2) in residue.internal_bonds():
template.addBondByName(atom1.name, atom2.name)
residue_atoms = [ atom for atom in residue.atoms() ]
for (atom1,atom2) in residue.external_bonds():
if atom1 in residue_atoms:
template.addExternalBondByName(atom1.name)
elif atom2 in residue_atoms:
template.addExternalBondByName(atom2.name)
# Register the template. # Register the template.
forcefield.registerResidueTemplate(template) forcefield.registerResidueTemplate(template)
...@@ -347,11 +339,103 @@ class TestForceField(unittest.TestCase): ...@@ -347,11 +339,103 @@ class TestForceField(unittest.TestCase):
system = forcefield.createSystem(pdb.topology, nonbondedMethod=test['nonbondedMethod']) system = forcefield.createSystem(pdb.topology, nonbondedMethod=test['nonbondedMethod'])
# TODO: Test energies are finite? # TODO: Test energies are finite?
def test_getUnmatchedResidues(self):
"""Test retrieval of list of residues for which no templates are available."""
# Load the PDB file.
pdb = PDBFile(os.path.join('systems', 'T4-lysozyme-L99A-p-xylene-implicit.pdb'))
# Create a ForceField object.
forcefield = ForceField('amber99sb.xml', 'tip3p.xml')
# Get list of unmatched residues.
unmatched_residues = forcefield.getUnmatchedResidues(pdb.topology)
# Check results.
self.assertEqual(len(unmatched_residues), 1)
self.assertEqual(unmatched_residues[0].name, 'TMP')
self.assertEqual(unmatched_residues[0].id, '163')
# Load the PDB file.
pdb = PDBFile(os.path.join('systems', 'ala_ala_ala.pdb'))
# Create a ForceField object.
forcefield = ForceField('tip3p.xml')
# Get list of unmatched residues.
unmatched_residues = forcefield.getUnmatchedResidues(pdb.topology)
# Check results.
self.assertEqual(len(unmatched_residues), 3)
self.assertEqual(unmatched_residues[0].name, 'ALA')
self.assertEqual(unmatched_residues[0].chain.id, 'X')
self.assertEqual(unmatched_residues[0].id, '1')
def test_ggenerateTemplatesForUnmatchedResidues(self):
"""Test generation of blank forcefield residue templates for unmatched residues."""
#
# Test where we generate parameters for only a ligand.
#
# Load the PDB file.
pdb = PDBFile(os.path.join('systems', 'nacl-water.pdb'))
# Create a ForceField object.
forcefield = ForceField('tip3p.xml')
# Get list of unmatched residues.
unmatched_residues = forcefield.getUnmatchedResidues(pdb.topology)
[templates, residues] = forcefield.generateTemplatesForUnmatchedResidues(pdb.topology)
# Check results.
self.assertEqual(len(unmatched_residues), 24)
self.assertEqual(len(residues), 2)
self.assertEqual(len(templates), 2)
unique_names = set([ residue.name for residue in residues ])
self.assertTrue('HOH' not in unique_names)
self.assertTrue('NA' in unique_names)
self.assertTrue('CL' in unique_names)
template_names = set([ template.name for template in templates ])
self.assertTrue('HOH' not in template_names)
self.assertTrue('NA' in template_names)
self.assertTrue('CL' in template_names)
# Define forcefield parameters using returned templates.
# NOTE: This parameter definition file will currently only work for residues that either have
# no external bonds or external bonds to other residues parameterized by the simpleTemplateGenerator.
simple_ffxml_contents = """
<ForceField>
<AtomTypes>
<Type name="XXX" class="XXX" element="C" mass="12.0"/>
</AtomTypes>
<HarmonicBondForce>
<Bond type1="XXX" type2="XXX" length="0.1409" k="392459.2"/>
</HarmonicBondForce>
<HarmonicAngleForce>
<Angle type1="XXX" type2="XXX" type3="XXX" angle="2.09439510239" k="527.184"/>
</HarmonicAngleForce>
<NonbondedForce coulomb14scale="0.833333" lj14scale="0.5">
<Atom type="XXX" charge="0.000" sigma="0.315" epsilon="0.635"/>
</NonbondedForce>
</ForceField>"""
#
# Test the pre-geenration of missing residue template for a ligand.
#
# Load the PDB file.
pdb = PDBFile(os.path.join('systems', 'T4-lysozyme-L99A-p-xylene-implicit.pdb'))
# Create a ForceField object.
forcefield = ForceField('amber99sb.xml', 'tip3p.xml', StringIO(simple_ffxml_contents))
# Get list of unique unmatched residues.
[templates, residues] = forcefield.generateTemplatesForUnmatchedResidues(pdb.topology)
# Add residue templates to forcefield.
for template in templates:
# Replace atom types.
for atom in template.atoms:
atom.type = 'XXX'
# Register the template.
forcefield.registerResidueTemplate(template)
# Parameterize system.
system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff)
# TODO: Test energies are finite?
class AmoebaTestForceField(unittest.TestCase): class AmoebaTestForceField(unittest.TestCase):
"""Test the ForceField.createSystem() method with the AMOEBA forcefield.""" """Test the ForceField.createSystem() method with the AMOEBA forcefield."""
def setUp(self): def setUp(self):
"""Set up the tests by loading the input pdb files and force field """Set up the tests by loading the input pdb files and force field
xml files. xml files.
""" """
...@@ -475,4 +559,3 @@ class AmoebaTestForceField(unittest.TestCase): ...@@ -475,4 +559,3 @@ class AmoebaTestForceField(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
This diff is collapsed.
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