argon-chemical-potential.py 8.17 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Example illustrating use of Free Energy OpenMM plugin.
# Calculation of chemical potential of argon in a periodic box.
#
# AUTHOR
#
# John D. Chodera <jchodera@berkeley.edu>
#
# REQUIREMENTS
#
# numpy - Scientific computing package - http://numpy.scipy.org
# pymbar - Python implementation of MBAR - https://simtk.org/home/pymbar
#
# REFERENCES
#
# [1] Michael R. Shirts and John D. Chodera. Statistically optimal analysis of samples from multiple equilibrium states.
# J. Chem. Phys. 129:124105 (2008)  http://dx.doi.org/10.1063/1.2978177

18
from __future__ import print_function
19
20
from openmm import *
from openmm.unit import *
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
import numpy

# =============================================================================
# Specify simulation parameters
# =============================================================================

nparticles = 216 # number of particles

mass = 39.9 * amu # mass
sigma = 3.4 * angstrom # Lennard-Jones sigma
epsilon = 0.238 * kilocalories_per_mole # Lennard-Jones well-depth
charge = 0.0 * elementary_charge # argon model has no charge

nequil_steps = 5000 # number of dynamics steps for equilibration
nprod_steps = 500 # number of dynamics steps per production iteration
nprod_iterations = 50 # number of production iterations per lambda value

reduced_density = 0.85 # reduced density rho*sigma^3
temperature = 300 * kelvin # temperature
pressure = 1 * atmosphere # pressure
collision_rate = 5.0 / picosecond # collision rate for Langevin thermostat
timestep = 1 * femtosecond # integrator timestep

# Specify lambda values for alchemically-modified intermediates.
lambda_values = [0.0, 0.25, 0.5, 0.75, 1.0]
nlambda = len(lambda_values)

# =============================================================================
# Compute box size.
# =============================================================================

volume = nparticles*(sigma**3)/reduced_density
box_edge = volume**(1.0/3.0)
cutoff = min(box_edge*0.49, 2.5*sigma) # Compute cutoff
55
56
57
print("sigma = %s" % sigma)
print("box_edge = %s" % box_edge)
print("cutoff = %s" % cutoff)
58
59
60
61
62

# =============================================================================
# Build systems at each alchemical lambda value.
# =============================================================================

63
print("Building alchemically-modified systems...")
64
65
66
67
68
alchemical_systems = list() # alchemical_systems[i] is the alchemically-modified System object corresponding to lambda_values[i]
for lambda_value in lambda_values:
   # Create argon system where first particle is alchemically modified by lambda_value.
   system = System()
   system.setDefaultPeriodicBoxVectors(Vec3(box_edge, 0, 0), Vec3(0, box_edge, 0), Vec3(0, 0, box_edge))
69
70
71
72
73
74
75
76
77
   force = CustomNonbondedForce("""4*epsilon*l12*(1/((alphaLJ*(1-l12) + (r/sigma)^6)^2) - 1/(alphaLJ*(1-l12) + (r/sigma)^6));
                                   sigma=0.5*(sigma1+sigma2);
                                   epsilon=sqrt(epsilon1*epsilon2);
                                   alphaLJ=0.5;
                                   l12=1-(1-lambda)*step(useLambda1+useLambda2-0.5)""")
   force.addPerParticleParameter("sigma")
   force.addPerParticleParameter("epsilon")
   force.addPerParticleParameter("useLambda")
   force.addGlobalParameter("lambda", lambda_value)
78
79
80
81
82
83
   force.setNonbondedMethod(NonbondedForce.CutoffPeriodic)
   force.setCutoffDistance(cutoff) 
   for particle_index in range(nparticles):
      system.addParticle(mass)
      if (particle_index == 0):
         # Add alchemically-modified particle.
84
         force.addParticle([sigma, epsilon, 1])
85
86
      else:
         # Add normal particle.
87
         force.addParticle([sigma, epsilon, 0])
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
   system.addForce(force)

   # Add barostat.
   #barostat = MonteCarloBarostat(pressure, temperature)
   #system.addForce(barostat)

   # Store system.
   alchemical_systems.append(system)

# Create random initial positions.
import numpy.random
positions = Quantity(numpy.random.uniform(high=box_edge/angstroms, size=[nparticles,3]), angstrom)

# =============================================================================
# Run simulations at each alchemical lambda value.
# Reduced potentials of sampled configurations are computed for all alchemical states for use with MBAR analysis.
# =============================================================================

u_kln = numpy.zeros([nlambda, nlambda, nprod_iterations], numpy.float64) # u_kln[k,l,n] is reduced potential of snapshot n from simulation k at alchemical state l
for (lambda_index, lambda_value) in enumerate(lambda_values):
   # Create Integrator and Context.
   integrator = LangevinIntegrator(temperature, collision_rate, timestep)
   context = Context(alchemical_systems[lambda_index], integrator)

   # Initiate from last set of positions.
   context.setPositions(positions)

   # Minimize energy from coordinates.
116
   print("Lambda %d/%d : minimizing..." % (lambda_index+1, nlambda))
117
118
119
   LocalEnergyMinimizer.minimize(context)

   # Equilibrate.
120
   print("Lambda %d/%d : equilibrating..." % (lambda_index+1, nlambda))
121
122
123
124
125
   integrator.step(nequil_steps)

   # Sample.
   position_history = list() # position_history[i] is the set of positions after iteration i
   for iteration in range(nprod_iterations):
126
      print("Lambda %d/%d : production iteration %d/%d" % (lambda_index+1, nlambda, iteration+1, nprod_iterations))
127
128
129
130
131

      # Run dynamics.
      integrator.step(nprod_steps)

      # Get coordinates.
Peter Eastman's avatar
Peter Eastman committed
132
      state = context.getState(positions=True)
133
134
135
136
137
138
139
140
141
      positions = state.getPositions(asNumpy=True)

      # Store positions.
      position_history.append(positions)
      
   # Clean up.
   del context, integrator

   # Compute reduced potentials of all snapshots at all alchemical states for MBAR.
142
   print("Lambda %d/%d : computing energies at all states..." % (lambda_index+1, nlambda))
143
144
145
146
147
148
149
150
151
   beta = 1.0 / (BOLTZMANN_CONSTANT_kB * AVOGADRO_CONSTANT_NA * temperature) # inverse temperature
   for l in range(nlambda):
      # Set up Context just to evaluate energies.
      integrator = VerletIntegrator(timestep)
      context = Context(alchemical_systems[l], integrator)

      # Compute reduced potentials of all snapshots.
      for n in range(nprod_iterations):
         context.setPositions(position_history[n])
Peter Eastman's avatar
Peter Eastman committed
152
         state = context.getState(energy=True)
153
154
155
156
157
158
159
         u_kln[lambda_index, l, n] = beta * state.getPotentialEnergy()

      # Clean up.
      del context, integrator

# Check to make sure pymbar is installed.
try:
160
   from pymbar.timeseries import subsample_correlated_data
161
162
   from pymbar import MBAR
except:
163
   raise ImportError("pymbar [https://pymbar.readthedocs.io/] must be installed to complete analysis of free energies.")
164
165
166
167
168
   
# =============================================================================
# Subsample correlated samples to generate uncorrelated subsample.
# =============================================================================

169
print("Subsampling data to remove correlation...")
170
171
172
173
174
K = nlambda # number of states
N_k = nprod_iterations*numpy.ones([K], numpy.int32) # N_k[k] is the number of uncorrelated samples at state k
u_kln_subsampled = numpy.zeros([K,K,nprod_iterations], numpy.float64) # subsampled data
for k in range(K):
   # Get indices of uncorrelated samples.
175
   indices = subsample_correlated_data(u_kln[k,k,:])
176
177
178
179
   # Store only uncorrelated data.
   N_k[k] = len(indices)
   for l in range(K):
      u_kln_subsampled[k,l,0:len(indices)] = u_kln[k,l,indices]
180
181
print("Number of uncorrelated samples per state:")
print(N_k)
182
183
184
185
186

# =============================================================================
# Analyze with MBAR to compute free energy differences and statistical errors.
# =============================================================================

187
print("Analyzing with MBAR...")
188
mbar = MBAR(u_kln_subsampled, N_k)
189
190
191
result = mbar.compute_free_energy_differences()
Deltaf_ij = result['Delta_f']
dDeltaf_ij = result['dDelta_f']
192
193
194
195
print("Free energy differences (in kT)")
print(Deltaf_ij)
print("Statistical errors (in kT)")
print(dDeltaf_ij)
196
197
198
199
200

# =============================================================================
# Report result.
# =============================================================================

201
print("Free energy of inserting argon particle: %.3f +- %.3f kT" % (Deltaf_ij[0,K-1], dDeltaf_ij[0,K-1]))