Python偏微分方程

  • 1. 导入模块
  • 2. 偏微分方程
  • 3. 有限差分法
  • 3.1 一维热传导
  • 3.2 二维热传导
  • 3.3 有源问题
  • 3.4 稀疏矩阵性能
  • 4. 有限元法
  • 4.1 FEM求解步骤



偏微分方程(PDE)是多元微分方程,方程中的导数是偏导数。处理ODE和PDE所需的计算方法大不相同,后者对计算的要求更高。

数值求解PDE的大多数技术都基于将PDE问题中的每个因变量离散化的思想,从而将微分问题变换为代数形式。将PDE转化为代数问题的两种常用技术是有限差分法(FDM)和有限元法(FEM)。其中有限差分法是将问题中的导数近似为有限差分,而有限元法则是将未知函数写成简单基函数的线性组合,其中基函数可以较容易进行微分和积分。未知函数可以表示为基函数的一组系数。

求解PDE问题所需的计算资源一般都非常大,一部分原因是对空间进行离散化所需要点的数量与维数是指数关系。例如一个一维问题如果需要用100个点来表示,那么具有类似分辨率的二维问题将需要10000个点。由于离散空间中的每个点都对应一个未知变量,因此PDE问题需要非常大的方程组。与OED问题不同,不存在标准形式对任意PDE问题进行定义。

对于FDM和FEM,得到的代数方程组一般都非常大。在矩阵表示下,此类方程组一般都非常稀疏。基于存储和计算效率考虑,FDM和FEM都非常依赖于稀疏矩阵来表示代数线性方程组。

1. 导入模块


为了使用稀疏矩阵,我们将导入SciPy的sparse模块,以及sparse模块的linalg线性代数子模块。

Python的PDE求解器只能由专门用于PDE问题的外部库和框架提供。我们将在后续章节中使用FEniCSx框架进行演示。

import matplotlib.pyplot as plt
import matplotlib as mpl
import mpl_toolkits.mplot3d

import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg
import scipy.linalg as la
%reload_ext version_information
%version_information numpy, matplotlib, scipy

python偏函数 python如何解偏微分方程_python

2. 偏微分方程


PDE中的未知函数是多元函数。在N维问题中,函数python偏函数 python如何解偏微分方程_python_02依赖于n个独立变量。一般的PDE可以写为:

python偏函数 python如何解偏微分方程_python_03

其中python偏函数 python如何解偏微分方程_ci_04表示自变量的所有一阶导数,python偏函数 python如何解偏微分方程_python偏函数_05表示所有二阶导数。这里的python偏函数 python如何解偏微分方程_python_06是已知函数,用于描述PDE的形式,python偏函数 python如何解偏微分方程_python偏函数_07是PDE问题的定义域。

为了简化符号,通常使用python偏函数 python如何解偏微分方程_python_08表示自变量x的一阶偏导数,python偏函数 python如何解偏微分方程_python_09表示二阶导数。

大部分PDE问题最多包含二阶导数,并且通常是在二维或者三维空间中求解问题。例如热量方程在二维笛卡尔坐标系中的形式是python偏函数 python如何解偏微分方程_矩阵_10。这里函数python偏函数 python如何解偏微分方程_python偏函数_11用于描述时间python偏函数 python如何解偏微分方程_线性代数_12时,点python偏函数 python如何解偏微分方程_python_13处的温度,python偏函数 python如何解偏微分方程_python偏函数_14是热传导系数。

为了完全确定PDE的解,需要定义PDE的边界条件。边界条件是沿着问题域python偏函数 python如何解偏微分方程_python偏函数_07的函数值(Dirichlet边界条件)或者外法线导数(Neumann边界条件)的组合。如果问题是时间依赖的,那么还需要初始值。

3. 有限差分法


有限差分法的基本思想是:利用离散空间中的有限差分公式来近似PDE中出现的导数。

例如,在将连续变量python偏函数 python如何解偏微分方程_ci_16离散化成python偏函数 python如何解偏微分方程_矩阵_17时,常导数python偏函数 python如何解偏微分方程_python_18的有限差分公式可以表示为:

前向差分公式 python偏函数 python如何解偏微分方程_python_19

后向差分公式 python偏函数 python如何解偏微分方程_线性代数_20

中心差分公式 python偏函数 python如何解偏微分方程_python偏函数_21

同样,也可以为高阶导数(例如二阶)构造有限差分公式:
python偏函数 python如何解偏微分方程_python偏函数_22

使用有限差分公式替代ODE或者PDE中的导数,就可以将微分方程转换为代数方程。

3.1 一维热传导

为了具体说明有限差分法,我们首先考虑一维稳态热方程中的ODE问题python偏函数 python如何解偏微分方程_矩阵_23,其中python偏函数 python如何解偏微分方程_线性代数_24,边界条件是python偏函数 python如何解偏微分方程_矩阵_25python偏函数 python如何解偏微分方程_矩阵_26。与常微分方程章节中讨论的ODE初始值问题不同,这是边界值问题。

我们将区间python偏函数 python如何解偏微分方程_ci_27均匀离散成N+2个空间点(包含边界点),这样问题就转换为了找到这些点的函数值python偏函数 python如何解偏微分方程_线性代数_28。将ODE问题写为有限差分的形式,得到方程:

python偏函数 python如何解偏微分方程_python偏函数_29

其中间隔python偏函数 python如何解偏微分方程_python偏函数_30

由于函数在两个边界点的值是已知的,因此存在N个位置变量,对应内部点的函数值。我们可以将内部点的方程组表示为矩阵形式 python偏函数 python如何解偏微分方程_线性代数_31

python偏函数 python如何解偏微分方程_ci_32

这里矩阵python偏函数 python如何解偏微分方程_矩阵_33描述了方程python偏函数 python如何解偏微分方程_矩阵_34和相邻点的耦合。边界值包含在向量python偏函数 python如何解偏微分方程_线性代数_35中。

现在,我们可以直接求解线性方程组python偏函数 python如何解偏微分方程_线性代数_31中的未知向量python偏函数 python如何解偏微分方程_python_02,从而获得离散点python偏函数 python如何解偏微分方程_矩阵_17处函数的近似值。

N = 5
u0, u1 = 1., 2.
dx = 1.0 / (N + 1)

使用NumPy的eye函数,构造一个二维对角矩阵,同时使用参数k给定的偏移量生成上下对角线。

A = (np.eye(N, k=-1) - 2 * np.eye(N) + np.eye(N, k=1)) / dx**2
A

python偏函数 python如何解偏微分方程_矩阵_39


为向量 𝑏 准备一个数组,该数组对应微分方程中的源项(热源)-5以及边界条件。

d = -5 * np.ones(N)
d[0] -= u0 / dx**2
d[N-1] -= u1 / dx**2
d

python偏函数 python如何解偏微分方程_ci_40


使用SciPy的线性方程求解器求解方程组:

u = np.linalg.solve(A, d)
u

python偏函数 python如何解偏微分方程_矩阵_41


容易可知,该ODE问题的解析解为python偏函数 python如何解偏微分方程_python偏函数_42。我们现在对解进行可视化。

f = lambda x: -2.5*x**2 + 3.5*x + 1

x = np.linspace(0, 1, N+2)
U = np.hstack([[u0], u, [u1]])

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, f(x))
ax.plot(x[1:-1], u, 'ks')
ax.set_xlim(0, 1)
ax.set_xlabel(r"$x$", fontsize=18)
ax.set_ylabel(r"$u(x)$", fontsize=18)

python偏函数 python如何解偏微分方程_矩阵_43

3.2 二维热传导

沿着每个离散化的坐标使用有限差分公式,可以很容易将有限差分法扩展到更高维度。对于二维问题,可以用二维数组python偏函数 python如何解偏微分方程_python_02来表示未知的函数值。使用有限差分公式时,对于python偏函数 python如何解偏微分方程_python_02中的每个元素可以得到一个耦合方程组。为了将这些方程写为标准的矩阵-向量点乘的形式,可以将二维数组python偏函数 python如何解偏微分方程_python_02重排成向量,并构建有限差分方程对应的矩阵python偏函数 python如何解偏微分方程_矩阵_33
考虑二维热传导问题,PDE为拉普拉斯公式 python偏函数 python如何解偏微分方程_python_48,源项为0,边界条件为:
python偏函数 python如何解偏微分方程_python偏函数_49
python偏函数 python如何解偏微分方程_python_50
python偏函数 python如何解偏微分方程_python_51
python偏函数 python如何解偏微分方程_线性代数_52

有限差分形式为:
python偏函数 python如何解偏微分方程_矩阵_53
python偏函数 python如何解偏微分方程_矩阵_54

如果将x和y的区间分成N个内部点,那么python偏函数 python如何解偏微分方程_python_55
为了将方程写成标准形式python偏函数 python如何解偏微分方程_线性代数_31,可以重新排布矩阵python偏函数 python如何解偏微分方程_python_02,将其的行或者列叠加成大小为python偏函数 python如何解偏微分方程_矩阵_58的向量。矩阵python偏函数 python如何解偏微分方程_矩阵_33的大小是python偏函数 python如何解偏微分方程_python_60。由于有限差分公式中只有相邻点发生耦合,因此矩阵python偏函数 python如何解偏微分方程_矩阵_33很稀疏。

N = 100
u0_t, u0_b = 5, -5
u0_l, u0_r = 3, -1
dx = 1. / (N+1)

二维问题中相邻的行和列都会发生耦合,因此构造矩阵python偏函数 python如何解偏微分方程_矩阵_33会稍微复杂一些。一种相对直接的方式是首先定义矩阵python偏函数 python如何解偏微分方程_ci_63,对应一个坐标轴上的一维公式。为了在每一行使用该公式,可以将大小为python偏函数 python如何解偏微分方程_ci_64的对角矩阵与python偏函数 python如何解偏微分方程_ci_63进行张量积计算。为了涵盖每一列上耦合方程的项,需要将与主对角线相隔python偏函数 python如何解偏微分方程_python偏函数_66个位置的对角线相加。

我们将使用scipy.sparse模块中的eyekron函数构造矩阵A。

A_1d = (sp.eye(N, k=-1) + sp.eye(N, k=1) - 4 * sp.eye(N))/dx**2
A = sp.kron(sp.eye(N), A_1d) + (sp.eye(N**2, k=-N) + sp.eye(N**2, k=N))/dx**2
A

python偏函数 python如何解偏微分方程_ci_67


矩阵python偏函数 python如何解偏微分方程_矩阵_33的非零值有49600个,占总元素数目的0.496%,可见其非常稀疏。

从边界条件构建向量python偏函数 python如何解偏微分方程_线性代数_35,可以先生成一个python偏函数 python如何解偏微分方程_ci_64的零值数组,并将边界条件赋值给数组的边元素。随后,可以用reshape方法,将其重排成python偏函数 python如何解偏微分方程_矩阵_58的向量。

d = np.zeros((N, N))

d[0, :] += -u0_b 
d[-1, :] += -u0_t
d[:, 0] += -u0_l
d[:, -1] += -u0_r

d = d.reshape(N**2) / dx**2

生成数组python偏函数 python如何解偏微分方程_矩阵_33和向量python偏函数 python如何解偏微分方程_线性代数_35后,我们可以求解方程组,并使用reshape方法将python偏函数 python如何解偏微分方程_python_02转成python偏函数 python如何解偏微分方程_ci_64的矩阵。

u = sp.linalg.spsolve(A, d).reshape(N, N)

为了可视化绘图,我们创建一个矩阵python偏函数 python如何解偏微分方程_线性代数_76,将矩阵python偏函数 python如何解偏微分方程_python_02和边界条件组合到一起。

U = np.vstack([np.ones((1, N+2)) * u0_b,
               np.hstack([np.ones((N, 1)) * u0_l, u, np.ones((N, 1)) * u0_r]),
               np.ones((1, N+2)) * u0_t])

x = np.linspace(0, 1, N+2)
X, Y = np.meshgrid(x, x)

fig = plt.figure(figsize=(10, 5))
cmap = mpl.cm.get_cmap('RdBu_r')

ax1 = fig.add_subplot(1, 2, 1)
c = ax1.pcolor(X, Y, U, vmin=-5, vmax=5, cmap=cmap)
ax1.set_xlabel(r"$x_1$", fontsize=18)
ax1.set_ylabel(r"$x_2$", fontsize=18)

ax2 = fig.add_subplot(1, 2, 2, projection='3d')
p = ax2.plot_surface(X, Y, U, vmin=-5, vmax=5, rstride=3, cstride=3, cmap=cmap)
ax2.set_xlabel(r"$x_1$", fontsize=18)
ax2.set_ylabel(r"$x_2$", fontsize=18)

cb = plt.colorbar(p, ax=ax2)
cb.set_label(r"$u(x_1, x_2)$", fontsize=18)

python偏函数 python如何解偏微分方程_python偏函数_78

3.3 有源问题

考虑PDE问题 python偏函数 python如何解偏微分方程_线性代数_79

d = - np.ones((N, N))
d = d.reshape(N**2)
u = sp.linalg.spsolve(A, d).reshape(N, N)
U = np.vstack([np.zeros((1, N+2)),
               np.hstack([np.zeros((N, 1)), u, np.zeros((N, 1))]),
               np.zeros((1, N+2))])

x = np.linspace(0, 1, N+2)
X, Y = np.meshgrid(x, x)

fig, ax = plt.subplots(1, 1, figsize=(8, 6), subplot_kw={'projection': '3d'})

p = ax.plot_surface(X, Y, U, rstride=4, cstride=4, linewidth=0, cmap=mpl.cm.get_cmap("Reds"))
cb = fig.colorbar(p, shrink=0.5)

ax.set_xlabel(r"$x_1$", fontsize=18)
ax.set_ylabel(r"$x_2$", fontsize=18)
cb.set_label(r"$u(x_1, x_2)$", fontsize=18)

python偏函数 python如何解偏微分方程_python偏函数_80

3.4 稀疏矩阵性能

正如上面展示,使用FDM方法得到的矩阵python偏函数 python如何解偏微分方程_矩阵_33非常稀疏。我们下面对比稀疏矩阵和稠密矩阵求解方程python偏函数 python如何解偏微分方程_线性代数_31所需的时间。

A_dense = A.todense()
%time sp.linalg.spsolve(A, d)

python偏函数 python如何解偏微分方程_python_83

%time np.linalg.solve(A_dense, d)

python偏函数 python如何解偏微分方程_python偏函数_84

%time la.solve(A_dense, d)

python偏函数 python如何解偏微分方程_矩阵_85


从上面示例可知,有限差分法是一种强大且简单的求解ODE边界值问题以及简单形状PDE问题的方法。但是,这种方法并不适用更复杂的问题(计算量过大),以及不均匀坐标网格的问题。对于此类问题,有限元法FEM更加灵活和方便。

4. 有限元法


简单来说,有限差分法是基于泰勒展开的近似,有限元法是基于变分法的近似。

有限元法FEM的基本思想是用有限的离散区域或单元的集合来代表PDE的定义域,并将未知函数近似为基函数的线性组合,而这些基函可以在每个单元(或一组相邻单元)上获得局部支持。

在数学上,这种近似解python偏函数 python如何解偏微分方程_python偏函数_86表示从函数空间python偏函数 python如何解偏微分方程_矩阵_87中的精确解python偏函数 python如何解偏微分方程_python_02到有限子空间(问题域的离散化相关)python偏函数 python如何解偏微分方程_矩阵_89的映射。如果python偏函数 python如何解偏微分方程_矩阵_89python偏函数 python如何解偏微分方程_矩阵_87的合适子空间,可以预期python偏函数 python如何解偏微分方程_python偏函数_86能够很好近似python偏函数 python如何解偏微分方程_python_02

为了在简化的函数空间python偏函数 python如何解偏微分方程_矩阵_89中求解近似问题,可以将PDE从原始公式(称为强形式)重写为对应的变体形式(称为弱形式)。为了得到弱形式,我们将PDE乘以任意函数python偏函数 python如何解偏微分方程_python_95,并在整个问题域上积分。函数python偏函数 python如何解偏微分方程_python_95称为test函数,通常可以在不用于python偏函数 python如何解偏微分方程_矩阵_87python偏函数 python如何解偏微分方程_矩阵_89的函数空间python偏函数 python如何解偏微分方程_ci_99中定义该函数。

4.1 FEM求解步骤

通常而言,使用FEM求解PDE问题通常涉及以下步骤:

  1. 为问题域生成网格
  2. 将PDE写成弱形式
  3. 在FEM框架中对问题进行编码
  4. 求解得到的代数方程
  5. 后续处理和可视化

参考文献来自桑鸿乾老师的课件:科学计算和人工智能