与えられた行列A A A を単位下三角行列L L L と上三角行列U U U との積で表すこと
A = L U = [ 1 l 21 1 ⋮ ⋱ ⋱ l n 1 ⋯ l n , n − 1 1 ] [ u 11 u 12 ⋯ u 1 n u 22 ⋯ u 2 n ⋱ ⋮ u n n ] A = LU
=
\left[\begin{array}{cccc}
1 & & & \\
l_{21} & 1 & & \\
\vdots & \ddots & \ddots & \\
l_{n 1} & \cdots & l_{n, n-1} & 1
\end{array}\right]\left[\begin{array}{cccc}
u_{11} & u_{12} & \cdots & u_{1 n} \\
& u_{22} & \cdots & u_{2 n} \\
& & \ddots & \vdots \\
& & & u_{n n}
\end{array}\right] A = LU = ⎣ ⎡ 1 l 21 ⋮ l n 1 1 ⋱ ⋯ ⋱ l n , n − 1 1 ⎦ ⎤ ⎣ ⎡ u 11 u 12 u 22 ⋯ ⋯ ⋱ u 1 n u 2 n ⋮ u nn ⎦ ⎤ をLU分解 と呼ぶ。
分解すると何が嬉しいのか? ¶ 行列式や一次方程式を少ない計算量で求められる材料としてL , U L,U L , U が手に入り、全体としての計算を効率化できる可能性がある。
例:連立1次方程式・逆行列 ¶ 例えば様々なb b b に対してA x = b Ax=b A x = b を解く場合はA = L U A=LU A = LU と一旦分解して、後でL , U L,U L , U を使い回せばよい
様々なb b b というのは、たとえばB = ( b 1 , b 2 , ⋯ , b n ) B=(b_1, b_2, \cdots, b_n) B = ( b 1 , b 2 , ⋯ , b n ) として行列の連立一次方程式
を解くことができる。B = I B=I B = I (b 1 = e 1 , b 2 = e 2 , ⋯ , b n = e n b_1 = e_1, b_2 = e_2, \cdots, b_n = e_n b 1 = e 1 , b 2 = e 2 , ⋯ , b n = e n )とすればA X = I AX=I A X = I を解いてX = A − 1 X=A^{-1} X = A − 1 を求めることができる。
計算量はLU分解にO ( n 3 / 3 ) O(n^3/3) O ( n 3 /3 ) かかる。そこから逆行列を計算する場合はO ( 2 n 3 / 3 ) O(2n^3/3) O ( 2 n 3 /3 ) かかり、全体でO ( n 3 / 3 + 2 n 3 / 3 ) = O ( n 3 ) O(n^3/3 + 2n^3/3)=O(n^3) O ( n 3 /3 + 2 n 3 /3 ) = O ( n 3 ) となる。これは掃き出し法と同程度だが、もとの行列が疎行列だとLUも疎行列になるので、入力が疎行列の場合も含めた計算量でいうとLU分解のほうが効率的らしい。
例:行列式 ¶ ∣ A ∣ = ∣ L U ∣ = ∣ L ∣ ∣ U ∣ ( ∵ 行列式の性質 ∣ A B ∣ = ∣ A ∣ ∣ B ∣ より ) = 1 × ∣ U ∣ ( ∵ L は単位下三角行列のため ) = ∣ U ∣ \begin{align}
|A|
&= |LU|\\
&= |L| |U| \quad (\because \text{ 行列式の性質 } |AB|=|A||B| \text{ より })\\
&= 1\times |U| \quad (\because L \text{ は単位下三角行列のため })\\
&= |U|
\end{align} ∣ A ∣ = ∣ LU ∣ = ∣ L ∣∣ U ∣ ( ∵ 行列式の性質 ∣ A B ∣ = ∣ A ∣∣ B ∣ より ) = 1 × ∣ U ∣ ( ∵ L は単位下三角行列のため ) = ∣ U ∣ 三角行列の行列式は対角行列の積になるため、∣ L ∣ = 1 |L|=1 ∣ L ∣ = 1 であり∣ A ∣ = ∣ U ∣ |A|=|U| ∣ A ∣ = ∣ U ∣ になるし∣ U ∣ |U| ∣ U ∣ もU U U の対角成分の積をとるだけでいい
計算手順(ドゥーリトル法) ¶ L L L の対角成分は1に固定し、次のような分解を仮定する
A = [ 1 0 l 21 1 ] [ u 11 u 12 0 u 22 ] A =
\left[\begin{array}{cc}
1 & 0 \\
l_{21} & 1
\end{array}\right]
\left[\begin{array}{cc}
u_{11} & u_{12} \\
0 & u_{22}
\end{array}\right] A = [ 1 l 21 0 1 ] [ u 11 0 u 12 u 22 ] L U LU LU の積を計算すると
A = [ u 11 u 12 l 21 u 11 l 21 u 12 + u 22 ] A=\left[\begin{array}{ccc}
u_{11} & u_{12}\\
l_{21} u_{11} & l_{21} u_{12} + u_{22}
\end{array}\right] A = [ u 11 l 21 u 11 u 12 l 21 u 12 + u 22 ] となるため、A A A の各要素との対応がとれる
a 11 = u 11 a 12 = u 12 a 21 = l 21 u 11 a 22 = l 21 u 12 + u 22 \begin{aligned}
& a_{11}=u_{11} \\
& a_{12}=u_{12} \\
& a_{21}=l_{21} u_{11} \\
& a_{22}=l_{21} u_{12}+u_{22}
\end{aligned} a 11 = u 11 a 12 = u 12 a 21 = l 21 u 11 a 22 = l 21 u 12 + u 22 これらを解いていく
一般化 ¶ もっと一般化すると次のようになる
m × n m\times n m × n 行列A = ( a i j ) A=(a_{ij}) A = ( a ij ) に対して、s = min ( m , n ) s=\min(m,n) s = min ( m , n ) とおき、m × s m\times s m × s のL L L とs × n s\times n s × n のU U U で分解することを考える。
L = ( l 1 , … , l s ) , U = ( u 1 T ⋮ u s T ) L = \left(\boldsymbol{l}_1, \ldots, \boldsymbol{l}_s\right),
\quad
U = \left(\begin{array}{c}
\boldsymbol{u}_1^T \\
\vdots \\
\boldsymbol{u}_s^T
\end{array}\right) L = ( l 1 , … , l s ) , U = ⎝ ⎛ u 1 T ⋮ u s T ⎠ ⎞ とすると
A = l 1 u 1 T + ⋯ + l s u s T A = l_1 \boldsymbol{u}_1^T+\cdots+\boldsymbol{l}_s \boldsymbol{u}_s^T A = l 1 u 1 T + ⋯ + l s u s T とできるので、この表記を使っていく。
反復計算するのでA = A ( 1 ) = ( a i j ( 1 ) ) A = A(1) = (a_{ij}(1)) A = A ( 1 ) = ( a ij ( 1 )) と表記する
l 1 = 1 a 11 ( 1 ) ( a 11 ( 1 ) ⋮ a m 1 ( 1 ) ) , u 1 T = ( a 11 ( 1 ) , … , a 1 n ( 1 ) ) \boldsymbol{l}_1=\frac{1}{a_{11}(1)}\left(\begin{array}{c}
a_{11}(1) \\
\vdots \\
a_{m 1}(1)
\end{array}\right), \quad \boldsymbol{u}_1^T=\left(a_{11}(1), \ldots, a_{1 n}(1)\right) l 1 = a 11 ( 1 ) 1 ⎝ ⎛ a 11 ( 1 ) ⋮ a m 1 ( 1 ) ⎠ ⎞ , u 1 T = ( a 11 ( 1 ) , … , a 1 n ( 1 ) ) と置くと、
A ( 1 ) − l 1 u 1 T = ( 0 0 ⋯ 0 0 ⋮ A ( 2 ) 0 ) , すなわち A = l 1 u 1 T + ( 0 0 ⋯ 0 0 ⋮ A ( 2 ) 0 ) A(1)-\boldsymbol{l}_1 \boldsymbol{u}_1^T=\left(\begin{array}{c|ccc}
0 & 0 & \cdots & 0 \\
\hline 0 & & & \\
\vdots & & A(2) & \\
0 & & &
\end{array}\right), \quad \text { すなわち } A=l_1 \boldsymbol{u}_1^T+\left(\begin{array}{c|ccc}
0 & 0 & \cdots & 0 \\
\hline 0 & & & \\
\vdots & & A(2) & \\
0 & &
\end{array}\right) A ( 1 ) − l 1 u 1 T = ⎝ ⎛ 0 0 ⋮ 0 0 ⋯ A ( 2 ) 0 ⎠ ⎞ , すなわち A = l 1 u 1 T + ⎝ ⎛ 0 0 ⋮ 0 0 ⋯ A ( 2 ) 0 ⎠ ⎞ となる。残ったブロック行列をA ( 2 ) A(2) A ( 2 ) としている。
ゼロじゃない要素をl ( 2 ) , u ( 2 ) \boldsymbol{l}(2), \boldsymbol{u}(2) l ( 2 ) , u ( 2 ) とおき、同様に
l ( 2 ) = 1 a 11 ( 2 ) ( a 11 ( 2 ) ⋮ a m ′ 1 ( 2 ) ) , u ( 2 ) T = ( a 11 ( 2 ) , … , a 1 n ′ ( 2 ) ) \boldsymbol{l}(2)=\frac{1}{a_{11}(2)}\left(\begin{array}{c}
a_{11}(2) \\
\vdots \\
a_{m^{\prime} 1}(2)
\end{array}\right), \quad \boldsymbol{u}(2)^T=\left(a_{11}(2), \ldots, a_{1 n^{\prime}}(2)\right) l ( 2 ) = a 11 ( 2 ) 1 ⎝ ⎛ a 11 ( 2 ) ⋮ a m ′ 1 ( 2 ) ⎠ ⎞ , u ( 2 ) T = ( a 11 ( 2 ) , … , a 1 n ′ ( 2 ) ) とすると(ここでm ′ = m − 1 , n ′ = n − 1 m'=m-1, n'=n-1 m ′ = m − 1 , n ′ = n − 1 )、同様に
A ( 2 ) − l ( 2 ) u ( 2 ) T = ( 0 0 ⋯ 0 0 ⋮ A ( 3 ) 0 ) A(2)-l(2) \boldsymbol{u}(2)^T=\left(\begin{array}{c|ccc}
0 & 0 & \cdots & 0 \\
\hline 0 & & & \\
\vdots & & A(3) & \\
0 & & &
\end{array}\right) A ( 2 ) − l ( 2 ) u ( 2 ) T = ⎝ ⎛ 0 0 ⋮ 0 0 ⋯ A ( 3 ) 0 ⎠ ⎞ となる。ここで
l 2 = ( 0 l ( 2 ) ) , u 2 T = ( 0 , u ( 2 ) T ) \boldsymbol{l}_2=\left(\begin{array}{c}
0 \\
\boldsymbol{l}(2)
\end{array}\right), \quad \boldsymbol{u}_2^T=\left(0, \boldsymbol{u}(2)^T\right) l 2 = ( 0 l ( 2 ) ) , u 2 T = ( 0 , u ( 2 ) T ) のように頭に0をつけてサイズを戻せば
( 0 0 ⋯ ⋯ 0 0 ⋮ A ( 2 ) ⋮ 0 ) = l 2 u 2 T + ( 0 0 0 ⋯ 0 0 0 0 ⋯ 0 0 0 ⋮ ⋮ A ( 3 ) 0 0 ) \left(\begin{array}{c|cccc}
0 & 0 & \cdots & \cdots & 0 \\
\hline 0 & & & & \\
\vdots & & A(2) & & \\
\vdots & & & & \\
0 & & & &
\end{array}\right)=l_2 \boldsymbol{u}_2^T+\left(\begin{array}{cc|ccc}
0 & 0 & 0 & \cdots & 0 \\
0 & 0 & 0 & \cdots & 0 \\
\hline 0 & 0 & & & \\
\vdots & \vdots & & A(3) & \\
0 & 0 & &
\end{array}\right) ⎝ ⎛ 0 0 ⋮ ⋮ 0 0 ⋯ A ( 2 ) ⋯ 0 ⎠ ⎞ = l 2 u 2 T + ⎝ ⎛ 0 0 0 ⋮ 0 0 0 0 ⋮ 0 0 0 ⋯ ⋯ A ( 3 ) 0 0 ⎠ ⎞ から
A = l 1 u 1 T + l 2 u 2 T + ( 0 0 0 ⋯ 0 0 0 0 ⋯ 0 0 0 ⋮ ⋮ A ( 3 ) 0 0 ) A=l_1 \boldsymbol{u}_1^T+\boldsymbol{l}_2 \boldsymbol{u}_2^T+\left(\begin{array}{cc|ccc}
0 & 0 & 0 & \cdots & 0 \\
0 & 0 & 0 & \cdots & 0 \\
\hline 0 & 0 & & & \\
\vdots & \vdots & & A(3) & \\
0 & 0 & & &
\end{array}\right) A = l 1 u 1 T + l 2 u 2 T + ⎝ ⎛ 0 0 0 ⋮ 0 0 0 0 ⋮ 0 0 0 ⋯ ⋯ A ( 3 ) 0 0 ⎠ ⎞ となる。
これを何度も繰り返していくと、最後は残る行列がゼロ行列になる
A = l 1 u 1 T + ⋯ + l s u s T + O A=l_1 \boldsymbol{u}_1^T+\cdots+\boldsymbol{l}_s \boldsymbol{u}_s^T+O A = l 1 u 1 T + ⋯ + l s u s T + O そうしたら
L = ( l 1 , … , l s ) , U = ( u 1 T ⋮ u s T ) L = \left(\boldsymbol{l}_1, \ldots, \boldsymbol{l}_s\right),
\quad
U = \left(\begin{array}{c}
\boldsymbol{u}_1^T \\
\vdots \\
\boldsymbol{u}_s^T
\end{array}\right) L = ( l 1 , … , l s ) , U = ⎝ ⎛ u 1 T ⋮ u s T ⎠ ⎞ のように行列L , U L,U L , U を作ればよい
import numpy as np
# 対象の行列
A = np.array([
[1, 2],
[3, 4]
])
# iter 1
l1 = (1 / A[0,0]) * A[:, 0]
u1 = A[0, :]
print(f"{l1=}, {u1=}")l1=array([1., 3.]), u1=array([1, 2])
# ベクトルの外積
np.outer(l1, u1)array([[1., 2.],
[3., 6.]])
A2 = A - np.outer(l1, u1)
A2array([[ 0., 0.],
[ 0., -2.]])
A_ = A2[1:, 1:]
l2_ = (1 / A_[0,0]) * A_[:, 0]
u2_ = A_[0, :]
print(f"{l2_=}, {u2_=}")l2_=array([1.]), u2_=array([-2.])
# 残りがゼロに
A3 = A_ - np.outer(l2_, u2_)
A3# 先頭にゼロを足して次元を合わせる
l2 = np.concatenate([np.zeros(shape=(1,)), l2_])
u2 = np.concatenate([np.zeros(shape=(1,)), u2_])
# それぞれのiterationの外積の和が元の行列になる
np.outer(l1, u1) + np.outer(l2, u2)array([[1., 2.],
[3., 4.]])
L = np.array([l1, l2]).T
Larray([[1., 0.],
[3., 1.]])
array([[ 1., 2.],
[ 0., -2.]])
array([[1., 2.],
[3., 4.]])
print(f"""
|A| = {np.linalg.det(A):.1f}
|L||U| = {np.linalg.det(L) * np.linalg.det(U):.1f}
Uの対角成分の積 = {np.diag(U)[0] * np.diag(U)[1]}
""")
|A| = -2.0
|L||U| = -2.0
Uの対角成分の積 = -2.0
連立一次方程式をLU分解で解く ¶ 連立一次方程式
に対し、LU分解すると
A x = L U x = b Ax = LUx = b A x = LUx = b であり、変数y y y を
とおき代入すると
になることから変数y y y を求める。
L y = b Ly = b L y = b (前進代入)は
y 1 : = b 1 y 2 : = b 2 − l 21 y 1 ⋮ y n − 1 : = b n − 1 − ∑ j = 1 n − 2 l n − 1 , j y j y n : = b n − ∑ j = 1 n − 1 l n j y j \begin{aligned}
& y_1:=b_1 \\
& y_2:=b_2-l_{21} y_1 \\
& \vdots \\
& y_{n-1}:=b_{n-1}-\sum_{j=1}^{n-2} l_{n-1, j} y_j \\
& y_n:=b_n-\sum_{j=1}^{n-1} l_{n j} y_j
\end{aligned} y 1 := b 1 y 2 := b 2 − l 21 y 1 ⋮ y n − 1 := b n − 1 − j = 1 ∑ n − 2 l n − 1 , j y j y n := b n − j = 1 ∑ n − 1 l nj y j U x = y Ux = y Ux = y (後退代入)は
x n : = y n u n n x n − 1 : = y n − 1 − u n − 1 , n x n u n − 1 , n − 1 ⋮ x 2 : = y 2 − ∑ j = 3 n u 2 j x j u 22 x 1 : = y 1 − ∑ j = 2 n u 1 j x j u 11 \begin{aligned}
& x_n:=\frac{y_n}{u_{n n}} \\
& x_{n-1}:=\frac{y_{n-1}-u_{n-1, n} x_n}{u_{n-1, n-1}}\\
& \vdots\\
& x_2:=\frac{y_2-\sum_{j=3}^n u_{2 j} x_j}{u_{22}} \\
& x_1:=\frac{y_1-\sum_{j=2}^n u_{1 j} x_j}{u_{11}}
\end{aligned} x n := u nn y n x n − 1 := u n − 1 , n − 1 y n − 1 − u n − 1 , n x n ⋮ x 2 := u 22 y 2 − ∑ j = 3 n u 2 j x j x 1 := u 11 y 1 − ∑ j = 2 n u 1 j x j という計算
def forward_substitution(L, b):
# 前進代入
n = L.shape[0]
y_ = np.zeros_like(b, dtype=np.double)
for i in range(n):
sumj = 0
for j in range(i):
sumj += L[i, j] * y_[j]
y_[i] = b[i] - sumj
return y_
def backword_substitution(U, y):
# 後退代入
n = U.shape[0]
x = np.zeros_like(y, dtype=np.double)
for i in range(n-1, -1, -1):
sumj = 0
for j in range(n-1, i-1, -1):
sumj += U[i, j] * x[j]
x[i] = (y[i] - sumj) / U[i, i]
return x
xGETRS ¶ 前進代入・後退代入を行うのがLAPACKのxGETRSサブルーチン
from scipy.linalg.lapack import dgetrf, dgetrs
LU, P, info = dgetrf(A)
dgetrs(LU, P, b)y = np.linalg.inv(L) @ b
yx = np.linalg.inv(U) @ y
x逆行列 ¶ A − 1 = U − 1 L − 1 A^{-1} = U^{-1} L^{-1} A − 1 = U − 1 L − 1 あるいはA x = b Ax=b A x = b のx x x を求める操作をn n n 回繰り返す
array([[-2. , 1. ],
[ 1.5, -0.5]])
np.linalg.inv(U) @ np.linalg.inv(L)array([[-2. , 1. ],
[ 1.5, -0.5]])
A x = b Ax=b A x = b のx x x を求める操作をn n n 回繰り返す
y = L @ b
b = np.array([1, 2])
from scipy.linalg import lu
P, L, U = lu(A)
array([[0., 1.],
[1., 0.]])
array([[1. , 0. ],
[0.33333333, 1. ]])
array([[3. , 4. ],
[0. , 0.66666667]])
array([[1., 2.],
[3., 4.]])
置換行列P P P つきの分解はP A = L U PA=LU P A = LU となる。
A x = b Ax = b A x = b は2つの方程式L y = P b , U x = y Ly=Pb, Ux=y L y = P b , Ux = y と同値。
L ( U x ) = P − 1 b L(Ux) = P^{-1} b L ( Ux ) = P − 1 b となり、並べ替えをもとに戻したb = P − 1 b b = P^{-1} b b = P − 1 b を使ってやればいい
L y = b Ly = b L y = b (前進代入)は
y i = b i − ∑ j = 1 i − 1 l i j y j y_i = b_i - \sum^{i-1}_{j=1} l_{ij} y_j y i = b i − j = 1 ∑ i − 1 l ij y j このy y y は枢軸選択つきの前進消去で得られる( b 1 ( 1 ) , ⋯ , b n ( n ) ) ⊤ \left(b_1^{(1)}, \cdots, b_n^{(n)}\right)^{\top} ( b 1 ( 1 ) , ⋯ , b n ( n ) ) ⊤ に等しい
U x = y Ux = y Ux = y (後退代入)は消去法と同じく
x i = 1 u i i ( b i ( i ) − ∑ j = i + 1 n u i j x j ) ( i = n , n − 1 , ⋯ , 1 ) x_i=\frac{1}{u_{i i}}\left(b_i^{(i)}-\sum_{j=i+1}^n u_{i j} x_j\right) \quad(i=n, n-1, \cdots, 1) x i = u ii 1 ( b i ( i ) − j = i + 1 ∑ n u ij x j ) ( i = n , n − 1 , ⋯ , 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
bfrom scipy.linalg import lu
P, L, U = lu(A)
# b := P^{-1} b
b_pi = np.linalg.inv(P) @ b
b_pidef forward_substitution(L, b):
# 前進代入
n = L.shape[0]
y_ = np.zeros_like(b, dtype=np.double)
for i in range(n):
sumj = 0
for j in range(i):
sumj += L[i, j] * y_[j]
y_[i] = b[i] - sumj
return y_
def backword_substitution(U, y):
# 後退代入
n = U.shape[0]
x = np.zeros_like(y, dtype=np.double)
for i in range(n-1, -1, -1):
sumj = 0
for j in range(n-1, i-1, -1):
sumj += U[i, j] * x[j]
x[i] = (y[i] - sumj) / U[i, i]
return x
y_ = forward_substitution(L, b_pi)
y_array([23. , 10.71428571, 13.5 ])
x = backword_substitution(U, y_)
x逆行列を解く ¶ I = ( e 1 , ⋯ , e n ) I=(e_1, \cdots, e_n) I = ( e 1 , ⋯ , e n ) として
を解く
n = A.shape[0]
I = np.eye(n)
Iarray([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
X = np.zeros_like(I)
for i in range(n):
b = I[:, i]
b_pi = np.linalg.inv(P) @ b
y_ = forward_substitution(L, b_pi)
x = backword_substitution(U, y_)
X[:, i] = x
array([[-1.778, 0.889, -0.111],
[ 1.556, -0.778, 0.222],
[-0.111, 0.222, -0.111]])
np.linalg.inv(A).round(3)array([[-1.778, 0.889, -0.111],
[ 1.556, -0.778, 0.222],
[-0.111, 0.222, -0.111]])