codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2005/ii/paper-1-bifurcations.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2005/ii/paper-1-bifurcations.py
"""Original equilibrium diagrams, Python 3.14 / NumPy 2.3.5 / Matplotlib 3.10.7.
Writes only paper-1-bifurcations.png to caller CWD, opaque white background.
"""
import os
os.environ.setdefault('MPLCONFIGDIR','/tmp/codex-wiki-matplotlib')
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
blue='#1769aa';red='#c74444'
fig,axes=plt.subplots(1,3,figsize=(10.6,3.9),dpi=120,facecolor='white')
a=np.linspace(-.3,1.6,900)
for mask,color,style in [(a<0,red,'--'),((a>=0)&(a<=1),blue,'-'),(a>1,red,'--')]:axes[0].plot(a[mask],a[mask]**2,color=color,ls=style)
p=np.linspace(0,1.6,650)
axes[0].plot(p,-np.sqrt(p),color=red,ls='--')
axes[0].plot(p[p<=1],np.sqrt(p[p<=1]),color=red,ls='--')
axes[0].plot(p[p>=1],np.sqrt(p[p>=1]),color=blue)
axes[0].scatter([0,1],[0,1],color='black',s=18,zorder=6)
axes[0].set(xlabel='$a$',ylabel='equilibrium $x=y$',title='Unperturbed branches',ylim=(-1.4,2.7))
eps=.004
for aa in np.linspace(-.04,.25,700):
 for z in np.roots([1,0,-aa,eps]):
  if abs(z.imag)<1e-8:
   stable=3*z.real*z.real-aa<0
   axes[1].plot(aa,z.real,'.',color=blue if stable else red,markersize=1.6)
axes[1].set(xlabel='$a$',ylabel='$x$',title=r'Near $a=0$: broken pitchfork')
eps2=.006;mu=np.linspace(-.23,.23,800);d=9*mu*mu-8*eps2;valid=d>=0
axes[2].plot(mu[valid & (mu<0)],(5*mu[valid & (mu<0)]-np.sqrt(d[valid & (mu<0)]))/4,color=blue)
axes[2].plot(mu[valid & (mu>0)],(5*mu[valid & (mu>0)]-np.sqrt(d[valid & (mu>0)]))/4,color=blue)
for mask in [valid & (mu<0),valid & (mu>0)]:axes[2].plot(mu[mask],(5*mu[mask]+np.sqrt(d[mask]))/4,color=red,ls='--')
axes[2].axvspan(-np.sqrt(8*eps2)/3,np.sqrt(8*eps2)/3,color='#eef0f2')
axes[2].set(xlabel=r'$\mu=a-1$',ylabel='$u=x-1$',title=r'Near $a=1$: gap and folds')
for ax in axes:ax.spines[['top','right']].set_visible(False);ax.grid(alpha=.15)
fig.legend(handles=[Line2D([0],[0],color=blue,label='attracting'),Line2D([0],[0],color=red,ls='--',label='saddle / unstable slow direction')],loc='lower center',ncol=2,fontsize=9)
fig.suptitle('Positive forcing breaks the crossings (unfolded panels show leading local forms)',fontsize=11)
fig.tight_layout(rect=(0,.08,1,.94));fig.savefig('paper-1-bifurcations.png',facecolor='white',transparent=False);plt.close(fig)