-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy path11_molecular_dynamics.jl
211 lines (180 loc) · 5.22 KB
/
11_molecular_dynamics.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
using PyPlot
using DifferentialEquations
using Random
using LinearAlgebra
using Statistics
using PyCall
FuncAnimation = pyimport("matplotlib.animation").FuncAnimation
function kinetic_energy(v)
n = size(v,2)
E = 0.0
for i = 1:n
E += (v[1,i]^2 + v[2,i]^2)/2
end
return E
end
function potential_energy(x,L)
n = size(x,2)
E = 0.0
for i = 1:n
for j = 1:i-1
# Periodic boundary conditions:
# When an leaves the simulation box on one side, we make it re-enter
# on the other side. This means that in order to get consistent
# forces, we must use the distance between the closest pair of
# periodic images of atoms i and j.
r2 = minimum(
(x[1,i] - x[1,j] + sqrt(3)*L*s1)^2 +
(x[2,i] - x[2,j] + L*s2)^2
for s1 = (-1,0,1), s2 = (-1,0,1)
)
ir2 = inv(r2)
ir6 = ir2^3
E += ir6^2 - 2*ir6
end
end
return E
end
energy(x,v,L) = kinetic_energy(v) + potential_energy(x,L)
function initial_positions(n,L)
x = Matrix{Float64}(undef, 2,2n^2)
o = (L/2-n/2) .* (sqrt(3),1)
for i = 1:n
for j = 1:n
x[:,2n*(i-1) + 2j-1] .= (sqrt(3)*(i-1 ), j-1 ) .+ o
x[:,2n*(i-1) + 2j ] .= (sqrt(3)*(i-0.5), j-0.5) .+ o
end
end
return x
end
function initial_velocities(n,E)
v = randn(2,2n^2)
v .-= mean(v,dims=2)
EE = kinetic_energy(v,)
v .*= sqrt(E/EE)
return v
end
function map_to_box!(x,L)
# Impose periodic boundary conditions:
# When an leaves the simulation box on one side, make it re-enter on the
# other side.
n = size(x,2)
for i = 1:n
if x[1,i] < 0
x[1,i] += sqrt(3)*L
elseif x[1,i] > sqrt(3)*L
x[1,i] -= sqrt(3)*L
end
if x[2,i] < 0
x[2,i] += L
elseif x[2,i] > L
x[2,i] -= L
end
end
return x
end
function plot_positions(x,L)
p, = plot(x[1,:],x[2,:], "o", ms = 10)
xlim([0, sqrt(3)*L])
ylim([0, L])
gca().set_aspect("equal","box")
return p
end
function assemble_problem(n,L,E,T)
Random.seed!(42)
x = initial_positions(n,L)
v = initial_velocities(n,E)
problem = HamiltonianProblem(
(x,v,_)->energy(x,v,L),
x,v, (0,T)
)
enforce_pbc = DiscreteCallback(
(u,t,integrator) -> true,
integrator -> begin
x = integrator.u.x[1]
map_to_box!(x,L)
end
)
return problem, enforce_pbc
end
function example()
# I could not figure out how to make the plot show up in VSCode, so you will
# have to run this function from the Julia REPL.
# You can do so using the following steps:
# - Run the Julia executable that comes with your Julia installation.
# - Type `cd("[path]")` where `[path]` to move to the directory of this file.
# - Type `include("11_molecular_dynamics.jl"); example()`
# If all goes well, a new window should appear with the animation.
# Feel free to contact me if you have issues getting this to work.
n = 3
L = 5*n
E = 2n^2 * 2.0 # Energy of the system
# Increase this parameter to go from solid to liquid to gas
dt = 0.01
dt_per_frame = 0.1
frames_per_second = 20
problem, enforce_pbc = assemble_problem(n,L,E,Inf)
integrator = init(
problem,
Midpoint(),
adaptive=false, dt = dt,
alias_u0 = true,
save_on = false,
callback=enforce_pbc
)
clf()
p = plot_positions(integrator.u.x[1],L)
FuncAnimation(
gcf(),
i->begin
step!(integrator, dt_per_frame)
p.set_data(integrator.u.x[1])
return (p,)
end,
interval = 1000/frames_per_second,
blit=true,
init_func=()->(p,)
)
end
function energy_conservation()
n = 3
L = 5*n
E = 2n^2 * 0.1
T = 10.0
dt = 0.01
method = Midpoint() # Energy drifts
# method = VerletLeapfrog() # Energy oscillates but does not drift
# dt = 0.06 # Still stable for VerletLeapfrog()
# dt = 0.07 # Exponential blow-up for VerletLeapfrog()
problem,enforce_pbc = assemble_problem(n,L,E,T)
energy_values = SavedValues(Float64, NTuple{2,Float64})
save_energies = SavingCallback(
(u,t,integrator) -> (
kinetic_energy(u.x[2]),
potential_energy(u.x[1],L)
),
energy_values
)
solve(
problem,
method,
adaptive=false, dt = dt,
alias_u0 = true,
save_on = false,
callback = CallbackSet(enforce_pbc,save_energies)
)
t = energy_values.t
Ekin = [energy_values.saveval[i][1] for i = 1:length(t)]
Epot = [energy_values.saveval[i][2] for i = 1:length(t)]
clf()
plot(t, Ekin.+Epot, label="total")
# ---------------------------------------
# Comment these lines to see the variation in the total energy more clearly
plot(t, Ekin, label="kinetic")
plot(t, Epot, label="potential")
# ---------------------------------------
legend(loc="best")
xlabel("time")
ylabel("energy")
display(gcf())
end