----------------------------------------------------------------------------- -- Copyright 2013, Open Universiteit Nederland. This file is distributed -- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | -- Maintainer : bastiaan.heeren@ou.nl -- Stability : provisional -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- module Domain.LinearAlgebra.Matrix ( Matrix, Row, Column, isRectangular, makeMatrix, identity, mapWithPos , changeEntries, changeEntry, setEntries, setEntry , rows, row, columns, column, dimensions, entry, isEmpty , add, scale, multiply , reduce, forward, backward, inverse, invertible, rank, nullity, eqMatrix , switchRows, scaleRow, addRow , inRowEchelonForm, inRowReducedEchelonForm , nonZero, pivot, isPivotColumn , isSquare, identityMatrix, isLowerTriangular, isUpperTriangular ) where import Control.Applicative import Control.Monad import Data.Foldable (Foldable, foldMap) import Data.List hiding (transpose) import Data.Maybe import Data.Monoid import Data.Traversable (Traversable, sequenceA) import Domain.Math.Simplification import Ideas.Common.Rewriting import Test.QuickCheck import qualified Data.List as L import qualified Data.Map as M import qualified Ideas.Text.OpenMath.Dictionary.Linalg2 as OM -- Invariant: a matrix is always rectangular newtype Matrix a = M [[a]] deriving (Eq, Ord, Show) type Row a = [a] type Column a = [a] instance Functor Matrix where fmap f (M rs) = M (map (map f) rs) instance Foldable Matrix where foldMap f (M xss) = foldMap (mconcat . map f) xss instance Traversable Matrix where sequenceA (M xss) = M <$> sequenceA (map sequenceA xss) instance IsTerm a => IsTerm (Matrix a) where toTerm = let f = function matrixrowSymbol . map toTerm in function matrixSymbol . map f . rows fromTerm a = do rs <- isFunction matrixSymbol a xss <- mapM (isFunction matrixrowSymbol) rs yss <- mapM (mapM fromTerm) xss guard (isRectangular yss) return (makeMatrix yss) instance Arbitrary a => Arbitrary (Matrix a) where arbitrary = do (i, j) <- arbitrary arbSizedMatrix (i `mod` 5, j `mod` 5) instance CoArbitrary a => CoArbitrary (Matrix a) where coarbitrary = coarbitrary . rows arbSizedMatrix :: Arbitrary a => (Int, Int) -> Gen (Matrix a) arbSizedMatrix (i, j) = do rs <- replicateM i (vector j) return (makeMatrix rs) matrixSymbol, matrixrowSymbol :: Symbol matrixSymbol = newSymbol OM.matrixSymbol matrixrowSymbol = newSymbol OM.matrixrowSymbol instance Simplify a => Simplify (Matrix a) where simplifyWith opt = fmap (simplifyWith opt) -- Check whether the table is rectangular isRectangular :: [[a]] -> Bool isRectangular xss = case map length xss of [] -> True n:ns -> all (==n) ns -- Constructor function that checks whether the table is rectangular makeMatrix :: [Row a] -> Matrix a makeMatrix rs | null (concat rs) = M [] | isRectangular rs = M rs | otherwise = error "makeMatrix: not rectangular" identity :: Num a => Int -> Matrix a identity n = M $ map f [0..n-1] where f i = replicate i 0 ++ [1] ++ replicate (n-i-1) 0 isEmpty :: Matrix a -> Bool isEmpty (M xs) = null xs rows :: Matrix a -> [Row a] rows (M rs) = rs row :: Int -> Matrix a -> Row a row n = (!!n) . rows columns :: Matrix a -> [Column a] columns = rows . transpose column :: Int -> Matrix a -> Column a column n = (!!n) . columns dimensions :: Matrix a -> (Int, Int) dimensions m = (length $ rows m, length $ columns m) entry :: (Int, Int) -> Matrix a -> a entry (i, j) m = row i m !! j mapWithPos :: ((Int, Int) -> a -> b) -> Matrix a -> Matrix b mapWithPos f (M rs) = M $ zipWith g [0..] rs where g y = zipWith (\x -> f (y, x)) [0..] changeEntries :: M.Map (Int, Int) (a -> a) -> Matrix a -> Matrix a changeEntries mp = mapWithPos (\pos -> M.findWithDefault id pos mp) changeEntry :: (Int, Int) -> (a -> a) -> Matrix a -> Matrix a changeEntry pos = changeEntries . M.singleton pos setEntries :: M.Map (Int, Int) a -> Matrix a -> Matrix a setEntries mp = mapWithPos (\pos a -> M.findWithDefault a pos mp) setEntry :: (Int, Int) -> a -> Matrix a -> Matrix a setEntry pos = setEntries . M.singleton pos ------------------------------------------------------- add :: Num a => Matrix a -> Matrix a -> Matrix a add a b | dimensions a == dimensions b = M $ zipWith (zipWith (+)) (rows a) (rows b) | otherwise = error "add: dimensions differ" scale :: Num a => a -> Matrix a -> Matrix a scale a = fmap (*a) multiply :: Num a => Matrix a -> Matrix a -> Matrix a multiply a b | snd (dimensions a) == fst (dimensions b) = M $ map (\r -> map (sum . zipWith (*) r) (columns b)) (rows a) | otherwise = error "multiply: incorrect dimensions" ------------------------------------------------------- -- Gaussian Elimination reduce :: (Eq a,Fractional a) => Matrix a -> Matrix a reduce = backward . forward forward :: (Eq a,Fractional a) => Matrix a -> Matrix a forward m | h==0 || w==0 = m | all (==0) col = M $ zipWith (:) (repeat 0) $ rows $ forward $ M $ map tail $ rows m | x == 0 = forward (switchRows 0 (fromJust $ findIndex (/= 0) col) m) | x == 1 = let M (r:rs) = foldr (\k -> addRow k 0 (negate $ entry (k,0) m)) m [1..h-1] M ts = forward (M rs) in M (r:ts) | otherwise = forward (scaleRow 0 (1/x) m) where (h, w) = dimensions m x = entry (0,0) m col = column 0 m backward :: (Eq a,Fractional a) => Matrix a -> Matrix a backward m = foldr f m [1..h-1] where (h, _) = dimensions m f i = let g j = case findIndex (/=0) (row i m) of Just k -> addRow j i (negate (entry (j, k) m)) Nothing -> id in flip (foldr g) [0..i-1] rank :: (Eq a,Fractional a) => Matrix a -> Int rank = length . filter (isJust . pivot) . rows . reduce nullity :: (Eq a,Fractional a) => Matrix a -> Int nullity m = snd (dimensions m) - rank m inverse :: (Eq a,Fractional a) => Matrix a -> Maybe (Matrix a) inverse m | h /= w = Nothing | rank m < w = Nothing | otherwise = Just $ M $ map (drop h) $ rows $ reduce $ M $ zipWith (++) (rows m) $ rows $ identity h where (h, w) = dimensions m invertible :: (Eq a,Fractional a) => Matrix a -> Bool invertible = isJust . inverse eqMatrix :: (Eq a,Fractional a) => Matrix a -> Matrix a -> Bool eqMatrix m1 m2 = reduce m1 == reduce m2 -- test = rank $ makeMatrix $ [[0 :: Rational ,1,1,1], [1,2,3,2], [3,1,1,3]] -- t = inverse $ M [[1,0],[0,3]] ------------------------------------------------------- transpose :: Matrix a -> Matrix a transpose (M rs) = M (L.transpose rs) ------------------------------------------------------- isSquare :: Matrix a -> Bool isSquare m = i==j where (i, j) = dimensions m identityMatrix :: Num a => Int -> Matrix a identityMatrix n = M $ map (\y -> map (\x -> if x==y then 1 else 0) list) list where list = [0..n-1] ------------------------------------------------------- -- Elementary row operations (preserve matrix equivalence) checkRow :: Int -> Matrix a -> Bool checkRow i m = i >= 0 && i < fst (dimensions m) switchRows :: Int -> Int -> Matrix a -> Matrix a switchRows i j m@(M rs) | i == j = m | i > j = switchRows j i m | checkRow i m && checkRow j m = let (before, r1:rest) = splitAt i rs (middle, r2:after) = splitAt (j-i-1) rest in M $ before ++ [r2] ++ middle ++ [r1] ++ after | otherwise = error "switchRows: invalid rows" scaleRow :: Num a => Int -> a -> Matrix a -> Matrix a scaleRow i a m@(M rs) | checkRow i m = let f y = if y==i then map (*a) else id in M $ zipWith f [0..] rs | otherwise = error "scaleRow: invalid row" addRow :: Num a => Int -> Int -> a -> Matrix a -> Matrix a addRow i j a m@(M rs) | checkRow i m && checkRow j m = let rj = map (*a) (row j m) f y = if y==i then zipWith (+) rj else id in M $ zipWith f [0..] rs | otherwise = error "addRow: invalid row" ------------------------------------------------------- isLowerTriangular :: (Eq a,Num a) => Matrix a -> Bool isLowerTriangular = and . zipWith check [1..] . rows where check n = all (==0) . drop n isUpperTriangular :: (Eq a,Num a) => Matrix a -> Bool isUpperTriangular = and . zipWith check [0..] . rows where check n = all (==0) . take n inRowEchelonForm :: (Eq a,Num a) => Matrix a -> Bool inRowEchelonForm (M rs) = not (any nonZero (dropWhile nonZero rs)) && increasing (map (length . takeWhile (==0)) (filter nonZero rs)) where increasing (x:ys@(y:_)) = x < y && increasing ys increasing _ = True nonZero :: (Eq a,Num a) => [a] -> Bool nonZero = any (/=0) -- or row canonical form inRowReducedEchelonForm :: (Eq a,Num a) => Matrix a -> Bool inRowReducedEchelonForm m@(M rs) = inRowEchelonForm m && all (==1) (mapMaybe pivot rs) && all (isPivotColumn . flip column m . length . takeWhile (==0)) (filter nonZero rs) pivot :: (Eq a,Num a) => Row a -> Maybe a pivot r = case dropWhile (==0) r of hd:_ -> Just hd _ -> Nothing isPivotColumn :: (Eq a,Num a) => Column a -> Bool isPivotColumn c = case filter (/=0) c of [1] -> True _ -> False