介绍
在 二维Poisson方程五点差分格式及简单求解方法Python实现 一文中给出了二维齐次 Dirichlet 型边界条件的 Poisson 方程的标准五点差分格式离散, 得到了离散系统:
A
X
=
b
AX = b
AX=b
其中
A
A
A 为离散后的微分矩阵:
对于一维情形:
A
=
1
h
2
A
1
=
1
h
2
(
−
2
1
1
−
2
1
⋱
⋱
⋱
1
−
2
1
1
−
2
)
A = \frac{1}{h^{2}}A_{1} = \frac{1}{h^{2}} \begin{pmatrix} -2 & 1 & & & \\ 1 & -2 & 1 & & \\ & \ddots & \ddots & \ddots & \\ & & 1 & -2 & 1 \\ & & & 1 & -2 \end{pmatrix}
A=h21A1=h21⎝⎜⎜⎜⎜⎛−211−2⋱1⋱1⋱−211−2⎠⎟⎟⎟⎟⎞
对于二维情形:
A
=
A
2
=
1
h
x
2
I
N
⊗
A
1
+
1
h
y
2
A
1
⊗
I
N
A = A_{2} = \frac{1}{h_{x}^{2}}I_{N} \otimes A_{1} + \frac{1}{h_{y}^{2}}A_{1} \otimes I_{N}
A=A2=hx21IN⊗A1+hy21A1⊗IN
在上文中采取最简单粗暴的方式直接生成 A A A, 并调用库函数直接对线性系统进行求解, 实际上这样的方法不适用于大规模问题,并且效率较低,本文介绍 Poisson 系统的一些常用的解法,这些解法适用于规模较大的一些问题.
简单迭代法
考虑线性系统:
A
x
=
b
Ax = b
Ax=b
将上系统改写为如下等价形式
x
=
(
I
−
C
A
)
x
+
C
b
x = (I - CA) x + Cb
x=(I−CA)x+Cb
对上述形式进行不动点迭代得如下迭代格式:
x
(
k
+
1
)
=
(
I
−
C
A
)
x
(
k
)
+
C
b
x^{(k+1)} = (I - CA)x^{(k)} + Cb
x(k+1)=(I−CA)x(k)+Cb
其中
C
C
C 待定, 由不动点迭代原理可知, 如果能够找到某个范数
∥
⋅
∥
\|\cdot\|
∥⋅∥, 使得:
∥
I
−
C
A
∥
<
1
\| I - CA\| < 1
∥I−CA∥<1, 则上述迭代格式收敛,(并且
∥
I
−
C
A
∥
越
小
时
,
收
敛
速
度
越
快
.
\| I - CA\| 越小时,收敛速度越快.
∥I−CA∥越小时,收敛速度越快.) 因此可知引入
C
C
C 的目的即通过找到合适的
C
C
C, 使得上述迭代格式收敛,
C
C
C 的选择应当尽可能的简洁.( 显然,取
C
=
A
−
1
C = A^{-1}
C=A−1 时,
∥
I
−
C
A
∥
=
0
\| I - CA\| = 0
∥I−CA∥=0, 从而迭代效果最好, 但显然这是不现实的,因为求矩阵逆将比求解一个线性方程组复杂的多.)
通过上面的思路得到的迭代方法统称为 简单迭代法 , 最经典的两种简答迭代法即: Jacobi 迭代, Gauss-Seidel 迭代 (下面简记为 GS 迭代) .
Jacobi 迭代
Jacobi 迭代原理
对于矩阵
A
A
A, 如果满足对角线元素的量级相对于该行其它元素较大时(一般而言,严格主对角占优即可), 可以使用
J
a
c
o
b
i
Jacobi
Jacobi 迭代,
J
a
c
o
b
i
Jacobi
Jacobi 迭代取上面中的
C
C
C 为:
C
=
D
−
1
:
C = D^{-1} :
C=D−1:
其中
D
:
=
d
i
a
g
(
A
)
D : = diag(A)
D:=diag(A) 表示由
A
A
A 的对角元素构成的对角矩阵.
则
J
a
c
o
b
i
Jacobi
Jacobi 迭代方法迭代格式为:
x
(
k
+
1
)
=
(
I
−
D
−
1
A
)
x
(
k
)
+
D
−
1
b
x^{(k+1)} = (I - D^{-1}A)x^{(k)} + D^{-1}b
x(k+1)=(I−D−1A)x(k)+D−1b
一般的,对迭代格式需要做一些修改, 记:
A
=
L
+
D
+
U
A = L + D + U
A=L+D+U
其中
L
L
L 表示
A
A
A 的下三角部分,
U
U
U 表示
A
A
A 的上三角部分,
D
D
D 表示
A
A
A 的对角线部分, 则容易知道,
J
a
c
o
b
i
Jacobi
Jacobi 迭代格式可以改写为:
x
(
k
+
1
)
=
−
D
−
1
(
L
+
U
)
x
(
k
)
+
D
−
1
b
x^{(k+1)} = -D^{-1}\left( L + U \right) x^{(k)} + D^{-1}b
x(k+1)=−D−1(L+U)x(k)+D−1b
可以知道, 当
A
A
A 具有较好的稀疏性时, 上述迭代能够以
O
(
N
)
O(N)
O(N) 的计算量完成, 反之, 当
A
A
A 为满矩阵时, 则上述计算过程仍然有
O
(
N
2
)
O(N^{2})
O(N2) 的复杂度, 如果是这样,
J
a
c
o
b
i
Jacobi
Jacobi 迭代和
L
U
LU
LU 分解比起来就没有很明显的优势了, 但本文关注
P
o
i
s
s
o
n
Poisson
Poisson 系统的求解 (甚至其它的 PDE有很大一部分情况下,离散出来的矩阵具有比较好的稀疏性).
由收敛性条件, 可知如果 : ∥ D − 1 ( L + U ) ∥ < 1 \| D^{-1}\left( L + U\right) \| < 1 ∥D−1(L+U)∥<1 时, J a c o b i Jacobi Jacobi 迭代收敛, 因此前面要求对角线元素量级较大.
Jacobi 迭代 Python 实现
import numpy as np # 调用numpy包
def jacobi_iter(A, b, x0 = None, tol = 1e-14, itmax = 1000):
L = np.tril(A, -1) # 获取矩阵 A 下三角部分
U = np.triu(A, 1) # 获取矩阵 A 上三角部分
D_inv = 1 / np.diag(A) # 获取矩阵 A 对角元的逆 (D_inv 是一维数组)
B = - np.einsum('i,ij->ij',D_inv, L + U) # 创建迭代矩阵 B
g = D_inv * b #
iter_count = 0 # 设置初始迭代次数为 0
iter_err = 1 # 设置初始迭代误差为 1
iter_max = itmax # 设置最大迭代次数
iter_tol = tol # 设置停机条件
iter_val = np.random.random(len(b)) if x0 is None else x0 # 如果没有赋初值,随机生成初值.
while iter_err >= iter_tol and iter_count < iter_max: # 进入循环迭代
iter_rec = iter_val # 记录上一次迭代变量
iter_val = B @ iter_val + g # 获取下一个迭代值
iter_count += 1 # 增加迭代次数
iter_err = np.max(np.abs(iter_val - iter_rec)) # 计算迭代误差
print('iteration numbers = %d, iteration error = %e'%(iter_count, iter_err))
# 退出循环, 处理结果并进行相应输出
if iter_count == iter_max:
print("exceeding the maximum iteration numbers") # 打印提示,超出最大迭代次数
return None
print("iteration numbers: %d, error = %e"%(iter_count, iter_err))
return iter_val # 如果在上一步没有 return 表示成功找到解,打印最终迭代信息,并返回方程组的解.
用以下程序测试迭代法:
def iter_test():
A = np.array([[10, 5, 1], [2, 10, 3], [3, 3, 10]], dtype=np.float64)
b = np.array([3, 2, 6])
x = jacobi_iter(A, b)
print(x)
x = np.linalg.inv(A) @ b
print(x)
输出结果如下:
iteration numbers: 56, error = 8.992806e-15
[ 0.25150421 -0.00842359 0.52707581] # Jacobi 迭代的结果
[ 0.25150421 -0.00842359 0.52707581] # 用逆矩阵乘以 b 得到的结果
GS 迭代
GS 迭代算法原理
G S GS GS 迭代是对 J a c o b i Jacobi Jacobi 迭代的改进, 考虑 J a c o b i 迭 代 的 标 量 形 式 : Jacobi 迭代的标量形式: Jacobi迭代的标量形式:
x
i
(
k
+
1
)
=
1
a
i
i
[
b
i
−
∑
j
<
i
a
i
i
x
j
(
k
)
−
∑
j
>
i
a
i
j
x
j
(
k
)
]
x_{i}^{(k+1)} = \dfrac{1}{a_{ii}} \left[ b_{i} - \sum_{j < i} a_{ii} x_{j}^{(k)} - \sum_{j > i}a_{ij}x_{j}^{(k)} \right]
xi(k+1)=aii1[bi−j<i∑aiixj(k)−j>i∑aijxj(k)]
注意, 当按照
i
=
1
,
2
,
⋯
,
n
i = 1,2,\cdots,n
i=1,2,⋯,n 计算时, 当计算
∑
j
<
i
a
i
i
x
j
(
k
)
\sum_{j < i}a_{ii}x_{j}^{(k)}
∑j<iaiixj(k) 时, 已经计算出了
x
1
(
k
+
1
)
,
⋯
,
x
i
−
1
(
k
+
1
)
,
x_{1}^{(k+1)}, \cdots, x_{i-1}^{(k+1)},
x1(k+1),⋯,xi−1(k+1), 因此这里可以用已经计算出的结果进行改进, 改进得到的就是
G
S
GS
GS 迭代法:
x i ( k + 1 ) = 1 a i i [ b i − ∑ j < i a i j x j ( k + 1 ) − ∑ j > i a i j x j ( k ) ] , i = 1 , 2 , ⋯ , n x_{i}^{(k+1)} = \dfrac{1}{a_{ii}} \left[ b_{i} - \sum_{j < i} a_{ij} x_{j}^{\color{red}{(k+1)}} - \sum_{j > i}a_{ij}x_{j}^{(k)} \right], \quad i = 1,2, \cdots, n xi(k+1)=aii1[bi−j<i∑aijxj(k+1)−j>i∑aijxj(k)],i=1,2,⋯,n
理论上,由表达式可知, G S GS GS 迭代和 J a c o b i Jacobi Jacobi 迭代的复杂度相差不大,并且直观上 G S GS GS 迭代具有更好的收敛性.
下面来看一下 G S GS GS 迭代的矩阵形式, 写出 G S GS GS 迭代的矩阵形式如下:
D x ( k + 1 ) = b − L x ( k + 1 ) − U x ( k ) Dx^{(k+1)} = b - Lx^{(k+1)} - Ux^{(k)} Dx(k+1)=b−Lx(k+1)−Ux(k)
整理得:
( D + L ) x ( k + 1 ) = − U x ( k ) + b (D + L)x^{(k+1)} = -Ux^{(k)} + b (D+L)x(k+1)=−Ux(k)+b
注意 D + L D + L D+L 为三角矩阵
注意编程时候不要用矩阵形式,不要用矩阵形式,不要用矩阵形式…不要对上三角矩阵求逆,直接用标量形式, 或者直接用回代方法求解三角方程组.
GS迭代 Python 实现:
import numpy as np
def gs_iter(A, b, x0 = None, tol = 1e-14, itmax = 1000):
L = np.tril(A, -1) # 获取矩阵 A 下三角部分
U = np.triu(A, 1) # 获取矩阵 A 上三角部分
D = np.diag(np.diag(A))
P = D + L
n = len(b) # 计算 b 长度
iter_count = 0 # 设置初始迭代次数为 0
iter_err = 1 # 设置初始迭代误差为 1
iter_max = itmax # 设置最大迭代次数
iter_tol = tol # 设置停机条件
iter_val = np.random.random(n) if x0 is None else x0 # 如果没有赋初值,随机生成初值.
while iter_err >= iter_tol and iter_count < iter_max: # 进入循环迭代
iter_rec = iter_val # 记录上一次迭代变量
# 获取下一个迭代值
g = -U @ iter_val + b
x = np.zeros(n) # 注意 Python 的拷贝机制, 这里不能直接对 iter_val 中的元素逐个修改.
# 利用循环进行回代来求解下三角矩阵方程组 Pxk+1 = g
for i in range(n):
x[i] = (g[i] - (P[i, :i] * x[:i]).sum()) / P[i, i]
iter_val = x
iter_count += 1 # 增加迭代次数
iter_err = np.max(np.abs(iter_val - iter_rec)) # 计算迭代误差
print('iteration numbers = %d, iteration error = %e'%(iter_count, iter_err))
# 退出循环, 处理结果并进行相应输出
if iter_count == iter_max:
print("exceeding the maximum iteration numbers") # 打印提示,超出最大迭代次数
return None
print("iteration numbers: %d, error = %e"%(iter_count, iter_err))
return iter_val
同样,使用上面的测试脚本,得到结果如下:
iteration numbers: 22, error = 5.939693e-15
[ 0.25150421 -0.00842359 0.52707581]
[ 0.25150421 -0.00842359 0.52707581]
可以看到, GS 需要的迭代次数比 Jacobi 迭代少
显然, G S GS GS 迭代收敛性要比 J a c o b i Jacobi Jacobi 迭代好, 但是注意, J a c o b i Jacobi Jacobi 迭代能够并行计算, 而 G S GS GS 迭代需要求解前 i − 1 i-1 i−1 个分量,逐个刷新 x ( k + 1 ) x^{(k+1)} x(k+1)的每个分量, 因此 G S GS GS 迭代 和 J a c o b i Jacobi Jacobi 迭代各自都拥有着非常明显的优势, 因此这两种迭代都是非常重要的.
判断 Jacobi 迭代以及 GS 迭代收敛的一些条件.
每次通过判断迭代矩阵谱半径是否小于1 来判断收敛性过于麻烦,这里给出几个常用的 判断 Jacobi 迭代以及 GS 迭代收敛的条件:
- 假设 A A A 严格对角占优, 则 J a c o b i Jacobi Jacobi 迭代 和 G S GS GS 迭代收敛.
- A A A 对称正定, 则 G S GS GS 迭代收敛.
- A A A 以及 2 D − A 2D - A 2D−A 对称正定, 则 J a c o b i Jacobi Jacobi 迭代收敛.
超松弛迭代 SOR
后面暂未写完
SOR 算法原理
J a c o b i Jacobi Jacobi 迭代 和 G S GS GS 迭代比较简单, 效果不是特别明显, S O R SOR SOR 是对 G S GS GS 迭代的一种改进,并且效果良好.