argon-chemical-potential.py 8.11 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
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
from simtk.openmm import *
from simtk.unit import *
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
132
133
134
135
136
137
138
139
140
141

      # Run dynamics.
      integrator.step(nprod_steps)

      # Get coordinates.
      state = context.getState(getPositions=True)
      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
152
153
154
155
156
157
158
159
160
161
162
   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])
         state = context.getState(getEnergy=True)
         u_kln[lambda_index, l, n] = beta * state.getPotentialEnergy()

      # Clean up.
      del context, integrator

# Check to make sure pymbar is installed.
try:
   from timeseries import subsampleCorrelatedData
   from pymbar import MBAR
except:
163
   raise ImportError("pymbar [https://simtk.org/home/pymbar] 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
175
176
177
178
179
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.
   indices = subsampleCorrelatedData(u_kln[k,k,:])
   # 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
192
193
Deltaf_ij, dDeltaf_ij = mbar.getFreeEnergyDifferences()
print("Free energy differences (in kT)")
print(Deltaf_ij)
print("Statistical errors (in kT)")
print(dDeltaf_ij)
194
195
196
197
198

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

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