codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2014/iii/paper-59-resonance-portrait.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2014/iii/paper-59-resonance-portrait.py
"""Original exam-solution diagram; Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
Writes only the same-basename PNG in the current working directory.
Uses MPLCONFIGDIR supplied by the caller.
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from pathlib import Path
plt.rcParams.update({"font.size":10,"figure.facecolor":"white","axes.facecolor":"white"})
fig,axes=plt.subplots(1,2,figsize=(10,4.6),layout="constrained")
phi=np.linspace(0,2*np.pi,1800);pot=2*np.sin((phi-np.pi)/2)**2
for en,col in [(.08,"#2166ac"),(.65,"#2166ac"),(1.6,"#2166ac"),(2.,"#b2182b"),(2.6,"#555555")]:
 v=np.sqrt(np.maximum(0,2*(en-pot)));v[pot>en]=np.nan
 axes[0].plot(phi,v,c=col,lw=1.5);axes[0].plot(phi,-v,c=col,lw=1.5)
axes[0].scatter([0,np.pi,2*np.pi],[0,0,0],c=["#b2182b","#2166ac","#b2182b"])
axes[0].set(xticks=[0,np.pi,2*np.pi],xticklabels=["0","π","2π"],xlabel="Resonant argument φ",ylabel="Angular speed φ̇/ω₀",title="Libration (blue), separatrix (red), circulation (grey)")
axes[0].grid(alpha=.2)
z=np.linspace(-.04,.04,400);e0=.2;ratio=1/3
ee=np.sqrt(e0*e0+ratio*np.log1p(z))
axes[1].plot(1+z,ee,c="#2166ac",lw=2)
for idx,delta in [(130,15),(280,-15)]:
 axes[1].annotate("",(1+z[idx+delta],ee[idx+delta]),(1+z[idx],ee[idx]),arrowprops={"arrowstyle":"->","color":"#b2182b"})
axes[1].scatter([1],[e0],c="black",s=18)
axes[1].set(xlabel="Semimajor axis a/a(0)",ylabel="Eccentricity e",title="Same invariant curve traversed in both directions\nq/(p+q) = 1/3; e(0) = 0.2")
axes[1].grid(alpha=.2)
fig.savefig(Path(__file__).stem+".png",dpi=100,facecolor="white",transparent=False)