:

## Maple helps in simulating multiscale-multidomain battery models efficiently

Maple

We recently published a paper on multiscale-multidomain simulation of battery models.

https://iopscience.iop.org/article/10.1149/1945-7111/abb37b

Some challenges are listed at

Maple and symbolic math can play a critical role in solving many challenging problems. For example, consider a seemingly-trivial problem

uxx+uyy = 0

x = 0 and x = 1, ux = 0 for all y

y = 1, u = 0, for all x

y = 0, u =1, 0<=x<=0.5

y = 0, uy = 0, 0.5<x<=1

There is a singularity at (0.5,0) and most numerical methods will have trouble there. In these equations, uxx means the second derivative of u with respect to x.

Maple can help solve this problem with conformal mapping to achieve arbitrary precision. As of today, machine precision is not possible with any numerical method even with millions of Degrees of Freedom. The Maple code is given below. A FEM code is given below as well. Models like this can benefit from Maple adding parallel sparse direct and iterative solvers.

 >
 > restart;
 > Digits:=15;
 (1)

The domain is tranformed from Z = X+IY to w. The points tranformed are

 > Zdomain:=[[0,0],[1,0],[1,1],[0,1]];
 (2)
 > wdomain:=[0,1,1+a,a+2];
 (3)
 > eq1:=diff(Z(w),w)=-I*K1/sqrt(w)/sqrt(w-1)/sqrt(w-a-1)/sqrt(w-a-2);
 (4)

a is not known apriori. The value of a should be found to make sure [1,1] in the Z coordinate is transformed to [1+a] in the w coordinate.

 > a:=sqrt(2)-1;
 (5)

Value of K1 is found using the transformation of [1,0] to 1 in the w coordinate

 > eq11:=1=int(rhs(eq1),w=0..1);
 (6)
 > K1:=solve(eq11,K1);
 (7)

The height Y in the Z coordinate is found by integrating from 1 to 1+a in the w coordinate.

 > simplify(int(rhs(eq1),w=1..1+a));
 (8)
 > evalf(%);
 (9)

The choice of a = sqrt(2)-1 gives the height of 1 for Z coordinate.

 > eval(simplify(int(rhs(eq1),w=0..1+a)));
 (10)
 > evalf(%);
 (11)

integration from 0 to 1+a in the w coordinate gives 1,1 in the Z coordinate.

 > simplify(int(rhs(eq1),w=0..1/sqrt(2)));
 (12)
 > evalf(%);
 (13)

Integrating w from 0 to wmid =1/sqrt(2) gives the point 0.5,0 in the Z coordinate

 > wmid:=1/sqrt(2);
 (14)

Next w domain is transformed to Z2 domain Z2 = X2+IY2

 > Z2domain:=[[0,0],[1,0],[1,H],[0,H]];
 (15)
 > wdomain2:=[0,1/sqrt(2),1+a,a+2];
 (16)
 > eq2:=diff(Z2(w),w)=-I*K2/sqrt(w)/sqrt(w-wmid)/sqrt(w-1-a)/sqrt(w-a-2);
 (17)

K2 is found based on the transformation of wmid to [1,0] in Z2 coordinate.

 > eq21:=1=int(rhs(eq2),w=0..wmid);
 (18)
 > K2:=solve(eq21,K2);
 (19)

The corner 0,1 in the Z coordinate is mapped by integrating eq2 from 0 to 1 in the w coordinate

 > int(rhs(eq2),w=0..1);
 (20)
 > corner:=evalf(%);
 (21)

This is the point 1,0 in the original coordinate.

The height in the Z2 coorinate is found by integrating eq2 from wmid to 1+a.

 > ytot:=int(rhs(eq2),w=wmid..1+a);
 (22)
 > evalf(%);
 (23)

The magnitude in the Y direction is given by the coefficient of I, the imaginary number

 > Ytot:=coeff(ytot,I);
 (24)

The analytial solution in the Z2 corordinate is a line in Y2 to satisfy simple zero flux conditions at X2 = 0 and X2 = 1.

 > phianal:=1+b*Y2;
 (25)

The value of phi is zero at Y2 = Ytot (originally the cathode domain in the Z domain)

 > bc:=subs(Y2=Ytot,phianal)=0;
 (26)
 > b:=solve(bc,b);
 (27)

The analytical solution is given by

 > phianal;
 (28)
 > evalf(phianal);
 (29)

The potential at the corner is given by substituting the imaginary value of corner for Y2 in phinanal)

 > phicorner:=subs(Y2=Im(corner),phianal);
 (30)
 > evalf(phicorner);
 (31)

local flux/current density calculation, written in terms of w is

 > curr:=b*rhs(eq2)/rhs(eq1);
 (32)

average flux/current density calculation for the anode

 > currave:=int((curr),w=0..wmid)/wmid;
 (33)
 > Digits:=25:

The average current density at Y =0, local current density at X = 0,Y=0 and potential at X=1,Y=0 (Corner) can be used to study convergence of FEM and other numerical methods

 > evalf(currave),evalf(subs(w=0,curr)),evalf(phicorner);
 (34)
 >

This FEM code is for solving Laplace's equation with primary current distribution considered in Model 1.
This code is based on FEM weak-form. Biquadratic Lagrange shape functions (9nodes in an element) are used.

 > restart;
 > with(LinearAlgebra):
 > Lx:=1: #length in X
 > Ly:=1: #length in Y
 > nx:=10: #number of elements in X (even numbers only)
 > ny:=10: #number of elements in Y, to be kept same as nx in this version
 > hx:=Lx/nx: #element size x
 > hy:=Ly/ny: #element size y

Procedure to perform numerical integration on shape functions to obtain local matrices (can be replaced with analytical integration for this particular problem)
-Shape functions are also used as weight functions in applying weak formulation. Numerical integration is done using Simpson's rule.
-Local cartesian coordinates x,y are converted to natural coordinates zeta and eta. This transformation is not required for this simple geomerty but useful in general. zeta and eta are obtained by scaling x and y with hx/2 and hy/2, respectively, in this code.

 > A:=[(1-zeta)*zeta*(1-eta)*eta/4,-(1-(zeta)^2)*(1-eta)*eta/2,-(1+zeta)*zeta*(1-eta)*eta/4,(1-(eta)^2)*(1+zeta)*zeta/2,(1+zeta)*zeta*(1+eta)*eta/4,(1-(zeta)^2)*(1+eta)*eta/2,-(1-zeta)*zeta*(1+eta)*eta/4,-(1-(eta)^2)*(1-zeta)*(zeta)/2,(1-(zeta)^2)*(1-(eta)^2)]; #bi quadratic langrange shape functions
 > y:=[-a1,-a2,-a3,0,a3,a2,a1,0,0];x:=[-b1,0,b1,b2,b3,0,-b3,-b2,0];
 > Kx:=Matrix(nops(A),nops(A),datatype=float[8]):
 > Ky:=Matrix(nops(A),nops(A),datatype=float[8]): c:=Vector(nops(A),datatype=float[8]):
 > terms:=20:#number of terms for numerical integration dzeta:=2/terms: deta:=2/terms:
 > for i from 1 to nops(Nx) do #loop to obtain local matrices       for j from 1 to nops(Ny) do Kx[i,j]:=0; Ky[i,j]:=0; for k from 0 to terms do #outer loop double integration, integration in zeta if k = 0 then fx[k]:= subs(zeta=-1,Nx[i]*Nx[j]*J); fy[k]:= subs(zeta=-1,Ny[i]*Ny[j]*J);   elif k = terms then fx[k]:= subs(zeta=-1+(k*dzeta),Nx[i]*Nx[j]*J); fy[k]:= subs(zeta=-1+(k*dzeta),Ny[i]*Ny[j]*J);   elif irem(k,2) = 0 then fx[k]:= 2*subs(zeta=-1+(k*dzeta),Nx[i]*Nx[j]*J); fy[k]:=     2*subs(zeta=-1+(k*dzeta),Ny[i]*Ny[j]*J); else fx[k]:= 4*subs(zeta=-1+(k*dzeta),Nx[i]*Nx[j]*J); fy[k]:=     4*subs(zeta=-1+(k*dzeta),Ny[i]*Ny[j]*J);  end if; for l from 0 to terms do #inner loop double integration, integration in eta if l = 0 then fxy[l]:= subs(eta=-1,fx[k]); fyy[l]:= subs(eta=-1,fy[k]); elif l = terms then fxy[l]:= subs(eta=-1+(l*deta),fx[k]); fyy[l]:= subs(eta=-1+(l*deta),fy[k]); elif irem(l,2) = 0 then fxy[l]:= 2*subs(eta=-1+(l*deta),fx[k]); fyy[l]:= 2*subs(eta=-1+(l*deta),fy[k]); else fxy[l]:=4*subs(eta=-1+(l*deta),fx[k]); fyy[l]:=4*subs(eta=-1+(l*deta),fy[k]); end if; Kx[i,j]:=Kx[i,j]+fxy[l]; Ky[i,j]:=Ky[i,j]+fyy[l]; end do;      end do; Kx[i,j]:=Kx[i,j]*dzeta*deta/9; Ky[i,j]:=Ky[i,j]*dzeta*deta/9; end do; end do:
 > end proc:
 > n:=nx*ny; #total number of elements
 (1)
 > Nx1:=nx*2+1: #number of nodes in x in one row
 > N:=Nx1*(2*ny+1); # total number of nodes/equations
 (2)
 > K:=Matrix(N,N,storage=sparse): # global K matrix
 > C:=Vector(N,storage=sparse): # global c matrix
 > L2G:=Matrix(n,9):  #mapping matrix - each row has node numbers for each element
 > l:=1:k:=1: localmatrices(hy/2,hy/2,hy/2,hx/2,hx/2,hx/2,0,0): kx:=copy(Kx):ky:=copy(Ky):c0:=copy(c):
 > for i from 1 to n do #modifying,adding and assembling matrices to get global matrix   if i<=nx/2 then   a1:=copy(kx); a2:=copy(ky); a3:=0; a1[1..3,1..9]:=IdentityMatrix(3,9); a2[1..3,1..9]:=Matrix(3,9,shape=zero); a4:=a1+a2; c:=copy(c0); c[1..3]:=1.0; elif i=nx/2+1 then  a1:=copy(kx); a2:=copy(ky); a3:=0; a1[1,1..9]:=IdentityMatrix(1,9); a2[1,1..9]:=Matrix(1,9,shape=zero); a4:=a1+a2; c:=copy(c0); c[1]:=1.0; elif i>nx*(ny-1) then a1:=copy(kx); a2:=copy(ky); a3:=0; a1[5..7,5..7]:=IdentityMatrix(3,3); a1[5..7,1..4]:=ZeroMatrix(3,4); a1[5..7,8..9]:=ZeroMatrix(3,2); a2[5..7,1..9]:=Matrix(3,9,shape=zero); a4:=a1+a2; c:=copy(c0); c[5..7]:=0; else a1:=kx; a2:=ky; a3:=0;a4:=a1+a2; c:=c0;  end if; L2G[i,1..9]:=Matrix([l,l+1,l+2,l+2+Nx1,l+2+Nx1*2,l+1+Nx1*2,l+Nx1*2,l+Nx1,l+1+Nx1]): k:=k+1:   if k>nx then k:=1; l:=l+Nx1+3; else l:=l+2; end if: indx2:=L2G[i,1..9]: indx2:=convert(indx2,list): C[indx2]:=C[indx2]+c[1..9]; c[1..9]; for i1 from 1 to 9 do indx1:=L2G[i,i1]: K[indx1,indx2]:=K[indx1,indx2]+a4[i1,1..9]: end do: end do:
 > phi:=LinearSolve(K,C,method=SparseLU): #linear set of equations solved using Sparse LU solver
 > phi_at(1,0):=phi[Nx1];
 (3)
 > dNdy:=copy(Ny):
 > dNdy_bottom_left:=subs(eta=-1,zeta=-1,dNdy):