ODE Lab II: Central Potential Problem#
In this lab, we will implement two numerical integration methods—Runge-Kutta 4th order (RK4) and Leapfrog/Verlet—to study a 2D central potential problem. This problem models a body in an inverse-square law force field, such as planetary motion under Newtonian gravity.
The equation governing motion in a central potential is:
This describes how a particle moves under a gravitational or electrostatic potential following an inverse-square law.
In Cartesian coordinates, this system is rewritten as:
where:
\((x, y)\) are the position coordinates,
\((u, v)\) are the velocity components,
The acceleration components are computed using the inverse-square law.
We will solve this system numerically using RK4 and Leapfrog/Verlet methods.
import numpy as np
from matplotlib import pyplot as plt
Fourth-Order Runge-Kutta Method (RK4)#
The RK4 method estimates the solution using four intermediate slopes:
The update step is:
This method is fourth-order accurate, meaning it introduces an error on the order of \(\mathcal{O}(\Delta t^4)\) per step.
# We improve the implementation from last lecture slightly
def RK4(f, x0, t0, dt, n):
T = [t0]
X = [x0]
for i in range(n):
k1 = dt * np.array(f(X[-1] ))
...
T.append(T[-1] + dt)
X.append(...)
return np.array(T), np.array(X)
Defining the Acceleration Function#
We define a function that computes the acceleration based on the inverse-square law:
def va(rv):
x, y, u, v = rv
...
return np.array([u, v, ...])
Now we apply RK4 to solve the system:
T, RV = RK4(va, np.array([1, 0, 0, 0.75]), 0, 0.1, 1000)
X_RK4 = RV[:,0]
Y_RK4 = RV[:,1]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[4], line 1
----> 1 T, RV = RK4(va, np.array([1, 0, 0, 0.75]), 0, 0.1, 1000)
3 X_RK4 = RV[:,0]
4 Y_RK4 = RV[:,1]
Cell In[2], line 8, in RK4(f, x0, t0, dt, n)
5 X = [x0]
7 for i in range(n):
----> 8 k1 = dt * np.array(f(X[-1] ))
9 ...
11 T.append(T[-1] + dt)
TypeError: unsupported operand type(s) for *: 'float' and 'ellipsis'
plt.scatter([0],[0], color='k')
plt.plot(X_RK4, Y_RK4)
plt.xlim(-1.2,1.2)
plt.ylim(-1.2,1.2)
plt.gca().set_aspect('equal')
Leapfrog/Verlet Method#
Leapfrog integrates the equations in a staggered manner:
# Leapfrog/Verlet Method
def leapfrog(a, r0, v0, t0, dt, n):
T = [t0]
X = [r0[0]]
Y = [r0[1]]
U = [v0[0]]
V = [v0[1]]
for _ in range(n):
...
return np.array(T), np.array([X, Y]).T, np.array([U, V]).T
With leapfrog()
, the “force” term is easier to implement:
def a(x, y):
...
Running the leapfrog integration:
T, R, V = leapfrog(a, (1, 0), (0, 0.75), 0, 0.1, 1000)
X_lf = R[:,0]
Y_lf = R[:,1]
plt.scatter([0],[0], color='k')
plt.plot(X_lf, Y_lf)
plt.xlim(-1.2,1.2)
plt.ylim(-1.2,1.2)
plt.gca().set_aspect('equal')
Comparing RK4 and Leapfrog#
Now integrate this up to 2,000 dynamic time and compare between RK4 and leapfrog.
T, RV = RK4(va, np.array([1, 0, 0, 0.75]), 0, 0.1, 20_000)
X_RK4 = RV[:,0]
Y_RK4 = RV[:,1]
T, R, V = leapfrog(a, (1, 0), (0, 0.75), 0, 0.1, 20_000)
X_lf = R[:,0]
Y_lf = R[:,1]
plt.scatter([0],[0], color='k')
plt.plot(X_lf, Y_lf)
plt.plot(X_RK4, Y_RK4)
plt.xlim(-1.2,1.2)
plt.ylim(-1.2,1.2)
plt.gca().set_aspect('equal')
Energy Computation and Conservation Analysis#
To analyze the stability of our integrators, we compute the total energy:
def energy(R, V):
...
E_rk4 = energy(RV[:,:2], RV[:,2:])
E_lf = energy(R, V)
plt.plot(T, E_rk4, label='RK4 Energy', linestyle='--')
plt.plot(T, E_lf, label='Leapfrog Energy')
plt.xlabel('Time')
plt.ylabel('Total Energy')
plt.ylim(-10,10)
plt.legend()
Conclusion#
RK4 provides high accuracy but does not conserve energy perfectly.
Leapfrog maintains energy conservation better, making it suitable for long-term orbital simulations.