codex/past-exam-of-the-mathematical-methods-ii-course-of-the-university-of-oxford/2017-resit-string-boundary-modes.py

past-exam-of-the-mathematical-methods-ii-course-of-the-university-of-oxford/2017-resit-string-boundary-modes.py
"""Original fixed/free string eigenfunctions. Python 3.14, NumPy, Matplotlib."""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.rcParams.update({'font.size':11,'figure.facecolor':'white','savefig.facecolor':'white'})
fig,axs=plt.subplots(1,3,figsize=(10.8,5.4),layout='constrained');x=np.linspace(0,1,1201)
for col,title in enumerate(['Fixed–fixed','Fixed–free','Free–free']):
 ax=axs[col]
 for j in range(3):
  n=j+1 if col!=1 else j;k=n*np.pi if col!=1 else (n+.5)*np.pi;y=np.sin(k*x) if col<2 else np.cos(k*x);offset=3.5-1.5*j
  ax.axhline(offset,color='.8',lw=.8);ax.plot(x,offset+.48*y,lw=2,label=f'n = {n}');ax.text(.04,offset+.58,f'n = {n}',fontsize=10)
  if col==0:
   nodes=np.arange(n+1)/n;ax.plot(nodes,np.full(len(nodes),offset),'o',color='black',ms=3)
  elif col==1:
   nodes=np.arange(n+1)*np.pi/k;ax.plot(nodes,np.full(len(nodes),offset),'o',color='black',ms=3)
 if col==2:
  ax.axhline(-1,color='tab:purple',lw=2);ax.text(.04,-.7,'n = 0: uniform zero mode',fontsize=9);ax.text(.04,-1.25,'translation, not oscillation',fontsize=9)
 else:ax.text(.02,-.7,'Vertical offsets separate modes.',fontsize=9)
 ax.set(xlim=(-.02,1.02),ylim=(-1.5,4.4),xlabel=r'position $x/L$',title=title,yticks=[]);ax.axvline(0,color='.5',lw=1);ax.axvline(1,color='.5',lw=1)
axs[0].set_ylabel('displacement profiles, arbitrary common scale')
fig.savefig(Path.cwd()/Path(__file__).with_suffix('.png').name,dpi=120,facecolor='white',transparent=False);plt.close(fig)