metropolis.py
You can download the metropolis.py
script directly by clicking here (depending on your browser, on the next page you may need to click on the button marked ‘Raw’, or right click on the button marked ‘Raw’ and choose ‘Save As…’).
Alternatively, you can copy and paste the script from below. To do this, first remove your old copy of metropolis.py
using the command;
rm metropolis.py
Next, create a new file called metropolis.py
using the command;
nano metropolis.py
Then copy and paste the below script into that file;
import random
import math
import copy
# Set the number of atoms in the box
= 25
n_atoms
# Set the number of Monte Carlo moves to perform
= 5000
num_moves
# Set the size of the box (in Angstroms)
= [ 15.0, 15.0, 15.0 ]
box_size
# The maximum amount that the atom can be translated by
= 0.5 # angstroms
max_translate
# The maximum amount to change the volume - the
# best value is 10% of the number of atoms
= 0.1 * n_atoms # angstroms**3
max_volume_change
# Simulation temperature
= 298.15 # kelvin
temperature
# Simulation pressure
= 1 # atmospheres
pressure
# Convert pressure to internal units (kcal mol-1 A-3)
= pressure * 1.458397506863647E-005
pressure
# Give the Lennard Jones parameters for the atoms
# (these are the OPLS parameters for Krypton)
= 3.624 # angstroms
sigma = 0.317 # kcal mol-1
epsilon
# Create an array to hold the coordinates of the atoms
= []
coords
# Randomly generate the coordinates of the atoms in the box
for i in range(0,n_atoms):
# Note "random.uniform(x,y)" would generate a random number
# between x and y
0,box_size[0]), \
coords.append( [random.uniform(0,box_size[1]), \
random.uniform(0,box_size[2]) ] )
random.uniform(
def make_periodic(x, box):
"""Subroutine to apply periodic boundaries"""
while (x < -0.5*box):
+= box
x
while (x > 0.5*box):
-= box
x
return x
def wrap_into_box(x, box):
"""Subroutine to wrap the coordinates into a box"""
while (x > box):
-= box
x
while (x < 0):
+= box
x
return x
def print_pdb(move):
"""Print a PDB for the specified move"""
= "output%000006d.pdb" % move
filename
= open(filename, "w")
FILE
"CRYST1 %8.3f %8.3f %8.3f 90.00 90.00 90.00\n" % \
FILE.write(0], box_size[1], box_size[2]))
(box_size[
for i in range(0,n_atoms):
"ATOM %5d Kr Kr 1 %8.3f%8.3f%8.3f 1.00 0.00 Kr\n" % \
FILE.write(+1, coords[i][0], coords[i][1], coords[i][2]))
(i"TER\n")
FILE.write(
FILE.close()
# Subroutine that calculates the energies of the atoms
def calculate_energy():
"""Calculate the energy of the passed atoms (assuming all atoms
have the same LJ sigma and epsilon values)"""
# Loop over all pairs of atoms and calculate
# the LJ energy
= 0
total_energy
for i in range(0,n_atoms-1):
for j in range(i+1, n_atoms):
= coords[j][0] - coords[i][0]
delta_x = coords[j][1] - coords[i][1]
delta_y = coords[j][2] - coords[i][2]
delta_z
# Apply periodic boundaries
= make_periodic(delta_x, box_size[0])
delta_x = make_periodic(delta_y, box_size[1])
delta_y = make_periodic(delta_z, box_size[2])
delta_z
# Calculate the distance between the atoms
= math.sqrt( (delta_x*delta_x) + (delta_y*delta_y) +
r *delta_z) )
(delta_z
# E_LJ = 4*epsilon[ (sigma/r)^12 - (sigma/r)^6 ]
= 4.0 * epsilon * ( (sigma/r)**12 - (sigma/r)**6 )
e_lj
+= e_lj
total_energy
# return the total energy of the atoms
return total_energy
# calculate kT
= 1.987206504191549E-003 # kcal mol-1 K-1
k_boltz
= k_boltz * temperature
kT
# The total number of accepted moves
= 0
naccept
# The total number of rejected moves
= 0
nreject
# Print the initial PDB file
0)
print_pdb(
# Now perform all of the moves
for move in range(1,num_moves+1):
# calculate the old energy
= calculate_energy()
old_energy
# calculate the old volume of the box
= box_size[0] * box_size[1] * box_size[2]
V_old
# Pick a random atom (random.randint(x,y) picks a random
# integer between x and y, including x and y)
= random.randint(0, n_atoms-1)
atom
# save the old coordinates
= copy.deepcopy(coords)
old_coords
# save the old box dimensions
= copy.deepcopy(box_size)
old_box_size
# Decide if we are performing an atom move, or a volume move
if random.uniform(0.0,1.0) <= (1.0 / n_atoms):
# 1 in n_atoms chance of being here. Perform a volume move
# by changing the volume for a random amount
= random.uniform(-max_volume_change, max_volume_change)
delta_vol
# calculate the new volume
= V_old + delta_vol
V_new
# The new box length is the cube root of the volume
= V_new**(1.0/3.0)
box_side
# The amount to scale each atom from the center is
# the cube root of the ratio of the new and old volumes
= ( V_new / V_old )**(1.0/3.0)
scale_ratio
# now translate every atom so that it is scaled from the center
# of the box
for i in range(0,n_atoms):
= coords[i][0] - (0.5*box_size[0])
dx = coords[i][1] - (0.5*box_size[1])
dy = coords[i][2] - (0.5*box_size[2])
dz
= math.sqrt(dx*dx + dy*dy + dz*dz)
length
if length > 0.01: # don't scale atoms already near the center
/= length
dx /= length
dy /= length
dz
# now scale the distance from the atom to the box center
# by 'scale_ratio'
*= scale_ratio
length
# move the atom to its new location
0] = (0.5*box_size[0]) + dx*length
coords[i][1] = (0.5*box_size[1]) + dy*length
coords[i][2] = (0.5*box_size[2]) + dz*length
coords[i][
0] = box_side
box_size[1] = box_side
box_size[2] = box_side
box_size[else:
# Make the move - translate by a delta in each dimension
= random.uniform( -max_translate, max_translate )
delta_x = random.uniform( -max_translate, max_translate )
delta_y = random.uniform( -max_translate, max_translate )
delta_z
0] += delta_x
coords[atom][1] += delta_y
coords[atom][2] += delta_z
coords[atom][
# wrap the coordinates back into the box
0] = wrap_into_box(coords[atom][0], box_size[0])
coords[atom][1] = wrap_into_box(coords[atom][1], box_size[1])
coords[atom][2] = wrap_into_box(coords[atom][2], box_size[2])
coords[atom][
# calculate the new energy
= calculate_energy()
new_energy
# calculate the new volume of the box
= box_size[0] * box_size[1] * box_size[2]
V_new
= False
accept
# Automatically accept if the energy goes down
if (new_energy <= old_energy):
= True
accept
else:
# Now apply the Monte Carlo test - compare
# exp( -(E_new - E_old + P(V_new - V_old)) / kT
# + N ln (V_new - V_old) ) >= rand(0,1)
= math.exp( (-(new_energy - old_energy + pressure * (V_new - V_old)) / kT)
x + (n_atoms * (math.log(V_new) - math.log(V_old)) ) )
if (x >= random.uniform(0.0,1.0)):
= True
accept else:
= False
accept
if accept:
# accept the move
+= 1
naccept = new_energy
total_energy else:
# reject the move - restore the old coordinates
+= 1
nreject
# restore the old conformation
= copy.deepcopy(old_coords)
coords = copy.deepcopy(old_box_size)
box_size
= old_energy
total_energy
# print the energy every 10 moves
if move % 10 == 0:
print("%s %s %s %s" % (move, total_energy, naccept, nreject))
print(" Box size = %sx%sx%s. Volume = %s A^3" % \
0], box_size[1], box_size[2],
(box_size[0]*box_size[1]*box_size[2]))
box_size[
# print the coordinates every 100 moves
if move % 100 == 0:
print_pdb(move)