views:

439

answers:

1

Hi there!

This is a relatively long post. F# has a matrix and vector type(in PowerPack not in the Core) now. This is great! Even Python's numerical computing ability is from the third part.

But the functions provided there is limited to the matrix and vector arithmetic, so to do inversion, decompositions etc. we still need to use another library. I am now using the latest dnAnalytics, which is merging into Math.Net project. But Math.Net project has no updates to the public for more than a whole year now, I don't know if they have a plan to continue.

I did the following wrapper, this wrapper uses Matlab-like functions to do simple linear algebra. As I am new to F# and FP, would you please give some advices to improve the wrapper and code? Thanks!

#r @"D:\WORK\tools\dnAnalytics_windows_x86\bin\dnAnalytics.dll"
#r @"FSharp.PowerPack.dll"

open dnAnalytics.LinearAlgebra
open Microsoft.FSharp.Math
open dnAnalytics.LinearAlgebra.Decomposition

// F# matrix -> ndAnalytics DenseMatrix
let mat2dnmat (mat:matrix) = 
    let m = new DenseMatrix(mat.ToArray2D())
    m

// ndAnalytics DenseMatrix -> F# matrix 
let dnmat2mat (dnmat:DenseMatrix) = 
    let n = dnmat.Rows
    let m = dnmat.Columns
    let mat = Matrix.create n m 0.
    for i=0 to n-1 do
        for j=0 to m-1 do
            mat.[i,j] <- dnmat.Item(i,j)
    mat

// random matrix
let randmat n m =
    let r = new System.Random()
    let ranlist m = 
        [ for i in 1..m do yield r.NextDouble() ]
    matrix ([1..n] |> List.map (fun x-> ranlist m))

// is square matrix
let issqr (m:matrix) =
    let n, m = m.Dimensions
    n = m

// is postive definite 
let ispd m =
    if not (issqr m) then false
    else 
        let m1 = mat2dnmat m
        let qrsolver = dnAnalytics.LinearAlgebra.Decomposition.Cholesky(m1)
        qrsolver.IsPositiveDefinite()

// determinant
let det m =
    let m1 = mat2dnmat m
    let lusolver = dnAnalytics.LinearAlgebra.Decomposition.LU(m1)
    lusolver.Determinant ()

// is full rank
let isfull m =
    let m1 = mat2dnmat m
    let qrsolver = dnAnalytics.LinearAlgebra.Decomposition.GramSchmidt(m1)
    qrsolver.IsFullRank()

// rank
let rank m =
    let m1 = mat2dnmat m
    let svdsolver = dnAnalytics.LinearAlgebra.Decomposition.Svd(m1, false)
    svdsolver.Rank()

// inversion by lu
let inv m =
    let m1 = mat2dnmat m
    let lusolver = dnAnalytics.LinearAlgebra.Decomposition.LU(m1)
    lusolver.Inverse()

// lu
let lu m =
    let m1 = mat2dnmat m
    let lusolver = dnAnalytics.LinearAlgebra.Decomposition.LU(m1)
    let l = dnmat2mat (DenseMatrix (lusolver.LowerFactor ()))
    let u = dnmat2mat (DenseMatrix (lusolver.UpperFactor ()))
    (l,u)

// qr 
let qr m =
    let m1 = mat2dnmat m
    let qrsolver = dnAnalytics.LinearAlgebra.Decomposition.GramSchmidt(m1)
    let q = dnmat2mat (DenseMatrix (qrsolver.Q()))
    let r = dnmat2mat (DenseMatrix (qrsolver.R()))
    (q, r)

// svd 
let svd m =
    let m1 = mat2dnmat m
    let svdsolver = dnAnalytics.LinearAlgebra.Decomposition.Svd(m1, true)
    let u = dnmat2mat (DenseMatrix (svdsolver.U()))
    let w = dnmat2mat (DenseMatrix  (svdsolver.W()))
    let vt = dnmat2mat (DenseMatrix (svdsolver.VT()))
    (u, w, vt.Transpose)

and test code

(* todo: read matrix market format   ref: http://math.nist.gov/MatrixMarket/formats.html *)
let readmat (filename:string) = 
    System.IO.File.ReadAllLines(filename) |> Array.map (fun x-> (x |> String.split [' '] |> List.toArray |> Array.map float))
    |> matrix

let timeit f str= 
    let watch = new System.Diagnostics.Stopwatch()
    watch.Start()
    let res = f()
    watch.Stop()
    printfn "%s Needed %f ms" str watch.Elapsed.TotalMilliseconds
    res

let test() = 
    let testlu() = 
        for i=1 to 10 do
            let a,b = lu (randmat 1000 1000)
            ()
        ()
    let testsvd() =
        for i=1 to 10 do
            let u,w,v = svd (randmat 300 300)
            ()
        ()
    let testdet() =
        for i=1 to 10 do
            let d = det (randmat 650 650)
            ()
        ()
    timeit testlu "lu" 
    timeit testsvd "svd"
    timeit testdet "det"

I also compared with matlab

t = cputime; for i=1:10, [l,u] = lu(rand(1000,1000)); end; e = cputime-t
t = cputime; for i=1:10, [u,w,vt] = svd(rand(300,300)); end; e = cputime-t
t = cputime; for i=1:10, d = det(rand(650,650)); end; e = cputime-t

The timings (Duo Core 2.0GH, 2GB memory, Matlab 2009a)

f#:
lu Needed 8875.941700 ms
svd Needed 14469.102900 ms
det Needed 2820.304600 ms
matlab:
  lu 3.7752
  svd 5.7408
  det 1.2636

matlab is about two times faster. This is reasonable, as a native R also has similar results.

+3  A: 

I think that both dnmat2mat and randmat could be simplified by using Matrix.init:

let dnmat2mat (dnmat : DenseMatrix) =
  Matrix.init (dnmat.Rows) (dnmat.Columns) (fun i j -> dnmat.[i,j])

let randmat n m =
  let r = System.Random()
  Matrix.init n m (fun _ _ -> r.NextDouble())
kvb