# [xdot, jac] = stiffode(x)## Example stiff ODE. First output is dx/dt; second is Jacobian of dx/dt.importnumpyasnpdefstiffode(x):""" [xdot, jac] = stiffode(x) Example stiff ODE. First output is dx/dt; second is Jacobian of dx/dt. """x=np.asarray(x,dtype=float).reshape(-1)A=np.array([[0,1],[-1,0]],dtype=float)lambda_=500rho=float(x@x-1)xdot=A@x-lambda_*rho*xjac=A-np.eye(2)*lambda_*rho-2*lambda_*np.outer(x,x)returnxdot,jac
# [xplus, converged, iter, k] = butcherintegration(x, f, h, a, b, [Niter=100])## Performs numerical integration of f starting from x for h time units.## f must be a function handle that returns dx/dt. If a defines an implicit# method, f may also return the Jacobian as a second output; if so, Newton's# method is used. Otherwise, fixed-point iteration is used.## a and b should be the coefficients from the Butcher tableau for the# integration scheme. Scalars are accepted for 1-stage methods. If the a# matrix is strictly lower triangular, the explicit formulas are used.## Niter is the maximum number of iterations to perform.importnumpyasnpfromnumpy.linalgimportnormfromscipy.linalgimportsolvedefbutcherintegration(x,func,h,a,b,Niter=100):""" Performs numerical integration of f starting from x for h time units. Returns (xplus, converged, iter, k). """x=np.asarray(x,dtype=float).reshape(-1)Nx=len(x)# Accept scalars for 1-stage methods.ifnp.isscalar(a):a=np.array([[a]],dtype=float)else:a=np.asarray(a,dtype=float)ifnp.isscalar(b):b=np.array([b],dtype=float)else:b=np.asarray(b,dtype=float)Ns=len(b)ifa.shape!=(Ns,Ns):raiseValueError('a must be square with one row/col per element of b!')b=h*ba=h*aifnp.count_nonzero(np.triu(a))==0:k=_butcher_explicit(x,func,a)converged=Trueniter=0else:# Probe whether func returns a Jacobian.test=func(x)has_jac=isinstance(test,tuple)ifhas_jac:k,converged,niter=_butcher_implicit_newton(x,func,a,Niter)else:k,converged,niter=_butcher_implicit_fixedpoint(x,func,a,Niter)xplus=x+k@breturnxplus,converged,niter,kdef_butcher_explicit(x,func,a):"""Explicit integration step."""Nx=len(x)Ns=a.shape[0]k=np.zeros((Nx,Ns))foriinrange(Ns):val=func(x+k@a[i,:])k[:,i]=np.asarray(val).reshape(-1)returnkdef_butcher_implicit_newton(x,func,a,Niter):"""Implicit step via Newton's method (func must return (f, J))."""Nx=len(x)Ns=a.shape[0]k=np.zeros((Nx,Ns))converged=Falseniter=0whilenotconvergedandniter<Niter:niter+=1dX=k@a.TifNs==1:f,J=func(x+dX.reshape(-1))f=np.asarray(f).reshape(-1)J=np.asarray(J)*a[0,0]else:f=np.zeros(Nx*Ns)J=np.zeros((Nx*Ns,Nx*Ns))foriinrange(Ns):I=slice(i*Nx,(i+1)*Nx)fi,Ji=func(x+dX[:,i])f[I]=np.asarray(fi).reshape(-1)J[I,:]=np.kron(a[i,:],np.asarray(Ji))f=f-k.flatten(order='F')J=J-np.eye(Nx*Ns)dk=-solve(J,f)k=k+dk.reshape(Nx,Ns,order='F')converged=norm(dk)<1e-8returnk,converged,niterdef_butcher_implicit_fixedpoint(x,func,a,Niter):"""Implicit step via fixed-point iteration (func returns only f)."""Nx=len(x)Ns=a.shape[0]k=np.zeros((Nx,Ns))converged=Falseniter=0whilenotconvergedandniter<Niter:niter+=1k_old=k.copy()dX=k@a.Tforiinrange(Ns):xi=(x+dX[:,i])ifNs>1else(x+dX.reshape(-1))val=func(xi)k[:,i]=np.asarray(val).reshape(-1)converged=norm(k-k_old)<1e-10returnk,converged,niter
# Illustration of GL4 collocation approximation# [depends] functions/stiffode.py functions/butcherintegration.py# [makes] gl4illustration.matimportosimportsyssys.path.insert(0,os.path.join(os.path.dirname(os.path.abspath(__file__)),'functions'))importnumpyasnpimportmatplotlib.pyplotaspltimportscipy.iofrombutcherintegrationimportbutcherintegrationfromstiffodeimportstiffode# Butcher tableau for GL4tab={'a':np.array([[1/4,1/4-np.sqrt(3)/6],[1/4+np.sqrt(3)/6,1/4]]),'b':np.array([1/2,1/2]),'c':np.array([1/2-np.sqrt(3)/6,1/2+np.sqrt(3)/6])}# Run one step of GL4h=2*np.pi/5x0=np.array([1.0,0.0])x1,_,_,k=butcherintegration(x0,stiffode,h,tab['a'],tab['b'])# Get exact solutiont=np.linspace(0,h,1001)x=np.vstack((np.cos(t),-np.sin(t)))xdot=np.vstack((-np.sin(t),-np.cos(t)))# Get collocation polynomial approximationk1=k[:,0:1]k2=k[:,1:2]c1=tab['c'][0]c2=tab['c'][1]rho1=h*(k1*c2-k2*c1)/(c1-c2)rho2=0.5*h*(k1-k2)/(c1-c2)xc=np.full((2,len(t)),np.nan)xdotc=np.full((2,len(t)),np.nan)xnode=np.full((2,len(tab['c'])),np.nan)foriinrange(2):xc[i,:]=x0[i]-rho1[i,0]*(t/h)+rho2[i,0]*(t/h)**2xdotc[i,:]=-rho1[i,0]/h+2*rho2[i,0]/h*(t/h)xnode[i,:]=x0[i]-rho1[i,0]*tab['c']+rho2[i,0]*tab['c']**2# Get tangent lines for derivativedt=0.03# Width for tangentstt=np.array([tab['c'][0]-dt,tab['c'][0]+dt,np.nan,tab['c'][1]-dt,tab['c'][1]+dt])xt=np.hstack((xnode[:,0:1]-h*k1*dt,xnode[:,0:1]+h*k1*dt,np.full((2,1),np.nan),xnode[:,1:2]-h*k2*dt,xnode[:,1:2]+h*k2*dt))t=t/hifnotos.getenv('OMIT_PLOTS')=='true':plt.figure()foriinrange(2):plt.subplot(2,2,i+1)plt.plot(t,x[i,:],'-k',t,xc[i,:],'--k')plt.plot(tt,xt[i,:],'-k',linewidth=5)plt.ylabel(f'x_{i+1}',rotation=0)plt.xlabel(r'$\tau/h$')plt.subplot(2,2,i+3)plt.plot(t,xdot[i,:],'-k',t,xdotc[i,:],'--k')plt.plot(tab['c'],[k1[i,0],k2[i,0]],'ok',markerfacecolor='k')plt.ylabel(f'dx_{i+1}/dt',rotation=0)plt.xlabel(r'$\tau/h$')# Save datadata={'t':t,'x':x,'xdot':xdot,'xc':xc,'xdotc':xdotc,'tt':tt,'xt':xt,'h':h,'k':k,'c':tab['c'],'xn':xnode}scipy.io.savemat('gl4illustration.mat',data)