packages feed

srtree-2.0.0.0: src/Algorithm/Massiv/Utils.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
module Algorithm.Massiv.Utils where

import Data.Massiv.Array hiding ( forM_, unzip, map, init, zipWith, zip, tail, replicate, take )
import qualified Data.Massiv.Array as A
import qualified Data.Massiv.Array.Unsafe as UMA
import qualified Data.Massiv.Array.Mutable as MMA
import Control.Monad
import Data.Vector.Storable ((//))
import System.IO.Unsafe

-- taken from https://hackage.haskell.org/package/cubicspline-0.1.2
import Control.Arrow
import Data.List(unfoldr)

import Data.SRTree.Eval

type MMassArray m = MMA.MArray (PrimState m) S Ix2 Double

getRows :: SRMatrix -> Array B Ix1 PVector
getRows = computeAs B . outerSlices
{-# INLINE getRows #-}
getCols :: SRMatrix -> Array B Ix1 PVector
getCols = computeAs B . A.map (computeAs S) . innerSlices
{-# INLINE getCols #-}

appendRow :: MonadThrow m => SRMatrix -> PVector -> m SRMatrix
appendRow xs v = computeAs S <$> (stackOuterSlicesM . toList . computeAs B $ snoc (outerSlices xs) v)
{-# INLINE appendRow #-}

appendCol :: MonadThrow m => SRMatrix -> PVector -> m SRMatrix
appendCol xs v = computeAs S <$> (stackInnerSlicesM . toList . computeAs B $ snoc (A.map (computeAs S) $ innerSlices xs) v)
{-# INLINE appendCol #-}

updateS :: Array S Ix1 Double -> [(Int, Double)] -> Array S Ix1 Double
updateS vec new = fromStorableVector compMode $ toStorableVector vec // new

linSpace :: Int -> (Double, Double) -> [Double]
linSpace num (lo, hi) = Prelude.take num $ iterate (\x -> x + step) lo
  where
    step = (hi - lo) / (fromIntegral num - 1)
{-# INLINE linSpace #-}

outer :: (MonadThrow m)
  => PVector
  -> PVector
  -> m SRMatrix
outer arr1 arr2
  | isEmpty arr1 || isEmpty arr2 = pure $ setComp comp empty
  | otherwise =
      pure $ makeArray comp (Sz2 m1 m2) $ \(i :. j) ->
          UMA.unsafeIndex arr1 i * UMA.unsafeIndex arr2 j
  where
      comp   = getComp arr1 <> getComp arr2
      Sz1 m1 = size arr1
      Sz1 m2 = size arr2
{-# INLINE outer #-}

det :: SRMatrix -> Double 
det mtx
  | m==0 || n==0 = 1
  | otherwise    = (^2) $ Prelude.product [l ! (i :. i) | i <- [0 .. m-1]]
  where
    Sz (m :. n)  = size mtx
    (l, _) = unsafePerformIO (lu mtx)
      
detChol :: SRMatrix -> Double
detChol mtx
  | m==0 || n==0 = 1
  | otherwise    = (^2) $ Prelude.product [cho ! (i :. i) | i <- [0 .. m-1]]
  where
    Sz (m :. n)  = size mtx
    cho = unsafePerformIO (cholesky mtx)
{-# INLINE det #-}

rangedLinearDotProd :: PrimMonad m => Int -> Int -> Int -> MMassArray m -> m Double
rangedLinearDotProd r1 r2 len arr = go 0 0
  where
    go !acc k
      | k < len   = do x <- UMA.unsafeLinearRead arr (r1 + k)
                       y <- UMA.unsafeLinearRead arr (r2 + k)
                       go (acc + x*y) (k + 1)
      | otherwise = pure acc
{-# INLINE rangedLinearDotProd #-}

data NegDef = NegDef
    deriving Show

instance Exception NegDef

cholesky :: (PrimMonad m, MonadThrow m, MonadIO m)
  => SRMatrix
  -> m SRMatrix
cholesky arr
  | m /= n       = throwM $ SizeMismatchException (size arr) (size arr)
  | isEmpty arr  = pure $ setComp comp empty
  | otherwise    = MMA.createArrayS_ (size arr) create
  where
    comp      = getComp arr
    (Sz2 m n) = size arr
    create l  = Prelude.mapM_ (update l) [i :. j | i <- [0..m-1], j <- [0..m-1]]

    update l ix@(i :. j)
      | i < j     = UMA.unsafeWrite l ix 0
      | otherwise = do let cur  = UMA.unsafeIndex arr ix
                           rowI = i*m
                           rowJ = j*m
                       xjj <- UMA.unsafeLinearRead l (rowJ + j)
                       tot <- rangedLinearDotProd rowI rowJ j l
                       let delta = cur - tot
                       if i == j
                          then if delta <= 0
                                 then throwM NegDef -- SizeMismatchException (size arr) (size arr) -- look at a better exception
                                 else UMA.unsafeLinearWrite l (rowI + j) (sqrt delta)
                          else UMA.unsafeLinearWrite l (rowI + j) (delta / xjj)
{-# INLINE cholesky #-}

invChol :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> m SRMatrix
invChol arr = do l <- cholesky arr -- lower diag
                 mtx <- thawS l
                 forM_ [0 .. m-1] $ \i -> do
                     lII <- UMA.unsafeRead mtx (i :. i)
                     UMA.unsafeWrite mtx (i :. i) (1 / lII)
                     forM_ [0 .. i-1] $ \j -> do
                         tot <- rangedLinearDotProd (i*m + j) (j*m + j) (i-j) mtx
                         UMA.unsafeWrite mtx (j :. i) ((-tot)/lII)
                         UMA.unsafeWrite mtx (i :. j) 0
                 mm <- newMArray (Sz2 m m) 0
                 forM_ [0 .. m-1] $ \i -> do
                     dii <- rangedLinearDotProd (i*m + i) (i*m + i) (m - i) mtx
                     UMA.unsafeWrite mm (i :. i) dii
                     forM_ [i+1 .. m-1] $ \j -> do
                          dij <- rangedLinearDotProd (i*m + j) (j*m + j) (m - j) mtx
                          UMA.unsafeWrite mm (i :. j) dij
                          UMA.unsafeWrite mm (j :. i) dij
                 freezeS mm

  where
    Sz2 m _ = size arr
{-# INLINE invChol #-}

-- LU decomposition and solver taken from https://hackage.haskell.org/package/linear-1.23/docs/src/Linear.Matrix.html
lu :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> m (SRMatrix, SRMatrix)
lu mtx = do
    let (Sz2 m n) = size mtx
    u <- thawS $ computeAs S $ identityMatrix (Sz m)
    l <- thawS $ A.replicate compMode (Sz2 m n) 0

    let buildLVal !i !j = do
            let go !k !s
                    | k == j    = pure s
                    | otherwise = do lik <- UMA.unsafeRead l (i :. k)
                                     ukj <- UMA.unsafeRead u (k :. j)
                                     go (k+1) ( s + (lik * ukj) )
            s' <- go 0 0
            UMA.unsafeWrite l (i :. j) ((mtx ! (i :. j)) - s')
            -- pure l
        buildL !i !j
            = when (i /= n) $ do buildLVal i j
                                 buildL (i+1) j
        buildUVal !i !j = do
            let go !k !s
                    | k == j = pure s
                    | otherwise = do ljk <- UMA.unsafeRead l (j :. k)
                                     uki <- UMA.unsafeRead u (k :. i)
                                     go (k+1) (s + ljk * uki)

            s' <- go 0 0
            ljj <- UMA.unsafeRead l (j :. j)
            UMA.unsafeWrite u (j :. i) (((mtx ! (j :. i)) - s') / (ljj))
            -- pure u

        buildU !i !j
            = when (i /= n) $ do buildUVal i j
                                 buildU (i+1) j
        buildLU !j
            = when (j /= n) $
                 do buildL j j
                    buildU j j
                    buildLU (j+1)
    buildLU 0
    finalL <- freezeS l
    finalU <- freezeS u
    pure (finalL, finalU)

forwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector
forwardSub a b = do
    let (Sz m) = size b
    x <- thawS $ A.replicate compMode (Sz1 m) 0
    let coeff !i !j !s
            | j == i = pure s
            | otherwise = do let aij = a ! (i :. j)
                             xj  <- UMA.unsafeRead x j
                             coeff i (j+1) (s + aij * xj)
        go !i = when (i/= m) $
                   do let bi = b ! i
                          aii = a ! (i :. i)
                      c <- coeff i 0 0
                      UMA.unsafeWrite x i ((bi - c)/aii)
                      go (i+1)
    go 0
    freezeS x

backwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector
backwardSub a b = do
    let (Sz m) = size b
    x <- thawS $ A.replicate compMode (Sz1 m) 0
    let coeff !i !j !s
            | j == m = pure s
            | otherwise = do let aij = a ! (i :. j)
                             xj  <- UMA.unsafeRead x j
                             coeff i (j+1) (s + aij * xj)
        go !i = when (i >= 0) $
                        do let bi  = b ! i
                               aii = a ! (i :. i)
                           c <- coeff i (i+1) 0
                           UMA.unsafeWrite x i ((bi - c)/aii)
                           go (i-1)
    go (m-1)
    freezeS x

luSolve :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector
luSolve a b = do (l, u) <- lu a
                 forwardSub l b >>= backwardSub u

type PolyCos = (Double, Double, Double)

-- | Given a list of (x,y) co-ordinates, produces a list of coefficients to cubic equations, with knots at each of the initially provided x co-ordinates. Natural cubic spline interpololation is used. See: <http://en.wikipedia.org/wiki/Spline_interpolation#Interpolation_using_natural_cubic_spline>.
cubicSplineCoefficients :: [(Double, Double)] -> [PolyCos]
cubicSplineCoefficients xs = Prelude.zip3 x y z'
    where
      x = map fst xs
      y = map snd xs
      xdiff = zipWith (-) (tail x) x
      xdiff' = fromList compMode xdiff :: Vector S Double
      dydx :: Vector S Double
      dydx  = fromList compMode $ Prelude.zipWith3 (\y0 y1 xd -> (y0-y1)/xd) (tail y) y xdiff
      n = length x

      w :: [Double]
      w = 0 : nextW 1 w
        where
          nextW ix (wi : t)
            | ix == n-1 = []
            | otherwise = let m  = (xdiff' ! (ix-1)) * (2 - wi) + 2 * (xdiff' ! ix)
                              wn = (xdiff' ! ix) / m
                           in wn : nextW (ix+1) t
      z :: [Double]
      z = 0 : nextZ 1 z
        where
          nextZ ix (zi : t)
            | ix == n-1 = [0]
            | otherwise = let m  = (xdiff' ! (ix-1)) * (2 - (w !! (ix-1))) + 2 * (xdiff' ! ix)
                              zn = (6*((dydx ! ix) - (dydx ! (ix-1))) - (xdiff' ! (ix-1)) * zi) / m
                          in zn : nextZ (ix+1) t

      z' :: [Double]
      z' = Prelude.reverse $ 0 : [z !! i - w !! i * z !! (i+1) | i <- [n-2,n-3 .. 0]]

chunkBy :: Int -> [t] -> [[t]]
chunkBy n = unfoldr go
    where go [] = Nothing
          go x  = Just $ splitAt n x

genSplineFun :: [(Double, Double)] -> Double -> Double
genSplineFun pts x = go xs $ zip coefs (tail coefs)
  where
    xs    = map fst pts
    coefs = cubicSplineCoefficients pts
    evalAt (a1,b1,c1) (a2,b2,c2) y = let hi1 = a2 - a1
                                     in c1/(6*hi1)*(a2-y)^3 + c2/(6*hi1)*(y-a1)^3 + (b2/hi1 - c2*hi1/6)*(y-a1) + (b1/hi1 - c1*hi1/6)*(a2-y)

    go [x1,x2] [(c1,c2)] = evalAt c1 c2 x
    go (x1:x2:xs) ((c1,c2):cs)
      | x < x1 = evalAt c1 c2 x
      | x >= x1 && x <= x2 = evalAt c1 c2 x
      | otherwise          = go (x2:xs) cs