codex/past-exam-of-the-mathematics-course-of-the-year-2-of-the-natural-sciences-course-of-the-university-of-cambridge/2013/paper-2-2013-ib-two-mass-modes.py

past-exam-of-the-mathematics-course-of-the-year-2-of-the-natural-sciences-course-of-the-university-of-cambridge/2013/paper-2-2013-ib-two-mass-modes.py
"""Original unequal-mass mode illustration. Python 3.14, NumPy 2.3, Matplotlib 3.10.
Run in the mirrored _media directory; the opaque white PNG is written to cwd.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Circle,Rectangle,FancyArrowPatch
import numpy as np


def spring(ax,left,right):
    xx=np.linspace(left,right,121);yy=np.zeros_like(xx)
    yy[8:-8]=.07*np.sin(np.linspace(0,16*np.pi,len(xx)-16))
    ax.plot(xx,yy,color='#72859a',lw=1.4)


fig,axes=plt.subplots(1,2,figsize=(9,3.9),facecolor='white')
ratios=[(1+np.sqrt(3))/2,(1-np.sqrt(3))/2]
labels=[r'$\omega_-^2=\dfrac{k}{m_1}\dfrac{3-\sqrt{3}}{2}$',r'$\omega_+^2=\dfrac{k}{m_1}\dfrac{3+\sqrt{3}}{2}$']
for ax,ratio,title,freq in zip(axes,ratios,['Lower mode: in phase','Higher mode: opposite directions'],labels):
    for wall in [-.08,4]:ax.add_patch(Rectangle((wall,-.4),.08,.8,color='#bbc6d0'))
    points=[1.15,2.85];radii=[.19,.19*np.sqrt(2)]
    spring(ax,0,points[0]-radii[0]);spring(ax,points[0]+radii[0],points[1]-radii[1]);spring(ax,points[1]+radii[1],4)
    for j,(p,rad,amp) in enumerate(zip(points,radii,[1,ratio]),1):
        ax.add_patch(Circle((p,0),rad,facecolor='#e0ebf4',edgecolor='#2a557b',lw=1.5,zorder=3))
        ax.text(p,0,rf'$m_{j}$',ha='center',va='center',fontsize=11)
        length=.6*amp
        ax.add_patch(FancyArrowPatch((p-length/2,.57),(p+length/2,.57),arrowstyle='-|>',mutation_scale=11,shrinkA=0,shrinkB=0,lw=1.9,color='#b45131'))
        ax.text(p,.84,f'{amp:+.3f}',ha='center',fontsize=11,color='#2a557b')
    ax.text(2,-.7,freq,ha='center',fontsize=13)
    ax.set(xlim=(-.3,4.3),ylim=(-1.1,1.25),aspect='equal',title=title)
    ax.axis('off')
fig.suptitle('Two normal modes for an example with $m_2=2m_1$',fontsize=15,y=.98)
fig.text(.5,.12,'Arrows give relative horizontal displacements; all three spring constants equal k.',ha='center',fontsize=10)
fig.text(.5,.035,'Springs are drawn at equilibrium; each arrow pattern acquires one sinusoidal time amplitude.',ha='center',fontsize=9)
fig.subplots_adjust(left=.03,right=.97,top=.8,bottom=.21,wspace=.2)
fig.savefig(Path.cwd()/Path(__file__).with_suffix('.png').name,dpi=145,facecolor='white')
plt.close(fig)