旧タイプなので、連立一次方程式を解くときに逆行列を使って解けばOK…と頭から思い込んでいたのですが、Gaussの消去法を使うと逆行列は出てこなくて「?」となるわけです。上三角化を使うのだから、LU分解でいいんですよね。
LU分解で行列式と逆行列の計算 その3: メモブログ
http://sssiii.seesaa.net/article/379560261.html
のところから、
連立1次方程式 I
http://nalab.mind.meiji.ac.jp/~mk/labo/text/linear-eq-1.pdf
のPDFを読んで、なるほど、LU分解で十分ですね。ってことで自前で実装してみます。
4 LU分解
http://akita-nct.jp/yamamoto/lecture/2004/5E/linear_equations/text/html/node4.html#eq:LU____________
を参考にして式を流用します。最初、手順がうまくわからなくて、自分で 4×4 の行列を作って手計算をした後に、数式に直して、元の式と一致することを確認。なるほど、行単位じゃなくて「列単位」で計算するので、計算する順番は、β11,α21,α31,α41 で計算した後に、β12,β22,α32,α42 と計算していくんですね。もう一度読み直したらそう書いてあるし…
出来上がったコードがこんな感じ。
/// LU分解 let LU ( A : matrix ) = let n = A.NumCols let L = Matrix.identity n let U = Matrix.zero n n for j in 0..n-1 do // Uを計算 for i in 0..j do U.[i,j] <- A.[i,j] for k in 0..i-1 do U.[i,j] <- U.[i,j] - L.[i,k]*U.[k,j] // Lを計算 for i in j+1..n-1 do L.[i,j] <- A.[i,j] for k in 0..j-1 do L.[i,j] <- L.[i,j] - L.[i,k]*U.[k,j] L.[i,j] <- L.[i,j] / U.[j,j] // 結果を返す (L,U) [/code] <p> 次のように行列を設定しておくと </p> [code lang="csharp"] let A = matrix [[1.0;2.0;1.0]; [2.0;1.0;0.0]; [1.0;1.0;2.0]] LU A
こんな感じで、L と U にして返してくれます。
val it : matrix * matrix = (matrix [[1.0; 0.0; 0.0] [2.0; 1.0; 0.0] [1.0; 0.3333333333; 1.0]], matrix [[1.0; 2.0; 1.0] [0.0; -3.0; -2.0] [0.0; 0.0; 1.666666667]])
先のページにも書いてあるのですが、Ujj で割ってしまうので、ピボット選択は必須…なのか?(ちょっとよくわからず)。浮動小数点だったら大丈夫な気もするのですが、これはあとで確認してみます。まあ、割り算のところにチェックを入れるのは数値計算の基本なので、なるべく割り算で誤差が出ないようにする方向でよいかと。
ちなみに F# MathProvider を使うと
let (p,L,U) = MathProvider.LinearAlgebra.lu A
として、次なる結果を得られます。
val p : (int -> int) val U : matrix = matrix [[2.0; 1.0; 0.0] [0.0; 1.5; 1.0] [0.0; 0.0; 1.666666667]] val L : matrix = matrix [[1.0; 0.0; 0.0] [0.5; 1.0; 0.0] [0.5; 0.3333333333; 1.0]]
MathProvider の LU 分解のコードはもっとシンプルで、こんな感じになっています。うまく交互に計算すると L のほうはほとんど計算せずに済みみたいです。行列数が多くなると swapR のパフォーマンスが問題になりますが、有限要素法以外だったら大丈夫かと。
for i=0 to nA-2 do
let mutable maxi = i // Find the biggest pivot element.
for k=i+1 to nA-1 do
if abs(U.[maxi,i]) < abs(U.[k,i]) then maxi <- k
done
//let maxi = maxIndex (i+1) (nA-1) (fun k -> abs(U.[k,i]))
if maxi <> i then
swapR U i maxi // Swap rows if needed.
swapR L i maxi // REVIEW can be made more performant.
let t = P.[i]
P.[i] <- P.[maxi]
P.[maxi] <- t
for j=i+1 to nA-1 do
L.[j,i] <- U.[j,i] / U.[i,i]
for k=i+1 to nA-1 do
U.[j,k] <- U.[j,k] - L.[j,i] * U.[i,k]
done
U.[j,i] <- 0.0
done
done
[/code]
sum のところは、もっと F# らしく書き換えられないかな、と思案中です。コードが短くなるしΣを使ったほうが、数式を同じになるので。