# [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
# Accuracy of numerical integration of a stiff ODE.# [depends] functions/stiffode.py functions/butcherintegration.py# [makes] stiffode_accuracy.mat## [rule] copymatimportosimportsyssys.path.insert(0,os.path.join(os.path.dirname(os.path.abspath(__file__)),'functions'))importnumpyasnpimportscipy.iofrombutcherintegrationimportbutcherintegrationfromstiffodeimportstiffode# Butcher tableaus (only a and b) for the three methods.tabs={}tabs['ieu']={'a':1.0,'b':1.0}tabs['mid']={'a':0.5,'b':1.0}tabs['gl4']={'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])}tabnames=list(tabs.keys())# Run each method for varying number of iterations.x0=np.array([1.0,0.0])f=stiffode# Choose number of function evaluations. Note that the figure in the text goes# up to M = 10^6, which takes about an hour to run; the checked-in data file# in data/ is used via the copymat rule.Ms=[1,10,100,1000,10000]err={}converged={}fornintabnames:err[n]=np.full(len(Ms),np.nan)converged[n]=np.ones(len(Ms),dtype=bool)print(f'Running {n}.')foriinrange(len(Ms)):M=Ms[i]print(f' M = {M}')a=tabs[n]['a']b=tabs[n]['b']nstages=1ifnp.isscalar(b)elselen(b)N=int(np.ceil(M/nstages))h=2*np.pi/Nx=x0.copy()fortinrange(N):x,okay,*_=butcherintegration(x,f,h,a,b,100)converged[n][i]=converged[n][i]andokayerr[n][i]=np.linalg.norm(x-x0)# Save datascipy.io.savemat('stiffode_accuracy.mat',{'Ms':np.array(Ms),'err':err,'converged':converged})