codex/past-exam-of-the-differential-equations-and-applications-course-of-the-university-of-oxford/2007-relaxation-cycle.py

past-exam-of-the-differential-equations-and-applications-course-of-the-university-of-oxford/2007-relaxation-cycle.py
# Tested with Python3.14.4,numpy2.3.5,matplotlib3.10.7. Output is written to CWD.
import numpy as np
import matplotlib.pyplot as plt
G=.5
f=lambda y:y-y*y+G*y**3/3
lo=(1-np.sqrt(1-G))/G;hi=(1+np.sqrt(1-G))/G
bottom=3/G-2*hi;top=3/G-2*lo
fig,ax=plt.subplots(figsize=(7,5),facecolor='white')
for a,b,color,style in [(-1.2,lo,'#147d6c','-'),(lo,hi,'#9c7373','--'),(hi,5.2,'#147d6c','-')]:
 y=np.linspace(a,b,250);ax.plot(f(y),y,style,color=color,lw=2)
for a,b in [(bottom,lo),(top,hi)]:
 y=np.linspace(a,b,200);ax.plot(f(y),y,color='#2048aa',lw=3)
 m=(a+b)/2;dy=np.sign(b-a)*.18;ax.annotate('',xy=(f(m+dy),m+dy),xytext=(f(m-dy),m-dy),arrowprops={'arrowstyle':'->','color':'#2048aa','lw':2})
for y0,y1 in [(lo,top),(hi,bottom)]:
 ax.annotate('',xy=(f(y0),y1),xytext=(f(y0),y0),arrowprops={'arrowstyle':'->','color':'#bd4328','lw':2.3})
ax.axhline(2,color='gray',ls=':',label=r'$y=α=2$')
ax.scatter([f(lo),f(hi)],[lo,hi],color='black',zorder=5)
ax.text(f(lo)+.15,lo-.15,r'$y_-$');ax.text(f(hi)-.48,hi+.06,r'$y_+$')
ax.set(xlabel='$x$',ylabel='$y$',title='Slow arcs and fast jumps of a cubic relaxation oscillator',xlim=(-2.5,2),ylim=(-1.3,5.3))
ax.text(.03,.97,'Green: attracting cubic branches\nDashed: repelling cubic branch\nBlue: slow drift; red: fast jumps',transform=ax.transAxes,va='top',fontsize=9,bbox={'facecolor':'white','alpha':.9,'edgecolor':'none'})
ax.legend(loc='lower right');ax.grid(alpha=.18);fig.tight_layout();fig.savefig('2007-relaxation-cycle.png',dpi=160,facecolor='white')