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 ...@@ -36,6 +36,7 @@ from math import sqrt
from simtk.openmm.app import Topology from simtk.openmm.app import Topology
from simtk.openmm.app import PDBFile from simtk.openmm.app import PDBFile
from simtk.openmm.app.internal import amber_file_parser from simtk.openmm.app.internal import amber_file_parser
from simtk.openmm.app.internal.singleton import Singleton
from . import forcefield as ff from . import forcefield as ff
from . import element as elem from . import element as elem
import simtk.unit as u import simtk.unit as u
...@@ -44,27 +45,27 @@ from simtk.openmm.app.internal.unitcell import computePeriodicBoxVectors ...@@ -44,27 +45,27 @@ from simtk.openmm.app.internal.unitcell import computePeriodicBoxVectors
# Enumerated values for implicit solvent model # Enumerated values for implicit solvent model
class HCT(object): class HCT(Singleton):
def __repr__(self): def __repr__(self):
return 'HCT' return 'HCT'
HCT = HCT() HCT = HCT()
class OBC1(object): class OBC1(Singleton):
def __repr__(self): def __repr__(self):
return 'OBC1' return 'OBC1'
OBC1 = OBC1() OBC1 = OBC1()
class OBC2(object): class OBC2(Singleton):
def __repr__(self): def __repr__(self):
return 'OBC2' return 'OBC2'
OBC2 = OBC2() OBC2 = OBC2()
class GBn(object): class GBn(Singleton):
def __repr__(self): def __repr__(self):
return 'GBn' return 'GBn'
GBn = GBn() GBn = GBn()
class GBn2(object): class GBn2(Singleton):
def __repr__(self): def __repr__(self):
return 'GBn2' return 'GBn2'
GBn2 = GBn2() GBn2 = GBn2()
...@@ -168,7 +169,7 @@ class AmberPrmtopFile(object): ...@@ -168,7 +169,7 @@ class AmberPrmtopFile(object):
---------- ----------
nonbondedMethod : object=NoCutoff nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are 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 nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions The cutoff distance to use for nonbonded interactions
constraints : object=None constraints : object=None
...@@ -201,7 +202,7 @@ class AmberPrmtopFile(object): ...@@ -201,7 +202,7 @@ class AmberPrmtopFile(object):
added to a hydrogen is subtracted from the heavy atom to keep their added to a hydrogen is subtracted from the heavy atom to keep their
total mass the same. total mass the same.
ewaldErrorTolerance : float=0.0005 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 switchDistance : float=0*nanometers
The distance at which the potential energy switching function is The distance at which the potential energy switching function is
turned on for Lennard-Jones interactions. If the switchDistance is 0 turned on for Lennard-Jones interactions. If the switchDistance is 0
...@@ -221,10 +222,11 @@ class AmberPrmtopFile(object): ...@@ -221,10 +222,11 @@ class AmberPrmtopFile(object):
ff.CutoffNonPeriodic:'CutoffNonPeriodic', ff.CutoffNonPeriodic:'CutoffNonPeriodic',
ff.CutoffPeriodic:'CutoffPeriodic', ff.CutoffPeriodic:'CutoffPeriodic',
ff.Ewald:'Ewald', ff.Ewald:'Ewald',
ff.PME:'PME'} ff.PME:'PME',
ff.LJPME:'LJPME'}
if nonbondedMethod not in methodMap: if nonbondedMethod not in methodMap:
raise ValueError('Illegal value for nonbonded method') 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') raise ValueError('Illegal nonbonded method for a non-periodic system')
constraintMap = {None:None, constraintMap = {None:None,
ff.HBonds:'h-bonds', ff.HBonds:'h-bonds',
......
...@@ -690,7 +690,7 @@ class CharmmPsfFile(object): ...@@ -690,7 +690,7 @@ class CharmmPsfFile(object):
The parameter set to use to parametrize this molecule The parameter set to use to parametrize this molecule
nonbondedMethod : object=NoCutoff nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are 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 nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions. The cutoff distance to use for nonbonded interactions.
switchDistance : distance=0*nanometer switchDistance : distance=0*nanometer
...@@ -728,7 +728,7 @@ class CharmmPsfFile(object): ...@@ -728,7 +728,7 @@ class CharmmPsfFile(object):
added to a hydrogen is subtracted from the heavy atom to keep their added to a hydrogen is subtracted from the heavy atom to keep their
total mass the same. total mass the same.
ewaldErrorTolerance : float=0.0005 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 flexibleConstraints : bool=True
Are our constraints flexible or not? Are our constraints flexible or not?
verbose : bool=False verbose : bool=False
...@@ -746,10 +746,10 @@ class CharmmPsfFile(object): ...@@ -746,10 +746,10 @@ class CharmmPsfFile(object):
cutoff = cutoff.value_in_unit(u.nanometers) cutoff = cutoff.value_in_unit(u.nanometers)
if nonbondedMethod not in (ff.NoCutoff, ff.CutoffNonPeriodic, 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') raise ValueError('Illegal value for nonbonded method')
if not hasbox and nonbondedMethod in (ff.CutoffPeriodic, 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 ' raise ValueError('Illegal nonbonded method for a '
'non-periodic system') 'non-periodic system')
if implicitSolvent not in (HCT, OBC1, OBC2, GBn, GBn2, None): if implicitSolvent not in (HCT, OBC1, OBC2, GBn, GBn2, None):
...@@ -1009,6 +1009,8 @@ class CharmmPsfFile(object): ...@@ -1009,6 +1009,8 @@ class CharmmPsfFile(object):
force.setNonbondedMethod(mm.NonbondedForce.Ewald) force.setNonbondedMethod(mm.NonbondedForce.Ewald)
elif nonbondedMethod is ff.PME: elif nonbondedMethod is ff.PME:
force.setNonbondedMethod(mm.NonbondedForce.PME) force.setNonbondedMethod(mm.NonbondedForce.PME)
elif nonbondedMethod is ff.LJPME:
force.setNonbondedMethod(mm.NonbondedForce.LJPME)
else: else:
raise ValueError('Cutoff method is not understood') raise ValueError('Cutoff method is not understood')
...@@ -1088,8 +1090,7 @@ class CharmmPsfFile(object): ...@@ -1088,8 +1090,7 @@ class CharmmPsfFile(object):
mm.Discrete2DFunction(num_lj_types, num_lj_types, bcoef)) mm.Discrete2DFunction(num_lj_types, num_lj_types, bcoef))
cforce.addPerParticleParameter('type') cforce.addPerParticleParameter('type')
cforce.setForceGroup(self.NONBONDED_FORCE_GROUP) cforce.setForceGroup(self.NONBONDED_FORCE_GROUP)
if (nonbondedMethod is ff.PME or nonbondedMethod is ff.Ewald or if (nonbondedMethod in (ff.PME, ff.LJPME, ff.Ewald, ff.CutoffPeriodic)):
nonbondedMethod is ff.CutoffPeriodic):
cforce.setNonbondedMethod(cforce.CutoffPeriodic) cforce.setNonbondedMethod(cforce.CutoffPeriodic)
cforce.setCutoffDistance(nonbondedCutoff) cforce.setCutoffDistance(nonbondedCutoff)
cforce.setUseLongRangeCorrection(True) cforce.setUseLongRangeCorrection(True)
......
...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of ...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org. 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 Authors: Robert McGibbon
Contributors: Contributors:
...@@ -33,6 +33,9 @@ __author__ = "Robert McGibbon" ...@@ -33,6 +33,9 @@ __author__ = "Robert McGibbon"
__version__ = "1.0" __version__ = "1.0"
import simtk.openmm as mm import simtk.openmm as mm
import os
import os.path
__all__ = ['CheckpointReporter'] __all__ = ['CheckpointReporter']
...@@ -80,12 +83,7 @@ class CheckpointReporter(object): ...@@ -80,12 +83,7 @@ class CheckpointReporter(object):
""" """
self._reportInterval = reportInterval self._reportInterval = reportInterval
if isinstance(file, str): self._file = file
self._own_handle = True
self._out = open(file, 'w+b', 0)
else:
self._out = file
self._own_handle = False
def describeNextReport(self, simulation): def describeNextReport(self, simulation):
"""Get information about the next report this object will generate. """Get information about the next report this object will generate.
...@@ -116,13 +114,24 @@ class CheckpointReporter(object): ...@@ -116,13 +114,24 @@ class CheckpointReporter(object):
state : State state : State
The current state of the simulation The current state of the simulation
""" """
self._out.seek(0) if isinstance(self._file, str):
chk = simulation.context.createCheckpoint() # Do a safe save.
self._out.write(chk)
self._out.truncate() tempFilename1 = self._file+".backup1"
self._out.flush() tempFilename2 = self._file+".backup2"
with open(tempFilename1, 'w+b', 0) as out:
def __del__(self): out.write(simulation.context.createCheckpoint())
if self._own_handle: exists = os.path.exists(self._file)
self._out.close() 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): ...@@ -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.""" 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): def __init__(self, file, topology, dt, firstStep=0, interval=1, append=False):
"""Create a DCD file and write out the header. """Create a DCD file and write out the header, or open an existing file to append.
Parameters Parameters
---------- ----------
...@@ -68,6 +68,8 @@ class DCDFile(object): ...@@ -68,6 +68,8 @@ class DCDFile(object):
interval : int=1 interval : int=1
The frequency (measured in time steps) at which states are written The frequency (measured in time steps) at which states are written
to the trajectory 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._file = file
self._topology = topology self._topology = topology
...@@ -77,15 +79,24 @@ class DCDFile(object): ...@@ -77,15 +79,24 @@ class DCDFile(object):
if is_quantity(dt): if is_quantity(dt):
dt = dt.value_in_unit(picoseconds) dt = dt.value_in_unit(picoseconds)
dt /= 0.04888821 dt /= 0.04888821
self._dt = dt
boxFlag = 0 boxFlag = 0
if topology.getUnitCellDimensions() is not None: if topology.getUnitCellDimensions() is not None:
boxFlag = 1 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) if append:
header += struct.pack('<13i', boxFlag, 0, 0, 0, 0, 0, 0, 0, 0, 24, 84, 164, 2) file.seek(8, os.SEEK_SET)
header += struct.pack('<80s', b'Created by OpenMM') self._modelCount = struct.unpack('<i', file.read(4))[0]
header += struct.pack('<80s', b'Created '+time.asctime(time.localtime(time.time())).encode('ascii')) file.seek(268, os.SEEK_SET)
header += struct.pack('<4i', 164, 4, len(list(topology.atoms())), 4) numAtoms = struct.unpack('<i', file.read(4))[0]
file.write(header) 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): def writeModel(self, positions, unitCellDimensions=None, periodicBoxVectors=None):
"""Write out a model to the DCD file. """Write out a model to the DCD file.
...@@ -116,9 +127,19 @@ class DCDFile(object): ...@@ -116,9 +127,19 @@ class DCDFile(object):
raise ValueError('Particle position is infinite') raise ValueError('Particle position is infinite')
file = self._file 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. # Update the header.
self._modelCount += 1
file.seek(8, os.SEEK_SET) file.seek(8, os.SEEK_SET)
file.write(struct.pack('<i', self._modelCount)) file.write(struct.pack('<i', self._modelCount))
file.seek(20, os.SEEK_SET) file.seek(20, os.SEEK_SET)
...@@ -149,3 +170,7 @@ class DCDFile(object): ...@@ -149,3 +170,7 @@ class DCDFile(object):
data = array.array('f', (10*x[i] for x in positions)) data = array.array('f', (10*x[i] for x in positions))
data.tofile(file) data.tofile(file)
file.write(length) file.write(length)
try:
file.flush()
except AttributeError:
pass
...@@ -42,7 +42,7 @@ class DCDReporter(object): ...@@ -42,7 +42,7 @@ class DCDReporter(object):
To use it, create a DCDReporter, then add it to the Simulation's list of reporters. 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. """Create a DCDReporter.
Parameters Parameters
...@@ -51,9 +51,16 @@ class DCDReporter(object): ...@@ -51,9 +51,16 @@ class DCDReporter(object):
The file to write to The file to write to
reportInterval : int reportInterval : int
The interval (in time steps) at which to write frames 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._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 self._dcd = None
def describeNextReport(self, simulation): def describeNextReport(self, simulation):
...@@ -87,7 +94,7 @@ class DCDReporter(object): ...@@ -87,7 +94,7 @@ class DCDReporter(object):
""" """
if self._dcd is None: 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()) self._dcd.writeModel(state.getPositions(), periodicBoxVectors=state.getPeriodicBoxVectors())
def __del__(self): def __del__(self):
......
...@@ -165,11 +165,11 @@ class DesmondDMSFile(object): ...@@ -165,11 +165,11 @@ class DesmondDMSFile(object):
---------- ----------
nonbondedMethod : object=NoCutoff nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are 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 nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions The cutoff distance to use for nonbonded interactions
ewaldErrorTolerance : float=0.0005 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 removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None hydrogenMass : mass=None
...@@ -185,7 +185,7 @@ class DesmondDMSFile(object): ...@@ -185,7 +185,7 @@ class DesmondDMSFile(object):
boxSize = self.topology.getUnitCellDimensions() boxSize = self.topology.getUnitCellDimensions()
if boxSize is not None: if boxSize is not None:
sys.setDefaultPeriodicBoxVectors((boxSize[0], 0, 0), (0, boxSize[1], 0), (0, 0, boxSize[2])) 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') raise ValueError('Illegal nonbonded method for a non-periodic system')
# Create all of the particles # Create all of the particles
...@@ -207,7 +207,8 @@ class DesmondDMSFile(object): ...@@ -207,7 +207,8 @@ class DesmondDMSFile(object):
ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic, ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic, ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
ff.Ewald:mm.NonbondedForce.Ewald, 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.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff) nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance) nb.setEwaldErrorTolerance(ewaldErrorTolerance)
......
...@@ -40,10 +40,12 @@ import math ...@@ -40,10 +40,12 @@ import math
from math import sqrt, cos from math import sqrt, cos
from copy import deepcopy from copy import deepcopy
from heapq import heappush, heappop from heapq import heappush, heappop
from collections import defaultdict
import simtk.openmm as mm import simtk.openmm as mm
import simtk.unit as unit import simtk.unit as unit
from . import element as elem from . import element as elem
from simtk.openmm.app import Topology from simtk.openmm.app import Topology
from simtk.openmm.app.internal.singleton import Singleton
def _convertParameterToNumber(param): def _convertParameterToNumber(param):
if unit.is_quantity(param): if unit.is_quantity(param):
...@@ -52,46 +54,85 @@ def _convertParameterToNumber(param): ...@@ -52,46 +54,85 @@ def _convertParameterToNumber(param):
return param.value_in_unit_system(unit.md_unit_system) return param.value_in_unit_system(unit.md_unit_system)
return float(param) 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 # Enumerated values for nonbonded method
class NoCutoff(object): class NoCutoff(Singleton):
def __repr__(self): def __repr__(self):
return 'NoCutoff' return 'NoCutoff'
NoCutoff = NoCutoff() NoCutoff = NoCutoff()
class CutoffNonPeriodic(object): class CutoffNonPeriodic(Singleton):
def __repr__(self): def __repr__(self):
return 'CutoffNonPeriodic' return 'CutoffNonPeriodic'
CutoffNonPeriodic = CutoffNonPeriodic() CutoffNonPeriodic = CutoffNonPeriodic()
class CutoffPeriodic(object): class CutoffPeriodic(Singleton):
def __repr__(self): def __repr__(self):
return 'CutoffPeriodic' return 'CutoffPeriodic'
CutoffPeriodic = CutoffPeriodic() CutoffPeriodic = CutoffPeriodic()
class Ewald(object): class Ewald(Singleton):
def __repr__(self): def __repr__(self):
return 'Ewald' return 'Ewald'
Ewald = Ewald() Ewald = Ewald()
class PME(object): class PME(Singleton):
def __repr__(self): def __repr__(self):
return 'PME' return 'PME'
PME = PME() PME = PME()
class LJPME(Singleton):
def __repr__(self):
return 'LJPME'
LJPME = LJPME()
# Enumerated values for constraint type # Enumerated values for constraint type
class HBonds(object): class HBonds(Singleton):
def __repr__(self): def __repr__(self):
return 'HBonds' return 'HBonds'
HBonds = HBonds() HBonds = HBonds()
class AllBonds(object): class AllBonds(Singleton):
def __repr__(self): def __repr__(self):
return 'AllBonds' return 'AllBonds'
AllBonds = AllBonds() AllBonds = AllBonds()
class HAngles(object): class HAngles(Singleton):
def __repr__(self): def __repr__(self):
return 'HAngles' return 'HAngles'
HAngles = HAngles() HAngles = HAngles()
...@@ -326,7 +367,7 @@ class ForceField(object): ...@@ -326,7 +367,7 @@ class ForceField(object):
"""Register a new residue template.""" """Register a new residue template."""
if template.name in self._templates: if template.name in self._templates:
# There is already a template with this name, so check the override levels. # There is already a template with this name, so check the override levels.
existingTemplate = self._templates[template.name] existingTemplate = self._templates[template.name]
if template.overrideLevel < existingTemplate.overrideLevel: if template.overrideLevel < existingTemplate.overrideLevel:
# The existing one takes precedence, so just return. # The existing one takes precedence, so just return.
...@@ -338,9 +379,9 @@ class ForceField(object): ...@@ -338,9 +379,9 @@ class ForceField(object):
self._templateSignatures[existingSignature].remove(existingTemplate) self._templateSignatures[existingSignature].remove(existingTemplate)
else: else:
raise ValueError('Residue template %s with the same override level %d already exists.' % (template.name, template.overrideLevel)) raise ValueError('Residue template %s with the same override level %d already exists.' % (template.name, template.overrideLevel))
# Register the template. # Register the template.
self._templates[template.name] = template self._templates[template.name] = template
signature = _createResidueSignature([atom.element for atom in template.atoms]) signature = _createResidueSignature([atom.element for atom in template.atoms])
if signature in self._templateSignatures: if signature in self._templateSignatures:
...@@ -351,7 +392,7 @@ class ForceField(object): ...@@ -351,7 +392,7 @@ class ForceField(object):
def registerPatch(self, patch): def registerPatch(self, patch):
"""Register a new patch that can be applied to templates.""" """Register a new patch that can be applied to templates."""
self._patches[patch.name] = patch self._patches[patch.name] = patch
def registerTemplatePatch(self, residue, patch, patchResidueIndex): def registerTemplatePatch(self, residue, patch, patchResidueIndex):
"""Register that a particular patch can be used with a particular residue.""" """Register that a particular patch can be used with a particular residue."""
if residue not in self._templatePatches: if residue not in self._templatePatches:
...@@ -488,7 +529,7 @@ class ForceField(object): ...@@ -488,7 +529,7 @@ class ForceField(object):
else: else:
self.constraints[key] = distance self.constraints[key] = distance
system.addConstraint(atom1, atom2, distance) system.addConstraint(atom1, atom2, distance)
def recordMatchedAtomParameters(self, residue, template, matches): def recordMatchedAtomParameters(self, residue, template, matches):
"""Record parameters for atoms based on having matched a residue to a template.""" """Record parameters for atoms based on having matched a residue to a template."""
matchAtoms = dict(zip(matches, residue.atoms())) matchAtoms = dict(zip(matches, residue.atoms()))
...@@ -606,21 +647,21 @@ class ForceField(object): ...@@ -606,21 +647,21 @@ class ForceField(object):
self.deletedBonds = [] self.deletedBonds = []
self.addedExternalBonds = [] self.addedExternalBonds = []
self.deletedExternalBonds = [] self.deletedExternalBonds = []
def createPatchedTemplates(self, templates): def createPatchedTemplates(self, templates):
"""Apply this patch to a set of templates, creating new modified ones.""" """Apply this patch to a set of templates, creating new modified ones."""
if len(templates) != self.numResidues: if len(templates) != self.numResidues:
raise ValueError("Patch '%s' expected %d templates, received %d", (self.name, self.numResidues, len(templates))) raise ValueError("Patch '%s' expected %d templates, received %d", (self.name, self.numResidues, len(templates)))
# Construct a new version of each template. # Construct a new version of each template.
newTemplates = [] newTemplates = []
for index, template in enumerate(templates): for index, template in enumerate(templates):
newTemplate = ForceField._TemplateData("%s-%s" % (template.name, self.name)) newTemplate = ForceField._TemplateData("%s-%s" % (template.name, self.name))
newTemplates.append(newTemplate) newTemplates.append(newTemplate)
# Build the list of atoms in it. # Build the list of atoms in it.
for atom in template.atoms: for atom in template.atoms:
if not any(deleted.name == atom.name and deleted.residue == index for deleted in self.deletedAtoms): 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)) newTemplate.atoms.append(ForceField._TemplateAtomData(atom.name, atom.type, atom.element, atom.parameters))
...@@ -634,9 +675,9 @@ class ForceField(object): ...@@ -634,9 +675,9 @@ class ForceField(object):
if atom.name not in newAtomIndex: if atom.name not in newAtomIndex:
raise ValueError("Patch '%s' modifies nonexistent atom '%s' in template '%s'" % (self.name, atom.name, template.name)) 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) newTemplate.atoms[newAtomIndex[atom.name]] = ForceField._TemplateAtomData(atom.name, atom.type, atom.element, atom.parameters)
# Copy over the virtual sites, translating the atom indices. # Copy over the virtual sites, translating the atom indices.
indexMap = dict([(oldAtomIndex[name], newAtomIndex[name]) for name in newAtomIndex if name in oldAtomIndex]) indexMap = dict([(oldAtomIndex[name], newAtomIndex[name]) for name in newAtomIndex if name in oldAtomIndex])
for site in template.virtualSites: for site in template.virtualSites:
if site.index in indexMap and all(i in indexMap for i in site.atoms): if site.index in indexMap and all(i in indexMap for i in site.atoms):
...@@ -644,9 +685,9 @@ class ForceField(object): ...@@ -644,9 +685,9 @@ class ForceField(object):
newSite.index = indexMap[site.index] newSite.index = indexMap[site.index]
newSite.atoms = [indexMap[i] for i in site.atoms] newSite.atoms = [indexMap[i] for i in site.atoms]
newTemplate.virtualSites.append(newSite) newTemplate.virtualSites.append(newSite)
# Build the lists of bonds and external bonds. # Build the lists of bonds and external bonds.
atomMap = dict([(template.atoms[i], indexMap[i]) for i in indexMap]) 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] 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: for atom1, atom2 in template.bonds:
...@@ -668,7 +709,7 @@ class ForceField(object): ...@@ -668,7 +709,7 @@ class ForceField(object):
for atom in self.addedExternalBonds: for atom in self.addedExternalBonds:
newTemplate.addExternalBondByName(atom.name) newTemplate.addExternalBondByName(atom.name)
return newTemplates return newTemplates
class _PatchAtomData(object): class _PatchAtomData(object):
"""Inner class used to encapsulate data about an atom in a patch definition.""" """Inner class used to encapsulate data about an atom in a patch definition."""
def __init__(self, description): def __init__(self, description):
...@@ -759,7 +800,7 @@ class ForceField(object): ...@@ -759,7 +800,7 @@ class ForceField(object):
raise ValueError('%s: No parameters defined for atom type %s' % (self.forceName, t)) 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. """Return the residue template matches, or None if none are found.
Parameters Parameters
...@@ -786,14 +827,14 @@ class ForceField(object): ...@@ -786,14 +827,14 @@ class ForceField(object):
if signature in templateSignatures: if signature in templateSignatures:
allMatches = [] allMatches = []
for t in templateSignatures[signature]: for t in templateSignatures[signature]:
match = _matchResidue(res, t, bondedToAtom) match = _matchResidue(res, t, bondedToAtom, ignoreExternalBonds)
if match is not None: if match is not None:
allMatches.append((t, match)) allMatches.append((t, match))
if len(allMatches) == 1: if len(allMatches) == 1:
template = allMatches[0][0] template = allMatches[0][0]
matches = allMatches[0][1] matches = allMatches[0][1]
elif len(allMatches) > 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] return [template, matches]
def _buildBondedToAtomList(self, topology): def _buildBondedToAtomList(self, topology):
...@@ -848,7 +889,7 @@ class ForceField(object): ...@@ -848,7 +889,7 @@ class ForceField(object):
return unmatched_residues 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. """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. .. CAUTION:: This method is experimental, and its API is subject to change.
...@@ -857,7 +898,8 @@ class ForceField(object): ...@@ -857,7 +898,8 @@ class ForceField(object):
---------- ----------
topology : Topology topology : Topology
The Topology whose residues are to be checked against the forcefield residue templates. 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 Returns
------- -------
templates : list of _TemplateData templates : list of _TemplateData
...@@ -871,7 +913,7 @@ class ForceField(object): ...@@ -871,7 +913,7 @@ class ForceField(object):
templates = list() # list of templates matching the corresponding residues templates = list() # list of templates matching the corresponding residues
for res in topology.residues(): for res in topology.residues():
# Attempt to match one of the existing templates. # 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. # Raise an exception if we have found no templates that match.
if matches is None: if matches is None:
raise ValueError('No template found for residue %d (%s). %s' % (res.index+1, res.name, _findMatchErrors(self, res))) 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): ...@@ -914,7 +956,7 @@ class ForceField(object):
if signature in signatures: if signature in signatures:
# Signature is the same as an existing residue; check connectivity. # Signature is the same as an existing residue; check connectivity.
for check_residue in unique_unmatched_residues: 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: if matches is not None:
is_unique = False is_unique = False
if is_unique: if is_unique:
...@@ -926,7 +968,8 @@ class ForceField(object): ...@@ -926,7 +968,8 @@ class ForceField(object):
return [templates, unique_unmatched_residues] return [templates, unique_unmatched_residues]
def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer, def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, 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. """Construct an OpenMM System representing a Topology with this force field.
Parameters Parameters
...@@ -935,7 +978,7 @@ class ForceField(object): ...@@ -935,7 +978,7 @@ class ForceField(object):
The Topology for which to create a System The Topology for which to create a System
nonbondedMethod : object=NoCutoff nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are 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 nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions The cutoff distance to use for nonbonded interactions
constraints : object=None constraints : object=None
...@@ -951,16 +994,21 @@ class ForceField(object): ...@@ -951,16 +994,21 @@ class ForceField(object):
added to a hydrogen is subtracted from the heavy atom to keep added to a hydrogen is subtracted from the heavy atom to keep
their total mass the same. their total mass the same.
residueTemplates : dict=dict() residueTemplates : dict=dict()
Key: Topology Residue object Key: Topology Residue object
Value: string, name of _TemplateData residue template object to use for Value: string, name of _TemplateData residue template object to use for (Key) residue.
(Key) residue This allows user to specify which template to apply to particular Residues
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+
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).
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 args
Arbitrary additional keyword arguments may also be specified. Arbitrary additional keyword arguments may also be specified.
This allows extra parameters to be specified that are specific to This allows extra parameters to be specified that are specific to
particular force fields. particular force fields.
Returns Returns
------- -------
...@@ -998,34 +1046,34 @@ class ForceField(object): ...@@ -998,34 +1046,34 @@ class ForceField(object):
if res in residueTemplates: if res in residueTemplates:
tname = residueTemplates[res] tname = residueTemplates[res]
template = self._templates[tname] template = self._templates[tname]
matches = _matchResidue(res, template, bondedToAtom) matches = _matchResidue(res, template, bondedToAtom, ignoreExternalBonds)
if matches is None: if matches is None:
raise Exception('User-supplied template %s does not match the residue %d (%s)' % (tname, res.index+1, res.name)) raise Exception('User-supplied template %s does not match the residue %d (%s)' % (tname, res.index+1, res.name))
else: else:
# Attempt to match one of the existing templates. # 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: if matches is None:
unmatchedResidues.append(res) unmatchedResidues.append(res)
else: else:
data.recordMatchedAtomParameters(res, template, matches) data.recordMatchedAtomParameters(res, template, matches)
# Try to apply patches to find matches for any unmatched residues. # Try to apply patches to find matches for any unmatched residues.
if len(unmatchedResidues) > 0: 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 # 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). # new templates (and potentially atom types/parameters).
for res in unmatchedResidues: for res in unmatchedResidues:
# A template might have been generated on an earlier iteration of this loop. # 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: if matches is None:
# Try all generators. # Try all generators.
for generator in self._templateGenerators: for generator in self._templateGenerators:
if generator(self, res): if generator(self, res):
# This generator has registered a new residue template that should match. # 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: if matches is None:
# Something went wrong because the generated template does not match the residue signature. # 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))) 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): ...@@ -1254,7 +1302,7 @@ def _createResidueSignature(elements):
s += element.symbol+str(count) s += element.symbol+str(count)
return s return s
def _matchResidue(res, template, bondedToAtom): def _matchResidue(res, template, bondedToAtom, ignoreExternalBonds=False):
"""Determine whether a residue matches a template and return a list of corresponding atoms. """Determine whether a residue matches a template and return a list of corresponding atoms.
Parameters Parameters
...@@ -1265,6 +1313,8 @@ def _matchResidue(res, template, bondedToAtom): ...@@ -1265,6 +1313,8 @@ def _matchResidue(res, template, bondedToAtom):
The template to compare it to The template to compare it to
bondedToAtom : list bondedToAtom : list
Enumerates which other atoms each atom is bonded to Enumerates which other atoms each atom is bonded to
ignoreExternalBonds : bool
If true, ignore external bonds when matching templates
Returns Returns
------- -------
...@@ -1287,7 +1337,7 @@ def _matchResidue(res, template, bondedToAtom): ...@@ -1287,7 +1337,7 @@ def _matchResidue(res, template, bondedToAtom):
for atom in atoms: for atom in atoms:
bonds = [renumberAtoms[x] for x in bondedToAtom[atom.index] if x in renumberAtoms] bonds = [renumberAtoms[x] for x in bondedToAtom[atom.index] if x in renumberAtoms]
bondedTo.append(bonds) 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 # For each unique combination of element and number of bonds, make sure the residue and
# template have the same number of atoms. # template have the same number of atoms.
...@@ -1300,15 +1350,15 @@ def _matchResidue(res, template, bondedToAtom): ...@@ -1300,15 +1350,15 @@ def _matchResidue(res, template, bondedToAtom):
residueTypeCount[key] += 1 residueTypeCount[key] += 1
templateTypeCount = {} templateTypeCount = {}
for i, atom in enumerate(template.atoms): 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: if key not in templateTypeCount:
templateTypeCount[key] = 1 templateTypeCount[key] = 1
templateTypeCount[key] += 1 templateTypeCount[key] += 1
if residueTypeCount != templateTypeCount: if residueTypeCount != templateTypeCount:
return None return None
# Identify template atoms that could potentially be matches for each atom. # Identify template atoms that could potentially be matches for each atom.
candidates = [[] for i in range(numAtoms)] candidates = [[] for i in range(numAtoms)]
for i in range(numAtoms): for i in range(numAtoms):
for j, atom in enumerate(template.atoms): for j, atom in enumerate(template.atoms):
...@@ -1316,13 +1366,13 @@ def _matchResidue(res, template, bondedToAtom): ...@@ -1316,13 +1366,13 @@ def _matchResidue(res, template, bondedToAtom):
continue continue
if len(atom.bondedTo) != len(bondedTo[i]): if len(atom.bondedTo) != len(bondedTo[i]):
continue continue
if atom.externalBonds != externalBonds[i]: if not ignoreExternalBonds and atom.externalBonds != externalBonds[i]:
continue continue
candidates[i].append(j) candidates[i].append(j)
# Find an optimal ordering for matching atoms. This means 1) start with the one that has the fewest options, # 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. # and 2) follow with ones that are bonded to an already matched atom.
searchOrder = [] searchOrder = []
atomsToOrder = set(range(numAtoms)) atomsToOrder = set(range(numAtoms))
efficientAtomSet = set() efficientAtomSet = set()
...@@ -1390,12 +1440,12 @@ def _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, position ...@@ -1390,12 +1440,12 @@ def _findAtomMatches(template, bondedTo, matches, hasMatch, candidates, position
return False return False
def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom): def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom, ignoreExternalBonds):
"""Try to apply patches to find matches for residues.""" """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 # 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 # to a single template. The number of these is usually not too large, and they often cover a large fraction
# of residues. # of residues.
patchedTemplateSignatures = {} patchedTemplateSignatures = {}
patchedTemplates = {} patchedTemplates = {}
for name, template in forcefield._templates.items(): for name, template in forcefield._templates.items():
...@@ -1411,23 +1461,23 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom): ...@@ -1411,23 +1461,23 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom):
patchedTemplateSignatures[signature].append(patchedTemplate) patchedTemplateSignatures[signature].append(patchedTemplate)
else: else:
patchedTemplateSignatures[signature] = [patchedTemplate] patchedTemplateSignatures[signature] = [patchedTemplate]
# Now see if any of those templates matches any of the residues. # Now see if any of those templates matches any of the residues.
unmatchedResidues = [] unmatchedResidues = []
for res in residues: for res in residues:
[template, matches] = forcefield._getResidueTemplateMatches(res, bondedToAtom, patchedTemplateSignatures) [template, matches] = forcefield._getResidueTemplateMatches(res, bondedToAtom, patchedTemplateSignatures, ignoreExternalBonds)
if matches is None: if matches is None:
unmatchedResidues.append(res) unmatchedResidues.append(res)
else: else:
data.recordMatchedAtomParameters(res, template, matches) data.recordMatchedAtomParameters(res, template, matches)
if len(unmatchedResidues) == 0: if len(unmatchedResidues) == 0:
return [] return []
# We need to consider multi-residue patches. This can easily lead to a combinatorial explosion, so we make a simplifying # 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 # 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). Record all multi-residue patches, and the templates they can be applied to.
patches = {} patches = {}
maxPatchSize = 0 maxPatchSize = 0
for patch in forcefield._patches.values(): for patch in forcefield._patches.values():
...@@ -1443,9 +1493,9 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom): ...@@ -1443,9 +1493,9 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom):
patches[patchName][patchResidueIndex].append(forcefield._templates[templateName]) patches[patchName][patchResidueIndex].append(forcefield._templates[templateName])
if templateName in patchedTemplates: if templateName in patchedTemplates:
patches[patchName][patchResidueIndex] += patchedTemplates[templateName] patches[patchName][patchResidueIndex] += patchedTemplates[templateName]
# Record which unmatched residues are bonded to each other. # Record which unmatched residues are bonded to each other.
bonds = set() bonds = set()
topology = residues[0].chain.topology topology = residues[0].chain.topology
for atom1, atom2 in topology.bonds(): for atom1, atom2 in topology.bonds():
...@@ -1456,26 +1506,26 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom): ...@@ -1456,26 +1506,26 @@ def _applyPatchesToMatchResidues(forcefield, data, residues, bondedToAtom):
bond = tuple(sorted((res1, res2), key=lambda x: x.index)) bond = tuple(sorted((res1, res2), key=lambda x: x.index))
if bond not in bonds: if bond not in bonds:
bonds.add(bond) bonds.add(bond)
# Identify clusters of unmatched residues that are all bonded to each other. These are the ones we'll # 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. # try to apply multi-residue patches to.
clusterSize = 2 clusterSize = 2
clusters = bonds clusters = bonds
while clusterSize <= maxPatchSize: while clusterSize <= maxPatchSize:
# Try to apply patches to clusters of this size. # Try to apply patches to clusters of this size.
for patchName in patches: for patchName in patches:
patch = forcefield._patches[patchName] patch = forcefield._patches[patchName]
if patch.numResidues == clusterSize: 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 cluster in matchedClusters:
for residue in cluster: for residue in cluster:
unmatchedResidues.remove(residue) unmatchedResidues.remove(residue)
bonds = set(bond for bond in bonds if bond[0] in unmatchedResidues and bond[1] in unmatchedResidues) 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. # Now extend the clusters to find ones of the next size up.
largerClusters = set() largerClusters = set()
for cluster in clusters: for cluster in clusters:
for bond in bonds: for bond in bonds:
...@@ -1503,34 +1553,34 @@ def _generatePatchedSingleResidueTemplates(template, patches, index, newTemplate ...@@ -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, # This probably means the patch is inconsistent with another one that has already been applied,
# so just ignore it. # so just ignore it.
patchedTemplate = None patchedTemplate = None
# Call this function recursively to generate combinations of patches. # Call this function recursively to generate combinations of patches.
if index+1 < len(patches): if index+1 < len(patches):
_generatePatchedSingleResidueTemplates(template, patches, index+1, newTemplates) _generatePatchedSingleResidueTemplates(template, patches, index+1, newTemplates)
if patchedTemplate is not None: if patchedTemplate is not None:
_generatePatchedSingleResidueTemplates(patchedTemplate, patches, index+1, newTemplates) _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.""" """Apply a multi-residue patch to templates, then try to match them against clusters of residues."""
matchedClusters = [] matchedClusters = []
selectedTemplates = [None]*patch.numResidues 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 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.""" """This is called recursively to apply a multi-residue patch to all possible combinations of templates."""
if index < patch.numResidues: if index < patch.numResidues:
for template in candidateTemplates[index]: for template in candidateTemplates[index]:
selectedTemplates[index] = template 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: else:
# We're at the deepest level of the recursion. We've selected a template for each residue, so apply the patch, # 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. # then try to match it against clusters.
try: try:
patchedTemplates = patch.createPatchedTemplates(selectedTemplates) patchedTemplates = patch.createPatchedTemplates(selectedTemplates)
except: except:
...@@ -1543,7 +1593,7 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT ...@@ -1543,7 +1593,7 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT
for residues in itertools.permutations(cluster): for residues in itertools.permutations(cluster):
residueMatches = [] residueMatches = []
for residue, template in zip(residues, patchedTemplates): for residue, template in zip(residues, patchedTemplates):
matches = _matchResidue(residue, template, bondedToAtom) matches = _matchResidue(residue, template, bondedToAtom, ignoreExternalBonds)
if matches is None: if matches is None:
residueMatches = None residueMatches = None
break break
...@@ -1551,18 +1601,18 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT ...@@ -1551,18 +1601,18 @@ def _applyMultiResiduePatch(data, clusters, patch, candidateTemplates, selectedT
residueMatches.append(matches) residueMatches.append(matches)
if residueMatches is not None: if residueMatches is not None:
# We successfully matched the template to the residues. Record the parameters. # We successfully matched the template to the residues. Record the parameters.
for i in range(patch.numResidues): for i in range(patch.numResidues):
data.recordMatchedAtomParameters(residues[i], patchedTemplates[i], residueMatches[i]) data.recordMatchedAtomParameters(residues[i], patchedTemplates[i], residueMatches[i])
newlyMatchedClusters.append(cluster) newlyMatchedClusters.append(cluster)
break break
# Record which clusters were successfully matched. # Record which clusters were successfully matched.
matchedClusters += newlyMatchedClusters matchedClusters += newlyMatchedClusters
for cluster in newlyMatchedClusters: for cluster in newlyMatchedClusters:
clusters.remove(cluster) clusters.remove(cluster)
def _findMatchErrors(forcefield, res): def _findMatchErrors(forcefield, res):
"""Try to guess why a residue failed to match any template and return an error message.""" """Try to guess why a residue failed to match any template and return an error message."""
...@@ -1664,6 +1714,7 @@ class HarmonicBondGenerator(object): ...@@ -1664,6 +1714,7 @@ class HarmonicBondGenerator(object):
def __init__(self, forcefield): def __init__(self, forcefield):
self.ff = forcefield self.ff = forcefield
self.bondsForAtomType = defaultdict(set)
self.types1 = [] self.types1 = []
self.types2 = [] self.types2 = []
self.length = [] self.length = []
...@@ -1672,8 +1723,13 @@ class HarmonicBondGenerator(object): ...@@ -1672,8 +1723,13 @@ class HarmonicBondGenerator(object):
def registerBond(self, parameters): def registerBond(self, parameters):
types = self.ff._findAtomTypes(parameters, 2) types = self.ff._findAtomTypes(parameters, 2)
if None not in types: if None not in types:
index = len(self.types1)
self.types1.append(types[0]) self.types1.append(types[0])
self.types2.append(types[1]) 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.length.append(_convertParameterToNumber(parameters['length']))
self.k.append(_convertParameterToNumber(parameters['k'])) self.k.append(_convertParameterToNumber(parameters['k']))
...@@ -1699,7 +1755,7 @@ class HarmonicBondGenerator(object): ...@@ -1699,7 +1755,7 @@ class HarmonicBondGenerator(object):
for bond in data.bonds: for bond in data.bonds:
type1 = data.atomType[data.atoms[bond.atom1]] type1 = data.atomType[data.atoms[bond.atom1]]
type2 = data.atomType[data.atoms[bond.atom2]] type2 = data.atomType[data.atoms[bond.atom2]]
for i in range(len(self.types1)): for i in self.bondsForAtomType[type1]:
types1 = self.types1[i] types1 = self.types1[i]
types2 = self.types2[i] types2 = self.types2[i]
if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1): if (type1 in types1 and type2 in types2) or (type1 in types2 and type2 in types1):
...@@ -1719,6 +1775,7 @@ class HarmonicAngleGenerator(object): ...@@ -1719,6 +1775,7 @@ class HarmonicAngleGenerator(object):
def __init__(self, forcefield): def __init__(self, forcefield):
self.ff = forcefield self.ff = forcefield
self.anglesForAtom2Type = defaultdict(list)
self.types1 = [] self.types1 = []
self.types2 = [] self.types2 = []
self.types3 = [] self.types3 = []
...@@ -1728,9 +1785,12 @@ class HarmonicAngleGenerator(object): ...@@ -1728,9 +1785,12 @@ class HarmonicAngleGenerator(object):
def registerAngle(self, parameters): def registerAngle(self, parameters):
types = self.ff._findAtomTypes(parameters, 3) types = self.ff._findAtomTypes(parameters, 3)
if None not in types: if None not in types:
index = len(self.types1)
self.types1.append(types[0]) self.types1.append(types[0])
self.types2.append(types[1]) self.types2.append(types[1])
self.types3.append(types[2]) self.types3.append(types[2])
for t in types[1]:
self.anglesForAtom2Type[t].append(index)
self.angle.append(_convertParameterToNumber(parameters['angle'])) self.angle.append(_convertParameterToNumber(parameters['angle']))
self.k.append(_convertParameterToNumber(parameters['k'])) self.k.append(_convertParameterToNumber(parameters['k']))
...@@ -1757,7 +1817,7 @@ class HarmonicAngleGenerator(object): ...@@ -1757,7 +1817,7 @@ class HarmonicAngleGenerator(object):
type1 = data.atomType[data.atoms[angle[0]]] type1 = data.atomType[data.atoms[angle[0]]]
type2 = data.atomType[data.atoms[angle[1]]] type2 = data.atomType[data.atoms[angle[1]]]
type3 = data.atomType[data.atoms[angle[2]]] type3 = data.atomType[data.atoms[angle[2]]]
for i in range(len(self.types1)): for i in self.anglesForAtom2Type[type2]:
types1 = self.types1[i] types1 = self.types1[i]
types2 = self.types2[i] types2 = self.types2[i]
types3 = self.types3[i] types3 = self.types3[i]
...@@ -2209,7 +2269,8 @@ class NonbondedGenerator(object): ...@@ -2209,7 +2269,8 @@ class NonbondedGenerator(object):
CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic, CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic, CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
Ewald:mm.NonbondedForce.Ewald, Ewald:mm.NonbondedForce.Ewald,
PME:mm.NonbondedForce.PME} PME:mm.NonbondedForce.PME,
LJPME:mm.NonbondedForce.LJPME}
if nonbondedMethod not in methodMap: if nonbondedMethod not in methodMap:
raise ValueError('Illegal nonbonded method for NonbondedForce') raise ValueError('Illegal nonbonded method for NonbondedForce')
force = mm.NonbondedForce() force = mm.NonbondedForce()
...@@ -2297,7 +2358,7 @@ class LennardJonesGenerator(object): ...@@ -2297,7 +2358,7 @@ class LennardJonesGenerator(object):
reverseMap[typeMap[typeValue]] = typeValue reverseMap[typeMap[typeValue]] = typeValue
# Now everything is assigned. Create the A- and B-coefficient arrays # Now everything is assigned. Create the A- and B-coefficient arrays
acoef = [0]*(numLjTypes*numLjTypes) acoef = [0]*(numLjTypes*numLjTypes)
bcoef = acoef[:] bcoef = acoef[:]
for m in range(numLjTypes): for m in range(numLjTypes):
...@@ -2321,7 +2382,7 @@ class LennardJonesGenerator(object): ...@@ -2321,7 +2382,7 @@ class LennardJonesGenerator(object):
self.force.addTabulatedFunction('acoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, acoef)) self.force.addTabulatedFunction('acoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, acoef))
self.force.addTabulatedFunction('bcoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, bcoef)) self.force.addTabulatedFunction('bcoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, bcoef))
self.force.addPerParticleParameter('type') self.force.addPerParticleParameter('type')
if nonbondedMethod in [CutoffPeriodic, Ewald, PME]: if nonbondedMethod in [CutoffPeriodic, Ewald, PME, LJPME]:
self.force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) self.force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
elif nonbondedMethod is NoCutoff: elif nonbondedMethod is NoCutoff:
self.force.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff) self.force.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff)
...@@ -2340,11 +2401,11 @@ class LennardJonesGenerator(object): ...@@ -2340,11 +2401,11 @@ class LennardJonesGenerator(object):
def postprocessSystem(self, sys, data, args): def postprocessSystem(self, sys, data, args):
# Create the exceptions. # Create the exceptions.
bondIndices = _findBondsForExclusions(data, sys) bondIndices = _findBondsForExclusions(data, sys)
if self.lj14scale == 1: if self.lj14scale == 1:
# Just exclude the 1-2 and 1-3 interactions. # Just exclude the 1-2 and 1-3 interactions.
self.force.createExclusionsFromBonds(bondIndices, 2) self.force.createExclusionsFromBonds(bondIndices, 2)
else: else:
forceCopy = deepcopy(self.force) forceCopy = deepcopy(self.force)
...@@ -2352,7 +2413,7 @@ class LennardJonesGenerator(object): ...@@ -2352,7 +2413,7 @@ class LennardJonesGenerator(object):
self.force.createExclusionsFromBonds(bondIndices, 3) self.force.createExclusionsFromBonds(bondIndices, 3)
if self.force.getNumExclusions() > forceCopy.getNumExclusions() and self.lj14scale != 0: 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. # 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 = mm.CustomBondForce('%g*epsilon*((sigma/r)^12-(sigma/r)^6)' % (4*self.lj14scale))
bonded.addPerBondParameter('sigma') bonded.addPerBondParameter('sigma')
bonded.addPerBondParameter('epsilon') bonded.addPerBondParameter('epsilon')
...@@ -2658,6 +2719,7 @@ class CustomNonbondedGenerator(object): ...@@ -2658,6 +2719,7 @@ class CustomNonbondedGenerator(object):
generator.perParticleParams.append(param.attrib['name']) generator.perParticleParams.append(param.attrib['name'])
generator.params = ForceField._AtomTypeParameters(ff, 'CustomNonbondedForce', 'Atom', generator.perParticleParams) generator.params = ForceField._AtomTypeParameters(ff, 'CustomNonbondedForce', 'Atom', generator.perParticleParams)
generator.params.parseDefinitions(element) generator.params.parseDefinitions(element)
generator.functions += _parseFunctions(element)
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.CustomNonbondedForce.NoCutoff, methodMap = {NoCutoff:mm.CustomNonbondedForce.NoCutoff,
...@@ -2670,19 +2732,7 @@ class CustomNonbondedGenerator(object): ...@@ -2670,19 +2732,7 @@ class CustomNonbondedGenerator(object):
force.addGlobalParameter(param, self.globalParams[param]) force.addGlobalParameter(param, self.globalParams[param])
for param in self.perParticleParams: for param in self.perParticleParams:
force.addPerParticleParameter(param) force.addPerParticleParameter(param)
for (name, type, values, params) in self.functions: _createFunctions(force, 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))
for atom in data.atoms: for atom in data.atoms:
values = self.params.getAtomParameters(atom, data) values = self.params.getAtomParameters(atom, data)
force.addParticle(values) force.addParticle(values)
...@@ -2729,19 +2779,7 @@ class CustomGBGenerator(object): ...@@ -2729,19 +2779,7 @@ class CustomGBGenerator(object):
generator.computedValues.append((value.attrib['name'], value.text, computationMap[value.attrib['type']])) generator.computedValues.append((value.attrib['name'], value.text, computationMap[value.attrib['type']]))
for term in element.findall('EnergyTerm'): for term in element.findall('EnergyTerm'):
generator.energyTerms.append((term.text, computationMap[term.attrib['type']])) generator.energyTerms.append((term.text, computationMap[term.attrib['type']]))
for function in element.findall("Function"): generator.functions += _parseFunctions(element)
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))
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
methodMap = {NoCutoff:mm.CustomGBForce.NoCutoff, methodMap = {NoCutoff:mm.CustomGBForce.NoCutoff,
...@@ -2758,19 +2796,7 @@ class CustomGBGenerator(object): ...@@ -2758,19 +2796,7 @@ class CustomGBGenerator(object):
force.addComputedValue(value[0], value[1], value[2]) force.addComputedValue(value[0], value[1], value[2])
for term in self.energyTerms: for term in self.energyTerms:
force.addEnergyTerm(term[0], term[1]) force.addEnergyTerm(term[0], term[1])
for (name, type, values, params) in self.functions: _createFunctions(force, 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))
for atom in data.atoms: for atom in data.atoms:
values = self.params.getAtomParameters(atom, data) values = self.params.getAtomParameters(atom, data)
force.addParticle(values) force.addParticle(values)
...@@ -2781,6 +2807,171 @@ class CustomGBGenerator(object): ...@@ -2781,6 +2807,171 @@ class CustomGBGenerator(object):
parsers["CustomGBForce"] = CustomGBGenerator.parseElement 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 ## @private
class CustomManyParticleGenerator(object): class CustomManyParticleGenerator(object):
"""A CustomManyParticleGenerator constructs a CustomManyParticleForce.""" """A CustomManyParticleGenerator constructs a CustomManyParticleForce."""
...@@ -4801,7 +4992,7 @@ class AmoebaMultipoleGenerator(object): ...@@ -4801,7 +4992,7 @@ class AmoebaMultipoleGenerator(object):
bondedAtomZ = data.atoms[bondedAtomZIndex] bondedAtomZ = data.atoms[bondedAtomZIndex]
if (kx == 0 and kz == bondedAtomZType): if (kx == 0 and kz == bondedAtomZType):
kz = bondedAtomZIndex zaxis = bondedAtomZIndex
savedMultipoleDict = multipoleDict savedMultipoleDict = multipoleDict
hit = 5 hit = 5
......
...@@ -552,7 +552,7 @@ class GromacsTopFile(object): ...@@ -552,7 +552,7 @@ class GromacsTopFile(object):
---------- ----------
nonbondedMethod : object=NoCutoff nonbondedMethod : object=NoCutoff
The method to use for nonbonded interactions. Allowed values are 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 nonbondedCutoff : distance=1*nanometer
The cutoff distance to use for nonbonded interactions The cutoff distance to use for nonbonded interactions
constraints : object=None constraints : object=None
...@@ -570,7 +570,7 @@ class GromacsTopFile(object): ...@@ -570,7 +570,7 @@ class GromacsTopFile(object):
The solvent dielectric constant to use in the implicit solvent The solvent dielectric constant to use in the implicit solvent
model. model.
ewaldErrorTolerance : float=0.0005 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 removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None hydrogenMass : mass=None
...@@ -589,7 +589,7 @@ class GromacsTopFile(object): ...@@ -589,7 +589,7 @@ class GromacsTopFile(object):
boxVectors = self.topology.getPeriodicBoxVectors() boxVectors = self.topology.getPeriodicBoxVectors()
if boxVectors is not None: if boxVectors is not None:
sys.setDefaultPeriodicBoxVectors(*boxVectors) 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') raise ValueError('Illegal nonbonded method for a non-periodic system')
nb = mm.NonbondedForce() nb = mm.NonbondedForce()
sys.addForce(nb) sys.addForce(nb)
...@@ -772,7 +772,7 @@ class GromacsTopFile(object): ...@@ -772,7 +772,7 @@ class GromacsTopFile(object):
if periodic is None: if periodic is None:
periodic = mm.PeriodicTorsionForce() periodic = mm.PeriodicTorsionForce()
sys.addForce(periodic) 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': elif dihedralType == '2':
# Harmonic torsion # Harmonic torsion
k = float(params[6]) k = float(params[6])
...@@ -877,7 +877,8 @@ class GromacsTopFile(object): ...@@ -877,7 +877,8 @@ class GromacsTopFile(object):
ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic, ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic, ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
ff.Ewald:mm.NonbondedForce.Ewald, 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.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff) nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance) nb.setEwaldErrorTolerance(ewaldErrorTolerance)
......
...@@ -776,6 +776,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -776,6 +776,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
force.setNonbondedMethod(mm.NonbondedForce.Ewald) force.setNonbondedMethod(mm.NonbondedForce.Ewald)
elif nonbondedMethod == 'PME': elif nonbondedMethod == 'PME':
force.setNonbondedMethod(mm.NonbondedForce.PME) force.setNonbondedMethod(mm.NonbondedForce.PME)
elif nonbondedMethod == 'LJPME':
force.setNonbondedMethod(mm.NonbondedForce.LJPME)
else: else:
raise Exception("Cutoff method not understood.") raise Exception("Cutoff method not understood.")
...@@ -885,7 +887,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -885,7 +887,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
ii, jj, chg, sig, eps = force.getExceptionParameters(i) ii, jj, chg, sig, eps = force.getExceptionParameters(i)
cforce.addExclusion(ii, jj) cforce.addExclusion(ii, jj)
# Now set the various properties based on the NonbondedForce object # 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.setNonbondedMethod(cforce.CutoffPeriodic)
cforce.setCutoffDistance(nonbondedCutoff) cforce.setCutoffDistance(nonbondedCutoff)
cforce.setUseLongRangeCorrection(True) cforce.setUseLongRangeCorrection(True)
......
...@@ -353,13 +353,13 @@ def _createEnergyTerms(force, solventDielectric, soluteDielectric, SA, cutoff, k ...@@ -353,13 +353,13 @@ def _createEnergyTerms(force, solventDielectric, soluteDielectric, SA, cutoff, k
if cutoff is not None: if cutoff is not None:
params += "; cutoff=%.16g" % cutoff params += "; cutoff=%.16g" % cutoff
if kappa > 0: 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) CustomGBForce.SingleParticle)
elif kappa < 0: elif kappa < 0:
# Do kappa check here to avoid repeating code everywhere # Do kappa check here to avoid repeating code everywhere
raise ValueError('kappa/ionic strength must be >= 0') raise ValueError('kappa/ionic strength must be >= 0')
else: 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) CustomGBForce.SingleParticle)
if SA=='ACE': if SA=='ACE':
force.addEnergyTerm("28.3919551*(radius+0.14)^2*(radius/B)^6; radius=or+offset"+params, CustomGBForce.SingleParticle) 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 ...@@ -367,17 +367,17 @@ def _createEnergyTerms(force, solventDielectric, soluteDielectric, SA, cutoff, k
raise ValueError('Unknown surface area method: '+SA) raise ValueError('Unknown surface area method: '+SA)
if cutoff is None: if cutoff is None:
if kappa > 0: 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) "f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
else: 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) "f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
else: else:
if kappa > 0: 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) "f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
else: 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) "f=sqrt(r^2+B1*B2*exp(-r^2/(4*B1*B2)))"+params, CustomGBForce.ParticlePairNoExclusions)
...@@ -476,7 +476,7 @@ class CustomAmberGBForceBase(CustomGBForce): ...@@ -476,7 +476,7 @@ class CustomAmberGBForceBase(CustomGBForce):
class GBSAHCTForce(CustomAmberGBForceBase): class GBSAHCTForce(CustomAmberGBForceBase):
"""This class is equivalent to Amber ``igb=1`` """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 Parameters
---------- ----------
...@@ -499,7 +499,7 @@ class GBSAHCTForce(CustomAmberGBForceBase): ...@@ -499,7 +499,7 @@ class GBSAHCTForce(CustomAmberGBForceBase):
cutoff=None, kappa=0.0): cutoff=None, kappa=0.0):
CustomAmberGBForceBase.__init__(self) CustomAmberGBForceBase.__init__(self)
self.addPerParticleParameter("q") self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled 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);" 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): ...@@ -537,7 +537,7 @@ class GBSAHCTForce(CustomAmberGBForceBase):
class GBSAOBC1Force(CustomAmberGBForceBase): class GBSAOBC1Force(CustomAmberGBForceBase):
"""This class is equivalent to Amber ``igb=2`` """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 Parameters
---------- ----------
...@@ -561,7 +561,7 @@ class GBSAOBC1Force(CustomAmberGBForceBase): ...@@ -561,7 +561,7 @@ class GBSAOBC1Force(CustomAmberGBForceBase):
CustomAmberGBForceBase.__init__(self) CustomAmberGBForceBase.__init__(self)
self.addPerParticleParameter("q") self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled 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);" 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): ...@@ -599,7 +599,7 @@ class GBSAOBC1Force(CustomAmberGBForceBase):
class GBSAOBC2Force(GBSAOBC1Force): class GBSAOBC2Force(GBSAOBC1Force):
"""This class is equivalent to Amber ``igb=5`` """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 Parameters
---------- ----------
...@@ -625,7 +625,7 @@ class GBSAOBC2Force(GBSAOBC1Force): ...@@ -625,7 +625,7 @@ class GBSAOBC2Force(GBSAOBC1Force):
# is different. We inherit for getStandardParameters. # is different. We inherit for getStandardParameters.
CustomAmberGBForceBase.__init__(self) CustomAmberGBForceBase.__init__(self)
self.addPerParticleParameter("q") self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled 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);" 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): ...@@ -641,7 +641,7 @@ class GBSAOBC2Force(GBSAOBC1Force):
class GBSAGBnForce(CustomAmberGBForceBase): class GBSAGBnForce(CustomAmberGBForceBase):
"""This class is equivalent to Amber ``igb=7`` """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 Parameters
---------- ----------
...@@ -746,7 +746,7 @@ class GBSAGBnForce(CustomAmberGBForceBase): ...@@ -746,7 +746,7 @@ class GBSAGBnForce(CustomAmberGBForceBase):
CustomGBForce.addParticle(self, p + [radIndex]) CustomGBForce.addParticle(self, p + [radIndex])
def _addEnergyTerms(self): def _addEnergyTerms(self):
self.addPerParticleParameter("q") self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled offset radius self.addPerParticleParameter("sr") # Scaled offset radius
self.addPerParticleParameter("radindex") self.addPerParticleParameter("radindex")
...@@ -834,7 +834,7 @@ class GBSAGBn2Force(GBSAGBnForce): ...@@ -834,7 +834,7 @@ class GBSAGBn2Force(GBSAGBnForce):
return radii return radii
def _addEnergyTerms(self): def _addEnergyTerms(self):
self.addPerParticleParameter("q") self.addPerParticleParameter("charge")
self.addPerParticleParameter("or") # Offset radius self.addPerParticleParameter("or") # Offset radius
self.addPerParticleParameter("sr") # Scaled offset radius self.addPerParticleParameter("sr") # Scaled offset radius
self.addPerParticleParameter("alpha") 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): ...@@ -186,8 +186,7 @@ class Modeller(object):
def convertWater(self, model='tip3p'): def convertWater(self, model='tip3p'):
"""Convert all water molecules to a different water model. """Convert all water molecules to a different water model.
@deprecated Use addExtraParticles() instead. It performs the same @deprecated Use addExtraParticles() instead. It performs the same function but in a more general way.
function but in a more general way.
Parameters Parameters
---------- ----------
...@@ -408,12 +407,17 @@ class Modeller(object): ...@@ -408,12 +407,17 @@ class Modeller(object):
if len(self.positions) == 0: if len(self.positions) == 0:
positions = [] positions = []
else: else:
positions = self.positions.value_in_unit(nanometer) positions = self.positions.value_in_unit(nanometer)[:]
cells = {} cells = {}
numCells = tuple((max(1, int(floor(box[i]/maxCutoff))) for i in range(3))) 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))) cellSize = tuple((box[i]/numCells[i] for i in range(3)))
for i in range(len(positions)): 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: if cell in cells:
cells[cell].append(i) cells[cell].append(i)
else: else:
...@@ -871,7 +875,7 @@ class Modeller(object): ...@@ -871,7 +875,7 @@ class Modeller(object):
# and causes hydrogens to spread out evenly. # and causes hydrogens to spread out evenly.
system = System() system = System()
nonbonded = CustomNonbondedForce('100/((r/0.1)^4+1)') nonbonded = CustomNonbondedForce('100/(r/0.1)^4')
nonbonded.setNonbondedMethod(CustomNonbondedForce.CutoffNonPeriodic); nonbonded.setNonbondedMethod(CustomNonbondedForce.CutoffNonPeriodic);
nonbonded.setCutoffDistance(1*nanometer) nonbonded.setCutoffDistance(1*nanometer)
bonds = HarmonicBondForce() bonds = HarmonicBondForce()
...@@ -1006,7 +1010,7 @@ class Modeller(object): ...@@ -1006,7 +1010,7 @@ class Modeller(object):
signature = _createResidueSignature([atom.element for atom in residue.atoms()]) signature = _createResidueSignature([atom.element for atom in residue.atoms()])
if signature in forcefield._templateSignatures: if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]: 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 matchFound = True
if matchFound: if matchFound:
# Just copy the residue over. # Just copy the residue over.
...@@ -1025,7 +1029,7 @@ class Modeller(object): ...@@ -1025,7 +1029,7 @@ class Modeller(object):
if signature in forcefield._templateSignatures: if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]: for t in forcefield._templateSignatures[signature]:
if t in templatesNoEP: if t in templatesNoEP:
matches = _matchResidue(residueNoEP, templatesNoEP[t], bondedToAtomNoEP) matches = _matchResidue(residueNoEP, templatesNoEP[t], bondedToAtomNoEP, False)
if matches is not None: if matches is not None:
template = t; template = t;
# Record the corresponding atoms. # Record the corresponding atoms.
......
...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of ...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org. 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 Authors: Peter Eastman
Contributors: Contributors:
...@@ -32,12 +32,41 @@ from __future__ import absolute_import ...@@ -32,12 +32,41 @@ from __future__ import absolute_import
__author__ = "Peter Eastman" __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
from collections import namedtuple
import os import os
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from simtk.openmm.vec3 import Vec3 from simtk.openmm.vec3 import Vec3
from simtk.openmm.app.internal.singleton import Singleton
from simtk.unit import nanometers, sqrt, is_quantity from simtk.unit import nanometers, sqrt, is_quantity
from copy import deepcopy 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): class Topology(object):
"""Topology stores the topological information about a system. """Topology stores the topological information about a system.
...@@ -155,7 +184,7 @@ class Topology(object): ...@@ -155,7 +184,7 @@ class Topology(object):
residue._atoms.append(atom) residue._atoms.append(atom)
return 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. """Create a new bond and add it to the Topology.
Parameters Parameters
...@@ -164,8 +193,13 @@ class Topology(object): ...@@ -164,8 +193,13 @@ class Topology(object):
The first Atom connected by the bond The first Atom connected by the bond
atom2 : Atom atom2 : Atom
The second Atom connected by the bond 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): def chains(self):
"""Iterate over all Chains in the Topology.""" """Iterate over all Chains in the Topology."""
...@@ -387,7 +421,7 @@ class Residue(object): ...@@ -387,7 +421,7 @@ class Residue(object):
return "<Residue %d (%s) of chain %d>" % (self.index, self.name, self.chain.index) return "<Residue %d (%s) of chain %d>" % (self.index, self.name, self.chain.index)
class Atom(object): 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): def __init__(self, name, element, index, residue, id):
"""Construct a new Atom. You should call addAtom() on the Topology instead of calling this directly.""" """Construct a new Atom. You should call addAtom() on the Topology instead of calling this directly."""
...@@ -404,3 +438,32 @@ class Atom(object): ...@@ -404,3 +438,32 @@ class Atom(object):
def __repr__(self): 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) 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 ...@@ -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 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE. USE OR OTHER DEALINGS IN THE SOFTWARE.
""" """
from __future__ import absolute_import from __future__ import absolute_import, division
__author__ = "Peter Eastman" __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
...@@ -90,18 +90,20 @@ class MTSIntegrator(CustomIntegrator): ...@@ -90,18 +90,20 @@ class MTSIntegrator(CustomIntegrator):
def _createSubsteps(self, parentSubsteps, groups): def _createSubsteps(self, parentSubsteps, groups):
group, substeps = groups[0] group, substeps = groups[0]
stepsPerParentStep = substeps//parentSubsteps stepsPerParentStep = substeps / parentSubsteps
if stepsPerParentStep < 1 or stepsPerParentStep != int(stepsPerParentStep): 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") 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: if group < 0 or group > 31:
raise ValueError("Force group must be between 0 and 31") raise ValueError("Force group must be between 0 and 31")
for i in range(stepsPerParentStep): for i in range(stepsPerParentStep):
self.addComputePerDof("v", "v+0.5*(dt/"+str(substeps)+")*f"+str(group)+"/m") self.addComputePerDof("v", "v+0.5*(dt/"+str(substeps)+")*f"+str(group)+"/m")
if len(groups) == 1: if len(groups) == 1:
self.addComputePerDof("x1", "x")
self.addComputePerDof("x", "x+(dt/"+str(substeps)+")*v") self.addComputePerDof("x", "x+(dt/"+str(substeps)+")*v")
self.addComputePerDof("x1", "x")
self.addConstrainPositions(); self.addConstrainPositions();
self.addComputePerDof("v", "(x-x1)/(dt/"+str(substeps)+")"); self.addComputePerDof("v", "v+(x-x1)/(dt/"+str(substeps)+")");
self.addConstrainVelocities()
else: else:
self._createSubsteps(substeps, groups[1:]) self._createSubsteps(substeps, groups[1:])
self.addComputePerDof("v", "v+0.5*(dt/"+str(substeps)+")*f"+str(group)+"/m") 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}) ...@@ -79,6 +79,8 @@ yard = yards = Unit({yard_base_unit: 1.0})
furlongs = furlong = yard.create_unit(scale=220.0, name="furlong", symbol="furlong") furlongs = furlong = yard.create_unit(scale=220.0, name="furlong", symbol="furlong")
miles = mile = furlong.create_unit(scale=8.0, name="mile", symbol="mi") 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 ### ### MASS ###
############ ############
......
...@@ -1232,13 +1232,13 @@ ENABLE_PREPROCESSING = YES ...@@ -1232,13 +1232,13 @@ ENABLE_PREPROCESSING = YES
# compilation will be performed. Macro expansion can be done in a controlled # compilation will be performed. Macro expansion can be done in a controlled
# way by setting EXPAND_ONLY_PREDEF to YES. # 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 # 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 # then the macro expansion is limited to the macros specified with the
# PREDEFINED and EXPAND_AS_DEFINED tags. # 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 # 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. # in the INCLUDE_PATH (see below) will be search if a #include is found.
...@@ -1266,7 +1266,7 @@ INCLUDE_FILE_PATTERNS = ...@@ -1266,7 +1266,7 @@ INCLUDE_FILE_PATTERNS =
# undefined via #undef or recursively expanded use the := operator # undefined via #undef or recursively expanded use the := operator
# instead of the = operator. # instead of the = operator.
PREDEFINED = PREDEFINED = OPENMM_EXPORT=
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # 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. # this tag can be used to specify a list of macro names that should be expanded.
......
...@@ -74,6 +74,11 @@ def getNodeText(node): ...@@ -74,6 +74,11 @@ def getNodeText(node):
s = "%s%s\n\n" % (s, getNodeText(n)) s = "%s%s\n\n" % (s, getNodeText(n))
elif n.tag == "ref": elif n.tag == "ref":
s = "%s%s" % (s, getNodeText(n)) 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: else:
if n.tag in docTags: if n.tag in docTags:
tag = docTags[n.tag] tag = docTags[n.tag]
...@@ -242,6 +247,7 @@ class SwigInputBuilder: ...@@ -242,6 +247,7 @@ class SwigInputBuilder:
self.fOut.write("\n/* Declare factories */\n\n") self.fOut.write("\n/* Declare factories */\n\n")
forceSubclassList = [] forceSubclassList = []
integratorSubclassList = [] integratorSubclassList = []
tabulatedFunctionSubclassList = []
for classNode in findNodes(self.doc.getroot(), "compounddef", kind="class", prot="public"): for classNode in findNodes(self.doc.getroot(), "compounddef", kind="class", prot="public"):
className = getText("compoundname", classNode) className = getText("compoundname", classNode)
shortClassName=stripOpenmmPrefix(className) shortClassName=stripOpenmmPrefix(className)
...@@ -256,6 +262,8 @@ class SwigInputBuilder: ...@@ -256,6 +262,8 @@ class SwigInputBuilder:
forceSubclassList.append(shortClassName) forceSubclassList.append(shortClassName)
elif baseName == 'OpenMM::Integrator': elif baseName == 'OpenMM::Integrator':
integratorSubclassList.append(shortClassName) integratorSubclassList.append(shortClassName)
elif baseName == 'OpenMM::TabulatedFunction':
tabulatedFunctionSubclassList.append(shortClassName)
self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce") self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce")
for name in sorted(forceSubclassList): for name in sorted(forceSubclassList):
...@@ -292,6 +300,16 @@ class SwigInputBuilder: ...@@ -292,6 +300,16 @@ class SwigInputBuilder:
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::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("%factory(OpenMM::VirtualSite& OpenMM::System::getVirtualSite, OpenMM::TwoParticleAverageSite, OpenMM::ThreeParticleAverageSite, OpenMM::OutOfPlaneSite, OpenMM::LocalCoordinatesSite);\n\n")
self.fOut.write("\n") self.fOut.write("\n")
......
...@@ -13,7 +13,10 @@ DOC_STRINGS = {("Context", "setPositions") : ...@@ -13,7 +13,10 @@ DOC_STRINGS = {("Context", "setPositions") :
# Do not generate wrappers for the following methods. # Do not generate wrappers for the following methods.
# Indexed by (className, [methodName [, numParams]]) # Indexed by (className, [methodName [, numParams]])
SKIP_METHODS = [('State',), SKIP_METHODS = [('State', 'getPositions'),
('State', 'getVelocities'),
('State', 'getForces'),
('StateBuilder',),
('Vec3',), ('Vec3',),
('AngleInfo',), ('AngleInfo',),
('ApplyAndersenThermostatKernel',), ('ApplyAndersenThermostatKernel',),
...@@ -87,8 +90,6 @@ SKIP_METHODS = [('State',), ...@@ -87,8 +90,6 @@ SKIP_METHODS = [('State',),
('UpdateTimeKernel',), ('UpdateTimeKernel',),
('VdwInfo',), ('VdwInfo',),
('WcaDispersionInfo',), ('WcaDispersionInfo',),
('Context', 'getState'),
('Context', 'setState'),
('Context', 'createCheckpoint'), ('Context', 'createCheckpoint'),
('Context', 'loadCheckpoint'), ('Context', 'loadCheckpoint'),
('CudaPlatform',), ('CudaPlatform',),
...@@ -102,7 +103,6 @@ SKIP_METHODS = [('State',), ...@@ -102,7 +103,6 @@ SKIP_METHODS = [('State',),
('Platform', 'createKernel'), ('Platform', 'createKernel'),
('Platform', 'registerKernelFactory'), ('Platform', 'registerKernelFactory'),
('IntegrateRPMDStepKernel',), ('IntegrateRPMDStepKernel',),
('RPMDIntegrator', 'getState'),
('CalcDrudeForceKernel',), ('CalcDrudeForceKernel',),
('IntegrateDrudeLangevinStepKernel',), ('IntegrateDrudeLangevinStepKernel',),
('IntegrateDrudeSCFStepKernel',), ('IntegrateDrudeSCFStepKernel',),
...@@ -427,6 +427,13 @@ UNITS = { ...@@ -427,6 +427,13 @@ UNITS = {
: (None, (None, None, None, None, : (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',
'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", "getConstraintParameters") : (None, (None, None, 'unit.nanometer')),
("System", "getForce") : (None, ()), ("System", "getForce") : (None, ()),
("System", "getVirtualSite") : (None, ()), ("System", "getVirtualSite") : (None, ()),
......
%inline %{ %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 { %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 %{ %pythoncode %{
def getState(self, getPositions=False, getVelocities=False, def getState(self, getPositions=False, getVelocities=False,
getForces=False, getEnergy=False, getParameters=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. """Get a State object recording the current state information stored in this context.
Parameters Parameters
...@@ -66,8 +21,10 @@ ...@@ -66,8 +21,10 @@
whether to store the forces acting on particles in the State whether to store the forces acting on particles in the State
getEnergy : bool=False getEnergy : bool=False
whether to store potential and kinetic energy in the State whether to store potential and kinetic energy in the State
getParameter : bool=False getParameters : bool=False
whether to store context parameters in the State whether to store context parameters in the State
getParameterDerivatives : bool=False
whether to store parameter derivatives in the State
enforcePeriodicBox : bool=False enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position if false, the position of each particle will be whatever position
is stored in the Context, regardless of periodic boundary conditions. is stored in the Context, regardless of periodic boundary conditions.
...@@ -79,10 +36,6 @@ ...@@ -79,10 +36,6 @@
can also be passed as an unsigned integer interpreted as a bitmask, 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. 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: try:
# is the input integer-like? # is the input integer-like?
groups_mask = int(groups) groups_mask = int(groups)
...@@ -93,43 +46,24 @@ ...@@ -93,43 +46,24 @@
((1<<x) & 0xffffffff for x in groups)) ((1<<x) & 0xffffffff for x in groups))
else: else:
raise TypeError('%s is neither an int nor set' % groups) raise TypeError('%s is neither an int nor set' % groups)
if groups_mask > 0x80000000:
(simTime, periodicBoxVectorsList, energy, coordList, velList, groups_mask -= 0x100000000
forceList, paramMap) = \ types = 0
self._getStateAsLists(getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask) if getPositions:
types += State.Positions
state = State(simTime=simTime, if getVelocities:
energy=energy, types += State.Velocities
coordList=coordList, if getForces:
velList=velList, types += State.Forces
forceList=forceList, if getEnergy:
periodicBoxVectorsList=periodicBoxVectorsList, types += State.Energy
paramMap=paramMap) if getParameters:
types += State.Parameters
if getParameterDerivatives:
types += State.ParameterDerivatives
state = _openmm.Context_getState(self, types, enforcePeriodicBox, groups_mask)
return state 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. %feature("docstring") createCheckpoint "Create a checkpoint recording the current state of the Context.
...@@ -170,34 +104,6 @@ Parameters: ...@@ -170,34 +104,6 @@ Parameters:
} }
%extend OpenMM::RPMDIntegrator { %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 %{ %pythoncode %{
def getState(self, def getState(self,
copy, copy,
...@@ -206,6 +112,7 @@ Parameters: ...@@ -206,6 +112,7 @@ Parameters:
getForces=False, getForces=False,
getEnergy=False, getEnergy=False,
getParameters=False, getParameters=False,
getParameterDerivatives=False,
enforcePeriodicBox=False, enforcePeriodicBox=False,
groups=-1): groups=-1):
"""Get a State object recording the current state information about one copy of the system. """Get a State object recording the current state information about one copy of the system.
...@@ -222,8 +129,10 @@ Parameters: ...@@ -222,8 +129,10 @@ Parameters:
whether to store the forces acting on particles in the State whether to store the forces acting on particles in the State
getEnergy : bool=False getEnergy : bool=False
whether to store potential and kinetic energy in the State whether to store potential and kinetic energy in the State
getParameter : bool=False getParameters : bool=False
whether to store context parameters in the State whether to store context parameters in the State
getParameterDerivatives : bool=False
whether to store parameter derivatives in the State
enforcePeriodicBox : bool=False enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position if false, the position of each particle will be whatever position
is stored in the Context, regardless of periodic boundary conditions. is stored in the Context, regardless of periodic boundary conditions.
...@@ -235,9 +144,9 @@ Parameters: ...@@ -235,9 +144,9 @@ Parameters:
can also be passed as an unsigned integer interpreted as a bitmask, 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. 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, (getPositions, getVelocities, getForces, getEnergy, getParameters,
enforcePeriodicBox)) getParameterDerivatives, enforcePeriodicBox))
try: try:
# is the input integer-like? # is the input integer-like?
...@@ -248,18 +157,22 @@ Parameters: ...@@ -248,18 +157,22 @@ Parameters:
((1<<x) & 0xffffffff for x in groups)) ((1<<x) & 0xffffffff for x in groups))
else: else:
raise TypeError('%s is neither an int nor set' % groups) raise TypeError('%s is neither an int nor set' % groups)
if groups_mask > 0x80000000:
(simTime, periodicBoxVectorsList, energy, coordList, velList, groups_mask -= 0x100000000
forceList, paramMap) = \ types = 0
self._getStateAsLists(copy, getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask) if getPositions:
types += State.Positions
state = State(simTime=simTime, if getVelocities:
energy=energy, types += State.Velocities
coordList=coordList, if getForces:
velList=velList, types += State.Forces
forceList=forceList, if getEnergy:
periodicBoxVectorsList=periodicBoxVectorsList, types += State.Energy
paramMap=paramMap) if getParameters:
types += State.Parameters
if getParameterDerivatives:
types += State.ParameterDerivatives
state = _openmm.RPMDIntegrator_getState(self, copy, types, enforcePeriodicBox, groups_mask)
return state return state
%} %}
} }
...@@ -311,14 +224,14 @@ Parameters: ...@@ -311,14 +224,14 @@ Parameters:
} }
%extend OpenMM::XmlSerializer { %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) { static std::string serializeSystem(const OpenMM::System* object) {
std::stringstream ss; std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::System>(object, "System", ss); OpenMM::XmlSerializer::serialize<OpenMM::System>(object, "System", ss);
return ss.str(); 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; %newobject deserializeSystem;
static OpenMM::System* deserializeSystem(const char* inputString) { static OpenMM::System* deserializeSystem(const char* inputString) {
std::stringstream ss; std::stringstream ss;
...@@ -352,87 +265,33 @@ Parameters: ...@@ -352,87 +265,33 @@ Parameters:
return OpenMM::XmlSerializer::deserialize<OpenMM::Integrator>(ss); return OpenMM::XmlSerializer::deserialize<OpenMM::Integrator>(ss);
} }
static std::string _serializeStateAsLists( static std::string _serializeTabulatedFunction(const OpenMM::TabulatedFunction* object) {
const std::vector<Vec3>& pos, std::stringstream ss;
const std::vector<Vec3>& vel, OpenMM::XmlSerializer::serialize<OpenMM::TabulatedFunction>(object, "TabulatedFunction", ss);
const std::vector<Vec3>& forces, return ss.str();
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 PyObject* _deserializeStringIntoLists(const std::string &stateAsString) { %newobject _deserializeTabulatedFunction;
std::stringstream ss; static OpenMM::TabulatedFunction* _deserializeTabulatedFunction(const char* inputString) {
ss << stateAsString; std::stringstream ss;
OpenMM::State* deserializedState = OpenMM::XmlSerializer::deserialize<OpenMM::State>(ss); ss << inputString;
PyObject* obj = _convertStateToLists(*deserializedState); return OpenMM::XmlSerializer::deserialize<OpenMM::TabulatedFunction>(ss);
delete deserializedState;
return obj;
} }
%pythoncode %{ static std::string _serializeState(const OpenMM::State* object) {
@staticmethod std::stringstream ss;
def _serializeState(pythonState): OpenMM::XmlSerializer::serialize<OpenMM::State>(object, "State", ss);
positions = [] return ss.str();
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)
state = State(simTime=simTime, %newobject _deserializeState;
energy=energy, static OpenMM::State* _deserializeState(const char* inputString) {
coordList=coordList, std::stringstream ss;
velList=velList, ss << inputString;
forceList=forceList, return OpenMM::XmlSerializer::deserialize<OpenMM::State>(ss);
periodicBoxVectorsList=periodicBoxVectorsList, }
paramMap=paramMap)
return state
%pythoncode %{
@staticmethod @staticmethod
def serialize(object): def serialize(object):
"""Serialize an object as XML.""" """Serialize an object as XML."""
...@@ -444,6 +303,8 @@ Parameters: ...@@ -444,6 +303,8 @@ Parameters:
return XmlSerializer._serializeIntegrator(object) return XmlSerializer._serializeIntegrator(object)
elif isinstance(object, State): elif isinstance(object, State):
return XmlSerializer._serializeState(object) return XmlSerializer._serializeState(object)
elif isinstance(object, TabulatedFunction):
return XmlSerializer._serializeTabulatedFunction(object)
raise ValueError("Unsupported object type") raise ValueError("Unsupported object type")
@staticmethod @staticmethod
...@@ -462,6 +323,8 @@ Parameters: ...@@ -462,6 +323,8 @@ Parameters:
return XmlSerializer._deserializeIntegrator(inputString) return XmlSerializer._deserializeIntegrator(inputString)
if type == "State": if type == "State":
return XmlSerializer._deserializeState(inputString) return XmlSerializer._deserializeState(inputString)
if type == "TabulatedFunction":
return XmlSerializer._deserializeTabulatedFunction(inputString)
raise ValueError("Unsupported object type") raise ValueError("Unsupported object type")
%} %}
} }
...@@ -511,3 +374,137 @@ Parameters: ...@@ -511,3 +374,137 @@ Parameters:
return OpenMM::XmlSerializer::clone<OpenMM::Integrator>(*self); 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) { ...@@ -20,107 +20,6 @@ PyObject *copyVVec3ToList(std::vector<Vec3> vVec3) {
return pyList; 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 } // 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