codexpast-exam-of-the-topics-in-applied-mathematics-course-of-the-university-of-oxford2007-damped-double-well.py
past-exam-of-the-topics-in-applied-mathematics-course-of-the-university-of-oxford/2007-damped-double-well.py
# Tested with Python3.14.4,numpy2.3.5,matplotlib3.10.7. Writes PNG to CWD.
import numpy as np
import matplotlib.pyplot as plt
def rhs(z):
x,y=z;return np.array([y,2*x-y-3*x**3])
def step(z,dt):
a=rhs(z);b=rhs(z+dt*a/2);c=rhs(z+dt*b/2);d=rhs(z+dt*c);return z+dt*(a+2*b+2*c+d)/6
fig,ax=plt.subplots(figsize=(7,5),facecolor='white');x,y=np.meshgrid(np.linspace(-1.65,1.65,180),np.linspace(-2,2,190));ax.streamplot(x,y,y,2*x-y-3*x**3,density=1.0,color='#c5c5c5',linewidth=.6,arrowsize=.8)
for sign in [-1,1]:
z=np.array([sign*.0002,sign*.0002]);arr=[z.copy()]
for i in range(2200):z=step(z,.01);arr.append(z.copy())
arr=np.array(arr);ax.plot(arr[:,0],arr[:,1],color='#c34127',lw=2,label='Unstable branches'if sign==1 else None)
z=np.array([sign*.0001,-sign*.0002]);arr=[z.copy()]
for i in range(2500):
z=step(z,-.004);arr.append(z.copy())
if abs(z[0])>1.7 or abs(z[1])>2.1:break
arr=np.array(arr);ax.plot(arr[:,0],arr[:,1],color='#244eab',lw=2,label='Stable branches'if sign==1 else None)
xx=np.linspace(-.36,.36,200);ax.plot(xx,-2*xx+3*xx**3/7,'--',color='#163f9f',lw=2,label='Stable cubic graph');ax.plot(xx,xx-3*xx**3/5,'--',color='#9c271c',lw=2,label='Unstable cubic graph');ax.scatter([0,np.sqrt(2/3),-np.sqrt(2/3)],[0,0,0],color='black',s=24,zorder=5);ax.set(xlim=(-1.65,1.65),ylim=(-2,2),xlabel='$x$',ylabel='$y$',title='Damped double-well phase portrait');ax.legend(loc='lower right',fontsize=8);ax.grid(alpha=.15);fig.tight_layout()
fig.savefig('2007-damped-double-well.png',dpi=160,facecolor='white')