Commit 895f9a6f authored by Jason Swails's avatar Jason Swails
Browse files

Update the CHARMM API to be consistent with the rest of OpenMM.

parent 2e927a35
...@@ -220,7 +220,7 @@ class CharmmParameterSet(object): ...@@ -220,7 +220,7 @@ class CharmmParameterSet(object):
atomic_number = get_by_symbol(elem).atomic_number atomic_number = get_by_symbol(elem).atomic_number
except (IndexError, KeyError): except (IndexError, KeyError):
# Figure it out from the mass # Figure it out from the mass
atomic_number = element_by_mass(mass).atomic_number atomic_number = Element.getByMass(mass).atomic_number
atype = AtomType(name=name, number=idx, mass=mass, atype = AtomType(name=name, number=idx, mass=mass,
atomic_number=atomic_number) atomic_number=atomic_number)
self.atom_types_str[atype.name] = atype self.atom_types_str[atype.name] = atype
...@@ -441,7 +441,7 @@ class CharmmParameterSet(object): ...@@ -441,7 +441,7 @@ class CharmmParameterSet(object):
atomic_number = get_by_symbol(elem).atomic_number atomic_number = get_by_symbol(elem).atomic_number
except (IndexError, KeyError): except (IndexError, KeyError):
# Figure it out from the mass # Figure it out from the mass
atomic_number = element_by_mass(mass).atomic_number atomic_number = Element.getByMass(mass).atomic_number
atype = AtomType(name=name, number=idx, mass=mass, atype = AtomType(name=name, number=idx, mass=mass,
atomic_number=atomic_number) atomic_number=atomic_number)
self.atom_types_str[atype.name] = atype self.atom_types_str[atype.name] = atype
...@@ -488,7 +488,7 @@ class CharmmParameterSet(object): ...@@ -488,7 +488,7 @@ class CharmmParameterSet(object):
time. time.
Example: Example:
>>> params = CharmmParameterSet.loadSet(pfile='charmm.prm').condense() >>> params = CharmmParameterSet('charmm.prm').condense()
""" """
# First scan through all of the bond types # First scan through all of the bond types
self._condense_types(self.bond_types) self._condense_types(self.bond_types)
...@@ -529,18 +529,3 @@ class CharmmParameterSet(object): ...@@ -529,18 +529,3 @@ class CharmmParameterSet(object):
typedict[key2] = typedict[key1] typedict[key2] = typedict[key1]
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def element_by_mass(mass):
""" Determines what element the given atom is based on its mass """
diff = mass
best_guess = 'EP'
for key in Element._elements_by_atomic_number:
element = Element._elements_by_atomic_number[key]
massdiff = abs(element.mass.value_in_unit(u.daltons) - mass)
if massdiff < diff:
best_guess = element
diff = massdiff
return best_guess
...@@ -53,7 +53,7 @@ def _catchindexerror(func): ...@@ -53,7 +53,7 @@ def _catchindexerror(func):
class CharmmPsfFile(object): class CharmmPsfFile(object):
""" """
A chemical structure instantiated from CHARMM files. You can instantiate a A chemical structure instantiated from CHARMM files. You can instantiate a
ProteinStructure from a PSF file using the load_from_psf constructor CharmmPsfFile from a PSF file using the load_from_psf constructor
Example: Example:
>>> cs = CharmmPsfFile("testfiles/test.psf") >>> cs = CharmmPsfFile("testfiles/test.psf")
...@@ -80,7 +80,7 @@ class CharmmPsfFile(object): ...@@ -80,7 +80,7 @@ class CharmmPsfFile(object):
etc.) etc.)
Example: Example:
>>> cs = ProteinStructure.load_from_psf("testfiles/test.psf") >>> cs = CharmmPsfFile("testfiles/test.psf")
>>> len(cs.atom_list) >>> len(cs.atom_list)
33 33
>>> len(cs.bond_list) >>> len(cs.bond_list)
...@@ -102,8 +102,8 @@ class CharmmPsfFile(object): ...@@ -102,8 +102,8 @@ class CharmmPsfFile(object):
@_catchindexerror @_catchindexerror
def __init__(self, psf_name): def __init__(self, psf_name):
""" """
Opens and parses a PSF file, then instantiates a ProteinStructure Opens and parses a PSF file, then instantiates a CharmmPsfFile instance
instance from the data. from the data.
Parameters: Parameters:
psf_name (str) : Name of the PSF file (it must exist) psf_name (str) : Name of the PSF file (it must exist)
...@@ -112,7 +112,7 @@ class CharmmPsfFile(object): ...@@ -112,7 +112,7 @@ class CharmmPsfFile(object):
IOError : If file "psf_name" does not exist IOError : If file "psf_name" does not exist
CharmmPSFError: If any parsing errors are encountered CharmmPSFError: If any parsing errors are encountered
""" """
conv = ProteinStructure._convert conv = CharmmPsfFile._convert
# Make sure the file exists # Make sure the file exists
if not os.path.exists(psf_name): if not os.path.exists(psf_name):
raise IOError('Could not find PSF file %s' % psf_name) raise IOError('Could not find PSF file %s' % psf_name)
...@@ -164,7 +164,7 @@ class CharmmPsfFile(object): ...@@ -164,7 +164,7 @@ class CharmmPsfFile(object):
# Eat the next line # Eat the next line
psf.readline() psf.readline()
# Now get the number of bonds # Now get the number of bonds
nbond, holder = ProteinStructure._parse_psf_section(psf, int) nbond, holder = CharmmPsfFile._parse_psf_section(psf, int)
bond_list = TrackedList() bond_list = TrackedList()
if len(holder) != nbond * 2: if len(holder) != nbond * 2:
raise CharmmPSFError('Got %d indexes for %d bonds' % raise CharmmPSFError('Got %d indexes for %d bonds' %
...@@ -175,7 +175,7 @@ class CharmmPsfFile(object): ...@@ -175,7 +175,7 @@ class CharmmPsfFile(object):
bond_list.append(Bond(atom_list[id1], atom_list[id2])) bond_list.append(Bond(atom_list[id1], atom_list[id2]))
bond_list.changed = False bond_list.changed = False
# Now get the number of angles and the angle list # Now get the number of angles and the angle list
ntheta, holder = ProteinStructure._parse_psf_section(psf, int) ntheta, holder = CharmmPsfFile._parse_psf_section(psf, int)
angle_list = TrackedList() angle_list = TrackedList()
if len(holder) != ntheta * 3: if len(holder) != ntheta * 3:
raise CharmmPSFError('Got %d indexes for %d angles' % raise CharmmPSFError('Got %d indexes for %d angles' %
...@@ -189,7 +189,7 @@ class CharmmPsfFile(object): ...@@ -189,7 +189,7 @@ class CharmmPsfFile(object):
) )
angle_list.changed = False angle_list.changed = False
# Now get the number of torsions and the torsion list # Now get the number of torsions and the torsion list
nphi, holder = ProteinStructure._parse_psf_section(psf, int) nphi, holder = CharmmPsfFile._parse_psf_section(psf, int)
dihedral_list = TrackedList() dihedral_list = TrackedList()
if len(holder) != nphi * 4: if len(holder) != nphi * 4:
raise CharmmPSFError('Got %d indexes for %d torsions' % raise CharmmPSFError('Got %d indexes for %d torsions' %
...@@ -205,7 +205,7 @@ class CharmmPsfFile(object): ...@@ -205,7 +205,7 @@ class CharmmPsfFile(object):
) )
dihedral_list.changed = False dihedral_list.changed = False
# Now get the number of improper torsions # Now get the number of improper torsions
nimphi, holder = ProteinStructure._parse_psf_section(psf, int) nimphi, holder = CharmmPsfFile._parse_psf_section(psf, int)
improper_list = TrackedList() improper_list = TrackedList()
if len(holder) != nimphi * 4: if len(holder) != nimphi * 4:
raise CharmmPSFError('Got %d indexes for %d impropers' % raise CharmmPSFError('Got %d indexes for %d impropers' %
...@@ -221,7 +221,7 @@ class CharmmPsfFile(object): ...@@ -221,7 +221,7 @@ class CharmmPsfFile(object):
) )
improper_list.changed = False improper_list.changed = False
# Now handle the donors (what is this used for??) # Now handle the donors (what is this used for??)
ndon, holder = ProteinStructure._parse_psf_section(psf, int) ndon, holder = CharmmPsfFile._parse_psf_section(psf, int)
donor_list = TrackedList() donor_list = TrackedList()
if len(holder) != ndon * 2: if len(holder) != ndon * 2:
raise CharmmPSFError('Got %d indexes for %d donors' % raise CharmmPSFError('Got %d indexes for %d donors' %
...@@ -232,7 +232,7 @@ class CharmmPsfFile(object): ...@@ -232,7 +232,7 @@ class CharmmPsfFile(object):
donor_list.append(AcceptorDonor(atom_list[id1], atom_list[id2])) donor_list.append(AcceptorDonor(atom_list[id1], atom_list[id2]))
donor_list.changed = False donor_list.changed = False
# Now handle the acceptors (what is this used for??) # Now handle the acceptors (what is this used for??)
nacc, holder = ProteinStructure._parse_psf_section(psf, int) nacc, holder = CharmmPsfFile._parse_psf_section(psf, int)
acceptor_list = TrackedList() acceptor_list = TrackedList()
if len(holder) != nacc * 2: if len(holder) != nacc * 2:
raise CharmmPSFError('Got %d indexes for %d acceptors' % raise CharmmPSFError('Got %d indexes for %d acceptors' %
...@@ -244,9 +244,9 @@ class CharmmPsfFile(object): ...@@ -244,9 +244,9 @@ class CharmmPsfFile(object):
acceptor_list.changed = False acceptor_list.changed = False
# Now get the NNB section. Not sure what this section is for or what it # Now get the NNB section. Not sure what this section is for or what it
# does... # does...
nnb, holder = ProteinStructure._parse_psf_section(psf, int) nnb, holder = CharmmPsfFile._parse_psf_section(psf, int)
# Now get the group sections # Now get the group sections
pointers, holder = ProteinStructure._parse_psf_section(psf, int) pointers, holder = CharmmPsfFile._parse_psf_section(psf, int)
group_list = TrackedList() group_list = TrackedList()
try: try:
ngrp, nst2 = pointers ngrp, nst2 = pointers
...@@ -273,7 +273,7 @@ class CharmmPsfFile(object): ...@@ -273,7 +273,7 @@ class CharmmPsfFile(object):
# and the number of entries in the group. If the # of entries is # and the number of entries in the group. If the # of entries is
# NATOM, assume we have MOLNT section. Warn if the MOLNT section is # NATOM, assume we have MOLNT section. Warn if the MOLNT section is
# 'wrong'... # 'wrong'...
pointer, holder = ProteinStructure._parse_psf_section(psf, int) pointer, holder = CharmmPsfFile._parse_psf_section(psf, int)
# Assign all of the atoms to molecules recursively # Assign all of the atoms to molecules recursively
set_molecules(atom_list) set_molecules(atom_list)
...@@ -292,7 +292,7 @@ class CharmmPsfFile(object): ...@@ -292,7 +292,7 @@ class CharmmPsfFile(object):
'section.') 'section.')
psf.readline() # blank psf.readline() # blank
# Now we get to the cross-term section # Now we get to the cross-term section
ncrterm, holder = ProteinStructure._parse_psf_section(psf, int) ncrterm, holder = CharmmPsfFile._parse_psf_section(psf, int)
else: else:
ncrterm = pointer ncrterm = pointer
# At this point, ncrterm and holder are both set to the CMAP list for # At this point, ncrterm and holder are both set to the CMAP list for
...@@ -370,7 +370,7 @@ class CharmmPsfFile(object): ...@@ -370,7 +370,7 @@ class CharmmPsfFile(object):
- data (list) : A list of all data in the parsed section converted - data (list) : A list of all data in the parsed section converted
to `dtype' to `dtype'
""" """
conv = ProteinStructure._convert conv = CharmmPsfFile._convert
words = psf.readline().split() words = psf.readline().split()
if len(words) == 1: if len(words) == 1:
pointers = conv(words[0], int, 'pointer') pointers = conv(words[0], int, 'pointer')
...@@ -400,7 +400,7 @@ class CharmmPsfFile(object): ...@@ -400,7 +400,7 @@ class CharmmPsfFile(object):
- vmd (bool) : If True, it will write out a PSF in the format that - vmd (bool) : If True, it will write out a PSF in the format that
VMD prints it in (i.e., no NUMLP/NUMLPH or MOLNT sections) VMD prints it in (i.e., no NUMLP/NUMLPH or MOLNT sections)
Example: Example:
>>> cs = ProteinStructure.load_from_psf('testfiles/test.psf') >>> cs = CharmmPsfFile.load_from_psf('testfiles/test.psf')
>>> cs.write_psf('testfiles/test2.psf') >>> cs.write_psf('testfiles/test2.psf')
""" """
# See if this is an extended format # See if this is an extended format
...@@ -769,8 +769,13 @@ class CharmmPsfFile(object): ...@@ -769,8 +769,13 @@ class CharmmPsfFile(object):
if atom.residue.idx != last_residue: if atom.residue.idx != last_residue:
last_residue = atom.residue.idx last_residue = atom.residue.idx
residue = topology.addResidue(atom.residue.resname, chain) residue = topology.addResidue(atom.residue.resname, chain)
atomic_num = atom.type.atomic_number if atom.type is not None:
elem = element.Element.getByAtomicNumber(atomic_num) # This is the most reliable way of determining the element
atomic_num = atom.type.atomic_number
elem = element.Element.getByAtomicNumber(atomic_num)
else:
# If we didn't load a parameter set yet, try based on mass
elem = element.Element.getByMass(atom.mass)
topology.addAtom(atom.name, elem, residue) topology.addAtom(atom.name, elem, residue)
# Add all of the bonds # Add all of the bonds
...@@ -933,7 +938,7 @@ class CharmmPsfFile(object): ...@@ -933,7 +938,7 @@ class CharmmPsfFile(object):
# back up the dihedral list # back up the dihedral list
dihedral_list = self.dihedral_list dihedral_list = self.dihedral_list
# Load the parameter set # Load the parameter set
self.load_parameters(params) self.load_parameters(params.condense())
hasbox = self.topology.getUnitCellDimensions() is not None hasbox = self.topology.getUnitCellDimensions() is not None
# Set the cutoff distance in nanometers # Set the cutoff distance in nanometers
cutoff = None cutoff = None
......
...@@ -34,7 +34,7 @@ __author__ = "Christopher M. Bruns" ...@@ -34,7 +34,7 @@ __author__ = "Christopher M. Bruns"
__version__ = "1.0" __version__ = "1.0"
from simtk.unit import daltons from simtk.unit import daltons, is_quantity
import copy_reg import copy_reg
class Element(object): class Element(object):
...@@ -93,6 +93,23 @@ class Element(object): ...@@ -93,6 +93,23 @@ class Element(object):
def getByAtomicNumber(atomic_number): def getByAtomicNumber(atomic_number):
return Element._elements_by_atomic_number[atomic_number] return Element._elements_by_atomic_number[atomic_number]
@staticmethod
def getByMass(mass):
# Assume masses are in daltons if they are not units
if not is_quantity(mass):
mass = mass * daltons
diff = mass
best_guess = 'EP'
for key in Element._elements_by_atomic_number:
element = Element._elements_by_atomic_number[key]
massdiff = abs(element.mass - mass)
if massdiff < diff:
best_guess = element
diff = massdiff
return best_guess
@property @property
def atomic_number(self): def atomic_number(self):
return self._atomic_number return self._atomic_number
......
...@@ -13,20 +13,13 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -13,20 +13,13 @@ class TestCharmmFiles(unittest.TestCase):
"""Set up the tests by loading the input files.""" """Set up the tests by loading the input files."""
# alanine tripeptide; no waters # alanine tripeptide; no waters
self.psf_c = CharmmPSF.load_from_psf('systems/ala_ala_ala.psf') self.psf_c = CharmmPsfFile('systems/ala_ala_ala.psf')
self.psf_x = CharmmPSF.load_from_psf('systems/ala_ala_ala.xpsf') self.psf_x = CharmmPsfFile('systems/ala_ala_ala.xpsf')
self.psf_v = CharmmPSF.load_from_psf('systems/ala_ala_ala.vpsf') self.psf_v = CharmmPsfFile('systems/ala_ala_ala.vpsf')
params = CharmmParameterSet.load_set( self.params = CharmmParameterSet(
tfile='systems/charmm22.rtf', 'systems/charmm22.rtf', 'systems/charmm22.par')
pfile='systems/charmm22.par',
).condense()
self.pdb = PDBFile('systems/ala_ala_ala.pdb') self.pdb = PDBFile('systems/ala_ala_ala.pdb')
# Load parameters
self.psf_c.load_parameters(params)
self.psf_x.load_parameters(params)
self.psf_v.load_parameters(params)
def test_NonbondedMethod(self): def test_NonbondedMethod(self):
"""Test both non-periodic methods for the systems""" """Test both non-periodic methods for the systems"""
...@@ -34,7 +27,7 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -34,7 +27,7 @@ class TestCharmmFiles(unittest.TestCase):
CutoffNonPeriodic:NonbondedForce.CutoffNonPeriodic} CutoffNonPeriodic:NonbondedForce.CutoffNonPeriodic}
for top in (self.psf_c, self.psf_x, self.psf_v): for top in (self.psf_c, self.psf_x, self.psf_v):
for method in methodMap: for method in methodMap:
system = top.createSystem(nonbondedMethod=method) system = top.createSystem(self.params, 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]
...@@ -45,7 +38,7 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -45,7 +38,7 @@ class TestCharmmFiles(unittest.TestCase):
for top in (self.psf_c, self.psf_x, self.psf_v): for top in (self.psf_c, self.psf_x, self.psf_v):
for method in [CutoffNonPeriodic]: for method in [CutoffNonPeriodic]:
system = top.createSystem(nonbondedMethod=method, system = top.createSystem(self.params, nonbondedMethod=method,
nonbondedCutoff=2*nanometer, nonbondedCutoff=2*nanometer,
constraints=HBonds) constraints=HBonds)
cutoff_distance = 0.0*nanometer cutoff_distance = 0.0*nanometer
...@@ -59,21 +52,21 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -59,21 +52,21 @@ class TestCharmmFiles(unittest.TestCase):
"""Test both options (True and False) for the removeCMMotion parameter.""" """Test both options (True and False) for the removeCMMotion parameter."""
for b in [True, False]: for b in [True, False]:
system = self.psf_c.createSystem(removeCMMotion=b) system = self.psf_c.createSystem(self.params, removeCMMotion=b)
self.assertEqual(any(isinstance(f, CMMotionRemover) for f in system.getForces()), b) self.assertEqual(any(isinstance(f, CMMotionRemover) for f in system.getForces()), b)
def test_ImplicitSolvent(self): def test_ImplicitSolvent(self):
"""Test implicit solvent using the implicitSolvent parameter. """Test implicit solvent using the implicitSolvent parameter.
""" """
system = self.psf_v.createSystem(implicitSolvent=OBC2) system = self.psf_v.createSystem(self.params, implicitSolvent=OBC2)
self.assertTrue(any(isinstance(f, CustomGBForce) for f in system.getForces())) self.assertTrue(any(isinstance(f, CustomGBForce) for f in system.getForces()))
def test_ImplicitSolventParameters(self): def test_ImplicitSolventParameters(self):
"""Test that solventDielectric and soluteDielectric are passed correctly. """Test that solventDielectric and soluteDielectric are passed correctly.
""" """
system = self.psf_x.createSystem(implicitSolvent=GBn, system = self.psf_x.createSystem(self.params, implicitSolvent=GBn,
solventDielectric=50.0, solventDielectric=50.0,
soluteDielectric = 0.9) soluteDielectric = 0.9)
found_matching_solvent_dielectric=False found_matching_solvent_dielectric=False
...@@ -97,8 +90,8 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -97,8 +90,8 @@ class TestCharmmFiles(unittest.TestCase):
topology = self.psf_v.topology topology = self.psf_v.topology
hydrogenMass = 4*amu hydrogenMass = 4*amu
system1 = self.psf_v.createSystem() system1 = self.psf_v.createSystem(self.params)
system2 = self.psf_v.createSystem(hydrogenMass=hydrogenMass) system2 = self.psf_v.createSystem(self.params, hydrogenMass=hydrogenMass)
for atom in topology.atoms(): for atom in topology.atoms():
if atom.element == elem.hydrogen: if atom.element == elem.hydrogen:
self.assertNotEqual(hydrogenMass, system1.getParticleMass(atom.index)) self.assertNotEqual(hydrogenMass, system1.getParticleMass(atom.index))
......
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