codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2007iapaper-4-potential-phase.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2007/ia/paper-4-potential-phase.py
"""Cubic potential and its phase portrait. Python 3.14; root pyproject dependencies.
Write only the PNG basename to the caller's working directory. Matplotlib honours
any MPLCONFIGDIR supplied by the caller.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
def potential(x):
return 3*x*x-2*x*x*x
def main():
fig,axes=plt.subplots(1,2,figsize=(12,4.8),layout='constrained',facecolor='white')
left,right=axes
x=np.linspace(-1.1,2.4,1200)
left.plot(x,potential(x),color='#172b4d',lw=2)
left.axhline(0,color='#999999',lw=.7)
left.axhline(1,color='#bb3333',lw=.9,ls='--',label='Barrier energy 1')
left.axvline(0,color='#bbbbbb',lw=.6)
left.scatter([0,1,1.5],[0,1,0],color='#172b4d',zorder=4)
left.annotate('Minimum (0, 0)',(0,0),xytext=(-.8,-1.1),arrowprops={'arrowstyle':'->','color':'#444444'})
left.annotate('Maximum (1, 1)',(1,1),xytext=(.1,2.1),arrowprops={'arrowstyle':'->','color':'#444444'})
left.annotate('(1.5, 0)',(1.5,0),xytext=(1.6,.8))
left.set(xlim=(-1.1,2.4),ylim=(-3.5,3),xlabel='Position x',ylabel='Potential V(x)',title='Cubic potential V = 3x² − 2x³')
left.grid(alpha=.15)
left.legend(loc='lower left',frameon=False)
xs=np.linspace(-1.15,2.35,1000)
vs=np.linspace(-3,3,700)
xx,vv=np.meshgrid(xs,vs)
energy=vv**2/2+potential(xx)
levels=[-.6,0,.25,.6,1.7,2.5]
cs=right.contour(xx,vv,energy,levels=levels,colors=['#999999','#777777','#2962a3','#2962a3','#777777','#999999'],linewidths=1)
right.clabel(cs,inline=True,fmt=lambda e:f'E={e:g}',fontsize=8)
sep=np.linspace(-.5,2.35,1100)
speed=np.abs(sep-1)*np.sqrt(2*(2*sep+1))
right.plot(sep,speed,color='#bb3333',lw=1.8,label='E = 1 separatrix')
right.plot(sep,-speed,color='#bb3333',lw=1.8)
qx,qv=np.meshgrid(np.linspace(-.9,2.1,12),np.linspace(-2.6,2.6,11))
dx=qv;dv=6*qx*(qx-1)
norm=np.hypot(dx,dv)
right.quiver(qx,qv,dx/np.maximum(norm,1e-9),dv/np.maximum(norm,1e-9),angles='xy',scale_units='xy',scale=6,color='#788899',alpha=.35,width=.0025)
right.scatter([0],[0],s=40,color='#2962a3',zorder=5)
right.scatter([1],[0],s=45,marker='x',color='#bb3333',zorder=5)
right.annotate('Centre',(0,0),xytext=(-.6,.2))
right.annotate('Saddle',(1,0),xytext=(1.1,.25))
right.axhline(0,color='#bbbbbb',lw=.5)
right.set(xlim=(-1.15,2.35),ylim=(-3,3),xlabel='Position x',ylabel='Velocity v',title='Energy trajectories: E = v²/2 + V(x)')
right.legend(loc='lower left',fontsize=9,frameon=False)
right.grid(alpha=.12)
fig.savefig(Path('paper-4-potential-phase.png'),dpi=100,facecolor='white',transparent=False)
plt.close(fig)
if __name__=='__main__':
main()