# Soft Disk Lennard-Jones 2D fluid
In this proble we try to simulate soft-disk fluid that consist of small particle interact with each other through Lennard-Jones potential in which are repulsive at short distance and attractive at longer distance.
\begin{equation}
V\left(r\right)=4\epsilon\left[\left({\dfrac{\sigma}{r}}\right)^{12}-\left({\dfrac{\sigma}{r}}\right)^{6}\right]
\tag{1}
\end{equation}
And the force is the gradient of the potential plus a minus sign
\begin{equation}
F\left(r\right)=
\dfrac{24\epsilon}{r}\dfrac{\sigma}{r}^{6}[2\left({\dfrac{\sigma}{r}}\right)^{6}-1]
\tag{2}
\end{equation}
## Verlet and Predictor-Corrector method
There are two easy way that we apply to solve the equation of motion by this force, since this system required relatively large amount of particle so we need a method that is both efficiency and accuracy. Both verlet and predictor-corrector are the two method that is on the top of the shelf. We first use velocity verlet method and did convergence test on both energy and momentum. The result are pretty decent, as the time step reduce the value converges and both energy and momentum have
relatively small error at $dt=10^{-4}$.
![](https://i.imgur.com/j0znJNF.png)
We then take a step further by apply predictor-corrector method in which the central idea is that at each step it try to converge the difference between predictor and corrector until it reach a desire tolerance. Below figure show that the difference between predictor and corrector after at each time of correction. The difference decrease down to machine accuracy after roughly 8 iteration. So in our program, there are two criteria that we can consider the corrector are good enough: first is it need to at least iterate over at least 4 times, secondly it the difference needs to be less than the tolerance. If the the value didn't converges after 8 iteration, we will stop the iteration.
![](https://i.imgur.com/cPnX7mD.png)
Similarly, we did the convergence test with predictor-corrector method using difference tolerance which show a better accuracy with the same time step compare to verlet method. Follow table summarize the pros and cons of two method.
![](https://i.imgur.com/2lbdGgS.jpg)
![](https://i.imgur.com/aPJddge.png)
As you can see from above two figure about convergence of two method, I also show the temperature of the system. After a while the temperature approach equilibrium. I then measure the probability distribution of the speed $P(v_x)$, $P(v_y)$ and $P(v)$ at different temperature. Below three column are different initial velocity which result to different equilibrium temperature.
## Periodic Boundary Condition
Since we are considering a infinite 2d system, we can use peridic boundary condition to achieve that. When particle hit one side of the wall, it will reappear on the other side; and the force we need to calculate the nearest force considering the image of periodic boundary condition.
![](https://i.imgur.com/o79B6CO.png)
## Maxwell-Boltzmann distribution
As you can see, according to Maxwell-Boltzmann distribution when the temperature are lower the distribution are more center at low speed with a higher peak, but if the temperature increase the distribution spreads out and the height also decrease. Since our system are smaller (only 49 particles), the distribution looks very discrete. If we increase the number of particles, the equilibrium temperature increase and the graph become smoother. Since the temperature also higher the distribution didn't seem much difference, but we can still see that the peak of each distribution shift to higher velocity.
\begin{equation*}
NP = 49
\end{equation*}
![](https://i.imgur.com/i9mNUWX.jpg)
\begin{equation*}
NP = 100
\end{equation*}
![](https://i.imgur.com/GfxOnXT.jpg)
## Temperature and Pressure
The formula for measuring temperature and pressure are given below.
\begin{equation}
k_BT = \dfrac{m}{d} \langle v^2 \rangle
\tag{3}
\end{equation}
and
\begin{equation}
P = \dfrac{1}{A} \left[ Nk_BT - \dfrac{1}{2} \sum_{k=1}^{N}
\sum_{j<k} \dfrac{dV(r)}{dr}r_{jk} \right]
\tag{4}
\end{equation}
Using above two equation we can monitor system temperature and
pressure at all time. Below show that after some time both
temperature and pressure goes to equilibrium.
\begin{equation*}
NP = 49
\end{equation*}
![](https://i.imgur.com/VLNOldq.png)
\begin{equation*}
NP = 100
\end{equation*}
![](https://i.imgur.com/zK583Hm.png)
## Heat Capacity
If we then measure the energy after equilibrium and then by varying the initial velocity using following formula we can get roughly the desire equilibrium temperature.
\begin{equation}
v = v \sqrt{\dfrac{T_{desire}}{T_{old}}}
\tag{5}
\end{equation}
And the heat capacity $c_v$ is given by $\dfrac{dE}{dT}$. Below left figure show the $E(t)$ relation. The energy is negative is because it is govern by potential energy. In the case of $NP=49$ the heat capacity is roughly 58.
![](https://i.imgur.com/3LVZj8m.png)
There is an alternatively way of calculating heat capacity which is given by
\begin{equation}
\langle T^2 \rangle - \langle T \rangle^2 = N(k_B
\langle T \rangle)^2 [1-\dfrac{Nk_B}{c_v}].
\tag{6}
\end{equation}
Using this formula we then measure the same system for heat capacity, the heat capacity are different by some degree when $NP=100$ . But if we look at same method for two different number
of particles, you can see that $c_v$ roughly double as the particle number double which is what we expected.
\begin{equation*}
NP = 49
\end{equation*}
![](https://i.imgur.com/TOJZB7n.png)
\begin{equation*}
NP = 100
\end{equation*}
![](https://i.imgur.com/i8pXjwc.png)
## Pair correlation of LJ fluid
![](https://i.imgur.com/fcpVTcE.png)
## Code
```fortran=
!========================================================
! purpose: MD simulation
!
! methods: velocity verlet + predictor-corrector
!
! input: NP, L, maxt, vmax, h
!
! output: position, velocity, T, K, U, P, Cv
!
! date programer description of change
! ==== ========= =====================
! 11/19/20 morrisH original code
!========================================================
program MD
implicit none
integer :: NP, i, n, j
double precision :: L, maxt, h, time, tol
double precision, dimension(:,:), allocatable :: r, v, rbef
real :: start, finish
double precision :: T, vmax, P
double precision :: TF
! parameter
time = 0.d0
NP = 100
L = 8.d0
maxt = 2.d-1
h = 1.d-4
tol = 1.d-17
vmax = 0.d0
TF = 1000.d0
n = nint(maxt / h)
allocate(r(NP, 2))
allocate(rbef(NP, 2))
allocate(v(NP, 2))
call init(NP, L, r, v, vmax)
! call output(NP, L, r, v, time, T, P)
call cpu_time(start)
! move first step
rbef(i, :) = r(i, :)
call verlet(NP, L, r, v, h)
do
do i = 1, n
! call verlet(NP, L, r, v, h)
call predcorr(NP, L, r, v, h, rbef, tol)
! call output(NP, L, r, v, time, T, P)
call pstatus(n, i, time, T, P)
time = time + h
end do
call output(NP, L, r, v, time, T, P)
! rescale velocity
do j = 1, NP
v(j, :) = dsqrt((T + 1.d1) / T) * v(j, :)
end do
if (T.gt.TF) then; exit; end if
end do
call cpu_time(finish)
write(*, *) 'cost :', finish-start
end program MD
subroutine init(NP, L, r, v, vmax)
implicit none
integer :: NP, i, j, num
double precision :: L, r(NP, 2), v(NP, 2), vmax, dx, dy
call random_seed()
num = int(sqrt(dble(NP)))
dx = L / (dble(num) + 1.d0)
dy = L / (dble(num) + 1.d0)
! random position
do i = 1, num
do j = 1, num
r(num*(i-1)+j, 1) = dble(i) * dx
r(num*(i-1)+j, 2) = dble(j) * dy
end do
end do
! random velocity
call random_number(v)
v = (v - .5d0) * 2.d0 * vmax
end subroutine init
subroutine output(NP, L, r, v, time, T, P)
implicit none
integer :: NP, i, j
double precision :: r(NP, 2), v(NP, 2), L, K, U, E, Px, Py, T, P, Cv
double precision :: r1(2), r2(2), d, dx, dy, time
double precision :: TT
! conpute some physical quantity
K = 0.d0
U = 0.d0
Px = 0.d0
Py = 0.d0
T = 0.d0
TT = 0.d0
P = 0.d0
do i = 1, NP
r1 = r(i, :)
K = K + 0.5d0 * (v(i, 1)**2 + v(i, 2)**2)
Px = Px + v(i, 1)
Py = Py + v(i, 2)
T = T + (v(i, 1)**2 + v(i, 2)**2)
TT = TT + (v(i, 1)**2 + v(i, 2)**2)**2
! calculate potential energy
do j = 1, i
if (i.ne.j) then
r2 = r(j, :)
d = dsqrt((r1(1) - r2(1))**2 + (r1(2) - r2(2))**2)
if (d.gt.(L/2.d0)) then
! find nearest image
dx = r2(1) - r1(1)
dy = r2(2) - r1(2)
if (dx.gt.(L/2.d0)) then; dx = dx - L; end if
if (dy.gt.(L/2.d0)) then; dy = dy - L; end if
if (dx.lt.(-L/2.d0)) then; dx = dx + L; end if
if (dy.lt.(-L/2.d0)) then; dy = dy + L; end if
d = dsqrt(dx ** 2 + dy ** 2)
U = U + 4.d0 * ( (1.d0 / d)**12 - (1.d0 / d)*6 )
P = P + 0.5d0 * (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * d
else
U = U + 4.d0 * ( (1.d0 / d)**12 - (1.d0 / d)*6 )
P = P + 0.5d0 * (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * d
end if
end if
end do
end do
T = T / NP
TT = TT / NP
E = K + U
P = (P + NP * T) / L**2
Cv = NP / (1.d0 - (TT - T**2) / (NP * T**2))
! open(66, file='data/np49/Cv/r.dat', access='append', status='unknown')
! write(66, '(100e40.30)') r(:, 1)
! write(66, '(100e40.30)') r(:, 2)
! close(66)
! open(77, file='data/np49/Cv/v.dat', access='append', status='unknown')
! write(77, '(100e40.30)') v(:, 1)
! write(77, '(100e40.30)') v(:, 2)
! close(77)
open(66, file='data/np100/Cv/info_alt.dat', access='append', status='unknown')
write(66, '(9e40.30)') time, K, U, E, Px, Py, T, P, Cv
close(66)
end subroutine output
subroutine verlet(NP, L, r, v, h)
implicit none
integer :: NP, i, j
double precision :: L, r(NP, 2), v(NP, 2), rnew(NP, 2), vnew(NP, 2)
double precision :: a1(2), a2(2), d, dx, dy, r1(2), r2(2)
double precision :: h
do i = 1, NP
r1 = r(i, :)
! compute a(t)
a1 = 0.d0
do j = 1, NP
if (i.ne.j) then
r2 = r(j, :)
d = dsqrt((r1(1) - r2(1))**2 + (r1(2) - r2(2))**2)
if (d.gt.(L/2.d0)) then
! find nearest image
dx = r2(1) - r1(1)
dy = r2(2) - r1(2)
if (dx.gt.(L/2.d0)) then; dx = dx - L; end if
if (dy.gt.(L/2.d0)) then; dy = dy - L; end if
if (dx.lt.(-L/2.d0)) then; dx = dx + L; end if
if (dy.lt.(-L/2.d0)) then; dy = dy + L; end if
d = dsqrt(dx ** 2 + dy ** 2)
a1 = a1 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(/ -dx, -dy /) / d
else
a1 = a1 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(r1 - r2) / d
end if
end if
end do
rnew(i, :) = r(i, :) + v(i, :) * h + .5d0 * a1 * h ** 2
! compute a(t+h)
a2 = 0.d0
r1 = rnew(i, :)
do j = 1, NP
if (i.ne.j) then
r2 = r(j, :)
d = dsqrt((r1(1) - r2(1))**2 + (r1(2) - r2(2))**2)
if (d.gt.(L/2.d0)) then
! find nearest image
dx = r2(1) - r1(1)
dy = r2(2) - r1(2)
if (dx.gt.(L/2.d0)) then; dx = dx - L; end if
if (dy.gt.(L/2.d0)) then; dy = dy - L; end if
if (dx.lt.(-L/2.d0)) then; dx = dx + L; end if
if (dy.lt.(-L/2.d0)) then; dy = dy + L; end if
d = dsqrt(dx ** 2 + dy ** 2)
a2 = a2 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(/ -dx, -dy /) / d
else
a2 = a2 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(r1 - r2) / d
end if
end if
end do
vnew(i, :) = v(i, :) + h / 2.d0 * (a1 + a2)
! periodic bd
if (rnew(i, 1) > L) then
rnew(i, 1) = rnew(i, 1) - L
elseif (rnew(i, 1) < 0.d0) then
rnew(i, 1) = rnew(i, 1) + L
end if
if (rnew(i, 2) > L) then
rnew(i, 2) = rnew(i, 2) - L
elseif (rnew(i, 2) < 0.d0) then
rnew(i, 2) = rnew(i, 2) + L
end if
end do
r = rnew
v = vnew
end subroutine verlet
subroutine predcorr(NP, L, r, v, h, rbef, tol)
implicit none
integer :: NP, i, j, k
double precision :: L, r(NP, 2), v(NP, 2), rnew(NP, 2), vnew(NP, 2)
double precision :: a1(2), a2(2), d, dx, dy, r1(2), r2(2)
double precision :: h
double precision :: rbef(NP, 2), rp(2), rc(2), vc(2), diff, tol
do i = 1, NP
! predictor
rp = rbef(i, :) + 2.d0 * v(i, :) * h
r1 = r(i, :)
! compute a(t)
a1 = 0.d0
do j = 1, NP
if (i.ne.j) then
r2 = r(j, :)
d = dsqrt((r1(1) - r2(1))**2 + (r1(2) - r2(2))**2)
if (d.gt.(L/2.d0)) then
! find nearest image
dx = r2(1) - r1(1)
dy = r2(2) - r1(2)
if (dx.gt.(L/2.d0)) then; dx = dx - L; end if
if (dy.gt.(L/2.d0)) then; dy = dy - L; end if
if (dx.lt.(-L/2.d0)) then; dx = dx + L; end if
if (dy.lt.(-L/2.d0)) then; dy = dy + L; end if
d = dsqrt(dx ** 2 + dy ** 2)
a1 = a1 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(/ -dx, -dy /) / d
else
a1 = a1 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(r1 - r2) / d
end if
end if
end do
! calculate a^p
k = 0
do
k = k + 1
! compute a(t+h)
a2 = 0.d0
r1 = rp
do j = 1, NP
if (i.ne.j) then
r2 = r(j, :)
d = dsqrt((r1(1) - r2(1))**2 + (r1(2) - r2(2))**2)
if (d.gt.(L/2.d0)) then
! find nearest image
dx = r2(1) - r1(1)
dy = r2(2) - r1(2)
if (dx.gt.(L/2.d0)) then; dx = dx - L; end if
if (dy.gt.(L/2.d0)) then; dy = dy - L; end if
if (dx.lt.(-L/2.d0)) then; dx = dx + L; end if
if (dy.lt.(-L/2.d0)) then; dy = dy + L; end if
d = dsqrt(dx ** 2 + dy ** 2)
a2 = a2 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(/ -dx, -dy /) / d
else
a2 = a2 + (24.d0 / d) * (2.d0 * (1 / d)**12 - (1 / d)**6) * &
(r1 - r2) / d
end if
end if
end do
! corrector
vc = v(i, :) + .5d0 * h * (a1 + a2)
rc = r(i, :) + .5d0 * h * (v(i, :) + vc)
! compute error
diff = dsqrt( (rc(1) - rp(1))**2 + (rc(2) - rp(2))**2 )
! output diff
! open(66, file='data/diff.dat', access='append', status='unknown')
! write(66, '(2e40.30)') dble(k), diff
! close(66)
! print *, k, diff
if ((diff.le.tol).or.(k.ge.8)) then
exit
else
rp = rc
end if
end do
rnew(i, :) = rc
vnew(i, :) = vc
! periodic bd
if (rnew(i, 1) > L) then
rnew(i, 1) = rnew(i, 1) - L
elseif (rnew(i, 1) < 0.d0) then
rnew(i, 1) = rnew(i, 1) + L
end if
if (rnew(i, 2) > L) then
rnew(i, 2) = rnew(i, 2) - L
elseif (rnew(i, 2) < 0.d0) then
rnew(i, 2) = rnew(i, 2) + L
end if
end do
rbef = r
r = rnew
v = vnew
end subroutine predcorr
subroutine pstatus(n, i, time, T, P)
implicit none
integer :: n, i
double precision :: time, T, P
write(*, '(1a, 1f6.2, 1a5, 1a5, 1f4.1, 1a, 1a, 1a5, 1f6.2, 1a5, 1f8.2)') &
't:', time, '', 'left:', dble(n-i)/dble(n)*100.d0, '%', &
'', 'T=', T, 'P=', P
end subroutine pstatus
```