codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2015/iii/paper-75-phase-dynamics.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2015/iii/paper-75-phase-dynamics.py
"""Adler drift and tilted-potential sketches. Python 3.14; root NumPy/Matplotlib.
Writes only the opaque PNG basename in cwd; set MPLCONFIGDIR in the caller.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
theta=np.linspace(-.3,2*np.pi+.3,700)
fig,axs=plt.subplots(2,2,figsize=(10,6),dpi=100,facecolor='white')
for j,w in enumerate((.55,1.25)):
 drift=w-np.sin(theta);potential=-w*theta-np.cos(theta)
 axs[0,j].plot(theta/np.pi,drift,color='#245786',lw=2.2)
 axs[0,j].axhline(0,color='#777777',lw=.9)
 axs[0,j].set_title(('Locked: ' if j==0 else 'Running: ')+rf'$\omega/\epsilon={w}$')
 axs[0,j].set_ylabel(r'Drift $\dot\theta/\epsilon$')
 axs[1,j].plot(theta/np.pi,potential,color='#aa683a',lw=2.2)
 axs[1,j].set_ylabel(r'Potential $V/\epsilon$');axs[1,j].set_xlabel(r'Unwrapped phase $\theta/\pi$')
 if j==0:
  a=np.arcsin(w);b=np.pi-a
  for th,color,lab in [(a,'#228855','Stable / minimum'),(b,'#aa3333','Unstable / maximum')]:
   axs[0,j].scatter(th/np.pi,0,s=32,c=color,zorder=3,label=lab)
   axs[1,j].scatter(th/np.pi,-w*th-np.cos(th),s=32,c=color,zorder=3)
  axs[0,j].legend(loc='upper left',fontsize=8)
 else:
  axs[1,j].annotate('Potential always decreases',xy=(1.35,-w*1.35*np.pi-np.cos(1.35*np.pi)),xytext=(.6,-1.8),arrowprops={'arrowstyle':'->','color':'#666666'},fontsize=9)
 for ax in axs[:,j]:
  ax.set_xlim(-.05,2.05);ax.set_xticks([0,.5,1,1.5,2]);ax.grid(alpha=.17);ax.set_facecolor('white')
fig.suptitle('Phase coupling creates wells only below the locking threshold',fontsize=13)
fig.tight_layout(rect=(0,0,1,.95))
fig.savefig(Path.cwd()/'paper-75-phase-dynamics.png',dpi=100,facecolor='white',transparent=False)
plt.close(fig)