Commit 047934e2 authored by Rafal P. Wiewiora's avatar Rafal P. Wiewiora
Browse files

Merge remote-tracking branch 'upstream/master'

parents ce3a5dc0 d12c9bd1
......@@ -36,6 +36,7 @@ from math import sqrt
from simtk.openmm.app import Topology
from simtk.openmm.app import PDBFile
from simtk.openmm.app.internal import amber_file_parser
from simtk.openmm.app.internal.singleton import Singleton
from . import forcefield as ff
from . import element as elem
import simtk.unit as u
......@@ -44,27 +45,27 @@ from simtk.openmm.app.internal.unitcell import computePeriodicBoxVectors
# Enumerated values for implicit solvent model
class HCT(object):
class HCT(Singleton):
def __repr__(self):
return 'HCT'
HCT = HCT()
class OBC1(object):
class OBC1(Singleton):
def __repr__(self):
return 'OBC1'
OBC1 = OBC1()
class OBC2(object):
class OBC2(Singleton):
def __repr__(self):
return 'OBC2'
OBC2 = OBC2()
class GBn(object):
class GBn(Singleton):
def __repr__(self):
return 'GBn'
GBn = GBn()
class GBn2(object):
class GBn2(Singleton):
def __repr__(self):
return 'GBn2'
GBn2 = GBn2()
......@@ -168,7 +169,7 @@ class AmberPrmtopFile(object):
----------
nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME.
nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions
constraints : object=None
......@@ -201,7 +202,7 @@ class AmberPrmtopFile(object):
added to a hydrogen is subtracted from the heavy atom to keep their
total mass the same.
ewaldErrorTolerance : float=0.0005
The error tolerance to use if nonbondedMethod is Ewald or PME.
The error tolerance to use if nonbondedMethod is Ewald, PME, or LJPME.
switchDistance : float=0*nanometers
The distance at which the potential energy switching function is
turned on for Lennard-Jones interactions. If the switchDistance is 0
......@@ -221,10 +222,11 @@ class AmberPrmtopFile(object):
ff.CutoffNonPeriodic:'CutoffNonPeriodic',
ff.CutoffPeriodic:'CutoffPeriodic',
ff.Ewald:'Ewald',
ff.PME:'PME'}
ff.PME:'PME',
ff.LJPME:'LJPME'}
if nonbondedMethod not in methodMap:
raise ValueError('Illegal value for nonbonded method')
if not self._prmtop.getIfBox() and nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME):
if not self._prmtop.getIfBox() and nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME):
raise ValueError('Illegal nonbonded method for a non-periodic system')
constraintMap = {None:None,
ff.HBonds:'h-bonds',
......
......@@ -690,7 +690,7 @@ class CharmmPsfFile(object):
The parameter set to use to parametrize this molecule
nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME.
nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions.
switchDistance : distance=0*nanometer
......@@ -728,7 +728,7 @@ class CharmmPsfFile(object):
added to a hydrogen is subtracted from the heavy atom to keep their
total mass the same.
ewaldErrorTolerance : float=0.0005
The error tolerance to use if the nonbonded method is Ewald or PME.
The error tolerance to use if the nonbonded method is Ewald, PME, or LJPME.
flexibleConstraints : bool=True
Are our constraints flexible or not?
verbose : bool=False
......@@ -746,10 +746,10 @@ class CharmmPsfFile(object):
cutoff = cutoff.value_in_unit(u.nanometers)
if nonbondedMethod not in (ff.NoCutoff, ff.CutoffNonPeriodic,
ff.CutoffPeriodic, ff.Ewald, ff.PME):
ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME):
raise ValueError('Illegal value for nonbonded method')
if not hasbox and nonbondedMethod in (ff.CutoffPeriodic,
ff.Ewald, ff.PME):
ff.Ewald, ff.PME, ff.LJPME):
raise ValueError('Illegal nonbonded method for a '
'non-periodic system')
if implicitSolvent not in (HCT, OBC1, OBC2, GBn, GBn2, None):
......@@ -1009,6 +1009,8 @@ class CharmmPsfFile(object):
force.setNonbondedMethod(mm.NonbondedForce.Ewald)
elif nonbondedMethod is ff.PME:
force.setNonbondedMethod(mm.NonbondedForce.PME)
elif nonbondedMethod is ff.LJPME:
force.setNonbondedMethod(mm.NonbondedForce.LJPME)
else:
raise ValueError('Cutoff method is not understood')
......@@ -1088,8 +1090,7 @@ class CharmmPsfFile(object):
mm.Discrete2DFunction(num_lj_types, num_lj_types, bcoef))
cforce.addPerParticleParameter('type')
cforce.setForceGroup(self.NONBONDED_FORCE_GROUP)
if (nonbondedMethod is ff.PME or nonbondedMethod is ff.Ewald or
nonbondedMethod is ff.CutoffPeriodic):
if (nonbondedMethod in (ff.PME, ff.LJPME, ff.Ewald, ff.CutoffPeriodic)):
cforce.setNonbondedMethod(cforce.CutoffPeriodic)
cforce.setCutoffDistance(nonbondedCutoff)
cforce.setUseLongRangeCorrection(True)
......
......@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2014 Stanford University and the Authors.
Portions copyright (c) 2014-2016 Stanford University and the Authors.
Authors: Robert McGibbon
Contributors:
......@@ -33,6 +33,9 @@ __author__ = "Robert McGibbon"
__version__ = "1.0"
import simtk.openmm as mm
import os
import os.path
__all__ = ['CheckpointReporter']
......@@ -80,12 +83,7 @@ class CheckpointReporter(object):
"""
self._reportInterval = reportInterval
if isinstance(file, str):
self._own_handle = True
self._out = open(file, 'w+b', 0)
else:
self._out = file
self._own_handle = False
self._file = file
def describeNextReport(self, simulation):
"""Get information about the next report this object will generate.
......@@ -116,13 +114,24 @@ class CheckpointReporter(object):
state : State
The current state of the simulation
"""
self._out.seek(0)
chk = simulation.context.createCheckpoint()
self._out.write(chk)
self._out.truncate()
self._out.flush()
def __del__(self):
if self._own_handle:
self._out.close()
if isinstance(self._file, str):
# Do a safe save.
tempFilename1 = self._file+".backup1"
tempFilename2 = self._file+".backup2"
with open(tempFilename1, 'w+b', 0) as out:
out.write(simulation.context.createCheckpoint())
exists = os.path.exists(self._file)
if exists:
os.rename(self._file, tempFilename2)
os.rename(tempFilename1, self._file)
if exists:
os.remove(tempFilename2)
else:
# Replace the contents of the file.
self._file.seek(0)
chk = simulation.context.createCheckpoint()
self._file.write(chk)
self._file.truncate()
self._file.flush()
......@@ -52,8 +52,8 @@ class DCDFile(object):
To use this class, create a DCDFile object, then call writeModel() once for each model in the file."""
def __init__(self, file, topology, dt, firstStep=0, interval=1):
"""Create a DCD file and write out the header.
def __init__(self, file, topology, dt, firstStep=0, interval=1, append=False):
"""Create a DCD file and write out the header, or open an existing file to append.
Parameters
----------
......@@ -68,6 +68,8 @@ class DCDFile(object):
interval : int=1
The frequency (measured in time steps) at which states are written
to the trajectory
append : bool=False
If True, open an existing DCD file to append to. If False, create a new file.
"""
self._file = file
self._topology = topology
......@@ -77,15 +79,24 @@ class DCDFile(object):
if is_quantity(dt):
dt = dt.value_in_unit(picoseconds)
dt /= 0.04888821
self._dt = dt
boxFlag = 0
if topology.getUnitCellDimensions() is not None:
boxFlag = 1
header = struct.pack('<i4c9if', 84, b'C', b'O', b'R', b'D', 0, firstStep, interval, 0, 0, 0, 0, 0, 0, dt)
header += struct.pack('<13i', boxFlag, 0, 0, 0, 0, 0, 0, 0, 0, 24, 84, 164, 2)
header += struct.pack('<80s', b'Created by OpenMM')
header += struct.pack('<80s', b'Created '+time.asctime(time.localtime(time.time())).encode('ascii'))
header += struct.pack('<4i', 164, 4, len(list(topology.atoms())), 4)
file.write(header)
if append:
file.seek(8, os.SEEK_SET)
self._modelCount = struct.unpack('<i', file.read(4))[0]
file.seek(268, os.SEEK_SET)
numAtoms = struct.unpack('<i', file.read(4))[0]
if numAtoms != len(list(topology.atoms())):
raise ValueError('Cannot append to a DCD file that contains a different number of atoms')
else:
header = struct.pack('<i4c9if', 84, b'C', b'O', b'R', b'D', 0, firstStep, interval, 0, 0, 0, 0, 0, 0, dt)
header += struct.pack('<13i', boxFlag, 0, 0, 0, 0, 0, 0, 0, 0, 24, 84, 164, 2)
header += struct.pack('<80s', b'Created by OpenMM')
header += struct.pack('<80s', b'Created '+time.asctime(time.localtime(time.time())).encode('ascii'))
header += struct.pack('<4i', 164, 4, len(list(topology.atoms())), 4)
file.write(header)
def writeModel(self, positions, unitCellDimensions=None, periodicBoxVectors=None):
"""Write out a model to the DCD file.
......@@ -116,9 +127,19 @@ class DCDFile(object):
raise ValueError('Particle position is infinite')
file = self._file
self._modelCount += 1
if self._interval > 1 and self._firstStep+self._modelCount*self._interval > 1<<31:
# This will exceed the range of a 32 bit integer. To avoid crashing or producing a corrupt file,
# update the header to say the trajectory consisted of a smaller number of larger steps (so the
# total trajectory length remains correct).
self._firstStep //= self._interval
self._dt *= self._interval
self._interval = 1
file.seek(0, os.SEEK_SET)
file.write(struct.pack('<i4c9if', 84, b'C', b'O', b'R', b'D', 0, self._firstStep, self._interval, 0, 0, 0, 0, 0, 0, self._dt))
# Update the header.
self._modelCount += 1
file.seek(8, os.SEEK_SET)
file.write(struct.pack('<i', self._modelCount))
file.seek(20, os.SEEK_SET)
......@@ -149,3 +170,7 @@ class DCDFile(object):
data = array.array('f', (10*x[i] for x in positions))
data.tofile(file)
file.write(length)
try:
file.flush()
except AttributeError:
pass
......@@ -42,7 +42,7 @@ class DCDReporter(object):
To use it, create a DCDReporter, then add it to the Simulation's list of reporters.
"""
def __init__(self, file, reportInterval):
def __init__(self, file, reportInterval, append=False):
"""Create a DCDReporter.
Parameters
......@@ -51,9 +51,16 @@ class DCDReporter(object):
The file to write to
reportInterval : int
The interval (in time steps) at which to write frames
append : bool=False
If True, open an existing DCD file to append to. If False, create a new file.
"""
self._reportInterval = reportInterval
self._out = open(file, 'wb')
self._append = append
if append:
mode = 'a+b'
else:
mode = 'wb'
self._out = open(file, mode)
self._dcd = None
def describeNextReport(self, simulation):
......@@ -87,7 +94,7 @@ class DCDReporter(object):
"""
if self._dcd is None:
self._dcd = DCDFile(self._out, simulation.topology, simulation.integrator.getStepSize(), 0, self._reportInterval)
self._dcd = DCDFile(self._out, simulation.topology, simulation.integrator.getStepSize(), 0, self._reportInterval, self._append)
self._dcd.writeModel(state.getPositions(), periodicBoxVectors=state.getPeriodicBoxVectors())
def __del__(self):
......
......@@ -165,11 +165,11 @@ class DesmondDMSFile(object):
----------
nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME.
nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions
ewaldErrorTolerance : float=0.0005
The error tolerance to use if nonbondedMethod is Ewald or PME.
The error tolerance to use if nonbondedMethod is Ewald, PME, or LJPME.
removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None
......@@ -185,7 +185,7 @@ class DesmondDMSFile(object):
boxSize = self.topology.getUnitCellDimensions()
if boxSize is not None:
sys.setDefaultPeriodicBoxVectors((boxSize[0], 0, 0), (0, boxSize[1], 0), (0, 0, boxSize[2]))
elif nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME):
elif nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME):
raise ValueError('Illegal nonbonded method for a non-periodic system')
# Create all of the particles
......@@ -207,7 +207,8 @@ class DesmondDMSFile(object):
ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
ff.Ewald:mm.NonbondedForce.Ewald,
ff.PME:mm.NonbondedForce.PME}
ff.PME:mm.NonbondedForce.PME,
ff.LJPME:mm.NonbondedForce.LJPME}
nb.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance)
......
......@@ -40,10 +40,12 @@ import math
from math import sqrt, cos
from copy import deepcopy
from heapq import heappush, heappop
from collections import defaultdict
import simtk.openmm as mm
import simtk.unit as unit
from . import element as elem
from simtk.openmm.app import Topology
from simtk.openmm.app.internal.singleton import Singleton
def _convertParameterToNumber(param):
if unit.is_quantity(param):
......@@ -52,46 +54,85 @@ def _convertParameterToNumber(param):
return param.value_in_unit_system(unit.md_unit_system)
return float(param)
def _parseFunctions(element):
"""Parse the attributes on an XML tag to find any tabulated functions it defines."""
functions = []
for function in element.findall('Function'):
values = [float(x) for x in function.text.split()]
if 'type' in function.attrib:
functionType = function.attrib['type']
else:
functionType = 'Continuous1D'
params = {}
for key in function.attrib:
if key.endswith('size'):
params[key] = int(function.attrib[key])
elif key.endswith('min') or key.endswith('max'):
params[key] = float(function.attrib[key])
functions.append((function.attrib['name'], functionType, values, params))
return functions
def _createFunctions(force, functions):
"""Add TabulatedFunctions to a Force based on the information that was recorded by _parseFunctions()."""
for (name, type, values, params) in functions:
if type == 'Continuous1D':
force.addTabulatedFunction(name, mm.Continuous1DFunction(values, params['min'], params['max']))
elif type == 'Continuous2D':
force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax']))
elif type == 'Continuous3D':
force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], params['zsize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax'], params['zmin'], params['zmax']))
elif type == 'Discrete1D':
force.addTabulatedFunction(name, mm.Discrete1DFunction(values))
elif type == 'Discrete2D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], values))
elif type == 'Discrete3D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
# Enumerated values for nonbonded method
class NoCutoff(object):
class NoCutoff(Singleton):
def __repr__(self):
return 'NoCutoff'
NoCutoff = NoCutoff()
class CutoffNonPeriodic(object):
class CutoffNonPeriodic(Singleton):
def __repr__(self):
return 'CutoffNonPeriodic'
CutoffNonPeriodic = CutoffNonPeriodic()
class CutoffPeriodic(object):
class CutoffPeriodic(Singleton):
def __repr__(self):
return 'CutoffPeriodic'
CutoffPeriodic = CutoffPeriodic()
class Ewald(object):
class Ewald(Singleton):
def __repr__(self):
return 'Ewald'
Ewald = Ewald()
class PME(object):
class PME(Singleton):
def __repr__(self):
return 'PME'
PME = PME()
class LJPME(Singleton):
def __repr__(self):
return 'LJPME'
LJPME = LJPME()
# Enumerated values for constraint type
class HBonds(object):
class HBonds(Singleton):
def __repr__(self):
return 'HBonds'
HBonds = HBonds()
class AllBonds(object):
class AllBonds(Singleton):
def __repr__(self):
return 'AllBonds'
AllBonds = AllBonds()
class HAngles(object):
class HAngles(Singleton):
def __repr__(self):
return 'HAngles'
HAngles = HAngles()
......@@ -326,7 +367,7 @@ class ForceField(object):
"""Register a new residue template."""
if template.name in self._templates:
# There is already a template with this name, so check the override levels.
existingTemplate = self._templates[template.name]
if template.overrideLevel < existingTemplate.overrideLevel:
# The existing one takes precedence, so just return.
......@@ -338,9 +379,9 @@ class ForceField(object):
self._templateSignatures[existingSignature].remove(existingTemplate)
else:
raise ValueError('Residue template %s with the same override level %d already exists.' % (template.name, template.overrideLevel))
# Register the template.
self._templates[template.name] = template
signature = _createResidueSignature([atom.element for atom in template.atoms])
if signature in self._templateSignatures:
......@@ -351,7 +392,7 @@ class ForceField(object):
def registerPatch(self, patch):
"""Register a new patch that can be applied to templates."""
self._patches[patch.name] = patch
def registerTemplatePatch(self, residue, patch, patchResidueIndex):
"""Register that a particular patch can be used with a particular residue."""
if residue not in self._templatePatches:
......@@ -488,7 +529,7 @@ class ForceField(object):
else:
self.constraints[key] = distance
system.addConstraint(atom1, atom2, distance)
def recordMatchedAtomParameters(self, residue, template, matches):
"""Record parameters for atoms based on having matched a residue to a template."""
matchAtoms = dict(zip(matches, residue.atoms()))
......@@ -606,21 +647,21 @@ class ForceField(object):
self.deletedBonds = []
self.addedExternalBonds = []
self.deletedExternalBonds = []
def createPatchedTemplates(self, templates):
"""Apply this patch to a set of templates, creating new modified ones."""
if len(templates) != self.numResidues:
raise ValueError("Patch '%s' expected %d templates, received %d", (self.name, self.numResidues, len(templates)))
# Construct a new version of each template.
newTemplates = []
for index, template in enumerate(templates):
newTemplate = ForceField._TemplateData("%s-%s" % (template.name, self.name))
newTemplates.append(newTemplate)
# Build the list of atoms in it.
for atom in template.atoms:
if not any(deleted.name == atom.name and deleted.residue == index for deleted in self.deletedAtoms):
newTemplate.atoms.append(ForceField._TemplateAtomData(atom.name, atom.type, atom.element, atom.parameters))
......@@ -634,9 +675,9 @@ class ForceField(object):
if atom.name not in newAtomIndex:
raise ValueError("Patch '%s' modifies nonexistent atom '%s' in template '%s'" % (self.name, atom.name, template.name))
newTemplate.atoms[newAtomIndex[atom.name]] = ForceField._TemplateAtomData(atom.name, atom.type, atom.element, atom.parameters)
# Copy over the virtual sites, translating the atom indices.
indexMap = dict([(oldAtomIndex[name], newAtomIndex[name]) for name in newAtomIndex if name in oldAtomIndex])
for site in template.virtualSites:
if site.index in indexMap and all(i in indexMap for i in site.atoms):
......@@ -644,9 +685,9 @@ class ForceField(object):
newSite.index = indexMap[site.index]
newSite.atoms = [indexMap[i] for i in site.atoms]
newTemplate.virtualSites.append(newSite)
# Build the lists of bonds and external bonds.
atomMap = dict([(template.atoms[i], indexMap[i]) for i in indexMap])
deletedBonds = [(atom1.name, atom2.name) for atom1, atom2 in self.deletedBonds if atom1.residue == index and atom2.residue == index]
for atom1, atom2 in template.bonds:
......@@ -668,7 +709,7 @@ class ForceField(object):
for atom in self.addedExternalBonds:
newTemplate.addExternalBondByName(atom.name)
return newTemplates
class _PatchAtomData(object):
"""Inner class used to encapsulate data about an atom in a patch definition."""
def __init__(self, description):
......@@ -759,7 +800,7 @@ class ForceField(object):
raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t))
def _getResidueTemplateMatches(self, res, bondedToAtom, templateSignatures=None):
def _getResidueTemplateMatches(self, res, bondedToAtom, templateSignatures=None, ignoreExternalBonds=False):
"""Return the residue template matches, or None if none are found.
Parameters
......@@ -786,14 +827,14 @@ class ForceField(object):
if signature in templateSignatures:
allMatches = []
for t in templateSignatures[signature]:
match = _matchResidue(res, t, bondedToAtom)
match = _matchResidue(res, t, bondedToAtom, ignoreExternalBonds)
if match is not None:
allMatches.append((t, match))
if len(allMatches) == 1:
template = allMatches[0][0]
matches = allMatches[0][1]
elif len(allMatches) > 1:
raise Exception('Multiple matching templates found for residue %d (%s).' % (res.index+1, res.name))
raise Exception('Multiple matching templates found for residue %d (%s): %s.' % (res.index+1, res.name, ', '.join(match[0].name for match in allMatches)))
return [template, matches]
def _buildBondedToAtomList(self, topology):
......@@ -848,7 +889,7 @@ class ForceField(object):
return unmatched_residues
def getMatchingTemplates(self, topology):
def getMatchingTemplates(self, topology, ignoreExternalBonds=False):
"""Return a list of forcefield residue templates matching residues in the specified topology.
.. CAUTION:: This method is experimental, and its API is subject to change.
......@@ -857,7 +898,8 @@ class ForceField(object):
----------
topology : Topology
The Topology whose residues are to be checked against the forcefield residue templates.
ignoreExternalBonds : bool=False
If true, ignore external bonds when matching residues to templates.
Returns
-------
templates : list of _TemplateData
......@@ -871,7 +913,7 @@ class ForceField(object):
templates = list() # list of templates matching the corresponding residues
for res in topology.residues():
# Attempt to match one of the existing templates.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom)
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
# Raise an exception if we have found no templates that match.
if matches is None:
raise ValueError('No template found for residue %d (%s). %s' % (res.index+1, res.name, _findMatchErrors(self, res)))
......@@ -914,7 +956,7 @@ class ForceField(object):
if signature in signatures:
# Signature is the same as an existing residue; check connectivity.
for check_residue in unique_unmatched_residues:
matches = _matchResidue(check_residue, template, bondedToAtom)
matches = _matchResidue(check_residue, template, bondedToAtom, False)
if matches is not None:
is_unique = False
if is_unique:
......@@ -926,7 +968,8 @@ class ForceField(object):
return [templates, unique_unmatched_residues]
def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, residueTemplates=dict(), **args):
constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, residueTemplates=dict(),
ignoreExternalBonds=False, **args):
"""Construct an OpenMM System representing a Topology with this force field.
Parameters
......@@ -935,7 +978,7 @@ class ForceField(object):
The Topology for which to create a System
nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME.
nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions
constraints : object=None
......@@ -951,16 +994,21 @@ class ForceField(object):
added to a hydrogen is subtracted from the heavy atom to keep
their total mass the same.
residueTemplates : dict=dict()
Key: Topology Residue object
Value: string, name of _TemplateData residue template object to use for
(Key) residue
This allows user to specify which template to apply to particular Residues
in the event that multiple matching templates are available (e.g Fe2+ and Fe3+
templates in the ForceField for a monoatomic iron ion in the topology).
Key: Topology Residue object
Value: string, name of _TemplateData residue template object to use for (Key) residue.
This allows user to specify which template to apply to particular Residues
in the event that multiple matching templates are available (e.g Fe2+ and Fe3+
templates in the ForceField for a monoatomic iron ion in the topology).
ignoreExternalBonds : boolean=False
If true, ignore external bonds when matching residues to templates. This is
useful when the Topology represents one piece of a larger molecule, so chains are
not terminated properly. This option can create ambiguities where multiple
templates match the same residue. If that happens, use the residueTemplates
argument to specify which one to use.
args
Arbitrary additional keyword arguments may also be specified.
This allows extra parameters to be specified that are specific to
particular force fields.
Arbitrary additional keyword arguments may also be specified.
This allows extra parameters to be specified that are specific to
particular force fields.
Returns
-------
......@@ -998,34 +1046,34 @@ class ForceField(object):
if res in residueTemplates:
tname = residueTemplates[res]
template = self._templates[tname]
matches = _matchResidue(res, template, bondedToAtom)
matches = _matchResidue(res, template, bondedToAtom, ignoreExternalBonds)
if matches is None:
raise Exception('User-supplied template %s does not match the residue %d (%s)' % (tname, res.index+1, res.name))
else:
# Attempt to match one of the existing templates.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom)
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
if matches is None:
unmatchedResidues.append(res)
else:
data.recordMatchedAtomParameters(res, template, matches)
# Try to apply patches to find matches for any unmatched residues.
if len(unmatchedResidues) > 0:
unmatchedResidues = _applyPatchesToMatchResidues(self, data, unmatchedResidues, bondedToAtom)
unmatchedResidues = _applyPatchesToMatchResidues(self, data, unmatchedResidues, bondedToAtom, ignoreExternalBonds)
# If we still haven't found a match for a residue, attempt to use residue template generators to create
# new templates (and potentially atom types/parameters).
for res in unmatchedResidues:
# A template might have been generated on an earlier iteration of this loop.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom)
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
if matches is None:
# Try all generators.
for generator in self._templateGenerators:
if generator(self, res):
# This generator has registered a new residue template that should match.
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom)
[template, matches] = self._getResidueTemplateMatches(res, bondedToAtom, ignoreExternalBonds=ignoreExternalBonds)
if matches is None:
# Something went wrong because the generated template does not match the residue signature.
raise Exception('The residue handler %s indicated it had correctly parameterized residue %s, but the generated template did not match the residue signature.' % (generator.__class__.__name__, str(res)))
......@@ -1254,7 +1302,7 @@ def _createResidueSignature(elements):
s += element.symbol+str(count)
return s
def _matchResidue(res, template, bondedToAtom):
def _matchResidue(res, template, bondedToAtom, ignoreExternalBonds=False):
"""Determine whether a residue matches a template and return a list of corresponding atoms.
Parameters
......@@ -1265,6 +1313,8 @@ def _matchResidue(res, template, bondedToAtom):
The template to compare it to
bondedToAtom : list
Enumerates which other atoms each atom is bonded to
ignoreExternalBonds : bool
If true, ignore external bonds when matching templates
Returns
-------
......@@ -1287,7 +1337,7 @@ def _matchResidue(res, template, bondedToAtom):
for atom in atoms:
bonds = [renumberAtoms[x] for x in bondedToAtom[atom.index] if x in renumberAtoms]
bondedTo.append(bonds)
externalBonds.append(len([x for x in bondedToAtom[atom.index] if x not in renumberAtoms]))
externalBonds.append(0 if ignoreExternalBonds else len([x for x in bondedToAtom[atom.index] if x not in renumberAtoms]))
# For each unique combination of element and number of bonds, make sure the residue and
# template have the same number of atoms.
......@@ -1300,15 +1350,15 @@ def _matchResidue(res, template, bondedToAtom):
residueTypeCount[key] += 1
templateTypeCount = {}
for i, atom in enumerate(template.atoms):
key = (atom.element, len(atom.bondedTo), atom.externalBonds)
key = (atom.element, len(atom.bondedTo), 0 if ignoreExternalBonds else atom.externalBonds)
if key not in templateTypeCount:
templateTypeCount[key] = 1
templateTypeCount[key] += 1
if residueTypeCount != templateTypeCount:
return None
# Identify template atoms that could potentially be matches for each atom.
candidates = [[] for i in range(numAtoms)]
for i in range(numAtoms):
for j, atom in enumerate(template.atoms):
......@@ -1316,13 +1366,13 @@ def _matchResidue(res, template, bondedToAtom):
continue
if len(atom.bondedTo) != len(bondedTo[i]):
continue
if atom.externalBonds != externalBonds[i]:
if not ignoreExternalBonds and atom.externalBonds != externalBonds[i]:
continue
candidates[i].append(j)
# Find an optimal ordering for matching atoms. This means 1) start with the one that has the fewest options,
# and 2) follow with ones that are bonded to an already matched atom.
searchOrder = []
atomsToOrder = set(range(numAtoms))
efficientAtomSet = set()
......@@ -1390,12 +1440,12 @@ def _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, position
return False
def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom):
def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignoreExternalBonds):
"""Try to apply patches to find matches for residues."""
# Start by creating all templates than can be created by applying a combination of one-residue patches
# to a single template. The number of these is usually not too large, and they often cover a large fraction
# of residues.
patchedTemplateSignatures = {}
patchedTemplates = {}
for name, template in forcefield._templates.items():
......@@ -1411,23 +1461,23 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom):
patchedTemplateSignatures[signature].append(patchedTemplate)
else:
patchedTemplateSignatures[signature] = [patchedTemplate]
# Now see if any of those templates matches any of the residues.
unmatchedResidues = []
for res in residues:
[template, matches] = forcefield._getResidueTemplateMatches(res, bondedToAtom, patchedTemplateSignatures)
[template, matches] = forcefield._getResidueTemplateMatches(res, bondedToAtom, patchedTemplateSignatures, ignoreExternalBonds)
if matches is None:
unmatchedResidues.append(res)
else:
data.recordMatchedAtomParameters(res, template, matches)
if len(unmatchedResidues) == 0:
return []
# We need to consider multi-residue patches. This can easily lead to a combinatorial explosion, so we make a simplifying
# assumption: that no residue is affected by more than one multi-residue patch (in addition to any number of single-residue
# patches). Record all multi-residue patches, and the templates they can be applied to.
patches = {}
maxPatchSize = 0
for patch in forcefield._patches.values():
......@@ -1443,9 +1493,9 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom):
patches[patchName][patchResidueIndex].append(forcefield._templates[templateName])
if templateName in patchedTemplates:
patches[patchName][patchResidueIndex] += patchedTemplates[templateName]
# Record which unmatched residues are bonded to each other.
bonds = set()
topology = residues[0].chain.topology
for atom1, atom2 in topology.bonds():
......@@ -1456,26 +1506,26 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom):
bond = tuple(sorted((res1, res2), key=lambda x: x.index))
if bond not in bonds:
bonds.add(bond)
# Identify clusters of unmatched residues that are all bonded to each other. These are the ones we'll
# try to apply multi-residue patches to.
clusterSize = 2
clusters = bonds
while clusterSize <= maxPatchSize:
# Try to apply patches to clusters of this size.
for patchName in patches:
patch = forcefield._patches[patchName]
if patch.numResidues == clusterSize:
matchedClusters = _matchToMultiResiduePatchedTemplates(data, clusters, patch, patches[patchName], bondedToAtom)
matchedClusters = _matchToMultiResiduePatchedTemplates(data, clusters, patch, patches[patchName], bondedToAtom, ignoreExternalBonds)
for cluster in matchedClusters:
for residue in cluster:
unmatchedResidues.remove(residue)
bonds = set(bond for bond in bonds if bond[0] in unmatchedResidues and bond[1] in unmatchedResidues)
# Now extend the clusters to find ones of the next size up.
largerClusters = set()
for cluster in clusters:
for bond in bonds:
......@@ -1503,34 +1553,34 @@ def _generatePatchedSingleResidueTemplates(template, patches, index, newTemplate
# This probably means the patch is inconsistent with another one that has already been applied,
# so just ignore it.
patchedTemplate = None
# Call this function recursively to generate combinations of patches.
if index+1 < len(patches):
_generatePatchedSingleResidueTemplates(template, patches, index+1, newTemplates)
if patchedTemplate is not None:
_generatePatchedSingleResidueTemplates(patchedTemplate, patches, index+1, newTemplates)
def _matchToMultiResiduePatchedTemplates(data, clusters, patch, residueTemplates, bondedToAtom):
def _matchToMultiResiduePatchedTemplates(data, clusters, patch, residueTemplates, bondedToAtom, ignoreExternalBonds):
"""Apply a multi-residue patch to templates, then try to match them against clusters of residues."""
matchedClusters = []
selectedTemplates = [None]*patch.numResidues
_applyMultiResiduePatch(data, clusters, patch, residueTemplates, selectedTemplates, 0, matchedClusters, bondedToAtom)
_applyMultiResiduePatch(data, clusters, patch, residueTemplates, selectedTemplates, 0, matchedClusters, bondedToAtom, ignoreExternalBonds)
return matchedClusters
def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index, matchedClusters, bondedToAtom):
def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index, matchedClusters, bondedToAtom, ignoreExternalBonds):
"""This is called recursively to apply a multi-residue patch to all possible combinations of templates."""
if index < patch.numResidues:
for template in candidateTemplates[index]:
selectedTemplates[index] = template
_applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index+1, matchedClusters, bondedToAtom)
_applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedTemplates, index+1, matchedClusters, bondedToAtom, ignoreExternalBonds)
else:
# We're at the deepest level of the recursion. We've selected a template for each residue, so apply the patch,
# then try to match it against clusters.
try:
patchedTemplates = patch.createPatchedTemplates(selectedTemplates)
except:
......@@ -1543,7 +1593,7 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT
for residues in itertools.permutations(cluster):
residueMatches = []
for residue, template in zip(residues, patchedTemplates):
matches = _matchResidue(residue, template, bondedToAtom)
matches = _matchResidue(residue, template, bondedToAtom, ignoreExternalBonds)
if matches is None:
residueMatches = None
break
......@@ -1551,18 +1601,18 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT
residueMatches.append(matches)
if residueMatches is not None:
# We successfully matched the template to the residues. Record the parameters.
for i in range(patch.numResidues):
data.recordMatchedAtomParameters(residues[i], patchedTemplates[i], residueMatches[i])
newlyMatchedClusters.append(cluster)
break
# Record which clusters were successfully matched.
matchedClusters += newlyMatchedClusters
for cluster in newlyMatchedClusters:
clusters.remove(cluster)
def _findMatchErrors(forcefield, res):
"""Try to guess why a residue failed to match any template and return an error message."""
......@@ -1664,6 +1714,7 @@ class HarmonicBondGenerator(object):
def __init__(self, forcefield):
self.ff = forcefield
self.bondsForAtomType = defaultdict(set)
self.types1 = []
self.types2 = []
self.length = []
......@@ -1672,8 +1723,13 @@ class HarmonicBondGenerator(object):
def registerBond(self, parameters):
types = self.ff._findAtomTypes(parameters, 2)
if None not in types:
index = len(self.types1)
self.types1.append(types[0])
self.types2.append(types[1])
for t in types[0]:
self.bondsForAtomType[t].add(index)
for t in types[1]:
self.bondsForAtomType[t].add(index)
self.length.append(_convertParameterToNumber(parameters['length']))
self.k.append(_convertParameterToNumber(parameters['k']))
......@@ -1699,7 +1755,7 @@ class HarmonicBondGenerator(object):
for bond in data.bonds:
type1 = data.atomType[data.atoms[bond.atom1]]
type2 = data.atomType[data.atoms[bond.atom2]]
for i in range(len(self.types1)):
for i in self.bondsForAtomType[type1]:
types1 = self.types1[i]
types2 = self.types2[i]
if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
......@@ -1719,6 +1775,7 @@ class HarmonicAngleGenerator(object):
def __init__(self, forcefield):
self.ff = forcefield
self.anglesForAtom2Type = defaultdict(list)
self.types1 = []
self.types2 = []
self.types3 = []
......@@ -1728,9 +1785,12 @@ class HarmonicAngleGenerator(object):
def registerAngle(self, parameters):
types = self.ff._findAtomTypes(parameters, 3)
if None not in types:
index = len(self.types1)
self.types1.append(types[0])
self.types2.append(types[1])
self.types3.append(types[2])
for t in types[1]:
self.anglesForAtom2Type[t].append(index)
self.angle.append(_convertParameterToNumber(parameters['angle']))
self.k.append(_convertParameterToNumber(parameters['k']))
......@@ -1757,7 +1817,7 @@ class HarmonicAngleGenerator(object):
type1 = data.atomType[data.atoms[angle[0]]]
type2 = data.atomType[data.atoms[angle[1]]]
type3 = data.atomType[data.atoms[angle[2]]]
for i in range(len(self.types1)):
for i in self.anglesForAtom2Type[type2]:
types1 = self.types1[i]
types2 = self.types2[i]
types3 = self.types3[i]
......@@ -2209,7 +2269,8 @@ class NonbondedGenerator(object):
CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
Ewald:mm.NonbondedForce.Ewald,
PME:mm.NonbondedForce.PME}
PME:mm.NonbondedForce.PME,
LJPME:mm.NonbondedForce.LJPME}
if nonbondedMethod not in methodMap:
raise ValueError('Illegal nonbonded method for NonbondedForce')
force = mm.NonbondedForce()
......@@ -2297,7 +2358,7 @@ class LennardJonesGenerator(object):
reverseMap[typeMap[typeValue]] = typeValue
# Now everything is assigned. Create the A- and B-coefficient arrays
acoef = [0]*(numLjTypes*numLjTypes)
bcoef = acoef[:]
for m in range(numLjTypes):
......@@ -2321,7 +2382,7 @@ class LennardJonesGenerator(object):
self.force.addTabulatedFunction('acoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, acoef))
self.force.addTabulatedFunction('bcoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, bcoef))
self.force.addPerParticleParameter('type')
if nonbondedMethod in [CutoffPeriodic, Ewald, PME]:
if nonbondedMethod in [CutoffPeriodic, Ewald, PME, LJPME]:
self.force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
elif nonbondedMethod is NoCutoff:
self.force.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff)
......@@ -2340,11 +2401,11 @@ class LennardJonesGenerator(object):
def postprocessSystem(self, sys, data, args):
# Create the exceptions.
bondIndices = _findBondsForExclusions(data, sys)
if self.lj14scale == 1:
# Just exclude the 1-2 and 1-3 interactions.
self.force.createExclusionsFromBonds(bondIndices, 2)
else:
forceCopy = deepcopy(self.force)
......@@ -2352,7 +2413,7 @@ class LennardJonesGenerator(object):
self.force.createExclusionsFromBonds(bondIndices, 3)
if self.force.getNumExclusions() > forceCopy.getNumExclusions() and self.lj14scale != 0:
# We need to create a CustomBondForce and use it to implement the scaled 1-4 interactions.
bonded = mm.CustomBondForce('%g*epsilon*((sigma/r)^12-(sigma/r)^6)' % (4*self.lj14scale))
bonded.addPerBondParameter('sigma')
bonded.addPerBondParameter('epsilon')
......@@ -2658,6 +2719,7 @@ class CustomNonbondedGenerator(object):
generator.perParticleParams.append(param.attrib['name'])
generator.params = ForceField._AtomTypeParameters(ff, 'CustomNonbondedForce', 'Atom', generator.perParticleParams)
generator.params.parseDefinitions(element)
generator.functions += _parseFunctions(element)
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.CustomNonbondedForce.NoCutoff,
......@@ -2670,19 +2732,7 @@ class CustomNonbondedGenerator(object):
force.addGlobalParameter(param, self.globalParams[param])
for param in self.perParticleParams:
force.addPerParticleParameter(param)
for (name, type, values, params) in self.functions:
if type == 'Continuous1D':
force.addTabulatedFunction(name, mm.Continuous1DFunction(values, params['min'], params['max']))
elif type == 'Continuous2D':
force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax']))
elif type == 'Continuous3D':
force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], params['zsize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax'], params['zmin'], params['zmax']))
elif type == 'Discrete1D':
force.addTabulatedFunction(name, mm.Discrete1DFunction(values))
elif type == 'Discrete2D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], values))
elif type == 'Discrete3D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
_createFunctions(force, self.functions)
for atom in data.atoms:
values = self.params.getAtomParameters(atom, data)
force.addParticle(values)
......@@ -2729,19 +2779,7 @@ class CustomGBGenerator(object):
generator.computedValues.append((value.attrib['name'], value.text, computationMap[value.attrib['type']]))
for term in element.findall('EnergyTerm'):
generator.energyTerms.append((term.text, computationMap[term.attrib['type']]))
for function in element.findall("Function"):
values = [float(x) for x in function.text.split()]
if 'type' in function.attrib:
type = function.attrib['type']
else:
type = 'Continuous1D'
params = {}
for key in function.attrib:
if key.endswith('size'):
params[key] = int(function.attrib[key])
elif key.endswith('min') or key.endswith('max'):
params[key] = float(function.attrib[key])
generator.functions.append((function.attrib['name'], type, values, params))
generator.functions += _parseFunctions(element)
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.CustomGBForce.NoCutoff,
......@@ -2758,19 +2796,7 @@ class CustomGBGenerator(object):
force.addComputedValue(value[0], value[1], value[2])
for term in self.energyTerms:
force.addEnergyTerm(term[0], term[1])
for (name, type, values, params) in self.functions:
if type == 'Continuous1D':
force.addTabulatedFunction(name, mm.Continuous1DFunction(values, params['min'], params['max']))
elif type == 'Continuous2D':
force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax']))
elif type == 'Continuous3D':
force.addTabulatedFunction(name, mm.Continuous2DFunction(params['xsize'], params['ysize'], params['zsize'], values, params['xmin'], params['xmax'], params['ymin'], params['ymax'], params['zmin'], params['zmax']))
elif type == 'Discrete1D':
force.addTabulatedFunction(name, mm.Discrete1DFunction(values))
elif type == 'Discrete2D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], values))
elif type == 'Discrete3D':
force.addTabulatedFunction(name, mm.Discrete2DFunction(params['xsize'], params['ysize'], params['zsize'], values))
_createFunctions(force, self.functions)
for atom in data.atoms:
values = self.params.getAtomParameters(atom, data)
force.addParticle(values)
......@@ -2781,6 +2807,171 @@ class CustomGBGenerator(object):
parsers["CustomGBForce"] = CustomGBGenerator.parseElement
## @private
class CustomHbondGenerator(object):
"""A CustomHbondGenerator constructs a CustomHbondForce."""
def __init__(self, forcefield):
self.ff = forcefield
self.donorTypes1 = []
self.donorTypes2 = []
self.donorTypes3 = []
self.acceptorTypes1 = []
self.acceptorTypes2 = []
self.acceptorTypes3 = []
self.globalParams = {}
self.perDonorParams = []
self.perAcceptorParams = []
self.donorParamValues = []
self.acceptorParamValues = []
self.functions = []
@staticmethod
def parseElement(element, ff):
generator = CustomHbondGenerator(ff)
ff.registerGenerator(generator)
generator.energy = element.attrib['energy']
generator.bondCutoff = int(element.attrib['bondCutoff'])
generator.particlesPerDonor = int(element.attrib['particlesPerDonor'])
generator.particlesPerAcceptor = int(element.attrib['particlesPerAcceptor'])
if generator.particlesPerDonor < 1 or generator.particlesPerDonor > 3:
raise ValueError('Illegal value for particlesPerDonor for CustomHbondForce')
if generator.particlesPerAcceptor < 1 or generator.particlesPerAcceptor > 3:
raise ValueError('Illegal value for particlesPerAcceptor for CustomHbondForce')
for param in element.findall('GlobalParameter'):
generator.globalParams[param.attrib['name']] = float(param.attrib['defaultValue'])
for param in element.findall('PerDonorParameter'):
generator.perDonorParams.append(param.attrib['name'])
for param in element.findall('PerAcceptorParameter'):
generator.perAcceptorParams.append(param.attrib['name'])
for donor in element.findall('Donor'):
types = ff._findAtomTypes(donor.attrib, 3)[:generator.particlesPerDonor]
if None not in types:
generator.donorTypes1.append(types[0])
if len(types) > 1:
generator.donorTypes2.append(types[1])
if len(types) > 2:
generator.donorTypes3.append(types[2])
generator.donorParamValues.append([float(donor.attrib[param]) for param in generator.perDonorParams])
for acceptor in element.findall('Acceptor'):
types = ff._findAtomTypes(acceptor.attrib, 3)[:generator.particlesPerAcceptor]
if None not in types:
generator.acceptorTypes1.append(types[0])
if len(types) > 1:
generator.acceptorTypes2.append(types[1])
if len(types) > 2:
generator.acceptorTypes3.append(types[2])
generator.acceptorParamValues.append([float(acceptor.attrib[param]) for param in generator.perAcceptorParams])
generator.functions += _parseFunctions(element)
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.CustomHbondForce.NoCutoff,
CutoffNonPeriodic:mm.CustomHbondForce.CutoffNonPeriodic,
CutoffPeriodic:mm.CustomHbondForce.CutoffPeriodic}
if nonbondedMethod not in methodMap:
raise ValueError('Illegal nonbonded method for CustomNonbondedForce')
force = mm.CustomHbondForce(self.energy)
sys.addForce(force)
for param in self.globalParams:
force.addGlobalParameter(param, self.globalParams[param])
for param in self.perDonorParams:
force.addPerDonorParameter(param)
for param in self.perAcceptorParams:
force.addPerAcceptorParameter(param)
_createFunctions(force, self.functions)
force.setNonbondedMethod(methodMap[nonbondedMethod])
force.setCutoffDistance(nonbondedCutoff)
# Add donors.
if self.particlesPerDonor == 1:
for atom in data.atoms:
type1 = data.atomType[atom]
for i in range(len(self.donorTypes1)):
types1 = self.donorTypes1[i]
if type1 in self.donorTypes1[i]:
force.addDonor(atom.index, -1, -1, self.donorParamValues[i])
elif self.particlesPerDonor == 2:
for bond in data.bonds:
type1 = data.atomType[data.atoms[bond.atom1]]
type2 = data.atomType[data.atoms[bond.atom2]]
for i in range(len(self.donorTypes1)):
types1 = self.donorTypes1[i]
types2 = self.donorTypes2[i]
if type1 in types1 and type2 in types2:
force.addDonor(bond.atom1, bond.atom2, -1, self.donorParamValues[i])
elif type1 in types2 and type2 in types1:
force.addDonor(bond.atom2, bond.atom1, -1, self.donorParamValues[i])
else:
for angle in data.angles:
type1 = data.atomType[data.atoms[angle[0]]]
type2 = data.atomType[data.atoms[angle[1]]]
type3 = data.atomType[data.atoms[angle[2]]]
for i in range(len(self.donorTypes1)):
types1 = self.donorTypes1[i]
types2 = self.donorTypes2[i]
types3 = self.donorTypes3[i]
if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
force.addDonor(angle[0], angle[1], angle[2], self.donorParamValues[i])
# Add acceptors.
if self.particlesPerAcceptor == 1:
for atom in data.atoms:
type1 = data.atomType[atom]
for i in range(len(self.acceptorTypes1)):
types1 = self.acceptorTypes1[i]
if type1 in self.acceptorTypes1[i]:
force.addAcceptor(atom.index, -1, -1, self.acceptorParamValues[i])
elif self.particlesPerAcceptor == 2:
for bond in data.bonds:
type1 = data.atomType[data.atoms[bond.atom1]]
type2 = data.atomType[data.atoms[bond.atom2]]
for i in range(len(self.acceptorTypes1)):
types1 = self.acceptorTypes1[i]
types2 = self.acceptorTypes2[i]
if type1 in types1 and type2 in types2:
force.addAcceptor(bond.atom1, bond.atom2, -1, self.acceptorParamValues[i])
elif type1 in types2 and type2 in types1:
force.addAcceptor(bond.atom2, bond.atom1, -1, self.acceptorParamValues[i])
else:
for angle in data.angles:
type1 = data.atomType[data.atoms[angle[0]]]
type2 = data.atomType[data.atoms[angle[1]]]
type3 = data.atomType[data.atoms[angle[2]]]
for i in range(len(self.acceptorTypes1)):
types1 = self.acceptorTypes1[i]
types2 = self.acceptorTypes2[i]
types3 = self.acceptorTypes3[i]
if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
force.addAcceptor(angle[0], angle[1], angle[2], self.acceptorParamValues[i])
# Add exclusions.
for donor in range(force.getNumDonors()):
(d1, d2, d3, params) = force.getDonorParameters(donor)
outerAtoms = set((d1, d2, d3))
if -1 in outerAtoms:
outerAtoms.remove(-1)
excludedAtoms = set(outerAtoms)
for i in range(self.bondCutoff):
newOuterAtoms = set()
for atom in outerAtoms:
for bond in data.atomBonds[atom]:
b = data.bonds[bond]
bondedAtom = (b.atom2 if b.atom1 == atom else b.atom1)
if bondedAtom not in excludedAtoms:
newOuterAtoms.add(bondedAtom)
excludedAtoms.add(bondedAtom)
outerAtoms = newOuterAtoms
for acceptor in range(force.getNumAcceptors()):
(a1, a2, a3, params) = force.getAcceptorParameters(acceptor)
if a1 in excludedAtoms or a2 in excludedAtoms or a3 in excludedAtoms:
force.addExclusion(donor, acceptor)
parsers["CustomHbondForce"] = CustomHbondGenerator.parseElement
## @private
class CustomManyParticleGenerator(object):
"""A CustomManyParticleGenerator constructs a CustomManyParticleForce."""
......@@ -4801,7 +4992,7 @@ class AmoebaMultipoleGenerator(object):
bondedAtomZ = data.atoms[bondedAtomZIndex]
if (kx == 0 and kz == bondedAtomZType):
kz = bondedAtomZIndex
zaxis = bondedAtomZIndex
savedMultipoleDict = multipoleDict
hit = 5
......
......@@ -552,7 +552,7 @@ class GromacsTopFile(object):
----------
nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME.
nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions
constraints : object=None
......@@ -570,7 +570,7 @@ class GromacsTopFile(object):
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.
The error tolerance to use if nonbondedMethod is Ewald, PME, or LJPME.
removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None
......@@ -589,7 +589,7 @@ class GromacsTopFile(object):
boxVectors = self.topology.getPeriodicBoxVectors()
if boxVectors is not None:
sys.setDefaultPeriodicBoxVectors(*boxVectors)
elif nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME):
elif nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME):
raise ValueError('Illegal nonbonded method for a non-periodic system')
nb = mm.NonbondedForce()
sys.addForce(nb)
......@@ -772,7 +772,7 @@ class GromacsTopFile(object):
if periodic is None:
periodic = mm.PeriodicTorsionForce()
sys.addForce(periodic)
periodic.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], int(params[7]), float(params[5])*degToRad, k)
periodic.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], int(float(params[7])), float(params[5])*degToRad, k)
elif dihedralType == '2':
# Harmonic torsion
k = float(params[6])
......@@ -877,7 +877,8 @@ class GromacsTopFile(object):
ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
ff.Ewald:mm.NonbondedForce.Ewald,
ff.PME:mm.NonbondedForce.PME}
ff.PME:mm.NonbondedForce.PME,
ff.LJPME:mm.NonbondedForce.LJPME}
nb.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance)
......
......@@ -776,6 +776,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
force.setNonbondedMethod(mm.NonbondedForce.Ewald)
elif nonbondedMethod == 'PME':
force.setNonbondedMethod(mm.NonbondedForce.PME)
elif nonbondedMethod == 'LJPME':
force.setNonbondedMethod(mm.NonbondedForce.LJPME)
else:
raise Exception("Cutoff method not understood.")
......@@ -885,7 +887,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
ii, jj, chg, sig, eps = force.getExceptionParameters(i)
cforce.addExclusion(ii, jj)
# Now set the various properties based on the NonbondedForce object
if nonbondedMethod in ('PME', 'Ewald', 'CutoffPeriodic'):
if nonbondedMethod in ('PME', 'LJPME', 'Ewald', 'CutoffPeriodic'):
cforce.setNonbondedMethod(cforce.CutoffPeriodic)
cforce.setCutoffDistance(nonbondedCutoff)
cforce.setUseLongRangeCorrection(True)
......
......@@ -353,13 +353,13 @@ def _createEnergyTerms(force, solventDielectric, soluteDielectric, SA, cutoff, k
if cutoff is not None:
params += "; cutoff=%.16g" % cutoff
if kappa > 0:
force.addEnergyTerm("-0.5*138.935485*(1/soluteDielectric-exp(-kappa*B)/solventDielectric)*q^2/B"+params,
force.addEnergyTerm("-0.5*138.935485*(1/soluteDielectric-exp(-kappa*B)/solventDielectric)*charge^2/B"+params,
CustomGBForce.SingleParticle)
elif kappa < 0:
# Do kappa check here to avoid repeating code everywhere
raise ValueError('kappa/ionic strength must be >= 0')
else:
force.addEnergyTerm("-0.5*138.935485*(1/soluteDielectric-1/solventDielectric)*q^2/B"+params,
force.addEnergyTerm("-0.5*138.935485*(1/soluteDielectric-1/solventDielectric)*charge^2/B"+params,
CustomGBForce.SingleParticle)
if SA=='ACE':
force.addEnergyTerm("28.3919551*(radius+0.14)^2*(radius/B)^6; radius=or+offset"+params, CustomGBForce.SingleParticle)
......@@ -367,17 +367,17 @@ def _createEnergyTerms(force, solventDielectric, soluteDielectric, SA, cutoff, k
raise ValueError('Unknown surface area method: '+SA)
if cutoff is None:
if kappa > 0:
force.addEnergyTerm("-138.935485*(1/soluteDielectric-exp(-kappa*f)/solventDielectric)*q1*q2/f;"
force.addEnergyTerm("-138.935485*(1/soluteDielectric-exp(-kappa*f)/solventDielectric)*charge1*charge2/f;"
"f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
else:
force.addEnergyTerm("-138.935485*(1/soluteDielectric-1/solventDielectric)*q1*q2/f;"
force.addEnergyTerm("-138.935485*(1/soluteDielectric-1/solventDielectric)*charge1*charge2/f;"
"f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
else:
if kappa > 0:
force.addEnergyTerm("-138.935485*(1/soluteDielectric-exp(-kappa*f)/solventDielectric)*q1*q2*(1/f-"+str(1/cutoff)+");"
force.addEnergyTerm("-138.935485*(1/soluteDielectric-exp(-kappa*f)/solventDielectric)*charge1*charge2*(1/f-"+str(1/cutoff)+");"
"f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
else:
force.addEnergyTerm("-138.935485*(1/soluteDielectric-1/solventDielectric)*q1*q2*(1/f-"+str(1/cutoff)+");"
force.addEnergyTerm("-138.935485*(1/soluteDielectric-1/solventDielectric)*charge1*charge2*(1/f-"+str(1/cutoff)+");"
"f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
......@@ -476,7 +476,7 @@ class CustomAmberGBForceBase(CustomGBForce):
class GBSAHCTForce(CustomAmberGBForceBase):
"""This class is equivalent to Amber ``igb=1``
The list of parameters to ``addParticle`` is: ``[q, or, sr]``.
The list of parameters to ``addParticle`` is: ``[charge, or, sr]``.
Parameters
----------
......@@ -499,7 +499,7 @@ class GBSAHCTForce(CustomAmberGBForceBase):
cutoff=None, kappa=0.0):
CustomAmberGBForceBase.__init__(self)
self.addPerParticleParameter("q")
self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled offset radius
self.addComputedValue("I", "step(r+sr2-or1)*0.5*(1/L-1/U+0.25*(r-sr2^2/r)*(1/(U^2)-1/(L^2))+0.5*log(L/U)/r);"
......@@ -537,7 +537,7 @@ class GBSAHCTForce(CustomAmberGBForceBase):
class GBSAOBC1Force(CustomAmberGBForceBase):
"""This class is equivalent to Amber ``igb=2``
The list of parameters to ``addParticle`` is: ``[q, or, sr]``.
The list of parameters to ``addParticle`` is: ``[charge, or, sr]``.
Parameters
----------
......@@ -561,7 +561,7 @@ class GBSAOBC1Force(CustomAmberGBForceBase):
CustomAmberGBForceBase.__init__(self)
self.addPerParticleParameter("q")
self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled offset radius
self.addComputedValue("I", "step(r+sr2-or1)*0.5*(1/L-1/U+0.25*(r-sr2^2/r)*(1/(U^2)-1/(L^2))+0.5*log(L/U)/r);"
......@@ -599,7 +599,7 @@ class GBSAOBC1Force(CustomAmberGBForceBase):
class GBSAOBC2Force(GBSAOBC1Force):
"""This class is equivalent to Amber ``igb=5``
The list of parameters to ``addParticle`` is: ``[q, or, sr]``.
The list of parameters to ``addParticle`` is: ``[charge, or, sr]``.
Parameters
----------
......@@ -625,7 +625,7 @@ class GBSAOBC2Force(GBSAOBC1Force):
# is different. We inherit for getStandardParameters.
CustomAmberGBForceBase.__init__(self)
self.addPerParticleParameter("q")
self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled offset radius
self.addComputedValue("I", "step(r+sr2-or1)*0.5*(1/L-1/U+0.25*(r-sr2^2/r)*(1/(U^2)-1/(L^2))+0.5*log(L/U)/r);"
......@@ -641,7 +641,7 @@ class GBSAOBC2Force(GBSAOBC1Force):
class GBSAGBnForce(CustomAmberGBForceBase):
"""This class is equivalent to Amber ``igb=7``
The list of parameters to ``addParticle`` is: ``[q, or, sr]``.
The list of parameters to ``addParticle`` is: ``[charge, or, sr]``.
Parameters
----------
......@@ -746,7 +746,7 @@ class GBSAGBnForce(CustomAmberGBForceBase):
CustomGBForce.addParticle(self, p + [radIndex])
def _addEnergyTerms(self):
self.addPerParticleParameter("q")
self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled offset radius
self.addPerParticleParameter("radindex")
......@@ -834,7 +834,7 @@ class GBSAGBn2Force(GBSAGBnForce):
return radii
def _addEnergyTerms(self):
self.addPerParticleParameter("q")
self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled offset radius
self.addPerParticleParameter("alpha")
......
"""
Creates a subclass for all classes intended to be a singleton. This
maintains the correctness of instance is instance even following
pickling/unpickling
"""
class Singleton(object):
_inst = None
def __new__(cls):
if cls._inst is None:
cls._inst = super(Singleton, cls).__new__(cls)
return cls._inst
def __reduce__(self):
return repr(self)
......@@ -186,8 +186,7 @@ class Modeller(object):
def convertWater(self, model='tip3p'):
"""Convert all water molecules to a different water model.
@deprecated Use addExtraParticles() instead. It performs the same
function but in a more general way.
@deprecated Use addExtraParticles() instead. It performs the same function but in a more general way.
Parameters
----------
......@@ -408,12 +407,17 @@ class Modeller(object):
if len(self.positions) == 0:
positions = []
else:
positions = self.positions.value_in_unit(nanometer)
positions = self.positions.value_in_unit(nanometer)[:]
cells = {}
numCells = tuple((max(1, int(floor(box[i]/maxCutoff))) for i in range(3)))
cellSize = tuple((box[i]/numCells[i] for i in range(3)))
for i in range(len(positions)):
cell = tuple((int(floor(positions[i][j]/cellSize[j]))%numCells[j] for j in range(3)))
pos = positions[i]
pos = pos - floor(pos[2]*invBox[2])*vectors[2]
pos -= floor(pos[1]*invBox[1])*vectors[1]
pos -= floor(pos[0]*invBox[0])*vectors[0]
positions[i] = pos
cell = tuple((int(floor(pos[j]/cellSize[j]))%numCells[j] for j in range(3)))
if cell in cells:
cells[cell].append(i)
else:
......@@ -871,7 +875,7 @@ class Modeller(object):
# and causes hydrogens to spread out evenly.
system = System()
nonbonded = CustomNonbondedForce('100/((r/0.1)^4+1)')
nonbonded = CustomNonbondedForce('100/(r/0.1)^4')
nonbonded.setNonbondedMethod(CustomNonbondedForce.CutoffNonPeriodic);
nonbonded.setCutoffDistance(1*nanometer)
bonds = HarmonicBondForce()
......@@ -1006,7 +1010,7 @@ class Modeller(object):
signature = _createResidueSignature([atom.element for atom in residue.atoms()])
if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]:
if _matchResidue(residue, t, bondedToAtom) is not None:
if _matchResidue(residue, t, bondedToAtom, False) is not None:
matchFound = True
if matchFound:
# Just copy the residue over.
......@@ -1025,7 +1029,7 @@ class Modeller(object):
if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]:
if t in templatesNoEP:
matches = _matchResidue(residueNoEP, templatesNoEP[t], bondedToAtomNoEP)
matches = _matchResidue(residueNoEP, templatesNoEP[t], bondedToAtomNoEP, False)
if matches is not None:
template = t;
# Record the corresponding atoms.
......
......@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2012-2015 Stanford University and the Authors.
Portions copyright (c) 2012-2016 Stanford University and the Authors.
Authors: Peter Eastman
Contributors:
......@@ -32,12 +32,41 @@ from __future__ import absolute_import
__author__ = "Peter Eastman"
__version__ = "1.0"
from collections import namedtuple
import os
import xml.etree.ElementTree as etree
from simtk.openmm.vec3 import Vec3
from simtk.openmm.app.internal.singleton import Singleton
from simtk.unit import nanometers, sqrt, is_quantity
from copy import deepcopy
# Enumerated values for bond type
class Single(Singleton):
def __repr__(self):
return 'Single'
Single = Single()
class Double(Singleton):
def __repr__(self):
return 'Double'
Double = Double()
class Triple(Singleton):
def __repr__(self):
return 'Triple'
Triple = Triple()
class Aromatic(Singleton):
def __repr__(self):
return 'Aromatic'
Aromatic = Aromatic()
class Amide(Singleton):
def __repr__(self):
return 'Amide'
Amide = Amide()
class Topology(object):
"""Topology stores the topological information about a system.
......@@ -155,7 +184,7 @@ class Topology(object):
residue._atoms.append(atom)
return atom
def addBond(self, atom1, atom2):
def addBond(self, atom1, atom2, type=None, order=None):
"""Create a new bond and add it to the Topology.
Parameters
......@@ -164,8 +193,13 @@ class Topology(object):
The first Atom connected by the bond
atom2 : Atom
The second Atom connected by the bond
type : object=None
The type of bond to add. Allowed values are None, Single, Double, Triple,
Aromatic, or Amide.
order : int=None
The bond order, or None if it is not specified
"""
self._bonds.append((atom1, atom2))
self._bonds.append(Bond(atom1, atom2, type, order))
def chains(self):
"""Iterate over all Chains in the Topology."""
......@@ -387,7 +421,7 @@ class Residue(object):
return "<Residue %d (%s) of chain %d>" % (self.index, self.name, self.chain.index)
class Atom(object):
"""An Atom object represents a residue within a Topology."""
"""An Atom object represents an atom within a Topology."""
def __init__(self, name, element, index, residue, id):
"""Construct a new Atom. You should call addAtom() on the Topology instead of calling this directly."""
......@@ -404,3 +438,32 @@ class Atom(object):
def __repr__(self):
return "<Atom %d (%s) of chain %d residue %d (%s)>" % (self.index, self.name, self.residue.chain.index, self.residue.index, self.residue.name)
class Bond(namedtuple('Bond', ['atom1', 'atom2'])):
"""A Bond object represents a bond between two Atoms within a Topology.
This class extends tuple, and may be interpreted as a 2 element tuple of Atom objects.
It also has fields that can optionally be used to describe the bond order and type of bond."""
def __new__(cls, atom1, atom2, type=None, order=None):
"""Create a new Bond. You should call addBond() on the Topology instead of calling this directly."""
bond = super(Bond, cls).__new__(cls, atom1, atom2)
bond.type = type
bond.order = order
return bond
def __getnewargs__(self):
"Support for pickle protocol 2: http://docs.python.org/2/library/pickle.html#pickling-and-unpickling-normal-class-instances"
return self[0], self[1], self.type, self.order
def __deepcopy__(self, memo):
return Bond(self[0], self[1], self.type, self.order)
def __repr__(self):
s = "Bond(%s, %s" % (self[0], self[1])
if self.type is not None:
s = "%s, type=%s" % (s, self.type)
if self.order is not None:
s = "%s, order=%d" % (s, self.order)
s += ")"
return s
......@@ -28,7 +28,7 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
from __future__ import absolute_import
from __future__ import absolute_import, division
__author__ = "Peter Eastman"
__version__ = "1.0"
......@@ -90,18 +90,20 @@ class MTSIntegrator(CustomIntegrator):
def _createSubsteps(self, parentSubsteps, groups):
group, substeps = groups[0]
stepsPerParentStep = substeps//parentSubsteps
stepsPerParentStep = substeps / parentSubsteps
if stepsPerParentStep < 1 or stepsPerParentStep != int(stepsPerParentStep):
raise ValueError("The number for substeps for each group must be a multiple of the number for the previous group")
stepsPerParentStep = int(stepsPerParentStep)
if group < 0 or group > 31:
raise ValueError("Force group must be between 0 and 31")
for i in range(stepsPerParentStep):
self.addComputePerDof("v", "v+0.5*(dt/"+str(substeps)+")*f"+str(group)+"/m")
if len(groups) == 1:
self.addComputePerDof("x1", "x")
self.addComputePerDof("x", "x+(dt/"+str(substeps)+")*v")
self.addComputePerDof("x1", "x")
self.addConstrainPositions();
self.addComputePerDof("v", "(x-x1)/(dt/"+str(substeps)+")");
self.addComputePerDof("v", "v+(x-x1)/(dt/"+str(substeps)+")");
self.addConstrainVelocities()
else:
self._createSubsteps(substeps, groups[1:])
self.addComputePerDof("v", "v+0.5*(dt/"+str(substeps)+")*f"+str(group)+"/m")
......@@ -79,6 +79,8 @@ yard = yards = Unit({yard_base_unit: 1.0})
furlongs = furlong = yard.create_unit(scale=220.0, name="furlong", symbol="furlong")
miles = mile = furlong.create_unit(scale=8.0, name="mile", symbol="mi")
bohrs = bohr = angstrom.create_unit(scale=0.52917721067, name='bohr', symbol='r_0')
############
### MASS ###
############
......
......@@ -1232,13 +1232,13 @@ ENABLE_PREPROCESSING = YES
# compilation will be performed. Macro expansion can be done in a controlled
# way by setting EXPAND_ONLY_PREDEF to YES.
MACRO_EXPANSION = NO
MACRO_EXPANSION = YES
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
# then the macro expansion is limited to the macros specified with the
# PREDEFINED and EXPAND_AS_DEFINED tags.
EXPAND_ONLY_PREDEF = NO
EXPAND_ONLY_PREDEF = YES
# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
# in the INCLUDE_PATH (see below) will be search if a #include is found.
......@@ -1266,7 +1266,7 @@ INCLUDE_FILE_PATTERNS =
# undefined via #undef or recursively expanded use the := operator
# instead of the = operator.
PREDEFINED =
PREDEFINED = OPENMM_EXPORT=
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
# this tag can be used to specify a list of macro names that should be expanded.
......
......@@ -74,6 +74,11 @@ def getNodeText(node):
s = "%s%s\n\n" % (s, getNodeText(n))
elif n.tag == "ref":
s = "%s%s" % (s, getNodeText(n))
elif n.tag == "xrefsect":
title = n.find("xreftitle")
description = n.find("xrefdescription")
if title is not None and description is not None and getNodeText(title).lower() == "deprecated":
s = "%s\n@deprecated %s\n\n" % (s, getNodeText(description))
else:
if n.tag in docTags:
tag = docTags[n.tag]
......@@ -242,6 +247,7 @@ class SwigInputBuilder:
self.fOut.write("\n/* Declare factories */\n\n")
forceSubclassList = []
integratorSubclassList = []
tabulatedFunctionSubclassList = []
for classNode in findNodes(self.doc.getroot(), "compounddef", kind="class", prot="public"):
className = getText("compoundname", classNode)
shortClassName=stripOpenmmPrefix(className)
......@@ -256,6 +262,8 @@ class SwigInputBuilder:
forceSubclassList.append(shortClassName)
elif baseName == 'OpenMM::Integrator':
integratorSubclassList.append(shortClassName)
elif baseName == 'OpenMM::TabulatedFunction':
tabulatedFunctionSubclassList.append(shortClassName)
self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce")
for name in sorted(forceSubclassList):
......@@ -292,6 +300,16 @@ class SwigInputBuilder:
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::TabulatedFunction* OpenMM::TabulatedFunction::__copy__")
for name in sorted(tabulatedFunctionSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::TabulatedFunction* OpenMM_XmlSerializer__deserializeTabulatedFunction")
for name in sorted(tabulatedFunctionSubclassList):
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")
......
......@@ -13,7 +13,10 @@ DOC_STRINGS = {("Context", "setPositions") :
# Do not generate wrappers for the following methods.
# Indexed by (className, [methodName [, numParams]])
SKIP_METHODS = [('State',),
SKIP_METHODS = [('State', 'getPositions'),
('State', 'getVelocities'),
('State', 'getForces'),
('StateBuilder',),
('Vec3',),
('AngleInfo',),
('ApplyAndersenThermostatKernel',),
......@@ -87,8 +90,6 @@ SKIP_METHODS = [('State',),
('UpdateTimeKernel',),
('VdwInfo',),
('WcaDispersionInfo',),
('Context', 'getState'),
('Context', 'setState'),
('Context', 'createCheckpoint'),
('Context', 'loadCheckpoint'),
('CudaPlatform',),
......@@ -102,7 +103,6 @@ SKIP_METHODS = [('State',),
('Platform', 'createKernel'),
('Platform', 'registerKernelFactory'),
('IntegrateRPMDStepKernel',),
('RPMDIntegrator', 'getState'),
('CalcDrudeForceKernel',),
('IntegrateDrudeLangevinStepKernel',),
('IntegrateDrudeSCFStepKernel',),
......@@ -427,6 +427,13 @@ UNITS = {
: (None, (None, None, None, None,
'unit.kilojoules_per_mole', 'unit.kilojoules_per_mole', 'unit.kilojoules_per_mole',
'unit.kilojoules_per_mole', 'unit.kilojoules_per_mole', 'unit.kilojoules_per_mole')),
("State", "getTime") : ('unit.picosecond', ()),
("State", "getKineticEnergy") : ('unit.kilojoules_per_mole', ()),
("State", "getPotentialEnergy") : ('unit.kilojoules_per_mole', ()),
("State", "getPeriodicBoxVolume") : ('unit.nanometers**3', ()),
("State", "getPeriodicBoxVectors") : ('unit.nanometers', ()),
("State", "getParameters") : (None, ()),
("State", "getEnergyParameterDerivatives") : (None, ()),
("System", "getConstraintParameters") : (None, (None, None, 'unit.nanometer')),
("System", "getForce") : (None, ()),
("System", "getVirtualSite") : (None, ()),
......
%inline %{
typedef int bitmask32t;
#include <cstring>
#include <numpy/arrayobject.h>
%}
%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 {
PyObject *_getStateAsLists(int getPositions,
int getVelocities,
int getForces,
int getEnergy,
int getParameters,
int enforcePeriodic,
bitmask32t groups) {
State state;
PyThreadState* _savePythonThreadState = PyEval_SaveThread();
int types = 0;
if (getPositions) types |= State::Positions;
if (getVelocities) types |= State::Velocities;
if (getForces) types |= State::Forces;
if (getEnergy) types |= State::Energy;
if (getParameters) types |= State::Parameters;
try {
state = self->getState(types, enforcePeriodic, groups);
}
catch (...) {
PyEval_RestoreThread(_savePythonThreadState);
throw;
}
PyEval_RestoreThread(_savePythonThreadState);
return _convertStateToLists(state);
}
%pythoncode %{
def getState(self, getPositions=False, getVelocities=False,
getForces=False, getEnergy=False, getParameters=False,
enforcePeriodicBox=False, groups=-1):
getParameterDerivatives=False, enforcePeriodicBox=False, groups=-1):
"""Get a State object recording the current state information stored in this context.
Parameters
......@@ -66,8 +21,10 @@
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
getParameters : bool=False
whether to store context parameters in the State
getParameterDerivatives : bool=False
whether to store parameter derivatives 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.
......@@ -79,10 +36,6 @@
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.
"""
getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
(getPositions, getVelocities, getForces, getEnergy, getParameters,
enforcePeriodicBox))
try:
# is the input integer-like?
groups_mask = int(groups)
......@@ -93,43 +46,24 @@
((1<<x) & 0xffffffff for x in groups))
else:
raise TypeError('%s is neither an int nor set' % groups)
(simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \
self._getStateAsLists(getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
state = State(simTime=simTime,
energy=energy,
coordList=coordList,
velList=velList,
forceList=forceList,
periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap)
if groups_mask > 0x80000000:
groups_mask -= 0x100000000
types = 0
if getPositions:
types += State.Positions
if getVelocities:
types += State.Velocities
if getForces:
types += State.Forces
if getEnergy:
types += State.Energy
if getParameters:
types += State.Parameters
if getParameterDerivatives:
types += State.ParameterDerivatives
state = _openmm.Context_getState(self, types, enforcePeriodicBox, groups_mask)
return state
def setState(self, state):
"""
setState(Context self, State state)
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
a piece of information (e.g. positions or velocities), that aspect of the Context is
left unchanged.
Even when all possible information is included in the State, the effect of calling this method
is still less complete than loadCheckpoint(). For example, it does not restore the internal
states of random number generators. On the other hand, it has the advantage of not being hardware
specific.
"""
self.setTime(state._simTime)
self.setPeriodicBoxVectors(state._periodicBoxVectorsList[0], state._periodicBoxVectorsList[1], state._periodicBoxVectorsList[2])
if state._coordList is not None:
self.setPositions(state._coordList)
if state._velList is not None:
self.setVelocities(state._velList)
if state._paramMap is not None:
for param in state._paramMap:
self.setParameter(param, state._paramMap[param])
%}
%feature("docstring") createCheckpoint "Create a checkpoint recording the current state of the Context.
......@@ -170,34 +104,6 @@ Parameters:
}
%extend OpenMM::RPMDIntegrator {
PyObject *_getStateAsLists(int copy,
int getPositions,
int getVelocities,
int getForces,
int getEnergy,
int getParameters,
int enforcePeriodic,
int groups) {
State state;
PyThreadState* _savePythonThreadState = PyEval_SaveThread();
int types = 0;
if (getPositions) types |= State::Positions;
if (getVelocities) types |= State::Velocities;
if (getForces) types |= State::Forces;
if (getEnergy) types |= State::Energy;
if (getParameters) types |= State::Parameters;
try {
state = self->getState(copy, types, enforcePeriodic, groups);
}
catch (...) {
PyEval_RestoreThread(_savePythonThreadState);
throw;
}
PyEval_RestoreThread(_savePythonThreadState);
return _convertStateToLists(state);
}
%pythoncode %{
def getState(self,
copy,
......@@ -206,6 +112,7 @@ Parameters:
getForces=False,
getEnergy=False,
getParameters=False,
getParameterDerivatives=False,
enforcePeriodicBox=False,
groups=-1):
"""Get a State object recording the current state information about one copy of the system.
......@@ -222,8 +129,10 @@ Parameters:
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
getParameters : bool=False
whether to store context parameters in the State
getParameterDerivatives : bool=False
whether to store parameter derivatives 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.
......@@ -235,9 +144,9 @@ Parameters:
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.
"""
getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
getP, getV, getF, getE, getPa, getPd, enforcePeriodic = map(bool,
(getPositions, getVelocities, getForces, getEnergy, getParameters,
enforcePeriodicBox))
getParameterDerivatives, enforcePeriodicBox))
try:
# is the input integer-like?
......@@ -248,18 +157,22 @@ Parameters:
((1<<x) & 0xffffffff for x in groups))
else:
raise TypeError('%s is neither an int nor set' % groups)
(simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \
self._getStateAsLists(copy, getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
state = State(simTime=simTime,
energy=energy,
coordList=coordList,
velList=velList,
forceList=forceList,
periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap)
if groups_mask > 0x80000000:
groups_mask -= 0x100000000
types = 0
if getPositions:
types += State.Positions
if getVelocities:
types += State.Velocities
if getForces:
types += State.Forces
if getEnergy:
types += State.Energy
if getParameters:
types += State.Parameters
if getParameterDerivatives:
types += State.ParameterDerivatives
state = _openmm.RPMDIntegrator_getState(self, copy, types, enforcePeriodicBox, groups_mask)
return state
%}
}
......@@ -311,14 +224,14 @@ Parameters:
}
%extend OpenMM::XmlSerializer {
%feature(docstring, "This method exists only for backward compatibility. @deprecated Use serialize() instead.") serializeSystem;
%feature(docstring, "This method exists only for backward compatibility.\n@deprecated Use serialize() instead.") serializeSystem;
static std::string serializeSystem(const OpenMM::System* object) {
std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::System>(object, "System", ss);
return ss.str();
}
%feature(docstring, "This method exists only for backward compatibility. @deprecated Use deserialize() instead.") deserializeSystem;
%feature(docstring, "This method exists only for backward compatibility.\n@deprecated Use deserialize() instead.") deserializeSystem;
%newobject deserializeSystem;
static OpenMM::System* deserializeSystem(const char* inputString) {
std::stringstream ss;
......@@ -352,87 +265,33 @@ Parameters:
return OpenMM::XmlSerializer::deserialize<OpenMM::Integrator>(ss);
}
static std::string _serializeStateAsLists(
const std::vector<Vec3>& pos,
const std::vector<Vec3>& vel,
const std::vector<Vec3>& forces,
double kineticEnergy,
double potentialEnergy,
double time,
const std::vector<Vec3>& boxVectors,
const std::map<string, double>& params,
int types) {
OpenMM::State myState = _convertListsToState(pos,vel,forces,kineticEnergy,potentialEnergy,time,boxVectors,params,types);
std::stringstream buffer;
OpenMM::XmlSerializer::serialize<OpenMM::State>(&myState, "State", buffer);
return buffer.str();
static std::string _serializeTabulatedFunction(const OpenMM::TabulatedFunction* object) {
std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::TabulatedFunction>(object, "TabulatedFunction", ss);
return ss.str();
}
static PyObject* _deserializeStringIntoLists(const std::string &stateAsString) {
std::stringstream ss;
ss << stateAsString;
OpenMM::State* deserializedState = OpenMM::XmlSerializer::deserialize<OpenMM::State>(ss);
PyObject* obj = _convertStateToLists(*deserializedState);
delete deserializedState;
return obj;
%newobject _deserializeTabulatedFunction;
static OpenMM::TabulatedFunction* _deserializeTabulatedFunction(const char* inputString) {
std::stringstream ss;
ss << inputString;
return OpenMM::XmlSerializer::deserialize<OpenMM::TabulatedFunction>(ss);
}
%pythoncode %{
@staticmethod
def _serializeState(pythonState):
positions = []
velocities = []
forces = []
kineticEnergy = 0.0
potentialEnergy = 0.0
params = {}
types = 0
try:
positions = pythonState.getPositions().value_in_unit(unit.nanometers)
types |= 1
except:
pass
try:
velocities = pythonState.getVelocities().value_in_unit(unit.nanometers/unit.picoseconds)
types |= 2
except:
pass
try:
forces = pythonState.getForces().value_in_unit(unit.kilojoules_per_mole/unit.nanometers)
types |= 4
except:
pass
try:
kineticEnergy = pythonState.getKineticEnergy().value_in_unit(unit.kilojoules_per_mole)
potentialEnergy = pythonState.getPotentialEnergy().value_in_unit(unit.kilojoules_per_mole)
types |= 8
except:
pass
try:
params = pythonState.getParameters()
types |= 16
except:
pass
time = pythonState.getTime().value_in_unit(unit.picoseconds)
boxVectors = pythonState.getPeriodicBoxVectors().value_in_unit(unit.nanometers)
string = XmlSerializer._serializeStateAsLists(positions, velocities, forces, kineticEnergy, potentialEnergy, time, boxVectors, params, types)
return string
@staticmethod
def _deserializeState(pythonString):
(simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = XmlSerializer._deserializeStringIntoLists(pythonString)
static std::string _serializeState(const OpenMM::State* object) {
std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::State>(object, "State", ss);
return ss.str();
}
state = State(simTime=simTime,
energy=energy,
coordList=coordList,
velList=velList,
forceList=forceList,
periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap)
return state
%newobject _deserializeState;
static OpenMM::State* _deserializeState(const char* inputString) {
std::stringstream ss;
ss << inputString;
return OpenMM::XmlSerializer::deserialize<OpenMM::State>(ss);
}
%pythoncode %{
@staticmethod
def serialize(object):
"""Serialize an object as XML."""
......@@ -444,6 +303,8 @@ Parameters:
return XmlSerializer._serializeIntegrator(object)
elif isinstance(object, State):
return XmlSerializer._serializeState(object)
elif isinstance(object, TabulatedFunction):
return XmlSerializer._serializeTabulatedFunction(object)
raise ValueError("Unsupported object type")
@staticmethod
......@@ -462,6 +323,8 @@ Parameters:
return XmlSerializer._deserializeIntegrator(inputString)
if type == "State":
return XmlSerializer._deserializeState(inputString)
if type == "TabulatedFunction":
return XmlSerializer._deserializeTabulatedFunction(inputString)
raise ValueError("Unsupported object type")
%}
}
......@@ -511,3 +374,137 @@ Parameters:
return OpenMM::XmlSerializer::clone<OpenMM::Integrator>(*self);
}
}
%extend OpenMM::TabulatedFunction {
%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):
return self.__copy__()
%}
%newobject __copy__;
OpenMM::TabulatedFunction* __copy__() {
return OpenMM::XmlSerializer::clone<OpenMM::TabulatedFunction>(*self);
}
}
%extend OpenMM::State {
%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):
return self.__copy__()
def getPeriodicBoxVectors(self, asNumpy=False):
"""Get the vectors defining the axes of the periodic box."""
vectors = _openmm.State_getPeriodicBoxVectors(self)
if asNumpy:
vectors = numpy.array(vectors)
return vectors*unit.nanometers
def getPositions(self, asNumpy=False):
"""Get the position of each particle with units.
Raises an exception if positions where not requested in
the context.getState() call.
Returns a list of Vec3s, unless asNumpy is True, in
which case a Numpy array of arrays will be returned.
"""
if asNumpy:
if '_positionsNumpy' not in dir(self):
self._positionsNumpy = numpy.empty([self._getNumParticles(), 3], numpy.float64)
self._getVectorAsNumpy(State.Positions, self._positionsNumpy)
self._positionsNumpy = self._positionsNumpy*unit.nanometers
return self._positionsNumpy
if '_positions' not in dir(self):
self._positions = self._getVectorAsVec3(State.Positions)*unit.nanometers
return self._positions
def getVelocities(self, asNumpy=False):
"""Get the velocity of each particle with units.
Raises an exception if velocities where not requested in
the context.getState() call.
Returns a list of Vec3s if asNumpy is False, or a Numpy
array if asNumpy is True.
"""
if asNumpy:
if '_velocitiesNumpy' not in dir(self):
self._velocitiesNumpy = numpy.empty([self._getNumParticles(), 3], numpy.float64)
self._getVectorAsNumpy(State.Velocities, self._velocitiesNumpy)
self._velocitiesNumpy = self._velocitiesNumpy*unit.nanometers/unit.picosecond
return self._velocitiesNumpy
if '_velocities' not in dir(self):
self._velocities = self._getVectorAsVec3(State.Velocities)*unit.nanometers/unit.picosecond
return self._velocities
def getForces(self, asNumpy=False):
"""Get the force acting on each particle with units.
Raises an exception if forces where not requested in
the context.getState() call.
Returns a list of Vec3s if asNumpy is False, or a Numpy
array if asNumpy is True.
"""
if asNumpy:
if '_forcesNumpy' not in dir(self):
self._forcesNumpy = numpy.empty([self._getNumParticles(), 3], numpy.float64)
self._getVectorAsNumpy(State.Forces, self._forcesNumpy)
self._forcesNumpy = self._forcesNumpy*unit.kilojoules_per_mole/unit.nanometer
return self._forcesNumpy
if '_forces' not in dir(self):
self._forces = self._getVectorAsVec3(State.Forces)*unit.kilojoules_per_mole/unit.nanometer
return self._forces
%}
int _getNumParticles() {
if ((self->getDataTypes() & State::Positions) != 0)
return self->getPositions().size();
if ((self->getDataTypes() & State::Velocities) != 0)
return self->getVelocities().size();
if ((self->getDataTypes() & State::Forces) != 0)
return self->getForces().size();
return 0;
}
PyObject* _getVectorAsVec3(State::DataType type) {
if (type == State::Positions)
return copyVVec3ToList(self->getPositions());
if (type == State::Velocities)
return copyVVec3ToList(self->getVelocities());
if (type == State::Forces)
return copyVVec3ToList(self->getForces());
PyErr_SetString(PyExc_ValueError, "Illegal type specified in _getVectorAsVec3");
return NULL;
}
void _getVectorAsNumpy(State::DataType type, PyObject* output) {
const std::vector<Vec3>* array;
if (type == State::Positions)
array = &self->getPositions();
else if (type == State::Velocities)
array = &self->getVelocities();
else if (type == State::Forces)
array = &self->getForces();
else {
PyErr_SetString(PyExc_ValueError, "Illegal type specified in _getVectorAsNumpy");
return;
}
void* data = PyArray_DATA((PyArrayObject*) output);
memcpy(data, &array[0][0], 3*sizeof(double)*array->size());
}
%newobject __copy__;
OpenMM::State* __copy__() {
return OpenMM::XmlSerializer::clone<OpenMM::State>(*self);
}
}
......@@ -20,107 +20,6 @@ PyObject *copyVVec3ToList(std::vector<Vec3> vVec3) {
return pyList;
}
State _convertListsToState( const std::vector<Vec3> &pos,
const std::vector<Vec3> &vel,
const std::vector<Vec3> &forces,
double kineticEnergy,
double potentialEnergy,
double time,
const std::vector<Vec3> &boxVectors,
const std::map<std::string, double> &params,
int types ) {
State::StateBuilder sb(time);
if(types & State::Positions)
sb.setPositions(pos);
if(types & State::Velocities)
sb.setVelocities(vel);
if(types & State::Forces)
sb.setForces(forces);
if(types & State::Energy)
sb.setEnergy(kineticEnergy, potentialEnergy);
if(types & State::Parameters)
sb.setParameters(params);
sb.setPeriodicBoxVectors(boxVectors[0], boxVectors[1], boxVectors[2]);
return sb.getState();
}
PyObject *_convertStateToLists(const State& state) {
double simTime;
PyObject *pPeriodicBoxVectorsList;
PyObject *pEnergy;
PyObject *pPositions;
PyObject *pVelocities;
PyObject *pForces;
PyObject *pyTuple;
PyObject *pParameters;
simTime=state.getTime();
OpenMM::Vec3 myVecA;
OpenMM::Vec3 myVecB;
OpenMM::Vec3 myVecC;
state.getPeriodicBoxVectors(myVecA, myVecB, myVecC);
PyObject* mm = PyImport_AddModule("simtk.openmm");
PyObject* vec3 = PyObject_GetAttrString(mm, "Vec3");
PyObject* args1 = Py_BuildValue("(d,d,d)", myVecA[0], myVecA[1], myVecA[2]);
PyObject* args2 = Py_BuildValue("(d,d,d)", myVecB[0], myVecB[1], myVecB[2]);
PyObject* args3 = Py_BuildValue("(d,d,d)", myVecC[0], myVecC[1], myVecC[2]);
PyObject* pyVec1 = PyObject_CallObject(vec3, args1);
PyObject* pyVec2 = PyObject_CallObject(vec3, args2);
PyObject* pyVec3 = PyObject_CallObject(vec3, args3);
Py_DECREF(args1);
Py_DECREF(args2);
Py_DECREF(args3);
pPeriodicBoxVectorsList = Py_BuildValue("N,N,N", pyVec1, pyVec2, pyVec3);
try {
pPositions = copyVVec3ToList(state.getPositions());
}
catch (std::exception& ex) {
pPositions = Py_None;
Py_INCREF(Py_None);
}
try {
pVelocities = copyVVec3ToList(state.getVelocities());
}
catch (std::exception& ex) {
pVelocities = Py_None;
Py_INCREF(Py_None);
}
try {
pForces = copyVVec3ToList(state.getForces());
}
catch (std::exception& ex) {
pForces = Py_None;
Py_INCREF(Py_None);
}
try {
pEnergy = Py_BuildValue("(d,d)",
state.getKineticEnergy(),
state.getPotentialEnergy());
}
catch (std::exception& ex) {
pEnergy = Py_None;
Py_INCREF(Py_None);
}
try {
pParameters = PyDict_New();
const std::map<std::string, double>& params = state.getParameters();
for (std::map<std::string, double>::const_iterator iter = params.begin(); iter != params.end(); ++iter)
PyDict_SetItemString(pParameters, iter->first.c_str(), Py_BuildValue("d", iter->second));
}
catch (std::exception& ex) {
pParameters = Py_None;
Py_INCREF(Py_None);
}
pyTuple=Py_BuildValue("(d,N,N,N,N,N,N)",
simTime, pPeriodicBoxVectorsList, pEnergy,
pPositions, pVelocities,
pForces, pParameters);
return pyTuple;
}
} // namespace OpenMM
%}
......
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