sparse-tensor-0.2: src/Math/Tensor/Internal/LinearAlgebra.hs
-----------------------------------------------------------------------------
-- |
-- Module : Math.Tensor.Internal.LinearAlgebra
-- Copyright : (c) 2019 Tobias Reinhart and Nils Alex
-- License : MIT
-- Maintainer : tobi.reinhart@fau.de, nils.alex@fau.de
--
-- Gaussian elimination algorithm based on hmatrix.
-----------------------------------------------------------------------------
module Math.Tensor.Internal.LinearAlgebra (
-- * Gaussian Elimination
gaussianST,
gaussian,
-- * Linearly Independent Columns
independentColumns,
independentColumnsMat,
-- * Pivots
pivotsU,
findPivotMax)
where
import Numeric.LinearAlgebra
import Numeric.LinearAlgebra.Data
import Numeric.LinearAlgebra.Devel
import Data.List (maximumBy)
import Control.Monad
import Control.Monad.ST
-- | Returns the pivot columns of an upper triangular matrix.
--
-- @
-- λ let mat = (3 >< 4) [1, 0, 2, -3, 0, 0, 1, 0, 0, 0, 0, 0]
-- λ mat
-- (3><4)
-- [ 1.0, 0.0, 2.0, -3.0
-- , 0.0, 0.0, 1.0, 0.0
-- , 0.0, 0.0, 0.0, 0.0 ]
-- λ pivotsU mat
-- [0,2]
-- @
--
pivotsU :: Matrix Double -> [Int]
pivotsU mat = go (0,0)
where
go (i,j)
= case findPivot mat (i,j) of
Nothing -> []
Just (i', j') -> j' : go (i'+1, j'+1)
-- naive check for numerical zero
eps :: Double -> Bool
eps = (< 1e-12) . abs
-- find next pivot in upper triangular matrix
findPivot :: Matrix Double -> (Int, Int) -> Maybe (Int, Int)
findPivot mat (i, j)
| n == j = Nothing
| m == i = Nothing
| otherwise = case nonZeros of
[] -> if n == j+1
then Nothing
else findPivot mat (i, j+1)
(pi, pj):_ -> Just (pi, pj+j)
where
m = rows mat
n = cols mat
col = mat ¿ [j]
nonZeros = filter (\(i', _) -> i' >= i) $ find (not . eps) col
-- | Find pivot element below position (i, j) with greatest absolute value.
findPivotMax :: Matrix Double -> Int -> Int -> Maybe (Int, Int)
findPivotMax mat i j
| n == j = Nothing
| m == i = Nothing
| otherwise = case nonZeros of
[] -> if n == j+1
then Nothing
else findPivotMax mat i (j+1)
_ -> Just (pi, pj+j)
where
m = rows mat
n = cols mat
col = mat ¿ [j]
nonZeros = filter (\(i', _) -> i' >= i) $ find (not . eps) col
(pi, pj) = maximumBy (\ix jx -> abs (col `atIndex` ix)
`compare`
abs (col `atIndex` jx))
nonZeros
-- gaussian elimination of sub matrix below position (i, j)
gaussian' :: Int -> Int -> STMatrix s Double -> ST s ()
gaussian' i j mat = do
m <- liftSTMatrix rows mat
n <- liftSTMatrix cols mat
iPivot' <- liftSTMatrix (\x -> findPivotMax x i j) mat
case iPivot' of
Nothing -> return ()
Just (r, p) -> do
rowOper (SWAP i r (FromCol j)) mat
pv <- liftSTMatrix (`atIndex` (i, p)) mat
mapM_ (reduce pv p) [i+1 .. m-1]
gaussian' (i+1) (p+1) mat
where
reduce pv p r = do
rv <- liftSTMatrix (`atIndex` (r, p)) mat
if eps rv
then return ()
else
let frac = -rv / pv
op = AXPY frac i r (FromCol p)
in rowOper op mat
-- | Gaussian elimination perfomed in-place in the @'ST'@ monad.
gaussianST :: STMatrix s Double -> ST s ()
gaussianST = gaussian' 0 0
-- | Gaussian elimination as pure function. Involves a copy of the input matrix.
--
-- @
-- λ let mat = (3 >< 4) [1, 1, -2, 0, 0, 2, -6, -4, 3, 0, 3, 1]
-- λ mat
-- (3><4)
-- [ 1.0, 1.0, -2.0, 0.0
-- , 0.0, 2.0, -6.0, -4.0
-- , 3.0, 0.0, 3.0, 1.0 ]
-- λ gaussian mat
-- (3><4)
-- [ 3.0, 0.0, 3.0, 1.0
-- , 0.0, 2.0, -6.0, -4.0
-- , 0.0, 0.0, 0.0, 1.6666666666666667 ]
-- @
--
gaussian :: Matrix Double -> Matrix Double
gaussian mat = runST $ do
m <- thawMatrix mat
gaussianST m
freezeMatrix m
-- | Returns the indices of a maximal linearly independent subset of the columns
-- in the matrix.
--
-- @
-- λ let mat = (3 >< 4) [1, 1, -2, 0, 0, 2, -6, -4, 3, 0, 3, 1]
-- λ mat
-- (3><4)
-- [ 1.0, 1.0, -2.0, 0.0
-- , 0.0, 2.0, -6.0, -4.0
-- , 3.0, 0.0, 3.0, 1.0 ]
-- λ independentColumns mat
-- [0,1,3]
-- @
--
independentColumns :: Matrix Double -> [Int]
independentColumns mat = pivotsU mat'
where
mat' = gaussian mat
-- | Returns a sub matrix containing a maximal linearly independent subset of
-- the columns in the matrix.
--
-- @
-- λ let mat = (3 >< 4) [1, 1, -2, 0, 0, 2, -6, -4, 3, 0, 3, 1]
-- λ mat
-- (3><4)
-- [ 1.0, 1.0, -2.0, 0.0
-- , 0.0, 2.0, -6.0, -4.0
-- , 3.0, 0.0, 3.0, 1.0 ]
-- λ independentColumnsMat mat
-- (3><3)
-- [ 1.0, 1.0, 0.0
-- , 0.0, 2.0, -4.0
-- , 3.0, 0.0, 1.0 ]
-- @
--
independentColumnsMat :: Matrix Double -> Matrix Double
independentColumnsMat mat =
case independentColumns mat of
[] -> (rows mat >< 1) $ repeat 0
cs -> mat ¿ cs