codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2003/ia/paper-4-rolling-and-orbit.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2003/ia/paper-4-rolling-and-orbit.py
"""Original geometry/scattering figure. Tested Python 3.14/numpy 2.3/matplotlib 3.10.
Outputs its opaque PNG basename in caller CWD and preserves supplied MPLCONFIGDIR.
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon, Arc
fig,axes=plt.subplots(1,2,figsize=(10,4.4),layout='constrained',facecolor='white')
a=axes[0];theta=np.pi/4;d=3/8
O=np.array([0.,1.]);P=np.array([0.,0.]);n=np.array([np.sin(theta),-np.cos(theta)]);G=O+d*n
phi=np.linspace(theta-np.pi,theta,250)
arc=O+np.column_stack([np.cos(phi),np.sin(phi)])
a.add_patch(Polygon(arc,closed=True,facecolor='#ddeaf3',edgecolor='#196080',lw=1.7))
a.axhline(0,color='black',lw=1.5)
a.plot([O[0],P[0]],[O[1],P[1]],'k:',lw=1.4)
a.plot([O[0],G[0]],[O[1],G[1]],color='#a72b1f',lw=2)
a.plot([P[0],G[0]],[P[1],G[1]],color='#8b6e12',lw=2)
for pt,label,offset in [(O,'O',(-.16,.07)),(G,'G',(.08,.01)),(P,'P',(-.15,-.13))]:
    a.plot(*pt,'ko',ms=4)
    a.text(pt[0]+offset[0],pt[1]+offset[1],label)
a.text(-.17,.48,'a');a.text(.18,.98,r'$d=3a/8$',color='#a72b1f');a.text(.28,.39,'b',color='#8b6e12')
start=arc[0];a.plot([start[0],start[0]+.45],[start[1],start[1]],'k:',lw=1)
a.add_patch(Arc(start,.5,.5,theta1=0,theta2=45,color='black'))
a.text(start[0]+.26,start[1]+.09,r'$\theta$')
a.set(xlim=(-1,1.15),ylim=(-.22,1.95),aspect='equal',title='Rounded-contact rolling geometry')
a.axis('off')
a=axes[1];beta=np.sqrt(2.);limit=np.arccos(.25)/beta
t=np.linspace(-limit,limit,500);r=1/np.cos(beta*t)
x=r*np.cos(t);y=r*np.sin(t)
a.plot(x,y,color='#17659a',lw=2,label=r'$r=\sec(\sqrt{2}\theta)$')
for sign in [-1,1]:
    ang=sign*np.pi/(2*beta);direction=np.array([np.cos(ang),np.sin(ang)]);normal=np.array([-np.sin(ang),np.cos(ang)])
    offset=-sign*normal/beta
    pts=offset+np.linspace(.2,4.5,150)[:,None]*direction
    a.plot(pts[:,0],pts[:,1],color='gray',ls='--',lw=1)
for j,forward in [(75,1),(400,1)]:
    a.annotate('',xy=(x[j+10],y[j+10]),xytext=(x[j-10],y[j-10]),arrowprops={'arrowstyle':'->','color':'#17659a','lw':2})
a.plot(0,0,'ko',ms=4);a.text(.05,-.40,'Force centre',ha='center',fontsize=9)
a.plot(1,0,'o',color='#a72b1f');a.text(1.1,.12,'Closest approach',fontsize=9)
a.set(xlim=(-.7,3.3),ylim=(-3.6,3.6),aspect='equal',xlabel=r'$x/r_{\min}$',ylabel=r'$y/r_{\min}$',title='Repulsive inverse-cube force')
a.legend(fontsize=9,loc='upper right')
fig.savefig('paper-4-rolling-and-orbit.png',dpi=120,facecolor='white',transparent=False)