基于Python编写求解抛物型pde方程的经典数值格式模拟

基于Python编写求解抛物型pde方程的经典数值格式模拟

前言

热方程的在很多领域都有所应用,熟知的在金融领域求解期权定价公式之Black-Scholes方程,就可以用数值格式求解此类方程,因为很多复杂的期权定价公式很难有显式解,数值方法在这方面就有很多优越性。本文将基于Python编写常见的三种数值格式来求解传统的初-边值一维热方程问题。

岁月如云,匪我思存,写作不易,望路过的朋友们点赞收藏加关注哈,在此表示感谢!

一:一维热传导方程简介

一维热传导方程如下:
ϑ u ϑ t = a ϑ 2 u ϑ x 2 + f ( x ) , 0 < t ≤ T . ( 1 ) \frac{\vartheta u}{\vartheta t}=a\frac{\vartheta^2u}{\vartheta x^2}+f(x),0< t\leq T.(1) ϑtϑu=aϑx2ϑ2u+f(x),0<tT.1
其中 a 是正常数, f(x) 是给定的连续函数,对于初-边值问题的描述为,对于有一定阶偏导微商函数 u ( x , t ) u(x,t) u(x,t) ,满足方程(1)的初始条件: u ( x , 0 ) = Φ ( x ) , 0 < x < l u(x,0)=\Phi(x),0<x<l u(x,0)=Φ(x),0<x<l 以及边界条件 u ( 0 , t ) = u ( l , t ) = 0 , 0 ≤ t ≤ T u(0,t)=u(l,t)=0,0\leq t\leq T u(0,t)=u(l,t)=0,0tT

特别提示:由于本文不是纯粹的数学推理文章,所以会涉及到一些条件和假设的简化,如果从纯粹数学角度考虑,一个条件的成立往往需要很多假设和前提,以及精准的定义,这一点跟工业中算法成立还是有很大区别的。

对于光滑 f ( x ) f(x) f(x) Φ ( x ) \Phi(x) Φ(x) ,我们从初-边值考虑差分逼近。取空间步长 h = l / J h=l/J h=l/J 和时间步长 τ = T / N \tau=T/N τ=T/N ,其中 J 、 N J、N JN 都是自然数。用两族平行直线 x = x j = j h ( j = 0 , 1 , . . . , J ) x=x_j=jh(j=0,1,...,J) x=xj=jh(j=0,1,...,J) t = t n = n τ ( n = 0 , 1 , . . . N ) t=t_n=n\tau(n=0,1,...N) t=tn=nτ(n=0,1,...N) 将如下矩形分割成网格,网格节点设为 ( x j , t n ) (x_j,t_n) (xj,tn)
在这里插入图片描述

我们用 u j n u_j^n ujn 表示定义在网点 ( x j , t n ) (x_j,t_n) (xj,tn) 上的函数,接着用差商代替上述(1)方程,接下来介绍几种简便的经典差分格式。

二:差分格式

  • 向前差分格式(显式格式)

u j n + 1 − u j n τ = a u j + 1 n − 2 u j n + u j − 1 n h 2 + f j \frac{u_j^{n+1}-u_j^n}{\tau}=a\frac{u_{j+1}^{n}-2u_j^n+u_{j-1}^n}{h^2}+f_j τujn+1ujn=ah2uj+1n2ujn+uj1n+fj ,其中 j = 1 , 2 , . . . , J − 1 , n = 0 , 1 , . . . , N − 1 j=1,2,...,J-1,n=0,1,...,N-1 j=1,2,...,J1,n=0,1,...,N1 ,以 α = a τ / h 2 \alpha=a\tau/h^2 α=aτ/h2 表示网格比,上式我们变形可得: u j n + 1 = α u j + 1 n + ( 1 − 2 α ) u j n + r u j − 1 n + τ f j u_j^{n+1}=\alpha u_{j+1}^n+(1-2\alpha)u_j^n+ru_{j-1}^n+\tau f_j ujn+1=αuj+1n+(12α)ujn+ruj1n+τfj ,取 n = 0 n=0 n=0 ,利用初值 u j 0 = φ j u_j^0=\varphi_j uj0=φj 和边值 u 0 n = u J n = 0 u_0^n=u_J^n=0 u0n=uJn=0 可以通过变形式算出 u j 1 u_j^1 uj1 ,同理下去可以算出 u j 2 . . . . u_j^2.... uj2....,此格式不需要求解线性方程组,所以此格式视为显式格式,但显式格式并不是无条件稳定的,只有当 α ≤ 1 / 2 \alpha\leq1/2 α1/2 时,格式才是稳定的通过泰勒公式与截断误差定义,我们能证明出此格式基于时间步长 τ \tau τ 是一阶收敛的(由于此文不是存粹数学文章,这里涉及到的一些结论不再过多深入,有兴趣的朋友可以参考相关文献)。

  • 向后差分格式(隐式格式)

u j n + 1 − u j n τ = a u j + 1 n + 1 − 2 u j n + 1 + u j − 1 n + 1 h 2 + f j \frac{u_j^{n+1}-u_j^n}{\tau}=a\frac{u_{j+1}^{n+1}-2u_j^{n+1}+u_{j-1}^{n+1}}{h^2}+f_j τujn+1ujn=ah2uj+1n+12ujn+1+uj1n+1+fj ,其中 j = 1 , 2 , . . . , J − 1 , n = 0 , 1 , . . . , N − 1 j=1,2,...,J-1,n=0,1,...,N-1 j=1,2,...,J1,n=0,1,...,N1 ,同理我们改写上式为: − α u j + 1 n + 1 + ( 1 + 2 α ) u j n + 1 − α u j − 1 n + 1 = u j n + τ f j -\alpha u_{j+1}^{n+1}+(1+2\alpha)u_j^{n+1}-\alpha u_{j-1}^{n+1}=u_j^n+\tau f_j αuj+1n+1+(1+2α)ujn+1αuj1n+1=ujn+τfj ,令 $n=0,1,2,… $,则可利用 u j 0 u_j^0 uj0 和边值确定 u j 1 u_j^1 uj1 ,利用 u j 1 u_j^1 uj1 和边值确定 u j 2 u_j^2 uj2 等,现在第 n + 1 n+1 n+1 层的值不能用第 n n n 层值明显表示,而是由线性方程组求解。由于变形式左端系数矩阵是严格对角占优的,所以方程肯定有解的,且该格式是无条件稳定的。同样此格式也是基于时间步长 τ \tau τ 一阶收敛的

  • 六点对称格式(Crank-Nicolson 格式)

即向前差分格式和向后差分格式做算术平均,即可得到CN格式如下:
u j n + 1 − u j n τ = a 2 [ u j + 1 n + 1 − 2 u j n + 1 + u j − 1 n + 1 h 2 + u j + 1 n − 2 u j n + u j − 1 n h 2 ] + f i \frac{u_j^{n+1}-u_j^n}{\tau}=\frac{a}{2}\left[ \frac{u_{j+1}^{n+1}-2u_j^{n+1}+u_{j-1}^{n+1}}{h^2}+\frac{u_{j+1}^n-2u_j^n+u_{j-1}^n}{h^2} \right]+f_i τujn+1ujn=2a[h2uj+1n+12ujn+1+uj1n+1+h2uj+1n2ujn+uj1n]+fi
其中 j = 1 , 2 , . . . , J − 1 , n = 0 , 1 , . . . , N − 1 j=1,2,...,J-1,n=0,1,...,N-1 j=1,2,...,J1,n=0,1,...,N1,同理上式我们可以改写为 − α 2 u j + 1 n + 1 + ( 1 + α ) u j n + 1 − α 2 u j − 1 n + 1 = r 2 u j + 1 n + ( 1 − α ) u j n + α 2 u j − 1 n + τ f j -\frac{\alpha}{2}u_{j+1}^{n+1}+(1+\alpha)u_j^{n+1}-\frac{\alpha}{2}u_{j-1}^{n+1}=\frac{r}{2}u_{j+1}^n+(1-\alpha)u_j^n+\frac{\alpha}{2}u_{j-1}^n+\tau f_j 2αuj+1n+1+(1+α)ujn+12αuj1n+1=2ruj+1n+(1α)ujn+2αuj1n+τfj ,利用 u j 0 u_j^0 uj0 和边值便可以逐层求解到 u j n u_j^n ujn同样 C N CN CN格式是隐式格式,无条件稳定的,由第 n 层计算第 n+1 层时,需要求解线性方程组,但此格式基于时间步长 τ \tau τ 能达到二阶收敛

三:代码实现

接下来我们通过python的面向对象编程,逐一实现上面的三种数值格式。

以下代码经过本人调试,没任何bug,直接复制即可,有兴趣需要的朋友别忘记点赞关注加收藏哦!!谢谢!!

##############导入相应模块
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

编写差分格式类如下:

class diff_schemes:

    def __init__(self,dt,dx,r,x,t):##定义内置初始化函数
        self.dt = dt
        self.dx = dx
        self.r = r
        self.x = x
        self.t = t

    def make_figure(self,matx,title_msg):###作图
        fig = plt.figure()
        ax = fig.gca(projection='3d')
        x, y = np.meshgrid(self.x, self.t)
        z = matx
        ax.plot_surface(x, y, z, cmap=cm.coolwarm, linewidth=0, antialiased=False)
        ax.set_xlabel('X Label')
        ax.set_ylabel('Y Label')
        ax.set_zlabel('Z Label')
        plt.title(title_msg)
        plt.show()

    def init_condition(self):##定义初值函数,这里选择混个三角函数
        return np.sin(self.x) + np.cos(self.x)  ###也可以选择其他函数如指数函数exp(x)等等

    def forward_diff_scheme(self):##向前差分格式(显式)
        matx = np.zeros([len(self.t),len(self.x)])###默认边值都为0
        matx[0,:] = ic
        matx[:,0] = 0
        matx[:,-1] = 0
        for i in range(1,len(self.t)):
            for j in range(1,len(self.x)-1):
                matx[i,j] = self.r * matx[i - 1,j - 1] + (1 - 2 * self.r) * matx[i - 1,j] + self.r * matx[i - 1,j + 1]
        print(matx)
        return matx

    def backward_diff_scheme(self):
        matx = np.zeros([len(self.t), len(self.x)])
        matx[0, :] = ic
        matx[:, 0] = 0
        matx[:, -1] = 0
        matxa = np.zeros([len(self.x) - 2,len(self.x) - 2 ])
        for j in range(len(self.x) - 2):
            matxa[j, j] = 1 + 2 * self.r
            if j >= 1:
                matxa[j - 1, j] = - self.r
                matxa[j, j - 1] = - self.r
        iv = ic[1:-1]
        matxa_t = np.linalg.inv(matxa)##求解线性方程组
        for i in range(1, len(self.t)):
            res = np.dot(matxa_t,iv)
            matx[i,1:-1] = res
            iv = res
        print(matx)
        return matx

    def CN_diff_scheme(self):
        r1 = 1 + self.r
        r2 = 1 - self.r
        matx = np.zeros([len(self.t), len(self.x)])
        matx[0, :] = ic
        matx[:, 0] = 0
        matx[:, -1] = 0
        matxp = np.zeros([len(self.x) - 2, len(self.x) - 2])
        matxq = np.zeros([len(self.x) - 2, len(self.x) - 2])
        for j in range(len(self.x) - 2):
            matxp[j, j] = r1
            matxq[j, j] = r2
            if j >= 1:
                matxp[j - 1, j] = - self.r / 2
                matxp[j, j - 1] = - self.r / 2
                matxq[j - 1, j] = self.r / 2
                matxq[j, j - 1] = self.r / 2
        iv = np.dot(matxq,ic[1:-1])
        matxa_t = np.linalg.inv(matxp)
        for i in range(1, len(self.t)):
            res = np.dot(matxa_t, iv)
            matx[i, 1:-1] = res
            iv = np.dot(matxq,res)
        print(matx)
        return matx

调用相关类中方法展示实验结果

dt = 0.01##时间步长
dx = 0.01##空间步长
a = 0.5##网格比的取值,且显式格式时a的取值不能大于0.5
X_array = np.arange(-2.5,2.5 + dx,dx)
T_array = np.arange(0,1 + dt,dt)
res = diff_schemes(dt,dx,a,X_array,T_array)
ic = res.init_condition()###初始条件
rfd = res.forward_diff_scheme()
rbd = res.backward_diff_scheme()
rcnd = res.CN_diff_scheme()

res.make_figure(rfd,'网格比a=%s时,显式格式求解'%a)
res.make_figure(rbd,'网格比a=%s时,隐式格式求解'%a)
res.make_figure(rcnd,'网格比a=%s时,CN格式求解'%a)

四:数值结果

1

图1

在这里插入图片描述

图2

在这里插入图片描述

图3

在这里插入图片描述

图4(显式格式求解值)

在这里插入图片描述

图5(隐式格式求解值)

在这里插入图片描述

图6(CN格式求解值)

从上面所有图中结果可知,三种格式最终在末端位置(时间末端和空间末端)的运算结果很接近(如果深入从收敛阶角度出发,其实CN格式的精度更高更接近真解,收敛速率最快)。

在这里插入图片描述

图7

当我们网格比取值大于0.5时,显式格式符合理论上的结果,明显不稳定收敛了,但两种隐式格式还是依旧很稳定,从图7很明显能发掘到这一点。

五:总结

本文主要是数值模拟实验类文章,也是学习数值求解偏微分方程的基础性文章。偏微分方程的理论是非常广且深,另外,它跟随机微分方程也有这千丝万缕的联系。如果有兴趣的小伙伴可以多多交流。

写作不易,望路过的朋友们点赞收藏加关注哈,在此表示感谢!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43577256/article/details/120781416