以下内容均可参考本人知乎文章添加链接描述和添加链接描述

有限差分法

有限差分法finite difference(FD)是求解微分方程的最为容易理解的方法,下面将针对几类常见的PDE来做一些具体的介绍。由于本人知识有限,关于误差分析和收敛性证明都不会介绍.
一维例子
我们以一个一维PDE的求解来介绍有限差分算法,这里给出精确解u^=x(1−x)exp⁡(x)\hat{u} = x(1-x)\exp(x)u^=x(1−x)exp(x).

{−uxx=f,x∈(0,1),f=exp⁡(x)(−x2−x+1),u(0)=0,u(1)=0.\left\{\begin{array}{l} -u_{xx} = f, x \in (0,1),\\ f = \exp(x)(-x^2 - x + 1),\\ u(0) = 0,u(1) = 0.\\ \end{array}\right.⎩⎨⎧​−uxx​=f,x∈(0,1),f=exp(x)(−x2−x+1),u(0)=0,u(1)=0.​

对区域[0,1][0,1][0,1]剖分,均匀分成NNN份,即x0=0<xi<xN=1x_0 = 0 < x_i < x_N = 1x0​=0<xi​<xN​=1,其中hx=1Nh_x = \frac{1}{N}hx​=N1​,现在要求ui=u(xi),1≤i≤N−1u_i=u(x_i),1 \leq i \leq N - 1ui​=u(xi​),1≤i≤N−1的取值.利用ui+1−2ui+ui−1hx2,1≤i≤N−1\frac{u_{i+1} - 2u_i + u_{i-1}}{h_x^2},1 \leq i \leq N - 1hx2​ui+1​−2ui​+ui−1​​,1≤i≤N−1可以近似代替uxx(xi)u_{xx}(x_i)uxx​(xi​),那么得到一个线性系统:

{−(ui+1−2ui+ui−1)=fihx2,1≤i≤N−1,u0=0,uN=0,Au=b.\left\{\begin{array}{l} -(u_{i+1} - 2u_i + u_{i-1}) = f_i h_x^2,1 \leq i \leq N - 1,\\ u_0 = 0,u_N = 0,\\ Au = b. \end{array}\right.⎩⎨⎧​−(ui+1​−2ui​+ui−1​)=fi​hx2​,1≤i≤N−1,u0​=0,uN​=0,Au=b.​

这里的b,ub,ub,u是一个N+1N + 1N+1维向量,其中b0=0,bN=0b_0 = 0,b_N=0b0​=0,bN​=0,其他部分bi=fihx2,1≤i≤N−1b_i = f_i h_x^2,1 \leq i \leq N - 1bi​=fi​hx2​,1≤i≤N−1,这里的A是一个(N+1)×(N+1)(N + 1)\times (N + 1)(N+1)×(N+1)的带状矩阵,满足A0,0=AN,N=1A_{0,0}=A_{N,N} = 1A0,0​=AN,N​=1,其余对角线元素Ai,i=2A_{i,i}=2Ai,i​=2,就是下面这个样子.

A=(100⋯0−12−1⋯0⋮⋮⋱⋮0⋯−12−100⋯01)A=\left(\begin{array}{ccccc} 1 & 0 & 0 & \cdots & 0 \\ -1 & 2 & -1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & \cdots & -1 & 2 & -1 \\ 0 & 0 & \cdots & 0 & 1 \end{array}\right)A=⎝⎜⎜⎜⎜⎜⎛​1−1⋮00​02⋮⋯0​0−1⋱−1⋯​⋯⋯⋮20​00−11​⎠⎟⎟⎟⎟⎟⎞​

FD求解泊松方程

Dirichlet边界条件
给定泊松方程如下,其中Ω=[0,1]2,x=(x,y)\Omega = [0,1]^2,\mathbf{x} = (x,y)Ω=[0,1]2,x=(x,y):

{−Δu=f,x∈Ω,u=u0,x∈∂Ω.\left\{\begin{array}{l} -\Delta u = f,\mathbf{x} \in \Omega,\\ u = u_0,\mathbf{x} \in \partial \Omega .\\ \end{array}\right.{−Δu=f,x∈Ω,u=u0​,x∈∂Ω.​

引入剖分M,N,hx=1M,hy=1NM,N,h_x = \frac{1}{M},h_y = \frac{1}{N}M,N,hx​=M1​,hy​=N1​,将区域离散化以后得到网格点(xi,yj),0≤i≤M,0≤j≤N(x_i,y_j),0 \leq i \leq M,0 \leq j \leq N(xi​,yj​),0≤i≤M,0≤j≤N,记ui,j=u(xi,yj)u_{i,j}=u(x_i,y_j)ui,j​=u(xi​,yj​),利用中心差分公式\ref{center}得到离散以后的系统,网格剖分如下图所示

{uxx(xi,yj)=ui+1,j−2ui,j+ui−1,jhx2+O(hx2),1≤i≤M−1,uyy(xi,yj)=ui,j+1−2ui,j+ui,j−1hy2+O(hy2),1≤j≤N−1.\left\{\begin{array}{l} u_{xx}(x_i,y_j) = \frac{u_{i + 1,j} - 2u_{i,j} + u_{i - 1,j}}{h_x^2} + O(h_x^2),1 \leq i \leq M - 1,\\ u_{yy}(x_i,y_j) = \frac{u_{i,j + 1} - 2u_{i,j} + u_{i,j-1}}{h_y^2} + O(h_y^2) , 1 \leq j \leq N - 1.\\ \end{array}\right.{uxx​(xi​,yj​)=hx2​ui+1,j​−2ui,j​+ui−1,j​​+O(hx2​),1≤i≤M−1,uyy​(xi​,yj​)=hy2​ui,j+1​−2ui,j​+ui,j−1​​+O(hy2​),1≤j≤N−1.​

{−ui+1,j−2ui,j+ui−1,jhx2−ui,j+1−2ui,j+ui,j−1hy2=fi,j,1≤i≤M−1,1≤j≤N−1,ui,j=u0(xi,yj),(xi,yj)∈∂Ω.\left\{\begin{array}{l} -\frac{u_{i + 1,j} - 2u_{i,j} + u_{i - 1,j}}{h_x^2} - \frac{u_{i,j + 1} - 2u_{i,j} + u_{i,j-1}}{h_y^2} = f_{i,j} ,1 \leq i \leq M - 1,1 \leq j \leq N - 1,\\ u_{i,j} = u_0(x_i,y_j),(x_i,y_j) \in \partial \Omega.\\ \end{array}\right.{−hx2​ui+1,j​−2ui,j​+ui−1,j​​−hy2​ui,j+1​−2ui,j​+ui,j−1​​=fi,j​,1≤i≤M−1,1≤j≤N−1,ui,j​=u0​(xi​,yj​),(xi​,yj​)∈∂Ω.​

通过上面的公式可以形成一个线性方程组Ax=b,A∈R(M+1)(N+1)×(M+1)(N+1)Ax = b,A \in R^{(M + 1)(N + 1)\times (M + 1)(N + 1)}Ax=b,A∈R(M+1)(N+1)×(M+1)(N+1),不过由于在实际操作过程中M,NM,NM,N很大,会导致hx2,hy2h_x^2,h_y^2hx2​,hy2​太大,为了避免矩阵AAA病态,我们可以在\ref{FDmat1}第一个公式两边同时乘一个hxhyh_xh_yhx​hy​,经过整理以后得到下面这个公式
{−hyhxui−1,j+(2hxhy+2hxhy)ui,j−hyhxui+1,j−hxhyui,j−1−hxhyui,j+1=fi,jhxhy,ui,j=u0(xi,yj),(xi,yj)∈∂Ω.\left\{\begin{array}{l} -\frac{h_y}{h_x}u_{i - 1,j} + (2\frac{h_x}{h_y} + 2\frac{h_x}{h_y})u_{i,j} - \frac{h_y}{h_x}u_{i + 1,j} - \frac{h_x}{h_y}u_{i,j - 1} - \frac{h_x}{h_y}u_{i,j + 1} = f_{i,j}h_x h_y,\\ u_{i,j} = u_0(x_i,y_j),(x_i,y_j) \in \partial \Omega.\\ \end{array}\right.{−hx​hy​​ui−1,j​+(2hy​hx​​+2hy​hx​​)ui,j​−hx​hy​​ui+1,j​−hy​hx​​ui,j−1​−hy​hx​​ui,j+1​=fi,j​hx​hy​,ui,j​=u0​(xi​,yj​),(xi​,yj​)∈∂Ω.​

有了上述公式以后,下面我们开始在python中实现A,bA,bA,b的构造,这里说明x=(u0,0,u0,1,…,u0,N,…,uM,0,uM,1,…,uM,N)x = (u_{0,0},u_{0,1},\ldots,u_{0,N},\ldots,u_{M,0},u_{M,1},\ldots,u_{M,N})x=(u0,0​,u0,1​,…,u0,N​,…,uM,0​,uM,1​,…,uM,N​).

图片展示了五点差分法构造矩阵的全过程,uuu在边界点的取值已知,所以当循环遇到边界点的索引,此时矩阵的对角元素设定为1.事实上,如果想缩小矩阵的规模,可以把已知量挪到右边,只针对位于网格点内部的函数值ui,ju_{i,j}ui,j​求解,形成一个(M−1)(N−1)×(M−1)(N−1)(M - 1)(N-1)\times(M - 1)(N - 1)(M−1)(N−1)×(M−1)(N−1)的矩阵,读者可以自己尝试.引入精确解u=log⁡(10(x+y)2+(x−y)2+0.5)u = \log(10(x + y)^2 + (x - y)^2 + 0.5)u=log(10(x+y)2+(x−y)2+0.5),然后形成对应的右端项和边界条件测试的结果如下所示:

代码参考添加链接描述
Neumann边界条件
给定泊松方程如下,其中Ω=[0,1]2,x=(x,y)\Omega = [0,1]^2,\mathbf{x} = (x,y)Ω=[0,1]2,x=(x,y):

{−Δu+u=f,x∈Ω,∂u∂n=u0,x∈∂Ω.\left\{\begin{array}{l} -\Delta u + u= f,\mathbf{x} \in \Omega,\\ \frac{\partial u}{\partial n} = u_0,\mathbf{x} \in \partial \Omega .\\ \end{array}\right.{−Δu+u=f,x∈Ω,∂n∂u​=u0​,x∈∂Ω.​

针对Neumann边界条件,泊松方程的设定和上面的Dirichlet\ref{poisson}不太一样,在控制方程中多了一项关于uuu的线性项,这个设定可以保证该方程有唯一解.如果没有这一项,假设u∗u^*u∗是精确解,那么会发现u∗+cu^* + cu∗+c也是精确解,其中ccc是任意常数.在控制方程的处理和上面Dirichlet几乎一样,离散化以后会得到下面这个公式:

{−hyhxui−1,j+(2hxhy+2hxhy+hxhy)ui,j−hyhxui+1,j−hxhyui,j−1−hxhyui,j+1=fi,jhxhy,∂u∂n(xi,yj)=u0(xi,yj),(xi,yj)∈∂Ω.\left\{\begin{array}{l} -\frac{h_y}{h_x}u_{i - 1,j} + (2\frac{h_x}{h_y} + 2\frac{h_x}{h_y} + h_x h_y)u_{i,j} - \frac{h_y}{h_x}u_{i + 1,j} - \frac{h_x}{h_y}u_{i,j - 1} - \frac{h_x}{h_y}u_{i,j + 1} = f_{i,j}h_x h_y,\\ \frac{\partial u}{\partial n}(x_i,y_j) = u_0(x_i,y_j),(x_i,y_j) \in \partial \Omega.\\ \end{array}\right.{−hx​hy​​ui−1,j​+(2hy​hx​​+2hy​hx​​+hx​hy​)ui,j​−hx​hy​​ui+1,j​−hy​hx​​ui,j−1​−hy​hx​​ui,j+1​=fi,j​hx​hy​,∂n∂u​(xi​,yj​)=u0​(xi​,yj​),(xi​,yj​)∈∂Ω.​

重点是在边界上的处理,方向导数放在四个边界上是有两个ux,uyu_x,u_yux​,uy​,因此这里需要逼近ux,uyu_x,u_yux​,uy​,可以使用一阶差分或者二阶差分和虚拟点来逼近,下面重点介绍虚拟点方法.

这里我们选取边界点(xi,y0),1≤i≤M−1(x_i,y_0),1 \leq i \leq M - 1(xi​,y0​),1≤i≤M−1举例子,注意在4个角需要做特殊处理.则会有

∂u∂n(xi,y0)=ui,1−ui,−12hy=u0(xi,y0),1≤i≤M−1.\frac{\partial u}{\partial n}(x_i,y_0) = \frac{u_{i,1} - u_{i,-1}}{2h_y} = u_0(x_i,y_0),1 \leq i \leq M - 1.∂n∂u​(xi​,y0​)=2hy​ui,1​−ui,−1​​=u0​(xi​,y0​),1≤i≤M−1.

然后代入方程中替换ui,−1=ui,1−2u0(xi,y0)hyu_{i,-1} = u_{i,1} - 2u_0(x_i,y_0)h_yui,−1​=ui,1​−2u0​(xi​,y0​)hy​即可,对应需要修改右端项bbb的定义.代码参考添加链接描述.
FD求解含时间项的泊松方程
引入方程定义:

{u(x,t)−Δu(x,t)=f(x,t),(x,t)∈Ω×[0,1],u(x,t)=g(x,t),(x,t)∈∂Ω×[0,1],u(x,0)=h(x),x∈Ω.\left\{\begin{array}{l} u(\mathbf{x},t)-\Delta u(\mathbf{x},t) = f(\mathbf{x},t),(\mathbf{x},t) \in \Omega \times [0,1],\\ u(\mathbf{x},t) = g(\mathbf{x},t),(\mathbf{x},t) \in \partial \Omega \times [0,1],\\ u(\mathbf{x},0) = h(\mathbf{x}),\mathbf{x} \in \Omega. \end{array}\right.⎩⎨⎧​u(x,t)−Δu(x,t)=f(x,t),(x,t)∈Ω×[0,1],u(x,t)=g(x,t),(x,t)∈∂Ω×[0,1],u(x,0)=h(x),x∈Ω.​

针对方程\ref{tpoisson}引入4种差分格式如下,为了方便记忆,定义:\
δxui,jn=ui+1,jn−2ui,jn+ui−1,jnhx2\delta_x u_{i,j}^n = \frac{u_{i + 1,j}^n - 2u_{i,j}^n + u_{i - 1,j}^n}{h_x^2}δx​ui,jn​=hx2​ui+1,jn​−2ui,jn​+ui−1,jn​​,
δyui,jn=ui,j+1n−2ui,jn+ui,j−1nhy2\delta_y u_{i,j}^n = \frac{u_{i,j + 1}^n - 2u_{i,j}^n + u_{i,j - 1}^n}{h_y^2}δy​ui,jn​=hy2​ui,j+1n​−2ui,jn​+ui,j−1n​​ \
Forward-Euler:

ui,jn+1−ui,jnτ−δxui,jn−δyui,jn=fi,jn+1.\frac{u_{i,j}^{n + 1} - u_{i,j}^{n}}{\tau} - \delta_x u_{i,j}^n - \delta_y u_{i,j}^n = f_{i,j}^{n + 1}.τui,jn+1​−ui,jn​​−δx​ui,jn​−δy​ui,jn​=fi,jn+1​.

Backward-Euler:

ui,jn+1−ui,jnτ−δxui,jn+1−δyui,jn+1=fi,jn+1.\frac{u_{i,j}^{n + 1} - u_{i,j}^{n}}{\tau} - \delta_x u_{i,j}^{n + 1} - \delta_y u_{i,j}^{n + 1} = f_{i,j}^{n + 1}.τui,jn+1​−ui,jn​​−δx​ui,jn+1​−δy​ui,jn+1​=fi,jn+1​.

BDF:

3ui,jn+1−4ui,jn+ui,jn−12τ−δxui,jn+1−δyui,jn+1=fi,jn+1.\frac{3u_{i,j}^{n + 1} - 4u_{i,j}^{n} + u_{i,j}^{n - 1}}{2\tau} - \delta_x u_{i,j}^{n + 1} - \delta_y u_{i,j}^{n + 1} = f_{i,j}^{n + 1}.2τ3ui,jn+1​−4ui,jn​+ui,jn−1​​−δx​ui,jn+1​−δy​ui,jn+1​=fi,jn+1​.

CN:

ui,jn+1−ui,jnτ−12(δxui,jn+1+δyui,jn+1+δxui,jn+δyui,jn)=fi,jn+1.\frac{u_{i,j}^{n + 1} - u_{i,j}^{n}}{\tau} - \frac{1}{2}(\delta_x u_{i,j}^{n + 1} + \delta_y u_{i,j}^{n + 1} + \delta_x u_{i,j}^{n} + \delta_y u_{i,j}^{n}) = f_{i,j}^{n + 1}.τui,jn+1​−ui,jn​​−21​(δx​ui,jn+1​+δy​ui,jn+1​+δx​ui,jn​+δy​ui,jn​)=fi,jn+1​.

有了这些格式以后,我们引入Un=(u0,0n,…,u0,Nn,…,uM,0n,…,uM,Nn)U^n = (u_{0,0}^{n},\ldots,u_{0,N}^{n},\ldots,u_{M,0}^{n},\ldots,u_{M,N}^{n})Un=(u0,0n​,…,u0,Nn​,…,uM,0n​,…,uM,Nn​),则上述4个格式可以理解为4个迭代的线性系统.
Forward-Euler:

Un+1=AUn+b.U^{n + 1} = A U^{n} + b.Un+1=AUn+b.

Backward-Euler:

AUn+1=BUn+b.AU^{n + 1} = B U^{n} + b.AUn+1=BUn+b.

BDF:

AUn+1=BUn+CUn−1+b.AU^{n + 1} = B U^{n} + C U^{n - 1} + b.AUn+1=BUn+CUn−1+b.

CN:

AUn+1=BUn+b.AU^{n + 1} = B U^{n} + b.AUn+1=BUn+b.

在代码的实现过程中,Forward-Euler不需要求解线性系统,但是这里需要对时间步长τ\tauτ和空间步长hx,hyh_x,h_yhx​,hy​做一个限制来满足稳定性条件,另外三个格式对于任意的时间空间步长都是稳定的,而上面提到的4个格式里面的矩阵需要自己根据离散格式来确定.这里仅仅针对BDF格式做一个具体的说明,其他的类似.
\subsubsection{BDF格式}
对于时间层,我们每隔τ\tauτ时间做了一次计算,UnU^nUn表示当时间层到了第n层的时候那一层的解.将BDF格式整理如下:

(3+4τ(1hx2+1hy2))ui,jn+1−2τhy2(ui,j+1n+ui,j−1n)−2τhx2(ui+1,jn+ui−1,jn)=4ui,jn−ui,jn−1+2fi,jn+1τ.\begin{aligned} &(3 + 4\tau(\frac{1}{h_x^2} + \frac{1}{h_y^2}))u_{i,j}^{n+1} - 2\frac{\tau}{h_y^2}(u_{i,j + 1}^n + u_{i,j - 1}^n) - 2\frac{\tau}{h_x^2}(u_{i + 1,j}^n + u_{i - 1,j}^n) \\ &= 4u_{i,j}^n - u_{i,j}^{n-1} + 2f_{i,j}^{n+1}\tau. \end{aligned}​(3+4τ(hx2​1​+hy2​1​))ui,jn+1​−2hy2​τ​(ui,j+1n​+ui,j−1n​)−2hx2​τ​(ui+1,jn​+ui−1,jn​)=4ui,jn​−ui,jn−1​+2fi,jn+1​τ.​

具体代码参考下面图片

在BDF代码实现中,计算U2U^2U2的时候需要U1,U0U^1,U^0U1,U0,其中U0U^0U0可以直接通过初始条件获得,U1U^1U1可以通过CN格式或者Backward-Euler格式获得.
这里我们取u=(x2+y2)exp⁡(t−t2)u=(x^2 + y^2)\exp(t-t^2)u=(x2+y2)exp(t−t2)来做算例测试BDF格式,这里只计算最后一层u(x,y,1)u(x,y,1)u(x,y,1)的误差.

区域分解算法
上面的FD把整个区域进行剖分,当剖分很细的时候,得到的线性系统维度很大,为了降低存储成本和计算成本,可以将区域划分为若干区域,在每个区域上求解,具体过程将以数学公式来说明.这种算法也称之为cauchy-schwarz算法.

首先需要把区域进行划分,先假设划分为两个区域Ω=Ω0⋃Ω1\Omega=\Omega_0 \bigcup \Omega_1Ω=Ω0​⋃Ω1​,其中Ω0=[0,0.5]×[0,1],ω1=[0.4,1]×[0,1]\Omega_0=[0,0.5]\times[0,1],\omega_1=[0.4,1]\times[0,1]Ω0​=[0,0.5]×[0,1],ω1​=[0.4,1]×[0,1],注意两个区域必须要有重叠overlap.做剖分的时候注意,为了避免麻烦,应该让边界线{0.4}×[0,1],{0.5}×[0,1]\{0.4\}\times [0,1],\{0.5\}\times [0,1]{0.4}×[0,1],{0.5}×[0,1]都位于网格线上,针对泊松方程引入两个子问题.

{−Δu1n+1=f,x∈Ω0,u1n+1=u0,x∈∂Ω0∩∂Ω,u1n+1=u2n,x∈∂Ω0∖∂Ω.\left\{\begin{array}{l} -\Delta u_{1}^{n+1} = f,\mathbf{x} \in \Omega_0,\\ u_{1}^{n+1} = u_0,\mathbf{x} \in \partial \Omega_0 \cap \partial \Omega,\\ u_{1}^{n+1} = u_{2}^n,\mathbf{x} \in \partial \Omega_0 \setminus \partial \Omega. \end{array}\right.⎩⎨⎧​−Δu1n+1​=f,x∈Ω0​,u1n+1​=u0​,x∈∂Ω0​∩∂Ω,u1n+1​=u2n​,x∈∂Ω0​∖∂Ω.​

{−Δu2n=f,x∈Ω1,u2n=u0,x∈∂Ω1∩∂Ω,u2n=u1n+1,x∈∂Ω1∖∂Ω.\left\{\begin{array}{l} -\Delta u_{2}^n = f,\mathbf{x} \in \Omega_1,\\ u_{2}^n = u_0,\mathbf{x} \in \partial \Omega_1 \cap \partial \Omega,\\ u_{2}^n = u_{1}^{n+1},\mathbf{x} \in \partial \Omega_1 \setminus \partial \Omega. \end{array}\right.⎩⎨⎧​−Δu2n​=f,x∈Ω1​,u2n​=u0​,x∈∂Ω1​∩∂Ω,u2n​=u1n+1​,x∈∂Ω1​∖∂Ω.​

则cauchy-schwarz算法的定义是:

上面有两个子问题,两个线性系统的区别在于右端项,因此可以提前构建好矩阵AAA,然后根据不同的右端项定义两个求解函数solve,具体过程可以参考下列图片:

如果将区域分成3个区域,比如说Ω0=[0,0.5]×[0,1],ω1=[0.4,0.8]×[0,1],ω1=[0.7,1]×[0,1]\Omega_0=[0,0.5]\times[0,1],\omega_1=[0.4,0.8]\times[0,1],\omega_1=[0.7,1]\times[0,1]Ω0​=[0,0.5]×[0,1],ω1​=[0.4,0.8]×[0,1],ω1​=[0.7,1]×[0,1],仍然要保证两两之间有overlap,过程与上面算法一样,代码可以参考添加链接描述

1:在上述方法中,最终的问题都被转化成求解一个线性系统Ax=bAx=bAx=b,这里本人调用的是python库numpy自带的线性求解器linalg.solve(A,b)linalg.solve(A,b)linalg.solve(A,b),如何设计G-S迭代方法求解,如何证明G-S迭代法对这类线性系统的收敛性.\par
2:线性系统\ref{FDmat2}是稀疏的,是否可以设计一种针对这类问题的带状问题进行求解,并且减少矩阵存储量.
3:在介绍FD求解含时泊松方程时,向前欧拉格式不稳定,如何确定合适的τ,hx,hy\tau,h_x,h_yτ,hx​,hy​使得该格式稳定.
4:在区域分解算法中,本人的划分都是针对一个维度,如果对区域做棋盘划分,这个算法该如何写,参考提示添加链接描述

有限元法

FD方法的特点是只能求解离散点的取值,如果想要知道不在网格点的函数值,就需要重新做剖分或者做插值得到,基于此有限元方法finite element(FE)发展起来.FE可以被理解为一种插值方法,还是以二维区域为例,将区域Ω\OmegaΩ进行剖分得到M×NM\times NM×N个小区域,在每个小区域上定义一个分片函数,然后根据网格点的取值进行插值,下面直接以数学公式来进行说明.

首先引入单元函数ψ(x,y){\psi}(x,y)ψ(x,y),对区域进行剖分以后得到(xi,yj)(x_i,y_j)(xi​,yj​),则会有φi,j=ψ(x−xihx,y−yjhy)\varphi_{i,j} = \psi(\frac{x - x_i}{h_x},\frac{y - y_j}{h_y})φi,j​=ψ(hx​x−xi​​,hy​y−yj​​),那么这样定义的基函数φi,j\varphi_{i,j}φi,j​就满足在第(i,j)(i,j)(i,j)个网格点上取值为1,在其他网格点上取值为0.
ψ⁡(x,y)={(1−x)∗(1−y),0≤x≤1,0≤y≤1,(1+x)∗(1−y),−1≤x≤0,0≤y≤1,(1+x)∗(1+y),−1≤x≤0,−1≤y≤0,(1−x)∗(1+y),0≤x≤1,−1≤y≤0.\operatorname{\psi}(x,y)=\left\{\begin{array}{ll} (1 - x)*(1 - y),0 \leq x \leq 1,0 \leq y \leq 1, \\ (1 + x)*(1 - y),-1 \leq x \leq 0,0 \leq y \leq 1, \\ (1 + x)*(1 + y),-1 \leq x \leq 0,-1 \leq y \leq 0, \\ (1 - x)*(1 + y),0 \leq x \leq 1,-1 \leq y \leq 0.\\ \end{array}\right. ψ(x,y)=⎩⎪⎪⎨⎪⎪⎧​(1−x)∗(1−y),0≤x≤1,0≤y≤1,(1+x)∗(1−y),−1≤x≤0,0≤y≤1,(1+x)∗(1+y),−1≤x≤0,−1≤y≤0,(1−x)∗(1+y),0≤x≤1,−1≤y≤0.​
于是我们的有限元近似解定义为,接下来的重点就是确定ui,ju_{i,j}ui,j​的取值.有了上面FD的介绍,其实可以先通过FD求出对应的ui,ju_{i,j}ui,j​,然后根据\ref{FE}求出数值解uhu_huh​,这个方法很容易实现.不过FE并非如此.
uh=∑i=0i=M∑j=0j=Nui,jφi,j.u_h = \sum_{i=0}^{i = M}\sum_{j=0}^{j = N}u_{i,j}\varphi_{i,j}.uh​=∑i=0i=M​∑j=0j=N​ui,j​φi,j​.
FE首先需要引入弱形式\ref{FEGalerkin},其中v∈Vh={v∣v∣∂Ω=0}v \in V_h=\{v| v|_{\partial \Omega} = 0\}v∈Vh​={v∣v∣∂Ω​=0}.

{a(u,v)=∮Ω∇u⋅∇vdxdy,f(v)=∮Ωfvdxdy.\left\{\begin{array}{l} a(u,v) = \oint_{\Omega} \nabla u \cdot \nabla v dxdy,\\ f(v) = \oint_{\Omega} fv dxdy. \end{array}\right.{a(u,v)=∮Ω​∇u⋅∇vdxdy,f(v)=∮Ω​fvdxdy.​

根据方程\ref{FE},我们知道这个公式需要求解(M+1)(N+1)(M + 1)(N + 1)(M+1)(N+1)个未知数,在边界上的取值已知,还需要求出在区域内部的取值,因此取vi,j=φi,j,1≤i≤M−1,1≤j≤N−1v_{i,j} = \varphi_{i,j},1 \leq i \leq M - 1,1 \leq j \leq N - 1vi,j​=φi,j​,1≤i≤M−1,1≤j≤N−1带入方程\ref{FEGalerkin}就可以得到(M+1)(N+1)(M + 1)(N + 1)(M+1)(N+1)个方程,这里面有(M−1)(N−1)(M - 1)(N - 1)(M−1)(N−1)个积分需要计算.但是可以发现由于φi,j\varphi_{i,j}φi,j​在大部分区域都是0,因此可以采取扫描单元的做法来简化计算.\
重点:代入uh,v=φi,ju_h,v = \varphi_{i, j}uh​,v=φi,j​得到线性系统得到公式\eqref{intergral}.\
{a(uh,φi,j)=f(φi,j),1≤i≤M−1,1≤j≤N−1,uh(xi,yj)=u0(xi,yj),(xi,yj)∈∂Ω.\left\{\begin{array}{l} a(u_h,\varphi_{i, j}) = f(\varphi_{i, j}),1 \leq i \leq M - 1, 1 \leq j \leq N -1,\\ u_h(x_i,y_j) = u_0(x_i,y_j),(x_i,y_j) \in \partial \Omega. \end{array}\right.{a(uh​,φi,j​)=f(φi,j​),1≤i≤M−1,1≤j≤N−1,uh​(xi​,yj​)=u0​(xi​,yj​),(xi​,yj​)∈∂Ω.​
由于uhu_huh​和φi,j\varphi_{i, j}φi,j​的特殊性,区域Ω\OmegaΩ上的积分只有一小部分非0.
{∫Ω(∑j=0j=Nui,j∇φi,j)∇φi^,j^dΩ=∫Ωfφi^,j^dΩ,∑m=0MN−1∫Ωm(∑i=0i=M∑j=0j=Nui,j∇φi,j)∇φi^,j^dΩ=∑m=0MN−1∫Ωmfφi^,j^dΩ.\left\{\begin{array}{l} \int_{\Omega}(\sum_{j=0}^{j = N}u_{i,j}\nabla \varphi_{i,j})\nabla \varphi_{\hat{i},\hat{j}} d\Omega = \int_{\Omega} f \varphi_{\hat{i},\hat{j}}d\Omega,\\ \sum_{m=0}^{MN-1}\int_{\Omega_m}(\sum_{i=0}^{i = M}\sum_{j=0}^{j = N}u_{i,j}\nabla \varphi_{i,j})\nabla \varphi_{\hat{i},\hat{j}} d\Omega = \sum_{m=0}^{MN-1} \int_{\Omega_m} f \varphi_{\hat{i},\hat{j}}d\Omega. \end{array}\right.{∫Ω​(∑j=0j=N​ui,j​∇φi,j​)∇φi^,j^​​dΩ=∫Ω​fφi^,j^​​dΩ,∑m=0MN−1​∫Ωm​​(∑i=0i=M​∑j=0j=N​ui,j​∇φi,j​)∇φi^,j^​​dΩ=∑m=0MN−1​∫Ωm​​fφi^,j^​​dΩ.​
当(xi,yj),(xi^,yj^)(x_i,y_j),(x_{\hat{i}},y_{\hat{j}})(xi​,yj​),(xi^​,yj^​​)同时位于Ωm\Omega_mΩm​的顶点,才有∫Ωm∇φi,j∇φi^,j^≠0\int_{\Omega_m}\nabla \varphi_{i,j} \nabla \varphi_{\hat{i},\hat{j}} \neq 0∫Ωm​​∇φi,j​∇φi^,j^​​​=0.
∫Ωm(∑i=0i=M∑j=0j=Nui,j∇φi,j)∇φi^,j^dΩ=∫Ωm∑∣i−i^∣≤1,∣j−j^∣≤1∇φi,j∇φi^,j^dΩ.\int_{\Omega_m}(\sum_{i=0}^{i = M}\sum_{j=0}^{j = N}u_{i,j}\nabla \varphi_{i,j})\nabla \varphi_{\hat{i},\hat{j}} d\Omega = \int_{\Omega_m} \sum_{|i-\hat{i}|\leq 1,|j - \hat{j}|\leq 1} \nabla \varphi_{i,j} \nabla \varphi_{\hat{i},\hat{j}} d\Omega.∫Ωm​​(∑i=0i=M​∑j=0j=N​ui,j​∇φi,j​)∇φi^,j^​​dΩ=∫Ωm​​∑∣i−i^∣≤1,∣j−j^​∣≤1​∇φi,j​∇φi^,j^​​dΩ.
因此才有下面的公式成立.
ai,j=∑Nodes[i],Nodes[j]∈Tm∮Tm∇ϕi∇ϕjdxdy+∑Nodes[i],Nodes[j]∈∂Ωm∮∂Ωmβϕiϕjdsa_{i,j}= \sum_{Nodes[i],Nodes[j] \in T_{m}} \oint_{T_{m}} \nabla \phi_{i}\nabla \phi_{j}dxdy + \sum_{Nodes[i],Nodes[j] \in \partial \Omega_{m}} \oint_{\partial \Omega_{m}} \beta \phi_{i}\phi_{j}dsai,j​=∑Nodes[i],Nodes[j]∈Tm​​∮Tm​​∇ϕi​∇ϕj​dxdy+∑Nodes[i],Nodes[j]∈∂Ωm​​∮∂Ωm​​βϕi​ϕj​ds
其中Nodes$[i] 表示在整体排序中的第表示在整体排序中的第表示在整体排序中的第i个网格点,个网格点,个网格点,T_m表示第m个单元,区域一共有表示第m个单元,区域一共有表示第m个单元,区域一共有M\times N$个单元.
类似地,我们可以给出右端项,也就是所谓的载荷向量的b的定义
bi=∑Nodes[i]∈Tm∮Tmfϕidxdy+∑self.Nodes[i]∈∂Ωm∮∂Ωmβgϕidsb_{i}= \sum_{Nodes[i] \in T_{m}} \oint_{T_{m}} f\phi_{i}dxdy + \sum_{self.Nodes[i] \in \partial \Omega_{m}} \oint_{\partial \Omega_{m}} \beta g\phi_{i}dsbi​=∑Nodes[i]∈Tm​​∮Tm​​fϕi​dxdy+∑self.Nodes[i]∈∂Ωm​​∮∂Ωm​​βgϕi​ds

在给出这些矩阵元素和右端项元素的定义计算以后,我们就必须要解决积分问题,我们需要对区域进行离散化处理,选取有限个点作为积分节点,根据数值逼近的原理,我们知道在区域[0,1]2[0,1]^2[0,1]2中,我们只要选取以下4个点A1=(1−3/32,1−3/32),A2=(1+3/32,1−3/32),A3=(1+3/32,1+3/32),A4=(1−3/32,1+3/32)A_1 = (\frac{1 - \sqrt{3}/3}{2},\frac{1 - \sqrt{3}/3}{2}),A_2 = (\frac{1 + \sqrt{3}/3}{2},\frac{1 - \sqrt{3}/3}{2}),A_3 = (\frac{1 + \sqrt{3}/3}{2},\frac{1 + \sqrt{3}/3}{2}),A_4 = (\frac{1 - \sqrt{3}/3}{2},\frac{1 + \sqrt{3}/3}{2})A1​=(21−3​/3​,21−3​/3​),A2​=(21+3​/3​,21−3​/3​),A3​=(21+3​/3​,21+3​/3​),A4​=(21−3​/3​,21+3​/3​)作为积分点,
∮[0,1]2fdxdy=0.25∗∑i=14(f(Ai))\oint_{[0,1]^2} f dxdy = 0.25*\sum_{i = 1}^{4}(f(A_i))∮[0,1]2​fdxdy=0.25∗∑i=14​(f(Ai​))
那么上述积分近似就有2阶代数精度,即对于1,x,y,x2,y2,xy1,x,y,x^2,y^2,xy1,x,y,x2,y2,xy这些函数的线性组合,上述积分等式恒成立。本人代码里面FENET里面定义了self.gp_pos=[1−3/32,1−3/32]self.gp\_{}pos = [\frac{1 - \sqrt{3}/3}{2},\frac{1 - \sqrt{3}/3}{2}]self.gp_pos=[21−3​/3​,21−3​/3​]以便于在每个积分单元采取这样4个高斯积分节点。在这些定义完以后,本人定义了Int_basic_basicInt\_{}basic\_{}basicInt_basic_basic函数表示在某积分单元上两个基函数之间梯度的积分,定义了Int_F_basicInt\_{}F\_{}basicInt_F_basic表示在某积分单元上f和某基函数的积分,定义Bou_basic_basicBou\_{}basic\_{}basicBou_basic_basic表示在某Neumann边界线段单元上两个基函数的线积分,定义Bou_Neu_basicBou\_{}Neu\_{}basicBou_Neu_basic表示在Neumann边界上函数g和基函数在该线段单元上的线积分,代码参考添加链接描述

深度学习求解PDE

传统方法FD,FE最大的问题在于当坐标是高维向量的时候,无法对高维空间进行剖分,这个会造成维度灾难curse of dimensionality.而这些年深度学习的兴起给我们提供了一个新的方法来求解PDE,下图\ref{FNN}就是一个典型的全连接网络,该网络接受4个维度,输出一个标量,可以这么理解FNN(x)FNN(\mathbf{x})FNN(x)定义了一个从R4R^4R4到R1R^1R1的函数,这个函数是通过激活函数和线性组合得到的,使用深度学习求解PDE的核心就是选择适当的参数W,bW,bW,b使得FNN(x)FNN(\mathbf{x})FNN(x)尽可能逼近精确解.

一个通用的全连接网络NN表达式如下\ref{FNNform}所示,在图片\ref{FNN}中有两个隐藏层,因此n=2n=2n=2,网络搭建的细节可以参考文献\cite{2nd}.
{fi(s)=ϕ(Wi,2⋅ϕ(Wi,1s+bi,1)+bi,2),z=fn∘fn−1∘…∘f0(x),y(x)=Wz+b.\left\{\begin{array}{l} f_{i}(s)=\phi\left(W_{i, 2} \cdot \phi\left(W_{i, 1} s+b_{i, 1}\right)+b_{i, 2}\right),\\ z=f_n \circ f_{n-1}\circ \ldots \circ f_0(\mathbf{x}),\\ y(\mathbf{x}) = W z + b. \end{array}\right.⎩⎨⎧​fi​(s)=ϕ(Wi,2​⋅ϕ(Wi,1​s+bi,1​)+bi,2​),z=fn​∘fn−1​∘…∘f0​(x),y(x)=Wz+b.​
正如上面提到的,利用深度学习求解PDE的核心在于选择合适的权重和偏置,我们以θ\thetaθ来表示神经网络NN中的权重和偏置,这些也被称为NN的参数,我们需要根据PDE来选择等价的优化问题,然后利用优化中的迭代算法来迭代更新参数θ\thetaθ,下面我们引入几个基本的算法来做介绍,测试函数都是u=log⁡(10(x+y)2+(x−y)2+0.5)u=\log(10(x+y)^2 + (x - y)^2 + 0.5)u=log(10(x+y)2+(x−y)2+0.5).
Deep Ritz

对于泊松方程,利用极小位能原理可以把PDE转化为等价的变分问题
{min⁡u∫Ω∣∇u∣2−2fudΩ,subject to u=u0,x∈∂Ω.\left\{\begin{aligned} &\min_{u} \int_{\Omega}|\nabla u|^2 - 2fu d\Omega, \\ &\text { subject to } u = u_0,\mathbf{x} \in \partial \Omega. \end{aligned}\right.⎩⎪⎨⎪⎧​​umin​∫Ω​∣∇u∣2−2fudΩ, subject to u=u0​,x∈∂Ω.​
我们需要根据变分问题\ref{Ritz}来更新参数,首先就需要把积分离散化,我们将在区域Ω\OmegaΩ采样nIn_InI​个点{xi}i=0nI−1\{\mathbf{x}_i\}_{i=0}^{n_I - 1}{xi​}i=0nI​−1​作为积分点.下面我们将详细介绍利用python如何实现deep ritz方法:

第一:我们需要对区域Ω\OmegaΩ进行离散化,需要在内部采集样本点X={xi}i=0nI−1X = \{\mathbf{x}_i\}_{i=0}^{n_I - 1}X={xi​}i=0nI​−1​,从上面公式中可以发现还需要准备好函数f(X)f(X)f(X)数据集,采样参考以下图片.

第二:我们需要在区域边界∂Ω\partial \Omega∂Ω进行采样,得到边界上的样本点X={xi}i=0nB−1X=\{\mathbf{x}_i\}_{i=0}^{n_B - 1}X={xi​}i=0nB​−1​,以及u0(X)u_0(X)u0​(X).

第三:我们需要搭建全连接网络,pytorch自带的torch.nn.Linear来作为每一层的初始参数,搭建好一个输入节点为2,输出节点为1的Net.

第四:我们需要根据网络和区域内部边界样本点和\ref{Ritz}定义一个损失函数,对于这种约束优化问题,我们可以采取惩罚函数方法来定义一个损失函数\ref{DRM},其中u(θ)u(\theta)u(θ)表示FNN的预测解,∇u(θ)\nabla u(\theta)∇u(θ)通过调用pytorch中的自动求导函数torch.autograd.grad函数实现,损失函数以及一部分的训练过程参考以下图片.
{L=LI+βLB,LI=1nI∑i=0nI−1∣∇u(θ)i∣2−2fiu(θ)i,LB=1nB∑i=0nB−1(u(θ)−u0)i2.\left\{\begin{aligned} &L = L_I + \beta L_B, \\ &L_I =\frac{1}{n_I}\sum_{i=0}^{n_I - 1}|\nabla u(\theta)_i|^2 - 2f_i u(\theta)_i,\\ &L_B = \frac{1}{n_B}\sum_{i=0}^{n_B - 1} (u(\theta) - u_0)_i^2. \end{aligned}\right.⎩⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎧​​L=LI​+βLB​,LI​=nI​1​i=0∑nI​−1​∣∇u(θ)i​∣2−2fi​u(θ)i​,LB​=nB​1​i=0∑nB​−1​(u(θ)−u0​)i2​.​

第五:有了损失函数以后,我们需要对网络Net的参数做迭代优化,比如最简单的梯度下降法:
θk+1=θk−c∇L(θk)\theta^{k+1}=\theta^k - c \nabla L(\theta^k)θk+1=θk−c∇L(θk)
对于大部分人来说,手动求出∇L(θk)\nabla L(\theta^k)∇L(θk)极为困难,幸好pytorch自带这个功能,只需要调用L.backwad()函数就可以得到,并且调用pytorch自带的优化器比如说torch.optim.Adam(Net.parameters(),lr)就可以自动实现参数的更新,lr是步长,也称learning rate,参考论文,代码参考添加链接描述.

考虑该变分问题,可以发现这种方法有几个特点,首先这个问题只需要利用一阶梯度信息,其次这个问题不需要额外增加测试函数,正是这两点,使得Ritz方法非常适合求解高维PDE。但是另一方面,如果使用惩罚函数方法求解该优化问题,损失函数的最优值不见得是0,此时优化过程难以控制。因此如果可以寻找到一种方法,使得近似解自动满足边界条件,就可以避免惩罚项的出现。

Deep Nitsche
为了进一步提高精度,有的论文修改了针对泊松方程\ref{poisson}的变分问题,得到了一个Deep Nitsche Method,和Deep Ritz唯一的区别在于变分问题不一样,其他的区域采样或者训练如出一辙,细节参考论文,代码参考添加链接描述
Galerkin
Galerkin的特点,只需要利用pytorch对模型求一阶梯度(可以大幅度降低计算时间和存储成本,相比于PINN而言,PINN需要额外存储几个梯度数组),但是必须要提前准备好测试函数数据,这个对数学功底要求很高。以及,Galerkin仍然不能用来求解高维PDE,原因在于高维空间中的测试函数 vvv 是定义在高维超立方体的函数,这个定义一定需要利用for循环定义,而且高维超立方体的采样不容易。
Galerkin原理是从另一个角度来对泊松方程进行降阶处理,具体参考公式
{∫Ω∇u⋅∇v−fvdΩ=0,subject to u=u0,x∈∂Ω,v∈Vh={v∣v∣∂Ω=0}.\left\{\begin{aligned} &\int_{\Omega} \nabla u \cdot \nabla v - f v d \Omega = 0, \\ &\text { subject to } u = u_0,\mathbf{x} \in \partial \Omega,\\ &v \in V_h = \{v | v |_{\partial \Omega} = 0\}. \end{aligned}\right.⎩⎪⎪⎪⎨⎪⎪⎪⎧​​∫Ω​∇u⋅∇v−fvdΩ=0, subject to u=u0​,x∈∂Ω,v∈Vh​={v∣v∣∂Ω​=0}.​
在代码的实现过程中,除了采集样本点XXX和准备右端项f(X)f(X)f(X)以外,这里还需要额外准备好v(X),∇v(X)v(X),\nabla v(X)v(X),∇v(X),在本人的代码实现中,vvv取得是前面有限元提到的单元函数φi,j\varphi_{i,j}φi,j​,值得注意的是,此时我们需要选取尽可能多个测试函数vvv带入方程\ref{DGM}才能得到好的效果,针对测试函数的选取,又可以有多种策略.
特别注意,Galerkin对数学功底要求很大的另一个原因在于推导弱形式,上面这个二维泊松方程的弱形式只需要利用二维积分的格林公式即可,但是我特意没用含时间的热方程举例子,原来就在于即使是那种简单例子,弱形式的推导就已经有难度了。下面罗列一个NS方程的例子.

上面这个NS方程的弱形式就连很多数学系本科的同学也很难推导(此时反而利用PINN会更加方便)。PINN和Galerkin都有一个特点,就是损失函数直接描述了PDE的求解情况,根据损失函数的定义我们知道,神经网络的训练就是尽可能让损失函数接近于0,和0越接近,说明模型训练的效果越好。

PFNN
在论文中,以区域内点为中心构造了φi,j,1≤M−1,1≤j≤N−1\varphi_{i,j},1 \leq M - 1,1 \leq j \leq N - 1φi,j​,1≤M−1,1≤j≤N−1的(M−1)(N−1)(M - 1)(N - 1)(M−1)(N−1)个测试函数,然后代入方程\ref{DGM}来构造损失函数,同时使用length factor和另一个网络将区域内部和边界分开处理取得了不错的效果,损失函数参考\ref{lossDGM},其中由于φi,j\varphi_{i,j}φi,j​的特殊性质,Li,jL_{i,j}Li,j​的积分计算可以限制在以(xi,yj)(x_i,y_j)(xi​,yj​)为中心的周围4个单元上,这个可以简化积分的计算.
{L=∑i=1M−1∑j=1N−1Li,j+βLB,Li=(∫Ω∇u⋅∇φi,j−fφi,jdΩ)2,LB=1nB∑i=0nB−1(u(θ)−u0)i2.\left\{\begin{aligned} &L = \sum_{i=1}^{M-1} \sum_{j=1}^{N-1} L_{i,j} + \beta L_B, \\ &L_i = (\int_{\Omega} \nabla u \cdot \nabla \varphi_{i,j} - f \varphi_{i,j} d \Omega)^2,\\ &L_B = \frac{1}{n_B}\sum_{i=0}^{n_B - 1} (u(\theta) - u_0)_i^2. \end{aligned}\right.⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​​L=i=1∑M−1​j=1∑N−1​Li,j​+βLB​,Li​=(∫Ω​∇u⋅∇φi,j​−fφi,j​dΩ)2,LB​=nB​1​i=0∑nB​−1​(u(θ)−u0​)i2​.​
代码参考添加链接描述
\subsubsection{WAN}
对抗神经网络求解泊松方程的核心是利用两个网络,NuN_uNu​模拟精确解uuu,NvN_vNv​模拟测试函数vvv,在训练过程中需要不断调整网络NvN_vNv​的参数使得vvv尽量填充整个VhV_hVh​空间.假设θ\thetaθ表示网络NuN_uNu​的参数,η\etaη表示网络NvN_vNv​的参数,那么WAN的核心思想可以理解为求解下列优化问题\ref{WAN},其中LLL的定义类似于:
max⁡ηmin⁡θL.\max_{\eta}\min_{\theta} L.maxη​minθ​L.
这里需要注意,为了使得v∈Vhv \in V_hv∈Vh​,我们需要在网络NvN_vNv​的基础上乘一个length factor,与此同时这两个网络需要交替训练,具体细节参考论文和代码添加链接描述
PINN
physics informed neural network最早出现于论文,核心在于下面这个二阶损失函数\ref{lossPINN},里面的梯度也是调用pytorch的自动求导功能完成的,这个损失函数非常直观,事实上,根据本人的数值实验,在求解二维泊松方程这类问题上,PINN的效果远比上面提到的方法要好,PINN的代码实现和上面的如出一辙,这里不赘述,代码参考添加链接描述.
{L=LI+βLB,LI=1nI∑i=0nI−1(−Δu−f)i2,LB=1nB∑i=0nB−1(u(θ)−u0)i2.\left\{\begin{aligned} &L = L_I + \beta L_B, \\ &L_I = \frac{1}{n_I}\sum_{i=0}^{n_I - 1}(-\Delta u - f)_i^2,\\ &L_B = \frac{1}{n_B}\sum_{i=0}^{n_B - 1} (u(\theta) - u_0)_i^2. \end{aligned}\right.⎩⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎧​​L=LI​+βLB​,LI​=nI​1​i=0∑nI​−1​(−Δu−f)i2​,LB​=nB​1​i=0∑nB​−1​(u(θ)−u0​)i2​.​

PINN的优势在于损失函数的形成非常直观,如果使用高阶优化器可以得到非常好的效果。PINN的缺点在于不能求解高维PDE,原来在于自动求导的时候需要求二阶导数,想象一下,如果问题是一个100维的,求解一阶导数的时候只需要利用下面这一行代码就可以解决。

u_x, = torch.autograd.grad(u, inset.X, create_graph=True, retain_graph=True,grad_outputs=torch.ones(inset.size,1))

但是,如果求解二阶导数,就必须要使用for循环,在形成损失函数的时候出现这个for循环将会导致优化过程特别缓慢,没有特别意义。

u_xx = torch.zeros(100,N)
for i in range(100):
u_xx[i:i + 1,:], = torch.autograd.grad(u_x[i:i + 1,:], inset.X, create_graph=True, retain_graph=True,grad_outputs=torch.ones(inset.size,1))

parametric PDE
仍然考虑泊松方程,不过这里的右端项f=f(x,μ),μ∈Pf = f(\mathbf{x},\mathbf{\mu}),\mathbf{\mu} \in \mathcal{P}f=f(x,μ),μ∈P,现在需要求解对于不同参数μ\mathbf{\mu}μ的精确解,我们以
u(x,y,μ)=exp⁡(−(x−μ1)2+(y−μ2)22μ32)u(x,y,\mathbf{\mu})=\exp(-\frac{(x - \mu_1)^2 + (y - \mu_2)^2}{2\mu_3^2})u(x,y,μ)=exp(−2μ32​(x−μ1​)2+(y−μ2​)2​)
为例子,其中μ=(μ1,μ2,μ3)∈[0,1]×[0,1]×[0.1,1]\mathbf{\mu}=(\mu_1,\mu_2,\mu_3) \in [0,1]\times[0,1]\times[0.1,1]μ=(μ1​,μ2​,μ3​)∈[0,1]×[0,1]×[0.1,1].对于这个问题,其实做法和上面的泊松方程处理类似,可以把uuu理解为在一个5维空间的函数,也就是说在求解过程中,只需要在采样过程将原来的二维空间采样修改成现在的5维空间采样,把空间坐标和参数都当成输入传进网络就可以得到一个含参问题的解.引入网络Net接收(x,y,μ1,μ2,μ3)(x,y,\mu_1,\mu_2,\mu_3)(x,y,μ1​,μ2​,μ3​)为输入,输出对应的解即可,网络搭建参考\ref{paNN},这里为了简单放了一个二维参数空间的FNN示意图,代码参考添加链接描述,求解结果如下,参考论文

代码说明

为了更好理解,这里我们将针对一个典型的PINN求解含参PDE问题代码做一个细致的说明,代码参考添加链接描述

第一步:导入相关的库,torch是用于神经网络搭建和训练的库,安装参考官方说明添加链接描述,matplotlib用于可视化,用法比较复杂,本人也需要百度才能知道如何针对需求使用命令,time用于计时,numpy是python最主要的数学库,注意最后那个bfgs是本人课题组自己编写的一个优化器,读者可以注释掉,最后优化的时候用torch自带的LBFGS优化器代替.

第二步:定义代码的运行设备device,这里本人选择GPU也就是cuda,读者需要自己确定自己电脑或者服务器是否有GPU,其次定义精确解UU和右端项FF,UU里面的order表示精确解的阶数,比如说[0,1][0,1][0,1]表示uyu_yuy​,bounds表示求解区域和参数空间.

第三步:定义INSET,BDSET,TESET这三个类,分别表示内点集合,边界点集合,测试集集合,其中还会封装一些跟样本点有关的数据进去,比如说右端项.

第四步:搭建网络,这个Net类接受两个参数,layer表示网络的层数和每层的神经元个数,dtype表示网络的数据类型,我后面取的是double,根据我的经验,数据精度高得到的效果也会更好一些,当然了,这个也更消耗内存.

第五步:修改数据类型,上面的INSET那三个类里面的数据默认都是float,为了和Net数据类型匹配,这里需要把INSET那几个类里面的数据类型进行修改,比如说x=x.type(dtype)x = x.type(dtype)x=x.type(dtype)就可以把xxx从float变成dtype一样的类型,然后为了后面训练的时候加速,我们一开始就提出要把代码送到GPU上训练,但是数据一开始默认在CPU上,因此我们还需要写一个函数把所有的数据搬运到GPU,为了训练完以后数据可以回到CPU,这里相应地也要定义一个函数把数据从GPU搬运回CPU.

第六步:定义损失函数和训练函数,损失函数之前讲过,这里不赘述,主要介绍一下训练函数的定义.训练需要优化器optim,我一般都取LBFGS或者我们课题组自己写的BFGS,如果使用Adam优化器,训练过程的核心就是里面注释掉的代码,但是BFGS或者LBFGS的核心就是closure这个函数.这里做一些解释,optim.zero_\_{}_grad表示清除优化器里面存储的梯度,防止每一次的梯度进行叠加,loss.backward表示反向传播,这里会自动计算损失函数关于网络参数W,bW,bW,b的梯度,optim.step是负责根据优化器来对参数W,bW,bW,b做更新,三者缺一不可,等到这三个结束以后还需要重新计算一下loss,这里本人不是特别理解,但是本人的实验指出这一步很重要,如果注释掉,结果不对.

最后就是对上面定义的函数,网络,类进行实例化和调用,然后一切就结束了.

总结

上述提到的几种方法各有特点,这里对这些方法做一个总结.

神经网络方法求解PDE的困难和弊端
1:神经网络方法求解PDE缺乏收敛性保证。通过上面的介绍,我们可以这么理解,神经网络求解PDE是把原始问题转化为一个优化问题,然后不断通过更新神经网络的参数来达到min loss function的做法,比如说PINN和Galerkin思想,神经网络形成的损失函数只要不断接近0,那么说明神经网络学习的越好。但是现在有一个问题,会不会出现一种情况,形成了一个损失函数,不管怎么更新网络的参数,损失函数都维持在一个较高的水平呢?或者另一种情况,神经网络需要花大量时间训练才能把损失函数降到一个较低的水平。下面我们看一个障碍流问题的例子。

上面这个就是计算区域,这个问题描述了流体从入口进入管道,管道内部有一个障碍,然后现在想预测流体在出口的流速。这个问题可以使用PINN的思想,先采集样本点,然后搭建网络,优化损失函数,下面先给出这个问题的一个参考解,这个解是通过OpenFoam求出来的。

本人训练过程就发现,这个问题的损失函数特别难下降,具体可以参考下面这个图片,虽然原理在那摆着,但是这个问题却难以求解。

本人总结了一句话:PINN或者是Galerkin这类神经网络方法,都只能求解简单的PDE,对于简单的PDE,往往可以得到优于传统数值方法的效果,但是训练时间绝对更长。对于复杂的PDE,比如说NS方程,尤其是带有复杂边界的问题,这类简单的NN方法无法得到多好的效果,但是另一方面,传统数值方法求解复杂的PDE也同样需要消耗大量的计算精力。

个人以为NN求解PDE的优势应该在于求解高维的简单的PDE,比如说高维的Dirichlet边界泊松方程,这种方程由于计算区域维度更高,所以传统数值方法很容易发生维度灾难,但是神经网络方法却可以通过蒙特卡洛等方法对计算区域进行采样,轻而易举求解。

NN求解PDE目前更多还是应用于科研领域,对于实际应用目前难以推广,一个非常大的原因就是NN求解PDE没有收敛性的理论保证,对于PDE问题,NN就像一个炼丹炉,谁也不知道这个问题到底会求解成什么样子。反而是传统数值方法(FD,FE),虽然求解PDE很消耗时间,计算复杂度也高,但是这类方法的理论收敛经过几十年的研究论证,已经可以做到在求解问题之前就推断出计算结果大概可以到什么程度,这一点对于应用非常重要。

PDE的数值解法(有限元,有限差分法)综合介绍相关推荐

  1. 偏微分方程数值解法python_基于python求解偏微分方程的有限差分法资料

    基于python求解偏微分方程的有限差分法资料 Computer Era No. 11 2016 0 引言 在数学中, 偏微分方程是包含多变量和它们的偏 导数在内的微分方程.偏微分方程通常被用来求解 ...

  2. [常微分方程的数值解法系列五] 龙格-库塔(RK4)法

    龙格-库塔法 简介 基本思想 具体方法 一阶 二阶 求解参数 特殊二阶 三阶 高阶 步长选择 例子 在惯性导航以及VIO等实际问题中利用IMU求解位姿需要对IMU测量值进行积分得到需要的位置和姿态,其 ...

  3. [常微分方程的数值解法系列二] 欧拉法

    欧拉法 简介 几何意义 证明 泰勒展开近似 求导近似 积分近似 几种欧拉方式 向前欧拉公式 向后欧拉公式 梯形公式 中点公式 截断误差 求解过程 向前欧拉公式 例子 向前欧拉公式 在惯性导航以及VIO ...

  4. 二阶龙格库塔公式推导_[常微分方程的数值解法系列五] 龙格-库塔(RK4)法

    在惯性导航以及VIO等实际问题中利用IMU求解位姿需要对IMU测量值进行积分得到需要的位置和姿态,其中主要就是求解微分方程.但之前求解微分方程的解析方法主要是应用于一些简单和特殊的微分方程求解中,对于 ...

  5. 计算方法(六):常微分方程初值问题的数值解法

    文章目录 常微分方程初值问题的数值解法 欧拉(Euler)方法与改进欧拉方法 欧拉方法 欧拉公式的局部截断误差与精度分析 改进欧拉方法 龙格-库塔(Runge-Kutta)法 构造原理 经典龙格-库塔 ...

  6. matlab圆柱内导热分离变量法,一维热传导方程数值解法及matlab实现分离变量法和有限差分法...

    一维热传导方程数值解法及matlab实现分离变量法和有限差分法 一维热传导方程的Matlab解法分离变量法和有限差分法问题描述实验原理分离变量法实验原理有限差分法实验目的利用分离变量法和有限差分法解热 ...

  7. 偏微分方程数值解法python_Python数值计算----------求解简单的偏微分方程

    很多物理现象的都可以用方程来描述,比如热传导与物质扩散可以用扩散方程来描述,流体的流动可以用NS方程描述等等.如果能够将这些偏微分方程求解出来,就可以来对很多物理现象进行仿真,现在工程中的仿真软件都是 ...

  8. python计算机器人运动学分析_V-rep学习笔记:机器人逆运动学数值解法(The Jacobian Transpose Method)...

    机器人运动学逆解的问题经常出现在动画仿真和工业机器人的轨迹规划中:We want to know how the upper joints of the hierarchy would rotate ...

  9. 二阶边值问题的数值解matlab,《二阶常微分方程边值问题的数值解法》-毕业论文.doc...

    w 摘 要 本文主要研究二阶常微分方程边值问题的数值解法.对线性边值问题,我们总结了两类常用的数值方法,即打靶法和有限差分方法,对每种方法都列出了详细的计算步骤和Matlab程序代码,通过具体的算例对 ...

最新文章

  1. Mysql 中的事件//定时任务
  2. React开发(173):ant design设置额外的展开行
  3. c语言sort函数_C语言经典面试题目及答案详解(二)
  4. 【java】httpclient 链接偶尔会 Read timed out
  5. 【Redis】Redis 五大基本数据类型
  6. Linux---文件、软链接于硬链接文件
  7. iOS开发之注册推送通知权限
  8. OC中方法与函数的区别
  9. Java架构师成长之道之Java数据存储
  10. 20170830 - A - Java IO操作
  11. freeCodeCamp:Confirm the Ending
  12. BIOS 编译过程:C文件到EFI文件
  13. 数字图像处理与分析_第一章
  14. 1507. 旅行计划
  15. 偶的流氓老公zt (超搞笑-转)
  16. 微信小程序之实现层叠轮播图的效果案例(前端学习收藏夹必备)
  17. Python_oldboy_自动化运维之路_线程,进程,协程(十一)
  18. android 跳转到系统Settings界面的所有Intent
  19. openldap备份脚本
  20. oracle 中 in函数

热门文章

  1. 公众号征稿,50-150元/篇
  2. 一家专业的网络系统集成商
  3. 聚“慧”金融,华为云如何念好AI这本经?
  4. oracle餐饮管理,餐饮经营管理计划指标数据
  5. eNodeB 伪基站辨识与优化
  6. Southern and Volga Russia Qualifier 2019-2020
  7. 微信公众平台测试账号
  8. 逻辑面试题之:A说B说谎,B说C说谎,C说AB说谎,那么谁说的是真话谁说的是假话
  9. 高精度24bit 模数转化 AD7767芯片 使用总结
  10. SaaS运维平台 云HIS系统源码 一体化电子病历系统