Gauss-Jordanの消去法#

ガウスの消去法 (Gauss elimination)、 ガウス=ジョルダンの消去法 (Gauss-Jordan elimination)、あるいは 掃き出し法 と呼ばれる方法。

n 個の未知数 xj(j=1,,n)n 個の方程式からなる連立 1 次方程式

j=1naijxj=bi(i=1,,n)

について考える。

連立1次方程式を

a11(1)x1+a12(1)x2++a1n(1)xn=b1(1)a21(1)x1+a22(1)x2++a2n(1)xn=b2(1)an1(1)x1+an2(1)x2++ann(1)xn=bn(1)

の形に書く。

Gaussの消去法では、前進消去と後退代入という操作によって方程式を解く。

前進消去#

(1) 1番目の未知数x1に着目し、2~n番目の方程式から消去する。

i=2,,nについて、i番目の式から1番目の式のmi1=ai1(1)/a11(1)倍を引けばよい(a11(1)0と仮定しておく)

この操作のあとの連立1次方程式は、

aij(2)=aij(1)mi1a1j(1)(i=2,,n;j=2,,n)bi(2)=bi(1)mi1b1(1)(i=2,,n)

とおくと、

a11(1)x1+a12(1)x2++a1n(1)xn=b1(1)a22(2)x2++a2n(2)xn=b2(2)an2(2)x2++ann(2)xn=bn(2)

となる。

この消去操作において着目した行列要素の位置 (1,1)枢軸 (pivot)、その要素a11(1)枢軸要素mi1乗数 と呼ぶ。

(2) 2番目の未知数x2に着目し、3~n番目の方程式から消去する。

以下、このような操作をn1回まで繰り返す。そして以下を得る

a11(1)x1+a12(1)x2+a13(1)x3++a1n(1)xn=b1(1)a22(2)x2+a23(2)x3++a2n(2)xn=b2(2)a33(3)x3++a3n(3)xn=b3(3)ann(n)xn=bn(n)

ただし、a11(1)0,a22(2)0,,ann(n)0と仮定している。

後退代入#

xnについては、

ann(n)xn=bn(n)

なので、

xn=bn(n)ann(n)

として解が求まる。

xn1については

an1,n1(n1)xn1+ann(n1)xn=bn1(n1)

となっているので

xn1=1an1,n1(n1)(bn1(n1)ann(n1)xn)

として解が求まる。

これを一般化すると、解は

xi=1aii(i)(bi(i)j=i+1naij(i)xj)(i=n,n1,,1)

によって求まる。これを 後退代入 と呼ぶ。

# データの作成
import numpy as np

A = np.array([
    [1,2,3],
    [4,5,6],
    [7,8,0],
])
x_true = np.array([1, 2, 3])
b = A @ x_true
def forward_elimination(A, b):
    # 前進消去
    n = len(b)
    for k in range(n):
        for i in range(k + 1, n):
            m = A[i,k] / A[k,k]
            # numpyのベクトル演算に頼っているが、本来はここもA[i,]の各j要素にわたってfor j in range(n)が必要 → 計算量はO(n^3)
            A[i,k:] = A[i,k:] - m * A[k,k:]
            b[i] = b[i] - m * b[k]
            # print(f"{i=}, {j=}, {m=}")
            # print(A)
    return A, b

A, b = forward_elimination(A, b)
print(f"{A=}")
print(f"{b=}")
A=array([[ 1,  2,  3],
       [ 0, -3, -6],
       [ 0,  0, -9]])
b=array([ 14, -24, -27])
def backward_substitution(A, b):
    # 後退代入
    n = len(b)
    x = np.zeros(shape=(n,))
    for i in range(n-1, -1, -1):
        x[i] = (1 / A[i,i]) * (b[i] - A[i,i+1:] @ x[i+1:])
    return x

x = backward_substitution(A, b)
print(f"{x=}")
x=array([1., 2., 3.])

消去法が使える行列#

以下の条件のいずれかを満たす行列Aは、a11(1)0,a22(2)0,,ann(n)0を満たし、消去法が適用可能

  1. 行方向に一般化狭義優対角.

  2. 列方向に一般化狭義優対角.

  3. (正則な) M 行列.

  4. 対称部分が正定値: xAx=12x(A+A)x>0(x0).

一般化狭義優対角行列

各行において, 対角要素の絶対値が非対角要素の絶対値の和に比べて大きい 行列, すなわち,

|aii|ji|aij|,(1in)

を満たす行列 A を(行方向の) 狭義優対角行列 と呼ぶ.

また、正の実数d1,,dn に対して

|aii|di>ji|aij|dj(i=1,,n)

が成り立つとき, A を(行方向の) 一般化狭義優対角行列 と呼ぶ

M行列

正則な実行列A=(aij)について、非対角要素がすべて非正aij0(ij)であり、逆行列A1が非負行列(各要素が非負の実数である行列)であるとき、AM行列 と呼ぶ。

枢軸選択#

枢軸要素akk(k)が0になると次の段に進めなくなるし、0に近い値だと誤差の拡大が起きて精度の良い解が得られない。

k段の消去の前に、k番目以降の方程式を入れ替えて、枢軸要素の絶対値|akk(k)|がそれより下の位置にある要素の絶対値|aik(k)|(kin)の中で最大になるようにすることができる。この操作を 枢軸選択 (pivoting) あるいは 部分枢軸選択 (partial pivoting) と呼ぶ。

import numpy as np

A = np.array([
    [-0.001, 6],
    [3, 5],
])
x_true = np.array([-1, 1])
b = A @ x_true
print(f"{b=}")
b=array([6.001, 2.   ])
A, b = forward_elimination(A, b)
print(f"{A=}")
print(f"{b=}")

x = backward_substitution(A, b)
print(f"{x=}")
A=array([[-1.0000e-03,  6.0000e+00],
       [ 0.0000e+00,  1.8005e+04]])
b=array([6.0010e+00, 1.8005e+04])
x=array([-1.,  1.])

スケーリング#

各方程式にゼロでない定数を掛けることをスケーリングと呼ぶ。

数学的には解は不変のはずだが、枢軸選択の結果はスケーリングに依存してしまう問題がある。

参考文献#

  • 杉原正顯, & 室田一雄. (2009). 線形計算の数理. 岩波書店.