F#で数値・線形代数計算をするためのライブラリ紹介(F# PowerPack, F# MathProvider)
http://d.hatena.ne.jp/teramonagi/20111215/1323874810#20111215fn6
を見ると、F# PowerPack と F# MathProvider を使うと、逆行列が簡単に解けます。
#r "FSharp.PowerPack.dll"; #r @"D:toolsMathProviderNet 4.5MathProvider.dll"; let A = matrix [[3.0; 1.0;]; [2.0; 5.0;]] let det = MathProvider.LinearAlgebra.det A let inv = MathProvider.LinearAlgebra.inv A
こんな風にすると、
val A : matrix = matrix [[3.0; 1.0] [2.0; 5.0]] val det : float = 13.0 val inv : matrix = matrix [[0.3846153846; -0.07692307692] [-0.1538461538; 0.2307692308]]
と出ます。めでたしめでたし…なのは、後から知ったわけで、実は F# MathProvider は必ず Blas.dll と lapack.dll が必要なのかと思って躊躇してたんですよね。できることならば、F# だけで作りたい、というか、ストアアプリとか自前で高速化(有限要素法で1万節点とか)を考えると、中身を理解しておきたい、という願望があったので…車輪の再発明をしてしまいました。
let inv ( A' : matrix ) = let A = A'.Copy() let n = A.NumCols-1 let B = Matrix.identity A.NumCols for k in 0..n do // akkを1にする let p = A.[k,k] for j in 0..n do A.[k,j] <- A.[k,j] / p B.[k,j] <- B.[k,j] / p // k行以外から引く for i in 0..n do if i <> k then let d = A.[i,k] for j in 0..n do A.[i,j] <- A.[i,j] - A.[k,j] * d B.[i,j] <- B.[i,j] - B.[k,j] * d B
逆行列の計算
http://www.asahi-net.or.jp/~uc3k-ymd/Lesson/Section03/invmat.html
を参考にしてべたべたな手計算の方法をF#に直しています。結果が0になるところも計算してしまって無駄が多いので、高速化のし甲斐がある…というか、Fortran とか C言語とかのコードもあちこちにあるので、それらをコンバートしたほうが良いかも。
ただし、有限要素法で使う逆行列の場合には、対称性をうまく利用したり計算量を減らすことができるので、一般的な逆行列よりも手を加えたほうが実用的かもしれません。が、ADVENTURE のように大規模な構造(1億節点など)とは違って、節点数のオーダーが違えば、最近のPCならばさして高速化せずとも結構なところまでいけるのでは?と考えています。これは先行き試してみたいところです。
実はこのF#のコード、半日ほどかかったんですよね。なかなか数値が合わなくて苦労したのは、F#の文法に馴れていなかったためもあるのですが、既存の Fortran コードとは別の形で実装して手計算そのままを移したためです。まあ、勉強になったからいいか。正月だし。
これが「できたーっ!!!」後に気付いたのが、F# MathProvider の実装です。Blas.dll を使わない場合には、自前の F# コードを使うようにできているんですね。なるほど。
以下が抜粋、LU分解と上三角を使っています。
/// Given A[n,n] find it's inverse. /// This call may fail. let Inverse a = if HaveService() then LinearAlgebraService.inverse a else LinearAlgebraManaged.Inverse a let Inverse A = let (n,m) = matrixDims A if n <> m then invalid_arg "Matrix must be square when computing its inverse." let P,L,U = LU A (SolveTriangularLinearSystems U (SolveTriangularLinearSystems L ((Matrix.identity n).PermuteRows P) true) false) let LU A = let (nA,mA) = matrixDims A if nA<>mA then invalid_arg "lu: not square"; let L = Matrix.zero nA nA let U = Matrix.copy A let P = [| 0 .. nA-1 |] let abs (x:float) = System.Math.Abs x let swapR X i j = // REVIEW should we make this a general method? let (nX,mX) = matrixDims X let t = X.[i .. i,0 .. ] for k=0 to mX-1 do X.[i,k] <- X.[j,k] X.[j,k] <- t.[0,k] done 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 (((*P.Length,*)Permutation.ofArray P),L + Matrix.identity nA,U) let SolveTriangularLinearSystems K B isLower = if isLower then let (nK,mK) = matrixDims K let (nB,mB) = matrixDims B if nK<>mK || nB<> nK then invalid_arg "Cannot do backward substitution on non-square matrices." let X = Matrix.zero nK mK for i=0 to nK-1 do for k=0 to mB-1 do let s = ref B.[i,k] for j=0 to i-1 do s := !s - K.[i,j] * X.[j,k] done X.[i,k] <- !s / K.[i,i] done done X else let (nK,mK) = matrixDims K let (nB,mB) = matrixDims B if nK<>mK || nB<> nK then invalid_arg "Cannot do backward substitution on non-square matrices." let X = Matrix.zero nK mK for i=0 to nK-1 do for k=0 to mB-1 do let s = ref B.[nK-i-1,k] for j=0 to i-1 do s := !s - K.[nK-i-1,nK-j-1] * X.[nK-j-1,k] done X.[nK-i-1,k] <- !s / K.[nK-i-1,nK-i-1] done done X
まあ、数式は理解して使ったほうがいいので、しばらくは自前で実装ということで。