matrix 0.1.1 → 0.2
raw patch · 4 files changed
+523/−40 lines, 4 filesdep +criteriondep +matrixPVP ok
version bump matches the API change (PVP)
Dependencies added: criterion, matrix
API changes (from Hackage documentation)
+ Data.Matrix: colVector :: Vector a -> Matrix a
+ Data.Matrix: combineRows :: Num a => Int -> a -> Int -> Matrix a -> Matrix a
+ Data.Matrix: detLU :: (Ord a, Fractional a) => Matrix a -> a
+ Data.Matrix: detLaplace :: Num a => Matrix a -> a
+ Data.Matrix: diagProd :: Num a => Matrix a -> a
+ Data.Matrix: forceMatrix :: Matrix a -> Matrix a
+ Data.Matrix: getCol :: Int -> Matrix a -> Vector a
+ Data.Matrix: getDiag :: Matrix a -> Vector a
+ Data.Matrix: getRow :: Int -> Matrix a -> Vector a
+ Data.Matrix: luDecomp :: (Ord a, Fractional a) => Matrix a -> (Matrix a, Matrix a, Matrix a, a)
+ Data.Matrix: mapRow :: (Int -> a -> a) -> Int -> Matrix a -> Matrix a
+ Data.Matrix: minorMatrix :: Int -> Int -> Matrix a -> Matrix a
+ Data.Matrix: multStd :: Num a => Matrix a -> Matrix a -> Matrix a
+ Data.Matrix: multStrassen :: Num a => Matrix a -> Matrix a -> Matrix a
+ Data.Matrix: multStrassenMixed :: Num a => Matrix a -> Matrix a -> Matrix a
+ Data.Matrix: permMatrix :: Num a => Int -> Int -> Int -> Matrix a
+ Data.Matrix: rowVector :: Vector a -> Matrix a
+ Data.Matrix: scaleMatrix :: Num a => a -> Matrix a -> Matrix a
+ Data.Matrix: scaleRow :: Num a => a -> Int -> Matrix a -> Matrix a
+ Data.Matrix: setElem :: a -> (Int, Int) -> Matrix a -> Matrix a
+ Data.Matrix: switchRows :: Int -> Int -> Matrix a -> Matrix a
+ Data.Matrix: trace :: Num a => Matrix a -> a
Files
- Data/Matrix.hs +466/−37
- bench/mult.hs +25/−0
- matrix.cabal +23/−3
- readme.md +9/−0
Data/Matrix.hs view
@@ -1,35 +1,70 @@ --- | Matrix datatype an basic operations. +-- | Matrix datatype and operations. +-- +-- Every provided example has been tested. module Data.Matrix ( -- * Matrix type Matrix , prettyMatrix , nrows , ncols + , forceMatrix -- * Builders - , zero - , identity , matrix , fromLists + , rowVector + , colVector + -- ** Special matrices + , zero + , identity + , permMatrix -- * Accessing , getElem , (!) + , getRow , getCol + , getDiag -- * Manipulating matrices + , setElem , transpose , extendTo - -- * Working with blocks + , mapRow + -- * Submatrices -- ** Splitting blocks , submatrix + , minorMatrix , splitBlocks -- ** Joining blocks , (<|>) , (<->) , joinBlocks + -- * Matrix multiplication + -- ** About matrix multiplication + -- $mult + + -- ** Functions + , multStd + , multStrassen + , multStrassenMixed + -- * Linear transformations + , scaleMatrix + , scaleRow + , combineRows + , switchRows + -- * Decompositions + , luDecomp + -- * Properties + , trace , diagProd + -- ** Determinants + , detLaplace + , detLU ) where import Data.Monoid import Control.DeepSeq import qualified Data.Vector as V +import qualified Data.Vector.Mutable as MV +import Data.List (maximumBy) ------------------------------------------------------- ------------------------------------------------------- ---- MATRIX TYPE +-- | Type of matrices. data Matrix a = M { nrows :: !Int -- ^ Number of rows. , ncols :: !Int -- ^ Number of columns. @@ -40,7 +75,7 @@ sizeStr :: Int -> Int -> String sizeStr n m = show n ++ "x" ++ show m --- | Display a matrix as a 'String'. +-- | Display a matrix as a 'String' using the 'Show' instance of its elements. prettyMatrix :: Show a => Matrix a -> String prettyMatrix m@(M _ _ v) = unlines [ "( " <> unwords (fmap (\j -> fill mx $ show $ m ! (i,j)) [1..ncols m]) <> " )" | i <- [1..nrows m] ] @@ -54,6 +89,12 @@ instance NFData a => NFData (Matrix a) where rnf (M _ _ v) = rnf v +-- | /O(rows*cols)/. Similar to 'V.force', drop any extra memory. +-- +-- Useful when using 'submatrix' from a big matrix. +forceMatrix :: Matrix a -> Matrix a +forceMatrix (M n m v) = M n m $ V.force v + ------------------------------------------------------- ------------------------------------------------------- ---- ENCODING/DECODING @@ -89,6 +130,14 @@ ---- BUILDERS -- | The zero matrix of the given size. +-- +-- > zero n m = +-- > n +-- > 1 ( 0 0 ... 0 0 ) +-- > 2 ( 0 0 ... 0 0 ) +-- > ( ... ) +-- > ( 0 0 ... 0 0 ) +-- > n ( 0 0 ... 0 0 ) zero :: Num a => Int -- ^ Rows -> Int -- ^ Columns @@ -96,6 +145,12 @@ zero n m = M n m $ V.replicate (n*m) 0 -- | Generate a matrix from a generator function. +-- Example of usage: +-- +-- > ( 1 0 -1 -2 ) +-- > ( 3 2 1 0 ) +-- > ( 5 4 3 2 ) +-- > matrix 4 4 $ \(i,j) -> 2*i - j = ( 7 6 5 4 ) matrix :: Int -- ^ Rows -> Int -- ^ Columns -> ((Int,Int) -> a) -- ^ Generator function @@ -103,17 +158,72 @@ matrix n m f = M n m $ V.generate (n*m) (f . decode m) -- | Identity matrix of the given order. +-- +-- > identity n = +-- > n +-- > 1 ( 1 0 ... 0 0 ) +-- > 2 ( 0 1 ... 0 0 ) +-- > ( ... ) +-- > ( 0 0 ... 1 0 ) +-- > n ( 0 0 ... 0 1 ) +-- identity :: Num a => Int -> Matrix a identity n = matrix n n $ \(i,j) -> if i == j then 1 else 0 +-- | Create a matrix from an non-empty list of non-empty lists. +-- /Each list must have the same number of elements/. +-- For example: +-- +-- > fromLists [ [1,2,3] ( 1 2 3 ) +-- > , [4,5,6] ( 4 5 6 ) +-- > , [7,8,9] ] = ( 7 8 9 ) +-- fromLists :: [[a]] -> Matrix a fromLists xss = M (length xss) (length $ head xss) $ mconcat $ fmap V.fromList xss +-- | /O(1)/. Represent a vector as a one row matrix. +rowVector :: V.Vector a -> Matrix a +rowVector v = M 1 (V.length v) v + +-- | /O(1)/. Represent a vector as a one column matrix. +colVector :: V.Vector a -> Matrix a +colVector v = M (V.length v) 1 v + +-- | Permutation matrix. +-- +-- > permMatrix n i j = +-- > i j n +-- > 1 ( 1 0 ... 0 ... 0 ... 0 0 ) +-- > 2 ( 0 1 ... 0 ... 0 ... 0 0 ) +-- > ( ... ... ... ) +-- > i ( 0 0 ... 0 ... 1 ... 0 0 ) +-- > ( ... ... ... ) +-- > j ( 0 0 ... 1 ... 0 ... 0 0 ) +-- > ( ... ... ... ) +-- > ( 0 0 ... 0 ... 0 ... 1 0 ) +-- > n ( 0 0 ... 0 ... 0 ... 0 1 ) +-- +-- When @i == j@ it reduces to 'identity' @n@. +-- +permMatrix :: Num a + => Int -- ^ Size of the matrix. + -> Int -- ^ Permuted row 1. + -> Int -- ^ Permuted row 2. + -> Matrix a -- ^ Permutation matrix. +permMatrix n r1 r2 | r1 == r2 = identity n +permMatrix n r1 r2 = matrix n n f + where + f (i,j) + | i == r1 = if j == r2 then 1 else 0 + | i == r2 = if j == r1 then 1 else 0 + | i == j = 1 + | otherwise = 0 + ------------------------------------------------------- ------------------------------------------------------- ---- ACCESSING --- | Get an element of a matrix. +-- | /O(1)/. Get an element of a matrix. getElem :: Int -- ^ Row -> Int -- ^ Column -> Matrix a -- ^ Matrix @@ -123,15 +233,43 @@ ++ sizeStr n m ++ " matrix." | otherwise = v V.! encode m (i,j) --- | Nice alias for 'getElem'. +-- | Short alias for 'getElem'. (!) :: Matrix a -> (Int,Int) -> a m ! (i,j) = getElem i j m +-- | /O(1)/. Get a row of a matrix as a vector. +getRow :: Int -> Matrix a -> V.Vector a +getRow i m = V.slice (encode k (i,1)) k $ mvect m + where + k = ncols m + +-- | /O(rows)/. Get a column of a matrix as a vector. +getCol :: Int -> Matrix a -> V.Vector a +getCol j a@(M n _ _) = V.generate n $ \i -> a ! (i+1,j) + +-- | /O(min rows cols)/. Diagonal of a /not necessarily square/ matrix. +getDiag :: Matrix a -> V.Vector a +getDiag m = V.generate k $ \i -> m ! (i+1,i+1) + where + k = min (nrows m) (ncols m) + ------------------------------------------------------- ------------------------------------------------------- ---- MANIPULATING MATRICES --- | The transpose of a matrix. +-- | /O(1)/. Replace the value of a cell in a matrix. +setElem :: a -- ^ New value. + -> (Int,Int) -- ^ Position to replace. + -> Matrix a -- ^ Original matrix. + -> Matrix a -- ^ Matrix with the given position replaced with the given value. +setElem x (i,j) (M n m v) = M n m $ V.modify (\mv -> MV.write mv (encode m (i,j)) x) v + +-- | /O(rows*cols)/. The transpose of a matrix. +-- Example: +-- +-- > ( 1 2 3 ) ( 1 4 7 ) +-- > ( 4 5 6 ) ( 2 5 8 ) +-- > transpose ( 7 8 9 ) = ( 3 6 9 ) transpose :: Matrix a -> Matrix a transpose (M n m v) = M m n $ V.backpermute v $ fmap (\k -> let (q,r) = quotRem k n @@ -140,6 +278,13 @@ -- | Extend a matrix to a given size adding zeroes. -- If the matrix already has the required size, nothing happens. +-- The matrix is /never/ reduced in size. +-- Example: +-- +-- > ( 1 2 3 0 0 ) +-- > ( 1 2 3 ) ( 4 5 6 0 0 ) +-- > ( 4 5 6 ) ( 7 8 9 0 0 ) +-- > extendTo 4 5 ( 7 8 9 ) = ( 0 0 0 0 0 ) extendTo :: Num a => Int -- ^ Minimal number of rows. -> Int -- ^ Minimal number of columns. @@ -155,18 +300,38 @@ ------------------------------------------------------- ---- WORKING WITH BLOCKS --- | Extract a submatrix. +-- | Extract a submatrix given row and column limits. +-- Example: +-- +-- > ( 1 2 3 ) +-- > ( 4 5 6 ) ( 2 3 ) +-- > submatrix 1 2 2 3 ( 7 8 9 ) = ( 5 6 ) submatrix :: Int -- ^ Starting row -> Int -- ^ Ending row -> Int -- ^ Starting column -> Int -- ^ Ending column -> Matrix a -> Matrix a +{-# INLINE submatrix #-} submatrix r1 r2 c1 c2 (M _ m v) = M (r2-r1+1) m' $ - mconcat [ V.slice (encode m (r,c1)) m' v | r <- [r1 .. r2] ] + V.concat [ V.unsafeSlice (encode m (r,c1)) m' v | r <- [r1 .. r2] ] where m' = c2-c1+1 +-- | Remove a row and a column from a matrix. +-- Example: +-- +-- > ( 1 2 3 ) +-- > ( 4 5 6 ) ( 1 3 ) +-- > minorMatrix 2 2 ( 7 8 9 ) = ( 7 9 ) +minorMatrix :: Int -- ^ Row @r@ to remove. + -> Int -- ^ Column @c@ to remove. + -> Matrix a -- ^ Original matrix. + -> Matrix a -- ^ Matrix with row @r@ and column @c@ removed. +minorMatrix r c (M n m v) = M (n-1) (m-1) $ + V.ifilter (\k _ -> let (i,j) = decode m k + in i /= r && j /= c ) v + -- | Make a block-partition of a matrix using a given element as reference. -- The element will stay in the bottom-right corner of the top-left corner matrix. -- @@ -192,6 +357,7 @@ -> Matrix a -- ^ Matrix to split. -> (Matrix a,Matrix a ,Matrix a,Matrix a) -- ^ (TL,TR,BL,BR) +{-# INLINE splitBlocks #-} splitBlocks i j a@(M n m _) = ( submatrix 1 i 1 j a , submatrix 1 i (j+1) m a , submatrix (i+1) n 1 j a , submatrix (i+1) n (j+1) m a ) @@ -199,8 +365,9 @@ joinBlocks :: (Matrix a,Matrix a ,Matrix a,Matrix a) -> Matrix a +{-# INLINE joinBlocks #-} joinBlocks (tl,tr,bl,br) = (tl <|> tr) - <-> -- <-- How beautiful is this! + <-> (bl <|> br) -- | Horizontally join two matrices. Visually: @@ -209,12 +376,13 @@ -- -- Where both matrices /A/ and /B/ have the same number of rows. (<|>) :: Matrix a -> Matrix a -> Matrix a +{-# INLINE (<|>) #-} (M n m v) <|> (M n' m' v') | n /= n' = error $ "Horizontal join of " ++ sizeStr n m ++ " and " ++ sizeStr n' m' ++ " matrices." - | otherwise = let v'' = mconcat [ V.slice (encode m (r,1)) m v - <> V.slice (encode m' (r,1)) m' v' - | r <- [1..n] ] + | otherwise = let v'' = V.concat [ V.slice (encode m (r,1)) m v + <> V.slice (encode m' (r,1)) m' v' + | r <- [1..n] ] in M n (m+m') v'' -- | Vertically join two matrices. Visually: @@ -225,22 +393,62 @@ -- -- Where both matrices /A/ and /B/ have the same number of columns. (<->) :: Matrix a -> Matrix a -> Matrix a +{-# INLINE (<->) #-} (M n m v) <-> (M n' m' v') | m /= m' = error $ "Vertical join of " ++ sizeStr n m ++ " and " ++ sizeStr n' m' ++ " matrices." - | otherwise = M (n+n') m $ v <> v' + | otherwise = M (n+n') m $ v V.++ v' ------------------------------------------------------- ------------------------------------------------------- ----- FUNCTOR INSTANCE +---- MATRIX MULTIPLICATION -instance Functor Matrix where - fmap f (M n m v) = M n m $ fmap f v +{- $mult -------------------------------------------------------- -------------------------------------------------------- ----- NUMERICAL INSTANCE +Three methods are provided for matrix multiplication. +* 'multStd': + Matrix multiplication following directly the definition. + This is the best choice when you know for sure that your + matrices are small. + +* 'multStrassen': + Matrix multiplication following the Strassen's algorithm. + Complexity grows slower but also some work is added + partitioning the matrix. Also, it only works on square + matrices of order @2^n@, so if this condition is not + met, it is zero-padded until this is accomplished. + Therefore, its use it is not recommended. + +* 'multStrassenMixed': + This function mixes the 'multStd' and 'multStrassen' methods. + It provides a better performance in general. Method @(@'*'@)@ + of the 'Num' class uses this function because it gives the best + average performance. However, if you know for sure that your matrices are + small, you should use 'multStd' instead, since + 'multStrassenMixed' is going to switch to that function anyway. + +-} + +-- | Standard matrix multiplication by definition. +multStd :: Num a => Matrix a -> Matrix a -> Matrix a +multStd a1@(M n m _) a2@(M n' m' _) + -- Checking that sizes match... + | m /= n' = error $ "Multiplication of " ++ sizeStr n m ++ " and " + ++ sizeStr n' m' ++ " matrices." + | otherwise = multStd_ a1 a2 + +-- | Standard matrix multiplication by definition, without checking if sizes match. +multStd_ :: Num a => Matrix a -> Matrix a -> Matrix a +multStd_ a1@(M n m _) a2@(M _ m' _) = matrix n m' $ \(i,j) -> sum [ a1 ! (i,k) * a2 ! (k,j) | k <- [1 .. m] ] + +first :: (a -> Bool) -> [a] -> a +first f = go + where + go (x:xs) = if f x then x else go xs + go [] = error "first: no element match the condition." + +-- | Strassen's algorithm over square matrices of order @2^n@. strassen :: Num a => Matrix a -> Matrix a -> Matrix a -- Trivial 1x1 multiplication. strassen (M 1 1 v) (M 1 1 v') = M 1 1 $ V.zipWith (*) v v' @@ -267,12 +475,88 @@ c21 = p2 + p4 c22 = p1 - p2 + p3 + p6 -first :: (a -> Bool) -> [a] -> a -first f = go +-- | Strassen's matrix multiplication. +multStrassen :: Num a => Matrix a -> Matrix a -> Matrix a +multStrassen a1@(M n m _) a2@(M n' m' _) + | m /= n' = error $ "Multiplication of " ++ sizeStr n m ++ " and " + ++ sizeStr n' m' ++ " matrices." + | otherwise = + let mx = maximum [n,m,n',m'] + n2 = first (>= mx) $ fmap (2^) [(0 :: Int)..] + b1 = extendTo n2 n2 a1 + b2 = extendTo n2 n2 a2 + in submatrix 1 n 1 m' $ strassen b1 b2 + +strmixFactor :: Int +strmixFactor = 150 + +-- | Strassen's mixed algorithm. +strassenMixed :: Num a => Matrix a -> Matrix a -> Matrix a +strassenMixed a@(M r _ _) b + | r < strmixFactor = multStd_ a b + | odd r = let r' = r + 1 + a' = extendTo r' r' a + b' = extendTo r' r' b + in submatrix 1 r 1 r $ strassenMixed a' b' + | otherwise = joinBlocks (c11,c12,c21,c22) where - go (x:xs) = if f x then x else go xs - go [] = error "first: no element match the condition." + -- Size of the subproblem is halved. + n = quot r 2 + -- Split of the original problem into smaller subproblems. + (a11,a12,a21,a22) = splitBlocks n n a + (b11,b12,b21,b22) = splitBlocks n n b + -- The seven Strassen's products. + p1 = strassenMixed (a11 + a22) (b11 + b22) + p2 = strassenMixed (a21 + a22) b11 + p3 = strassenMixed a11 (b12 - b22) + p4 = strassenMixed a22 (b21 - b11) + p5 = strassenMixed (a11 + a12) b22 + p6 = strassenMixed (a21 - a11) (b11 + b12) + p7 = strassenMixed (a12 - a22) (b21 + b22) + -- Merging blocks + c11 = p1 + p4 - p5 + p7 + c12 = p3 + p5 + c21 = p2 + p4 + c22 = p1 - p2 + p3 + p6 +-- | Mixed Strassen's matrix multiplication. +multStrassenMixed :: Num a => Matrix a -> Matrix a -> Matrix a +multStrassenMixed a1@(M n m _) a2@(M n' m' _) + | m /= n' = error $ "Multiplication of " ++ sizeStr n m ++ " and " + ++ sizeStr n' m' ++ " matrices." + | n < strmixFactor = multStd_ a1 a2 + | otherwise = + let mx = maximum [n,m,n',m'] + n2 = if even mx then mx else mx+1 + b1 = extendTo n2 n2 a1 + b2 = extendTo n2 n2 a2 + in submatrix 1 n 1 m' $ strassenMixed b1 b2 + +------------------------------------------------------- +------------------------------------------------------- +---- FUNCTOR INSTANCE + +instance Functor Matrix where + fmap f (M n m v) = M n m $ fmap f v + +-- | Map a function over a row. +-- Example: +-- +-- > ( 1 2 3 ) ( 1 2 3 ) +-- > ( 4 5 6 ) ( 5 6 7 ) +-- > mapRow (\_ x -> x + 1) 2 ( 7 8 9 ) = ( 7 8 9 ) +-- +mapRow :: (Int -> a -> a) -- ^ Function takes the current column as additional argument. + -> Int -- ^ Row to map. + -> Matrix a -> Matrix a +mapRow f r (M n m v) = + M n m $ V.imap (\k x -> let (i,j) = decode m k + in if i == r then f j x else x) v + +------------------------------------------------------- +------------------------------------------------------- +---- NUMERICAL INSTANCE + instance Num a => Num (Matrix a) where fromInteger = M 1 1 . V.singleton . fromInteger negate = fmap negate @@ -286,15 +570,160 @@ -- Otherwise, trivial zip. | otherwise = M n m $ V.zipWith (+) v v' -- Multiplication of matrices. - (M 1 1 v) * (M 1 1 v') = M 1 1 $ V.zipWith (*) v v' - a1@(M n m _) * a2@(M n' m' _) - -- Checking that sizes match... - | m /= n' = error $ "Multiplication of " ++ sizeStr n m ++ " and " - ++ sizeStr n' m' ++ " matrices." - -- Otherwise, Strassen's Subcubic Matrix Multiplication Algorithm. - | otherwise = - let mx = maximum [n,m,n',m'] - n2 = first (>= mx) $ fmap (2^) [(0 :: Int)..] - b1 = extendTo n2 n2 a1 - b2 = extendTo n2 n2 a2 - in submatrix 1 n 1 m' $ strassen b1 b2+ (*) = multStrassenMixed + +------------------------------------------------------- +------------------------------------------------------- +---- TRANSFORMATIONS + +-- | Scale a matrix by a given factor. +-- Example: +-- +-- > ( 1 2 3 ) ( 2 4 6 ) +-- > ( 4 5 6 ) ( 8 10 12 ) +-- > scaleMatrix 2 ( 7 8 9 ) = ( 14 16 18 ) +scaleMatrix :: Num a => a -> Matrix a -> Matrix a +scaleMatrix = fmap . (*) + +-- | Scale a row by a given factor. +-- Example: +-- +-- > ( 1 2 3 ) ( 1 2 3 ) +-- > ( 4 5 6 ) ( 8 10 12 ) +-- > scaleRow 2 2 ( 7 8 9 ) = ( 7 8 9 ) +scaleRow :: Num a => a -> Int -> Matrix a -> Matrix a +scaleRow = mapRow . const . (*) + +-- | Add to one row a scalar multiple of other row. +-- Example: +-- +-- > ( 1 2 3 ) ( 1 2 3 ) +-- > ( 4 5 6 ) ( 6 9 12 ) +-- > combineRows 2 2 1 ( 7 8 9 ) = ( 7 8 9 ) +combineRows :: Num a => Int -> a -> Int -> Matrix a -> Matrix a +combineRows r1 l r2 m = mapRow (\j x -> x + l * getElem r2 j m) r1 m + +-- | Switch two rows of a matrix. +-- Example: +-- +-- > ( 1 2 3 ) ( 4 5 6 ) +-- > ( 4 5 6 ) ( 1 2 3 ) +-- > switchRows 1 2 ( 7 8 9 ) = ( 7 8 9 ) +switchRows :: Int -- ^ Row 1. + -> Int -- ^ Row 2. + -> Matrix a -- ^ Original matrix. + -> Matrix a -- ^ Matrix with rows 1 and 2 switched. +switchRows r1 r2 a@(M n m _) = matrix n m f + where + f (i,j) + | i == r1 = a ! (r2,j) + | i == r2 = a ! (r1,j) + | otherwise = a ! ( i,j) + +------------------------------------------------------- +------------------------------------------------------- +---- DECOMPOSITIONS + +-- LU DECOMPOSITION + +-- | Matrix LU decomposition with /partial pivoting/. +-- The result for a matrix /M/ is given in the format /(U,L,P,d)/ where: +-- +-- * /U/ is an upper triangular matrix. +-- +-- * /L/ is an /unit/ lower triangular matrix. +-- +-- * /P/ is a permutation matrix. +-- +-- * /d/ is the determinant of /P/. +-- +-- * /PM = LU/. +-- +-- These properties are only guaranteed when the input matrix is invertible. +-- An additional property matches thanks to the strategy followed for pivoting: +-- +-- * /L_(i,j)/ <= 1, for all /i,j/. +-- +-- This follows from the maximal property of the selected pivots, which also +-- leads to a better numerical stability of the algorithm. +-- +-- Example: +-- +-- > ( 1 2 0 ) ( 2 0 2 ) ( 1 0 0 ) ( 0 0 1 ) +-- > ( 0 2 1 ) ( 0 2 -1 ) ( 1/2 1 0 ) ( 1 0 0 ) +-- > luDecomp ( 2 0 2 ) = ( ( 0 0 2 ) , ( 0 1 1 ) , ( 0 1 0 ) , 1 ) +luDecomp :: (Ord a, Fractional a) => Matrix a -> (Matrix a,Matrix a,Matrix a,a) +luDecomp a = recLUDecomp a i i 1 1 n + where + n = nrows a + i = identity n + +recLUDecomp :: (Ord a, Fractional a) + => Matrix a -- ^ U + -> Matrix a -- ^ L + -> Matrix a -- ^ P + -> a -- ^ d + -> Int -- ^ Current row + -> Int -- ^ Total rows + -> (Matrix a,Matrix a,Matrix a,a) +recLUDecomp u l p d k n = + if k == n then (u,l,p,d) + else recLUDecomp u'' l'' p' d' (k+1) n + where + -- Pivot strategy: maximum value in absolute value below the current row. + i = maximumBy (\x y -> compare (abs $ u ! (x,k)) (abs $ u ! (y,k))) [ k .. n ] + -- Switching to place pivot in current row. + u' = switchRows k i u + l' = M n n $ + V.modify (\mv -> mapM_ (\j -> do + MV.write mv (encode n (i,j)) $ l ! (k,j) + MV.write mv (encode n (k,j)) $ l ! (i,j) + ) [1 .. k-1] ) $ mvect l + p' = switchRows k i p + -- Permutation determinant + d' = if i == k then d else negate d + -- Cancel elements below the pivot. + (u'',l'') = go u' l' (k+1) + ukk = u' ! (k,k) + go u_ l_ j = + if j > n then (u_,l_) + else let x = (u_ ! (j,k)) / ukk + in go (combineRows j (-x) k u_) (setElem x (j,k) l_) (j+1) + +------------------------------------------------------- +------------------------------------------------------- +---- PROPERTIES + +-- | Sum of the elements in the diagonal. See also 'getDiag'. +-- Example: +-- +-- > ( 1 2 3 ) +-- > ( 4 5 6 ) +-- > trace ( 7 8 9 ) = 15 +trace :: Num a => Matrix a -> a +trace = V.sum . getDiag + +-- | Product of the elements in the diagonal. See also 'getDiag'. +-- Example: +-- +-- > ( 1 2 3 ) +-- > ( 4 5 6 ) +-- > diagProd ( 7 8 9 ) = 45 +diagProd :: Num a => Matrix a -> a +diagProd = V.product . getDiag + +-- DETERMINANT + +-- | Matrix determinant using Laplace expansion. +-- If the elements of the 'Matrix' are instance of 'Ord' and 'Fractional' +-- consider to use 'detLU' in order to obtain better performance. +detLaplace :: Num a => Matrix a -> a +detLaplace (M 1 1 v) = V.head v +detLaplace m = + sum [ (-1)^(i-1) * m ! (i,1) * detLaplace (minorMatrix i 1 m) | i <- [1 .. nrows m] ] + +-- | Matrix determinant using LU decomposition. +detLU :: (Ord a, Fractional a) => Matrix a -> a +detLU m = d * diagProd u + where + (u,_,_,d) = luDecomp m
+ bench/mult.hs view
@@ -0,0 +1,25 @@+ +import Criterion.Main +-- +import Data.Matrix + +mat :: Int -> Matrix Int +mat n = matrix n n $ \(i,j) -> i - j + +testdef :: Int -> Matrix Int +testdef n = multStd (mat n) (mat n) + +teststr :: Int -> Matrix Int +teststr n = multStrassen (mat n) (mat n) + +teststrm :: Int -> Matrix Int +teststrm n = multStrassenMixed (mat n) (mat n) + +bmat :: Int -> Benchmark +bmat n = bgroup ("mult" ++ show n) + [ bench "Definition" $ nf testdef n + , bench "Strassen mixed" $ nf teststrm n + ] + +main :: IO () +main = defaultMain $ fmap bmat [10,25,100,250,500]
matrix.cabal view
@@ -1,5 +1,5 @@ Name: matrix -Version: 0.1.1 +Version: 0.2 Author: Daniel Díaz Category: Math Build-type: Simple @@ -10,8 +10,19 @@ Bug-reports: https://github.com/Daniel-Diaz/matrix/issues Synopsis: A native implementation of matrix operations. Description: - Matrix type and basic operations. Just a preliminary version without too many features. + Matrix library. Basic operations and some algorithms. + . + To get the library update your cabal package list (if needed) with @cabal update@ and + then use @cabal install matrix@, assuming that you already have Cabal installed. + Usage examples are included in the API reference generated by Haddock. + . + If you want to use GSL, BLAS and LAPACK, @hmatrix@ (<http://hackage.haskell.org/package/hmatrix>) + is the way to go. Cabal-version: >= 1.8 +Extra-source-files: + readme.md + -- Benchmarks + bench/mult.hs Source-repository head type: git @@ -22,4 +33,13 @@ , vector , deepseq Exposed-modules: Data.Matrix - GHC-Options: -Wall+ GHC-Options: -Wall + +Benchmark matrix-mult + type: exitcode-stdio-1.0 + hs-source-dirs: bench + main-is: mult.hs + build-depends: base ==4.* + , matrix + , criterion + ghc-options: -O2
+ readme.md view
@@ -0,0 +1,9 @@+# matrix package # + +Haskell Matrix library with common operations with them. + +Usage examples are populating the API reference. + +# Benchmarks # + +Some benchmarks in matrix multiplication can be found [here](http://deltadiaz.blogspot.com/2013/03/benchmarks-on-matrix-multiplication.html).