codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2017iiipaper-320-orbit-volume.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2017/iii/paper-320-orbit-volume.py
"""Original schematic confocal orbit-volume sketch; not a computed trajectory.
Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7; unchanged root dependency pins.
Rename beside paper-320.bigb to paper-320-orbit-volume.py; output goes to CWD.
"""
from pathlib import Path
import struct
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
A,B=1.,4.
def rz(lam,mu):
return np.sqrt((lam+A)*(mu+A)/(A-B)),np.sqrt(np.maximum(0,(lam+B)*(mu+B)/(B-A)))
def main():
plt.rcParams.update({'font.family':'DejaVu Sans','font.size':11,'figure.facecolor':'white','savefig.facecolor':'white'})
fig=plt.figure(figsize=(11,5.000001),dpi=100,facecolor='white')
left=fig.add_subplot(121);right=fig.add_subplot(122,projection='3d')
mu=np.linspace(-B,-2,130)
r1,z1=rz(1,mu);r2,z2=rz(4,mu)
for sign in [-1,1]:
left.fill(np.r_[r1,r2[::-1]],sign*np.r_[z1,z2[::-1]],color='#9cbfe0',edgecolor='#254f7c',lw=1.3)
t=np.linspace(0,2*np.pi,300)
for lam,color in [(1,'#bd593c'),(4,'#bd593c')]:
left.plot(np.sqrt(lam+A)*np.cos(t),np.sqrt(lam+B)*np.sin(t),color=color,lw=1.5)
for m,style in [(-2,'-'),(-3,':')]:
rv=np.linspace(0,3,200);zv=np.sqrt((m+B)*(1+rv**2/abs(m+A)))
for sign in [-1,1]:left.plot(rv,sign*zv,color='#488253',ls=style,lw=1.5)
left.scatter([0,0],[np.sqrt(B-A),-np.sqrt(B-A)],c='black',s=15,zorder=4)
left.annotate('Common foci',(0,np.sqrt(B-A)),(.25,3.12),arrowprops={'arrowstyle':'-','color':'black'},fontsize=10)
left.text(2.17,.16,r'$\lambda=4$',color='#bd593c');left.text(1.22,-.25,r'$\lambda=1$',color='#bd593c')
left.text(1.98,2.60,r'$\mu=-2$',color='#488253');left.text(1.05,.65,r'$\mu=-3$',color='#488253')
left.set_xlim(-.15,3);left.set_ylim(-3.3,3.3);left.set_aspect('equal');left.set_xlabel('$R$');left.set_ylabel('$z$')
left.axhline(0,color='#888888',lw=.7);left.axvline(0,color='#888888',lw=.7)
left.set_title('Meridional section: allowed region',fontsize=12)
# Opaque surface patches over a 285-degree sweep leave a geometric cutaway.
phi=np.linspace(.12,1.7*np.pi,100)
for lam,color in [(1,'#7199bc'),(4,'#a2c4e2')]:
mm,pp=np.meshgrid(mu,phi);rr,zz=rz(lam,mm)
for sign in [-1,1]:right.plot_surface(rr*np.cos(pp),rr*np.sin(pp),sign*zz,color=color,alpha=1,linewidth=0,shade=True)
ll,pp=np.meshgrid(np.linspace(1,4,90),phi);rr,zz=rz(ll,-2)
for sign in [-1,1]:right.plot_surface(rr*np.cos(pp),rr*np.sin(pp),sign*zz,color='#7ca786',alpha=1,linewidth=0,shade=True)
for pp0 in [phi[0],phi[-1]]:
for lam in [1,4]:
rr,zz=rz(lam,mu)
for sign in [-1,1]:right.plot(rr*np.cos(pp0),rr*np.sin(pp0),sign*zz,color='#24486a',lw=1.1)
rr,zz=rz(np.linspace(1,4,100),-2)
for sign in [-1,1]:right.plot(rr*np.cos(pp0),rr*np.sin(pp0),sign*zz,color='#24486a',lw=1.1)
right.set_box_aspect((1,1,1.2));right.set_xlim(-2.6,2.6);right.set_ylim(-2.6,2.6);right.set_zlim(-2.8,2.8)
right.view_init(elev=24,azim=-57);right.set_axis_off();right.set_title('Azimuthal sweep (cutaway)',fontsize=12)
fig.subplots_adjust(left=.055,right=.99,bottom=.13,top=.9,wspace=.13)
fig.text(.5,.025,r'Confocal spheroids bound $\lambda$; hyperboloids bound $\mu$. Generic nonresonant motion samples the swept volume.',ha='center',fontsize=10)
p=Path.cwd()/(Path(__file__).stem+'.png');fig.savefig(p,dpi=100,facecolor='white',transparent=False);plt.close(fig)
assert struct.unpack('>II',p.read_bytes()[16:24])==(1100,500)
print(p)
if __name__=='__main__':main()