ANN: BLAS Bindings for Haskell, version 0.6
There’s a new version of the BLAS bindings out. I put a lot of work
into it and did a pretty massive overhaul of the code. The highlight of the new
release is that now you can do operations in the ST
monad. Also, I fixed a lot
of organizational issues (no more orphan instances!), and cleaned up the
interface a bit. There are a few performance improvements, too (I shaved half
second off that old
benchmark). The
downside is that I completely broke backwards compatibility, but since as far as
I can tell I only have two users, I’m not too worried about that.
People have been clamoring for a tutorial, but unfortunately I still don’t have time. My Orals are in ten days, and this stuff is not really core to my research. Maybe in a few months I’ll do something.
In the mean time, I did manage to come up with some sample code. Here’s a Fortan90 routine for recursively computing an LU decomposition with row pivoting, taken from Jack Dongarra and Piotr Luszczek’s chapter (PDF) in Beautiful Code:
recursive subroutine rdgetrf(m, n, a, lda, ipiv, info)
implicit none
integer, intent(in) :: m, n, lda
double precision, intent(inout) :: a(lda,*)
integer, intent(out) :: ipiv(*)
integer, intent(out) :: info
integer :: mn, nleft, nright, i
double precision :: tmp
double precision :: pone, negone, zero
parameter (pone=1.0d0)
parameter (negone=-1.0d0)
parameter (zero=0.0d0)
intrinsic min
integer idamax
external dgemm, dtrsm, dlaswp, idamax, dscal
mn = min(m, n)
if (mn .gt. 1) then
nleft = mn / 2
nright = n - nleft
call rdgetrf(m, nleft, a, lda, ipiv, info)
if (info .ne. 0) return
call dlaswp(nright, a(1, nleft+1), lda, 1, nleft, ipiv, 1)
call dtrsm('L', 'L', 'N', 'U', nleft, nright, pone, a, lda,
$ a(1, nleft+1), lda)
call dgemm('N', 'N', m-nleft, nright, nleft, negone,
$ a(nleft+1,1) , lda, a(1, nleft+1), lda, pone,
$ a(nleft+1, nleft+1), lda)
call rdgetrf(m - nleft, nright, a(nleft+1, nleft+1), lda,
$ ipiv(nleft+1), info)
if (info .ne. 0) then
info = info + nleft
return
end if
do i = nleft+1, m
ipiv(i) = ipiv(i) + nleft
end do
call dlaswp(nleft, a, lda, nleft+1, mn, ipiv, 1)
else if (mn .eq. 1) then
i = idamax(m, a, 1)
ipiv(1) = i
tmp = a(i, 1)
if (tmp .ne. zero .and. tmp .ne. -zero) then
call dscal(m, pone/tmp, a, 1)
a(i,1) = a(1,1)
a(1,1) = tmp
else
info = 1
end if
end if
return
end
Here’s the same program in Haskell, using version 0.6 of the blas bindings:
module LU ( luFactorize ) where
import BLAS.Elem( BLAS3 )
import Control.Monad
import Control.Monad.ST
import Data.Matrix.Dense
import Data.Matrix.Dense.ST
import Data.Matrix.Tri
import Data.Vector.Dense.ST
luFactorize :: (BLAS3 e) => STMatrix s (m,n) e -> ST s (Either Int [Int])
luFactorize a
| mn > 1 =
let nleft = mn `div` 2
(a_1, a_2) = splitColsAt nleft a
(a11, a21) = splitRowsAt nleft a_1
(a12, a22) = splitRowsAt nleft a_2
in luFactorize a_1 >>=
either (return . Left) (\pivots -> do
zipWithM_ (swapRows a_2) [0..] pivots
doSolveMat_ (lowerU a11) a12
doSApplyAddMat (-1) a21 a12 1 a22
luFactorize a22 >>=
either (return . Left . (nleft+)) (\pivots' -> do
zipWithM_ (swapRows a21) [0..] pivots'
return (Right $ pivots ++ map (nleft+) pivots')
)
)
| mn == 1 =
let x = colView a 0
in getWhichMaxAbs x >>= \(i,e) ->
if (e /= 0)
then do
scaleBy (1/e) x
readElem x 0 >>= writeElem x i
writeElem x 0 e
return $ Right [i]
else
return $ Left 0
| otherwise =
return (Right [])
where
(m,n) = shape a
mn = min m n
The Haskell version returns Left
with a column index in the event of failure,
and Right
with a list of the pivot swaps in the case of success. It makes
exactly the same BLAS calls as the Fortran90 version. The performance of the two
versions should be close, especially for large inputs. (Anyone want to verify
this?)
Some day it will be possible to do serious scientific computing in Haskell without much effort. This is one small step towards that goal.