简体   繁体   中英

Weird results obtained while solving a set of coupled differential equations (using a sparse array) in python

I have tried to no avail for a week while trying to solve a system of coupled differential equations and reproduce the results shown in the attached image. I seem to be getting weird results as shown also. I don't seem to know what I might be doing wrong.The set of coupled differential equations were solved using Newman's BAND. Here's a link to the python implementation: python solution using BAND . And another link to the original image of the problem in case the attached is not clear enough: here you find a clearer image of the problem . Now what I am trying to do is to solve the same problem by creating a sparse array directly from the discretized equations using a combination of sympy and numpy and then solving using scipy's spsolve. Here is my code below. I need some help to figure out what I am doing wrong. 问题的图像,正确的解决方案和我的解决方案在这里 I have represented the variables as c1 = cA, c2 = cB, c3 = cC, c4 = cD in my code. Equation 2 has been linearized and phi10 and phi20 are the trial values of the variables cC and cD.

# import modules
import numpy as np
import sympy
from sympy.core.function import _mexpand
import scipy as sp
import scipy.sparse as ss
import scipy.sparse.linalg as ssl
import matplotlib.pyplot as plt

# define functions
def flatten(t):
    """
    function to flatten lists
    """
    return [item for sublist in t for item in sublist]


def get_coeffs(coeff_dict, func_vars):
    """
    function to extract coefficients from variables
    and form the sparse symbolic array
    """
    c = coeff_dict
    for i in list(c.keys()):
        b, _ = i.as_base_exp()
        if b == i:
            continue
        if b in c:
            c[i] = 0
        if any(k.has(b) for k in c):
            c[i] = 0

    return [coeff_dict[val] for val in func_vars]  

# Constants for the problem
I = 0.1                 # A/cm2
L = 1.0                 # distance (x) in cm
m = 100                 # grid spacing
h = L / (m-1)
a = 23300               # 1/cm
io = 2e-7               # A/cm2
n = 1
F = 96500               # C/mol
R = 8.314               # J/mol-K
T = 298                 # K
sigma = 20              # S/cm
kappa = 0.06            # S/cm
alpha = 0.5
beta = -(1-alpha)*n*F/R/T
phi10 , phi20 = 5, 0.5 # these are just guesses
P = a*io*np.exp(beta*(phi10-phi20))

j = sympy.symbols('j',integer = True)
cA = sympy.IndexedBase('cA')
cB = sympy.IndexedBase('cB')
cC = sympy.IndexedBase('cC')
cD = sympy.IndexedBase('cD')

# write the boundary conditions at x = 0
bc=[cA[1], cB[1],
(4/3)  * cC[2] - (1/3)*cC[3], # use a three point approximation for cC_prime
cD[1]
]

# form a list of expressions from the boundary conditions and equations
expr=flatten([bc,flatten([[
-cA[j-1] - cB[j-1] + cA[j+1] + cB[j+1],
cB[j-1] - 2*h*P*beta*cC[j] + 2*h*P*beta*cD[j] - cB[j+1],
-sigma*cC[j-1] + 2*h*cA[j] + sigma * cC[j+1],
-kappa * cD[j-1] + 2*h * cB[j] + kappa * cD[j+1]] for j in range(2, m)])])

vars = [cA[j], cB[j], cC[j], cD[j]]


# flatten the list of variables
unknowns = flatten([[cA[j], cB[j], cC[j], cD[j]] for j in range(1,m)])
var_len = len(unknowns)

# # # substitute in the boundary conditions at x = L while getting the coefficients
A = sympy.SparseMatrix([get_coeffs(_mexpand(i.subs({cA[m]:I}))\
    .as_coefficients_dict(), unknowns) for i in expr])




# convert to a numpy array
mat_temp = np.array(A).astype(np.float64)

# you can view the sparse array with this
fig = plt.figure(figsize=(6,6))
ax = fig.add_axes([0,0, 1,1])
cmap = plt.cm.binary
plt.spy(mat_temp, cmap = cmap, alpha = 0.8)

def solve_sparse(b0, error):
    # create the b column vector
    b = np.copy(b0)
    b[0:4] = np.array([0.0, I, 0.0, 0.0])
    b[var_len-4] =   I
    b[var_len-3] =   0
    b[var_len-2] =   0
    b[var_len-1] =   0

    print(b.shape)
    
    old = np.copy(b0)

    mat = np.copy(mat_temp)
    b_2 = np.copy(b)
       
    resid = 10
    lss = 0
    while lss < 100:
        mat_2 = np.copy(mat)
  
        
        for j in range(3, var_len - 3, 4):

            # update the forcing term of equation 2
            b_2[j+2] = 2*h*(1-beta*old[j+3]+beta*old[j+4])*a*io*np.exp(beta*(old[j+3]-old[j+4]))
            
            # update the sparse array at every iteration for variables cC and cD in equation2
            mat_2[j+2, j+3] += 2*h*beta*a*io*np.exp(beta*(old[j+3]-old[j+4]))
            mat_2[j+2, j+4] += 2*h*beta*a*io*np.exp(beta*(old[j+3]-old[j+4]))
       
        # form the column sparse matrix 
        A_s = ss.csc_matrix(mat_2)
        new = ssl.spsolve(A_s, b_2).flatten()


        resid = np.sum((new - old)**2)/var_len
 
        lss += 1

        old = np.copy(new)
        
    return new


val0 = np.array([[0.0, 0.0, 0.0, 0.0] for _ in range(m-1)]).flatten() # form an array of initial values
error = 1e-7
## Run the code
conc = solve_sparse(val0, error).reshape(m-1, len(vars))


conc.shape  # gives (99, 4)
# Plot result for cA:
plt.plot(conc[:,0], marker = 'o', linestyle = '')

What happens seems pretty clear. You are using the central Euler method as discretization, for u'=F(u) this reads as

u[j+1]-u[j-1] = 2*h*F(u[j])

This method is only weakly stable and allows the sub-sequences of odd and even indices to evolve rather independently. As equation this would mean that the solution might approximate the system ue'=F(uo), uo'=F(ue) with independent functions ue, uo that follow the path of the even or odd sub-sequence.

To avoid that requires a very careful handling of boundary values and equations for the boundary points.


But one can avoid all this unpleasantness by using the trapezoidal method

u[j+1]-u[j] = 0.5*h*(F(u[j+1])+F(u[j]))

This also reduces the band-width of the system matrix.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM