LU分解#
与えられた行列
をLU分解と呼ぶ。
分解すると何が嬉しいのか?#
行列式や一次方程式を少ない計算量で求められる材料として
例:連立1次方程式・逆行列#
例えば様々な
様々な
を解くことができる。
計算量はLU分解に
例:行列式#
三角行列の行列式は対角行列の積になるため、
計算手順(ドゥーリトル法)#
となるため、
これらを解いていく
一般化#
もっと一般化すると次のようになる
とすると
とできるので、この表記を使っていく。
反復計算するので
と置くと、
となる。残ったブロック行列を
ゼロじゃない要素を
とすると(ここで
となる。ここで
のように頭に0をつけてサイズを戻せば
から
$
となる。
これを何度も繰り返していくと、最後は残る行列がゼロ行列になる
そうしたら
のように行列
pythonの例#
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)
A2
array([[ 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
array([[0.]])
# 先頭にゼロを足して次元を合わせる
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
L
array([[1., 0.],
[3., 1.]])
U = np.array([u1, u2])
U
array([[ 1., 2.],
[ 0., -2.]])
L @ U
array([[1., 2.],
[3., 4.]])
行列式をLU分解で求める#
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分解すると
であり、変数
とおき代入すると
になることから変数
という計算
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サブルーチン
b = np.array([1, 2])
from scipy.linalg.lapack import dgetrf, dgetrs
LU, P, info = dgetrf(A)
dgetrs(LU, P, b)
(array([0. , 0.5]), 0)
y = np.linalg.inv(L) @ b
y
array([ 1., -1.])
x = np.linalg.inv(U) @ y
x
array([0. , 0.5])
# estimate b
A @ x
array([1., 2.])
逆行列#
あるいは
np.linalg.inv(A)
array([[-2. , 1. ],
[ 1.5, -0.5]])
np.linalg.inv(U) @ np.linalg.inv(L)
array([[-2. , 1. ],
[ 1.5, -0.5]])
y = L @ b
b = np.array([1, 2])
scipy.linalg.lu#
scipy.linalg.lu — SciPy v1.12.0 Manual
from scipy.linalg import lu
P, L, U = lu(A)
P
array([[0., 1.],
[1., 0.]])
L
array([[1. , 0. ],
[0.33333333, 1. ]])
U
array([[3. , 4. ],
[0. , 0.66666667]])
P @ L @ U
array([[1., 2.],
[3., 4.]])
置換行列
となり、並べ替えをもとに戻した
この
で解ける
# データの作成
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
b
array([14, 32, 23])
from scipy.linalg import lu
P, L, U = lu(A)
# b := P^{-1} b
b_pi = np.linalg.inv(P) @ b
b_pi
array([23., 14., 32.])
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
y_ = forward_substitution(L, b_pi)
y_
array([23. , 10.71428571, 13.5 ])
x = backword_substitution(U, y_)
x
array([1., 2., 3.])
逆行列を解く#
を解く
n = A.shape[0]
I = np.eye(n)
I
array([[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
X.round(3)
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]])
参考文献#
ドゥーリトル法:LU分解 - Wikipedia
一般化した計算手順:『プログラミングのための線形代数』