Package: pacopy
pacopypacopy provides various algorithms ofnumerical parameter
continuationfor ODEs and PDEs in
Python.pacopy is backend-agnostic, so it doesn't matter if your problem is formulated withNumPy,SciPy,FEniCS,pyfvm, or any
other Python package. The only thing the user must provide is a class with some simple
methods, e.g., a function evaluationf(u, lmbda), a Jacobian a solverjacobian_solver(u, lmbda, rhs)etc.Install withpip install pacopyTo get started, take a look at the examples below.Some pacopy documentation is availablehere.ExamplesBasic scalar exampleLet's start off with a problem where the solution space is scalar. We try to
solvesin(x) - lambdafor different values oflambda, starting at 0.importmathimportmatplotlib.pyplotaspltimportpacopyclassSimpleScalarProblem:definner(self,a,b):"""The inner product in the problem domain. For scalars, this is just amultiplication."""returna*bdefnorm2_r(self,a):"""The norm in the range space; used to determine if a solution has been found."""returna**2deff(self,u,lmbda):"""The evaluation of the function to be solved"""returnmath.sin(u)-lmbdadefdf_dlmbda(self,u,lmbda):"""The function's derivative with respect to the parameter. Used in Euler-Newtoncontinuation."""return-1.0defjacobian_solver(self,u,lmbda,rhs):"""A solver for the Jacobian problem. For scalars, this is just a division."""returnrhs/math.cos(u)problem=SimpleScalarProblem()lmbda_list=[]values_list=[]defcallback(k,lmbda,sol):# Use the callback for plotting, writing data to files etc.lmbda_list.append(lmbda)values_list.append(sol)pacopy.euler_newton(problem,u0=0.0,lmbda0=0.0,callback=callback,max_steps=20,newton_tol=1.0e-10,verbose=False,)# plot solutionplt.plot(values_list,lmbda_list,".-")plt.xlabel("$u_1$")plt.ylabel("$\\lambda$")plt.show()Simple 2D problemA similarly simple example with two unknowns and a parameter. The inner product and
Jacobian solver are getting more interesting.importmatplotlib.pyplotaspltimportnumpyasnpimportpacopyimportscipyfrommpl_toolkits.mplot3dimportAxes3DclassSimpleProblem2D:definner(self,a,b):returnnp.dot(a,b)defnorm2_r(self,a):returnnp.dot(a,a)deff(self,u,lmbda):returnnp.array([np.sin(u[0])-lmbda-u[1]**2,np.cos(u[1])*u[1]-lmbda,])defdf_dlmbda(self,u,lmbda):return-np.ones_like(u)defjacobian_solver(self,u,lmbda,rhs):A=np.array([[np.cos(u[0]),-2*u[1]],[0.0,np.cos(u[1])-np.sin(u[1])*u[1]],])returnnp.linalg.solve(A,rhs)problem=SimpleProblem2D()# Initial guess and initial parameter valueu0=np.zeros(2)lmbda0=0.0# Init listslmbda_list=[]values_list=[]defcallback(k,lmbda,sol):lmbda_list.append(lmbda)values_list.append(sol)pacopy.euler_newton(problem,u0,lmbda0,callback,max_steps=50,newton_tol=1.0e-10)# plot solutionfig=plt.figure()ax=fig.add_subplot(111,projection="3d")ax.plot(*np.array(values_list).T,lmbda_list,".-")ax.set_xlabel("$u_1$")ax.set_ylabel("$u_2$")ax.set_zlabel("$\\lambda$")# plt.show()plt.savefig("simple2d.svg",transparent=True,bbox_inches="tight")plt.close()BratuLet's deal with an actual PDE, the classicalBratu
problemin 1D with Dirichlet boundary conditions. Now, the solution space isn't scalar, but a
vector of lengthn(the values of the solution at given points on the 1D interval).
We now need numpy and scipy, the inner product and Jacobian solver are more complicated.importmatplotlib.pyplotaspltimportnumpyasnpimportpacopyimportscipy.sparseimportscipy.sparse.linalg# This is the classical finite-difference approximationclassBratu1d:def__init__(self,n):self.n=nh=1.0/(self.n-1)self.H=np.full(self.n,h)self.H[0]=h/2self.H[-1]=h/2self.A=(scipy.sparse.diags([-1.0,2.0,-1.0],[-1,0,1],shape=(self.n,self.n))/h**2)definner(self,a,b):"""The inner product of the problem. Can be np.dot(a, b), but factoring inthe mesh width stays true to the PDE."""returnnp.dot(a,self.H*b)defnorm2_r(self,a):"""The norm in the range space; used to determine if a solution has been found."""returnnp.dot(a,a)deff(self,u,lmbda):"""The evaluation of the function to be solved"""out=self.A.dot(u)-lmbda*np.exp(u)out[0]=u[0]out[-1]=u[-1]returnoutdefdf_dlmbda(self,u,lmbda):"""The function's derivative with respect to the parameter. Used in Euler-Newtoncontinuation."""out=-np.exp(u)out[0]=0.0out[-1]=0.0returnoutdefjacobian_solver(self,u,lmbda,rhs):"""A solver for the Jacobian problem."""M=self.A.copy()d=M.diagonal().copy()d-=lmbda*np.exp(u)M.setdiag(d)# Dirichlet conditionsM.data[0][self.n-2]=0.0M.data[1][0]=1.0M.data[1][self.n-1]=1.0M.data[2][1]=0.0returnscipy.sparse.linalg.spsolve(M.tocsr(),rhs)problem=Bratu1d(51)# Initial guess and parameter valueu0=np.zeros(problem.n)lmbda0=0.0lmbda_list=[]values_list=[]defcallback(k,lmbda,sol):# Use the callback for plotting, writing data to files etc.fig=plt.figure()ax1=fig.add_subplot(111)ax1.set_xlabel("$\\lambda$")ax1.set_ylabel("$||u||_2$")ax1.grid()lmbda_list.append(lmbda)# use the norm of the currentsolution for plotting on the y-axisvalues_list.append(np.sqrt(problem.inner(sol,sol)))ax1.plot(lmbda_list,values_list,"-x",color="C0")ax1.set_xlim(0.0,4.0)ax1.set_ylim(0.0,6.0)plt.close()# Natural parameter continuation# pacopy.natural(problem, u0, lmbda0, callback, max_steps=100)pacopy.euler_newton(problem,u0,lmbda0,callback,max_steps=500,max_num_retries=10,newton_tol=1.0e-10)Ginzburg–Landauhttps://user-images.githubusercontent.com/181628/146639709-90b6e6aa-48ba-418d-9aa4-ec5754f95b93.mp4TheGinzburg-Landau
equationsmodel
the behavior of extreme type-II superconductors under a magnetic field. The
above example (to be found in full detailhere) shows parameter continuation in the
strength of the magnetic field. The plot on the right-hand side shows the
complex-valued solution usingcplot.