Unverified Commit 9e4b6ba5 authored by Peter Eastman's avatar Peter Eastman Committed by GitHub
Browse files

addHydrogens() allows specifying exactly what hydrogens to add (#4585)

* addHydrogens() allows specifying exactly what hydrogens to add

* Prevent CI from using numpy 2.0
parent 51a112a3
...@@ -10,7 +10,7 @@ dependencies: ...@@ -10,7 +10,7 @@ dependencies:
- python - python
- cython - cython
- swig - swig
- numpy - numpy <2.0
- doxygen 1.9.1 - doxygen 1.9.1
# test # test
- pytest - pytest
......
...@@ -10,7 +10,7 @@ dependencies: ...@@ -10,7 +10,7 @@ dependencies:
- python - python
- cython - cython
- swig - swig
- numpy - numpy <2.0
- doxygen 1.8.14 - doxygen 1.8.14
# test # test
- pytest - pytest
......
...@@ -12,7 +12,7 @@ dependencies: ...@@ -12,7 +12,7 @@ dependencies:
- python - python
- cython - cython
- swig - swig
- numpy - numpy <2.0
- ocl-icd-system - ocl-icd-system
- doxygen 1.8.14 - doxygen 1.8.14
# test # test
......
...@@ -12,7 +12,7 @@ dependencies: ...@@ -12,7 +12,7 @@ dependencies:
- pypy - pypy
- cython - cython
- swig - swig
- numpy - numpy <2.0
- ocl-icd-system - ocl-icd-system
- doxygen 1.8.14 - doxygen 1.8.14
# test # test
......
...@@ -12,7 +12,7 @@ dependencies: ...@@ -12,7 +12,7 @@ dependencies:
- python - python
- cython - cython
- swig - swig
- numpy - numpy <2.0
- doxygen 1.8.14 - doxygen 1.8.14
- khronos-opencl-icd-loader - khronos-opencl-icd-loader
# test # test
......
...@@ -8,7 +8,7 @@ dependencies: ...@@ -8,7 +8,7 @@ dependencies:
# host # host
- python - python
- pip - pip
- numpy - numpy <2.0
- cython - cython
- swig - swig
- doxygen 1.8.14 - doxygen 1.8.14
......
...@@ -736,6 +736,15 @@ class Modeller(object): ...@@ -736,6 +736,15 @@ class Modeller(object):
terminal = hydrogen.attrib['terminal'] terminal = hydrogen.attrib['terminal']
data.hydrogens.append(Modeller._Hydrogen(hydrogen.attrib['name'], hydrogen.attrib['parent'], maxph, atomVariants, terminal)) data.hydrogens.append(Modeller._Hydrogen(hydrogen.attrib['name'], hydrogen.attrib['parent'], maxph, atomVariants, terminal))
@staticmethod
def _loadStandardHydrogenDefinitions():
"""Load the definitions of hydrogens for standard residues. Normally there is no need to call this directly.
It is automatically called by addHydrogens(). If the definitions have already been loaded, this returns without
doing anything."""
if not Modeller._hasLoadedStandardHydrogens:
Modeller.loadHydrogenDefinitions(os.path.join(os.path.dirname(__file__), 'data', 'hydrogens.xml'))
Modeller._hasLoadedStandardHydrogens = True
def addHydrogens(self, forcefield=None, pH=7.0, variants=None, platform=None, residueTemplates=dict()): def addHydrogens(self, forcefield=None, pH=7.0, variants=None, platform=None, residueTemplates=dict()):
"""Add missing hydrogens to the model. """Add missing hydrogens to the model.
...@@ -798,7 +807,11 @@ class Modeller(object): ...@@ -798,7 +807,11 @@ class Modeller(object):
length must equal the number of residues in the model. variants[i] length must equal the number of residues in the model. variants[i]
is the name of the variant to use for residue i (indexed starting at is the name of the variant to use for residue i (indexed starting at
0). If an element is None, the standard rules will be followed to 0). If an element is None, the standard rules will be followed to
select a variant for that residue. select a variant for that residue. Alternatively, an element may specify
exactly which hydrogens to add. In that case, variants[i] should be
a list of tuples [(name1, parent1), (name2, parent2), ...]. Each
tuple specifies the name of a hydrogen and the name of the parent atom
it should be bonded to.
platform : Platform=None platform : Platform=None
the Platform to use when computing the hydrogen atom positions. If the Platform to use when computing the hydrogen atom positions. If
this is None, the default Platform will be used. this is None, the default Platform will be used.
...@@ -827,8 +840,7 @@ class Modeller(object): ...@@ -827,8 +840,7 @@ class Modeller(object):
# Load the residue specifications. # Load the residue specifications.
if not Modeller._hasLoadedStandardHydrogens: Modeller._loadStandardHydrogenDefinitions()
Modeller.loadHydrogenDefinitions(os.path.join(os.path.dirname(__file__), 'data', 'hydrogens.xml'))
# Make a list of atoms bonded to each atom. # Make a list of atoms bonded to each atom.
...@@ -867,10 +879,9 @@ class Modeller(object): ...@@ -867,10 +879,9 @@ class Modeller(object):
newResidueTemplates[newResidue] = residueTemplates[residue] newResidueTemplates[newResidue] = residueTemplates[residue]
isNTerminal = (residue == chain._residues[0]) isNTerminal = (residue == chain._residues[0])
isCTerminal = (residue == chain._residues[-1]) isCTerminal = (residue == chain._residues[-1])
if residue.name in Modeller._residueHydrogens: if residue.name in Modeller._residueHydrogens or isinstance(variants[residue.index], list):
# Add hydrogens. First select which variant to use. # Add hydrogens. First select which variant to use.
spec = Modeller._residueHydrogens[residue.name]
variant = variants[residue.index] variant = variants[residue.index]
if variant is None: if variant is None:
if residue.name == 'CYS': if residue.name == 'CYS':
...@@ -930,6 +941,12 @@ class Modeller(object): ...@@ -930,6 +941,12 @@ class Modeller(object):
variant = 'HID' variant = 'HID'
elif residue.name == 'HIS': elif residue.name == 'HIS':
variant = 'HIP' variant = 'HIP'
if isinstance(variant, list):
spec = Modeller._ResidueData(residue.name)
infinity = float('Inf')
spec.hydrogens = [Modeller._Hydrogen(name, parent, infinity, None, None) for name, parent in variant]
else:
spec = Modeller._residueHydrogens[residue.name]
if variant is not None and variant not in spec.variants: if variant is not None and variant not in spec.variants:
raise ValueError('Illegal variant for %s residue: %s' % (residue.name, variant)) raise ValueError('Illegal variant for %s residue: %s' % (residue.name, variant))
actualVariants[residue.index] = variant actualVariants[residue.index] = variant
......
from collections import defaultdict from collections import defaultdict
import unittest import unittest
import random
from validateModeller import * from validateModeller import *
from openmm.app import * from openmm.app import *
...@@ -980,6 +981,33 @@ class TestModeller(unittest.TestCase): ...@@ -980,6 +981,33 @@ class TestModeller(unittest.TestCase):
names1 = sorted([a.name for a in res1.atoms()]) names1 = sorted([a.name for a in res1.atoms()])
names2 = sorted([a.name for a in res2.atoms()]) names2 = sorted([a.name for a in res2.atoms()])
self.assertEqual(names1, names2) self.assertEqual(names1, names2)
# Reset the loaded definitions so we don't affect other tests.
Modeller._residueHydrogens = {}
Modeller._hasLoadedStandardHydrogens = False
def test_addSpecificHydrogens(self):
"""Test specifying exactly which hydrogens to add."""
pdb = PDBFile('systems/glycopeptide.pdb')
variants = [None]*pdb.topology.getNumResidues()
for residue in pdb.topology.residues():
if residue.name != 'ALA':
var = []
for atom1, atom2 in residue.bonds():
if atom1.element == element.hydrogen:
var.append((atom1.name, atom2.name))
elif atom2.element == element.hydrogen:
var.append((atom2.name, atom1.name))
variants[residue.index] = var
modeller = Modeller(pdb.topology, pdb.positions)
hydrogens = [a for a in modeller.topology.atoms() if a.element == element.hydrogen and random.random() < 0.7]
modeller.delete(hydrogens)
self.assertTrue(modeller.topology.getNumAtoms() < pdb.topology.getNumAtoms())
modeller.addHydrogens(variants=variants)
self.assertEqual(modeller.topology.getNumAtoms(), pdb.topology.getNumAtoms())
for res1, res2 in zip(pdb.topology.residues(), modeller.topology.residues()):
names1 = sorted([a.name for a in res1.atoms()])
names2 = sorted([a.name for a in res2.atoms()])
self.assertEqual(names1, names2)
def test_removeExtraHydrogens(self): def test_removeExtraHydrogens(self):
"""Test that addHydrogens() can remove hydrogens that shouldn't be there. """ """Test that addHydrogens() can remove hydrogens that shouldn't be there. """
......
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