codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2005iiipaper-83-radial-lattice.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2005/iii/paper-83-radial-lattice.py
"""Solve and plot a stationary GP vortex in a radial lattice.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Writes only an opaque PNG basename to caller CWD; honors caller MPLCONFIGDIR.
"""
from pathlib import Path
import numpy as np
def tridiagonal(lower, diagonal, upper, rhs):
"""Thomas elimination for the finite-difference Newton correction."""
b=diagonal.copy(); d=rhs.copy(); c=upper.copy()
for j in range(1,len(b)):
scale=lower[j]/b[j-1]
b[j]-=scale*c[j-1]; d[j]-=scale*d[j-1]
answer=np.empty_like(d);answer[-1]=d[-1]/b[-1]
for j in range(len(b)-2,-1,-1):answer[j]=(d[j]-c[j]*answer[j+1])/b[j]
return answer
def solve_profile(depth=.6, period=4., outer=32., step=.025):
n=round(outer/step); r=np.linspace(0,outer,n+1); h=r[1]
potential=depth*np.sin(np.pi*r/period)**2
lower=np.zeros_like(r);upper=np.zeros_like(r);base=np.zeros_like(r)
lower[1:-1]=h**-2-1/(2*r[1:-1]*h)
upper[1:-1]=h**-2+1/(2*r[1:-1]*h)
base[1:-1]=-2/h**2-1/r[1:-1]**2+1-potential[1:-1]
lower[-1]=-1; base[-1]=1; base[0]=1
def residual(f):
out=np.zeros_like(f)
out[1:-1]=lower[1:-1]*f[:-2]+upper[1:-1]*f[2:]+base[1:-1]*f[1:-1]-f[1:-1]**3
out[0]=f[0];out[-1]=f[-1]-f[-2]
return out
f=np.tanh(r/np.sqrt(2))*np.sqrt(np.maximum(1-potential,.1))
for iteration in range(50):
res=residual(f); norm=np.max(np.abs(res))
if norm<2e-10:break
diagonal=base.copy();diagonal[1:-1]-=3*f[1:-1]**2
correction=tridiagonal(lower,diagonal,upper,-res)
weight=1.
for attempt in range(30):
candidate=f+weight*correction
if np.min(candidate[1:])>0 and np.max(np.abs(residual(candidate)))<norm:
f=candidate;break
weight*=.5
else:raise RuntimeError('Newton line search failed')
else:raise RuntimeError('Newton iteration did not converge')
return r,f,potential,float(np.max(np.abs(residual(f))))
def main():
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
r,f,potential,res=solve_profile()
r0,f0,_,res0=solve_profile(depth=0)
assert max(res,res0)<2e-10
fig,(ax,bx)=plt.subplots(2,1,figsize=(8.1,5.8),sharex=True,gridspec_kw={'height_ratios':[3,1]},constrained_layout=True)
ax.plot(r,f,lw=2,color='#176580',label='Radial lattice: computed unit vortex')
ax.plot(r0,f0,lw=1.3,ls='--',color='0.45',label='Zero-lattice vortex reference')
ax.set(xlim=(0,20),ylim=(0,1.08),ylabel=r'Amplitude $R/\sqrt{\mu/U}$',title='Stationary vortex in a radial optical lattice')
ax.text(6,.25,r'$\mathcal{N}=1$, $d=4\ell_0$, $sE_R/\mu=0.6$',fontsize=11)
ax.legend(loc='lower right',fontsize=9)
bx.plot(r,potential,color='#a05c22',lw=1.8)
bx.set(ylim=(0,.7),xlabel=r'Radius $r/\ell_0$',ylabel=r'$V/\mu$')
ax.grid(alpha=.18);bx.grid(alpha=.18)
fig.savefig(Path.cwd()/(Path(__file__).stem+'.png'),dpi=110,facecolor='white',transparent=False)
if __name__=='__main__':main()