"vscode:/vscode.git/clone" did not exist on "bedf3c0cbb4d8fe3e4d9bc031e943a18985de4dc"
Commit 2d2f05ce authored by Andy Simmonett's avatar Andy Simmonett
Browse files

Merge branch 'master' of github.com:pandegroup/openmm into genpt

parents 94823d84 4d32047c
...@@ -104,12 +104,14 @@ class ForceField(object): ...@@ -104,12 +104,14 @@ class ForceField(object):
def __init__(self, *files): def __init__(self, *files):
"""Load one or more XML files and create a ForceField object based on them. """Load one or more XML files and create a ForceField object based on them.
Parameters: Parameters
- files (list) A list of XML files defining the force field. Each entry may ----------
be an absolute file path, a path relative to the current working files : list
directory, a path relative to this module's data subdirectory A list of XML files defining the force field. Each entry may
(for built in force fields), or an open file-like object with a be an absolute file path, a path relative to the current working
read() method from which the forcefield XML data can be loaded. directory, a path relative to this module's data subdirectory
(for built in force fields), or an open file-like object with a
read() method from which the forcefield XML data can be loaded.
""" """
self._atomTypes = {} self._atomTypes = {}
self._templates = {} self._templates = {}
...@@ -123,12 +125,14 @@ class ForceField(object): ...@@ -123,12 +125,14 @@ class ForceField(object):
def loadFile(self, file): def loadFile(self, file):
"""Load an XML file and add the definitions from it to this FieldField. """Load an XML file and add the definitions from it to this FieldField.
Parameters: Parameters
- file (string or file) An XML file containing force field definitions. It may ----------
be either an absolute file path, a path relative to the current working file : string or file
directory, a path relative to this module's data subdirectory An XML file containing force field definitions. It may be either an
(for built in force fields), or an open file-like object with a absolute file path, a path relative to the current working
read() method from which the forcefield XML data can be loaded. directory, a path relative to this module's data subdirectory (for
built in force fields), or an open file-like object with a read()
method from which the forcefield XML data can be loaded.
""" """
try: try:
# this handles either filenames or open file-like objects # this handles either filenames or open file-like objects
...@@ -149,14 +153,29 @@ class ForceField(object): ...@@ -149,14 +153,29 @@ class ForceField(object):
for residue in root.find('Residues').findall('Residue'): for residue in root.find('Residues').findall('Residue'):
resName = residue.attrib['name'] resName = residue.attrib['name']
template = ForceField._TemplateData(resName) template = ForceField._TemplateData(resName)
atomIndices = {}
for atom in residue.findall('Atom'): for atom in residue.findall('Atom'):
template.atoms.append(ForceField._TemplateAtomData(atom.attrib['name'], atom.attrib['type'], self._atomTypes[atom.attrib['type']][2])) params = {}
for key in atom.attrib:
if key not in ('name', 'type'):
params[key] = _convertParameterToNumber(atom.attrib[key])
atomName = atom.attrib['name']
if atomName in atomIndices:
raise ValueError('Residue '+resName+' contains multiple atoms named '+atomName)
atomIndices[atomName] = len(template.atoms)
template.atoms.append(ForceField._TemplateAtomData(atomName, atom.attrib['type'], self._atomTypes[atom.attrib['type']][2], params))
for site in residue.findall('VirtualSite'): for site in residue.findall('VirtualSite'):
template.virtualSites.append(ForceField._VirtualSiteData(site)) template.virtualSites.append(ForceField._VirtualSiteData(site, atomIndices))
for bond in residue.findall('Bond'): for bond in residue.findall('Bond'):
template.addBond(int(bond.attrib['from']), int(bond.attrib['to'])) if 'atomName1' in bond.attrib:
template.addBond(atomIndices[bond.attrib['atomName1']], atomIndices[bond.attrib['atomName2']])
else:
template.addBond(int(bond.attrib['from']), int(bond.attrib['to']))
for bond in residue.findall('ExternalBond'): for bond in residue.findall('ExternalBond'):
b = int(bond.attrib['from']) if 'atomName' in bond.attrib:
b = atomIndices[bond.attrib['atomName']]
else:
b = int(bond.attrib['from'])
template.externalBonds.append(b) template.externalBonds.append(b)
template.atoms[b].externalBonds += 1 template.atoms[b].externalBonds += 1
self.registerResidueTemplate(template) self.registerResidueTemplate(template)
...@@ -256,6 +275,7 @@ class ForceField(object): ...@@ -256,6 +275,7 @@ class ForceField(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):
self.atomType = {} self.atomType = {}
self.atomParameters = {}
self.atoms = [] self.atoms = []
self.excludeAtomWith = [] self.excludeAtomWith = []
self.virtualSites = {} self.virtualSites = {}
...@@ -265,6 +285,17 @@ class ForceField(object): ...@@ -265,6 +285,17 @@ class ForceField(object):
self.impropers = [] self.impropers = []
self.atomBonds = [] self.atomBonds = []
self.isAngleConstrained = [] self.isAngleConstrained = []
self.constraints = {}
def addConstraint(self, system, atom1, atom2, distance):
"""Add a constraint to the system, avoiding duplicate constraints."""
key = (min(atom1, atom2), max(atom1, atom2))
if key in self.constraints:
if self.constraints(key) != distance:
raise ValueError('Two constraints were specified between atoms %d and %d with different distances' % (atom1, atom2))
else:
self.constraints[key] = distance
system.addConstraint(atom1, atom2, distance)
class _TemplateData: class _TemplateData:
"""Inner class used to encapsulate data about a residue template definition.""" """Inner class used to encapsulate data about a residue template definition."""
...@@ -282,10 +313,11 @@ class ForceField(object): ...@@ -282,10 +313,11 @@ class ForceField(object):
class _TemplateAtomData: class _TemplateAtomData:
"""Inner class used to encapsulate data about an atom in a residue template definition.""" """Inner class used to encapsulate data about an atom in a residue template definition."""
def __init__(self, name, type, element): def __init__(self, name, type, element, parameters={}):
self.name = name self.name = name
self.type = type self.type = type
self.element = element self.element = element
self.parameters = parameters
self.bondedTo = [] self.bondedTo = []
self.externalBonds = 0 self.externalBonds = 0
...@@ -299,50 +331,142 @@ class ForceField(object): ...@@ -299,50 +331,142 @@ class ForceField(object):
class _VirtualSiteData: class _VirtualSiteData:
"""Inner class used to encapsulate data about a virtual site.""" """Inner class used to encapsulate data about a virtual site."""
def __init__(self, node): def __init__(self, node, atomIndices):
attrib = node.attrib attrib = node.attrib
self.index = int(attrib['index'])
self.type = attrib['type'] self.type = attrib['type']
if self.type == 'average2': if self.type == 'average2':
self.atoms = [int(attrib['atom1']), int(attrib['atom2'])] numAtoms = 2
self.weights = [float(attrib['weight1']), float(attrib['weight2'])] self.weights = [float(attrib['weight1']), float(attrib['weight2'])]
elif self.type == 'average3': elif self.type == 'average3':
self.atoms = [int(attrib['atom1']), int(attrib['atom2']), int(attrib['atom3'])] numAtoms = 3
self.weights = [float(attrib['weight1']), float(attrib['weight2']), float(attrib['weight3'])] self.weights = [float(attrib['weight1']), float(attrib['weight2']), float(attrib['weight3'])]
elif self.type == 'outOfPlane': elif self.type == 'outOfPlane':
self.atoms = [int(attrib['atom1']), int(attrib['atom2']), int(attrib['atom3'])] numAtoms = 3
self.weights = [float(attrib['weight12']), float(attrib['weight13']), float(attrib['weightCross'])] self.weights = [float(attrib['weight12']), float(attrib['weight13']), float(attrib['weightCross'])]
elif self.type == 'localCoords': elif self.type == 'localCoords':
self.atoms = [int(attrib['atom1']), int(attrib['atom2']), int(attrib['atom3'])] numAtoms = 3
self.originWeights = [float(attrib['wo1']), float(attrib['wo2']), float(attrib['wo3'])] self.originWeights = [float(attrib['wo1']), float(attrib['wo2']), float(attrib['wo3'])]
self.xWeights = [float(attrib['wx1']), float(attrib['wx2']), float(attrib['wx3'])] self.xWeights = [float(attrib['wx1']), float(attrib['wx2']), float(attrib['wx3'])]
self.yWeights = [float(attrib['wy1']), float(attrib['wy2']), float(attrib['wy3'])] self.yWeights = [float(attrib['wy1']), float(attrib['wy2']), float(attrib['wy3'])]
self.localPos = [float(attrib['p1']), float(attrib['p2']), float(attrib['p3'])] self.localPos = [float(attrib['p1']), float(attrib['p2']), float(attrib['p3'])]
else: else:
raise ValueError('Unknown virtual site type: %s' % self.type) raise ValueError('Unknown virtual site type: %s' % self.type)
if 'siteName' in attrib:
self.index = atomIndices[attrib['siteName']]
self.atoms = [atomIndices[attrib['atomName%d'%(i+1)]] for i in range(numAtoms)]
else:
self.index = int(attrib['index'])
self.atoms = [int(attrib['atom%d'%(i+1)]) for i in range(numAtoms)]
if 'excludeWith' in attrib: if 'excludeWith' in attrib:
self.excludeWith = int(attrib['excludeWith']) self.excludeWith = int(attrib['excludeWith'])
else: else:
self.excludeWith = self.atoms[0] self.excludeWith = self.atoms[0]
class _AtomTypeParameters:
"""Inner class used to record parameter values for atom types."""
def __init__(self, forcefield, forceName, atomTag, paramNames):
self.ff = forcefield
self.forceName = forceName
self.atomTag = atomTag
self.paramNames = paramNames
self.paramsForType = {}
self.extraParamsForType = {}
def registerAtom(self, parameters, expectedParams=None):
if expectedParams is None:
expectedParams = self.paramNames
types = self.ff._findAtomTypes(parameters, 1)
if None not in types:
values = {}
extraValues = {}
for key in parameters:
if key in expectedParams:
values[key] = _convertParameterToNumber(parameters[key])
else:
extraValues[key] = parameters[key]
if len(values) < len(expectedParams):
for key in expectedParams:
if key not in values:
raise ValueError('%s: No value specified for "%s"' % (self.forceName, key))
for t in types[0]:
self.paramsForType[t] = values
self.extraParamsForType[t] = extraValues
def parseDefinitions(self, element):
""""Load the definitions from an XML element."""
expectedParams = list(self.paramNames)
excludedParams = [node.attrib['name'] for node in element.findall('UseAttributeFromResidue')]
for param in excludedParams:
if param not in expectedParams:
raise ValueError('%s: <UseAttributeFromResidue> specified an invalid attribute: %s' % (self.forceName, param))
expectedParams.remove(param)
for atom in element.findall(self.atomTag):
for param in excludedParams:
if param in atom.attrib:
raise ValueError('%s: The attribute "%s" appeared in both <%s> and <UseAttributeFromResidue> tags' % (self.forceName, param, self.atomTag))
self.registerAtom(atom.attrib, expectedParams)
def getAtomParameters(self, atom, data):
"""Get the parameter values for a particular atom."""
t = data.atomType[atom]
p = data.atomParameters[atom]
if t in self.paramsForType:
values = self.paramsForType[t]
result = [None]*len(self.paramNames)
for i, name in enumerate(self.paramNames):
if name in values:
result[i] = values[name]
elif name in p:
result[i] = p[name]
else:
raise ValueError('%s: No value specified for "%s"' % (self.forceName, name))
return result
else:
raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t))
def getExtraParameters(self, atom, data):
"""Get extra parameter values for an atom that appeared in the <Atom> tag but were not included in paramNames."""
t = data.atomType[atom]
if t in self.paramsForType:
return self.extraParamsForType[t]
else:
raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t))
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.
Parameters: Parameters
- topology (Topology) The Topology for which to create a System ----------
- nonbondedMethod (object=NoCutoff) The method to use for nonbonded interactions. Allowed values are topology : Topology
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME. The Topology for which to create a System
- nonbondedCutoff (distance=1*nanometer) The cutoff distance to use for nonbonded interactions nonbondedMethod : object=NoCutoff
- constraints (object=None) Specifies which bonds and angles should be implemented with constraints. The method to use for nonbonded interactions. Allowed values are
Allowed values are None, HBonds, AllBonds, or HAngles. NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
- rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the constraints argument nonbondedCutoff : distance=1*nanometer
- removeCMMotion (boolean=True) If true, a CMMotionRemover will be added to the System The cutoff distance to use for nonbonded interactions
- hydrogenMass (mass=None) The mass to use for hydrogen atoms bound to heavy atoms. Any mass added to a hydrogen is constraints : object=None
subtracted from the heavy atom to keep their total mass the same. Specifies which bonds and angles should be implemented with constraints.
- args Arbitrary additional keyword arguments may also be specified. This allows extra parameters to be specified that are specific to Allowed values are None, HBonds, AllBonds, or HAngles.
particular force fields. rigidWater : boolean=True
Returns: the newly created System If true, water molecules will be fully rigid regardless of the value
passed for the constraints argument
removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None
The mass to use for hydrogen atoms bound to heavy atoms. Any mass
added to a hydrogen is subtracted from the heavy atom to keep
their total mass the same.
args
Arbitrary additional keyword arguments may also be specified.
This allows extra parameters to be specified that are specific to
particular force fields.
Returns
-------
system
the newly created System
""" """
data = ForceField._SystemData() data = ForceField._SystemData()
data.atoms = list(topology.atoms()) data.atoms = list(topology.atoms())
...@@ -385,6 +509,7 @@ class ForceField(object): ...@@ -385,6 +509,7 @@ class ForceField(object):
matchAtoms = dict(zip(matches, res.atoms())) matchAtoms = dict(zip(matches, res.atoms()))
for atom, match in zip(res.atoms(), matches): for atom, match in zip(res.atoms(), matches):
data.atomType[atom] = template.atoms[match].type data.atomType[atom] = template.atoms[match].type
data.atomParameters[atom] = template.atoms[match].parameters
for site in template.virtualSites: for site in template.virtualSites:
if match == site.index: if match == site.index:
data.virtualSites[atom] = (site, [matchAtoms[i].index for i in site.atoms], matchAtoms[site.excludeWith].index) data.virtualSites[atom] = (site, [matchAtoms[i].index for i in site.atoms], matchAtoms[site.excludeWith].index)
...@@ -569,12 +694,20 @@ def _createResidueSignature(elements): ...@@ -569,12 +694,20 @@ def _createResidueSignature(elements):
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.
Parameters: Parameters
- res (Residue) The residue to check ----------
- template (_TemplateData) The template to compare it to res : Residue
- bondedToAtom (list) Enumerates which other atoms each atom is bonded to The residue to check
Returns: a list specifying which atom of the template each atom of the residue corresponds to, template : _TemplateData
or None if it does not match the template The template to compare it to
bondedToAtom : list
Enumerates which other atoms each atom is bonded to
Returns
-------
list
a list specifying which atom of the template each atom of the residue
corresponds to, or None if it does not match the template
""" """
atoms = list(res.atoms()) atoms = list(res.atoms())
if len(atoms) != len(template.atoms): if len(atoms) != len(template.atoms):
...@@ -751,7 +884,7 @@ class HarmonicBondGenerator: ...@@ -751,7 +884,7 @@ class HarmonicBondGenerator:
if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1): if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
bond.length = self.length[i] bond.length = self.length[i]
if bond.isConstrained: if bond.isConstrained:
sys.addConstraint(bond.atom1, bond.atom2, self.length[i]) data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
elif self.k[i] != 0: elif self.k[i] != 0:
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i]) force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
break break
...@@ -824,7 +957,7 @@ class HarmonicAngleGenerator: ...@@ -824,7 +957,7 @@ class HarmonicAngleGenerator:
l2 = data.bonds[bond2].length l2 = data.bonds[bond2].length
if l1 is not None and l2 is not None: if l1 is not None and l2 is not None:
length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(self.angle[i])) length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(self.angle[i]))
sys.addConstraint(angle[0], angle[2], length) data.addConstraint(sys, angle[0], angle[2], length)
elif self.k[i] != 0: elif self.k[i] != 0:
force.addAngle(angle[0], angle[1], angle[2], self.angle[i], self.k[i]) force.addAngle(angle[0], angle[1], angle[2], self.angle[i], self.k[i])
break break
...@@ -1138,14 +1271,10 @@ class NonbondedGenerator: ...@@ -1138,14 +1271,10 @@ class NonbondedGenerator:
self.ff = forcefield self.ff = forcefield
self.coulomb14scale = coulomb14scale self.coulomb14scale = coulomb14scale
self.lj14scale = lj14scale self.lj14scale = lj14scale
self.typeMap = {} self.params = ForceField._AtomTypeParameters(forcefield, 'NonbondedForce', 'Atom', ('charge', 'sigma', 'epsilon'))
def registerAtom(self, parameters): def registerAtom(self, parameters):
types = self.ff._findAtomTypes(parameters, 1) self.params.registerAtom(parameters)
if None not in types:
values = (_convertParameterToNumber(parameters['charge']), _convertParameterToNumber(parameters['sigma']), _convertParameterToNumber(parameters['epsilon']))
for t in types[0]:
self.typeMap[t] = values
@staticmethod @staticmethod
def parseElement(element, ff): def parseElement(element, ff):
...@@ -1158,8 +1287,7 @@ class NonbondedGenerator: ...@@ -1158,8 +1287,7 @@ class NonbondedGenerator:
generator = existing[0] generator = existing[0]
if generator.coulomb14scale != float(element.attrib['coulomb14scale']) or generator.lj14scale != float(element.attrib['lj14scale']): if generator.coulomb14scale != float(element.attrib['coulomb14scale']) or generator.lj14scale != float(element.attrib['lj14scale']):
raise ValueError('Found multiple NonbondedForce tags with different 1-4 scales') raise ValueError('Found multiple NonbondedForce tags with different 1-4 scales')
for atom in element.findall('Atom'): generator.params.parseDefinitions(element)
generator.registerAtom(atom.attrib)
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.NonbondedForce.NoCutoff, methodMap = {NoCutoff:mm.NonbondedForce.NoCutoff,
...@@ -1171,12 +1299,8 @@ class NonbondedGenerator: ...@@ -1171,12 +1299,8 @@ class NonbondedGenerator:
raise ValueError('Illegal nonbonded method for NonbondedForce') raise ValueError('Illegal nonbonded method for NonbondedForce')
force = mm.NonbondedForce() force = mm.NonbondedForce()
for atom in data.atoms: for atom in data.atoms:
t = data.atomType[atom] values = self.params.getAtomParameters(atom, data)
if t in self.typeMap: force.addParticle(values[0], values[1], values[2])
values = self.typeMap[t]
force.addParticle(values[0], values[1], values[2])
else:
raise ValueError('No nonbonded parameters defined for atom type '+t)
force.setNonbondedMethod(methodMap[nonbondedMethod]) force.setNonbondedMethod(methodMap[nonbondedMethod])
force.setCutoffDistance(nonbondedCutoff) force.setCutoffDistance(nonbondedCutoff)
if 'ewaldErrorTolerance' in args: if 'ewaldErrorTolerance' in args:
...@@ -1225,14 +1349,10 @@ class GBSAOBCGenerator: ...@@ -1225,14 +1349,10 @@ class GBSAOBCGenerator:
def __init__(self, forcefield): def __init__(self, forcefield):
self.ff = forcefield self.ff = forcefield
self.typeMap = {} self.params = ForceField._AtomTypeParameters(forcefield, 'GBSAOBCForce', 'Atom', ('charge', 'radius', 'scale'))
def registerAtom(self, parameters): def registerAtom(self, parameters):
types = self.ff._findAtomTypes(parameters, 1) self.params.registerAtom(parameters)
if None not in types:
values = (_convertParameterToNumber(parameters['charge']), _convertParameterToNumber(parameters['radius']), _convertParameterToNumber(parameters['scale']))
for t in types[0]:
self.typeMap[t] = values
@staticmethod @staticmethod
def parseElement(element, ff): def parseElement(element, ff):
...@@ -1243,8 +1363,7 @@ class GBSAOBCGenerator: ...@@ -1243,8 +1363,7 @@ class GBSAOBCGenerator:
else: else:
# Multiple <GBSAOBCForce> tags were found, probably in different files. Simply add more types to the existing one. # Multiple <GBSAOBCForce> tags were found, probably in different files. Simply add more types to the existing one.
generator = existing[0] generator = existing[0]
for atom in element.findall('Atom'): generator.params.parseDefinitions(element)
generator.registerAtom(atom.attrib)
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.NonbondedForce.NoCutoff, methodMap = {NoCutoff:mm.NonbondedForce.NoCutoff,
...@@ -1254,12 +1373,8 @@ class GBSAOBCGenerator: ...@@ -1254,12 +1373,8 @@ class GBSAOBCGenerator:
raise ValueError('Illegal nonbonded method for GBSAOBCForce') raise ValueError('Illegal nonbonded method for GBSAOBCForce')
force = mm.GBSAOBCForce() force = mm.GBSAOBCForce()
for atom in data.atoms: for atom in data.atoms:
t = data.atomType[atom] values = self.params.getAtomParameters(atom, data)
if t in self.typeMap: force.addParticle(values[0], values[1], values[2])
values = self.typeMap[t]
force.addParticle(values[0], values[1], values[2])
else:
raise ValueError('No GBSAOBC parameters defined for atom type '+t)
force.setNonbondedMethod(methodMap[nonbondedMethod]) force.setNonbondedMethod(methodMap[nonbondedMethod])
force.setCutoffDistance(nonbondedCutoff) force.setCutoffDistance(nonbondedCutoff)
if 'soluteDielectric' in args: if 'soluteDielectric' in args:
...@@ -1278,92 +1393,6 @@ class GBSAOBCGenerator: ...@@ -1278,92 +1393,6 @@ class GBSAOBCGenerator:
parsers["GBSAOBCForce"] = GBSAOBCGenerator.parseElement parsers["GBSAOBCForce"] = GBSAOBCGenerator.parseElement
## @private
class GBVIGenerator:
"""A GBVIGenerator constructs a GBVIForce."""
def __init__(self,ff):
self.ff = ff
self.fixedParameters = {}
self.fixedParameters['soluteDielectric'] = 1.0
self.fixedParameters['solventDielectric'] = 78.3
self.fixedParameters['scalingMethod'] = 1
self.fixedParameters['quinticUpperBornRadiusLimit'] = 5.0
self.fixedParameters['quinticLowerLimitFactor'] = 0.8
self.typeMap = {}
@staticmethod
def parseElement(element, ff):
generator = GBVIGenerator(ff)
for key in generator.fixedParameters.iterkeys():
if (key in element.attrib):
generator.fixedParameters[key] = float(element.attrib[key])
ff.registerGenerator(generator)
for atom in element.findall('Atom'):
types = ff._findAtomTypes(atom.attrib, 1)
if None not in types:
values = (float(atom.attrib['charge']), float(atom.attrib['radius']), float(atom.attrib['gamma']))
for t in types[0]:
generator.typeMap[t] = values
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.NonbondedForce.NoCutoff,
CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic}
if nonbondedMethod not in methodMap:
raise ValueError('Illegal nonbonded method for GB/VI Force')
# add particles
force = mm.GBVIForce()
for atom in data.atoms:
t = data.atomType[atom]
if t in self.typeMap:
values = self.typeMap[t]
force.addParticle(values[0], values[1], values[2])
else:
raise ValueError('No GB/VI parameters defined for atom type '+t)
# get HarmonicBond generator -- exit if not found
hbGenerator = 0
for generator in self.ff._forces:
if (generator.__class__.__name__ == 'HarmonicBondGenerator'):
hbGenerator = generator
break
if (hbGenerator == 0):
raise ValueError('HarmonicBondGenerator not found.')
# add bonds
for bond in data.bonds:
type1 = data.atomType[data.atoms[bond.atom1]]
type2 = data.atomType[data.atoms[bond.atom2]]
for i in range(len(hbGenerator.types1)):
types1 = hbGenerator.types1[i]
types2 = hbGenerator.types2[i]
if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
#bond.length = hbGenerator.length[i]
force.addBond(bond.atom1, bond.atom2, hbGenerator.length[i])
force.setNonbondedMethod(methodMap[nonbondedMethod])
force.setCutoffDistance(nonbondedCutoff)
force.setSolventDielectric(self.fixedParameters['solventDielectric'])
force.setSoluteDielectric(self.fixedParameters['soluteDielectric'])
force.setBornRadiusScalingMethod(self.fixedParameters['scalingMethod'])
force.setQuinticLowerLimitFactor(self.fixedParameters['quinticLowerLimitFactor'])
force.setQuinticUpperBornRadiusLimit(self.fixedParameters['quinticUpperBornRadiusLimit'])
sys.addForce(force)
parsers["GBVIForce"] = GBVIGenerator.parseElement
## @private ## @private
class CustomBondGenerator: class CustomBondGenerator:
"""A CustomBondGenerator constructs a CustomBondForce.""" """A CustomBondGenerator constructs a CustomBondForce."""
...@@ -1577,7 +1606,6 @@ class CustomNonbondedGenerator: ...@@ -1577,7 +1606,6 @@ class CustomNonbondedGenerator:
self.ff = forcefield self.ff = forcefield
self.energy = energy self.energy = energy
self.bondCutoff = bondCutoff self.bondCutoff = bondCutoff
self.typeMap = {}
self.globalParams = {} self.globalParams = {}
self.perParticleParams = [] self.perParticleParams = []
self.functions = [] self.functions = []
...@@ -1590,12 +1618,8 @@ class CustomNonbondedGenerator: ...@@ -1590,12 +1618,8 @@ class CustomNonbondedGenerator:
generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue']) generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
for param in element.findall('PerParticleParameter'): for param in element.findall('PerParticleParameter'):
generator.perParticleParams.append(param.attrib['name']) generator.perParticleParams.append(param.attrib['name'])
for atom in element.findall('Atom'): generator.params = ForceField._AtomTypeParameters(ff, 'CustomNonbondedForce', 'Atom', generator.perParticleParams)
types = ff._findAtomTypes(atom.attrib, 1) generator.params.parseDefinitions(element)
if None not in types:
values = [float(atom.attrib[param]) for param in generator.perParticleParams]
for t in types[0]:
generator.typeMap[t] = values
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.CustomNonbondedForce.NoCutoff, methodMap = {NoCutoff:mm.CustomNonbondedForce.NoCutoff,
...@@ -1622,11 +1646,8 @@ class CustomNonbondedGenerator: ...@@ -1622,11 +1646,8 @@ class CustomNonbondedGenerator:
elif type == 'Discrete3D': elif type == 'Discrete3D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values)) force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
for atom in data.atoms: for atom in data.atoms:
t = data.atomType[atom] values = self.params.getAtomParameters(atom, data)
if t in self.typeMap: force.addParticle(values)
force.addParticle(self.typeMap[t])
else:
raise ValueError('No CustomNonbonded parameters defined for atom type '+t)
force.setNonbondedMethod(methodMap[nonbondedMethod]) force.setNonbondedMethod(methodMap[nonbondedMethod])
force.setCutoffDistance(nonbondedCutoff) force.setCutoffDistance(nonbondedCutoff)
sys.addForce(force) sys.addForce(force)
...@@ -1671,7 +1692,6 @@ class CustomGBGenerator: ...@@ -1671,7 +1692,6 @@ class CustomGBGenerator:
def __init__(self, forcefield): def __init__(self, forcefield):
self.ff = forcefield self.ff = forcefield
self.typeMap = {}
self.globalParams = {} self.globalParams = {}
self.perParticleParams = [] self.perParticleParams = []
self.computedValues = [] self.computedValues = []
...@@ -1686,12 +1706,8 @@ class CustomGBGenerator: ...@@ -1686,12 +1706,8 @@ class CustomGBGenerator:
generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue']) generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
for param in element.findall('PerParticleParameter'): for param in element.findall('PerParticleParameter'):
generator.perParticleParams.append(param.attrib['name']) generator.perParticleParams.append(param.attrib['name'])
for atom in element.findall('Atom'): generator.params = ForceField._AtomTypeParameters(ff, 'CustomGBForce', 'Atom', generator.perParticleParams)
types = ff._findAtomTypes(atom.attrib, 1) generator.params.parseDefinitions(element)
if None not in types:
values = [float(atom.attrib[param]) for param in generator.perParticleParams]
for t in types[0]:
generator.typeMap[t] = values
computationMap = {"SingleParticle" : mm.CustomGBForce.SingleParticle, computationMap = {"SingleParticle" : mm.CustomGBForce.SingleParticle,
"ParticlePair" : mm.CustomGBForce.ParticlePair, "ParticlePair" : mm.CustomGBForce.ParticlePair,
"ParticlePairNoExclusions" : mm.CustomGBForce.ParticlePairNoExclusions} "ParticlePairNoExclusions" : mm.CustomGBForce.ParticlePairNoExclusions}
...@@ -1742,11 +1758,8 @@ class CustomGBGenerator: ...@@ -1742,11 +1758,8 @@ class CustomGBGenerator:
elif type == 'Discrete3D': elif type == 'Discrete3D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values)) force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
for atom in data.atoms: for atom in data.atoms:
t = data.atomType[atom] values = self.params.getAtomParameters(atom, data)
if t in self.typeMap: force.addParticle(values)
force.addParticle(self.typeMap[t])
else:
raise ValueError('No CustomGB parameters defined for atom type '+t)
force.setNonbondedMethod(methodMap[nonbondedMethod]) force.setNonbondedMethod(methodMap[nonbondedMethod])
force.setCutoffDistance(nonbondedCutoff) force.setCutoffDistance(nonbondedCutoff)
sys.addForce(force) sys.addForce(force)
...@@ -1764,7 +1777,6 @@ class CustomManyParticleGenerator: ...@@ -1764,7 +1777,6 @@ class CustomManyParticleGenerator:
self.energy = energy self.energy = energy
self.permutationMode = permutationMode self.permutationMode = permutationMode
self.bondCutoff = bondCutoff self.bondCutoff = bondCutoff
self.typeMap = {}
self.globalParams = {} self.globalParams = {}
self.perParticleParams = [] self.perParticleParams = []
self.functions = [] self.functions = []
...@@ -1782,12 +1794,8 @@ class CustomManyParticleGenerator: ...@@ -1782,12 +1794,8 @@ class CustomManyParticleGenerator:
generator.perParticleParams.append(param.attrib['name']) generator.perParticleParams.append(param.attrib['name'])
for param in element.findall('TypeFilter'): for param in element.findall('TypeFilter'):
generator.typeFilters.append((int(param.attrib['index']), [int(x) for x in param.attrib['types'].split(',')])) generator.typeFilters.append((int(param.attrib['index']), [int(x) for x in param.attrib['types'].split(',')]))
for atom in element.findall('Atom'): generator.params = ForceField._AtomTypeParameters(ff, 'CustomManyParticleForce', 'Atom', generator.perParticleParams)
types = ff._findAtomTypes(atom.attrib, 1) generator.params.parseDefinitions(element)
if None not in types:
values = [float(atom.attrib[param]) for param in generator.perParticleParams]
for t in types[0]:
generator.typeMap[t] = (values, int(atom.attrib['filterType']))
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.CustomManyParticleForce.NoCutoff, methodMap = {NoCutoff:mm.CustomManyParticleForce.NoCutoff,
...@@ -1817,12 +1825,9 @@ class CustomManyParticleGenerator: ...@@ -1817,12 +1825,9 @@ class CustomManyParticleGenerator:
elif type == 'Discrete3D': elif type == 'Discrete3D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values)) force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
for atom in data.atoms: for atom in data.atoms:
t = data.atomType[atom] values = self.params.getAtomParameters(atom, data)
if t in self.typeMap: type = int(self.params.getExtraParameters(atom, data)['filterType'])
values = self.typeMap[t] force.addParticle(values, type)
force.addParticle(values[0], values[1])
else:
raise ValueError('No CustomManyParticle parameters defined for atom type '+t)
force.setNonbondedMethod(methodMap[nonbondedMethod]) force.setNonbondedMethod(methodMap[nonbondedMethod])
force.setCutoffDistance(nonbondedCutoff) force.setCutoffDistance(nonbondedCutoff)
sys.addForce(force) sys.addForce(force)
...@@ -1954,7 +1959,7 @@ class AmoebaBondGenerator: ...@@ -1954,7 +1959,7 @@ class AmoebaBondGenerator:
if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1): if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
bond.length = self.length[i] bond.length = self.length[i]
if bond.isConstrained: if bond.isConstrained:
sys.addConstraint(bond.atom1, bond.atom2, self.length[i]) data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
elif self.k[i] != 0: elif self.k[i] != 0:
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i]) force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
break break
...@@ -1986,7 +1991,7 @@ def addAngleConstraint(angle, idealAngle, data, sys): ...@@ -1986,7 +1991,7 @@ def addAngleConstraint(angle, idealAngle, data, sys):
l2 = data.bonds[bond2].length l2 = data.bonds[bond2].length
if l1 is not None and l2 is not None: if l1 is not None and l2 is not None:
length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(idealAngle)) length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(idealAngle))
sys.addConstraint(angle[0], angle[2], length) data.addConstraint(sys, angle[0], angle[2], length)
return return
#============================================================================================= #=============================================================================================
...@@ -2620,7 +2625,7 @@ class AmoebaPiTorsionGenerator: ...@@ -2620,7 +2625,7 @@ class AmoebaPiTorsionGenerator:
atom1 = bond.atom1 atom1 = bond.atom1
atom2 = bond.atom2 atom2 = bond.atom2
if (len(data.atomBonds[atom1]) == 3 and len(data.atomBonds[atom1]) == 3): if (len(data.atomBonds[atom1]) == 3 and len(data.atomBonds[atom2]) == 3):
type1 = data.atomType[data.atoms[atom1]] type1 = data.atomType[data.atoms[atom1]]
type2 = data.atomType[data.atoms[atom2]] type2 = data.atomType[data.atoms[atom2]]
...@@ -2905,7 +2910,7 @@ class AmoebaTorsionTorsionGenerator: ...@@ -2905,7 +2910,7 @@ class AmoebaTorsionTorsionGenerator:
# match in reverse order # match in reverse order
if (type5 in types1 and type4 in types2 and type3 in types3 and type2 in types4 and type1 in types5): elif (type5 in types1 and type4 in types2 and type3 in types3 and type2 in types4 and type1 in types5):
chiralAtomIndex = self.getChiralAtomIndex(data, sys, ib, ic, id) chiralAtomIndex = self.getChiralAtomIndex(data, sys, ib, ic, id)
force.addTorsionTorsion(ie, id, ic, ib, ia, chiralAtomIndex, self.gridIndex[i]) force.addTorsionTorsion(ie, id, ic, ib, ia, chiralAtomIndex, self.gridIndex[i])
...@@ -3083,8 +3088,6 @@ class AmoebaVdwGenerator: ...@@ -3083,8 +3088,6 @@ class AmoebaVdwGenerator:
self.vdw14Scale = vdw14Scale self.vdw14Scale = vdw14Scale
self.vdw15Scale = vdw15Scale self.vdw15Scale = vdw15Scale
self.typeMap = {}
#============================================================================================= #=============================================================================================
@staticmethod @staticmethod
...@@ -3097,31 +3100,10 @@ class AmoebaVdwGenerator: ...@@ -3097,31 +3100,10 @@ class AmoebaVdwGenerator:
generator = AmoebaVdwGenerator(element.attrib['type'], element.attrib['radiusrule'], element.attrib['radiustype'], element.attrib['radiussize'], element.attrib['epsilonrule'], generator = AmoebaVdwGenerator(element.attrib['type'], element.attrib['radiusrule'], element.attrib['radiustype'], element.attrib['radiussize'], element.attrib['epsilonrule'],
float(element.attrib['vdw-13-scale']), float(element.attrib['vdw-14-scale']), float(element.attrib['vdw-15-scale'])) float(element.attrib['vdw-13-scale']), float(element.attrib['vdw-14-scale']), float(element.attrib['vdw-15-scale']))
forceField._forces.append(generator) forceField._forces.append(generator)
generator.params = ForceField._AtomTypeParameters(forceField, 'AmoebaVdwForce', 'Vdw', ('sigma', 'epsilon', 'reduction'))
generator.params.parseDefinitions(element)
two_six = 1.122462048309372 two_six = 1.122462048309372
# types[] = [ sigma, epsilon, reductionFactor, class ]
# sigma is modified based on radiustype and radiussize
for atom in element.findall('Vdw'):
types = forceField._findAtomTypes(atom.attrib, 1)
if None not in types:
values = [float(atom.attrib['sigma']), float(atom.attrib['epsilon']), float(atom.attrib['reduction'])]
if (generator.radiustype == 'SIGMA'):
values[0] *= two_six
if (generator.radiussize == 'DIAMETER'):
values[0] *= 0.5
for t in types[0]:
generator.typeMap[t] = values
else:
outputString = "AmoebaVdwGenerator: error getting type: %s" % (atom.attrib['class'])
raise ValueError(outputString)
#============================================================================================= #=============================================================================================
# Return a set containing the indices of particles bonded to particle with index=particleIndex # Return a set containing the indices of particles bonded to particle with index=particleIndex
...@@ -3199,28 +3181,25 @@ class AmoebaVdwGenerator: ...@@ -3199,28 +3181,25 @@ class AmoebaVdwGenerator:
force = existing[0] force = existing[0]
# add particles to force # add particles to force
# throw error if particle type not available
sigmaScale = 1
if self.radiustype == 'SIGMA':
sigmaScale = 1.122462048309372
if self.radiussize == 'DIAMETER':
sigmaScale = 0.5
for (i, atom) in enumerate(data.atoms): for (i, atom) in enumerate(data.atoms):
t = data.atomType[atom] values = self.params.getAtomParameters(atom, data)
if t in self.typeMap: # ivIndex = index of bonded partner for hydrogens; otherwise ivIndex = particle index
values = self.typeMap[t] ivIndex = i
if atom.element == elem.hydrogen and len(data.atomBonds[i]) == 1:
# ivIndex = index of bonded partner for hydrogens; otherwise ivIndex = particle index bondIndex = data.atomBonds[i][0]
if (data.bonds[bondIndex].atom1 == i):
ivIndex = i ivIndex = data.bonds[bondIndex].atom2
mass = sys.getParticleMass(i)/unit.dalton else:
if (mass < 1.9 and len(data.atomBonds[i]) == 1): ivIndex = data.bonds[bondIndex].atom1
bondIndex = data.atomBonds[i][0]
if (data.bonds[bondIndex].atom1 == i):
ivIndex = data.bonds[bondIndex].atom2
else:
ivIndex = data.bonds[bondIndex].atom1
force.addParticle(ivIndex, values[0], values[1], values[2]) force.addParticle(ivIndex, values[0]*sigmaScale, values[1], values[2])
else:
raise ValueError('No vdw type for atom %s' % (atom.name))
# set combining rules # set combining rules
...@@ -3937,8 +3916,6 @@ class AmoebaWcaDispersionGenerator: ...@@ -3937,8 +3916,6 @@ class AmoebaWcaDispersionGenerator:
self.dispoff = dispoff self.dispoff = dispoff
self.shctd = shctd self.shctd = shctd
self.typeMap = {}
#========================================================================================= #=========================================================================================
@staticmethod @staticmethod
...@@ -3957,19 +3934,8 @@ class AmoebaWcaDispersionGenerator: ...@@ -3957,19 +3934,8 @@ class AmoebaWcaDispersionGenerator:
element.attrib['dispoff'], element.attrib['dispoff'],
element.attrib['shctd']) element.attrib['shctd'])
forceField._forces.append(generator) forceField._forces.append(generator)
generator.params = ForceField._AtomTypeParameters(forceField, 'AmoebaWcaDispersionForce', 'WcaDispersion', ('radius', 'epsilon'))
# typeMap[] = [ radius, epsilon ] generator.params.parseDefinitions(element)
for atom in element.findall('WcaDispersion'):
types = forceField._findAtomTypes(atom.attrib, 1)
if None not in types:
values = [float(atom.attrib['radius']), float(atom.attrib['epsilon'])]
for t in types[0]:
generator.typeMap[t] = values
else:
outputString = "AmoebaWcaDispersionGenerator: error getting type: %s" % (atom.attrib['class'])
raise ValueError(outputString)
#========================================================================================= #=========================================================================================
...@@ -3997,14 +3963,9 @@ class AmoebaWcaDispersionGenerator: ...@@ -3997,14 +3963,9 @@ class AmoebaWcaDispersionGenerator:
force.setAwater( float(self.awater )) force.setAwater( float(self.awater ))
force.setShctd( float(self.shctd )) force.setShctd( float(self.shctd ))
for (i, atom) in enumerate(data.atoms): for atom in data.atoms:
t = data.atomType[atom] values = self.params.getAtomParameters(atom, data)
if t in self.typeMap: force.addParticle(values[0], values[1])
values = self.typeMap[t]
force.addParticle(values[0], values[1])
else:
raise ValueError('No WcaDispersion type for atom %s of %s %d' % (atom.name, atom.residue.name, atom.residue.index))
parsers["AmoebaWcaDispersionForce"] = AmoebaWcaDispersionGenerator.parseElement parsers["AmoebaWcaDispersionForce"] = AmoebaWcaDispersionGenerator.parseElement
......
...@@ -114,10 +114,11 @@ class GromacsGroFile(object): ...@@ -114,10 +114,11 @@ class GromacsGroFile(object):
The atom positions can be retrieved by calling getPositions(). The atom positions can be retrieved by calling getPositions().
Parameters: Parameters
- file (string) the name of the file to load ----------
file : string
the name of the file to load
""" """
xyzs = [] xyzs = []
elements = [] # The element, most useful for quantum chemistry calculations elements = [] # The element, most useful for quantum chemistry calculations
atomname = [] # The atom name, for instance 'HW1' atomname = [] # The atom name, for instance 'HW1'
...@@ -183,10 +184,14 @@ class GromacsGroFile(object): ...@@ -183,10 +184,14 @@ class GromacsGroFile(object):
def getPositions(self, asNumpy=False, frame=0): def getPositions(self, asNumpy=False, frame=0):
"""Get the atomic positions. """Get the atomic positions.
Parameters: Parameters
- asNumpy (boolean=False) if true, the values are returned as a numpy array instead of a list of Vec3s ----------
- frame (int=0) the index of the frame for which to get positions asNumpy : boolean=False
""" if true, the values are returned as a numpy array instead of a list
of Vec3s
frame : int=0
the index of the frame for which to get positions
"""
if asNumpy: if asNumpy:
if self._numpyPositions is None: if self._numpyPositions is None:
self._numpyPositions = [None]*len(self._positions) self._numpyPositions = [None]*len(self._positions)
...@@ -198,16 +203,20 @@ class GromacsGroFile(object): ...@@ -198,16 +203,20 @@ class GromacsGroFile(object):
def getPeriodicBoxVectors(self, frame=0): def getPeriodicBoxVectors(self, frame=0):
"""Get the vectors defining the periodic box. """Get the vectors defining the periodic box.
Parameters: Parameters
- frame (int=0) the index of the frame for which to get the box vectors ----------
frame : int=0
the index of the frame for which to get the box vectors
""" """
return self._periodicBoxVectors[frame] return self._periodicBoxVectors[frame]
def getUnitCellDimensions(self, frame=0): def getUnitCellDimensions(self, frame=0):
"""Get the dimensions of the crystallographic unit cell. """Get the dimensions of the crystallographic unit cell.
Parameters: Parameters
- frame (int=0) the index of the frame for which to get the unit cell dimensions ----------
frame : int=0
the index of the frame for which to get the unit cell dimensions
""" """
xsize = self._periodicBoxVectors[frame][0][0].value_in_unit(nanometers) xsize = self._periodicBoxVectors[frame][0][0].value_in_unit(nanometers)
ysize = self._periodicBoxVectors[frame][1][1].value_in_unit(nanometers) ysize = self._periodicBoxVectors[frame][1][1].value_in_unit(nanometers)
......
...@@ -433,16 +433,22 @@ class GromacsTopFile(object): ...@@ -433,16 +433,22 @@ class GromacsTopFile(object):
def __init__(self, file, periodicBoxVectors=None, unitCellDimensions=None, includeDir=None, defines=None): def __init__(self, file, periodicBoxVectors=None, unitCellDimensions=None, includeDir=None, defines=None):
"""Load a top file. """Load a top file.
Parameters: Parameters
- file (string) the name of the file to load ----------
- periodicBoxVectors (tuple of Vec3=None) the vectors defining the periodic box file : str
- unitCellDimensions (Vec3=None) the dimensions of the crystallographic unit cell. For the name of the file to load
non-rectangular unit cells, specify periodicBoxVectors instead. periodicBoxVectors : tuple of Vec3=None
- includeDir (string=None) A directory in which to look for other files the vectors defining the periodic box
included from the top file. If not specified, we will attempt to locate a gromacs unitCellDimensions : Vec3=None
installation on your system. When gromacs is installed in /usr/local, this will resolve the dimensions of the crystallographic unit cell. For
to /usr/local/gromacs/share/gromacs/top non-rectangular unit cells, specify periodicBoxVectors instead.
- defines (dict={}) preprocessor definitions that should be predefined when parsing the file includeDir : string=None
A directory in which to look for other files included from the
top file. If not specified, we will attempt to locate a gromacs
installation on your system. When gromacs is installed in
/usr/local, this will resolve to /usr/local/gromacs/share/gromacs/top
defines : dict={}
preprocessor definitions that should be predefined when parsing the file
""" """
if includeDir is None: if includeDir is None:
includeDir = _defaultGromacsIncludeDir() includeDir = _defaultGromacsIncludeDir()
...@@ -539,23 +545,43 @@ class GromacsTopFile(object): ...@@ -539,23 +545,43 @@ class GromacsTopFile(object):
def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*unit.nanometer, def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
constraints=None, rigidWater=True, implicitSolvent=None, soluteDielectric=1.0, solventDielectric=78.5, ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None): constraints=None, rigidWater=True, implicitSolvent=None, soluteDielectric=1.0, solventDielectric=78.5, ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None):
"""Construct an OpenMM System representing the topology described by this prmtop file. """Construct an OpenMM System representing the topology described by this
prmtop file.
Parameters:
- nonbondedMethod (object=NoCutoff) The method to use for nonbonded interactions. Allowed values are Parameters
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME. ----------
- nonbondedCutoff (distance=1*nanometer) The cutoff distance to use for nonbonded interactions nonbondedMethod : object=NoCutoff
- constraints (object=None) Specifies which bonds and angles should be implemented with constraints. The method to use for nonbonded interactions. Allowed values are
Allowed values are None, HBonds, AllBonds, or HAngles. NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
- rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the constraints argument nonbondedCutoff : distance=1*nanometer
- implicitSolvent (object=None) If not None, the implicit solvent model to use. The only allowed value is OBC2. The cutoff distance to use for nonbonded interactions
- soluteDielectric (float=1.0) The solute dielectric constant to use in the implicit solvent model. constraints : object=None
- solventDielectric (float=78.5) The solvent dielectric constant to use in the implicit solvent model. Specifies which bonds and angles should be implemented with
- ewaldErrorTolerance (float=0.0005) The error tolerance to use if nonbondedMethod is Ewald or PME. constraints. Allowed values are None, HBonds, AllBonds, or HAngles.
- removeCMMotion (boolean=True) If true, a CMMotionRemover will be added to the System rigidWater : boolean=True
- hydrogenMass (mass=None) The mass to use for hydrogen atoms bound to heavy atoms. Any mass added to a hydrogen is If true, water molecules will be fully rigid regardless of the value
subtracted from the heavy atom to keep their total mass the same. passed for the constraints argument
Returns: the newly created System implicitSolvent : object=None
If not None, the implicit solvent model to use. The only allowed
value is OBC2.
soluteDielectric : float=1.0
The solute dielectric constant to use in the implicit solvent model.
solventDielectric : float=78.5
The solvent dielectric constant to use in the implicit solvent
model.
ewaldErrorTolerance : float=0.0005
The error tolerance to use if nonbondedMethod is Ewald or PME.
removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None
The mass to use for hydrogen atoms bound to heavy atoms. Any mass
added to a hydrogen is subtracted from the heavy atom to keep their
total mass the same.
Returns
-------
System
the newly created System
""" """
# Create the System. # Create the System.
...@@ -586,9 +612,9 @@ class GromacsTopFile(object): ...@@ -586,9 +612,9 @@ class GromacsTopFile(object):
topologyAtoms = list(self.topology.atoms()) topologyAtoms = list(self.topology.atoms())
exceptions = [] exceptions = []
fudgeQQ = float(self._defaults[4]) fudgeQQ = float(self._defaults[4])
# Build a lookup table to let us process dihedrals more quickly. # Build a lookup table to let us process dihedrals more quickly.
dihedralTypeTable = {} dihedralTypeTable = {}
for key in self._dihedralTypes: for key in self._dihedralTypes:
if key[1] != 'X' and key[2] != 'X': if key[1] != 'X' and key[2] != 'X':
...@@ -837,7 +863,7 @@ class GromacsTopFile(object): ...@@ -837,7 +863,7 @@ class GromacsTopFile(object):
for atom in atoms[1:]: for atom in atoms[1:]:
if atom > atoms[0]: if atom > atoms[0]:
exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom, 0, 0, 0)) exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom, 0, 0, 0))
# Create nonbonded exceptions. # Create nonbonded exceptions.
...@@ -855,9 +881,9 @@ class GromacsTopFile(object): ...@@ -855,9 +881,9 @@ class GromacsTopFile(object):
nb.setNonbondedMethod(methodMap[nonbondedMethod]) nb.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff) nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance) nb.setEwaldErrorTolerance(ewaldErrorTolerance)
# Adjust masses. # Adjust masses.
if hydrogenMass is not None: if hydrogenMass is not None:
for atom1, atom2 in self.topology.bonds(): for atom1, atom2 in self.topology.bonds():
if atom1.element == elem.hydrogen: if atom1.element == elem.hydrogen:
......
...@@ -674,7 +674,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -674,7 +674,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
OPTIONAL ARGUMENTS OPTIONAL ARGUMENTS
shake (String) - if 'h-bonds', will SHAKE all bonds to hydrogen and water; if 'all-bonds', will SHAKE all bonds and water (default: None) shake (String) - if 'h-bonds', will SHAKE all bonds to hydrogen and water; if 'all-bonds', will SHAKE all bonds and water (default: None)
gbmodel (String) - if 'OBC', OBC GBSA will be used; if 'GBVI', GB/VI will be used (default: None) gbmodel (String) - if 'OBC', OBC GBSA will be used (default: None)
soluteDielectric (float) - The solute dielectric constant to use in the implicit solvent model (default: 1.0) soluteDielectric (float) - The solute dielectric constant to use in the implicit solvent model (default: 1.0)
solventDielectric (float) - The solvent dielectric constant to use in the implicit solvent model (default: 78.5) solventDielectric (float) - The solvent dielectric constant to use in the implicit solvent model (default: 78.5)
implicitSolventKappa (float) - The Debye screening parameter corresponding to implicit solvent ionic strength implicitSolventKappa (float) - The Debye screening parameter corresponding to implicit solvent ionic strength
...@@ -1097,8 +1097,6 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -1097,8 +1097,6 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
# created above. Do not bind force to another name before this! # created above. Do not bind force to another name before this!
force.setReactionFieldDielectric(1.0) force.setReactionFieldDielectric(1.0)
# TODO: Add GBVI terms?
return system return system
#============================================================================================= #=============================================================================================
......
...@@ -76,22 +76,33 @@ class AtomType(object): ...@@ -76,22 +76,33 @@ class AtomType(object):
new atom types with the "add" constructor to make sure the registry is new atom types with the "add" constructor to make sure the registry is
filled with only unique types filled with only unique types
Parameters and Attributes: Parameters
- name (str) : The name of the atom type ----------
- number (int) : The integer index of the atom type name : str
- mass (float) : The mass of the atom type The name of the atom type
- atomic_number (int) : The atomic number of the element of the atom number : int
type The integer index of the atom type
Attributes: mass : float
- name (str) : The name of the atom type The mass of the atom type
- number (int) : The integer index of the atom type atomic_number : int
- _member_number (int, private) : The order in which this atom type The atomic number of the element of the atom type
was 'added' this is used to make sure that atom types added
last have priority in assignment in the generated hash tables Attributes
- nbfix (dict) : Dictionary that maps nbfix terms with other atom types. ----------
Dict entries are (rmin, epsilon) -- precombined values name : str
for that particular atom pair The name of the atom type
Example: number : int
The integer index of the atom type
_member_number : int, private)
The order in which this atom type was 'added' this is used to make
sure that atom types added last have priority in assignment in the
generated hash tables
nbfix : dict
Dictionary that maps nbfix terms with other atom types. Dict entries
are (rmin, epsilon) -- precombined values for that particular atom pair
Examples
--------
>>> at = AtomType('HA', 1, 1.008, 1) >>> at = AtomType('HA', 1, 1.008, 1)
>>> at.name, at.number >>> at.name, at.number
('HA', 1) ('HA', 1)
...@@ -212,34 +223,59 @@ WildCard = WildCard() # Turn it into a singleton ...@@ -212,34 +223,59 @@ WildCard = WildCard() # Turn it into a singleton
class Atom(object): class Atom(object):
""" An atom in a structure. """ An atom in a structure.
Parameters: Parameters
system (str) : Name of the system this atom belongs to ----------
name (str): name of the atom system : str
type (str or int) : Type of the atom Name of the system this atom belongs to
charge (float) : Partial atomic charge (elementary charge units) name : str
mass (float) : Atomic mass (amu) name of the atom
props (list) : Other properties from the PSF type : str or int
Type of the atom
Attributes: charge : float
- attype (str) : Name of the atom type Partial atomic charge (elementary charge units)
- system (str) : The system name associated with this atom mass : float
- name (str) : Name of the atom (str) Atomic mass (amu)
- charge (float) : Partial atomic charge props : list
- mass (float) : Mass of the atom (amu) Other properties from the PSF
- idx (int) : index of the atom in the system, starting from 0
- props (list) : List of extraneous properties parsed from a PSF Attributes
- type (AtomType) : If assigned, has additional properties like the ----------
non-bonded LJ parameters. If None, it has not yet been assigned attype : str
Name of the atom type
Possible Attributes (SOA == Set of Atom instances) system : str
- bond_partners (SOA) : List of all atoms I am bonded to The system name associated with this atom
- angle_partners (SOA) : List of all atoms I am angled to name : str
- dihedral_partners (SOA) : List of all atoms I am dihedraled to Name of the atom (str)
- bonds (list of Bond's) : All bonds to which I belong charge : float
- angles (list of Angle's) : All angles to which I belong Partial atomic charge
- dihedrals (list of Dihedral's) : All dihedrals to which I belong mass : float
- impropers (list of Improper's) : All impropers to which I belong Mass of the atom (amu)
- cmaps (list of Cmap's) : All correction maps to which I belong idx : int
index of the atom in the system, starting from 0
props : list
List of extraneous properties parsed from a PSF
type : AtomType
If assigned, has additional properties like the non-bonded LJ
parameters. If None, it has not yet been assigned
Possible Attributes
-------------------
bond_partners : set of Atoms
List of all atoms I am bonded to
angle_partners set of Atoms
List of all atoms I am angled to
dihedral_partners : et of Atoms
List of all atoms I am dihedraled to
bonds : list of Bonds
All bonds to which I belong
angles : list of Angles
All angles to which I belong
dihedrals : list of Dihedrals
All dihedrals to which I belong
impropers : list of Impropers
All impropers to which I belong
cmaps : list of Cmaps
All correction maps to which I belong
""" """
def __init__(self, system, name, attype, charge, mass, props=None): def __init__(self, system, name, attype, charge, mass, props=None):
self.name = name self.name = name
...@@ -450,22 +486,33 @@ class ResidueList(list): ...@@ -450,22 +486,33 @@ class ResidueList(list):
def add_atom(self, system, resnum, resname, name, def add_atom(self, system, resnum, resname, name,
attype, charge, mass, inscode, props=None): attype, charge, mass, inscode, props=None):
""" """Adds an atom to the list of residues. If the residue is not the same as
Adds an atom to the list of residues. If the residue is not the same as the last residue that was created, a new Residue is created and added to
the last residue that was created, a new Residue is created and added this list
to this list
Parameters
Parameters: ----------
- system (str) : The system this atom belongs to system : str
- resnum (int) : Residue number The system this atom belongs to
- resname (str) : Name of the residue resnum : int
- name (str) : Name of the atom Residue number
- attype (int or str) : Type of the atom resname : str
- charge (float) : Partial atomic charge of the atom Name of the residue
- mass (float) : Mass (amu) of the atom name : str
- inscode (str) : Insertion code, if it is specified Name of the atom
attype : int or str
Returns: Type of the atom
charge : float
Partial atomic charge of the atom
mass : float
Mass (amu) of the atom
inscode : str
Insertion code, if it is specified
props : list
Other properties from the PSF
Returns
-------
The Atom instance created and added to the list of residues The Atom instance created and added to the list of residues
""" """
lr = self._last_residue lr = self._last_residue
...@@ -482,7 +529,7 @@ class ResidueList(list): ...@@ -482,7 +529,7 @@ class ResidueList(list):
atom = Atom(system, name, attype, float(charge), float(mass), props) atom = Atom(system, name, attype, float(charge), float(mass), props)
res.add_atom(atom) res.add_atom(atom)
return atom return atom
def append(self, thing): def append(self, thing):
raise NotImplemented('Use "add_atom" to build a residue list') raise NotImplemented('Use "add_atom" to build a residue list')
...@@ -491,13 +538,16 @@ class ResidueList(list): ...@@ -491,13 +538,16 @@ class ResidueList(list):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class Bond(object): class Bond(object):
""" """A bond object that links 2 atoms
A bond object that links 2 atoms
Parameters
Parameters: ----------
- atom1 (Atom) : First atom included in the bond atom1 : Atom
- atom2 (Atom) : Second atom included in the bond First atom included in the bond
- bond_type (BondType) : Type for the bond (None if unknown) atom2 : Atom
Second atom included in the bond
bond_type : BondType
Type for the bond (None if unknown)
""" """
def __init__(self, atom1, atom2, bond_type=None): def __init__(self, atom1, atom2, bond_type=None):
self.atom1 = atom1 self.atom1 = atom1
...@@ -519,14 +569,18 @@ class Bond(object): ...@@ -519,14 +569,18 @@ class Bond(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class Angle(object): class Angle(object):
""" """An angle object that links 3 atoms
An angle object that links 3 atoms
Parameters
Parameters: ----------
- atom1 (Atom) : First atom included in the angle atom1 : Atom
- atom2 (Atom) : Central atom in the valence angle First atom included in the angle
- atom3 (Atom) : Third atom in the valence angle atom2 : Atom
- angle_type (AngleType) : Type for the angle (None if unknown) Central atom in the valence angle
atom3 : Atom
Third atom in the valence angle
angle_type : AngleType
Type for the angle (None if unknown)
""" """
def __init__(self, atom1, atom2, atom3, angle_type=None): def __init__(self, atom1, atom2, atom3, angle_type=None):
self.atom1 = atom1 self.atom1 = atom1
...@@ -554,15 +608,17 @@ class Angle(object): ...@@ -554,15 +608,17 @@ class Angle(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class UreyBradley(object): class UreyBradley(object):
""" """A harmonic restraint between two atoms separated by 2 valence bonds
A harmonic restraint between two atoms separated by 2 valence bonds (i.e., (i.e., involved in a valence angle with each other
involved in a valence angle with each other
Parameters
Parameters: ----------
- atom1 (Atom) : The first atom included in the Urey-Bradley term atom1 : Atom
- atom2 (Atom) : The second atom included in the Urey-Bradley term The first atom included in the Urey-Bradley term
- ub_type (UreyBradleyType) : The type for the Urey-Bradley term (None atom2 : Atom
if unknown) The second atom included in the Urey-Bradley term
ub_type : UreyBradleyType
The type for the Urey-Bradley term (None if unknown)
""" """
def __init__(self, atom1, atom2, ub_type=None): def __init__(self, atom1, atom2, ub_type=None):
self.atom1 = atom1 self.atom1 = atom1
...@@ -599,15 +655,20 @@ class UreyBradley(object): ...@@ -599,15 +655,20 @@ class UreyBradley(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class Dihedral(object): class Dihedral(object):
""" """A torsion angle object that links 4 atoms
A torsion angle object that links 4 atoms
Parameters
Parameters: ----------
- atom1 (Atom) : First atom included in the torsion atom1 : Atom
- atom2 (Atom) : Second atom included in the torsion First atom included in the torsion
- atom3 (Atom) : Third atom included in the torsion atom2 : Atom
- atom4 (Atom) : Fourth atom included in the torsion Second atom included in the torsion
- dihedral_type (DihedralType) : Type for the torsion (None if unknown) atom3 : Atom
Third atom included in the torsion
atom4 : Atom
Fourth atom included in the torsion
dihedral_type : DihedralType
Type for the torsion (None if unknown)
""" """
def __init__(self, atom1, atom2, atom3, atom4, dihedral_type=None): def __init__(self, atom1, atom2, atom3, atom4, dihedral_type=None):
self.atom1 = atom1 self.atom1 = atom1
...@@ -648,15 +709,20 @@ class Dihedral(object): ...@@ -648,15 +709,20 @@ class Dihedral(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class Improper(object): class Improper(object):
""" """An improper torsion object. The third atom is bonded to each other atom
An improper torsion object. The third atom is bonded to each other atom
Parameters
Parameters: ----------
- atom1 (Atom) : First atom included in the torsion atom1 : Atom
- atom2 (Atom) : Second atom included in the torsion First atom included in the torsion
- atom3 (Atom) : Third atom included in the torsion atom2 : Atom
- atom4 (Atom) : Fourth atom included in the torsion Second atom included in the torsion
- improper_type (ImproperType) : Type for the improper (None if unknown) atom3 : Atom
Third atom included in the torsion
atom4 : Atom
Fourth atom included in the torsion
improper_type : ImproperType
Type for the improper (None if unknown)
""" """
def __init__(self, atom1, atom2, atom3, atom4, improper_type=None): def __init__(self, atom1, atom2, atom3, atom4, improper_type=None):
self.atom1 = atom1 self.atom1 = atom1
...@@ -685,7 +751,7 @@ class Improper(object): ...@@ -685,7 +751,7 @@ class Improper(object):
| |
| |
A4 ----- A1 ----- A2 A4 ----- A1 ----- A2
So the bonds will either be between atom1 and any other atom So the bonds will either be between atom1 and any other atom
""" """
if isinstance(thing, Bond): if isinstance(thing, Bond):
...@@ -703,12 +769,14 @@ class Improper(object): ...@@ -703,12 +769,14 @@ class Improper(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class AcceptorDonor(object): class AcceptorDonor(object):
""" """Just a holder for donors and acceptors in CHARMM speak
Just a holder for donors and acceptors in CHARMM speak
Parameters
Parameters: ----------
- atom1 (Atom) : First atom in the donor/acceptor group atom1 : Atom
- atom2 (Atom) : Second atom in the donor/acceptor group First atom in the donor/acceptor group
atom2 : Atom
Second atom in the donor/acceptor group
""" """
def __init__(self, atom1, atom2): def __init__(self, atom1, atom2):
self.atom1 = atom1 self.atom1 = atom1
...@@ -724,13 +792,16 @@ class AcceptorDonor(object): ...@@ -724,13 +792,16 @@ class AcceptorDonor(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class Group(object): class Group(object):
""" """An 'interacting' group defined by the PSF.
An 'interacting' group defined by the PSF.
Parameters: Parameters
- bs (int) : ?? ----------
- type (int) : The group type bs : int
- move (int) : If the group moves ?? ??
type : int
The group type
move : int
If the group moves ??
Disclaimer: I really don't know what these numbers mean. I'm speculating Disclaimer: I really don't know what these numbers mean. I'm speculating
based on the source code of 'chamber', and this section is simply ignored based on the source code of 'chamber', and this section is simply ignored
...@@ -749,36 +820,61 @@ class Cmap(object): ...@@ -749,36 +820,61 @@ class Cmap(object):
consecutive correction maps). "Consecutive torsions" (i.e., those definable consecutive correction maps). "Consecutive torsions" (i.e., those definable
by 5 atoms) will only be recognized if the two torsions have the same order by 5 atoms) will only be recognized if the two torsions have the same order
Parameters: Parameters
- atom1 (Atom) : 1st atom of first dihedral ----------
- atom2 (Atom) : 2nd atom of first dihedral atom1 : Atom
- atom3 (Atom) : 3rd atom of first dihedral 1st atom of first dihedral
- atom4 (Atom) : 4th atom of first dihedral atom2 : Atom
- atom5 (Atom) : 1st atom of second dihedral 2nd atom of first dihedral
- atom6 (Atom) : 2nd atom of second dihedral atom3 : Atom
- atom7 (Atom) : 3rd atom of second dihedral 3rd atom of first dihedral
- atom8 (Atom) : 4th atom of second dihedral atom4 : Atom
- cmap_type (CmapType) : Cmap type for this cmap (None if unknown) 4th atom of first dihedral
atom5 : Atom
Attributes: 1st atom of second dihedral
- consecutive (bool) : Are the dihedrals consecutive? atom6 : Atom
2nd atom of second dihedral
if consecutive: atom7 : Atom
- atom1 (Atom) : 1st atom of 1st dihedral 3rd atom of second dihedral
- atom2 (Atom) : 2nd atom of 1st dihedral && 1st atom of 2nd dihedral atom8 : Atom
- atom3 (Atom) : 3rd atom of 1st dihedral && 2nd atom of 2nd dihedral 4th atom of second dihedral
- atom4 (Atom) : 4th atom of 1st dihedral && 3rd atom of 2nd dihedral cmap_type : CmapType
- atom5 (Atom) : 4th atom of 2nd dihedral Cmap type for this cmap (None if unknown)
Attributes
----------
consecutive : bool
Are the dihedrals consecutive?
if consecutive:
atom1 : Atom
1st atom of 1st dihedral
atom2 L Atom
2nd atom of 1st dihedral && 1st atom of 2nd dihedral
atom3 : Atom
3rd atom of 1st dihedral && 2nd atom of 2nd dihedral
atom4 : Atom
4th atom of 1st dihedral && 3rd atom of 2nd dihedral
atom5 : Atom
4th atom of 2nd dihedral
if not consecutive: if not consecutive:
- atom1 (Atom) : 1st atom of first dihedral atom1 : Atom
- atom2 (Atom) : 2nd atom of first dihedral 1st atom of first dihedral
- atom3 (Atom) : 3rd atom of first dihedral atom2 : Atom
- atom4 (Atom) : 4th atom of first dihedral 2nd atom of first dihedral
- atom5 (Atom) : 1st atom of second dihedral atom3 : Atom
- atom6 (Atom) : 2nd atom of second dihedral 3rd atom of first dihedral
- atom7 (Atom) : 3rd atom of second dihedral atom4 : Atom
- atom8 (Atom) : 4th atom of second dihedral 4th atom of first dihedral
atom5 : Atom
1st atom of second dihedral
atom6 : Atom
2nd atom of second dihedral
atom7 : Atom
3rd atom of second dihedral
atom8 : Atom
4th atom of second dihedral
""" """
def __init__(self, atom1, atom2, atom3, atom4, atom5, atom6, atom7, def __init__(self, atom1, atom2, atom3, atom4, atom5, atom6, atom7,
atom8, cmap_type=None): atom8, cmap_type=None):
...@@ -866,9 +962,12 @@ class BondType(object): ...@@ -866,9 +962,12 @@ class BondType(object):
A bond type with an equilibrium length (Angstroms) and force constant A bond type with an equilibrium length (Angstroms) and force constant
(kcal/mol/Angstrom^2) (kcal/mol/Angstrom^2)
Parameters: Parameters
- k (float) : Force constant (kcal/mol/A^2) ----------
- req (float) : Equilibrium distance k : float
: Force constant (kcal/mol/A^2)
req : float
: Equilibrium distance
""" """
def __init__(self, k, req): def __init__(self, k, req):
self.k = k self.k = k
...@@ -880,13 +979,15 @@ class BondType(object): ...@@ -880,13 +979,15 @@ class BondType(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class AngleType(object): class AngleType(object):
""" """An angle type with an equilibrium angle (degrees) and force constant
An angle type with an equilibrium angle (degrees) and force constant
(kcal/mol/radians^2) (kcal/mol/radians^2)
Parameters: Parameters
- k (float) : Force constant (kcal/mol/radians^2) ----------
- theteq (float) : Equilibrium angle value (degrees) k : float
Force constant (kcal/mol/radians^2)
theteq : float
Equilibrium angle value (degrees)
""" """
def __init__(self, k, theteq): def __init__(self, k, theteq):
self.k = k self.k = k
...@@ -911,14 +1012,17 @@ NoUreyBradley = UreyBradleyType(None, None) ...@@ -911,14 +1012,17 @@ NoUreyBradley = UreyBradleyType(None, None)
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class DihedralType(object): class DihedralType(object):
""" """A torsion angle type with a force constant (kcal/mol), periodicity
A torsion angle type with a force constant (kcal/mol), periodicity (int), (int), and phase (degrees)
and phase (degrees)
Parameters
Parameters: ----------
- phi_k (float) : Force constant (kcal/mol) phi_k : float
- per (int) : Periodicity Force constant (kcal/mol)
- phase (float): Phase of the torsion per : int
Periodicity
phase : float
Phase of the torsion
""" """
def __init__(self, phi_k, per, phase): def __init__(self, phi_k, per, phase):
self.phi_k = float(phi_k) self.phi_k = float(phi_k)
...@@ -936,13 +1040,15 @@ class DihedralType(object): ...@@ -936,13 +1040,15 @@ class DihedralType(object):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class ImproperType(object): class ImproperType(object):
""" """An improper torsion angle type with a force constant (kcal/mol) and
An improper torsion angle type with a force constant (kcal/mol) and
equilibrium angle (degrees) equilibrium angle (degrees)
Parameters: Parameters
- k (float) : Force constant (kcal/mol) ----------
- phieq (int) : Equilibrium angle (degrees) k : float
: Force constant (kcal/mol)
phieq : int
: Equilibrium angle (degrees)
""" """
def __init__(self, k, phieq): def __init__(self, k, phieq):
self.k = k self.k = k
...@@ -950,7 +1056,7 @@ class ImproperType(object): ...@@ -950,7 +1056,7 @@ class ImproperType(object):
def __eq__(self, other): def __eq__(self, other):
return self.k == other.k and self.phieq == other.phieq return self.k == other.k and self.phieq == other.phieq
def __repr__(self): def __repr__(self):
return '<ImproperType; k=%s; phieq=%s>' % (self.k, self.phieq) return '<ImproperType; k=%s; phieq=%s>' % (self.k, self.phieq)
...@@ -959,11 +1065,14 @@ class ImproperType(object): ...@@ -959,11 +1065,14 @@ class ImproperType(object):
class CmapType(object): class CmapType(object):
""" """
Contains a correction map interpolation grid Contains a correction map interpolation grid
Parameters: Parameters
- resolution (int) : Number of interpolation points for each dihedral ----------
- grid (list of floats) : resolution x resolution list of energy values resolution : int
(kcal/mol) for the angles with the 2nd angle changing fastest. Number of interpolation points for each dihedral
grid : list of floats
resolution x resolution list of energy values (kcal/mol) for the
angles with the 2nd angle changing fastest.
The grid object is converted to a _CmapGrid instance which can be treated The grid object is converted to a _CmapGrid instance which can be treated
like a normal list, but also has the ability to quickly return a transpose like a normal list, but also has the ability to quickly return a transpose
......
...@@ -123,15 +123,18 @@ class PdbStructure(object): ...@@ -123,15 +123,18 @@ class PdbStructure(object):
""" """
def __init__(self, input_stream, load_all_models = False): def __init__(self, input_stream, load_all_models=False):
"""Create a PDB model from a PDB file stream. """Create a PDB model from a PDB file stream.
Parameters: Parameters
- self (PdbStructure) The new object that is created. ----------
- input_stream (stream) An input file stream, probably created with self : PdbStructure
open(). The new object that is created.
- load_all_models (bool) Whether to load every model of an NMR input_stream : stream
structure or trajectory, or just load the first model, to save memory. An input file stream, probably created with open().
load_all_models : bool
Whether to load every model of an NMR structure or trajectory, or
just load the first model, to save memory.
""" """
# initialize models # initialize models
self.load_all_models = load_all_models self.load_all_models = load_all_models
...@@ -201,7 +204,7 @@ class PdbStructure(object): ...@@ -201,7 +204,7 @@ class PdbStructure(object):
def _reset_atom_numbers(self): def _reset_atom_numbers(self):
self._atom_numbers_are_hex = False self._atom_numbers_are_hex = False
self._next_atom_number = 1 self._next_atom_number = 1
def _reset_residue_numbers(self): def _reset_residue_numbers(self):
self._residue_numbers_are_hex = False self._residue_numbers_are_hex = False
self._next_residue_number = 1 self._next_residue_number = 1
...@@ -269,8 +272,11 @@ class PdbStructure(object): ...@@ -269,8 +272,11 @@ class PdbStructure(object):
Iterate over atomic positions. Iterate over atomic positions.
Parameters Parameters
- use_all_models (bool=False) Get positions from all models or just the first one. ----------
- include_alt_loc (bool=False) Get all positions for each atom, or just the first one. use_all_models : bool=False
Get positions from all models or just the first one.
include_alt_loc : bool=False
Get all positions for each atom, or just the first one.
""" """
for model in self.iter_models(use_all_models): for model in self.iter_models(use_all_models):
for loc in model.iter_positions(include_alt_loc): for loc in model.iter_positions(include_alt_loc):
...@@ -711,7 +717,7 @@ class Atom(object): ...@@ -711,7 +717,7 @@ class Atom(object):
self.is_first_atom_in_chain = False self.is_first_atom_in_chain = False
self.is_final_atom_in_chain = False self.is_final_atom_in_chain = False
self.is_first_residue_in_chain = False self.is_first_residue_in_chain = False
self.is_final_residue_in_chain = False self.is_final_residue_in_chain = False
# Start parsing fields from pdb line # Start parsing fields from pdb line
self.record_name = pdb_line[0:6].strip() self.record_name = pdb_line[0:6].strip()
if pdbstructure is not None and pdbstructure._atom_numbers_are_hex: if pdbstructure is not None and pdbstructure._atom_numbers_are_hex:
......
...@@ -35,7 +35,7 @@ __author__ = "Peter Eastman" ...@@ -35,7 +35,7 @@ __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
from simtk.openmm.app import Topology, PDBFile, ForceField from simtk.openmm.app import Topology, PDBFile, ForceField
from simtk.openmm.app.forcefield import HAngles, _createResidueSignature, _matchResidue, DrudeGenerator from simtk.openmm.app.forcefield import HAngles, AllBonds, _createResidueSignature, _matchResidue, DrudeGenerator
from simtk.openmm.app.topology import Residue from simtk.openmm.app.topology import Residue
from simtk.openmm.vec3 import Vec3 from simtk.openmm.vec3 import Vec3
from simtk.openmm import System, Context, NonbondedForce, CustomNonbondedForce, HarmonicBondForce, HarmonicAngleForce, VerletIntegrator, LocalEnergyMinimizer from simtk.openmm import System, Context, NonbondedForce, CustomNonbondedForce, HarmonicBondForce, HarmonicAngleForce, VerletIntegrator, LocalEnergyMinimizer
...@@ -63,9 +63,12 @@ class Modeller(object): ...@@ -63,9 +63,12 @@ class Modeller(object):
def __init__(self, topology, positions): def __init__(self, topology, positions):
"""Create a new Modeller object """Create a new Modeller object
Parameters: Parameters
- topology (Topology) the initial Topology of the model ----------
- positions (list) the initial atomic positions topology : Topology
the initial Topology of the model
positions : list
the initial atomic positions
""" """
## The Topology describing the structure of the system ## The Topology describing the structure of the system
self.topology = topology self.topology = topology
...@@ -85,12 +88,16 @@ class Modeller(object): ...@@ -85,12 +88,16 @@ class Modeller(object):
def add(self, addTopology, addPositions): def add(self, addTopology, addPositions):
"""Add chains, residues, atoms, and bonds to the model. """Add chains, residues, atoms, and bonds to the model.
Specify what to add by providing a new Topology object and the corresponding atomic positions. Specify what to add by providing a new Topology object and the
All chains, residues, atoms, and bonds contained in the Topology are added to the model. corresponding atomic positions. All chains, residues, atoms, and bonds
contained in the Topology are added to the model.
Parameters: Parameters
- addTopoology (Topology) a Topology whose contents should be added to the model ----------
- addPositions (list) the positions of the atoms to add addTopology : Topology
a Topology whose contents should be added to the model
addPositions : list
the positions of the atoms to add
""" """
# Copy over the existing model. # Copy over the existing model.
...@@ -137,8 +144,11 @@ class Modeller(object): ...@@ -137,8 +144,11 @@ class Modeller(object):
You also can specify a bond (as a tuple of Atom objects) to delete just that bond without You also can specify a bond (as a tuple of Atom objects) to delete just that bond without
deleting the Atoms it connects. deleting the Atoms it connects.
Parameters: Parameters
- toDelete (list) a list of Atoms, Residues, Chains, and bonds (specified as tuples of Atoms) to delete ----------
toDelete : list
a list of Atoms, Residues, Chains, and bonds (specified as tuples of
Atoms) to delete
""" """
newTopology = Topology() newTopology = Topology()
newTopology.setPeriodicBoxVectors(self.topology.getPeriodicBoxVectors()) newTopology.setPeriodicBoxVectors(self.topology.getPeriodicBoxVectors())
...@@ -176,10 +186,14 @@ class Modeller(object): ...@@ -176,10 +186,14 @@ class Modeller(object):
def convertWater(self, model='tip3p'): def convertWater(self, model='tip3p'):
"""Convert all water molecules to a different water model. """Convert all water molecules to a different water model.
Parameters: @deprecated Use addExtraParticles() instead. It performs the same
- model (string='tip3p') the water model to convert to. Supported values are 'tip3p', 'spce', 'tip4pew', and 'tip5p'. function but in a more general way.
@deprecated Use addExtraParticles() instead. It performs the same function but in a more general way. Parameters
----------
model : string='tip3p'
the water model to convert to. Supported values are 'tip3p',
'spce', 'tip4pew', and 'tip5p'.
""" """
if model in ('tip3p', 'spce'): if model in ('tip3p', 'spce'):
sites = 3 sites = 3
...@@ -241,38 +255,51 @@ class Modeller(object): ...@@ -241,38 +255,51 @@ class Modeller(object):
self.topology = newTopology self.topology = newTopology
self.positions = newPositions self.positions = newPositions
def addSolvent(self, forcefield, model='tip3p', boxSize=None, boxVectors=None, padding=None, numAdded=None, positiveIon='Na+', negativeIon='Cl-', ionicStrength=0*molar): def addSolvent(self, forcefield, model='tip3p', boxSize=None, boxVectors=None, padding=None, numAdded=None, positiveIon='Na+', negativeIon='Cl-', ionicStrength=0*molar, neutralize=True):
"""Add solvent (both water and ions) to the model to fill a rectangular box. """Add solvent (both water and ions) to the model to fill a rectangular box.
The algorithm works as follows: The algorithm works as follows:
1. Water molecules are added to fill the box. 1. Water molecules are added to fill the box.
2. Water molecules are removed if their distance to any solute atom is less than the sum of their van der Waals radii. 2. Water molecules are removed if their distance to any solute atom is less than the sum of their van der Waals radii.
3. If the solute is charged, enough positive or negative ions are added to neutralize it. Each ion is added by 3. If the solute is charged and neutralize=True, enough positive or negative ions are added to neutralize it. Each ion is added by
randomly selecting a water molecule and replacing it with the ion. randomly selecting a water molecule and replacing it with the ion.
4. Ion pairs are added to give the requested total ionic strength. 4. Ion pairs are added to give the requested total ionic strength.
The box size can be specified in any of several ways: The box size can be specified in any of several ways:
1. You can explicitly give the vectors defining the periodic box to use. 1. You can explicitly give the vectors defining the periodic box to use.
2. Alternatively, for a rectangular box you can simply give the dimensions of the unit cell. 2. Alternatively, for a rectangular box you can simply give the dimensions of the unit cell.
3. You can give a padding distance. The largest dimension of the solute (along the x, y, or z axis) is determined, and a cubic 3. You can give a padding distance. The largest dimension of the solute (along the x, y, or z axis) is determined, and a cubic
box of size (largest dimension)+2*padding is used. box of size (largest dimension)+2*padding is used.
4. You can specify the total number of molecules (both waters and ions) to add. A cubic box is then created whose size is 4. You can specify the total number of molecules (both waters and ions) to add. A cubic box is then created whose size is
just large enough hold the specified amount of solvent. just large enough hold the specified amount of solvent.
5. Finally, if none of the above options is specified, the existing Topology's box vectors are used. 5. Finally, if none of the above options is specified, the existing Topology's box vectors are used.
Parameters: Parameters
- forcefield (ForceField) the ForceField to use for determining van der Waals radii and atomic charges ----------
- model (string='tip3p') the water model to use. Supported values are 'tip3p', 'spce', 'tip4pew', and 'tip5p'. forcefield : ForceField
- boxSize (Vec3=None) the size of the box to fill with water the ForceField to use for determining van der Waals radii and atomic charges
- boxVectors (tuple of Vec3=None) the vectors defining the periodic box to fill with water model : str='tip3p'
- padding (distance=None) the padding distance to use the water model to use. Supported values are 'tip3p', 'spce', 'tip4pew', and 'tip5p'.
- numAdded (int=None) the total number of molecules (waters and ions) to add boxSize : Vec3=None
- positiveIon (string='Na+') the type of positive ion to add. Allowed values are 'Cs+', 'K+', 'Li+', 'Na+', and 'Rb+' the size of the box to fill with water
- negativeIon (string='Cl-') the type of negative ion to add. Allowed values are 'Cl-', 'Br-', 'F-', and 'I-'. Be aware boxVectors : tuple of Vec3=None
that not all force fields support all ion types. the vectors defining the periodic box to fill with water
- ionicStrength (concentration=0*molar) the total concentration of ions (both positive and negative) to add. This padding : distance=None
does not include ions that are added to neutralize the system. the padding distance to use
numAdded : int=None
the total number of molecules (waters and ions) to add
positiveIon : string='Na+'
the type of positive ion to add. Allowed values are 'Cs+', 'K+', 'Li+', 'Na+', and 'Rb+'
negativeIon : string='Cl-'
the type of negative ion to add. Allowed values are 'Cl-', 'Br-', 'F-', and 'I-'. Be aware
that not all force fields support all ion types.
ionicStrength : concentration=0*molar
the total concentration of ions (both positive and negative) to add. This
does not include ions that are added to neutralize the system.
neutralize : bool=True
whether to add ions to neutralize the system
""" """
if len([x for x in (boxSize, boxVectors, padding, numAdded) if x is not None]) > 1: if len([x for x in (boxSize, boxVectors, padding, numAdded) if x is not None]) > 1:
raise ValueError('At most one of the following arguments may be specified: boxSize, boxVectors, padding, numAdded') raise ValueError('At most one of the following arguments may be specified: boxSize, boxVectors, padding, numAdded')
...@@ -294,13 +321,13 @@ class Modeller(object): ...@@ -294,13 +321,13 @@ class Modeller(object):
pdbTopology = pdb.getTopology() pdbTopology = pdb.getTopology()
pdbPositions = pdb.getPositions().value_in_unit(nanometer) pdbPositions = pdb.getPositions().value_in_unit(nanometer)
pdbResidues = list(pdbTopology.residues()) pdbResidues = list(pdbTopology.residues())
pdbBoxSize = pdbTopology.getUnitCellDimensions().value_in_unit(nanometer) pdbBoxSize = pdbTopology.getUnitCellDimensions().value_in_unit(nanometer)
# Pick a unit cell size. # Pick a unit cell size.
if numAdded is not None: if numAdded is not None:
# Select a padding distance which is guaranteed to give more than the specified number of molecules. # Select a padding distance which is guaranteed to give more than the specified number of molecules.
padding = 1.1*(numAdded/((len(pdbResidues)/pdbBoxSize[0]**3)*8))**(1.0/3.0) padding = 1.1*(numAdded/((len(pdbResidues)/pdbBoxSize[0]**3)*8))**(1.0/3.0)
if padding < 0.5: if padding < 0.5:
padding = 0.5 # Ensure we have enough when adding very small numbers of molecules padding = 0.5 # Ensure we have enough when adding very small numbers of molecules
...@@ -442,20 +469,20 @@ class Modeller(object): ...@@ -442,20 +469,20 @@ class Modeller(object):
if numAdded is not None: if numAdded is not None:
# We added many more waters than we actually want. Sort them based on distance to the nearest box edge and # We added many more waters than we actually want. Sort them based on distance to the nearest box edge and
# only keep the ones in the middle. # only keep the ones in the middle.
lowerBound = center-box/2 lowerBound = center-box/2
upperBound = center+box/2 upperBound = center+box/2
distToEdge = (min(min(pos-lowerBound), min(upperBound-pos)) for index, pos in addedWaters) distToEdge = (min(min(pos-lowerBound), min(upperBound-pos)) for index, pos in addedWaters)
sortedIndex = [i[0] for i in sorted(enumerate(distToEdge), key=lambda x: -x[1])] sortedIndex = [i[0] for i in sorted(enumerate(distToEdge), key=lambda x: -x[1])]
addedWaters = [addedWaters[i] for i in sortedIndex[:numAdded]] addedWaters = [addedWaters[i] for i in sortedIndex[:numAdded]]
# Compute a new periodic box size. # Compute a new periodic box size.
maxSize = max(max((pos[i] for index, pos in addedWaters))-min((pos[i] for index, pos in addedWaters)) for i in range(3)) maxSize = max(max((pos[i] for index, pos in addedWaters))-min((pos[i] for index, pos in addedWaters)) for i in range(3))
newTopology.setUnitCellDimensions(Vec3(maxSize, maxSize, maxSize)) newTopology.setUnitCellDimensions(Vec3(maxSize, maxSize, maxSize))
else: else:
# There could be clashes between water molecules at the box edges. Find ones to remove. # There could be clashes between water molecules at the box edges. Find ones to remove.
upperCutoff = center+box/2-Vec3(waterCutoff, waterCutoff, waterCutoff) upperCutoff = center+box/2-Vec3(waterCutoff, waterCutoff, waterCutoff)
lowerCutoff = center-box/2+Vec3(waterCutoff, waterCutoff, waterCutoff) lowerCutoff = center-box/2+Vec3(waterCutoff, waterCutoff, waterCutoff)
lowerSkinPositions = [pos for index, pos in addedWaters if pos[0] < lowerCutoff[0] or pos[1] < lowerCutoff[1] or pos[2] < lowerCutoff[2]] lowerSkinPositions = [pos for index, pos in addedWaters if pos[0] < lowerCutoff[0] or pos[1] < lowerCutoff[1] or pos[2] < lowerCutoff[2]]
...@@ -478,9 +505,6 @@ class Modeller(object): ...@@ -478,9 +505,6 @@ class Modeller(object):
# Add ions to neutralize the system. # Add ions to neutralize the system.
totalCharge = int(floor(0.5+sum((nonbonded.getParticleParameters(i)[0].value_in_unit(elementary_charge) for i in range(system.getNumParticles())))))
if abs(totalCharge) > len(addedWaters):
raise Exception('Cannot neutralize the system because the charge is greater than the number of available positions for ions')
def addIon(element): def addIon(element):
# Replace a water by an ion. # Replace a water by an ion.
index = random.randint(0, len(addedWaters)-1) index = random.randint(0, len(addedWaters)-1)
...@@ -488,8 +512,12 @@ class Modeller(object): ...@@ -488,8 +512,12 @@ class Modeller(object):
newTopology.addAtom(element.symbol, element, newResidue) newTopology.addAtom(element.symbol, element, newResidue)
newPositions.append(addedWaters[index][1]*nanometer) newPositions.append(addedWaters[index][1]*nanometer)
del addedWaters[index] del addedWaters[index]
for i in range(abs(totalCharge)): if neutralize:
addIon(positiveElement if totalCharge < 0 else negativeElement) totalCharge = int(floor(0.5+sum((nonbonded.getParticleParameters(i)[0].value_in_unit(elementary_charge) for i in range(system.getNumParticles())))))
if abs(totalCharge) > len(addedWaters):
raise Exception('Cannot neutralize the system because the charge is greater than the number of available positions for ions')
for i in range(abs(totalCharge)):
addIon(positiveElement if totalCharge < 0 else negativeElement)
# Add ions based on the desired ionic strength. # Add ions based on the desired ionic strength.
...@@ -604,16 +632,29 @@ class Modeller(object): ...@@ -604,16 +632,29 @@ class Modeller(object):
Definitions for standard amino acids and nucleotides are built in. You can call loadHydrogenDefinitions() to load Definitions for standard amino acids and nucleotides are built in. You can call loadHydrogenDefinitions() to load
additional definitions for other residue types. additional definitions for other residue types.
Parameters: Parameters
- forcefield (ForceField=None) the ForceField to use for determining the positions of hydrogens. If this is None, ----------
positions will be picked which are generally reasonable but not optimized for any particular ForceField. forcefield : ForceField=None
- pH (float=7.0) the pH based on which to select variants the ForceField to use for determining the positions of hydrogens.
- variants (list=None) an optional list of variants to use. If this is specified, its length must equal the number If this is None, positions will be picked which are generally
of residues in the model. variants[i] is the name of the variant to use for residue i (indexed starting at 0). reasonable but not optimized for any particular ForceField.
If an element is None, the standard rules will be followed to select a variant for that residue. pH : float=7.0
- platform (Platform=None) the Platform to use when computing the hydrogen atom positions. If this is None, the pH based on which to select variants
the default Platform will be used. variants : list=None
Returns: a list of what variant was actually selected for each residue, in the same format as the variants parameter an optional list of variants to use. If this is specified, its
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
0). If an element is None, the standard rules will be followed to
select a variant for that residue.
platform : Platform=None
the Platform to use when computing the hydrogen atom positions. If
this is None, the default Platform will be used.
Returns
-------
list
a list of what variant was actually selected for each residue,
in the same format as the variants parameter
""" """
# Check the list of variants. # Check the list of variants.
...@@ -802,7 +843,7 @@ class Modeller(object): ...@@ -802,7 +843,7 @@ class Modeller(object):
if forcefield is not None: if forcefield is not None:
# Use the ForceField the user specified. # Use the ForceField the user specified.
system = forcefield.createSystem(newTopology, rigidWater=False) system = forcefield.createSystem(newTopology, rigidWater=False)
atoms = list(newTopology.atoms()) atoms = list(newTopology.atoms())
for i in range(system.getNumParticles()): for i in range(system.getNumParticles()):
...@@ -812,7 +853,7 @@ class Modeller(object): ...@@ -812,7 +853,7 @@ class Modeller(object):
else: else:
# Create a System that restrains the distance of each hydrogen from its parent atom # Create a System that restrains the distance of each hydrogen from its parent atom
# and causes hydrogens to spread out evenly. # and causes hydrogens to spread out evenly.
system = System() system = System()
nonbonded = CustomNonbondedForce('100/((r/0.1)^4+1)') nonbonded = CustomNonbondedForce('100/((r/0.1)^4+1)')
bonds = HarmonicBondForce() bonds = HarmonicBondForce()
...@@ -836,7 +877,7 @@ class Modeller(object): ...@@ -836,7 +877,7 @@ class Modeller(object):
for residue in newTopology.residues(): for residue in newTopology.residues():
if residue.name == 'HOH': if residue.name == 'HOH':
# Add an angle term to make the water geometry correct. # Add an angle term to make the water geometry correct.
atoms = list(residue.atoms()) atoms = list(residue.atoms())
oindex = [i for i in range(len(atoms)) if atoms[i].element == elem.oxygen] oindex = [i for i in range(len(atoms)) if atoms[i].element == elem.oxygen]
if len(atoms) == 3 and len(oindex) == 1: if len(atoms) == 3 and len(oindex) == 1:
...@@ -844,12 +885,12 @@ class Modeller(object): ...@@ -844,12 +885,12 @@ class Modeller(object):
angles.addAngle(atoms[hindex[0]].index, atoms[oindex[0]].index, atoms[hindex[1]].index, 1.824, 836.8) angles.addAngle(atoms[hindex[0]].index, atoms[oindex[0]].index, atoms[hindex[1]].index, 1.824, 836.8)
else: else:
# Add angle terms for any hydroxyls. # Add angle terms for any hydroxyls.
for atom in residue.atoms(): for atom in residue.atoms():
index = atom.index index = atom.index
if atom.element == elem.oxygen and len(bondedTo[index]) == 2 and elem.hydrogen in (a.element for a in bondedTo[index]): if atom.element == elem.oxygen and len(bondedTo[index]) == 2 and elem.hydrogen in (a.element for a in bondedTo[index]):
angles.addAngle(bondedTo[index][0].index, index, bondedTo[index][1].index, 1.894, 460.24) angles.addAngle(bondedTo[index][0].index, index, bondedTo[index][1].index, 1.894, 460.24)
if platform is None: if platform is None:
context = Context(system, VerletIntegrator(0.0)) context = Context(system, VerletIntegrator(0.0))
else: else:
...@@ -862,18 +903,24 @@ class Modeller(object): ...@@ -862,18 +903,24 @@ class Modeller(object):
return actualVariants return actualVariants
def addExtraParticles(self, forcefield): def addExtraParticles(self, forcefield):
"""Add missing extra particles to the model that are required by a force field. """Add missing extra particles to the model that are required by a force
field.
Some force fields use "extra particles" that do not represent actual atoms, but still need to be included in
the System. Examples include lone pairs, Drude particles, and the virtual sites used in some water models Some force fields use "extra particles" that do not represent
to adjust the charge distribution. Extra particles can be recognized by the fact that their element is None. actual atoms, but still need to be included in the System. Examples
include lone pairs, Drude particles, and the virtual sites used in some
This method is primarily used to add extra particles, but it can also remove them. It tries to match every water models to adjust the charge distribution. Extra particles can be
residue in the Topology to a template in the force field. If there is no match, it will both add and remove recognized by the fact that their element is None.
extra particles as necessary to make it match.
This method is primarily used to add extra particles, but it can also
Parameters: remove them. It tries to match every residue in the Topology to a
- forcefield (ForceField) the ForceField defining what extra particles should be present template in the force field. If there is no match, it will both add
and remove extra particles as necessary to make it match.
Parameters
----------
forcefield : ForceField
the ForceField defining what extra particles should be present
""" """
# Create copies of all residue templates that have had all extra points removed. # Create copies of all residue templates that have had all extra points removed.
...@@ -929,6 +976,7 @@ class Modeller(object): ...@@ -929,6 +976,7 @@ class Modeller(object):
newTopology.setPeriodicBoxVectors(self.topology.getPeriodicBoxVectors()) newTopology.setPeriodicBoxVectors(self.topology.getPeriodicBoxVectors())
newAtoms = {} newAtoms = {}
newPositions = []*nanometer newPositions = []*nanometer
missingPositions = set()
for chain in self.topology.chains(): for chain in self.topology.chains():
newChain = newTopology.addChain(chain.id) newChain = newTopology.addChain(chain.id)
for residue in chain.residues(): for residue in chain.residues():
...@@ -986,9 +1034,10 @@ class Modeller(object): ...@@ -986,9 +1034,10 @@ class Modeller(object):
for index, atom in enumerate(template.atoms): for index, atom in enumerate(template.atoms):
if atom in matchingAtoms: if atom in matchingAtoms:
templateAtomPositions[index] = self.positions[matchingAtoms[atom].index].value_in_unit(nanometer) templateAtomPositions[index] = self.positions[matchingAtoms[atom].index].value_in_unit(nanometer)
newExtraPoints = {}
for index, atom in enumerate(template.atoms): for index, atom in enumerate(template.atoms):
if atom.element is None: if atom.element is None:
newTopology.addAtom(atom.name, None, newResidue) newExtraPoints[atom] = newTopology.addAtom(atom.name, None, newResidue)
position = None position = None
for site in template.virtualSites: for site in template.virtualSites:
if site.index == index: if site.index == index:
...@@ -1003,6 +1052,15 @@ class Modeller(object): ...@@ -1003,6 +1052,15 @@ class Modeller(object):
v2 = templateAtomPositions[site.atoms[2]] - templateAtomPositions[site.atoms[0]] v2 = templateAtomPositions[site.atoms[2]] - templateAtomPositions[site.atoms[0]]
cross = Vec3(v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0]) cross = Vec3(v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0])
position = templateAtomPositions[site.atoms[0]] + site.weights[0]*v1 + site.weights[1]*v2 + site.weights[2]*cross position = templateAtomPositions[site.atoms[0]] + site.weights[0]*v1 + site.weights[1]*v2 + site.weights[2]*cross
elif site.type == 'localCoords':
origin = templateAtomPositions[site.atoms[0]]*site.originWeights[0] + templateAtomPositions[site.atoms[1]]*site.originWeights[1] + templateAtomPositions[site.atoms[2]]*site.originWeights[2];
xdir = templateAtomPositions[site.atoms[0]]*site.xWeights[0] + templateAtomPositions[site.atoms[1]]*site.xWeights[1] + templateAtomPositions[site.atoms[2]]*site.xWeights[2];
ydir = templateAtomPositions[site.atoms[0]]*site.yWeights[0] + templateAtomPositions[site.atoms[1]]*site.yWeights[1] + templateAtomPositions[site.atoms[2]]*site.yWeights[2];
zdir = Vec3(xdir[1]*ydir[2]-xdir[2]*ydir[1], xdir[2]*ydir[0]-xdir[0]*ydir[2], xdir[0]*ydir[1]-xdir[1]*ydir[0])
xdir /= norm(xdir);
zdir /= norm(zdir);
ydir = Vec3(zdir[1]*xdir[2]-zdir[2]*xdir[1], zdir[2]*xdir[0]-zdir[0]*xdir[2], zdir[0]*xdir[1]-zdir[1]*xdir[0])
position = origin + xdir*site.localPos[0] + ydir*site.localPos[1] + zdir*site.localPos[2];
if position is None and atom.type in drudeTypeMap: if position is None and atom.type in drudeTypeMap:
# This is a Drude particle. Put it on top of its parent atom. # This is a Drude particle. Put it on top of its parent atom.
...@@ -1010,14 +1068,61 @@ class Modeller(object): ...@@ -1010,14 +1068,61 @@ class Modeller(object):
if atom2.type in drudeTypeMap[atom.type]: if atom2.type in drudeTypeMap[atom.type]:
position = deepcopy(pos) position = deepcopy(pos)
if position is None: if position is None:
# We couldn't figure out the correct position. As a wild guess, just put it at the center of the residue # We couldn't figure out the correct position. Put it at a random position near the center of the residue,
# and hope that energy minimization will fix it. # and we'll try to fix it later based on bonds.
knownPositions = [x for x in templateAtomPositions if x is not None] knownPositions = [x for x in templateAtomPositions if x is not None]
position = unit.sum(knownPositions)/len(knownPositions) position = Vec3(random.gauss(0, 1), random.gauss(0, 1), random.gauss(0, 1))+(unit.sum(knownPositions)/len(knownPositions))
missingPositions.add(len(newPositions))
newPositions.append(position*nanometer) newPositions.append(position*nanometer)
# Add bonds involving the extra points.
for atom1, atom2 in template.bonds:
atom1 = template.atoms[atom1]
atom2 = template.atoms[atom2]
if atom1 in newExtraPoints or atom2 in newExtraPoints:
if atom1 in newExtraPoints:
a1 = newExtraPoints[atom1]
else:
a1 = newAtoms[matchingAtoms[atom1]]
if atom2 in newExtraPoints:
a2 = newExtraPoints[atom2]
else:
a2 = newAtoms[matchingAtoms[atom2]]
newTopology.addBond(a1, a2)
for bond in self.topology.bonds(): for bond in self.topology.bonds():
if bond[0] in newAtoms and bond[1] in newAtoms: if bond[0] in newAtoms and bond[1] in newAtoms:
newTopology.addBond(newAtoms[bond[0]], newAtoms[bond[1]]) newTopology.addBond(newAtoms[bond[0]], newAtoms[bond[1]])
if len(missingPositions) > 0:
# There were particles whose position we couldn't identify before, since they were neither virtual sites nor Drude particles.
# Try to figure them out based on bonds. First, use the ForceField to create a list of every bond involving one of them.
system = forcefield.createSystem(newTopology, constraints=AllBonds)
bonds = []
for i in range(system.getNumConstraints()):
bond = system.getConstraintParameters(i)
if bond[0] in missingPositions or bond[1] in missingPositions:
bonds.append(bond)
# Now run a few iterations of SHAKE to try to select reasonable positions.
for iteration in range(15):
for atom1, atom2, distance in bonds:
if atom1 in missingPositions:
if atom2 in missingPositions:
weights = (0.5, 0.5)
else:
weights = (1.0, 0.0)
else:
weights = (0.0, 1.0)
delta = newPositions[atom2]-newPositions[atom1]
length = norm(delta)
delta *= (distance-length)/length
newPositions[atom1] -= weights[0]*delta
newPositions[atom2] += weights[1]*delta
self.topology = newTopology self.topology = newTopology
self.positions = newPositions self.positions = newPositions
...@@ -64,8 +64,10 @@ class PDBFile(object): ...@@ -64,8 +64,10 @@ class PDBFile(object):
The atom positions and Topology can be retrieved by calling getPositions() and getTopology(). The atom positions and Topology can be retrieved by calling getPositions() and getTopology().
Parameters: Parameters
- file (string) the name of the file to load ----------
file : string
the name of the file to load
""" """
top = Topology() top = Topology()
## The Topology read from the PDB file ## The Topology read from the PDB file
...@@ -152,7 +154,7 @@ class PDBFile(object): ...@@ -152,7 +154,7 @@ class PDBFile(object):
# Add bonds based on CONECT records. # Add bonds based on CONECT records.
connectBonds = [] connectBonds = []
for connect in pdb.models[0].connects: for connect in pdb.models[-1].connects:
i = connect[0] i = connect[0]
for j in connect[1:]: for j in connect[1:]:
if i in atomByNumber and j in atomByNumber: if i in atomByNumber and j in atomByNumber:
...@@ -176,10 +178,14 @@ class PDBFile(object): ...@@ -176,10 +178,14 @@ class PDBFile(object):
def getPositions(self, asNumpy=False, frame=0): def getPositions(self, asNumpy=False, frame=0):
"""Get the atomic positions. """Get the atomic positions.
Parameters: Parameters
- asNumpy (boolean=False) if true, the values are returned as a numpy array instead of a list of Vec3s ----------
- frame (int=0) the index of the frame for which to get positions asNumpy : boolean=False
""" if true, the values are returned as a numpy array instead of a list
of Vec3s
frame : int=0
the index of the frame for which to get positions
"""
if asNumpy: if asNumpy:
if self._numpyPositions is None: if self._numpyPositions is None:
self._numpyPositions = [None]*len(self._positions) self._numpyPositions = [None]*len(self._positions)
...@@ -234,13 +240,19 @@ class PDBFile(object): ...@@ -234,13 +240,19 @@ class PDBFile(object):
def writeFile(topology, positions, file=sys.stdout, keepIds=False): def writeFile(topology, positions, file=sys.stdout, keepIds=False):
"""Write a PDB file containing a single model. """Write a PDB file containing a single model.
Parameters: Parameters
- topology (Topology) The Topology defining the model to write ----------
- positions (list) The list of atomic positions to write topology : Topology
- file (file=stdout) A file to write to The Topology defining the model to write
- keepIds (bool=False) If True, keep the residue and chain IDs specified in the Topology rather than generating positions : list
new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of The list of atomic positions to write
the PDB format. Otherwise, the output file will be invalid. file : file=stdout
A file to write to
keepIds : bool=False
If True, keep the residue and chain IDs specified in the Topology
rather than generating new ones. Warning: It is up to the caller to
make sure these are valid IDs that satisfy the requirements of the
PDB format. Otherwise, the output file will be invalid.
""" """
PDBFile.writeHeader(topology, file) PDBFile.writeHeader(topology, file)
PDBFile.writeModel(topology, positions, file, keepIds=keepIds) PDBFile.writeModel(topology, positions, file, keepIds=keepIds)
...@@ -250,9 +262,12 @@ class PDBFile(object): ...@@ -250,9 +262,12 @@ class PDBFile(object):
def writeHeader(topology, file=sys.stdout): def writeHeader(topology, file=sys.stdout):
"""Write out the header for a PDB file. """Write out the header for a PDB file.
Parameters: Parameters
- topology (Topology) The Topology defining the molecular system being written ----------
- file (file=stdout) A file to write the file to topology : Topology
The Topology defining the molecular system being written
file : file=stdout
A file to write the file to
""" """
print("REMARK 1 CREATED WITH OPENMM %s, %s" % (Platform.getOpenMMVersion(), str(date.today())), file=file) print("REMARK 1 CREATED WITH OPENMM %s, %s" % (Platform.getOpenMMVersion(), str(date.today())), file=file)
vectors = topology.getPeriodicBoxVectors() vectors = topology.getPeriodicBoxVectors()
...@@ -266,15 +281,24 @@ class PDBFile(object): ...@@ -266,15 +281,24 @@ class PDBFile(object):
def writeModel(topology, positions, file=sys.stdout, modelIndex=None, keepIds=False): def writeModel(topology, positions, file=sys.stdout, modelIndex=None, keepIds=False):
"""Write out a model to a PDB file. """Write out a model to a PDB file.
Parameters: Parameters
- topology (Topology) The Topology defining the model to write ----------
- positions (list) The list of atomic positions to write topology : Topology
- file (file=stdout) A file to write the model to The Topology defining the model to write
- modelIndex (int=None) If not None, the model will be surrounded by MODEL/ENDMDL records with this index positions : list
- keepIds (bool=False) If True, keep the residue and chain IDs specified in the Topology rather than generating The list of atomic positions to write
new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of file : file=stdout
the PDB format. Otherwise, the output file will be invalid. A file to write the model to
modelIndex : int=None
If not None, the model will be surrounded by MODEL/ENDMDL records
with this index
keepIds : bool=False
If True, keep the residue and chain IDs specified in the Topology
rather than generating new ones. Warning: It is up to the caller to
make sure these are valid IDs that satisfy the requirements of the
PDB format. Otherwise, the output file will be invalid.
""" """
if len(list(topology.atoms())) != len(positions): if len(list(topology.atoms())) != len(positions):
raise ValueError('The number of positions must match the number of atoms') raise ValueError('The number of positions must match the number of atoms')
if is_quantity(positions): if is_quantity(positions):
...@@ -331,12 +355,15 @@ class PDBFile(object): ...@@ -331,12 +355,15 @@ class PDBFile(object):
def writeFooter(topology, file=sys.stdout): def writeFooter(topology, file=sys.stdout):
"""Write out the footer for a PDB file. """Write out the footer for a PDB file.
Parameters: Parameters
- topology (Topology) The Topology defining the molecular system being written ----------
- file (file=stdout) A file to write the file to topology : Topology
The Topology defining the molecular system being written
file : file=stdout
A file to write the file to
""" """
# Identify bonds that should be listed as CONECT records. # Identify bonds that should be listed as CONECT records.
standardResidues = ['ALA', 'ASN', 'CYS', 'GLU', 'HIS', 'LEU', 'MET', 'PRO', 'THR', 'TYR', standardResidues = ['ALA', 'ASN', 'CYS', 'GLU', 'HIS', 'LEU', 'MET', 'PRO', 'THR', 'TYR',
'ARG', 'ASP', 'GLN', 'GLY', 'ILE', 'LYS', 'PHE', 'SER', 'TRP', 'VAL', 'ARG', 'ASP', 'GLN', 'GLY', 'ILE', 'LYS', 'PHE', 'SER', 'TRP', 'VAL',
'A', 'G', 'C', 'U', 'I', 'DA', 'DG', 'DC', 'DT', 'DI', 'HOH'] 'A', 'G', 'C', 'U', 'I', 'DA', 'DG', 'DC', 'DT', 'DI', 'HOH']
...@@ -347,9 +374,9 @@ class PDBFile(object): ...@@ -347,9 +374,9 @@ class PDBFile(object):
elif atom1.name == 'SG' and atom2.name == 'SG' and atom1.residue.name == 'CYS' and atom2.residue.name == 'CYS': elif atom1.name == 'SG' and atom2.name == 'SG' and atom1.residue.name == 'CYS' and atom2.residue.name == 'CYS':
conectBonds.append((atom1, atom2)) conectBonds.append((atom1, atom2))
if len(conectBonds) > 0: if len(conectBonds) > 0:
# Work out the index used in the PDB file for each atom. # Work out the index used in the PDB file for each atom.
atomIndex = {} atomIndex = {}
nextAtomIndex = 0 nextAtomIndex = 0
prevChain = None prevChain = None
...@@ -360,9 +387,9 @@ class PDBFile(object): ...@@ -360,9 +387,9 @@ class PDBFile(object):
prevChain = atom.residue.chain prevChain = atom.residue.chain
atomIndex[atom] = nextAtomIndex atomIndex[atom] = nextAtomIndex
nextAtomIndex += 1 nextAtomIndex += 1
# Record which other atoms each atom is bonded to. # Record which other atoms each atom is bonded to.
atomBonds = {} atomBonds = {}
for atom1, atom2 in conectBonds: for atom1, atom2 in conectBonds:
index1 = atomIndex[atom1] index1 = atomIndex[atom1]
...@@ -373,9 +400,9 @@ class PDBFile(object): ...@@ -373,9 +400,9 @@ class PDBFile(object):
atomBonds[index2] = [] atomBonds[index2] = []
atomBonds[index1].append(index2) atomBonds[index1].append(index2)
atomBonds[index2].append(index1) atomBonds[index2].append(index1)
# Write the CONECT records. # Write the CONECT records.
for index1 in sorted(atomBonds): for index1 in sorted(atomBonds):
bonded = atomBonds[index1] bonded = atomBonds[index1]
while len(bonded) > 4: while len(bonded) > 4:
......
...@@ -44,9 +44,12 @@ class PDBReporter(object): ...@@ -44,9 +44,12 @@ class PDBReporter(object):
def __init__(self, file, reportInterval): def __init__(self, file, reportInterval):
"""Create a PDBReporter. """Create a PDBReporter.
Parameters: Parameters
- file (string) The file to write to ----------
- reportInterval (int) The interval (in time steps) at which to write frames file : string
The file to write to
reportInterval : int
The interval (in time steps) at which to write frames
""" """
self._reportInterval = reportInterval self._reportInterval = reportInterval
self._out = open(file, 'w') self._out = open(file, 'w')
...@@ -56,11 +59,18 @@ class PDBReporter(object): ...@@ -56,11 +59,18 @@ class PDBReporter(object):
def describeNextReport(self, simulation): def describeNextReport(self, simulation):
"""Get information about the next report this object will generate. """Get information about the next report this object will generate.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
Returns: A five element tuple. The first element is the number of steps until the simulation : Simulation
next report. The remaining elements specify whether that report will require The Simulation to generate a report for
positions, velocities, forces, and energies respectively.
Returns
-------
tuple
A five element tuple. The first element is the number of steps
until the next report. The remaining elements specify whether
that report will require positions, velocities, forces, and
energies respectively.
""" """
steps = self._reportInterval - simulation.currentStep%self._reportInterval steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, True, False, False, False) return (steps, True, False, False, False)
...@@ -68,9 +78,12 @@ class PDBReporter(object): ...@@ -68,9 +78,12 @@ class PDBReporter(object):
def report(self, simulation, state): def report(self, simulation, state):
"""Generate a report. """Generate a report.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
""" """
if self._nextModel == 0: if self._nextModel == 0:
PDBFile.writeHeader(simulation.topology, self._out) PDBFile.writeHeader(simulation.topology, self._out)
...@@ -95,9 +108,12 @@ class PDBxReporter(PDBReporter): ...@@ -95,9 +108,12 @@ class PDBxReporter(PDBReporter):
def report(self, simulation, state): def report(self, simulation, state):
"""Generate a report. """Generate a report.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
""" """
if self._nextModel == 0: if self._nextModel == 0:
PDBxFile.writeHeader(simulation.topology, self._out) PDBxFile.writeHeader(simulation.topology, self._out)
......
...@@ -53,10 +53,14 @@ class PDBxFile(object): ...@@ -53,10 +53,14 @@ class PDBxFile(object):
def __init__(self, file): def __init__(self, file):
"""Load a PDBx/mmCIF file. """Load a PDBx/mmCIF file.
The atom positions and Topology can be retrieved by calling getPositions() and getTopology(). The atom positions and Topology can be retrieved by calling
getPositions() and getTopology().
Parameters:
- file (string) the name of the file to load. Alternatively you can pass an open file object. Parameters
----------
file : string
the name of the file to load. Alternatively you can pass an open
file object.
""" """
top = Topology() top = Topology()
## The Topology read from the PDBx/mmCIF file ## The Topology read from the PDBx/mmCIF file
...@@ -193,10 +197,14 @@ class PDBxFile(object): ...@@ -193,10 +197,14 @@ class PDBxFile(object):
def getPositions(self, asNumpy=False, frame=0): def getPositions(self, asNumpy=False, frame=0):
"""Get the atomic positions. """Get the atomic positions.
Parameters: Parameters
- asNumpy (boolean=False) if true, the values are returned as a numpy array instead of a list of Vec3s ----------
- frame (int=0) the index of the frame for which to get positions asNumpy : bool=False
""" if true, the values are returned as a numpy array instead of a list
of Vec3s
frame : int=0
the index of the frame for which to get positions
"""
if asNumpy: if asNumpy:
if self._numpyPositions is None: if self._numpyPositions is None:
self._numpyPositions = [None]*len(self._positions) self._numpyPositions = [None]*len(self._positions)
...@@ -210,14 +218,21 @@ class PDBxFile(object): ...@@ -210,14 +218,21 @@ class PDBxFile(object):
entry=None): entry=None):
"""Write a PDBx/mmCIF file containing a single model. """Write a PDBx/mmCIF file containing a single model.
Parameters: Parameters
- topology (Topology) The Topology defining the model to write ----------
- positions (list) The list of atomic positions to write topology : Topology
- file (file=stdout) A file to write to The Topology defining the model to write
- keepIds (bool=False) If True, keep the residue and chain IDs specified in the Topology rather than generating positions : list
new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of The list of atomic positions to write
the PDBx/mmCIF format. Otherwise, the output file will be invalid. file : file=stdout
- entry (str=None) The entry ID to assign to the CIF file A file to write to
keepIds : bool=False
If True, keep the residue and chain IDs specified in the Topology
rather than generating new ones. Warning: It is up to the caller to
make sure these are valid IDs that satisfy the requirements of the
PDBx/mmCIF format. Otherwise, the output file will be invalid.
entry : str=None
The entry ID to assign to the CIF file
""" """
PDBxFile.writeHeader(topology, file, entry) PDBxFile.writeHeader(topology, file, entry)
PDBxFile.writeModel(topology, positions, file, keepIds=keepIds) PDBxFile.writeModel(topology, positions, file, keepIds=keepIds)
...@@ -226,10 +241,14 @@ class PDBxFile(object): ...@@ -226,10 +241,14 @@ class PDBxFile(object):
def writeHeader(topology, file=sys.stdout, entry=None): def writeHeader(topology, file=sys.stdout, entry=None):
"""Write out the header for a PDBx/mmCIF file. """Write out the header for a PDBx/mmCIF file.
Parameters: Parameters
- topology (Topology) The Topology defining the molecular system being written ----------
- file (file=stdout) A file to write the file to topology : Topology
- entry (str=None) The entry ID to assign to the CIF file The Topology defining the molecular system being written
file : file=stdout
A file to write the file to
entry : str=None
The entry ID to assign to the CIF file
""" """
if entry is not None: if entry is not None:
print('data_%s' % entry, file=file) print('data_%s' % entry, file=file)
...@@ -280,14 +299,21 @@ class PDBxFile(object): ...@@ -280,14 +299,21 @@ class PDBxFile(object):
def writeModel(topology, positions, file=sys.stdout, modelIndex=1, keepIds=False): def writeModel(topology, positions, file=sys.stdout, modelIndex=1, keepIds=False):
"""Write out a model to a PDBx/mmCIF file. """Write out a model to a PDBx/mmCIF file.
Parameters: Parameters
- topology (Topology) The Topology defining the model to write ----------
- positions (list) The list of atomic positions to write topology : Topology
- file (file=stdout) A file to write the model to The Topology defining the model to write
- modelIndex (int=1) The model number of this frame positions : list
- keepIds (bool=False) If True, keep the residue and chain IDs specified in the Topology rather than generating The list of atomic positions to write
new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of file : file=stdout
the PDBx/mmCIF format. Otherwise, the output file will be invalid. A file to write the model to
modelIndex : int=1
The model number of this frame
keepIds : bool=False
If True, keep the residue and chain IDs specified in the Topology
rather than generating new ones. Warning: It is up to the caller to
make sure these are valid IDs that satisfy the requirements of the
PDBx/mmCIF format. Otherwise, the output file will be invalid.
""" """
if len(list(topology.atoms())) != len(positions): if len(list(topology.atoms())) != len(positions):
raise ValueError('The number of positions must match the number of atoms') raise ValueError('The number of positions must match the number of atoms')
......
...@@ -55,13 +55,19 @@ class Simulation(object): ...@@ -55,13 +55,19 @@ class Simulation(object):
def __init__(self, topology, system, integrator, platform=None, platformProperties=None): def __init__(self, topology, system, integrator, platform=None, platformProperties=None):
"""Create a Simulation. """Create a Simulation.
Parameters: Parameters
- topology (Topology) A Topology describing the the system to simulate ----------
- system (System) The OpenMM System object to simulate topology : Topology
- integrator (Integrator) The OpenMM Integrator to use for simulating the System A Topology describing the the system to simulate
- platform (Platform=None) If not None, the OpenMM Platform to use system : System
- platformProperties (map=None) If not None, a set of platform-specific properties to pass The OpenMM System object to simulate
to the Context's constructor integrator : Integrator
The OpenMM Integrator to use for simulating the System
platform : Platform=None
If not None, the OpenMM Platform to use
platformProperties : map=None
If not None, a set of platform-specific properties to pass to the
Context's constructor
""" """
## The Topology describing the system being simulated ## The Topology describing the system being simulated
self.topology = topology self.topology = topology
...@@ -84,35 +90,48 @@ class Simulation(object): ...@@ -84,35 +90,48 @@ class Simulation(object):
def minimizeEnergy(self, tolerance=10*unit.kilojoule/unit.mole, maxIterations=0): def minimizeEnergy(self, tolerance=10*unit.kilojoule/unit.mole, maxIterations=0):
"""Perform a local energy minimization on the system. """Perform a local energy minimization on the system.
Parameters: Parameters
- tolerance (energy=10*kilojoules/mole) The energy tolerance to which the system should be minimized ----------
- maxIterations (int=0) The maximum number of iterations to perform. If this is 0, minimization is continued tolerance : energy=10*kilojoules/mole
until the results converge without regard to how many iterations it takes. The energy tolerance to which the system should be minimized
maxIterations : int=0
The maximum number of iterations to perform. If this is 0,
minimization is continued until the results converge without regard
to how many iterations it takes.
""" """
mm.LocalEnergyMinimizer.minimize(self.context, tolerance, maxIterations) mm.LocalEnergyMinimizer.minimize(self.context, tolerance, maxIterations)
def step(self, steps): def step(self, steps):
"""Advance the simulation by integrating a specified number of time steps.""" """Advance the simulation by integrating a specified number of time steps."""
self._simulate(endStep=self.currentStep+steps) self._simulate(endStep=self.currentStep+steps)
def runForClockTime(self, time, checkpointFile=None, stateFile=None, checkpointInterval=None): def runForClockTime(self, time, checkpointFile=None, stateFile=None, checkpointInterval=None):
"""Advance the simulation by integrating time steps until a fixed amount of clock time has elapsed. """Advance the simulation by integrating time steps until a fixed amount of clock time has elapsed.
This is useful when you have a limited amount of computer time available, and want to run the longest simulation This is useful when you have a limited amount of computer time available, and want to run the longest simulation
possible in that time. This method will continue taking time steps until the specified clock time has elapsed, possible in that time. This method will continue taking time steps until the specified clock time has elapsed,
then return. It also can automatically write out a checkpoint and/or state file before returning, so you can then return. It also can automatically write out a checkpoint and/or state file before returning, so you can
later resume the simulation. Another option allows it to write checkpoints or states at regular intervals, so later resume the simulation. Another option allows it to write checkpoints or states at regular intervals, so
you can resume even if the simulation is interrupted before the time limit is reached. you can resume even if the simulation is interrupted before the time limit is reached.
Parameters: Parameters
- time (time) the amount of time to run for. If no units are specified, it is assumed to be a number of hours. ----------
- checkpointFile (string or file=None) if specified, a checkpoint file will be written at the end of the time : time
simulation (and optionally at regular intervals before then) by passing this to saveCheckpoint(). the amount of time to run for. If no units are specified, it is
- stateFile (string or file=None) if specified, a state file will be written at the end of the assumed to be a number of hours.
simulation (and optionally at regular intervals before then) by passing this to saveState(). checkpointFile : string or file=None
- checkpointInterval (time=None) if specified, checkpoints and/or states will be written at regular intervals if specified, a checkpoint file will be written at the end of the
during the simulation, in addition to writing a final version at the end. If no units are specified, this is simulation (and optionally at regular intervals before then) by
assumed to be in hours. passing this to saveCheckpoint().
stateFile : string or file=None
if specified, a state file will be written at the end of the
simulation (and optionally at regular intervals before then) by
passing this to saveState().
checkpointInterval : time=None
if specified, checkpoints and/or states will be written at regular
intervals during the simulation, in addition to writing a final
version at the end. If no units are specified, this is assumed to
be in hours.
""" """
if unit.is_quantity(time): if unit.is_quantity(time):
time = time.value_in_unit(unit.hours) time = time.value_in_unit(unit.hours)
...@@ -131,7 +150,7 @@ class Simulation(object): ...@@ -131,7 +150,7 @@ class Simulation(object):
self.saveCheckpoint(checkpointFile) self.saveCheckpoint(checkpointFile)
if stateFile is not None: if stateFile is not None:
self.saveState(stateFile) self.saveState(stateFile)
def _simulate(self, endStep=None, endTime=None): def _simulate(self, endStep=None, endTime=None):
if endStep is None: if endStep is None:
endStep = sys.maxsize endStep = sys.maxsize
...@@ -174,30 +193,36 @@ class Simulation(object): ...@@ -174,30 +193,36 @@ class Simulation(object):
def saveCheckpoint(self, file): def saveCheckpoint(self, file):
"""Save a checkpoint of the simulation to a file. """Save a checkpoint of the simulation to a file.
The output is a binary file that contains a complete representation of the current state of the Simulation. The output is a binary file that contains a complete representation of the current state of the Simulation.
It includes both publicly visible data such as the particle positions and velocities, and also internal data It includes both publicly visible data such as the particle positions and velocities, and also internal data
such as the states of random number generators. Reloading the checkpoint will put the Simulation back into such as the states of random number generators. Reloading the checkpoint will put the Simulation back into
precisely the same state it had before, so it can be exactly continued. precisely the same state it had before, so it can be exactly continued.
A checkpoint file is highly specific to the Simulation it was created from. It can only be loaded into A checkpoint file is highly specific to the Simulation it was created from. It can only be loaded into
another Simulation that has an identical System, uses the same Platform and OpenMM version, and is running on another Simulation that has an identical System, uses the same Platform and OpenMM version, and is running on
identical hardware. If you need a more portable way to resume simulations, consider using saveState() instead. identical hardware. If you need a more portable way to resume simulations, consider using saveState() instead.
Parameters: Parameters
- file (string or file) a File-like object to write the checkpoint to, or alternatively a filename ----------
file : string or file
a File-like object to write the checkpoint to, or alternatively a
filename
""" """
if isinstance(file, str): if isinstance(file, str):
with open(file, 'wb') as f: with open(file, 'wb') as f:
f.write(self.context.createCheckpoint()) f.write(self.context.createCheckpoint())
else: else:
file.write(self.context.createCheckpoint()) file.write(self.context.createCheckpoint())
def loadCheckpoint(self, file): def loadCheckpoint(self, file):
"""Load a checkpoint file that was created with saveCheckpoint(). """Load a checkpoint file that was created with saveCheckpoint().
Parameters: Parameters
- file (string or file) a File-like object to load the checkpoint from, or alternatively a filename ----------
file : string or file
a File-like object to load the checkpoint from, or alternatively a
filename
""" """
if isinstance(file, str): if isinstance(file, str):
with open(file, 'rb') as f: with open(file, 'rb') as f:
...@@ -207,18 +232,21 @@ class Simulation(object): ...@@ -207,18 +232,21 @@ class Simulation(object):
def saveState(self, file): def saveState(self, file):
"""Save the current state of the simulation to a file. """Save the current state of the simulation to a file.
The output is an XML file containing a serialized State object. It includes all publicly visible data, The output is an XML file containing a serialized State object. It includes all publicly visible data,
including positions, velocities, and parameters. Reloading the State will put the Simulation back into including positions, velocities, and parameters. Reloading the State will put the Simulation back into
approximately the same state it had before. approximately the same state it had before.
Unlike saveCheckpoint(), this does not store internal data such as the states of random number generators. Unlike saveCheckpoint(), this does not store internal data such as the states of random number generators.
Therefore, you should not expect the following trajectory to be identical to what would have been produced Therefore, you should not expect the following trajectory to be identical to what would have been produced
with the original Simulation. On the other hand, this means it is portable across different Platforms or with the original Simulation. On the other hand, this means it is portable across different Platforms or
hardware. hardware.
Parameters: Parameters
- file (string or file) a File-like object to write the state to, or alternatively a filename ----------
file : string or file
a File-like object to write the state to, or alternatively a
filename
""" """
state = self.context.getState(getPositions=True, getVelocities=True, getParameters=True) state = self.context.getState(getPositions=True, getVelocities=True, getParameters=True)
xml = mm.XmlSerializer.serialize(state) xml = mm.XmlSerializer.serialize(state)
...@@ -227,12 +255,15 @@ class Simulation(object): ...@@ -227,12 +255,15 @@ class Simulation(object):
f.write(xml) f.write(xml)
else: else:
file.write(xml) file.write(xml)
def loadState(self, file): def loadState(self, file):
"""Load a State file that was created with saveState(). """Load a State file that was created with saveState().
Parameters: Parameters
- file (string or file) a File-like object to load the state from, or alternatively a filename ----------
file : string or file
a File-like object to load the state from, or alternatively a
filename
""" """
if isinstance(file, str): if isinstance(file, str):
with open(file, 'r') as f: with open(file, 'r') as f:
......
...@@ -60,31 +60,54 @@ class StateDataReporter(object): ...@@ -60,31 +60,54 @@ class StateDataReporter(object):
progress=False, remainingTime=False, speed=False, elapsedTime=False, separator=',', systemMass=None, totalSteps=None): progress=False, remainingTime=False, speed=False, elapsedTime=False, separator=',', systemMass=None, totalSteps=None):
"""Create a StateDataReporter. """Create a StateDataReporter.
Parameters: Parameters
- file (string or file) The file to write to, specified as a file name or file object ----------
- reportInterval (int) The interval (in time steps) at which to write frames file : string or file
- step (boolean=False) Whether to write the current step index to the file The file to write to, specified as a file name or file object
- time (boolean=False) Whether to write the current time to the file reportInterval : int
- potentialEnergy (boolean=False) Whether to write the potential energy to the file The interval (in time steps) at which to write frames
- kineticEnergy (boolean=False) Whether to write the kinetic energy to the file step : bool=False
- totalEnergy (boolean=False) Whether to write the total energy to the file Whether to write the current step index to the file
- temperature (boolean=False) Whether to write the instantaneous temperature to the file time : bool=False
- volume (boolean=False) Whether to write the periodic box volume to the file Whether to write the current time to the file
- density (boolean=False) Whether to write the system density to the file potentialEnergy : bool=False
- progress (boolean=False) Whether to write current progress (percent completion) to the file. Whether to write the potential energy to the file
If this is True, you must also specify totalSteps. kineticEnergy : bool=False
- remainingTime (boolean=False) Whether to write an estimate of the remaining clock time until Whether to write the kinetic energy to the file
completion to the file. If this is True, you must also specify totalSteps. totalEnergy : bool=False
- speed (bool=False) Whether to write an estimate of the simulation speed in ns/day to the file Whether to write the total energy to the file
- elapsedTime (bool=False) Whether to write the elapsed time of the simulation in seconds to the file. temperature : bool=False
- separator (string=',') The separator to use between columns in the file Whether to write the instantaneous temperature to the file
- systemMass (mass=None) The total mass to use for the system when reporting density. If this is volume : bool=False
None (the default), the system mass is computed by summing the masses of all particles. This Whether to write the periodic box volume to the file
parameter is useful when the particle masses do not reflect their actual physical mass, such as density : bool=False
when some particles have had their masses set to 0 to immobilize them. Whether to write the system density to the file
- totalSteps (int=None) The total number of steps that will be included in the simulation. This progress : bool=False
is required if either progress or remainingTime is set to True, and defines how many steps will Whether to write current progress (percent completion) to the file.
indicate 100% completion. If this is True, you must also specify totalSteps.
remainingTime : bool=False
Whether to write an estimate of the remaining clock time until
completion to the file. If this is True, you must also specify
totalSteps.
speed : bool=False
Whether to write an estimate of the simulation speed in ns/day to
the file
elapsedTime : bool=False
Whether to write the elapsed time of the simulation in seconds to
the file.
separator : string=','
The separator to use between columns in the file
systemMass : mass=None
The total mass to use for the system when reporting density. If
this is None (the default), the system mass is computed by summing
the masses of all particles. This parameter is useful when the
particle masses do not reflect their actual physical mass, such as
when some particles have had their masses set to 0 to immobilize
them.
totalSteps : int=None
The total number of steps that will be included in the simulation.
This is required if either progress or remainingTime is set to True,
and defines how many steps will indicate 100% completion.
""" """
self._reportInterval = reportInterval self._reportInterval = reportInterval
self._openedFile = isinstance(file, str) self._openedFile = isinstance(file, str)
...@@ -129,11 +152,18 @@ class StateDataReporter(object): ...@@ -129,11 +152,18 @@ class StateDataReporter(object):
def describeNextReport(self, simulation): def describeNextReport(self, simulation):
"""Get information about the next report this object will generate. """Get information about the next report this object will generate.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
Returns: A five element tuple. The first element is the number of steps until the simulation : Simulation
next report. The remaining elements specify whether that report will require The Simulation to generate a report for
positions, velocities, forces, and energies respectively.
Returns
-------
tuple
A five element tuple. The first element is the number of steps
until the next report. The remaining elements specify whether
that report will require positions, velocities, forces, and
energies respectively.
""" """
steps = self._reportInterval - simulation.currentStep%self._reportInterval steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, self._needsPositions, self._needsVelocities, self._needsForces, self._needEnergy) return (steps, self._needsPositions, self._needsVelocities, self._needsForces, self._needEnergy)
...@@ -141,9 +171,12 @@ class StateDataReporter(object): ...@@ -141,9 +171,12 @@ class StateDataReporter(object):
def report(self, simulation, state): def report(self, simulation, state):
"""Generate a report. """Generate a report.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
""" """
if not self._hasInitialized: if not self._hasInitialized:
self._initializeConstants(simulation) self._initializeConstants(simulation)
...@@ -174,11 +207,16 @@ class StateDataReporter(object): ...@@ -174,11 +207,16 @@ class StateDataReporter(object):
def _constructReportValues(self, simulation, state): def _constructReportValues(self, simulation, state):
"""Query the simulation for the current state of our observables of interest. """Query the simulation for the current state of our observables of interest.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
Returns: A list of values summarizing the current state of Returns
-------
A list of values summarizing the current state of
the simulation, to be printed or saved. Each element in the list the simulation, to be printed or saved. Each element in the list
corresponds to one of the columns in the resulting CSV file. corresponds to one of the columns in the resulting CSV file.
""" """
......
...@@ -62,19 +62,43 @@ class Topology(object): ...@@ -62,19 +62,43 @@ class Topology(object):
def __repr__(self): def __repr__(self):
nchains = len(self._chains) nchains = len(self._chains)
nres = sum(1 for r in self.residues()) nres = self._numResidues
natom = sum(1 for a in self.atoms()) natom = self._numAtoms
nbond = len(self._bonds) nbond = len(self._bonds)
return '<%s; %d chains, %d residues, %d atoms, %d bonds>' % ( return '<%s; %d chains, %d residues, %d atoms, %d bonds>' % (
type(self).__name__, nchains, nres, natom, nbond) type(self).__name__, nchains, nres, natom, nbond)
def getNumAtoms(self):
"""Return the number of atoms in the Topology.
"""
natom = self._numAtoms
return natom
def getNumResidues(self):
"""Return the number of residues in the Topology.
"""
nres = self._numResidues
return nres
def getNumChains(self):
"""Return the number of chains in the Topology.
"""
nchain = len(self._chains)
return nchain
def addChain(self, id=None): def addChain(self, id=None):
"""Create a new Chain and add it to the Topology. """Create a new Chain and add it to the Topology.
Parameters: Parameters
- id (string=None) An optional identifier for the chain. If this is omitted, an id ----------
is generated based on the chain index. id : string=None
Returns: the newly created Chain An optional identifier for the chain. If this is omitted, an id is
generated based on the chain index.
Returns
-------
Chain
the newly created Chain
""" """
if id is None: if id is None:
id = str(len(self._chains)+1) id = str(len(self._chains)+1)
...@@ -85,12 +109,20 @@ class Topology(object): ...@@ -85,12 +109,20 @@ class Topology(object):
def addResidue(self, name, chain, id=None): def addResidue(self, name, chain, id=None):
"""Create a new Residue and add it to the Topology. """Create a new Residue and add it to the Topology.
Parameters: Parameters
- name (string) The name of the residue to add ----------
- chain (Chain) The Chain to add it to name : string
- id (string=None) An optional identifier for the residue. If this is omitted, an id The name of the residue to add
is generated based on the residue index. chain : Chain
Returns: the newly created Residue The Chain to add it to
id : string=None
An optional identifier for the residue. If this is omitted, an id
is generated based on the residue index.
Returns
-------
Residue
the newly created Residue
""" """
if id is None: if id is None:
id = str(self._numResidues+1) id = str(self._numResidues+1)
...@@ -102,13 +134,22 @@ class Topology(object): ...@@ -102,13 +134,22 @@ class Topology(object):
def addAtom(self, name, element, residue, id=None): def addAtom(self, name, element, residue, id=None):
"""Create a new Atom and add it to the Topology. """Create a new Atom and add it to the Topology.
Parameters: Parameters
- name (string) The name of the atom to add ----------
- element (Element) The element of the atom to add name : string
- residue (Residue) The Residue to add it to The name of the atom to add
- id (string=None) An optional identifier for the atom. If this is omitted, an id element : Element
is generated based on the atom index. The element of the atom to add
Returns: the newly created Atom residue : Residue
The Residue to add it to
id : string=None
An optional identifier for the atom. If this is omitted, an id is
generated based on the atom index.
Returns
-------
Atom
the newly created Atom
""" """
if id is None: if id is None:
id = str(self._numAtoms+1) id = str(self._numAtoms+1)
...@@ -120,9 +161,12 @@ class Topology(object): ...@@ -120,9 +161,12 @@ class Topology(object):
def addBond(self, atom1, atom2): def addBond(self, atom1, atom2):
"""Create a new bond and add it to the Topology. """Create a new bond and add it to the Topology.
Parameters: Parameters
- atom1 (Atom) The first Atom connected by the bond ----------
- atom2 (Atom) The second Atom connected by the bond atom1 : Atom
The first Atom connected by the bond
atom2 : Atom
The second Atom connected by the bond
""" """
self._bonds.append((atom1, atom2)) self._bonds.append((atom1, atom2))
...@@ -256,10 +300,13 @@ class Topology(object): ...@@ -256,10 +300,13 @@ class Topology(object):
self.addBond(atomMaps[fromResidue][fromAtom], atomMaps[toResidue][toAtom]) self.addBond(atomMaps[fromResidue][fromAtom], atomMaps[toResidue][toAtom])
def createDisulfideBonds(self, positions): def createDisulfideBonds(self, positions):
"""Identify disulfide bonds based on proximity and add them to the Topology. """Identify disulfide bonds based on proximity and add them to the
Topology.
Parameters: Parameters
- positions (list) The list of atomic positions based on which to identify bonded atoms ----------
positions : list
The list of atomic positions based on which to identify bonded atoms
""" """
def isCyx(res): def isCyx(res):
names = [atom.name for atom in res._atoms] names = [atom.name for atom in res._atoms]
......
...@@ -10,7 +10,7 @@ Portions copyright (c) 2013-2015 Stanford University and the Authors. ...@@ -10,7 +10,7 @@ Portions copyright (c) 2013-2015 Stanford University and the Authors.
Authors: Peter Eastman Authors: Peter Eastman
Contributors: Contributors:
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
...@@ -36,49 +36,52 @@ from simtk.openmm import CustomIntegrator ...@@ -36,49 +36,52 @@ from simtk.openmm import CustomIntegrator
class MTSIntegrator(CustomIntegrator): class MTSIntegrator(CustomIntegrator):
"""MTSIntegrator implements the rRESPA multiple time step integration algorithm. """MTSIntegrator implements the rRESPA multiple time step integration algorithm.
This integrator allows different forces to be evaluated at different frequencies, This integrator allows different forces to be evaluated at different frequencies,
for example to evaluate the expensive, slowly changing forces less frequently than for example to evaluate the expensive, slowly changing forces less frequently than
the inexpensive, quickly changing forces. the inexpensive, quickly changing forces.
To use it, you must first divide your forces into two or more groups (by calling To use it, you must first divide your forces into two or more groups (by calling
setForceGroup() on them) that should be evaluated at different frequencies. When setForceGroup() on them) that should be evaluated at different frequencies. When
you create the integrator, you provide a tuple for each group specifying the index you create the integrator, you provide a tuple for each group specifying the index
of the force group and the frequency (as a fraction of the outermost time step) at of the force group and the frequency (as a fraction of the outermost time step) at
which to evaluate it. For example: which to evaluate it. For example:
<pre> <pre>
integrator = MTSIntegrator(4*femtoseconds, [(0,1), (1,2), (2,8)]) integrator = MTSIntegrator(4*femtoseconds, [(0,1), (1,2), (2,8)])
</pre> </pre>
This specifies that the outermost time step is 4 fs, so each step of the integrator This specifies that the outermost time step is 4 fs, so each step of the integrator
will advance time by that much. It also says that force group 0 should be evaluated will advance time by that much. It also says that force group 0 should be evaluated
once per time step, force group 1 should be evaluated twice per time step (every 2 fs), once per time step, force group 1 should be evaluated twice per time step (every 2 fs),
and force group 2 should be evaluated eight times per time step (every 0.5 fs). and force group 2 should be evaluated eight times per time step (every 0.5 fs).
A common use of this algorithm is to evaluate reciprocal space nonbonded interactions A common use of this algorithm is to evaluate reciprocal space nonbonded interactions
less often than the bonded and direct space nonbonded interactions. The following less often than the bonded and direct space nonbonded interactions. The following
example looks up the NonbondedForce, sets the reciprocal space interactions to their example looks up the NonbondedForce, sets the reciprocal space interactions to their
own force group, and then creates an integrator that evaluates them once every 4 fs, own force group, and then creates an integrator that evaluates them once every 4 fs,
but all other interactions every 2 fs. but all other interactions every 2 fs.
<pre> <pre>
nonbonded = [f for f in system.getForces() if isinstance(f, NonbondedForce)][0] nonbonded = [f for f in system.getForces() if isinstance(f, NonbondedForce)][0]
nonbonded.setReciprocalSpaceForceGroup(1) nonbonded.setReciprocalSpaceForceGroup(1)
integrator = MTSIntegrator(4*femtoseconds, [(1,1), (0,2)]) integrator = MTSIntegrator(4*femtoseconds, [(1,1), (0,2)])
</pre> </pre>
For details, see Tuckerman et al., J. Chem. Phys. 97(3) pp. 1990-2001 (1992). For details, see Tuckerman et al., J. Chem. Phys. 97(3) pp. 1990-2001 (1992).
""" """
def __init__(self, dt, groups): def __init__(self, dt, groups):
"""Create an MTSIntegrator. """Create an MTSIntegrator.
Parameters: Parameters
- dt (time) The largest (outermost) integration time step to use ----------
- groups (list) A list of tuples defining the force groups. The first element of each dt : time
tuple is the force group index, and the second element is the number of times that force The largest (outermost) integration time step to use
group should be evaluated in one time step. groups : list
A list of tuples defining the force groups. The first element of
each tuple is the force group index, and the second element is the
number of times that force group should be evaluated in one time step.
""" """
if len(groups) == 0: if len(groups) == 0:
raise ValueError("No force groups specified") raise ValueError("No force groups specified")
...@@ -88,7 +91,7 @@ class MTSIntegrator(CustomIntegrator): ...@@ -88,7 +91,7 @@ class MTSIntegrator(CustomIntegrator):
self.addUpdateContextState(); self.addUpdateContextState();
self._createSubsteps(1, groups) self._createSubsteps(1, groups)
self.addConstrainVelocities(); self.addConstrainVelocities();
def _createSubsteps(self, parentSubsteps, groups): def _createSubsteps(self, parentSubsteps, groups):
group, substeps = groups[0] group, substeps = groups[0]
stepsPerParentStep = substeps/parentSubsteps stepsPerParentStep = substeps/parentSubsteps
......
...@@ -54,9 +54,12 @@ class Unit(object): ...@@ -54,9 +54,12 @@ class Unit(object):
def __init__(self, base_or_scaled_units): def __init__(self, base_or_scaled_units):
"""Create a new Unit. """Create a new Unit.
Parameters: Parameters
- self (Unit) The newly created Unit. ----------
- base_or_scaled_units (dict) Keys are BaseUnits or ScaledUnits. Values are exponents (numbers). self : Unit
The newly created Unit.
base_or_scaled_units : dict
Keys are BaseUnits or ScaledUnits. Values are exponents (numbers).
""" """
# Unit contents are of two types: BaseUnits and ScaledUnits # Unit contents are of two types: BaseUnits and ScaledUnits
self._top_base_units = {} self._top_base_units = {}
...@@ -389,7 +392,8 @@ class Unit(object): ...@@ -389,7 +392,8 @@ class Unit(object):
Strips off any ScaledUnits in the Unit, leaving only BaseUnits. Strips off any ScaledUnits in the Unit, leaving only BaseUnits.
Parameters Parameters
- system: a dictionary of (BaseDimension, BaseUnit) pairs ----------
system : a dictionary of (BaseDimension, BaseUnit) pairs
""" """
return system.express_unit(self) return system.express_unit(self)
...@@ -583,7 +587,7 @@ class UnitSystem(object): ...@@ -583,7 +587,7 @@ class UnitSystem(object):
Parameters Parameters
---------- ----------
units: ``list`` units : list
List of base units from which to construct the unit system List of base units from which to construct the unit system
""" """
def __init__(self, units): def __init__(self, units):
...@@ -678,7 +682,7 @@ def is_unit(x): ...@@ -678,7 +682,7 @@ def is_unit(x):
Returns True if x is a Unit, False otherwise. Returns True if x is a Unit, False otherwise.
Examples Examples
--------
>>> is_unit(16) >>> is_unit(16)
False False
""" """
......
...@@ -45,7 +45,7 @@ using namespace OpenMM; ...@@ -45,7 +45,7 @@ using namespace OpenMM;
%} %}
%feature("autodoc", "1"); %feature("autodoc", "0");
%nodefaultctor; %nodefaultctor;
%include features.i %include features.i
...@@ -61,49 +61,3 @@ using namespace OpenMM; ...@@ -61,49 +61,3 @@ using namespace OpenMM;
# namespace # namespace
__all__ = [k for k in locals().keys() if not (k.endswith('_swigregister') or k.startswith('_'))] __all__ = [k for k in locals().keys() if not (k.endswith('_swigregister') or k.startswith('_'))]
%} %}
/*
%extend OpenMM::XmlSerializer {
%template(XmlSerializer_serialize_AndersenThermostat) XmlSerializer::serialize<AndersenThermostat>;
%template(XmlSerializer_serialize_RBTorsionForce) XmlSerializer::serialize<RBTorsionForce>;
%template(XmlSerializer_serialize_CMAPTorsionForce) XmlSerializer::serialize<CMAPTorsionForce>;
%template(XmlSerializer_serialize_CMMotionRemover) XmlSerializer::serialize<CMMotionRemover>;
%template(XmlSerializer_serialize_CustomAngleForce) XmlSerializer::serialize<CustomAngleForce>;
%template(XmlSerializer_serialize_CustomBondForce) XmlSerializer::serialize<CustomBondForce>;
%template(XmlSerializer_serialize_CustomExternalForce) XmlSerializer::serialize<CustomExternalForce>;
%template(XmlSerializer_serialize_CustomGBForce) XmlSerializer::serialize<CustomGBForce>;
%template(XmlSerializer_serialize_CustomHbondForce) XmlSerializer::serialize<CustomHbondForce>;
%template(XmlSerializer_serialize_CustomNonbondedForce) XmlSerializer::serialize<CustomNonbondedForce>;
%template(XmlSerializer_serialize_CustomTorsionForce) XmlSerializer::serialize<CustomTorsionForce>;
%template(XmlSerializer_serialize_GBSAOBCForce) XmlSerializer::serialize<GBSAOBCForce>;
%template(XmlSerializer_serialize_GBVIForce) XmlSerializer::serialize<GBVIForce>;
%template(XmlSerializer_serialize_HarmonicAngleForce) XmlSerializer::serialize<HarmonicAngleForce>;
%template(XmlSerializer_serialize_HarmonicBondForce) XmlSerializer::serialize<HarmonicBondForce>;
%template(XmlSerializer_serialize_MonteCarloBarostat) XmlSerializer::serialize<MonteCarloBarostat>;
%template(XmlSerializer_serialize_MonteCarloAnisotropicBarostat) XmlSerializer::serialize<MonteCarloAnisotropicBarostat>;
%template(XmlSerializer_serialize_NonbondedForce) XmlSerializer::serialize<NonbondedForce>;
%template(XmlSerializer_serialize_RBTorsionForce) XmlSerializer::serialize<RBTorsionForce>;
%template(XmlSerializer_serialize_System) XmlSerializer::serialize<System>;
%template(XmlSerializer_deserialize_AndersenThermostat) XmlSerializer::deserialize<AndersenThermostat>;
%template(XmlSerializer_deserialize_RBTorsionForce) XmlSerializer::deserialize<RBTorsionForce>;
%template(XmlSerializer_deserialize_CMAPTorsionForce) XmlSerializer::deserialize<CMAPTorsionForce>;
%template(XmlSerializer_deserialize_CMMotionRemover) XmlSerializer::deserialize<CMMotionRemover>;
%template(XmlSerializer_deserialize_CustomAngleForce) XmlSerializer::deserialize<CustomAngleForce>;
%template(XmlSerializer_deserialize_CustomBondForce) XmlSerializer::deserialize<CustomBondForce>;
%template(XmlSerializer_deserialize_CustomExternalForce) XmlSerializer::deserialize<CustomExternalForce>;
%template(XmlSerializer_deserialize_CustomGBForce) XmlSerializer::deserialize<CustomGBForce>;
%template(XmlSerializer_deserialize_CustomHbondForce) XmlSerializer::deserialize<CustomHbondForce>;
%template(XmlSerializer_deserialize_CustomNonbondedForce) XmlSerializer::deserialize<CustomNonbondedForce>;
%template(XmlSerializer_deserialize_CustomTorsionForce) XmlSerializer::deserialize<CustomTorsionForce>;
%template(XmlSerializer_deserialize_GBSAOBCForce) XmlSerializer::deserialize<GBSAOBCForce>;
%template(XmlSerializer_deserialize_GBVIForce) XmlSerializer::deserialize<GBVIForce>;
%template(XmlSerializer_deserialize_HarmonicAngleForce) XmlSerializer::deserialize<HarmonicAngleForce>;
%template(XmlSerializer_deserialize_HarmonicBondForce) XmlSerializer::deserialize<HarmonicBondForce>;
%template(XmlSerializer_deserialize_MonteCarloBarostat) XmlSerializer::deserialize<MonteCarloBarostat>;
%template(XmlSerializer_deserialize_MonteCarloAnisotropicBarostat) XmlSerializer::deserialize<MonteCarloAnisotropicBarostat>;
%template(XmlSerializer_deserialize_NonbondedForce) XmlSerializer::deserialize<NonbondedForce>;
%template(XmlSerializer_deserialize_RBTorsionForce) XmlSerializer::deserialize<RBTorsionForce>;
%template(XmlSerializer_deserialize_System) XmlSerializer::deserialize<System>;
};
*/
...@@ -10,6 +10,7 @@ import getopt ...@@ -10,6 +10,7 @@ import getopt
import re import re
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from distutils.version import LooseVersion from distutils.version import LooseVersion
import copy
try: try:
from html.parser import HTMLParser from html.parser import HTMLParser
...@@ -20,7 +21,6 @@ except ImportError: ...@@ -20,7 +21,6 @@ except ImportError:
INDENT = " " INDENT = " "
docTags = {'emphasis':'i', 'bold':'b', 'itemizedlist':'ul', 'listitem':'li', 'preformatted':'pre', 'computeroutput':'tt', 'subscript':'sub'} docTags = {'emphasis':'i', 'bold':'b', 'itemizedlist':'ul', 'listitem':'li', 'preformatted':'pre', 'computeroutput':'tt', 'subscript':'sub'}
def striphtmltags(s): def striphtmltags(s):
"""Strip a couple html tags used inside docstrings in the C++ source """Strip a couple html tags used inside docstrings in the C++ source
to produce something more easily read as plain text. to produce something more easily read as plain text.
...@@ -49,8 +49,7 @@ def striphtmltags(s): ...@@ -49,8 +49,7 @@ def striphtmltags(s):
s = s.replace('<i>', '_').replace('</i>', '_') s = s.replace('<i>', '_').replace('</i>', '_')
s = s.replace('<b>', '*').replace('</b>', '*') s = s.replace('<b>', '*').replace('</b>', '*')
s = re.sub('\s*(<ul>.*</ul>\s*)', replace_ul_tags, s, flags=re.MULTILINE | re.DOTALL) s = re.sub('\s*(<ul>.*?</ul>\s*)', replace_ul_tags, s, flags=re.MULTILINE | re.DOTALL)
return s return s
def trimToSingleSpace(text): def trimToSingleSpace(text):
...@@ -146,6 +145,20 @@ def getClassMethodList(classNode, skipMethods): ...@@ -146,6 +145,20 @@ def getClassMethodList(classNode, skipMethods):
return methodList return methodList
def docstringTypemap(cpptype):
"""Translate a C++ type to Python for inclusion in the Python docstrings.
This doesn't need to be perfectly accurate -- it's not used for generating
the actual swig wrapper code. It's only used for generating the docstrings.
"""
pytype = cpptype
if pytype.startswith('const '):
pytype = pytype[6:]
if pytype.startswith('std::'):
pytype = pytype[5:]
pytype = pytype.strip('&')
return pytype.strip()
class SwigInputBuilder: class SwigInputBuilder:
def __init__(self, def __init__(self,
inputDirname, inputDirname,
...@@ -157,7 +170,7 @@ class SwigInputBuilder: ...@@ -157,7 +170,7 @@ class SwigInputBuilder:
skipAdditionalMethods=[], skipAdditionalMethods=[],
SWIG_VERSION='3.0.2'): SWIG_VERSION='3.0.2'):
self.nodeByID={} self.nodeByID={}
self.SWIG_COMPACT_ARGUMENTS = LooseVersion(SWIG_VERSION) < LooseVersion('3.0.6') self.SWIG_COMPACT_ARGUMENTS = LooseVersion(SWIG_VERSION) < LooseVersion('3.0.5')
self.configModule = __import__(os.path.splitext(configFilename)[0]) self.configModule = __import__(os.path.splitext(configFilename)[0])
...@@ -243,23 +256,43 @@ class SwigInputBuilder: ...@@ -243,23 +256,43 @@ class SwigInputBuilder:
forceSubclassList.append(shortClassName) forceSubclassList.append(shortClassName)
elif baseName == 'OpenMM::Integrator': elif baseName == 'OpenMM::Integrator':
integratorSubclassList.append(shortClassName) integratorSubclassList.append(shortClassName)
self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce") self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce")
for name in sorted(forceSubclassList): for name in sorted(forceSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Force* OpenMM::Force::__copy__")
for name in sorted(forceSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Force* OpenMM_XmlSerializer__deserializeForce") self.fOut.write("%factory(OpenMM::Force* OpenMM_XmlSerializer__deserializeForce")
for name in sorted(forceSubclassList): for name in sorted(forceSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Integrator* OpenMM::Integrator::__copy__")
for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Integrator* OpenMM_XmlSerializer__deserializeIntegrator") self.fOut.write("%factory(OpenMM::Integrator* OpenMM_XmlSerializer__deserializeIntegrator")
for name in sorted(integratorSubclassList): for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Integrator& OpenMM::Context::getIntegrator") self.fOut.write("%factory(OpenMM::Integrator& OpenMM::Context::getIntegrator")
for name in sorted(integratorSubclassList): for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::VirtualSite& OpenMM::System::getVirtualSite, OpenMM::TwoParticleAverageSite, OpenMM::ThreeParticleAverageSite, OpenMM::OutOfPlaneSite);\n\n")
self.fOut.write("%factory(OpenMM::Integrator& OpenMM::CompoundIntegrator::getIntegrator")
for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::VirtualSite& OpenMM::System::getVirtualSite, OpenMM::TwoParticleAverageSite, OpenMM::ThreeParticleAverageSite, OpenMM::OutOfPlaneSite, OpenMM::LocalCoordinatesSite);\n\n")
self.fOut.write("\n") self.fOut.write("\n")
def writeGlobalConstants(self): def writeGlobalConstants(self):
...@@ -345,7 +378,6 @@ class SwigInputBuilder: ...@@ -345,7 +378,6 @@ class SwigInputBuilder:
self.fOut.write("\n%s};\n" % INDENT) self.fOut.write("\n%s};\n" % INDENT)
if len(enumNodes)>0: self.fOut.write("\n") if len(enumNodes)>0: self.fOut.write("\n")
def writeMethods(self, classNode): def writeMethods(self, classNode):
methodList=getClassMethodList(classNode, self.skipMethods) methodList=getClassMethodList(classNode, self.skipMethods)
...@@ -432,35 +464,44 @@ class SwigInputBuilder: ...@@ -432,35 +464,44 @@ class SwigInputBuilder:
(shortClassName, memberNode, (shortClassName, memberNode,
shortMethDefinition, methName, shortMethDefinition, methName,
isConstructors, isDestructor, templateType, templateName) = items isConstructors, isDestructor, templateType, templateName) = items
paramList=findNodes(memberNode, 'param') paramList = findNodes(memberNode, 'param')
#write pythonprepend blocks # write pythonprepend blocks
mArgsstring = getText("argsstring", memberNode) mArgsstring = getText("argsstring", memberNode)
if self.fOutPythonprepend and \ if self.fOutPythonprepend and \
len(paramList) and \ len(paramList) and \
mArgsstring.find('=0')<0: mArgsstring.find('=0') < 0:
key=(shortClassName, methName) text = '''
if key in self.configModule.STEAL_OWNERSHIP: %pythonprepend OpenMM::{shortClassName}::{methName}{mArgsstring} %{{{{{{0}}
for argNum in self.configModule.STEAL_OWNERSHIP[key]: %}}}}'''.format(shortClassName=shortClassName, methName=methName, mArgsstring=mArgsstring)
if self.SWIG_COMPACT_ARGUMENTS: textInside = ''
argName = 'args[%s]' % argNum key = (shortClassName, methName)
else: for argNum in self.configModule.STEAL_OWNERSHIP.get(key, []):
argName = getText('declname', paramList[argNum]) if self.SWIG_COMPACT_ARGUMENTS:
argName = 'args[%s]' % argNum
else:
argName = getText('declname', paramList[argNum])
text = ''' textInside += '''
%pythonprepend OpenMM::{shortClassName}::{methName}{mArgsstring} %{{
if not {argName}.thisown: if not {argName}.thisown:
s = ("the %s object does not own its corresponding OpenMM object" s = ("the %s object does not own its corresponding OpenMM object"
% self.__class__.__name__) % self.__class__.__name__)
raise Exception(s) raise Exception(s)'''.format(argName=argName)
%}}'''.format(argName=argName, shortClassName=shortClassName, methName=methName, mArgsstring=mArgsstring) for argNum in self.configModule.REQUIRE_ORDERED_SET.get(key, []):
self.fOutPythonprepend.write(text) if self.SWIG_COMPACT_ARGUMENTS:
argName = 'args[%s]' % argNum
else:
argName = getText('declname', paramList[argNum])
textInside += '''
{argName} = list({argName})'''.format(argName=argName)
if textInside:
self.fOutPythonprepend.write(text.format(textInside))
#write pythonappend blocks # write pythonappend blocks
if self.fOutPythonappend \ if self.fOutPythonappend \
and mArgsstring.find('=0')<0: and mArgsstring.find('=0') < 0:
key=(shortClassName, methName) key = (shortClassName, methName)
#print "key %s %s \n" % (shortClassName, methName) #print "key %s %s \n" % (shortClassName, methName)
addText='' addText=''
returnType = getText("type", memberNode) returnType = getText("type", memberNode)
...@@ -477,7 +518,7 @@ class SwigInputBuilder: ...@@ -477,7 +518,7 @@ class SwigInputBuilder:
valueUnits=[None, ()] valueUnits=[None, ()]
index=0 index=0
if valueUnits[0]: if valueUnits[0] is not None:
sys.stdout.write("%s.%s() returns %s\n" % sys.stdout.write("%s.%s() returns %s\n" %
(shortClassName, methName, valueUnits[0])) (shortClassName, methName, valueUnits[0]))
if len(valueUnits[1])>0: if len(valueUnits[1])>0:
...@@ -491,10 +532,10 @@ class SwigInputBuilder: ...@@ -491,10 +532,10 @@ class SwigInputBuilder:
% (addText, INDENT, valueUnits[0]) % (addText, INDENT, valueUnits[0])
for vUnit in valueUnits[1]: for vUnit in valueUnits[1]:
if vUnit!=None: if vUnit is not None:
addText = "%s%sval[%s]=unit.Quantity(val[%s], %s)\n" \ addText = "%s%sval[%s]=unit.Quantity(val[%s], %s)\n" \
% (addText, INDENT, index, index, vUnit) % (addText, INDENT, index, index, vUnit)
index+=1 index+=1
if key in self.configModule.STEAL_OWNERSHIP: if key in self.configModule.STEAL_OWNERSHIP:
for argNum in self.configModule.STEAL_OWNERSHIP[key]: for argNum in self.configModule.STEAL_OWNERSHIP[key]:
...@@ -548,32 +589,58 @@ class SwigInputBuilder: ...@@ -548,32 +589,58 @@ class SwigInputBuilder:
(shortClassName, memberNode, (shortClassName, memberNode,
shortMethDefinition, methName, shortMethDefinition, methName,
isConstructors, isDestructor, templateType, templateName ) = items isConstructors, isDestructor, templateType, templateName ) = items
if self.fOutDocstring: if self.fOutDocstring:
for dNode in findNodes(memberNode, 'detaileddescription'): signatureParams = findNodes(memberNode, 'param')
dString="" assert len(findNodes(memberNode, 'detaileddescription')) == 1
try: dNode = findNodes(memberNode, 'detaileddescription')[0]
description=getText('para', dNode)
description.strip() try:
if description: description=getText('para', dNode)
dString=description description.strip()
except IndexError: except IndexError:
pass description = ''
params = findNodes(dNode, 'para/parameterlist/parameteritem') params = findNodes(dNode, 'para/parameterlist/parameteritem')
if len(params) > 0:
dString="%s\n Parameters:" % dString paramString = ['Parameters', '----------']
for pNode in params: returnString = ['Returns', '-------']
argName = getText('parameternamelist/parametername', pNode)
if len(params) > 0:
if len(signatureParams) != len(params):
raise ValueError('docstring in %s.%s does not match the signature' % (shortClassName, methName))
for pNode, pSignatureNode in zip(params, signatureParams):
parameterNameNode = findNodes(pNode, 'parameternamelist/parametername')[0]
argDoc = getText('parameterdescription/para', pNode) argDoc = getText('parameterdescription/para', pNode)
dString="%s\n - %s %s" % (dString, argName, argDoc) argName = getNodeText(parameterNameNode)
dString.strip() argType = docstringTypemap(getText('type', pSignatureNode))
if dString:
dString=re.sub(r'([^\\])"', r'\g<1>\"', dString)
s = '%%feature("docstring") OpenMM::%s::%s "%s";' \
% (shortClassName, methName, dString)
self.fOutDocstring.write("%s\n" % s)
self.fOutDocstring.write("\n\n")
#print "Done write Docstring info\n"
isOutput = parameterNameNode.get('direction') == 'out'
if isOutput:
returnString.extend(['%s : %s' % (argName, argType), ' %s' % argDoc])
else:
paramString.extend(['%s : %s' % (argName, argType), ' %s' % argDoc])
returnSection = findNodes(dNode, 'para/simplesect')
if len(returnSection) > 0:
returnNode = returnSection[0]
if returnNode.get('kind') == 'return':
argType = getNodeText(findNodes(memberNode, 'type')[0])
argType = docstringTypemap(argType)
returnString.extend([argType, ' %s' % getNodeText(returnNode).strip()])
dString = '\n'.join(
([description] + [''] if len(description) > 0 else []) +
(paramString + [''] if len(paramString) > 2 else []) +
(returnString if len(returnString) > 2 else [])).strip()
if dString:
dString = re.sub(r'([^\\])"', r'\g<1>\"', dString)
s = '%%feature("docstring") OpenMM::%s::%s "%s";' \
% (shortClassName, methName, dString)
self.fOutDocstring.write("%s\n" % s)
self.fOutDocstring.write("\n\n")
def writeSwigFile(self): def writeSwigFile(self):
......
...@@ -43,7 +43,6 @@ SKIP_METHODS = [('State',), ...@@ -43,7 +43,6 @@ SKIP_METHODS = [('State',),
('CalcCustomTorsionForceKernel',), ('CalcCustomTorsionForceKernel',),
('CalcForcesAndEnergyKernel',), ('CalcForcesAndEnergyKernel',),
('CalcGBSAOBCForceKernel',), ('CalcGBSAOBCForceKernel',),
('CalcGBVIForceKernel',),
('CalcHarmonicAngleForceKernel',), ('CalcHarmonicAngleForceKernel',),
('CalcHarmonicBondForceKernel',), ('CalcHarmonicBondForceKernel',),
('CalcKineticEnergyKernel',), ('CalcKineticEnergyKernel',),
...@@ -141,8 +140,15 @@ STEAL_OWNERSHIP = {("Platform", "registerPlatform") : [0], ...@@ -141,8 +140,15 @@ STEAL_OWNERSHIP = {("Platform", "registerPlatform") : [0],
("CustomHbondForce", "addTabulatedFunction") : [1], ("CustomHbondForce", "addTabulatedFunction") : [1],
("CustomCompoundBondForce", "addTabulatedFunction") : [1], ("CustomCompoundBondForce", "addTabulatedFunction") : [1],
("CustomManyParticleForce", "addTabulatedFunction") : [1], ("CustomManyParticleForce", "addTabulatedFunction") : [1],
("CompoundIntegrator", "addIntegrator") : [0],
} }
REQUIRE_ORDERED_SET = {("CustomNonbondedForce", "addInteractionGroup") : [0, 1],
("CustomNonbondedForce", "setInteractionGroupParameters") : [1, 2],
}
# This is a list of units to attach to return values and method args. # This is a list of units to attach to return values and method args.
# Indexed by (ClassName, MethodsName) # Indexed by (ClassName, MethodsName)
UNITS = { UNITS = {
...@@ -216,22 +222,22 @@ UNITS = { ...@@ -216,22 +222,22 @@ UNITS = {
("AmoebaGeneralizedKirkwoodForce", "getDielectricOffset") : ( 'unit.nanometer', ()), ("AmoebaGeneralizedKirkwoodForce", "getDielectricOffset") : ( 'unit.nanometer', ()),
("AmoebaGeneralizedKirkwoodForce", "getIncludeCavityTerm") : ( None,()), ("AmoebaGeneralizedKirkwoodForce", "getIncludeCavityTerm") : ( None,()),
("AmoebaGeneralizedKirkwoodForce", "getProbeRadius") : ( 'unit.nanometer', ()), ("AmoebaGeneralizedKirkwoodForce", "getProbeRadius") : ( 'unit.nanometer', ()),
("AmoebaGeneralizedKirkwoodForce", "getSurfaceAreaFactor") : ( '(unit.nanometer*unit.nanometer)/unit.kilojoule_per_mole',()), ("AmoebaGeneralizedKirkwoodForce", "getSurfaceAreaFactor") : ( 'unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)',()),
("AmoebaAngleForce", "getAmoebaGlobalAngleCubic") : ( None,()), ("AmoebaAngleForce", "getAmoebaGlobalAngleCubic") : ( '1/unit.radian',()),
("AmoebaAngleForce", "getAmoebaGlobalAngleQuartic") : ( None,()), ("AmoebaAngleForce", "getAmoebaGlobalAngleQuartic") : ( '1/unit.radian**2',()),
("AmoebaAngleForce", "getAmoebaGlobalAnglePentic") : ( None,()), ("AmoebaAngleForce", "getAmoebaGlobalAnglePentic") : ( '1/unit.radian**3',()),
("AmoebaAngleForce", "getAmoebaGlobalAngleSextic") : ( None,()), ("AmoebaAngleForce", "getAmoebaGlobalAngleSextic") : ( '1/unit.radian**4',()),
("AmoebaAngleForce", "getAngleParameters") : ( None, (None, None, None, 'unit.radian', 'unit.kilojoule_per_mole/(unit.radian*unit.radian)')), ("AmoebaAngleForce", "getAngleParameters") : ( None, (None, None, None, 'unit.degree', 'unit.kilojoule_per_mole/(unit.radian*unit.radian)')),
("AmoebaBondForce", "getAmoebaGlobalBondCubic") : ( None,()), ("AmoebaBondForce", "getAmoebaGlobalBondCubic") : ( '1/unit.nanometer',()),
("AmoebaBondForce", "getAmoebaGlobalBondQuartic") : ( None,()), ("AmoebaBondForce", "getAmoebaGlobalBondQuartic") : ( '1/unit.nanometer**2',()),
("AmoebaBondForce", "getBondParameters") : ( None, (None, None, 'unit.nanometer', 'unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)')), ("AmoebaBondForce", "getBondParameters") : ( None, (None, None, 'unit.nanometer', 'unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)')),
("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAngleCubic") : ( None,()), ("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAngleCubic") : ( '1/unit.radian',()),
("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAngleQuartic") : ( None,()), ("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAngleQuartic") : ( '1/unit.radian**2',()),
("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAnglePentic") : ( None,()), ("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAnglePentic") : ( '1/unit.radian**3',()),
("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAngleSextic") : ( None,()), ("AmoebaInPlaneAngleForce", "getAmoebaGlobalInPlaneAngleSextic") : ( '1/unit.radian**4',()),
("AmoebaInPlaneAngleForce", "getAngleParameters") : ( None, (None, None, None, None, 'unit.radian', 'unit.kilojoule_per_mole/(unit.radian*unit.radian)')), ("AmoebaInPlaneAngleForce", "getAngleParameters") : ( None, (None, None, None, None, 'unit.radian', 'unit.kilojoule_per_mole/(unit.radian*unit.radian)')),
("AmoebaMultipoleForce", "getNumMultipoles") : ( None,()), ("AmoebaMultipoleForce", "getNumMultipoles") : ( None,()),
...@@ -257,7 +263,9 @@ UNITS = { ...@@ -257,7 +263,9 @@ UNITS = {
# void getCovalentMap(int index, CovalentType typeId, std::vector<int>& covalentAtoms ) # void getCovalentMap(int index, CovalentType typeId, std::vector<int>& covalentAtoms )
# void getCovalentMaps(int index, std::vector < std::vector<int> >& covalentLists ) # void getCovalentMaps(int index, std::vector < std::vector<int> >& covalentLists )
("AmoebaMultipoleForce", "getMultipoleParameters") : ( None, ()), ("AmoebaMultipoleForce", "getMultipoleParameters") : ( None, ('unit.elementary_charge', 'unit.elementary_charge*unit.nanometer',
'unit.elementary_charge*unit.nanometer**2', None, None, None, None, None, None,
'unit.nanometer**3')),
("AmoebaMultipoleForce", "getCovalentMap") : ( None, ()), ("AmoebaMultipoleForce", "getCovalentMap") : ( None, ()),
("AmoebaMultipoleForce", "getCovalentMaps") : ( None, ()), ("AmoebaMultipoleForce", "getCovalentMaps") : ( None, ()),
("AmoebaMultipoleForce", "getScalingDistanceCutoff") : ( 'unit.nanometer', ()), ("AmoebaMultipoleForce", "getScalingDistanceCutoff") : ( 'unit.nanometer', ()),
...@@ -269,17 +277,17 @@ UNITS = { ...@@ -269,17 +277,17 @@ UNITS = {
("AmoebaMultipoleForce", "getSystemMultipoleMoments") : ( None, ()), ("AmoebaMultipoleForce", "getSystemMultipoleMoments") : ( None, ()),
("AmoebaOutOfPlaneBendForce", "getNumOutOfPlaneBends") : ( None, ()), ("AmoebaOutOfPlaneBendForce", "getNumOutOfPlaneBends") : ( None, ()),
("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendCubic") : ( None,()), ("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendCubic") : ( '1/unit.radian',()),
("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendQuartic") : ( None,()), ("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendQuartic") : ( '1/unit.radian**2',()),
("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendPentic") : ( None,()), ("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendPentic") : ( '1/unit.radian**3',()),
("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendSextic") : ( None,()), ("AmoebaOutOfPlaneBendForce", "getAmoebaGlobalOutOfPlaneBendSextic") : ( '1/unit.radian**4',()),
("AmoebaOutOfPlaneBendForce", "getOutOfPlaneBendParameters") : ( None, (None, None, None, None, 'unit.kilojoule_per_mole')), ("AmoebaOutOfPlaneBendForce", "getOutOfPlaneBendParameters") : ( None, (None, None, None, None, 'unit.kilojoule_per_mole/unit.radians**2')),
("AmoebaPiTorsionForce", "getNumPiTorsions") : ( None, ()), ("AmoebaPiTorsionForce", "getNumPiTorsions") : ( None, ()),
("AmoebaPiTorsionForce", "getPiTorsionParameters") : ( None, (None, None, None, None, None, None, 'unit.kilojoule_per_mole')), ("AmoebaPiTorsionForce", "getPiTorsionParameters") : ( None, (None, None, None, None, None, None, 'unit.kilojoule_per_mole')),
("AmoebaStretchBendForce", "getNumStretchBends") : ( None, ()), ("AmoebaStretchBendForce", "getNumStretchBends") : ( None, ()),
("AmoebaStretchBendForce", "getStretchBendParameters") : ( None, (None, None, None, 'unit.nanometer', 'unit.nanometer', 'unit.radian', 'unit.kilojoule_per_mole/unit.nanometer/unit.degree', 'unit.kilojoule_per_mole/unit.nanometer/unit.degree')), ("AmoebaStretchBendForce", "getStretchBendParameters") : ( None, (None, None, None, 'unit.nanometer', 'unit.nanometer', 'unit.radian', 'unit.kilojoule_per_mole/unit.nanometer/unit.radian', 'unit.kilojoule_per_mole/unit.nanometer/unit.radian')),
("AmoebaTorsionTorsionForce", "getNumTorsionTorsions") : ( None, ()), ("AmoebaTorsionTorsionForce", "getNumTorsionTorsions") : ( None, ()),
("AmoebaTorsionTorsionForce", "getNumTorsionTorsionGrids") : ( None, ()), ("AmoebaTorsionTorsionForce", "getNumTorsionTorsionGrids") : ( None, ()),
...@@ -307,6 +315,7 @@ UNITS = { ...@@ -307,6 +315,7 @@ UNITS = {
("AmoebaWcaDispersionForce", "getShctd") : ( None, ()), ("AmoebaWcaDispersionForce", "getShctd") : ( None, ()),
("Context", "getParameter") : (None, ()), ("Context", "getParameter") : (None, ()),
("Context", "getParameters") : (None, ()),
("Context", "getMolecules") : (None, ()), ("Context", "getMolecules") : (None, ()),
("CMAPTorsionForce", "getMapParameters") : (None, (None, 'unit.kilojoule_per_mole')), ("CMAPTorsionForce", "getMapParameters") : (None, (None, 'unit.kilojoule_per_mole')),
("CMAPTorsionForce", "getTorsionParameters") : (None, ()), ("CMAPTorsionForce", "getTorsionParameters") : (None, ()),
...@@ -377,18 +386,13 @@ UNITS = { ...@@ -377,18 +386,13 @@ UNITS = {
("CustomTorsionForce", "getPerTorsionParameterName") : (None, ()), ("CustomTorsionForce", "getPerTorsionParameterName") : (None, ()),
("CustomTorsionForce", "getGlobalParameterName") : (None, ()), ("CustomTorsionForce", "getGlobalParameterName") : (None, ()),
("CustomTorsionForce", "getTorsionParameters") : (None, ()), ("CustomTorsionForce", "getTorsionParameters") : (None, ()),
("DrudeForce", "getParticleParameters") : (None, (None, None, None, None, None, 'unit.elementary_charge', 'unit.nanometer**3', None, None)),
("DrudeForce", "getNumScreenedPairs") : (None, ()),
("DrudeForce", "getScreenedPairParameters") : (None, ()),
("GBSAOBCForce", "getParticleParameters") ("GBSAOBCForce", "getParticleParameters")
: (None, ('unit.elementary_charge', : (None, ('unit.elementary_charge',
'unit.nanometer', None)), 'unit.nanometer', None)),
("GBSAOBCForce", "getSurfaceAreaEnergy") : ('unit.kilojoule_per_mole/unit.nanometer/unit.nanometer', ()), ("GBSAOBCForce", "getSurfaceAreaEnergy") : ('unit.kilojoule_per_mole/unit.nanometer/unit.nanometer', ()),
("GBVIForce", "getBornRadiusScalingMethod") : (None, ()),
("GBVIForce", "getQuinticLowerLimitFactor") : (None, ()),
("GBVIForce", "getQuinticUpperBornRadiusLimit") : ('unit.nanometer', ()),
("GBVIForce", "getBondParameters")
: (None, (None, None, 'unit.nanometer')),
("GBVIForce", "getParticleParameters")
: (None, ('unit.elementary_charge',
'unit.nanometer', 'unit.kilojoule_per_mole')),
("HarmonicAngleForce", "getAngleParameters") ("HarmonicAngleForce", "getAngleParameters")
: (None, (None, None, None, 'unit.radian', : (None, (None, None, None, 'unit.radian',
'unit.kilojoule_per_mole/(unit.radian*unit.radian)')), 'unit.kilojoule_per_mole/(unit.radian*unit.radian)')),
...@@ -421,6 +425,7 @@ UNITS = { ...@@ -421,6 +425,7 @@ UNITS = {
("System", "getForce") : (None, ()), ("System", "getForce") : (None, ()),
("System", "getVirtualSite") : (None, ()), ("System", "getVirtualSite") : (None, ()),
("DrudeLangevinIntegrator", "getDrudeTemperature") : ("unit.kelvin", ()), ("DrudeLangevinIntegrator", "getDrudeTemperature") : ("unit.kelvin", ()),
("DrudeLangevinIntegrator", "getMaxDrudeDistance") : ("unit.nanometer", ()),
("MonteCarloMembraneBarostat", "getXYMode") : (None, ()), ("MonteCarloMembraneBarostat", "getXYMode") : (None, ()),
("MonteCarloMembraneBarostat", "getZMode") : (None, ()), ("MonteCarloMembraneBarostat", "getZMode") : (None, ()),
("DrudeLangevinIntegrator", "getDrudeFriction") : ("1/unit.picosecond", ()), ("DrudeLangevinIntegrator", "getDrudeFriction") : ("1/unit.picosecond", ()),
......
%inline %{
typedef int bitmask32t;
%}
%typemap(in) bitmask32t %{
$1 = 0;
#if PY_VERSION_HEX >= 0x03000000
if (PyLong_Check($input)) {
unsigned long u = PyLong_AsUnsignedLongMask($input);
#else
if (PyInt_Check($input)) {
unsigned long u = PyInt_AsUnsignedLongMask($input);
#endif
// 64-bit Windows has 32-bit longs, but other platforms have
// 64-bit longs
$1 = u & 0xffffffff;
} else {
PyErr_SetString(PyExc_ValueError, "in method $symname, argument $argnum could not be converted to type $type");
SWIG_fail;
}
%}
%extend OpenMM::Context { %extend OpenMM::Context {
PyObject *_getStateAsLists(int getPositions, PyObject *_getStateAsLists(int getPositions,
int getVelocities, int getVelocities,
...@@ -5,7 +29,7 @@ ...@@ -5,7 +29,7 @@
int getEnergy, int getEnergy,
int getParameters, int getParameters,
int enforcePeriodic, int enforcePeriodic,
int groups) { bitmask32t groups) {
State state; State state;
PyThreadState* _savePythonThreadState = PyEval_SaveThread(); PyThreadState* _savePythonThreadState = PyEval_SaveThread();
int types = 0; int types = 0;
...@@ -27,54 +51,53 @@ ...@@ -27,54 +51,53 @@
%pythoncode %{ %pythoncode %{
def getState(self, def getState(self, getPositions=False, getVelocities=False,
getPositions=False, getForces=False, getEnergy=False, getParameters=False,
getVelocities=False, enforcePeriodicBox=False, groups=-1):
getForces=False, """Get a State object recording the current state information stored in this context.
getEnergy=False,
getParameters=False, Parameters
enforcePeriodicBox=False, ----------
groups=-1): getPositions : bool=False
""" whether to store particle positions in the State
getState(self, getVelocities : bool=False
getPositions = False, whether to store particle velocities in the State
getVelocities = False, getForces : bool=False
getForces = False, whether to store the forces acting on particles in the State
getEnergy = False, getEnergy : bool=False
getParameters = False, whether to store potential and kinetic energy in the State
enforcePeriodicBox = False, getParameter : bool=False
groups = -1) whether to store context parameters in the State
-> State enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position
Get a State object recording the current state information stored in this context. is stored in the Context, regardless of periodic boundary conditions.
If true, particle positions will be translated so the center of
Parameters: every molecule lies in the same periodic box.
- getPositions (bool=False) whether to store particle positions in the State groups : set={0,1,2,...,31}
- getVelocities (bool=False) whether to store particle velocities in the State a set of indices for which force groups to include when computing
- getForces (bool=False) whether to store the forces acting on particles in the State forces and energies. The default value includes all groups. groups
- getEnergy (bool=False) whether to store potential and kinetic energy in the State can also be passed as an unsigned integer interpreted as a bitmask,
- getParameter (bool=False) whether to store context parameters in the State in which case group i will be included if (groups&(1<<i)) != 0.
- enforcePeriodicBox (bool=False) if false, the position of each particle will be whatever position is stored in the Context, regardless of periodic boundary conditions. If true, particle positions will be translated so the center of every molecule lies in the same periodic box.
- groups (int=-1) a set of bit flags for which force groups to include when computing forces and energies. Group i will be included if (groups&(1<<i)) != 0. The default value includes all groups.
""" """
getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
if getPositions: getP=1 (getPositions, getVelocities, getForces, getEnergy, getParameters,
else: getP=0 enforcePeriodicBox))
if getVelocities: getV=1
else: getV=0 try:
if getForces: getF=1 # is the input integer-like?
else: getF=0 groups_mask = int(groups)
if getEnergy: getE=1 except TypeError:
else: getE=0 if isinstance(groups, set):
if getParameters: getPa=1 # nope, okay, then it should be an set
else: getPa=0 groups_mask = functools.reduce(operator.or_,
if enforcePeriodicBox: enforcePeriodic=1 ((1<<x) & 0xffffffff for x in groups))
else: enforcePeriodic=0 else:
raise TypeError('%s is neither an int nor set' % groups)
(simTime, periodicBoxVectorsList, energy, coordList, velList, (simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \ forceList, paramMap) = \
self._getStateAsLists(getP, getV, getF, getE, getPa, enforcePeriodic, groups) self._getStateAsLists(getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
state = State(simTime=simTime, state = State(simTime=simTime,
energy=energy, energy=energy,
coordList=coordList, coordList=coordList,
...@@ -83,11 +106,11 @@ ...@@ -83,11 +106,11 @@
periodicBoxVectorsList=periodicBoxVectorsList, periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap) paramMap=paramMap)
return state return state
def setState(self, state): def setState(self, state):
""" """
setState(Context self, State state) setState(Context self, State state)
Copy information from a State object into this Context. This restores the Context to Copy information from a State object into this Context. This restores the Context to
approximately the same state it was in when the State was created. If the State does not include approximately the same state it was in when the State was created. If the State does not include
a piece of information (e.g. positions or velocities), that aspect of the Context is a piece of information (e.g. positions or velocities), that aspect of the Context is
...@@ -108,7 +131,7 @@ ...@@ -108,7 +131,7 @@
for param in state._paramMap: for param in state._paramMap:
self.setParameter(param, state._paramMap[param]) self.setParameter(param, state._paramMap[param])
%} %}
%feature("docstring") createCheckpoint "Create a checkpoint recording the current state of the Context. %feature("docstring") createCheckpoint "Create a checkpoint recording the current state of the Context.
This should be treated as an opaque block of binary data. See loadCheckpoint() for more details. This should be treated as an opaque block of binary data. See loadCheckpoint() for more details.
...@@ -185,48 +208,51 @@ Parameters: ...@@ -185,48 +208,51 @@ Parameters:
getParameters=False, getParameters=False,
enforcePeriodicBox=False, enforcePeriodicBox=False,
groups=-1): groups=-1):
"""Get a State object recording the current state information about one copy of the system.
Parameters
----------
copy : int
the index of the copy for which to retrieve state information
getPositions : bool=False
whether to store particle positions in the State
getVelocities : bool=False
whether to store particle velocities in the State
getForces : bool=False
whether to store the forces acting on particles in the State
getEnergy : bool=False
whether to store potential and kinetic energy in the State
getParameter : bool=False
whether to store context parameters in the State
enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position
is stored in the Context, regardless of periodic boundary conditions.
If true, particle positions will be translated so the center of
every molecule lies in the same periodic box.
groups : set={0,1,2,...,31}
a set of indices for which force groups to include when computing
forces and energies. The default value includes all groups. groups
can also be passed as an unsigned integer interpreted as a bitmask,
in which case group i will be included if (groups&(1<<i)) != 0.
""" """
getState(self, getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
copy, (getPositions, getVelocities, getForces, getEnergy, getParameters,
getPositions = False, enforcePeriodicBox))
getVelocities = False,
getForces = False, try:
getEnergy = False, # is the input integer-like?
getParameters = False, groups_mask = int(groups)
enforcePeriodicBox = False, except TypeError:
groups = -1) if isinstance(groups, set):
-> State groups_mask = functools.reduce(operator.or_,
((1<<x) & 0xffffffff for x in groups))
Get a State object recording the current state information about one copy of the system. else:
raise TypeError('%s is neither an int nor set' % groups)
Parameters:
- copy (int) the index of the copy for which to retrieve state information
- getPositions (bool=False) whether to store particle positions in the State
- getVelocities (bool=False) whether to store particle velocities in the State
- getForces (bool=False) whether to store the forces acting on particles in the State
- getEnergy (bool=False) whether to store potential and kinetic energy in the State
- getParameter (bool=False) whether to store context parameters in the State
- enforcePeriodicBox (bool=False) if false, the position of each particle will be whatever position is stored in the Context, regardless of periodic boundary conditions. If true, particle positions will be translated so the center of every molecule lies in the same periodic box.
- groups (int=-1) a set of bit flags for which force groups to include when computing forces and energies. Group i will be included if (groups&(1<<i)) != 0. The default value includes all groups.
"""
if getPositions: getP=1
else: getP=0
if getVelocities: getV=1
else: getV=0
if getForces: getF=1
else: getF=0
if getEnergy: getE=1
else: getE=0
if getParameters: getPa=1
else: getPa=0
if enforcePeriodicBox: enforcePeriodic=1
else: enforcePeriodic=0
(simTime, periodicBoxVectorsList, energy, coordList, velList, (simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \ forceList, paramMap) = \
self._getStateAsLists(copy, getP, getV, getF, getE, getPa, enforcePeriodic, groups) self._getStateAsLists(copy, getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
state = State(simTime=simTime, state = State(simTime=simTime,
energy=energy, energy=energy,
coordList=coordList, coordList=coordList,
...@@ -299,7 +325,7 @@ Parameters: ...@@ -299,7 +325,7 @@ Parameters:
ss << inputString; ss << inputString;
return OpenMM::XmlSerializer::deserialize<OpenMM::System>(ss); return OpenMM::XmlSerializer::deserialize<OpenMM::System>(ss);
} }
static std::string _serializeForce(const OpenMM::Force* object) { static std::string _serializeForce(const OpenMM::Force* object) {
std::stringstream ss; std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::Force>(object, "Force", ss); OpenMM::XmlSerializer::serialize<OpenMM::Force>(object, "Force", ss);
...@@ -312,7 +338,7 @@ Parameters: ...@@ -312,7 +338,7 @@ Parameters:
ss << inputString; ss << inputString;
return OpenMM::XmlSerializer::deserialize<OpenMM::Force>(ss); return OpenMM::XmlSerializer::deserialize<OpenMM::Force>(ss);
} }
static std::string _serializeIntegrator(const OpenMM::Integrator* object) { static std::string _serializeIntegrator(const OpenMM::Integrator* object) {
std::stringstream ss; std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::Integrator>(object, "Integrator", ss); OpenMM::XmlSerializer::serialize<OpenMM::Integrator>(object, "Integrator", ss);
...@@ -327,8 +353,8 @@ Parameters: ...@@ -327,8 +353,8 @@ Parameters:
} }
static std::string _serializeStateAsLists( static std::string _serializeStateAsLists(
const std::vector<Vec3>& pos, const std::vector<Vec3>& pos,
const std::vector<Vec3>& vel, const std::vector<Vec3>& vel,
const std::vector<Vec3>& forces, const std::vector<Vec3>& forces,
double kineticEnergy, double kineticEnergy,
double potentialEnergy, double potentialEnergy,
...@@ -341,7 +367,7 @@ Parameters: ...@@ -341,7 +367,7 @@ Parameters:
OpenMM::XmlSerializer::serialize<OpenMM::State>(&myState, "State", buffer); OpenMM::XmlSerializer::serialize<OpenMM::State>(&myState, "State", buffer);
return buffer.str(); return buffer.str();
} }
static PyObject* _deserializeStringIntoLists(const std::string &stateAsString) { static PyObject* _deserializeStringIntoLists(const std::string &stateAsString) {
std::stringstream ss; std::stringstream ss;
ss << stateAsString; ss << stateAsString;
...@@ -369,7 +395,7 @@ Parameters: ...@@ -369,7 +395,7 @@ Parameters:
try: try:
velocities = pythonState.getVelocities().value_in_unit(unit.nanometers/unit.picoseconds) velocities = pythonState.getVelocities().value_in_unit(unit.nanometers/unit.picoseconds)
types |= 2 types |= 2
except: except:
pass pass
try: try:
forces = pythonState.getForces().value_in_unit(unit.kilojoules_per_mole/unit.nanometers) forces = pythonState.getForces().value_in_unit(unit.kilojoules_per_mole/unit.nanometers)
...@@ -390,14 +416,14 @@ Parameters: ...@@ -390,14 +416,14 @@ Parameters:
time = pythonState.getTime().value_in_unit(unit.picoseconds) time = pythonState.getTime().value_in_unit(unit.picoseconds)
boxVectors = pythonState.getPeriodicBoxVectors().value_in_unit(unit.nanometers) boxVectors = pythonState.getPeriodicBoxVectors().value_in_unit(unit.nanometers)
string = XmlSerializer._serializeStateAsLists(positions, velocities, forces, kineticEnergy, potentialEnergy, time, boxVectors, params, types) string = XmlSerializer._serializeStateAsLists(positions, velocities, forces, kineticEnergy, potentialEnergy, time, boxVectors, params, types)
return string return string
@staticmethod @staticmethod
def _deserializeState(pythonString): def _deserializeState(pythonString):
(simTime, periodicBoxVectorsList, energy, coordList, velList, (simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = XmlSerializer._deserializeStringIntoLists(pythonString) forceList, paramMap) = XmlSerializer._deserializeStringIntoLists(pythonString)
state = State(simTime=simTime, state = State(simTime=simTime,
energy=energy, energy=energy,
coordList=coordList, coordList=coordList,
...@@ -450,6 +476,14 @@ Parameters: ...@@ -450,6 +476,14 @@ Parameters:
%extend OpenMM::Force { %extend OpenMM::Force {
%pythoncode %{ %pythoncode %{
def __getstate__(self):
serializationString = XmlSerializer.serialize(self)
return serializationString
def __setstate__(self, serializationString):
system = XmlSerializer.deserialize(serializationString)
self.this = system.this
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
return self.__copy__() return self.__copy__()
%} %}
......
...@@ -2,11 +2,14 @@ ...@@ -2,11 +2,14 @@
try: try:
import numpy import numpy
except: except ImportError:
pass numpy = None
import copy
import sys import sys
import math import math
import functools
import operator
RMIN_PER_SIGMA=math.pow(2, 1/6.0) RMIN_PER_SIGMA=math.pow(2, 1/6.0)
RVDW_PER_SIGMA=math.pow(2, 1/6.0)/2.0 RVDW_PER_SIGMA=math.pow(2, 1/6.0)/2.0
if sys.version_info[0] == 2: if sys.version_info[0] == 2:
...@@ -224,3 +227,70 @@ class State(_object): ...@@ -224,3 +227,70 @@ class State(_object):
self._system = args[0] self._system = args[0]
self._integrator = args[1] self._integrator = args[1]
%} %}
%pythonprepend OpenMM::AmoebaAngleForce::addAngle %{
try:
length = args[3]
if isinstance(args, tuple):
args = list(args)
except (NameError, UnboundLocalError):
if unit.is_quantity(length):
length = length.value_in_unit(unit.degree)
else:
if unit.is_quantity(length):
args[3] = length.value_in_unit(unit.degree)
%}
%pythonprepend OpenMM::AmoebaAngleForce::setAngleParameters %{
try:
length = args[4]
if isinstance(args, tuple):
args = list(args)
except (NameError, UnboundLocalError):
if unit.is_quantity(length):
length = length.value_in_unit(unit.degree)
else:
if unit.is_quantity(length):
args[4] = length.value_in_unit(unit.degree)
%}
%pythonprepend OpenMM::AmoebaTorsionTorsionForce::setTorsionTorsionGrid %{
def deunitize_grid(grid):
if isinstance(grid, tuple):
grid = list(grid)
for i, row in enumerate(grid):
if isinstance(row, tuple):
row = list(row)
grid[i] = row
for i, column in enumerate(row):
if isinstance(column, tuple):
column = list(column)
row[i] = column
# Data is angle, angle, energy, de/dang1, de/dang2, d^2e/dang1dang2
if unit.is_quantity(column[0]):
column[0] = column[0].value_in_unit(unit.degree)
if unit.is_quantity(column[1]):
column[1] = column[1].value_in_unit(unit.degree)
if unit.is_quantity(column[2]):
column[2] = column[2].value_in_unit(unit.kilojoule_per_mole)
if len(column) > 3 and unit.is_quantity(column[3]):
column[3] = column[3].value_in_unit(unit.kilojoule_per_mole/unit.radians)
if len(column) > 4 and unit.is_quantity(column[4]):
column[4] = column[4].value_in_unit(unit.kilojoule_per_mole/unit.radians)
if len(column) > 5 and unit.is_quantity(column[5]):
column[5] = column[5].value_in_unit(unit.kilojoule_per_mole/unit.radians**2)
return grid
try:
grid = copy.deepcopy(args[1])
if isinstance(args, tuple):
args = list(args)
except (NameError, UnboundLocalError):
try:
# Support numpy arrays
grid = grid.tolist()
except AttributeError:
grid = copy.deepcopy(grid)
grid = deunitize_grid(grid)
else:
args[1] = deunitize_grid(grid)
%}
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