Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Gauss-Jordanの消去法

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

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

j=1naijxj=bi(i=1,,n)\sum_{j=1}^n a_{i j} x_j=b_i \quad(i=1, \cdots, n)

について考える。

連立1次方程式を

Undefined control sequence: \DeclareMathOperator at position 57: …{#1}}
% 演算子の定義
\̲D̲e̲c̲l̲a̲r̲e̲M̲a̲t̲h̲O̲p̲e̲r̲a̲t̲o̲r̲{\im}{ \text{Im…

% 太字のalias
\newcommand{\b}[1]{\boldsymbol{#1}}
% 演算子の定義
\DeclareMathOperator{\im}{ \text{Im} }
\DeclareMathOperator{\rank}{ \text{rank} }
\DeclareMathOperator{\span}{ \text{Span} }
\DeclareMathOperator{\Ker}{ \text{Ker} }
%
\begin{align}
& a_{11}^{(1)} x_1+a_{12}^{(1)} x_2+\cdots+a_{1 n}^{(1)} x_n=b_1^{(1)}\\
& a_{21}^{(1)} x_1+a_{22}^{(1)} x_2+\cdots+a_{2 n}^{(1)} x_n=b_2^{(1)}\\
&\quad \vdots\\
& a_{n 1}^{(1)} x_1+a_{n 2}^{(1)} x_2+\cdots+a_{n n}^{(1)} x_n=b_n^{(1)} \\
\end{align}

の形に書く。

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

前進消去

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

i=2,,ni=2,\cdots,nについて、ii番目の式から1番目の式のmi1=ai1(1)/a11(1)m_{i1} = a_{i1}^{(1)} / a_{11}^{(1)}倍を引けばよい(a11(1)0a_{11}^{(1)}\neq 0と仮定しておく)

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

aij(2)=aij(1)mi1a1j(1)(i=2,,n;j=2,,n)bi(2)=bi(1)mi1b1(1)(i=2,,n)\begin{align} a_{i j}^{(2)}=a_{i j}^{(1)}-m_{i 1} a_{1 j}^{(1)} & \quad(i=2, \cdots, n ; j=2, \cdots, n) \\ b_i^{(2)}=b_i^{(1)}-m_{i 1} b_1^{(1)} & \quad (i=2, \cdots, n) \end{align}

とおくと、

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)\begin{aligned} a_{11}^{(1)} x_1+a_{12}^{(1)} x_2+\cdots+a_{1 n}^{(1)} x_n=b_1^{(1)}&\\ a_{22}^{(2)} x_2+\cdots+a_{2 n}^{(2)} x_n=b_2^{(2)}&\\ \vdots \quad &\\ a_{n 2}^{(2)} x_2+\cdots+a_{n n}^{(2)} x_n=b_n^{(2)}&\\ \end{aligned}

となる。

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

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

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

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)\begin{aligned} a_{11}^{(1)} x_1+a_{12}^{(1)} x_2+a_{13}^{(1)} x_3+\cdots+a_{1 n}^{(1)} x_n =b_1^{(1)}&\\ a_{22}^{(2)} x_2+a_{23}^{(2)} x_3+\cdots+a_{2 n}^{(2)} x_n=b_2^{(2)}&\\ a_{33}^{(3)} x_3+\cdots+a_{3 n}^{(3)} x_n=b_3^{(3)}&\\ \ddots \quad \quad \vdots \quad \quad \vdots \quad&\\ a_{n n}^{(n)} x_n=b_n^{(n)} &\\ \end{aligned}

ただし、a11(1)0,a22(2)0,,ann(n)0a_{11}^{(1)} \neq 0, \quad a_{22}^{(2)} \neq 0, \quad \cdots, \quad a_{n n}^{(n)} \neq 0と仮定している。

後退代入

xnx_nについては、

ann(n)xn=bn(n)a_{n n}^{(n)} x_n=b_n^{(n)}

なので、

xn=bn(n)ann(n)x_n = \frac{ b_n^{(n)} }{ a_{n n}^{(n)} }

として解が求まる。

xn1x_{n-1}については

an1,n1(n1)xn1+ann(n1)xn=bn1(n1)a_{n-1, n-1}^{(n-1)} x_{n-1} + a_{n n}^{(n-1)} x_n = b_{n-1}^{(n-1)}

となっているので

xn1=1an1,n1(n1)(bn1(n1)ann(n1)xn)x_{n-1} = \frac{1}{a_{n-1, n-1}^{(n-1)}} (b_{n-1}^{(n-1)} - a_{n n}^{(n-1)} x_n )

として解が求まる。

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

xi=1aii(i)(bi(i)j=i+1naij(i)xj)(i=n,n1,,1)x_i=\frac{1}{a_{i i}^{(i)}}\left(b_i^{(i)}-\sum_{j=i+1}^n a_{i j}^{(i)} x_j\right) \quad(i=n, n-1, \cdots, 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.0000e-03,  6.0000e+00],
       [ 0.0000e+00,  1.8005e+04]])
b=array([6.0010e+00, 1.8005e+04])
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.])

消去法が使える行列

以下の条件のいずれかを満たす行列AAは、a11(1)0,a22(2)0,,ann(n)0a_{11}^{(1)} \neq 0, \quad a_{22}^{(2)} \neq 0, \quad \cdots, \quad a_{n n}^{(n)} \neq 0を満たし、消去法が適用可能

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

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

  3. (正則な) M\mathrm{M} 行列.

  4. 対称部分が正定値: xAx=12x(A+A)x>0(x0)\boldsymbol{x}^{\top} A \boldsymbol{x}=\frac{1}{2} \boldsymbol{x}^{\top}\left(A+A^{\top}\right) \boldsymbol{x}>0 \quad(\forall \boldsymbol{x} \neq \mathbf{0}).

枢軸選択

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

kk段の消去の前に、kk番目以降の方程式を入れ替えて、枢軸要素の絶対値akk(k)|a_{kk}^{(k)}|がそれより下の位置にある要素の絶対値aik(k)(kin)|a_{ik}^{(k)}| (k\leq i \leq n)の中で最大になるようにすることができる。この操作を 枢軸選択 (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). 線形計算の数理. 岩波書店.