离散Poisson系统的常用处理方法以及比较(Python实现)

介绍

在 二维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=h212112112112
对于二维情形:
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=hx21INA1+hy21A1IN

在上文中采取最简单粗暴的方式直接生成 A A A, 并调用库函数直接对线性系统进行求解, 实际上这样的方法不适用于大规模问题,并且效率较低,本文介绍 Poisson 系统的一些常用的解法,这些解法适用于规模较大的一些问题.

简单迭代法

考虑线性系统:
A x = b Ax = b Ax=b
将上系统改写为如下等价形式
x = ( I − C A ) x + C b x = (I - CA) x + Cb x=(ICA)x+Cb
对上述形式进行不动点迭代得如下迭代格式:
x ( k + 1 ) = ( I − C A ) x ( k ) + C b x^{(k+1)} = (I - CA)x^{(k)} + Cb x(k+1)=(ICA)x(k)+Cb
其中 C C C 待定, 由不动点迭代原理可知, 如果能够找到某个范数 ∥ ⋅ ∥ \|\cdot\| , 使得: ∥ I − C A ∥ < 1 \| I - CA\| < 1 ICA<1, 则上述迭代格式收敛,(并且 ∥ I − C A ∥ 越 小 时 , 收 敛 速 度 越 快 . \| I - CA\| 越小时,收敛速度越快. ICA,.) 因此可知引入 C C C 的目的即通过找到合适的 C C C, 使得上述迭代格式收敛, C C C 的选择应当尽可能的简洁.( 显然,取 C = A − 1 C = A^{-1} C=A1 时, ∥ I − C A ∥ = 0 \| I - CA\| = 0 ICA=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=D1:
其中 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)=(ID1A)x(k)+D1b
一般的,对迭代格式需要做一些修改, 记:
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)=D1(L+U)x(k)+D1b
可以知道, 当 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 D1(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[bij<iaiixj(k)j>iaijxj(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),,xi1(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[bij<iaijxj(k+1)j>iaijxj(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)=bLx(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 i1 个分量,逐个刷新 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 2DA 对称正定, 则 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 迭代的一种改进,并且效果良好.

SOR Python 实现

共轭梯度法 (CG)

Fast poisson solver

你可能感兴趣的:(偏微分方程数值解,算法)