codexpast-exam-of-the-physics-1-paper-of-the-university-of-oxford2013-resit-double-ball-bounce.py
past-exam-of-the-physics-1-paper-of-the-university-of-oxford/2013-resit-double-ball-bounce.py
"""Original CP1 collision illustration. Python 3.14; numpy 2.3.5; matplotlib 3.10.7.
Writes only the matching opaque PNG in the current working directory.
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
fig,axs=plt.subplots(1,2,figsize=(10,3.8),layout='constrained',facecolor='white')
a=axs[0]
for x,title,v1,v2 in [(0,'Falling',-1,-1),(2,'Floor rebound',-1,1),(4,'After mutual impact',2,0)]:
a.plot([x-.65,x+.65],[0,0],color='black',lw=1.5)
a.add_patch(Circle((x,1.55),.21,facecolor='#e29538',edgecolor='black'))
a.add_patch(Circle((x,.48),.32,facecolor='#5b9bd5',edgecolor='black'))
for y,v in [(1.55,v1),(.48,v2)]:
if v:a.annotate('',xy=(x+.43,y+.34*v),xytext=(x+.43,y),arrowprops=dict(arrowstyle='->',color='#383838',lw=1.6))
a.text(x,-.18,title,ha='center',va='top',fontsize=9)
a.text(x,2.35,r'$(-u,-u)$' if x==0 else (r'$(-u,+u)$' if x==2 else r'$(2u,0)$'),ha='center')
a.set(xlim=(-.8,4.8),ylim=(-.55,2.8),aspect='equal');a.axis('off');a.set_title(r'Optimal mass ratio $m_1/m_2=1/3$')
r=np.linspace(0,3,601);f=r*(3-r)**2/(1+r)**3
axs[1].plot(r,f,lw=2,color='#317496');axs[1].plot([1/3],[1],'o',color='#bd6022');axs[1].annotate('Complete transfer',xy=(1/3,1),xytext=(.7,.9),arrowprops=dict(arrowstyle='->'))
axs[1].set(xlabel=r'Mass ratio $r=m_1/m_2$',ylabel='Upper-ball energy fraction',xlim=(0,3),ylim=(0,1.08));axs[1].grid(alpha=.25)
fig.savefig('2013-resit-double-ball-bounce.png',dpi=150,facecolor='white',metadata={'Software':'Matplotlib'})