From now on, we will only use velocity Verlet method for solving Newton’s equation of motion. We generalize the method for systems of multiple variables. These systems can have a particle in 2D or 3D, or multiple particles.
# Velocity Verlet method for high dimensional systems (2D, 3D, or multiple particles)
velocity_verlet_generic <- function(f, t0, x0, v0, t.total, dt, ...){
# f: 2nd derivative function
# t0: initial time
# x0: a vector of initial position x
# v0: a vector of initial velocity v
# t.total: total simulation time
# dt: time step size
t_all = seq(t0, t.total, by=dt)
nt_all = length(t_all)
nx = length(x0)
x_all = matrix(0, nrow = nt_all, ncol = nx)
v_all = matrix(0, nrow = nt_all, ncol = nx)
x_all[1,] = x0
v_all[1,] = v0
for (i in 1:(nt_all-1)) {
v_half = v_all[i,] + 0.5 * dt * f(t_all[i], x_all[i,],...)
x_all[i+1,] = x_all[i,] + dt * v_half
v_all[i+1,] = v_half + 0.5 * dt * f(t_all[i+1], x_all[i+1,],...)
}
return(cbind(t_all, x_all, v_all)) # the output is a matrix of t, x, v for all time steps
}
In this new example, a 2D-particle is driven by a gravitational force from the center (e.g., Earth)
\[f(r) = \frac{G}{r} \tag{3}\] \(G\) is a constant. The direction of the force is from the particle to the center. We can write the \(x\) and \(y\) components of the force, \(f_x\) and \(f_y\), as
\[\begin{equation} \begin{cases} f_x(x, y) = - \frac{Gx}{r^2} \tag{4}\\ f_y(x, y) = - \frac{Gy}{r^2} \end{cases} \end{equation}\]
# gravitational force in 2D
f_g2d <- function(t, x, G){
# x: a vector of size two: x, y positions
r2 = x[1]**2 + x[2]**2
return(-x*G/r2)
}
results_g2d = velocity_verlet_generic(f = f_g2d, t0 = 0, x0 = c(4, 0), v0 = c(0, 1), t.total = 100, dt = 0.1, G = 1)
plot(results_g2d[,1], results_g2d[,2], type = "l", col=2,
xlab="t", ylab="position", xlim=c(0,100), ylim=c(-8,8))
lines(results_g2d[,1], results_g2d[,3], col=4)
legend("topleft", inset=0.02,
legend = c("x","y"),
col=c(2,4), lty=1, cex=0.8)
plot(results_g2d[,2], results_g2d[,3], type = "l", col=1,
xlab="x", ylab="y", xlim=c(-8,8), ylim=c(-8,8))
results_g2d2 = velocity_verlet_generic(f = f_g2d, t0 = 0, x0 = c(2, 2), v0 = c(-1, 1), t.total = 1000, dt = 0.1, G = 1)
plot(results_g2d2[,1], results_g2d2[,2], type = "l", col=2,
xlab="t", ylab="position", xlim=c(0,100), ylim=c(-8,8))
lines(results_g2d2[,1], results_g2d2[,3], col=4)
legend("topleft", inset=0.02,
legend = c("x","y"),
col=c(2,4), lty=1, cex=0.8)
plot(results_g2d2[,2], results_g2d2[,3], type = "l", col=1,
xlab="x", ylab="y", xlim=c(-8,8), ylim=c(-8,8))
We then consider two particles of equal masses in 2D, a system that can be described by 4 variables.
# two particles in 2D with gravitational forces
f_2body_2d <- function(t, x, G){
# x: a vector of size six: (x1, y1, x2, y2)
r12_2 = (x[1] - x[3])**2 + (x[2] - x[4])**2
f1_x = (x[3]-x[1])/r12_2
f1_y = (x[4]-x[2])/r12_2
f2_x = (x[1]-x[3])/r12_2
f2_y = (x[2]-x[4])/r12_2
return(G*c(f1_x, f1_y, f2_x, f2_y))
}
set.seed(1) # we use random initial conditions
results_2body = velocity_verlet_generic(f = f_2body_2d, t0 = 0, x0 = runif(4, -5, 5), v0 = runif(4, -0.3, 0.3),
t.total = 1000, dt = 0.1, G = 1)
plot(results_2body[,2], results_2body[,3], type = "l", col=2,
xlab="x", ylab="y")
lines(results_2body[,4], results_2body[,5], col=4)
legend("topleft", inset=0.02,
legend = c("1","2"),
col=c(2,4), lty=1, cex=0.8)
The time trajectories contain patterns but with a constant shift, because of a non-zero total momentum. To eliminate the constant shift (consider that the system is in another reference with a constant velocity), we can shift the velocities with constant values, so that the system has zero total momentum.
# velocity is shifted to remove net changes
center_v <- function(v, ndim){
# v: a vector of velocities
# ndim: dimension of the system
v_mat = matrix(v, ncol = ndim, byrow = T)
v_means = colMeans(v_mat)
return(v - v_means) # different sizes, the shorter vector iterates
}
set.seed(1) # we use random initial conditions
v0 = runif(4, -0.3, 0.3)
results_2body = velocity_verlet_generic(f = f_2body_2d, t0 = 0, x0 = runif(4, -5, 5), v0 = center_v(v0,2),
t.total = 1000, dt = 0.1, G = 1)
plot(results_2body[,2], results_2body[,3], type = "l", col=2,
xlab="x", ylab="y")
lines(results_2body[,4], results_2body[,5], col=4)
legend("topleft", inset=0.02,
legend = c("1","2"),
col=c(2,4), lty=1, cex=0.8)
An R package gganimate allows to generate movies of time trajectories! (Commented as they are slow to execute)
{r,fig.width = 5, fig.height = 5}
library(ggplot2)
library(gganimate)
library(gifski)
library(png)
npoints = nrow(results_2body)
results_2body_dframe = data.frame(t = results_2body[,1], x = c(results_2body[,2],results_2body[,4]),
y = c(results_2body[,3],results_2body[,5]),
particle = c(rep("1", npoints), rep("2", npoints)))
p = ggplot(results_2body_dframe, aes(x = x, y = y, colour = particle)) +
geom_point(aes(colour = particle), show.legend = FALSE, size = 5,alpha = 0.7) + labs(x = "x", y = "y") +
scale_color_manual(values = c("1" = "red", "2" = "blue"))
p
anim = p + transition_time(t, range = c(0, 50)) + labs(title = "t: {frame_time}")
anim = animate(anim, fps = 20, nframes = 200, end_pause = 0, rewind = F, renderer = gifski_renderer())
anim_save("2-body.gif", animation = anim)
Now we consider three particles of equal masses in 2D, a system that can be described by 6 variables.
# three particles in 2D with gravitational forces
f_3body_2d <- function(t, x, G){
# x: a vector of size six: (x1, y1, x2, y2, x3, y3)
r12_2 = (x[1] - x[3])**2 + (x[2] - x[4])**2
r13_2 = (x[1] - x[5])**2 + (x[2] - x[6])**2
r23_2 = (x[3] - x[5])**2 + (x[4] - x[6])**2
f1_x = (x[3]-x[1])/r12_2 + (x[5]-x[1])/r13_2
f1_y = (x[4]-x[2])/r12_2 + (x[6]-x[2])/r13_2
f2_x = (x[1]-x[3])/r12_2 + (x[5]-x[3])/r23_2
f2_y = (x[2]-x[4])/r12_2 + (x[6]-x[4])/r23_2
f3_x = (x[1]-x[5])/r13_2 + (x[3]-x[5])/r23_2
f3_y = (x[2]-x[6])/r13_2 + (x[4]-x[6])/r23_2
return(G*c(f1_x, f1_y, f2_x, f2_y, f3_x, f3_y))
}
set.seed(1) # we use random initial conditions
v0 = runif(6, -0.01, 0.01)
results_3body = velocity_verlet_generic(f = f_3body_2d, t0 = 0, x0 = runif(6, -5, 5), v0 = center_v(v0, 3),
t.total = 1000, dt = 0.1, G = 1)
plot(results_3body[,2], results_3body[,3], type = "l", col="red",
xlab="x", ylab="y")
lines(results_3body[,4], results_3body[,5], col="orange")
lines(results_3body[,6], results_3body[,7], col="blue")
legend("topright", inset=0.02,
legend = c("1","2", "3"),
col=c("red","orange","blue"), lty=1, cex=0.8)
Animations too (Commented as they are slow to execute)
{r,fig.width = 5, fig.height = 5}
npoints = nrow(results_3body)
results_3body_dframe = data.frame(t = results_3body[,1], x = c(results_3body[,2],results_3body[,4], results_3body[,6]),
y = c(results_3body[,3],results_3body[,5], results_3body[,7]),
particle = c(rep("1", npoints), rep("2", npoints), rep("3", npoints)))
p = ggplot(results_3body_dframe, aes(x = x, y = y, colour = particle)) +
geom_point(aes(colour = particle), show.legend = FALSE, size = 5,alpha = 0.7) + labs(x = "x", y = "y") +
scale_color_manual(values = c("1" = "red", "2" = "orange", "3" = "blue"))
p
anim = p + transition_time(t, range = c(0, 300)) + labs(title = "t: {frame_time}")
anim = animate(anim, fps = 20, nframes = 300, end_pause = 0, rewind = F, renderer = gifski_renderer())
anim_save("3-body.gif", animation = anim)