-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_mts_custom.py
167 lines (132 loc) · 5.67 KB
/
test_mts_custom.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import simtk.openmm.app as app
import simtk.openmm as mm
from simtk import unit as u
from openmmtools import testsystems, constants
kB = constants.kB
class HMCIntegrator(mm.CustomIntegrator):
"""
Hybrid Monte Carlo (HMC) integrator.
"""
def __init__(self, temperature=298.0 * u.kelvin, nsteps=10, timestep=1 * u.femtoseconds, force_str="f"):
"""
Create a hybrid Monte Carlo (HMC) integrator.
Parameters
----------
temperature : numpy.unit.Quantity compatible with kelvin, default: 298*u.kelvin
The temperature.
nsteps : int, default: 10
The number of velocity Verlet steps to take per HMC trial.
timestep : numpy.unit.Quantity compatible with femtoseconds, default: 1*u.femtoseconds
The integration timestep.
Warning
-------
Because 'nsteps' sets the number of steps taken, a call to integrator.step(1) actually takes 'nsteps' steps.
Notes
-----
The velocity is drawn from a Maxwell-Boltzmann distribution, then 'nsteps' steps are taken,
and the new configuration is either accepted or rejected.
Additional global variables 'ntrials' and 'naccept' keep track of how many trials have been attempted and
accepted, respectively.
TODO
----
Currently, the simulation timestep is only advanced by 'timestep' each step, rather than timestep*nsteps. Fix this.
Examples
--------
Create an HMC integrator.
>>> timestep = 1.0 * u.femtoseconds # fictitious timestep
>>> temperature = 298.0 * u.kelvin
>>> nsteps = 10 # number of steps per call
>>> integrator = HMCIntegrator(temperature, nsteps, timestep)
"""
super(HMCIntegrator, self).__init__(timestep)
# Compute the thermal energy.
kT = kB * temperature
#
# Integrator initialization.
#
self.addGlobalVariable("naccept", 0) # number accepted
self.addGlobalVariable("ntrials", 0) # number of Metropolization trials
self.addGlobalVariable("kT", kT) # thermal energy
self.addPerDofVariable("sigma", 0)
self.addGlobalVariable("ke", 0) # kinetic energy
self.addPerDofVariable("xold", 0) # old positions
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
#
# Pre-computation.
# This only needs to be done once, but it needs to be done for each degree of freedom.
# Could move this to initialization?
#
self.addComputePerDof("sigma", "sqrt(kT/m)")
#
# Allow Context updating here, outside of inner loop only.
#
self.addUpdateContextState()
#
# Draw new velocity.
#
self.addComputePerDof("v", "sigma*gaussian")
self.addConstrainVelocities()
#
# Store old position and energy.
#
self.addComputeSum("ke", "0.5*m*v*v")
self.addComputeGlobal("Eold", "ke + energy")
self.addComputePerDof("xold", "x")
#
# Inner symplectic steps using velocity Verlet.
#
for step in range(nsteps):
self.addComputePerDof("v", "v+0.5*dt*%s/m" % force_str)
self.addComputePerDof("x", "x+dt*v")
self.addComputePerDof("x1", "x")
self.addConstrainPositions()
self.addComputePerDof("v", "v+0.5*dt*%s/m+(x-x1)/dt" % force_str)
self.addConstrainVelocities()
#
# Accept/reject step.
#
self.addComputeSum("ke", "0.5*m*v*v")
self.addComputeGlobal("Enew", "ke + energy")
self.addComputeGlobal("accept", "step(exp(-(Enew-Eold)/kT) - uniform)")
self.addComputePerDof("x", "x*accept + xold*(1-accept)")
#
# Accumulate statistics.
#
self.addComputeGlobal("naccept", "naccept + accept")
self.addComputeGlobal("ntrials", "ntrials + 1")
@property
def n_accept(self):
"""The number of accepted HMC moves."""
return self.getGlobalVariableByName("naccept")
@property
def n_trials(self):
"""The total number of attempted HMC moves."""
return self.getGlobalVariableByName("ntrials")
@property
def acceptance_rate(self):
"""The acceptance rate: n_accept / n_trials."""
return self.n_accept / float(self.n_trials)
f = lambda x: (x.getPotentialEnergy(), x.getKineticEnergy(), x.getKineticEnergy() + x.getPotentialEnergy())
temperature = 300. * u.kelvin
testsystem = testsystems.WaterBox(box_edge=10.0 * u.nanometers, cutoff=1.1*u.nanometers, switch_width=3.0*u.angstroms, ewaldErrorTolerance=5E-5, constrained=False)
timestep = 0.1 * u.femtoseconds
nsteps = 300
print("*" * 80)
print("force groups:")
for force in testsystem.system.getForces():
print(force, force.getForceGroup())
print("*" * 80)
print("Platform, integrator / acceptance rate, Enew")
for platform_name in ["CUDA", "OpenCL", "CPU"]:
platform = mm.Platform.getPlatformByName(platform_name)
integrators = [HMCIntegrator(timestep, force_str="f"), HMCIntegrator(timestep, force_str="f0")]
for integrator in integrators:
simulation = app.Simulation(testsystem.topology, testsystem.system, integrator, platform=platform)
simulation.context.setPositions(testsystem.positions)
simulation.context.setVelocitiesToTemperature(temperature)
integrator.step(nsteps)
print(platform_name, integrator)
print(integrator.acceptance_rate, integrator.getGlobalVariableByName("Enew"))