2 Two Body Problem
2.1 Equations of Motion for the Two body problem
Let \(\vec{R}_1\) and \(\vec{R}_2\) denote the position vectors of masses \(m_1\) and \(m_2\) in an inertial reference frame. According to Newton’s Second Law and the law of gravitation:
\[ m_1\ddot{\vec{R}}_1=-G\frac{m_1 m_2}{r^3} \vec{r} \tag{2.1}\]
\[ m_2\ddot{\vec{R}}_2=G\frac{m_1 m_2}{r^3} \vec{r} \tag{2.2}\]
where \(\vec{r}= \vec{R}_2-\vec{R}_1\) the relative position vector between the two masses.
2.1.1 Derivation of the relative equation of motion
Substituting Equation 2.1 and Equation 2.2 to \(\vec{r}= \vec{R}_2-\vec{R}_1\):
\(\ddot{\vec{r}}= -G\frac{m_1 m_2}{r^3}\vec{r}\left(\frac{1}{m_2}+\frac{1}{m_1}\right)= -G \frac{m_1+m_2}{r^3} \vec{r}\)
Let the gravitational 𝜇 parameter be defined as:
\[ \mu= G(m_1+m_2) \]
Hence the final equation that we use turns out to be:
\[ \boxed{\ddot{\vec{r}}=-\frac{\mu}{r^3}\vec{r}} \tag{2.3}\]
2.1.2 Motion of Center of Mass
The center of mass vector is: \(\vec{R}_G=\frac{m_1\vec{R}_1+m_2\vec{R}_2}{m_1+m_2}\)
Using Equation 2.1 and Equation 2.2 :
\[ \ddot{\vec{R}}_G= \frac{m_1\ddot{\vec{R}}_1+m_2\ddot{\vec{R}}_2}{m_1+m_2} \tag{2.4}\]
\[ \ddot{\vec{R}}_G=\frac{-G\frac{m_1m_2}{r^3}\vec{r}+G\frac{m_1m_2}{r^3}\vec{r}}{m_1+m_2}=0 \]
Hence proved that the acceleration of center of mass is zero.
2.2 Angular momentum in two body problem
The angular momentum of body \(m_2\) relative to \(m_1\) is moment of \(m_2\)’s relative linear momentum \(m_2 \dot{\vec{r}}\):
\[ \vec{H}_{2/1}= \vec{r} \times m_2\dot{\vec{r}} \]
where \(\dot{\vec{r}}\) is the velocity of \(m_2\) relative to \(m_1\). We divide this equation of the mass term and get the specific relative angular momentum:
\[ \vec{h}= \vec{r}\times\dot{\vec{r}} \]
On calculating the time derivative:
\[ \frac{d\vec{h}}{dt}=\underbrace{\dot{\vec{r}}\times\dot{\vec{r}}}_{0}+ \vec{r}\times \ddot{\vec{r}} \]
From the Equation 2.3, we see that \(\ddot{\vec{r}} \parallel \vec{r}\) . Hence \(\frac{d\vec{h}}{dt}=0 \Rightarrow \vec{h}\) is conserved.
2.2.1 Eccentricity of orbit from Laplace-Runge-Lenz vector
Differentiating \(\dot {\vec r} \times \vec h\):
\[ \frac{d}{dt}(\dot{\vec r}\times\vec h)= \ddot{\vec r}\times\vec h + \dot{\vec r}\times\underbrace{\dot{\vec h}}_{0}= -\frac{\mu}{r^{3}}\vec r\times(\vec r\times\dot{\vec r}) \tag{2.5}\]
Using the triple product identity \(\vec{a}\times(\vec{b}\times\vec{c})=(\vec{a}\cdot\vec{c})\vec b-(\vec a \cdot \vec b)\vec c\) :
\[ \vec r\times(\vec r\times\dot{\vec r})= (\vec r\cdot\dot{\vec r})\vec r - r^{2}\dot{\vec r} \]
And using the identity \(\vec r \cdot \dot{\vec r}=r\dot r\), we get:
\[ \vec r\times(\vec r\times\dot{\vec r})= (r\dot r)\vec r - r^{2}\dot{\vec r} \tag{2.6}\]
We know that:\[\vec r \cdot\vec r=r^2\]
Then:\[\frac{d}{dt}(\vec r\cdot \vec r)=2r\frac{dr}{dt}=2r\dot r \]
But,\[\frac{d}{dt}(\vec r \cdot \vec r)=\vec r\cdot \frac{d\vec r}{dt}+\frac{d\vec r}{dt}\cdot \vec r=2\vec r\cdot \frac{d\vec r}{dt}=2\vec r\cdot \dot{\vec r}\]
Hence:\[ \boxed{\vec r\cdot\dot{\vec r}=r\dot r} \]
Inserting Equation 2.6 into Equation 2.5 :
\[ \frac{d}{dt}(\dot{\vec r}\times \vec h)=- \frac{\mu}{r^3}\left[(r \dot{r})\vec r - r^2 \dot{\vec r}\right]=-\mu \left[\frac{\dot r \vec r-r\dot{\vec{r}}}{r^2}\right] \tag{2.7}\]
But ,
\[ \frac{d}{dt}\left(\frac{\vec r}{r}\right)= \frac{r\dot{\vec r}-\dot r\vec r}{r^2}=-\frac{\dot r\vec r-r\dot{\vec{r}}}{r^2} \]
substituting the above into Equation 2.7;
\[ \frac{d}{dt}\left(\dot{\vec r}\times \vec h\right)= \frac{d}{dt}\left(\mu\frac{\vec r}{r}\right) \]
Which on integration, gives this solution:
\[ \frac{1}{\mu}(\dot{\vec{r}}\times \vec h)-\frac{\vec r}{r}=\vec e \tag{2.8}\]
where \(\vec e\) is the constant of integration and called as the Laplace-Runge-Lenz Vector. The significance of this vector is that its magnitude \(|\vec e|\) gives the eccentricity \(e\) and it faces in the direction of the periapsis of the orbit.
2.3 Orbit Equation (Trajectory Under Gravity)
Equation 2.8 is the vector equation that represents the orbit of one of the bodies wrt the other in the two body problem. In order to obtain the scalar form, we take a dot product with \(\vec r\):
\[ \frac{1}{\mu}(\dot{\vec{r}}\times \vec h)\cdot \vec r-\frac{\vec r\cdot \vec r}{r}=\vec e\cdot \vec r \]
Using the identity \(\vec a \cdot (\vec b \times \vec c)=(\vec a \times \vec b)\cdot \vec c\);
\[ \frac{1}{\mu}\underbrace{(\vec r \times \dot{\vec r})}_{\vec h}\cdot \vec h- \frac{\vec r\cdot\vec r}{r}=\vec e\cdot\vec r \]
\[ \frac{h^2}{\mu}-r=re\cos{\theta} \text{ where } \theta \text{ is the true anomaly angle } \]

Hence the final equation of orbit will turn out to be as follows:
\[ \boxed{r=\frac{\frac{h^2}{\mu}}{1+e\cos{\theta}}} \tag{2.9}\]
The above equation is similar to the parametric equation of a conic of eccentricity \(e\) : \[r=\frac{a(1-e^2)}{1+e\cos{\theta}}\]
2.4 Two body problem simulation
Below is a simulation of the two body problem with an example: a binary star system of two Suns whose masses are equal.
Click to view the code
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # noqa: F401 (needed for 3-D plots)
from matplotlib.animation import FuncAnimation, FFMpegWriter#, PillowWriter # (optional, for GIFs)
import os
# -------------------- physical constants --------------------
= 6.67430e-11 # SI [m^3 kg^-1 s^-2]
G
# -------------------- helper functions ----------------------
def orbital_velocity(mu, r, a=None, e=0.0):
"""
Magnitude of the relative velocity for the chosen orbit.
mu = G*(m1+m2)
r = current separation
a = semi-major axis (None -> circular orbit with radius r)
e = eccentricity
Returns scalar speed v.
"""
if a is None: # circular orbit
= r
a return np.sqrt(mu * (2.0/r - 1.0/a))
def set_initial_conditions(m1, m2, r_periapsis, e=0.0, inclination=0.0,omega=0.0, Omega=0.0, random_phase=False):
"""
Build the 12-component state vector
[r1, v1, r2, v2] consistent with the desired relative orbit.
All angles in radians.
Returns y0 = [x1,y,z1, vx1,vy1,vz1, x2,y2,z2, vx2,vy2,vz2]
"""
= G*(m1 + m2)
mu
# Relative orbit: choose the periapsis along the x-axis
= np.array([r_periapsis, 0.0, 0.0])
r_rel = orbital_velocity(mu, r_periapsis,a=r_periapsis/(1.0 - e), e=e)
v_rel_mag
# Rotate by argument of periapsis (ω) in orbital plane
= np.cos(omega), np.sin(omega)
cosw, sinw = np.array([[cosw, -sinw, 0],[sinw, cosw, 0],[0, 0, 1]])
rot_z = rot_z @ r_rel
r_rel = rot_z @ np.array([0.0, v_rel_mag, 0.0])
v_rel
# Inclination & line-of-nodes rotation
= np.cos(inclination), np.sin(inclination)
cosi, sini = np.cos(Omega), np.sin(Omega)
cosO, sinO
= np.array([[1, 0, 0],
rot_inc 0, cosi, -sini],
[0, sini, cosi]])
[= np.array([[cosO, -sinO, 0],
rot_O 0],
[sinO, cosO, 0, 0, 1]])
[= rot_O @ rot_inc @ r_rel
r_rel = rot_O @ rot_inc @ v_rel
v_rel
# Centre of mass at origin and zero total momentum
= m1 + m2
M = -(m2/M) * r_rel
r1 = (m1/M) * r_rel
r2 = -(m2/M) * v_rel
v1 = (m1/M) * v_rel
v2
= np.concatenate((r1, v1, r2, v2))
y0 return y0
# -------------------- integrator ----------------------------
def rhs(t, y, m1, m2):
"""
Derivatives for the 12-D state vector y.
Uses simple Newtonian gravity.
"""
= y[:3]
r1 = y[6:9]
r2 = r2 - r1
dr = np.linalg.norm(dr)**3
dist3 = G * m2 * dr / dist3
a1 = -G * m1 * dr / dist3
a2 = np.empty_like(y)
dydt 3] = y[3:6] # v1
dydt[:3:6] = a1
dydt[6:9] = y[9:12] # v2
dydt[9:12] = a2
dydt[return dydt
def rk4_step(f, t, y, h, *args):
= f(t, y, *args)
k1 = f(t + 0.5*h, y + 0.5*h*k1, *args)
k2 = f(t + 0.5*h, y + 0.5*h*k2, *args)
k3 = f(t + h, y + h*k3, *args)
k4 return y + (h/6.0)*(k1 + 2*k2 + 2*k3 + k4)
def simulate_two_body_orbit(m1, m2,
r_periapsis,=0.0,
e=0.0,
inclination=0.0,
Omega=0.0,
omega=None,
t_end=None,
dt=2000):
frames"""
Main user-facing routine.
m1, m2 in kg
r_periapsis in m
e, inclination, Omega, omega in radians
t_end : total integration time (s) (None -> 2 orbits)
dt : step size (s) (None -> adaptive)
frames: number of output samples
Returns dict with numpy arrays: t, r1, r2
"""
= set_initial_conditions(m1, m2, r_periapsis, e,
y0
inclination, omega, Omega)
# Estimate period for circular orbit (Kepler)
= r_periapsis/(1.0 - e)
a = 2*np.pi*np.sqrt(a**3 / (G*(m1 + m2)))
P
if t_end is None:
= 2*P
t_end if dt is None:
= P/5000.0
dt
= 0.0
t = y0.copy()
y = [t], [y[:3]], [y[6:9]]
times, traj1, traj2
= int(np.ceil(t_end/dt))
steps for _ in range(steps):
if t + dt > t_end:
= t_end - t
dt = rk4_step(rhs, t, y, dt, m1, m2)
y += dt
t
times.append(t)3])
traj1.append(y[:6:9])
traj2.append(y[
return dict(t=np.array(times),
=np.array(traj1),
r1=np.array(traj2))
r2
# -------------------- main program ---------------------------
# -----------------------------------------------------------
# Example: equal-mass binary (two Sun-like stars)
# -----------------------------------------------------------
if __name__ == "__main__":
# 1) Choose the system ----------------------------------------------------
= 1.0e30 # 0.5 M☉ each (feel free to tweak)
m1 = 1.0e30
m2 = 1.0e11 # semi-major axis = 0.67 AU → period ≈ 200 days
a = 0.4 # eccentric orbit just to make it interesting
ecc = a * (1 - ecc)
r_peri
= 20.0 * np.pi/180 # 20° inclination
inc = 0.0
Omega = 0.0
omega
# integrate 2 full periods so we see several loops
= G*(m1 + m2)
mu = 2*np.pi * np.sqrt(a**3 / mu) # time period
P = 2*P
t_end = simulate_two_body_orbit(m1, m2,
data =r_peri,
r_periapsis=ecc,
e=inc,
inclination=Omega,
Omega=omega,
omega=t_end,
t_end=600)
frames
= data['t'] # time array
t = data['r1'] # position of body 1
r1 = data['r2'] # position of body 2
r2 = r2 - r1 # relative position vector
r_rel
# 2) 3-D figure (unchanged) ----------------------------------------------
= plt.figure(figsize=(7, 6))
fig = fig.add_subplot(111, projection='3d')
ax
= 1.05 * np.abs(r1).max()
lim -lim, lim)
ax.set_xlim(-lim, lim)
ax.set_ylim(-lim, lim)
ax.set_zlim('x [m]')
ax.set_xlabel('y [m]')
ax.set_ylabel('z [m]')
ax.set_zlabel(r'Equal-mass binary ($m_1 = m_2 = 1\times10^{30}\,$kg)')
ax.set_title(
0], [0], [0], 'k+', markersize=8)
ax.plot([
= ax.plot([], [], [], 'o', color='tab:red', markersize=7, label='Star 1')
body1, = ax.plot([], [], [], 'o', color='tab:green', markersize=7, label='Star 2')
body2, = ax.plot([], [], [], '-', color='tab:red', alpha=0.4, lw=1)
trail1, = ax.plot([], [], [], '-', color='tab:green', alpha=0.4, lw=1)
trail2,
ax.legend()
= 300
max_trail
# 3) Animation callbacks (same as before) ---------------------------------
def init():
body1.set_data([], [])
body1.set_3d_properties([])
body2.set_data([], [])
body2.set_3d_properties([])
trail1.set_data([], [])
trail1.set_3d_properties([])
trail2.set_data([], [])
trail2.set_3d_properties([])return (body1, body2, trail1, trail2)
def update(frame):
0]], [r1[frame, 1]])
body1.set_data([r1[frame, 2]])
body1.set_3d_properties([r1[frame, 0]], [r2[frame, 1]])
body2.set_data([r2[frame, 2]])
body2.set_3d_properties([r2[frame,
= 0
start 0], r1[start:frame, 1])
trail1.set_data(r1[start:frame, 2])
trail1.set_3d_properties(r1[start:frame, 0], r2[start:frame, 1])
trail2.set_data(r2[start:frame, 2])
trail2.set_3d_properties(r2[start:frame,
return (body1, body2, trail1, trail2)
# -----------------------------------------------------------
# Optional 3-D GIF of the relative orbit ------------------
# (body 2 as seen from body 1) -----------------------------
# -----------------------------------------------------------
= plt.figure(figsize=(6,5))
fig3 = fig3.add_subplot(111, projection='3d')
ax3 = 1.05*np.abs(r_rel).max()
lim -lim, lim)
ax3.set_xlim(-lim, lim)
ax3.set_ylim(-lim, lim)
ax3.set_zlim('x [m]')
ax3.set_xlabel('y [m]')
ax3.set_ylabel('z [m]')
ax3.set_zlabel('Body 2 as seen from Body 1')
ax3.set_title(
# body-1 (fixed)
= ax3.plot([0], [0], [0], 'o', color='gold', markersize=7, label='body-1')
body1_rel, = ax3.plot([], [], [], 'o', color='tab:orange', markersize=7, label='body-2')
rel_pt, = ax3.plot([], [], [], '-', color='tab:orange', lw=1, alpha=0.5)
rel_trail,
ax3.legend()
def init_rel():
rel_pt.set_data([], [])
rel_pt.set_3d_properties([])
rel_trail.set_data([], [])
rel_trail.set_3d_properties([])return (body1_rel,rel_pt, rel_trail)
def update_rel(frame):
0]], [r_rel[frame, 1]])
rel_pt.set_data([r_rel[frame, 2]])
rel_pt.set_3d_properties([r_rel[frame, = 0
start 0], r_rel[start:frame, 1])
rel_trail.set_data(r_rel[start:frame, 2])
rel_trail.set_3d_properties(r_rel[start:frame, return (body1_rel, rel_pt, rel_trail)
# 4) Build 3-D video file in inertial frame --------------------------------------------------------
= FuncAnimation(fig, update,
ani =len(t),
frames=init,
init_func=True,
blit=1000/ 240)
interval
= "binary_3d.mp4"
outfile =FFMpegWriter(fps=240))
ani.save(outfile, writerprint(f"Saved equal-mass binary animation → {os.path.abspath(outfile)}")
# 5) Build 3-D video file in relative frame --------------------------------------------------------
= FuncAnimation(fig3, update_rel,
ani_rel =len(t),
frames=init_rel,
init_func=True,
blit=1000/ 240)
interval= "binary_rel_3d.mp4"
outfile_rel =FFMpegWriter(fps=240))
ani_rel.save(outfile_rel, writerprint(f"Saved relative orbit animation → {os.path.abspath(outfile_rel)}")
The output will be a video that shows the following:
2.5 The Energy Law
By taking the cross product of Equation 2.3 with the specific relative angular momentum per unit mass h, we were led to the vector equation Equation 2.8, from which we obtained the orbit formula. Now we will see what we will get when we take dot product of Equation 2.3 with the relative velocity \(\dot{\vec r}\).
\[ \ddot{\vec{r}}.\dot{\vec{r}}=-\mu\frac{\vec r.\dot{\vec{r}}}{r^3} \]
Now the left side:
\[ \dot{\vec r}\cdot\ddot{\vec r} = \frac{d}{dt}\!\left(\tfrac12\dot{\vec r}\cdot\dot{\vec r}\right) = \frac{d}{dt}\!\left(\tfrac12 v^2\right), \]
where \(v=\|\dot{\vec r}\|\) is the relative speed.
Right side:
\[ \dot{\vec r}\cdot\left(-\mu\frac{\vec r}{r^3}\right) = -\mu\,\frac{\vec r\cdot\dot{\vec r}}{r^3}. \]
But note
\[ \frac{d}{dt}\!\left(\frac{1}{r}\right) = -\frac{\vec r\cdot\dot{\vec r}}{r^3}, \]
so the right side equals \(\mu\,\dfrac{d}{dt}(1/r)\) with a minus sign handled:
Putting these together:
\[ \frac{d}{dt}\!\left(\tfrac12 v^2\right) = \mu\,\frac{d}{dt}\!\left(\frac{1}{r}\right). \]
Rearrange to get a time derivative of a single quantity:
\[ \frac{d}{dt}\!\left(\tfrac12 v^2 - \frac{\mu}{r}\right) = 0. \]
Therefore the quantity in parentheses is constant in time. Define the specific mechanical energy (energy per unit reduced mass, equivalently per unit mass in the relative problem)
\[ \frac{v^2}{2} - \frac{\mu}{r} =\; \varepsilon\quad\text{(a constant)} \]
This is called the vis-viva equation.
2.5.1 Specific mechanical energy
At the periapsis \(\vec r_p \perp \vec v_p\). Also,
\[ r_p=\frac{h^2}{\mu}\frac{1}{1+e} \]
And,
\[ h_p=|\vec r \times \vec v|_p=r_pv_p \]
\[ v_p=\frac{h_p}{r_p} \]
Now,
\[ \varepsilon=\varepsilon_p= \frac{v_p^2}{2} - \frac{\mu}{r_p} \]
Hence,
\[ \varepsilon= \frac{h_p^2}{2r_p^2}-\frac{\mu}{r_p} \]
Substituting \(r_p\) in the above equation will give
\[ \varepsilon=-\frac{1}{2}\frac{\mu^2}{h^2}(1-e^2) \tag{2.10}\]