Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add XC GHMC + RESPA integrators. #356

Open
wants to merge 82 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
09d8bc4
Initial checkin of hmc integrators, taken from existing branch moreint
kyleabeauchamp May 11, 2015
2a890c6
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp May 11, 2015
22cd9b5
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp May 12, 2015
507a547
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp May 16, 2015
7ad824d
Intermediate checkin of half-working lattice
kyleabeauchamp May 16, 2015
056ffc2
Added lattice option
kyleabeauchamp May 17, 2015
fe7e8f6
Allowed charged LJ particles
kyleabeauchamp May 17, 2015
9e66578
Added ewald error tolerance
kyleabeauchamp May 17, 2015
843e55c
Add ewald tolerance to waterbox
kyleabeauchamp May 18, 2015
0cb438b
Ran pyflakes, autopep8
kyleabeauchamp May 22, 2015
8a3f92a
monkeypatch in updated version of testsystems
kyleabeauchamp May 22, 2015
59b4321
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp May 26, 2015
ddc06f3
Change global variable a to accept for consistency across all HMC int…
kyleabeauchamp May 26, 2015
d42ee63
Added some extra decorators with accept probability statistics.
kyleabeauchamp May 27, 2015
fd0989e
Guess GB and NB forces to be in same group
kyleabeauchamp May 28, 2015
a344da3
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp May 29, 2015
5ad6292
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp May 29, 2015
a766118
Still not working, but checked in unrolled version
kyleabeauchamp Jun 2, 2015
4ddda8e
Fixed accumulation of steps in Unrolled HMC
kyleabeauchamp Jun 2, 2015
93d29df
Major refactor of HMC to unify GHMC and HMC, deleted rolled version o…
kyleabeauchamp Jun 3, 2015
91604d0
Updates
kyleabeauchamp Jun 3, 2015
86841a2
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp Jun 3, 2015
a8915b3
I think things are fixed after refactor
kyleabeauchamp Jun 3, 2015
14eb97b
Cleanup
kyleabeauchamp Jun 3, 2015
f5bb0a8
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp Jun 3, 2015
f41823e
Added comment on conditional termination.
kyleabeauchamp Jun 3, 2015
f36e1d6
Ran autopep8
kyleabeauchamp Jun 3, 2015
559f59d
More docstrings for new hmc integrators
kyleabeauchamp Jun 3, 2015
ce61709
Slight refactor of the elapsed timekeeping / ns_per_day stuff
kyleabeauchamp Jun 4, 2015
bf4879e
Fixes to docstrings
kyleabeauchamp Jun 5, 2015
a77681e
Cleaned up some unused integrator variables.
kyleabeauchamp Jun 5, 2015
c3d1759
Logger name
kyleabeauchamp Jun 5, 2015
15e31aa
Slight cleanup
kyleabeauchamp Jun 5, 2015
f135549
missing initialization of vold in XCGHMCIntegrator
kyleabeauchamp Jun 11, 2015
9edad61
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp Feb 29, 2016
da4a0c4
Merge remote-tracking branch 'upstream/master' into hmc
kyleabeauchamp Sep 23, 2016
0dd3f59
Check in half finished mjhmc
kyleabeauchamp Sep 25, 2016
59f4110
cleanup all integrators
kyleabeauchamp Sep 25, 2016
9844a03
Cleanup mjhmc
kyleabeauchamp Sep 25, 2016
52bdc18
Add some notes
kyleabeauchamp Sep 25, 2016
5fe2565
checkin
kyleabeauchamp Sep 25, 2016
439b703
More cleanup, fixed? MJHMC
kyleabeauchamp Sep 26, 2016
1eeb5e6
Remove extra global variable for timestep because openmm bug fixed
kyleabeauchamp Sep 27, 2016
9baf6a9
Fix bookkeeping
kyleabeauchamp Sep 27, 2016
6e86628
Fix some steps taken bookeeping issues
kyleabeauchamp Sep 28, 2016
46f84a3
Merge remote-tracking branch 'upstream/master' into mjhmc2
kyleabeauchamp Oct 7, 2016
766184a
Updates to RespaMixin
kyleabeauchamp Oct 10, 2016
9812f3f
Refactored python modules
kyleabeauchamp Oct 10, 2016
a845c12
Fixes to refactoring
kyleabeauchamp Oct 10, 2016
f355b49
Add PositionVerlet
kyleabeauchamp Oct 24, 2016
d9b08b3
Merge remote-tracking branch 'upstream/master' into mjhmc2
kyleabeauchamp Dec 1, 2016
485808b
Merge remote-tracking branch 'upstream/master' into mjhmc2
kyleabeauchamp Mar 21, 2017
5cd4283
Merge upstream
kyleabeauchamp Jun 3, 2017
414e6cd
Remove MJHMC integrator
kyleabeauchamp Jun 3, 2017
ee59e53
Update `vold` in `add_cache_variables_step`
maxentile Jul 10, 2017
1f59ae8
Merge pull request #1 from maxentile/patch-1
kyleabeauchamp Jul 10, 2017
a9cd44a
Merge remote-tracking branch 'upstream/master' into mjhmc2
kyleabeauchamp Jul 11, 2017
438ce7f
Merge branch 'mjhmc2' of github.com:kyleabeauchamp/openmmtools into m…
kyleabeauchamp Jul 11, 2017
853b358
Merge remote-tracking branch 'upstream/master' into mjhmc2
kyleabeauchamp Jul 15, 2017
9e8440b
Fix linter
kyleabeauchamp Jul 15, 2017
81e23d3
Add some warnings for experimental integrators.
kyleabeauchamp Jul 16, 2017
ed1fffc
Switch to using thermostatted integrator base class.
kyleabeauchamp Jul 16, 2017
69cd247
Fix addComputeTemperatureDependent in xchmc
kyleabeauchamp Jul 16, 2017
b4d840e
Remove debug prints
kyleabeauchamp Jul 16, 2017
bdef5ee
Fix merge conflicts
kyleabeauchamp Jun 5, 2018
475f6d3
Remove unused reference to `MJHMCIntegrator`
maxentile Jun 5, 2018
fe6c146
Tweak GHMC documentation
maxentile Jun 5, 2018
ef74360
Unused import
maxentile Jun 5, 2018
a2d4031
Extra indentation
maxentile Jun 5, 2018
c0c8c88
Tweak xchmc documentation
maxentile Jun 5, 2018
6477e8d
Add hmc_integrators to __init__.py
maxentile Jun 5, 2018
6dc32d6
Fix import error in PositionVerletIntegrator
maxentile Jun 5, 2018
e29df0d
Add hmc_integrators to test_integrators_and_testsystems
maxentile Jun 5, 2018
85962e6
Add hmc_integrators to test_integrators
maxentile Jun 5, 2018
1d99a41
Fix test_alchemical_langevin_integrator
maxentile Jun 5, 2018
a590bc2
Remove nsteps parameter in test
maxentile Jun 5, 2018
eacae9b
remove unused local variables in test
maxentile Jun 5, 2018
f0933f8
Remove commented out code
kyleabeauchamp Jun 6, 2018
01fbdd3
Merge pull request #357 from kyleabeauchamp/kab1
maxentile Jun 6, 2018
ef2cb00
Add tests for XCGHMCIntegrator stability
maxentile Jun 6, 2018
4e6f77e
Remove effective_timestep and effective_ns_per_day properties
maxentile Jun 6, 2018
ec849bd
Update new GHMCIntegrator
maxentile Jun 7, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openmmtools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
__version__ = version.version

# Import modules.
from openmmtools import testsystems, integrators, alchemy, mcmc, states, cache, utils, constants, forces, forcefactories, storage
from openmmtools import testsystems, integrators, alchemy, mcmc, states, cache, utils, constants, forces, forcefactories, storage, hmc_integrators
8 changes: 8 additions & 0 deletions openmmtools/hmc_integrators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .ghmc import GHMCIntegrator
from .xchmc import XCGHMCIntegrator, XCGHMCRESPAIntegrator
from .ghmc_respa import RESPAMixIn, GHMCRESPAIntegrator, check_groups, guess_force_groups


__all__ = ["GHMCIntegrator", "XCGHMCIntegrator", "XCGHMCRESPAIntegrator",
"RESPAMixIn", "GHMCRESPAIntegrator", "check_groups", "guess_force_groups",
]
282 changes: 282 additions & 0 deletions openmmtools/hmc_integrators/ghmc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
"""Hamiltonian Monte Carlo Integrators

Notes
-----
The code in this module is considered EXPERIMENTAL until further notice.
"""
import time
import logging
import pandas as pd
import numpy as np

import simtk.unit as u
import simtk.openmm as mm

from ..integrators import ThermostatedIntegrator
from .utils import warn_experimental

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class GHMCBase(ThermostatedIntegrator):
"""Generalized hybrid Monte Carlo integrator base class.

Notes
-----
This loosely follows the definition of GHMC given in the two below
references. Specifically, the velocities are corrupted (partially or completely),
several steps of hamiltonian dynamics are performed, and then
an accept / reject move is taken.

This class is the base class for a number of more specialized versions
of GHMC.

References
----------
C. M. Campos, J. M. Sanz-Serna, J. Comp. Phys. 281, (2015)
J. Sohl-Dickstein, M. Mudigonda, M. DeWeese. ICML (2014)
"""

def nan_to_inf(self, outkey, inkey):
"""Add a compute step that converts nan to positive infinity in `inkey` and ouputs in `outkey`."""
self.addComputeGlobal(outkey, "select(step(exp(%s)), %s, 1/0)" % (inkey, inkey))

def step(self, n_steps):
"""Do `n_steps` of dynamics while accumulating some timing information."""
if not hasattr(self, "elapsed_time"):
self.elapsed_time = 0.0

t0 = time.time()
mm.CustomIntegrator.step(self, n_steps)
dt = time.time() - t0

self.elapsed_time += dt

def reset_time(self):
"""Resets benchmark timing counters, avoids counting the CustomIntegrator setup time."""
self.step(1)
self.elapsed_time = 0.0
self.setGlobalVariableByName("steps_taken", 0.0)
self.setGlobalVariableByName("steps_accepted", 0.0)
# Note: XCHMC has additional counters n_i that are not reset here. This should not be a problem as they aren't used in benchmarking

@property
def time_per_step(self):
return (self.elapsed_time / self.steps_taken)

@property
def days_per_step(self):
return self.time_per_step / (60. * 60. * 24.)

@property
def ns_per_day(self):
return (self.timestep / self.days_per_step) / u.nanoseconds

def vstep(self, n_steps, verbose=False):
"""Do n_steps of dynamics and return a summary dataframe."""

data = []
for i in range(n_steps):
self.step(1)

d = self.summary()
data.append(d)

data = pd.DataFrame(data)

print(data.to_string(formatters=[lambda x: "%.4g" % x for x in range(data.shape[1])]))
return data

def add_compute_steps(self):
self.initialize_variables()
self.add_draw_velocities_step()
self.add_cache_variables_step()
self.add_hmc_iterations()
self.add_accept_or_reject_step()

def add_hmc_iterations(self):
"""Add self.steps_per_hmc iterations of symplectic hamiltonian dynamics."""
logger.debug("Adding (G)HMCIntegrator steps.")
for step in range(self.steps_per_hmc):
self.add_hamiltonian_step()

def add_hamiltonian_step(self):
"""Add a single step of hamiltonian integration.

Notes
-----
This function will be overwritten in RESPA subclasses!
"""
logger.debug("Adding step of hamiltonian dynamics.""")
self.addComputePerDof("v", "v+0.5*dt*f/m")
self.addComputePerDof("x", "x+dt*v")
self.addComputePerDof("x1", "x")
self.addConstrainPositions()
self.addComputePerDof("v", "v+0.5*dt*f/m+(x-x1)/dt")
self.addConstrainVelocities()

@property
def b(self):
"""The scaling factor for preserving versus randomizing velocities."""
return np.exp(-self.collision_rate * self.timestep)

def summary(self):
"""Return a dictionary of relevant state variables for XHMC, useful for debugging.
Append self.summary() to a list and print out as a dataframe.
"""
d = {}
d["arate"] = self.acceptance_rate
d["ns_per_day"] = self.ns_per_day
keys = ["accept", "ke", "Enew", "Eold"]

for key in keys:
d[key] = self.getGlobalVariableByName(key)

d["deltaE"] = d["Enew"] - d["Eold"]

return d

@property
def steps_taken(self):
"""Total number of hamiltonian steps taken."""
return self.getGlobalVariableByName("steps_taken")

@property
def steps_accepted(self):
"""Total number of hamiltonian steps accepted."""
return self.getGlobalVariableByName("steps_accepted")

@property
def accept(self):
"""Return True if the last step taken was accepted."""
return bool(self.getGlobalVariableByName("accept"))

@property
def acceptance_rate(self):
"""The acceptance rate, evaluated via the number of force evaluations.

Notes
-----
This should be sufficiently general to apply to both XCGHMC and GHMC.
In the latter case, the number of force evaluations taken (and accepted)
are both proportional to the number of MC moves taken.
"""
return self.steps_accepted / self.steps_taken

@property
def is_GHMC(self):
return self.collision_rate is not None


class GHMCIntegrator(GHMCBase):
"""Generalized hybrid Monte Carlo (GHMC) integrator.

Parameters
----------
temperature : simtk.unit.Quantity compatible with kelvin, default: 298*simtk.unit.kelvin
The temperature.
collision_rate : simtk.unit.Quantity compatible with 1 / femtoseconds, default: None
The collision rate for the velocity corruption (GHMC). If None,
velocities information will be discarded after each round (HMC).
timestep : simtk.unit.Quantity compatible with femtoseconds, default: 1*simtk.unit.femtoseconds
The integration timestep. The total time taken per iteration
will equal timestep * steps_per_hmc
steps_per_hmc : int, default: 1
The number of velocity Verlet steps to take per round of hamiltonian dynamics

Notes
-----
This loosely follows the definition of GHMC given in the three below
references. Specifically, the velocities are corrupted,
several steps of Hamiltonian dynamics are performed, and then
an accept / reject move is taken. If collision_rate is set to None, however,
we will do non-generalized HMC, where the velocity information is discarded
at each iteration.

This class is the base class for a number of more specialized versions
of GHMC.

Additional global variables 'ntrials' and 'naccept' keep track of how many trials have been attempted and accepted, respectively.

References
----------
C. M. Campos, J. M. Sanz-Serna, J. Comp. Phys. 281, (2015)
J. Sohl-Dickstein, M. Mudigonda, M. DeWeese. ICML (2014)
Lelievre T, Stoltz G, and Rousset M. Free Energy Computations: A Mathematical Perspective
http://www.amazon.com/Free-Energy-Computations-Mathematical-Perspective/dp/1848162472
"""

def __init__(self, temperature=298.0 * u.kelvin, collision_rate=91 / u.picosecond, timestep=1 * u.femtoseconds, steps_per_hmc=1):
warn_experimental()
super(GHMCIntegrator, self).__init__(temperature, timestep)

self.steps_per_hmc = steps_per_hmc
self.collision_rate = collision_rate
self.timestep = timestep
self.add_compute_steps()

def initialize_variables(self):

self.addPerDofVariable("sigma", 0)
self.addGlobalVariable("ke", 0) # kinetic energy
self.addPerDofVariable("xold", 0) # old positions
self.addPerDofVariable("vold", 0) # old velocities
self.addGlobalVariable("Eold", 0) # old energy
self.addGlobalVariable("Enew", 0) # new energy
self.addGlobalVariable("accept", 0) # accept or reject
self.addPerDofVariable("x1", 0) # for constraints



self.addGlobalVariable("steps_accepted", 0) # Number of productive hamiltonian steps
self.addGlobalVariable("steps_taken", 0) # Number of total hamiltonian steps

self.addGlobalVariable("naccept", 0) # Number of accepted proposal trajectories
self.addGlobalVariable("ntrials", 0) # Number of trial trajectories

if self.is_GHMC:
val = np.exp(-1.0 * self.collision_rate * self.timestep)
self.addGlobalVariable("b", val)

self.addComputeTemperatureDependentConstants({"sigma": "sqrt(kT/m)"})

self.addUpdateContextState()

def add_draw_velocities_step(self):
"""Draw perturbed velocities using either partial or complete momentum thermalization."""

self.addUpdateContextState()
self.addConstrainPositions()

if self.is_GHMC:
self.addComputePerDof("v", "sqrt(b)*v + sqrt(1-b)*sigma*gaussian")
else:
self.addComputePerDof("v", "sigma*gaussian")

self.addConstrainVelocities()

def add_cache_variables_step(self):
"""Store old positions and energies."""
self.addComputeSum("ke", "0.5*m*v*v")
self.nan_to_inf("Eold", "ke + energy")
self.addComputePerDof("xold", "x")
self.addComputePerDof("vold", "v")

def add_accept_or_reject_step(self):
logger.debug("GHMC: add_accept_or_reject_step()")
self.addComputeSum("ke", "0.5*m*v*v")
self.nan_to_inf("Enew", "energy + ke")
self.addComputeGlobal("accept", "step(exp(-(Enew-Eold)/kT) - uniform)")

self.addComputePerDof("x", "select(accept, x, xold)")

if self.is_GHMC:
self.addComputePerDof("v", "select(accept, v, -1*vold)")

self.addComputeGlobal("steps_accepted", "steps_accepted + accept * %d" % (self.steps_per_hmc))
self.addComputeGlobal("steps_taken", "steps_taken + %d" % (self.steps_per_hmc))

# For compatibility with the old GHMCIntegrator
self.addComputeGlobal("naccept", "naccept + accept")
self.addComputeGlobal("ntrials", "ntrials + 1")
Loading