codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2018/iii/paper-345-plume.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2018/iii/paper-345-plume.py
"""Integrate original top-hat plume equations by quadrature; save PNG in CWD.
Only NumPy 2.3.5 / Matplotlib 3.10.7, tested with Python 3.14.4.
"""
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
alpha=.09;E=2*alpha*np.sqrt(np.pi)
t=np.linspace(0,100,100001);r=t*t
I=np.r_[0,np.cumsum((2*t[1:]*(1+t[1:]**4)**.75+2*t[:-1]*(1+t[:-1]**4)**.75)*np.diff(t)/2)]
q=np.sqrt(2*I);dx=np.full_like(t,np.sqrt(2));dz=np.zeros_like(t);ds=dx.copy()
dx[1:]=2*t[1:]/q[1:];dz[1:]=r[1:]*dx[1:];ds[1:]=np.sqrt(1+r[1:]**2)*dx[1:]
integ=lambda f:np.r_[0,np.cumsum((f[1:]+f[:-1])*np.diff(t)/2)]
x,z,s=map(integ,[dx,dz,ds]);m=np.sqrt(1+r*r)
b=E/np.sqrt(np.pi)*q/np.sqrt(m)
xinf=x[-1]+2*np.sqrt(5)/t[-1]**.5
shat=r[-1]**.75/np.sqrt(.45);zv=z[-1]-shat
fig,ax=plt.subplots(figsize=(7.2,5.4),dpi=100,facecolor='white')
visible=z<17
ax.plot(x[visible],z[visible],color='#1c6b88',lw=2.4,label='centreline')
ax.plot((x-r/m*b)[visible],(z+b/m)[visible],color='#73a9bb',lw=1.3)
ax.plot((x+r/m*b)[visible],(z-b/m)[visible],color='#73a9bb',lw=1.3,label='top-hat edge')
zz=np.linspace(zv,17,200);cone=6*alpha/5*(zz-zv)
ax.plot(xinf+cone,zz,'--',color='#a26935',lw=1.2);ax.plot(xinf-cone,zz,'--',color='#a26935',lw=1.2,label='far-field extrapolation')
ax.axvline(xinf,color='#999999',ls=':',label='vertical asymptote')
ax.scatter([0,xinf],[0,zv],s=30,color=['#1c6b88','#a26935'],zorder=5)
ax.annotate('actual source',(0,0),xytext=(.3,1),arrowprops={'arrowstyle':'->'},fontsize=10)
ax.annotate('virtual source',(xinf,zv),xytext=(1.1,-1.5),arrowprops={'arrowstyle':'->'},fontsize=10)
ax.set(xlim=(-.3,8),ylim=(-2.3,16),xlabel='$x/\\ell_J$',ylabel='$z/\\ell_J$',title='Horizontal forced plume ($\\alpha=0.09$)')
ax.legend(loc='upper left',fontsize=9,frameon=False);ax.spines[['top','right']].set_visible(False)
fig.tight_layout();fig.savefig('paper-345-plume.png',facecolor='white',transparent=False)