2.370 Project Report
Molecular Dynamics Simulation of a Lennard-Jones Fluid
Bolin Liao
In this project, a molecular dynamics study is carried out on a Lennard-Jones fluid. Main
algorithms implemented and their basic features include: 1) Cell list based force calculation is
used to speed up the simulation and save computation resource; 2) Nose-Hoover thermostat is
added to maintain the system temperature within a reasonable fluctuation range (the impace
of different “thermostat mass” is also studied), with Beeman predictor-corrector integration
algorithm to shorten the time needed for reaching equilibrium; 3) Long range corrections are
added when calculating total potential energy and pressure, and good agreements are achieved
between MD and Monte Carlo simulation results; 3) Brownian dynamics simulation is carried
out to measure the diffusion coefficient, and good agreement is achieved when compared to
experiment data. In the following parts of this report, details of the algorithms and data
structures that are used in each stage of this simulation will be presented, while the complete
codes attached as an appendix. The codes are written in Fortran.
1. Initialization Two purposes are to be achieved in the initialization stage (SUBROUTINE init). The first is to
arrange the atoms in a simple cubic structure, as their initial positions, and the second is to
assign velocities from a Maxwell distribution to each atom, and rescale them in order to obtain
desired temperature and guarantee a rest center of mass. The arrangement of atoms is
straightforward. For generating velocities from a Maxwell distribution, a polar form Box-Muller
algorithm is used to generate a Gaussian distribution from 2-dimensional uniform distribution,
and then all velocities are rescaled to achieve zero average velocity and correct temperature.
Codes:
FUNCTION rangauss()
!Polar form Box-Muller Gaussian random number generator
REAL*8 ran_uniform, rangauss, v1, v2, rsq
100 CALL RANDOM_NUMBER(v1)
v1=2.0*v1-1.0
CALL RANDOM_NUMBER(v2)
v2=2.0*v2-1.0
rsq=v1*v1+v2*v2
IF (rsq .GE. 1.0 .OR. rsq .LE. 0.0) GOTO 100
rangauss=v1*SQRT(-2.0*LOG(rsq)/rsq)
RETURN
END
!----------------------------------------------------------------------------
!----------------------------------------------------------------------
SUBROUTINE init (temp,rho,pos,vel,len)
USE constants
IMPLICIT NONE
!assign initial positions and velocities to the atoms
REAL*8 temp !temp: Temperature of the simulation
REAL*8 rho !rho: Number density of the simulation
REAL*8 pos(dim,num) !Position record matrix
REAL*8 vel(dim,num) !Velocity record matrix
REAL*8 rangauss !Gaussian random number generator
REAL*8 vol !volume of the system
REAL*8 len !length of the box
REAL*8 gsize !grid size of a unit cell
INTEGER num_pd !number of particles per dimension
INTEGER i,j,k !control variable for the loops
INTEGER n !label of particles
REAL*8 v_sum(dim) !average velocity
REAL*8 v2_sum !mean-square velocity
REAL*8 sf !temperature scaling factor
vol = num/rho
len = vol ** (1.0/3.0)
num_pd = INT(num ** (1.0/3.0))
gsize = len/num_pd
!position initialization: arrange atoms in a single cubic structure
n = 1
outer: DO i = 1,num_pd
DO j = 1,num_pd
DO k = 1,num_pd
if (n .GT. num) EXIT outer
pos(1,n) = (i-1)*gsize
pos(2,n) = (j-1)*gsize
pos(3,n) = (k-1)*gsize
n = n + 1
END DO
END DO
END DO outer
!velocity initialization: Maxwell distribution with rescaling
v_sum(1:dim)=0;
v2_sum = 0;
DO i = 1,num
vel(1,i) = rangauss()
vel(2,i) = rangauss()
vel(3,i) = rangauss()
v_sum(1) = v_sum(1) + vel(1,i)
v_sum(2) = v_sum(2) + vel(2,i)
v_sum(3) = v_sum(3) + vel(3,i)
v2_sum = v2_sum + vel(1,i)*vel(1,i)+vel(2,i)*vel(2,i)+vel(3,i)*vel(3,i)
END DO
v_sum(1) = v_sum(1)/REAL(num) !average velocity
v_sum(2) = v_sum(2)/REAL(num)
v_sum(3) = v_sum(3)/REAL(num)
v2_sum = v2_sum/REAL(num) !average velocity square
sf = SQRT(3.0*temp/v2_sum) !scaling factor to desired temperature
!Rescale the velocity to set the initial temperature
v2_sum = 0
DO i = 1,num
vel(1,i) = (vel(1,i)-v_sum(1))*sf
vel(2,i) = (vel(2,i)-v_sum(2))*sf
vel(3,i) = (vel(3,i)-v_sum(3))*sf
v2_sum = v2_sum + vel(1,i)*vel(1,i)+vel(2,i)*vel(2,i)+vel(3,i)*vel(3,i)
END DO
RETURN
END SUBROUTINE init
After initialization, the atom positions are as shown below:
0 2 4 6 8 10 12
0
5
10
0
2
4
6
8
10
12
x
Initialized Position
y
z
The velocity distribution is shown below:
We can tell from this distribution fitting plot that the average value of velocities is zero and the
variance of the distribution is 4.996 (the given temperature is 5), which matches the given
conditions very well.
2. Cell list and force calculation To speed up force calculation, a cell list is implemented. First the simulation volume is divided
to smaller cells with size equal to or slightly larger than the cut-off radius. Then a cell list is built
up that contains the labels of the atoms that are in certain cell. The basic data structure used in
my program is a link list1. The link list consists of two matrices: cell and link. Each cell has a
corresponding link list that stores the information of what atoms are located in this cell, the
head of which is stored in the matrix cell, while the label of the next atom in the link list is
stored in the matrix link. All elements of the cell matrix are initialized to zero. Then scan all the
atoms, locate them to certain cell, and insert the atom into the link list of the corresponding cell
that it belongs to, which follows the standard routine of operations on a link list.
1 D. Frankel, B. Smit, “Understanding Molecular Simulation”, 2
nd edition, pp550-552, 2002
-6 -4 -2 0 2 4 60
0.02
0.04
0.06
0.08
0.1
0.12
0.14
0.16
0.18
Data
Den
sity
Initial Distribution
Gaussian
mean: -6e-17, variance 4.996
Codes: (set up cell list)
SUBROUTINE cell_list (pos,cell,ncel,rcel,link)
!generating cell_list
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num)
INTEGER ncel !number of cells per dimension
REAL*8 rcel !size of a cell
INTEGER n !label of particles
INTEGER i,j,k !loop control
INTEGER cellx,celly,cellz !id of the cell for certain atom
INTEGER link(num)
INTEGER cell(0:ncel-1,0:ncel-1,0:ncel-1)
!initialize the cell list
cell(0:ncel-1,0:ncel-1,0:ncel-1) = 0
!set up the cell list
DO n = 1,num !scan all atoms
!locate the atom to certain cell
cellx = INT(pos(1,n)/rcel)
celly = INT(pos(2,n)/rcel)
cellz = INT(pos(3,n)/rcel)
!insert the atom into the link list of the corresponding cell
link(n) = cell(cellx,celly,cellz)
cell(cellx,celly,cellz)=n;
END DO
RETURN
END SUBROUTINE cell_list
After setting up the cell list, force calculation is straight forward. First scan all the atoms, and
locate it to certain cell, then find the head of the link list of the cell by referring to matrix cell,
which is the first atom in the cell, then calculate the potential energy and forces between the
two atoms, and finally move on to the next atom in the link list by referring to matrix link, until
reaching the tail of the link list, which is marked by 0, indicating the search of this cell is finished.
The nearest 26 cells of the cell also need to be scanned to calculate forces on a certain atom. By
this means, it is not necessary to scan all other atoms to calculate the force on a certain atom,
instead only 27 cells need to be scanned, which speeds up the force calculation a lot. Another
complication of force calculation has to do with the periodic boundary condition. I set up a
signal matrix called period to mark whether a periodic boundary condition is encountered and
adjust the position of the corresponding atoms accordingly.
Codes: (force calculation)
SUBROUTINE force_calculation (pos,cell,link,ncel,rcel,force,len,potential)
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num) !position matrix
INTEGER cell(0:ncel-1,0:ncel-1,0:ncel-1) !cell list
INTEGER link(num) !link list
INTEGER ncel !number of cells per dimension
REAL*8 rcel !size of cells
REAL*8 force(dim,num) !force matrix
REAL*8 len !size of the box
INTEGER n !labels for atoms
INTEGER cellx,celly,cellz !id of the cell for certain atom
REAL*8 pos1(dim) !the positon of the other atom that interacts with the
current atom
INTEGER id !the label of the other atom
INTEGER i,j,k,m,ii,jj,kk !loop control
INTEGER period(dim)
!flag for periodic b.c. 0:no periodic,1:lower bound,2:upper bound
REAL*8 distance(dim) !distance in each dimensions
REAL*8 radius2,radius6,radius6i !square of the distance
REAL*8 potential(num),uij !potential energy for each atom
DO n = 1,num !consider the atom labeled n
cellx = INT(pos(1,n)/rcel) !find which cell this atom sits in
celly = INT(pos(2,n)/rcel)
cellz = INT(pos(3,n)/rcel)
force(1,n)=0 !initialize forces
force(2,n)=0
force(3,n)=0
potential(n)=0 !initalize the internal energy
DO i = cellx-1,cellx+1 !scan the nearest 27 cells
DO j = celly-1,celly+1
DO k = cellz-1,cellz+1
ii=i; !keep track of the control variable for recovery
jj=j;
kk=k;
!implementing periodic boundary condition
period(1:dim)=0 !intialize the flag
!adjust the cell id and set up the flags
IF(ii .LT. 0) THEN
ii = ii + ncel
period(1) = 1
END IF
IF(ii .GT. ncel-1) THEN
ii = ii - ncel
period(1) = 2
END IF
IF(jj .LT. 0) THEN
jj = jj + ncel
period(2) = 1
END IF
IF(jj .GT. ncel-1) THEN
jj = jj - ncel
period(2) = 2
END IF
IF(kk .LT. 0) THEN
kk = kk + ncel
period(3) = 1
END IF
IF(kk .GT. ncel-1) THEN
kk = kk - ncel
period(3) = 2
END IF
!start from the head of the chain
id=cell(ii,jj,kk)
DO WHILE (id .NE. 0) !if not reached the tail of the chain
IF (id .EQ. n) THEN !if find the atom itself, skip
id = link(id)
CYCLE
END IF
!find the position of the interacting atom
DO m=1,dim
IF (period(m) .EQ. 0) THEN
pos1(m)=pos(m,id)
ELSE IF (period(m) .EQ. 1) THEN
pos1(m)=pos(m,id)-len
ELSE IF (period(m) .EQ. 2) THEN
pos1(m)=pos(m,id)+len
END IF
!distance between the two atoms in each dimension
distance(m)=pos(m,n)-pos1(m)
END DO
!if the atom is outside the cut-off radius, continue to the next atom
radius2 =
distance(1)*distance(1)+distance(2)*distance(2)+distance(3)*distance(3)
IF (radius2 .GT. rcut*rcut) THEN
id = link(id)
CYCLE
END IF
radius6 = radius2**3
radius6i = 1.0/radius6
!calculate potential energy
uij = 4*radius6i*(radius6i-1)
potential(n) = potential(n) + uij - ucut
!caululate forces
DO m = 1,dim
force(m,n) = force(m,n) +
distance(m)*6*(uij+4*(radius6i**2))/radius2
END DO
!move on to the next atom in the chain
id = link(id)
END DO
END DO
END DO
END DO
END DO
END SUBROUTINE force_calculation
3. Integration of EOM Knowing the forces on each atom, the next step is to integrate the equations of motion to make
the system start evolving with time. To keep a stable temperature along the simulation, a Nose-
Hoover thermostat is added to the system. Since the Nose-Hoover thermostat is generally not
compatible with velocity Verlet algorithm2, the Beeman predictor-corrector algorithm is chosen
as the integration scheme. Since in Beeman algorithm the forces in the previous time point are
needed to launch the iteration, a bootstrap subroutine using velocity Verlet algorithm is
designed to launch the system at the first step.
Codes: (launch the system at the first step)
SUBROUTINE bootstrap(pos,vel,force,len,temp,trace)
!bootstrap the system using velocity Verlet algorithm
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num)
REAL*8 vel(dim,num)
REAL*8 force(dim,num)
INTEGER i,j !loop control
REAL*8 sumv(dim) !summation of velocity
REAL*8 sumv2 !summation of velocity square
REAL*8 sf !temperature scaling factor
REAL*8 len !box size
REAL*8 temp !temperature
REAL*8 trace(dim,num) !trace matrix
REAL*8 delta_pos !the relative translation of position, adding up to trace
sumv(1:dim)=0 !initialize the average velocity
sumv2=0 !initialize the average squared velocity
DO i = 1,num
DO j = 1,dim
!calculate the relative motion
delta_pos = vel(j,i)*time_step + force(j,i)/(2.0*mass)*time_step*time_step
!update the position, and keep track of the trace(for diffusion experiment)
pos(j,i) = pos(j,i) + delta_pos
trace(j,i) = trace(j,i) + delta_pos
!update the velocity
vel(j,i) = vel(j,i) + force(j,i)/mass*time_step
sumv(j) = sumv(j) + vel(j,i)
sumv2 = sumv2 + vel(j,i)*vel(j,i)
!adjust position according to periodic boundary conditions
IF (pos(j,i) .LT. 0) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len + len
IF (pos(j,i) .GT. len) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len
END DO
END DO
!rescaling the velocity again
sumv(1)=sumv(1)/REAL(num)
sumv(2)=sumv(2)/REAL(num)
sumv(3)=sumv(3)/REAL(num)
sumv2=sumv2/REAL(num)
2 D. Frankel, B. Smit, “Understanding Molecular Simulation”, 2
nd edition, pp535, 2002
sf = SQRT(3.0*temp/sumv2)
sumv2=0
DO i = 1,num
DO j = 1,dim
vel(j,i) = (vel(j,i)-sumv(j))*sf
sumv2 = sumv2 + vel(j,i)*vel(j,i)
END DO
END DO
WRITE (*,*) 'Bootstrap Temperature: ' , sumv2/REAL(num)/3.0
END SUBROUTINE bootstrap
For subsequent time steps, the Beeman algorithm is used to integrate the equations of motion.
The Nose-Hoover thermostat is added in the force calculation process in a Beeman iteration.
The thermostat feedback variable ksi evolves with time, while the time derivative of ksi
depends on the difference between the actual temperature and the given temperature. The
“mass” of the thermostat determines the feedback sensitivity and thus the temperature
fluctuation. The impacts of different thermostat mass are evaluated in this project.
02
46
810
12
0
5
10
0
2
4
6
8
10
12
x
After Boostrap
y
z
Codes (Beeman integration algorithm)
SUBROUTINE
beeman(pos,vel,force,force1,cell,link,ncel,rcel,len,potential,ksi,trace)
!integrate the EOM using Beeman's predictor-corrector algorithm with Nose-
Hoover thermostat
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num)
REAL*8 vel(dim,num),velp(dim,num) !velp:the predicted velocity
!forces at the previous, current and the next time point
REAL*8 force(dim,num),force1(dim,num),force2(dim,num)
INTEGER cell(0:ncel-1,0:ncel-1,0:ncel-1) !cell list
INTEGER link(num)!link list
INTEGER ncel !number of cells per dimension
REAL*8 rcel,len,potential(num)
REAL*8 ksi !friction factor
INTEGER i,j !loop control
REAL*8 trace(dim,num) !trace of the atoms
REAL*8 delta_pos !relative motion
DO i = 1,num
DO j = 1,dim
!calculate relative motion
delta_pos = vel(j,i)*time_step +
time_step*time_step/6*(4*force1(j,i)-force(j,i))/mass
pos(j,i) = pos(j,i) + delta_pos
trace(j,i) = trace(j,i) + delta_pos
!periodic boundary conditions
IF (pos(j,i) .LT. 0) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len + len
IF (pos(j,i) .GT. len) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len
!predicted velocities
velp(j,i) = vel(j,i) + time_step/2*(3*force1(j,i)-force(j,i))/mass
END DO
END DO
!update cell list and calculate forces
CALL cell_list(pos,cell,ncel,rcel,link)
CALL force_calculation (pos,cell,link,ncel,rcel,force2,len,potential)
DO i = 1,num
DO j = 1,dim
!calculate forces incorporating thermostat
force2(j,i)=force2(j,i)-ksi*mass*velp(j,i)
!corrected velocity
vel(j,i) = vel(j,i) + time_step/6*(2*force2(j,i)+5*force1(j,i)-
force(j,i))/mass
END DO
END DO
RETURN
END SUBROUTINE beeman
To maintain a stable temperature, generally two methods could be used, velocity-rescaling and
adding a thermostat. I first tried adding Nose-Hoover thermostat at the beginning, without
temperature rescaling, and played with the thermostat mass, and I got the following results:
From this plot we could see relatively large fluctuations of temperature and very slow
convergence. To get a more stable temperature and faster convergence, I tried first rescaling
the system for 100 time steps, then turning on the thermostat and stopping the rescaling, and I
got the following result, which indicates a much more stable temperature.
0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5
x 104
4
4.2
4.4
4.6
4.8
5
5.2
5.4
5.6
5.8
6
t
tem
pera
ture
Thermostat test (w/o rescaling, T=5)
Q=100
Q=10
Q=1
Q=0.1
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 100004
4.2
4.4
4.6
4.8
5
5.2
5.4
5.6
5.8
6
t
tem
pera
ture
Thermostat test (with rescaling)
4. Measurement After the system reaches equilibrium, measurements of the system properties could be carried
out. In this project, temperature, pressure, total potential energy and diffusion coefficient are
measured. Temperature and pressure are measured as described in the project assignment.
The diffusion coefficient is measured using the random-walk model, where the traces of each
atom are recorded in a matrix called trace. The summation of the squares of the relative
displacements of all the atoms is supposed to increase linearly with time, mimicking a diffusion
process, the slope of which gives the diffusion coefficient.
Codes: (measurement of the system)
SUBROUTINE measure(pos,vel,temp1,pressure,etot,v_ave,potential,rho)
!measure system properties
USE constants
IMPLICIT NONE
REAL*8 rho,vol !number density and volume
REAL*8 pos(dim,num)
REAL*8 vel(dim,num)
REAL*8 temp1 !current temperature
REAL*8 pressure, pxx, pyy, pzz !current pressure
REAL*8 etot !total energy of the system
REAL*8 potential(num), psum !potential energy
REAL*8 v_ave(dim) !average velocity of each dimension
REAL*8 sumv2 !summation of velocity square
REAL*8 radius !distance between two atoms
REAL*8 force !force between two atoms
INTEGER i,j
!calculate average velocities
v_ave(1:dim)=0
DO i = 1,num
DO j = 1,dim
v_ave(j) = v_ave(j) + vel(j,i)
END DO
END DO
v_ave(1) = v_ave(1)/REAL(num)
v_ave(2) = v_ave(2)/REAL(num)
v_ave(3) = v_ave(3)/REAL(num)
!calculate temperature
sumv2 = 0
DO i = 1,num
DO j = 1,dim
sumv2 = sumv2 + (vel(j,i)-v_ave(j))*(vel(j,i)-v_ave(j))
END DO
END DO
temp1 = sumv2/REAL(num)/3.0
!calculate total potential energy
psum = 0
DO i = 1,num
psum = psum + potential(i)
END DO
etot = psum/2.0/REAL(num)
!measure the pressure
vol = REAL(num)/rho
pxx = 0 !configurational pressure
pyy = 0
pzz = 0
DO i = 1,num
DO j = i+1,num
radius = (pos(1,i)-pos(1,j))*(pos(1,i)-pos(1,j)) + (pos(2,i)-
pos(2,j))*(pos(2,i)-pos(2,j)) +(pos(3,i)-pos(3,j))*(pos(3,i)-pos(3,j))
force = 4.0/(radius**4)*(12/(radius**3)-6)
pxx = pxx + (pos(1,i)-pos(1,j))*(pos(1,i)-pos(1,j))*force
pyy = pyy + (pos(2,i)-pos(2,j))*(pos(2,i)-pos(2,j))*force
pzz = pzz + (pos(3,i)-pos(3,j))*(pos(3,i)-pos(3,j))*force
END DO
END DO
pressure = rho*temp1+(pxx+pyy+pzz)/3.0/vol
RETURN
END SUBROUTINE measure
The codes for diffusion experiment are scattered in different parts of whole program, which
could be found in the appendix.
5. Long Range Corrections Long range corrections for total potential energy and pressure are incorporated in this project.
The long-range corrections are provided in the following two equations:
∫ ( )
(
)
∫
(
)
Codes: (long-range corrections)
!-----------------------------------------------------------------
SUBROUTINE long_range_correction(rho,ucor,pcor)
USE constants
IMPLICIT NONE
!calculate the long range corrections for potential energy and pressure
REAL*8 rho !numdensity of the simulation
REAL*8 ucor !long range correction for potential energy
REAL*8 pcor !long range correction for pressure
ucor = 8.0*3.1415926*rho*(1.0/(9.0*rcut**9)-1.0/(3.0*rcut**3))
pcor = 16.0*3.1415926*rho*(2.0/(9.0*rcut**9)-1.0/(3.0*rcut**3))
RETURN
END SUBROUTINE long_range_correction
!-----------------------------------------------------------------
6. Test of Performance Test Conditions 1: temperature = 5, number density = 0.5
Here I show the time evolution of pressure for systems consist of 1000 and 8000 atoms
respectively. And we can see apparently less fluctuations in a larger system. The convergence is
fast, generally after 100 time steps (~2 ps), the system will be in equilibrium. The averaged
pressure is around 4.3, which is comparable with the Monte Carlo result (=4.65).
0 100 200 300 400 500 600 700 800 900 1000-1
0
1
2
3
4
5
time
pre
ssure
Pressure Vs. Time (T=5, n=0.5)
n=1000
n=8000
Potential energy measured: -2.1, comparable with the Monte Carlo result (= -2.37)
Test Condition 2: temperature = 2, number density = 0.9
The measured pressure is around 9, while the Monte Carlo gives 9.1.
0 100 200 300 400 500 600 700 800 900 1000
-3
-2.5
-2
-1.5
-1
time
pote
ntia
l en
erg
y
Potential Energy vs. Time (T=5, n=0.5)
n=1000
n=8000
0 100 200 300 400 500 600 700 800 900 10006
7
8
9
10
11
12
13
14
15
16
time
pre
ssure
Pressure (T=2, rho=0.9)
n=1000
n=8000
Measured potential energy is -4.5, while the Monte Carlo gives -5.
Test Condition 3 : temperature = 1, number density = 0.05
0 100 200 300 400 500 600 700 800 900 1000-5
-4.5
-4
-3.5
-3
-2.5
time
Pote
ntia
l en
erg
y
Potential energy (T=2, rho=0.9)
n= 8000
n= 1000
0 500 1000 1500 2000 2500 3000 3500 4000 4500 50000
0.01
0.02
0.03
0.04
0.05
0.06
0.07
0.08
0.09
0.1
time
pre
ssure
Pressure (T=1, rho=0.05)
n=1000
n=8000
Measured pressure: 0.043, while the Monte Carlo gives 0.037
Measured potential energy: -0.34, while Monte Carlo gives -0.478
Test Condition 4 : temperature = 1.1709 (140K), number density = 0.904 ( m-3)
0 500 1000 1500 2000 2500 3000 3500 4000 4500 5000-0.35
-0.3
-0.25
-0.2
-0.15
-0.1
-0.05
0
time
Pote
ntia
l en
erg
y
Potential Energy (T=1, rho=0.05)
n=1000
n=8000
0 100 200 300 400 500 600 700 800 900 10004
5
6
7
8
9
10
11
12
time
pote
ntia
l
Pressure ( T=1.1709, rho=0.904)
1000
8000
Measured pressure: 4.7 (197.3 MPa)
The slope of the linear fitting is 0.431. Since the slope equals 6D in 3-dimension case, so the
diffusion coefficient is D=0.431/6=0.0718, which is equivalent to
⁄ .
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
time
R2
Random Walk
R^2
Linear Fit
Appendix: The complete codes:
!Molecular Dynamics Simulation of Lennard-Jones liquid
!Author: Bolin Liao Date: 05/09/2011
!All Rights Reserved
!Nose-Hoover Thermostat with Beeman predictor-corrector algorithm
!
!---------------------------------------------------------------
!define constants
MODULE constants
IMPLICIT NONE
INTEGER num
PARAMETER (num = 8000)
INTEGER dim !dimension of the simulation
PARAMETER (dim = 3)
REAL*8 time_step !time step
PARAMETER (time_step = 1.e-3)
REAL*8 rcut !cut-off radius
PARAMETER (rcut = 2.4)
REAL*8 ucut !cut-off energy
PARAMETER (ucut=4.0/(rcut**6)*(1/(rcut**6)-1))
REAL*8 mass !mass of the particle
PARAMETER (mass=1.0)
REAL*8 qmass !Nose-Hoover thermostat mass
PARAMETER (qmass=10)
END MODULE constants
!----------------------------------------------------------------------
PROGRAM md
USE constants
IMPLICIT NONE
REAL*8 temp !assigned temperature
REAL*8 temp1 !current temperature
REAL*8 pressure !measured pressure
REAL*8 rho !assigned number density
REAL*8 pos(dim,num) !position matrix
REAL*8 vel(dim,num) !velocity matrix
REAL*8 v_ave(dim) !average velocity
REAL*8 len !length of the box
INTEGER ncel !number of cells
REAL*8 rcel !size of a cell
INTEGER, ALLOCATABLE, DIMENSION(:,:,:) :: cell !cell list
INTEGER link(num) !link list for cells
REAL*8 force(dim,num),force1(dim,num) !force matrix
REAL*8 potential(num) !potential energy
REAL*8 ucor !long range correction for potential energy
REAL*8 pcor !long range correction for pressure
REAL*8 etot !total energy
REAL*8 ksi !friction factor for Nose-Hoover thermostat
REAL*8 trace(dim,num),trace2 !trace of all atoms - for calculating Diffusion
Coefficient
REAL*8 sf !temperature scaling factor
INTEGER cyc !cycle of simulation
INTEGER i,j,k !loop control
WRITE (*,*) 'Input Simulation Temperature'
READ (*,*) temp
WRITE(*,*) 'Input Simulation Density'
READ (*,*) rho
WRITE(*,*) 'Input Simulation Cycle'
READ (*,*) cyc
OPEN (UNIT=21, FILE='test.dat', STATUS='REPLACE')
!initialization
CALL init (temp,rho,pos,vel,len)
WRITE (*,*) 'Initialization Done!'
!calculate long range correction factors
CALL long_range_correction(rho,ucor,pcor)
WRITE (21,*) 'long range correction for potential:', ucor
WRITE (21,*) 'long range correction for pressure:', pcor
!initialize the cell list parameters
ncel=INT(len/rcut)
rcel=len/REAL(ncel)
WRITE (*,*) 'len: ', len,'ncel: ',ncel,'rcel: ',rcel
ALLOCATE (cell(0:ncel-1,0:ncel-1,0:ncel-1))
!set up cell list and calculate forces
CALL cell_list(pos,cell,ncel,rcel,link)
CALL force_calculation (pos,cell,link,ncel,rcel,force,len,potential)
!bootstrap the system
trace(1:dim,1:num) = 0 !initialize the trace matrix
CALL bootstrap(pos,vel,force,len,temp,trace)
CALL cell_list(pos,cell,ncel,rcel,link)
CALL force_calculation (pos,cell,link,ncel,rcel,force1,len,potential)
ksi = 0 !initialize the friction factor
DO i = 1,cyc
CALL
beeman(pos,vel,force,force1,cell,link,ncel,rcel,len,potential,ksi,trace)
force = force1
CALL cell_list(pos,cell,ncel,rcel,link)
CALL force_calculation (pos,cell,link,ncel,rcel,force1,len,potential)
CALL measure(pos,vel,temp1,pressure,etot,v_ave,potential,rho)
pressure = pressure + pcor
etot = etot + ucor
!calculate the collective displacement to obtain diffusion coefficient
trace2 = 0
DO j = 1,num
trace2 = trace2 + trace(1,j)*trace(1,j) + trace(2,j)*trace(2,j) +
trace(3,j)*trace(3,j)
END DO
trace2 = trace2/REAL(num)
200 FORMAT (F12.7,F12.7,F12.7,F12.7)
WRITE (21,200) temp1, pressure, etot,trace2
WRITE (*,200) temp1, pressure, etot,trace2
!Rescaling till 200 time steps to turn on thermostat
IF (i .LT. 200) THEN
sf = SQRT(temp/temp1)
vel = vel * sf
ELSE
ksi = ksi + (3*num+1)/qmass*(temp1-temp)*time_step
END IF
END DO
CLOSE (UNIT=21)
END PROGRAM md
!----------------------------------------------------------------------
!----------------------------------------------------------------------
SUBROUTINE init (temp,rho,pos,vel,len)
USE constants
IMPLICIT NONE
REAL*8 temp !temp: Temperature of the simulation
REAL*8 rho !rho: Number density of the simulation
REAL*8 pos(dim,num) !Position record matrix
REAL*8 vel(dim,num) !Velocity record matrix
REAL*8 rangauss !Gaussian random number generator
REAL*8 vol !volume of the system
REAL*8 len !length of the box
REAL*8 gsize !grid size of a unit cell
INTEGER num_pd !number of particles per dimension
INTEGER i,j,k !control variable for the loops
INTEGER n !label of particles
REAL*8 v_sum(dim) !average velocity
REAL*8 v2_sum !mean-square velocity
REAL*8 sf !temperature scaling factor
vol = num/rho
len = vol ** (1.0/3.0)
num_pd = INT(num ** (1.0/3.0))
!WRITE (*,*) 'number of particles per side' , num_pd
gsize = len/num_pd
!position initialization
n = 1
outer: DO i = 1,num_pd
DO j = 1,num_pd
DO k = 1,num_pd
if (n .GT. num) EXIT outer
pos(1,n) = (i-1)*gsize
pos(2,n) = (j-1)*gsize
pos(3,n) = (k-1)*gsize
n = n + 1
END DO
END DO
END DO outer
!velocity initialization
v_sum(1:dim)=0;
v2_sum = 0;
DO i = 1,num
vel(1,i) = rangauss()
vel(2,i) = rangauss()
vel(3,i) = rangauss()
v_sum(1) = v_sum(1) + vel(1,i)
v_sum(2) = v_sum(2) + vel(2,i)
v_sum(3) = v_sum(3) + vel(3,i)
v2_sum = v2_sum + vel(1,i)*vel(1,i)+vel(2,i)*vel(2,i)+vel(3,i)*vel(3,i)
END DO
v_sum(1) = v_sum(1)/REAL(num) !average velocity
v_sum(2) = v_sum(2)/REAL(num)
v_sum(3) = v_sum(3)/REAL(num)
v2_sum = v2_sum/REAL(num) !average velocity square
sf = SQRT(3.0*temp/v2_sum) !scaling factor to desired temperature
!Rescale the velocity to set the initial temperature
v2_sum = 0
DO i = 1,num
vel(1,i) = (vel(1,i)-v_sum(1))*sf
vel(2,i) = (vel(2,i)-v_sum(2))*sf
vel(3,i) = (vel(3,i)-v_sum(3))*sf
v2_sum = v2_sum + vel(1,i)*vel(1,i)+vel(2,i)*vel(2,i)+vel(3,i)*vel(3,i)
END DO
!WRITE (21,*) v2_sum/REAL(num)/3.0
RETURN
END SUBROUTINE init
!----------------------------------------------------------------------------
-
!----------------------------------------------------------------------------
-
SUBROUTINE cell_list (pos,cell,ncel,rcel,link)
!generating cell_list
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num)
INTEGER ncel !number of cells per dimension
REAL*8 rcel !size of a cell
INTEGER n !label of particles
INTEGER i,j,k !loop control
INTEGER cellx,celly,cellz !id of the cell for certain atom
INTEGER link(num)
INTEGER cell(0:ncel-1,0:ncel-1,0:ncel-1)
!initialize the cell list
cell(0:ncel-1,0:ncel-1,0:ncel-1) = 0
!set up the cell list
DO n = 1,num !scan all atoms
!locate the atom
cellx = INT(pos(1,n)/rcel)
celly = INT(pos(2,n)/rcel)
cellz = INT(pos(3,n)/rcel)
!insert the atom into the link list
link(n) = cell(cellx,celly,cellz)
cell(cellx,celly,cellz)=n;
END DO
RETURN
END SUBROUTINE cell_list
!----------------------------------------------------------------------------
-
!----------------------------------------------------------------------------
-
SUBROUTINE force_calculation (pos,cell,link,ncel,rcel,force,len,potential)
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num) !position matrix
INTEGER cell(0:ncel-1,0:ncel-1,0:ncel-1) !cell list
INTEGER link(num) !link list
INTEGER ncel !number of cells per dimension
REAL*8 rcel !size of cells
REAL*8 force(dim,num) !force matrix
REAL*8 len !size of the box
INTEGER n !labels for atoms
INTEGER cellx,celly,cellz !id of the cell for certain atom
REAL*8 pos1(dim) !the positon of the other atom that interacts with the
current atom
INTEGER id !the label of the other atom
INTEGER i,j,k,m,ii,jj,kk !loop control
INTEGER period(dim)
!flag for periodic boundary condition 0:no periodic, 1: lower bound, 2: upper
bound
REAL*8 distance(dim) !distance in each dimensions
REAL*8 radius2,radius6,radius6i !square of the distance
REAL*8 potential(num),uij !potential energy for each atom
DO n = 1,num !consider the atom labeled n
cellx = INT(pos(1,n)/rcel) !find which cell this atom sits in
celly = INT(pos(2,n)/rcel)
cellz = INT(pos(3,n)/rcel)
force(1,n)=0 !initialize forces
force(2,n)=0
force(3,n)=0
potential(n)=0 !initalize the internal energy
DO i = cellx-1,cellx+1 !scan the nearest 27 cells
DO j = celly-1,celly+1
DO k = cellz-1,cellz+1
ii=i; !keep track of the control variable for recovery
jj=j;
kk=k;
!implementing periodic boundary condition
period(1:dim)=0 !intialize the flag
!adjust the cell id and set up the flags
IF(ii .LT. 0) THEN
ii = ii + ncel
period(1) = 1
END IF
IF(ii .GT. ncel-1) THEN
ii = ii - ncel
period(1) = 2
END IF
IF(jj .LT. 0) THEN
jj = jj + ncel
period(2) = 1
END IF
IF(jj .GT. ncel-1) THEN
jj = jj - ncel
period(2) = 2
END IF
IF(kk .LT. 0) THEN
kk = kk + ncel
period(3) = 1
END IF
IF(kk .GT. ncel-1) THEN
kk = kk - ncel
period(3) = 2
END IF
!start from the head of the chain
id=cell(ii,jj,kk)
DO WHILE (id .NE. 0) !if not reached the tail of the chain
IF (id .EQ. n) THEN !if find the atom itself, skip
id = link(id)
CYCLE
END IF
!find the position of the interacting atom
DO m=1,dim
IF (period(m) .EQ. 0) THEN
pos1(m)=pos(m,id)
ELSE IF (period(m) .EQ. 1) THEN
pos1(m)=pos(m,id)-len
ELSE IF (period(m) .EQ. 2) THEN
pos1(m)=pos(m,id)+len
END IF
distance(m)=pos(m,n)-pos1(m) !distance between the
two atoms in each dimension
END DO
!if the atom is outside the cut-off radius, continue to
the next atom
radius2 =
distance(1)*distance(1)+distance(2)*distance(2)+distance(3)*distance(3)
IF (radius2 .GT. rcut*rcut) THEN
id = link(id)
CYCLE
END IF
radius6 = radius2**3
radius6i = 1.0/radius6
!calculate potential energy
uij = 4*radius6i*(radius6i-1)
potential(n) = potential(n) + uij - ucut
!caululate forces
DO m = 1,dim
force(m,n) = force(m,n) +
distance(m)*6*(uij+4*(radius6i**2))/radius2
END DO
!move on to the next atom in the chain
id = link(id)
END DO
END DO
END DO
END DO
END DO
END SUBROUTINE force_calculation
!----------------------------------------------------------------------------
-
!----------------------------------------------------------------------------
-
SUBROUTINE bootstrap(pos,vel,force,len,temp,trace)
!bootstrap the system using velocity Verlet algorithm
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num)
REAL*8 vel(dim,num)
REAL*8 force(dim,num)
INTEGER i,j !loop control
REAL*8 sumv(dim) !summation of velocity
REAL*8 sumv2 !summation of velocity square
REAL*8 sf !temperature scaling factor
REAL*8 len !box size
REAL*8 temp !temperature
REAL*8 trace(dim,num) !trace matrix
REAL*8 delta_pos !the relative translation of position, adding up to trace
sumv(1:dim)=0 !initialize the average velocity
sumv2=0 !initialize the average squared velocity
DO i = 1,num
DO j = 1,dim
!calculate the relative move
delta_pos = vel(j,i)*time_step +
force(j,i)/(2.0*mass)*time_step*time_step
!update the position, and keep track of the trace(for diffusion
experiment)
pos(j,i) = pos(j,i) + delta_pos
trace(j,i) = trace(j,i) + delta_pos
!update the velocity
vel(j,i) = vel(j,i) + force(j,i)/mass*time_step
sumv(j) = sumv(j) + vel(j,i)
sumv2 = sumv2 + vel(j,i)*vel(j,i)
!adjust position according to periodic boundary conditions
IF (pos(j,i) .LT. 0) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len +
len
IF (pos(j,i) .GT. len) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len
END DO
END DO
!rescaling the velocity again
sumv(1)=sumv(1)/REAL(num)
sumv(2)=sumv(2)/REAL(num)
sumv(3)=sumv(3)/REAL(num)
sumv2=sumv2/REAL(num)
sf = SQRT(3.0*temp/sumv2)
sumv2=0
DO i = 1,num
DO j = 1,dim
vel(j,i) = (vel(j,i)-sumv(j))*sf
sumv2 = sumv2 + vel(j,i)*vel(j,i)
END DO
END DO
WRITE (*,*) 'Bootstrap Temperature: ' , sumv2/REAL(num)/3.0
END SUBROUTINE bootstrap
!----------------------------------------------------------------------------
-
!----------------------------------------------------------------------------
-
SUBROUTINE
beeman(pos,vel,force,force1,cell,link,ncel,rcel,len,potential,ksi,trace)
!integrate the EOM using Beeman's predictor-corrector algorithm with Nose-
Hoover thermostat
USE constants
IMPLICIT NONE
REAL*8 pos(dim,num)
REAL*8 vel(dim,num),velp(dim,num) !velp:the predicted velocity
!forces at the previous, current and the next time point
REAL*8 force(dim,num),force1(dim,num),force2(dim,num)
INTEGER cell(0:ncel-1,0:ncel-1,0:ncel-1) !cell list
INTEGER link(num)!link list
INTEGER ncel !number of cells per dimension
REAL*8 rcel,len,potential(num)
REAL*8 ksi !friction factor
INTEGER i,j !loop control
REAL*8 trace(dim,num) !trace of the atoms
REAL*8 delta_pos !relative motion
DO i = 1,num
DO j = 1,dim
!calculate relative motion
delta_pos = vel(j,i)*time_step +
time_step*time_step/6*(4*force1(j,i)-force(j,i))/mass
pos(j,i) = pos(j,i) + delta_pos
trace(j,i) = trace(j,i) + delta_pos
!periodic boundary conditions
IF (pos(j,i) .LT. 0) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len +
len
IF (pos(j,i) .GT. len) pos(j,i) = pos(j,i) - INT(pos(j,i)/len)*len
!predicted velocities
velp(j,i) = vel(j,i) + time_step/2*(3*force1(j,i)-force(j,i))/mass
END DO
END DO
!update cell list and calculate forces
CALL cell_list(pos,cell,ncel,rcel,link)
CALL force_calculation (pos,cell,link,ncel,rcel,force2,len,potential)
DO i = 1,num
DO j = 1,dim
!calculate forces incorporating thermostat
force2(j,i)=force2(j,i)-ksi*mass*velp(j,i)
!corrected velocity
vel(j,i) = vel(j,i) + time_step/6*(2*force2(j,i)+5*force1(j,i)-
force(j,i))/mass
END DO
END DO
RETURN
END SUBROUTINE beeman
!----------------------------------------------------------------------------
-
!----------------------------------------------------------------------------
-
SUBROUTINE measure(pos,vel,temp1,pressure,etot,v_ave,potential,rho)
!measure system properties
USE constants
IMPLICIT NONE
REAL*8 rho,vol !number density and volume
REAL*8 pos(dim,num)
REAL*8 vel(dim,num)
REAL*8 temp1 !current temperature
REAL*8 pressure, pxx, pyy, pzz !current pressure
REAL*8 etot !total energy of the system
REAL*8 potential(num), psum !potential energy
REAL*8 v_ave(dim) !average velocity of each dimension
REAL*8 sumv2 !summation of velocity square
REAL*8 radius !distance between two atoms
REAL*8 force !force between two atoms
INTEGER i,j
!calculate average velocities
v_ave(1:dim)=0
DO i = 1,num
DO j = 1,dim
v_ave(j) = v_ave(j) + vel(j,i)
END DO
END DO
v_ave(1) = v_ave(1)/REAL(num)
v_ave(2) = v_ave(2)/REAL(num)
v_ave(3) = v_ave(3)/REAL(num)
!calculate temperature
sumv2 = 0
DO i = 1,num
DO j = 1,dim
sumv2 = sumv2 + (vel(j,i)-v_ave(j))*(vel(j,i)-v_ave(j))
END DO
END DO
temp1 = sumv2/REAL(num)/3.0
!calculate total potential energy
psum = 0
DO i = 1,num
psum = psum + potential(i)
END DO
etot = psum/2.0/REAL(num)
!measure the pressure
vol = REAL(num)/rho
pxx = 0 !configurational pressure
pyy = 0
pzz = 0
DO i = 1,num
DO j = i+1,num
radius = (pos(1,i)-pos(1,j))*(pos(1,i)-pos(1,j)) + (pos(2,i)-
pos(2,j))*(pos(2,i)-pos(2,j)) +(pos(3,i)-pos(3,j))*(pos(3,i)-pos(3,j))
force = 4.0/(radius**4)*(12/(radius**3)-6)
pxx = pxx + (pos(1,i)-pos(1,j))*(pos(1,i)-pos(1,j))*force
pyy = pyy + (pos(2,i)-pos(2,j))*(pos(2,i)-pos(2,j))*force
pzz = pzz + (pos(3,i)-pos(3,j))*(pos(3,i)-pos(3,j))*force
END DO
END DO
pressure = rho*temp1+(pxx+pyy+pzz)/3.0/vol
RETURN
END SUBROUTINE measure
!-----------------------------------------------------------------
!-----------------------------------------------------------------
SUBROUTINE long_range_correction(rho,ucor,pcor)
USE constants
IMPLICIT NONE
!calculate the long range corrections for potential energy and pressure
REAL*8 rho !numdensity of the simulation
REAL*8 ucor !long range correction for potential energy
REAL*8 pcor !long range correction for pressure
ucor = 8.0*3.1415926*rho*(1.0/(9.0*rcut**9)-1.0/(3.0*rcut**3))
pcor = 16.0*3.1415926*rho*(2.0/(9.0*rcut**9)-1.0/(3.0*rcut**3))
RETURN
END SUBROUTINE long_range_correction
!-----------------------------------------------------------------
!-----------------------------------------------------------------
FUNCTION rangauss()
!
!To generate random numbers from a gaussian distribution.
!
REAL*8 ran_uniform, rangauss, v1, v2, rsq
100 CALL RANDOM_NUMBER(v1)
v1=2.0*v1-1.0
CALL RANDOM_NUMBER(v2)
v2=2.0*v2-1.0
rsq=v1*v1+v2*v2
IF (rsq .GE. 1.0 .OR. rsq .LE. 0.0) GOTO 100
rangauss=v1*SQRT(-2.0*LOG(rsq)/rsq)
RETURN
END
!----------------------------------------------------------------------------
---