# [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 explicit numerical integration of a linear ODE.# [depends] functions/butcherintegration.py# [makes] explicitint_accuracy.mat## [rule] copymatimportosimportsyssys.path.insert(0,os.path.join(os.path.dirname(os.path.abspath(__file__)),'functions'))importnumpyasnpimportscipy.ioimportmatplotlib.pyplotaspltfrombutcherintegrationimportbutcherintegration# Butcher tableaus (only a and b) for the three methods.tabs={}tabs['euler']={'a':np.array([[0]]),'b':np.array([1])}tabs['heun']={'a':np.array([[0,0],[1,0]]),'b':np.array([0.5,0.5])}tabs['rk4']={'a':np.array([[0,0,0,0],[0.5,0,0,0],[0,0.5,0,0],[0,0,1,0]]),'b':np.array([1/6,1/3,1/3,1/6])}tabnames=list(tabs.keys())# Run each method.A=np.array([[0,1],[-1,0]])deff(x):returnA@xx0=np.array([1.0,0.0])# Choose values of M to testMs=np.array([2,32,1e2,1e3,1e4])err={}fornintabnames:err[n]=np.nan*np.ones(len(Ms))print(f'Running {n}.')foriinrange(len(Ms)):M=int(Ms[i])print(f' M = {M}')a=tabs[n]['a']b=tabs[n]['b']N=int(np.ceil(M/len(b)))# Number of integration steps.h=2*np.pi/Nx=x0.copy()fortinrange(N):x,*_=butcherintegration(x,f,h,a,b)err[n][i]=np.linalg.norm(x-x0)ifnotos.getenv('OMIT_PLOTS')=='true':plt.figure()colors=['r','g','b']forn,cinzip(tabnames,colors):plt.loglog(Ms,err[n],f'-o{c}',label=n)plt.xlabel('collocation points M')plt.ylabel('Error')plt.legend(loc='upper left')# Save data.scipy.io.savemat('explicitint_accuracy.mat',{'Ms':Ms,'err':err})ifnotos.getenv('OMIT_PLOTS')=='true':plt.savefig('explicitint_accuracy.png')plt.close()