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, 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
# Simulation temperature
= 298.15 # kelvin
temperature
# 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
# 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
# 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
= 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) / kT ) >= rand(0,1)
= math.exp( -(new_energy - old_energy) / kT )
x
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
= 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 the coordinates every 100 moves
if move % 100 == 0:
print_pdb(move)