Commit 9dd49614 authored by Charlles Abreu's avatar Charlles Abreu
Browse files

Tested changes moved from WellTemperedMetadynamics to Metadynamics

- temporary class WellTemperedMetadynamics deleted
parent 119bcf32
...@@ -33,7 +33,7 @@ from .charmmcrdfiles import CharmmCrdFile, CharmmRstFile ...@@ -33,7 +33,7 @@ from .charmmcrdfiles import CharmmCrdFile, CharmmRstFile
from .charmmparameterset import CharmmParameterSet from .charmmparameterset import CharmmParameterSet
from .charmmpsffile import CharmmPsfFile, CharmmPSFWarning from .charmmpsffile import CharmmPsfFile, CharmmPSFWarning
from .simulatedtempering import SimulatedTempering from .simulatedtempering import SimulatedTempering
from .metadynamics import Metadynamics, WellTemperedMetadynamics, BiasVariable from .metadynamics import Metadynamics, BiasVariable
# Enumerated values # Enumerated values
......
...@@ -72,7 +72,7 @@ class Metadynamics(object): ...@@ -72,7 +72,7 @@ class Metadynamics(object):
directory, and also load in and apply the biases added by other processes. directory, and also load in and apply the biases added by other processes.
""" """
def __init__(self, system, variables, temperature, biasFactor, height, frequency, saveFrequency=None, biasDir=None): def __init__(self, system, variables, temperature, biasFactor, height, frequency, saveFrequency=None, biasDir=None, gridExpansion=20):
"""Create a Metadynamics object. """Create a Metadynamics object.
Parameters Parameters
...@@ -100,6 +100,9 @@ class Metadynamics(object): ...@@ -100,6 +100,9 @@ class Metadynamics(object):
biasDir: str (optional) biasDir: str (optional)
the directory to which biases should be written, and from which biases written by the directory to which biases should be written, and from which biases written by
other processes should be loaded other processes should be loaded
gridExpansion: int (optional)
the number of extra grid points to be used in periodic directions of multidimensional
tabulated functions. This aims at avoiding boundary discontinuity artifacts.
""" """
if not unit.is_quantity(temperature): if not unit.is_quantity(temperature):
temperature = temperature*unit.kelvin temperature = temperature*unit.kelvin
...@@ -120,19 +123,27 @@ class Metadynamics(object): ...@@ -120,19 +123,27 @@ class Metadynamics(object):
self.saveFrequency = saveFrequency self.saveFrequency = saveFrequency
self._id = np.random.randint(0x7FFFFFFF) self._id = np.random.randint(0x7FFFFFFF)
self._saveIndex = 0 self._saveIndex = 0
self._selfBias = np.zeros(tuple(v.gridWidth for v in variables)) for v in variables:
self._totalBias = np.zeros(tuple(v.gridWidth for v in variables)) v._expanded = v.periodic and len(variables) > 1
v._extraWidth = min(gridExpansion, v.gridWidth - 1) if v._expanded else 0
extraRange = v._extraWidth*(v.maxValue - v.minValue)/(v.gridWidth - 1)
v._actualWidth = v.gridWidth + 2*v._extraWidth
v._actualMin = v.minValue - extraRange
v._actualMax = v.maxValue + extraRange
v._slice = slice(v._extraWidth, v.gridWidth + v._extraWidth)
self._selfBias = np.zeros(tuple(v._actualWidth for v in reversed(variables)))
self._totalBias = np.zeros(tuple(v._actualWidth for v in reversed(variables)))
self._loadedBiases = {} self._loadedBiases = {}
self._deltaT = temperature*(biasFactor-1) self._deltaT = temperature*(biasFactor-1)
varNames = ['cv%d' % i for i in range(len(variables))] varNames = ['cv%d' % i for i in range(len(variables))]
self._force = mm.CustomCVForce('table(%s)' % ', '.join(varNames)) self._force = mm.CustomCVForce('table(%s)' % ', '.join(varNames))
for name, var in zip(varNames, variables): for name, var in zip(varNames, variables):
self._force.addCollectiveVariable(name, var.force) self._force.addCollectiveVariable(name, var.force)
widths = [v.gridWidth for v in variables] widths = [v._actualWidth for v in variables]
mins = [v.minValue for v in variables] mins = [v._actualMin for v in variables]
maxs = [v.maxValue for v in variables] maxs = [v._actualMax for v in variables]
if len(variables) == 1: if len(variables) == 1:
self._table = mm.Continuous1DFunction(self._totalBias.flatten(), mins[0], maxs[0]) self._table = mm.Continuous1DFunction(self._totalBias.flatten(), mins[0], maxs[0], variables[0].periodic)
elif len(variables) == 2: elif len(variables) == 2:
self._table = mm.Continuous2DFunction(widths[0], widths[1], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1]) self._table = mm.Continuous2DFunction(widths[0], widths[1], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1])
elif len(variables) == 3: elif len(variables) == 3:
...@@ -140,7 +151,8 @@ class Metadynamics(object): ...@@ -140,7 +151,8 @@ class Metadynamics(object):
else: else:
raise ValueError('Metadynamics requires 1, 2, or 3 collective variables') raise ValueError('Metadynamics requires 1, 2, or 3 collective variables')
self._force.addTabulatedFunction('table', self._table) self._force.addTabulatedFunction('table', self._table)
self._force.setForceGroup(31) freeGroups = set(range(32)) - set(force.getForceGroup() for force in system.getForces())
self._force.setForceGroup(max(freeGroups))
system.addForce(self._force) system.addForce(self._force)
self._syncWithDisk() self._syncWithDisk()
...@@ -178,7 +190,12 @@ class Metadynamics(object): ...@@ -178,7 +190,12 @@ class Metadynamics(object):
variables. The values are in kJ/mole. The i'th position along an axis corresponds to variables. The values are in kJ/mole. The i'th position along an axis corresponds to
minValue + i*(maxValue-minValue)/gridWidth. minValue + i*(maxValue-minValue)/gridWidth.
""" """
return -((self.temperature+self._deltaT)/self._deltaT)*self._totalBias*unit.kilojoules_per_mole f = -((self.temperature+self._deltaT)/self._deltaT)*self._totalBias*unit.kilojoules_per_mole
if len(self.variables) == 1:
return f
else:
s = [v._slice for v in self.variables]
return f[s[1], s[0]] if len(self.variables) == 2 else f[s[2], s[1], s[0]]
def getCollectiveVariables(self, simulation): def getCollectiveVariables(self, simulation):
"""Get the current values of all collective variables in a Simulation.""" """Get the current values of all collective variables in a Simulation."""
...@@ -196,7 +213,11 @@ class Metadynamics(object): ...@@ -196,7 +213,11 @@ class Metadynamics(object):
dist = np.abs(np.linspace(0, 1.0, num=v.gridWidth) - x) dist = np.abs(np.linspace(0, 1.0, num=v.gridWidth) - x)
if v.periodic: if v.periodic:
dist = np.min(np.array([dist, np.abs(dist-1)]), axis=0) dist = np.min(np.array([dist, np.abs(dist-1)]), axis=0)
axisGaussians.append(np.exp(-dist*dist*v.gridWidth/v.biasWidth)) values = np.exp(-0.5*dist*dist/v._scaledVariance)
if v._expanded:
n = v._extraWidth + 1
values = np.hstack((values[-n:-1], values, values[1:n]))
axisGaussians.append(values)
# Compute their outer product. # Compute their outer product.
...@@ -210,10 +231,11 @@ class Metadynamics(object): ...@@ -210,10 +231,11 @@ class Metadynamics(object):
height = height.value_in_unit(unit.kilojoules_per_mole) height = height.value_in_unit(unit.kilojoules_per_mole)
self._selfBias += height*gaussian self._selfBias += height*gaussian
self._totalBias += height*gaussian self._totalBias += height*gaussian
widths = [v.gridWidth for v in self.variables] widths = [v._actualWidth for v in self.variables]
mins = [v.minValue for v in self.variables] mins = [v._actualMin for v in self.variables]
maxs = [v.maxValue for v in self.variables] maxs = [v._actualMax for v in self.variables]
if len(self.variables) == 1: if len(self.variables) == 1:
self._totalBias[-1] = self._totalBias[0]
self._table.setFunctionParameters(self._totalBias.flatten(), mins[0], maxs[0]) self._table.setFunctionParameters(self._totalBias.flatten(), mins[0], maxs[0])
elif len(self.variables) == 2: elif len(self.variables) == 2:
self._table.setFunctionParameters(widths[0], widths[1], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1]) self._table.setFunctionParameters(widths[0], widths[1], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1])
...@@ -265,156 +287,6 @@ class Metadynamics(object): ...@@ -265,156 +287,6 @@ class Metadynamics(object):
self._totalBias += bias.bias self._totalBias += bias.bias
class WellTemperedMetadynamics(Metadynamics):
"""
Temporary class.
"""
def __init__(self, system, variables, temperature, biasFactor, height, frequency, saveFrequency=None, biasDir=None, gridExpansion=20):
"""Create a Metadynamics object.
Parameters
----------
system: System
the System to simulate. A CustomCVForce implementing the bias is created and
added to the System.
variables: list of BiasVariables
the collective variables to sample
temperature: temperature
the temperature at which the simulation is being run. This is used in computing
the free energy.
biasFactor: float
used in scaling the height of the Gaussians added to the bias. The collective
variables are sampled as if the effective temperature of the simulation were
temperature*biasFactor.
height: energy
the initial height of the Gaussians to add
frequency: int
the interval in time steps at which Gaussians should be added to the bias potential
saveFrequency: int (optional)
the interval in time steps at which to write out the current biases to disk. At
the same time it writes biases, it also checks for updated biases written by other
processes and loads them in. This must be a multiple of frequency.
biasDir: str (optional)
the directory to which biases should be written, and from which biases written by
other processes should be loaded
gridExpansion: int (optional)
the extra number of grid points used in periodic directions for multidimensional
tabulated functions
"""
if not unit.is_quantity(temperature):
temperature = temperature*unit.kelvin
if not unit.is_quantity(height):
height = height*unit.kilojoules_per_mole
if biasFactor < 1.0:
raise ValueError('biasFactor must be >= 1')
if (saveFrequency is None and biasDir is not None) or (saveFrequency is not None and biasDir is None):
raise ValueError('Must specify both saveFrequency and biasDir')
if saveFrequency is not None and (saveFrequency < frequency or saveFrequency%frequency != 0):
raise ValueError('saveFrequency must be a multiple of frequency')
self.variables = variables
self.temperature = temperature
self.biasFactor = biasFactor
self.height = height
self.frequency = frequency
self.biasDir = biasDir
self.saveFrequency = saveFrequency
self._id = np.random.randint(0x7FFFFFFF)
self._saveIndex = 0
for v in variables:
v._expanded = v.periodic and len(variables) > 1
v._extraWidth = min(gridExpansion, v.gridWidth - 1) if v._expanded else 0
extraRange = v._extraWidth*(v.maxValue - v.minValue)/(v.gridWidth - 1)
v._actualWidth = v.gridWidth + 2*v._extraWidth
v._actualMin = v.minValue - extraRange
v._actualMax = v.maxValue + extraRange
v._slice = slice(v._extraWidth, v.gridWidth + v._extraWidth)
self._selfBias = np.zeros(tuple(v._actualWidth for v in reversed(variables)))
self._totalBias = np.zeros(tuple(v._actualWidth for v in reversed(variables)))
self._loadedBiases = {}
self._deltaT = temperature*(biasFactor-1)
varNames = ['cv%d' % i for i in range(len(variables))]
self._force = mm.CustomCVForce('table(%s)' % ', '.join(varNames))
for name, var in zip(varNames, variables):
self._force.addCollectiveVariable(name, var.force)
widths = [v._actualWidth for v in variables]
mins = [v._actualMin for v in variables]
maxs = [v._actualMax for v in variables]
if len(variables) == 1:
self._table = mm.Continuous1DFunction(self._totalBias.flatten(), mins[0], maxs[0], variables[0].periodic)
elif len(variables) == 2:
self._table = mm.Continuous2DFunction(widths[0], widths[1], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1])
elif len(variables) == 3:
self._table = mm.Continuous3DFunction(widths[0], widths[1], widths[2], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2])
else:
raise ValueError('Metadynamics requires 1, 2, or 3 collective variables')
self._force.addTabulatedFunction('table', self._table)
freeGroups = set(range(32)) - set(force.getForceGroup() for force in system.getForces())
self._force.setForceGroup(max(freeGroups))
system.addForce(self._force)
self._syncWithDisk()
def getFreeEnergy(self):
"""Get the free energy of the system as a function of the collective variables.
The result is returned as a N-dimensional NumPy array, where N is the number of collective
variables. The values are in kJ/mole. The i'th position along an axis corresponds to
minValue + i*(maxValue-minValue)/gridWidth.
"""
f = -((self.temperature+self._deltaT)/self._deltaT)*self._totalBias*unit.kilojoules_per_mole
if len(self.variables) == 1:
return f
else:
s = [v._slice for v in self.variables]
if len(self.variables) == 2:
return f[s[1], s[0]]
else:
return f[s[2], s[1], s[0]]
def _addGaussian(self, position, height, context):
"""Add a Gaussian to the bias function."""
# Compute a Gaussian along each axis.
axisGaussians = []
for i,v in enumerate(self.variables):
x = (position[i]-v.minValue) / (v.maxValue-v.minValue)
if v.periodic:
x = x % 1.0
dist = np.abs(np.linspace(0, 1.0, num=v.gridWidth) - x)
if v.periodic:
dist = np.min(np.array([dist, np.abs(dist-1)]), axis=0)
values = np.exp(-0.5*dist*dist/v._scaledVariance)
if v._expanded:
n = v._extraWidth + 1
values = np.hstack((values[-n:-1], values, values[1:n]))
axisGaussians.append(values)
# Compute their outer product.
if len(self.variables) == 1:
gaussian = axisGaussians[0]
else:
gaussian = reduce(np.multiply.outer, reversed(axisGaussians))
# Add it to the bias.
height = height.value_in_unit(unit.kilojoules_per_mole)
self._selfBias += height*gaussian
self._totalBias += height*gaussian
widths = [v._actualWidth for v in self.variables]
mins = [v._actualMin for v in self.variables]
maxs = [v._actualMax for v in self.variables]
if len(self.variables) == 1:
self._totalBias[-1] = self._totalBias[0]
self._table.setFunctionParameters(self._totalBias.flatten(), mins[0], maxs[0])
elif len(self.variables) == 2:
self._table.setFunctionParameters(widths[0], widths[1], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1])
elif len(self.variables) == 3:
self._table.setFunctionParameters(widths[0], widths[1], widths[2], self._totalBias.flatten(), mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2])
self._force.updateParametersInContext(context)
class BiasVariable(object): class BiasVariable(object):
"""A collective variable that can be used to bias a simulation with metadynamics.""" """A collective variable that can be used to bias a simulation with metadynamics."""
...@@ -425,17 +297,17 @@ class BiasVariable(object): ...@@ -425,17 +297,17 @@ class BiasVariable(object):
---------- ----------
force: Force force: Force
the Force object whose potential energy defines the collective variable the Force object whose potential energy defines the collective variable
minValue: float minValue: float or unit.Quantity
the minimum value the collective variable can take. If it should ever go below this, the minimum value the collective variable can take. If it should ever go below this,
the bias force will be set to 0. the bias force will be set to 0.
maxValue: float maxValue: float or unit.Quantity
the maximum value the collective variable can take. If it should ever go above this, the maximum value the collective variable can take. If it should ever go above this,
the bias force will be set to 0. the bias force will be set to 0.
biasWidth: float biasWidth: float or unit.Quantity
the width (standard deviation) of the Gaussians added to the bias during metadynamics the width (standard deviation) of the Gaussians added to the bias during metadynamics
periodic: bool periodic: bool (optional)
whether this is a periodic variable, such that minValue and maxValue are physical equivalent whether this is a periodic variable, such that minValue and maxValue are physical equivalent
gridWidth: int gridWidth: int (optional)
the number of grid points to use when tabulating the bias function. If this is omitted, the number of grid points to use when tabulating the bias function. If this is omitted,
a reasonable value is chosen automatically. a reasonable value is chosen automatically.
""" """
......
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