This example uses rebound
to create a Poincare surface of section of the restricted circular three body problem (RC3BP). First, a series of RC3BP simulations with test particles at different semi-major axes are initialized at a fixed value of the Jacobi constant $C_J$. Then, each simulation is integrated and the state of the test particle is recorded whenever the test particle and perturber are at opposition, i.e., $\lambda - \lambda_\mathrm{p} = \pi$ where $\lambda$ and $\lambda_\mathrm{p}$ are the mean longitudes of the test particle and massive perturber, respectively. Finally, a surface of section showing the particles' periods versus mean anomalies is plotted. Numerous resonant islands are visible at period ratios corresponding to mean motion resonances between the particle and perturber.
import rebound
import numpy as np
import matplotlib.pyplot as plt
def get_sim(m_pert,n_pert,a_tp,l_pert,l_tp,e_tp,pomega_tp):
sim = rebound.Simulation()
sim.add(m=1)
P_pert = 2 * np.pi / n_pert
sim.add(m=m_pert,P=P_pert,l=l_pert)
sim.add(m=0.,a = a_tp,l=l_tp,e=e_tp,pomega=pomega_tp)
sim.move_to_com()
return sim
Calculate the synodic angle, $\psi = \lambda - \lambda_p$, at a specified time T
from a simluation, sim
.
def get_psi(T,sim):
ps = sim.particles
sim.integrate(T)
return np.mod(ps[1].l - ps[2].l ,2*np.pi)
Calculate the Jacobi constant of the test particle, $$ C_J = n_p l_z - |\pmb{v}|^2 -\frac{Gm_*}{|\pmb{r}-\pmb{r_*}|}-\frac{Gm_p}{|\pmb{r}-\pmb{r_p}|} $$ where $l_z$ is the component of the test particle's specific angular momentum aligned with perturber's orbit normal.
def get_jacobi_const(sim):
ps = sim.particles
star = ps[0]
planet = ps[1]
particle = ps[2]
rstar = np.array(star.xyz)
rplanet = np.array(planet.xyz)
r = np.array(particle.xyz)
v = np.array(particle.vxyz)
KE = 0.5 * v@v # test particle kinetic energy
mu1 = sim.G * star.m
mu2 = sim.G * planet.m
r1 = r-rstar
r2 = r-rplanet
PE = -1*mu1/np.sqrt(r1@r1) - mu2/np.sqrt(r2@r2) # test particle potential energy
lz = np.cross(r,v)[-1]
CJ = 2 * planet.n * lz - 2 * (KE + PE) # jacobi constant
return CJ
Set the parameters of the simulations
m_pert = 3e-5
n_pert = 5/4 * (1+0.05)
e_tp = 0.0
l_tp = 0
l_pert = 0
pomega_tp = 0.5 * np.pi
Given a semi-major axis a
, we solve for the eccentricity such that the Jacobi constant is equal to the user-specified value CJ
. The eccentricity solution is assumed to lie in the interval specified by e_bracket
. After finding a solution, we initialize and return a simulation with a test particle with the desired semi-major axis/eccentricity combination.
from scipy.optimize import root_scalar
def get_sim_at_fixed_CJ(a,CJ,e_bracket):
get_sim_fn = lambda e,a: get_sim(m_pert,n_pert,a,l_pert,l_tp,e,pomega_tp)
root_fn = lambda e,a: get_jacobi_const(get_sim_fn(e,a)) - CJ
root = root_scalar(root_fn,args=(a,),bracket=e_bracket)
assert root.converged, "Root-finding failed to converge for a={:.1f}, CJ={:.1f}".format(a,CJ)
return get_sim_fn(root.root,a)
def get_sos_data(sim,Npts,psi_section = np.pi):
ps = sim.particles
n_syn = ps[1].n - ps[2].n
Tsyn = 2 * np.pi / n_syn
n,e,M = np.zeros((3,Npts))
for i in range(Npts):
try:
rt=root_scalar(lambda t: get_psi(t,sim) - psi_section , bracket=[sim.t + 0.8*Tsyn,sim.t + 1.2*Tsyn])
except:
# re-compute Tsyn
n_syn = ps[1].n - ps[2].n
Tsyn = 2*np.pi/n_syn
rt=root_scalar(lambda t: get_psi(t,sim) - psi_section , bracket=[sim.t + 0.8*Tsyn,sim.t + 1.2*Tsyn])
n[i] = ps[2].n
e[i] = ps[2].e
M[i] = ps[2].M
return n,e,M
a_tp0 = 1
sim0 = get_sim(m_pert,n_pert,a_tp0,l_pert,l_tp,e_tp,pomega_tp)
CJ0 = get_jacobi_const(sim0)
Nsims = 24 # Number of simulations to plot on surface of section
Npts = 100 # Number of points to plot per simulation
da_vals = np.linspace(0,0.1,Nsims)
sims = [get_sim_at_fixed_CJ(a_tp0 + da,CJ0,[0,0.3]) for da in da_vals]
all_pts = np.array([get_sos_data(sim,Npts) for sim in sims])
/Users/rein/git/rebound/rebound/simulation.py:712: RuntimeWarning: At least 10 predictor corrector loops in IAS15 did not converge. This is typically an indication of the timestep being too large. warnings.warn(msg[1:], RuntimeWarning)
The surface of section points are plotted below, along with the values of the test particles' eccentricities over the range of period ratio displayed in the surface of section.
fig,ax = plt.subplots(1,2,sharey=True,figsize=(12,5))
min_pratio=0
max_pratio=np.inf
for n,e,M in all_pts:
n_tp = n
alpha = (n/n_pert)**(2/3)
ecross = 1-alpha
ax[0].plot(M / np.pi, n/n_pert,'.')
ax[1].plot(e, n/n_pert,'.')
# plot the orbit-crossing eccentricity for reference
# versus period ratio
pratios = np.linspace(*ax[0].get_ylim())
alpha = pratios**(2/3)
ecross=1-alpha
ax[1].plot(ecross,pratios,'k-',lw=3,label="orbit crossing")
ax[1].legend()
ax[0].set_ylabel(r"$P_\mathrm{pert}/P$")
ax[0].set_xlabel(r"$M/\pi$")
ax[1].set_xlabel(r"$e$")
# plot the location of some resonances
for a in ax:
a.axhline(3/4,ls='-',color='k',lw=2) # 1st order mmr
a.axhline(8/11,ls='-.',color='k') # 3rd order mmr
a.axhline(5/7,ls='--',color='k') # 2nd order mmr
a.axhline(7/10,ls='-.',color='k') # 3rd order mmr
a.axhline(2/3,ls='-',color='k') # first order mmr