廣義最小殘量方法

在數學上，廣義最小殘量方法 (一般簡稱 GMRES) 是一個求解線性方程組 數值解的迭代方法。這個方法利用在 Krylov 子空間中有著最小殘量的向量來逼近解。Arnoldi 迭代方法被用來求解這個向量。

GMRES 方法由 Yousef Saad 和 Martin H. Schultz 在 1986 年提出。

GMRES 方法

需要求解的線性方程組記為。

假設矩陣 A 是 nn 階的可逆的。進一步，假設 b 是標準化的，即

b

= 1 (在這篇文章中，

・

是 Euclidean 範數)。

這個問題的 m 階 Krylov 子空間是。

GMRES 透過使得殘量 Axm − b 最小的向量 xm ∈ Km 來逼近 Ax = b 的精確解。

但是，向量 b, Ab, …, Am−1b 幾乎是線性相關的。因此，用 Arnoldi 迭代方法求得的這組 Km 的標準正交基

來取代上面的那組基。所以，向量 xm ∈ Km 寫成 xm = Qmym，其中 ym ∈ Rm 且 Qm 是由 q1, …, qm 組成的 nm 矩陣。

Arnoldi 過程也產生一個 (m+1) m 階上 Hessenberg 矩陣滿足。

因為是正交的，我們有

，

其中

是 Rm+1 的標準基的第一個向量，並且

 ，

其中是初始向量 (通常是零向量)。因此，求使得殘量。

的範數最小的。這是一個 m 階線性最小二乘問題。

這就是 GMRES 方法。在迭代的每一步中：

做一步 Arnoldi 迭代方法；
尋找使得

rm

最小的 ；

計算 ；
如果殘量不夠小，重複以上過程。

在每一步迭代中，必須計算一次矩陣向量積 Aqm。對於一般的 n 階稠密矩陣，這要計算複雜度大約 2n2 次浮點運算。但是對於稀疏矩陣，這個計算複雜度能減少到 O (n)。進一步，關於矩陣向量積，在 m 次迭代中能進行 O (m n) 次浮點運算。

收斂性

第 m 次迭代獲得在 Krylov 子空間 Km 下的最小殘量。因為每個子空間包含於下一個子空間中，所以殘量單調遞減。在第 n 次迭代後，其中 n 是矩陣 A 的階數，Krylov 空間 Kn 是完整的 Rn。因此，GMRES 方法達到精確解。然而，問題在於：在極少的幾次迭代後 (相對於 n)，向量 xm 幾乎已經是精確解的一個很好的逼近。

但是，在一般情況下這是不會發生的。事實上，Greenbaum，Pták 和 Strakoš的理論說明了對於每一個單調減少的序列 a1, …, an−1, an = 0 ，能夠找到一個矩陣 A 對於所有 m 滿足

rm

= am ，其中 rm 是上面所定義的殘量。特別的，有可能找到一個矩陣，使得前 n−1 次迭代的殘量一直保持為常數，而只在最後一次迭代時達到零。

在實驗中，GMRES 方法經常表現得很好。在特殊的情況下這能夠被證明。如果 A 是正定的，則

，

其中和分別為矩陣的最小和最大特徵值。

如果 A 是對稱的並且是正定的，則。

其中記為 A 在 Euclidean 範數下的條件數。

一般情況下，其中 A 是非正定的，則

，

其中 Pm 記為次數不超過 m 且 p (0) = 1 的多項式的集合，V 是 A 的譜分解中的矩陣，而 σ(A) 是 A 的譜。粗略的說，當 A 的特徵值聚集在遠離原點的區域且 A 離正規不太遠時，收斂速度較快。

所有的不等式只界定殘量，而不是實際誤差 (精確解和當前迭代 xm 之間的距離)。

GMRES 方法的拓展 (Restarted GMRES)

同其他迭代方法一樣，為了加快收斂，GMRES 經常結合預處理方法。

迭代的開銷以 O (m2) 增長，其中 m 是迭代次數。然而有時候，GMRES 方法在 k 次迭代後重新開始，即 xk 又變回初始值。這樣的方法叫做 GMRES (k)。

求解最小二乘問題

GMRES 方法的其中一部分是求解向量使得

最小。這個可以透過計算 QR 分解來實現：找到一個 (m+1)(m+1) 階正交矩陣 Ωm 和一個 (m+1) m 上三角矩陣滿足。

三角矩陣的行數比列數多 1，所以它的最後一行由零組成。因此，它能被分解為

，

其中是一個 mm 階三角 (方) 矩陣。

QR 分解能夠簡單的進行下去 (update)，從一步迭代到下一步迭代。因為每次的 Hessenberg 矩陣只在一行零元素和一列元素上有所不同：

 ，

其中 hm = (h1m, … hmm) T。這意味著，Hessenberg 矩陣左乘上 Ωm 的擴大矩陣 (透過並上零元素和單位元素)，所得到的是類似於三角矩陣的矩陣：

這個矩陣可以三角化，如果 σ 為零。為了修正這個矩陣，需要進行 Givens 旋轉

其中。

透過這個 Givens 旋轉，我們構造。

事實上，

是一個三角矩陣。

給出了 QR 分解，最小值問題就容易解決了。注意到。

記為

其中 gm ∈ Rm 和 γm ∈ R，則。

使得這個表示式最小的向量 y 為。

再一次，向量能夠簡單的進行下去 (update)。

Example code

6.1 Regular GMRES (python3)

from "https://github.com/J-N-ch/GMRESpyrestart/blob/master/GMRES_API/GMRES.py"

import numpy as np

import math

class GMRES_API (object):

def init( self,

Acoefficientmatrix: np.array (, dtype = float),

bboundarycondition_vector: np.array (, dtype = float),

maximumnumberofbasisused: int,

threshold = 1.0e-16 ):

self.A = Acoefficientmatrix

self.b = bboundarycondition_vector

self.maximumnumberofbasisused = maximumnumberofbasisused

self.threshold = threshold

def initialguessinput (self, xinputvectorinitialguess: np.array (, dtype = float) ):

self.x = xinputvectorinitialguess

try:

assert len (self.x) == len (self.b)

except Exception:

print ("The input guess vector's size must equal to the system's size !\n")

print ("The matrix system's size ==", len ( self.b))

print ("Your input vector's size ==", len ( self.x))

self.x = np.zeros (len ( self.b) )

print ("Use default input guess vector =", self.x, "instead of the incorrect vector you given !\n")

def run (self):

n = len (self.A)

m = self.maximumnumberofbasisused

r = self.b - np.dot (self.A , self.x)

r_norm = np.linalg.norm (r)

b_norm = np.linalg.norm (self.b)

self.error = np.linalg.norm (r) /b_norm

self.e = self.error

initialize the 1D vectors

sn = np.zeros (m)

cs = np.zeros (m)

e1 = np.zeros (m + 1)

e10 = 1.0

beta = r_norm * e1

beta is the beta vector instead of the beta scalar

H = np.zeros ((m+1, m+1))

Q = np.zeros ((n, m+1))

Q:,0 = r /r_norm

for k in range (m):

(H0:k+2, k, Q:, k+1) = class.arnoldi (self.A, Q, k)

(H0:k+2, k, csk, snk) = class.applygivensrotation (H0:k+2, k, cs, sn, k)

update the residual vector

beta k+1 = -snk * betak

beta k = csk * betak

calculate and save the errors

self.error = abs (betak+1) /b_norm

self.e = np.append (self.e, self.error)

if (self.error abs (v2) ):

t = v2 /v1

u = np.sign (v1) * math.hypot (1.0, t)

cs = 1.0 /u

sn = t * cs

r = v1 * u

else:

t = v1 /v2

u = np.sign (v2) * math.hypot (1.0, t)

sn = 1.0 /u

cs = t * sn

r = v2 * u

return cs, sn, r

From https://stackoverflow.com/questions/47551069/back-substitution-in-python

@staticmethod

def back_substitution (A: np.ndarray, b: np.ndarray) -> np.ndarray:

n = b.size

if An-1, n-1 == 0.0:

raise ValueError

x = np.zeros_like (b)

xn-1 = bn-1 / An-1, n-1

for i in range (n-2, -1, -1):

bb = 0

for j in range (i+1, n):

bb += Ai, j * xj

xi = (bi - bb) / Ai, i

return x

def finalresidualinfo_show (self):

print ("x =", self.x, "residualnorm =", self.finalresidual_norm)

def main :

A_mat = np.array ( 1.00, 1.00, 1.00,

1.00, 2.00, 1.00,

0.00, 0.00, 3.00 )

b_mat = np.array (3.0, 2.0, 1.0)

GMREStestitr2 = GMRESAPI (Amat, b_mat, 2, 0.01)

x_mat = np.array (1.0, 1.0, 1.0)

print ("x =", x_mat)

GMRES with restart, 2 iterations in each restart (GMRES (2) )

maxrestartcounts = 100

for restartcounter in range (maxrestart_counts):

GMREStestitr2.initialguessinput (x_mat)

xmat = GMREStest_itr2.run

print (restartcounter+1,": x =", xmat)

xx = np.matmul (np.linalg.inv (Amat), bmat )

print ("ANS : xx =", xx)

if name == 'main':

main