packages feed

srtree-3.0.0.0: src/Algorithm/SRTree/Utils.hs

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

import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as UM
import Control.Monad
import Control.Monad.Catch
import Control.Monad.Primitive
import Control.Monad.IO.Class
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
import Debug.Trace (traceShow)

-- | Internal helper to get dimensions (rows, columns)
matSize :: Columns -> (Int, Int)
matSize [] = (0, 0)
matSize cs@(c:_) = (U.length c, length cs)

getRows :: Columns -> [Target]
getRows mtx
  | n == 0 = []
  | otherwise = [ U.fromListN n [ c U.! i | c <- mtx ] | i <- [0 .. m - 1] ]
  where (m, n) = matSize mtx
{-# INLINE getRows #-}

getCols :: Columns -> [Target]
getCols = id
{-# INLINE getCols #-}

appendRow :: MonadThrow m => Columns -> Target -> m Columns
appendRow xs v = pure $ zipWith U.snoc xs (U.toList v)
{-# INLINE appendRow #-}

appendCol :: MonadThrow m => Columns -> Target -> m Columns
appendCol xs v = pure $ xs ++ [v]
{-# INLINE appendCol #-}

updateS :: Target -> [(Int, Double)] -> Target
updateS vec new = vec U.// 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) => Target -> Target -> m Columns
outer arr1 arr2
  | U.null arr1 || U.null arr2 = pure []
  | otherwise = pure [ U.map (* (arr2 U.! j)) arr1 | j <- [0 .. U.length arr2 - 1] ]
{-# INLINE outer #-}

-- | Flatten list of column vectors to a row-major U.Vector Double
toRowMajor :: Columns -> U.Vector Double
toRowMajor cols = U.generate (m * n) (\ix -> let (i, j) = ix `divMod` n in (cols !! j) U.! i)
  where (m, n) = matSize cols

-- | Restore a row-major continuous U.Vector Double back to Columns
fromRowMajor :: Int -> Int -> U.Vector Double -> Columns
fromRowMajor m n vec = [ U.generate m (\i -> vec U.! (i * n + j)) | j <- [0 .. n - 1] ]

unsafeRead :: PrimMonad m => Int -> UM.MVector (PrimState m) Double -> (Int, Int) -> m Double
unsafeRead stride arr (i, j) = UM.unsafeRead arr (i * stride + j)
{-# INLINE unsafeRead #-}

unsafeWrite :: PrimMonad m => Int -> UM.MVector (PrimState m) Double -> (Int, Int) -> Double -> m ()
unsafeWrite stride arr (i, j) val = UM.unsafeWrite arr (i * stride + j) val
{-# INLINE unsafeWrite #-}

det :: Columns -> Double
det mtx
  | m == 0 || n == 0 = 1
  | otherwise = (^2) $ product [ (toRowMajor l) U.! (i * n + i) | i <- [0 .. m - 1] ]
  where
    (m, n) = matSize mtx
    (l, _) = unsafePerformIO (lu mtx)

detChol :: Columns -> Double
detChol mtx
  | m == 0 || n == 0 = 1
  | otherwise = (^2) $ product [ (toRowMajor cho) U.! (i * m + i) | i <- [0 .. m - 1] ]
  where
    (m, n) = matSize mtx
    cho = unsafePerformIO (cholesky mtx)
{-# INLINE det #-}

rangedLinearDotProd :: PrimMonad m => Int -> Int -> Int -> UM.MVector (PrimState m) Double -> m Double
rangedLinearDotProd r1 r2 len arr = go 0 0
  where
    go !acc k
      | k < len = do
          x <- UM.unsafeRead arr (r1 + k)
          y <- UM.unsafeRead 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) => Columns -> m Columns
cholesky arr
  | m /= n = error $ "cholesky dimension mismatch " <> show m <> " X " <> show n
  | m == 0 = pure []
  | otherwise = do
      l <- UM.new (m * m)
      let orig = toRowMajor arr
      forM_ [0 .. m - 1] $ \i ->
        forM_ [0 .. m - 1] $ \j ->
          if i < j then unsafeWrite m l (i, j) 0
          else do
            let cur = orig U.! (i * m + j)
                rowI = i * m
                rowJ = j * m
            xjj <- UM.unsafeRead l (rowJ + j)
            tot <- rangedLinearDotProd rowI rowJ j l
            let delta = cur - tot
            if i == j
              then if delta <= 0
                   then throwM NegDef
                   else UM.unsafeWrite l (rowI + j) (sqrt delta)
              else UM.unsafeWrite l (rowI + j) (delta / xjj)
      frozen <- U.unsafeFreeze l
      pure $ fromRowMajor m m frozen
  where (m, n) = matSize arr
{-# INLINE cholesky #-}

invChol :: (PrimMonad m, MonadThrow m, MonadIO m) => Columns -> m Columns
invChol arr = do
  lMtx <- cholesky arr
  let (m, _) = matSize arr
  mtx <- U.thaw (toRowMajor lMtx)
  forM_ [0 .. m - 1] $ \i -> do
    lII <- unsafeRead m mtx (i, i)
    unsafeWrite m mtx (i, i) (1 / lII)
    forM_ [0 .. i - 1] $ \j -> do
      tot <- rangedLinearDotProd (i * m + j) (j * m + j) (i - j) mtx
      unsafeWrite m mtx (j, i) ((-tot) / lII)
      unsafeWrite m mtx (i, j) 0

  mm <- UM.replicate (m * m) 0
  forM_ [0 .. m - 1] $ \i -> do
    dii <- rangedLinearDotProd (i * m + i) (i * m + i) (m - i) mtx
    unsafeWrite m mm (i, i) dii
    forM_ [i + 1 .. m - 1] $ \j -> do
      dij <- rangedLinearDotProd (i * m + j) (j * m + j) (m - j) mtx
      unsafeWrite m mm (i, j) dij
      unsafeWrite m mm (j, i) dij
  frozen <- U.unsafeFreeze mm
  pure $ fromRowMajor m m frozen
{-# INLINE invChol #-}

lu :: (PrimMonad m, MonadThrow m, MonadIO m) => Columns -> m (Columns, Columns)
lu mtx = do
  let (m, n) = matSize mtx
      orig = toRowMajor mtx
  u <- UM.replicate (m * n) 0
  forM_ [0 .. min m n - 1] $ \i -> unsafeWrite n u (i, i) 1
  l <- UM.replicate (m * n) 0

  let buildLVal !i !j = do
        let go !k !s
              | k == j = pure s
              | otherwise = do
                  lik <- unsafeRead n l (i, k)
                  ukj <- unsafeRead n u (k, j)
                  go (k+1) (s + lik * ukj)
        s' <- go 0 0
        unsafeWrite n l (i, j) ((orig U.! (i * n + j)) - s')

      buildL !i !j = when (i /= m) $ do
        buildLVal i j
        buildL (i+1) j

      buildUVal !i !j = do
        let go !k !s
              | k == j = pure s
              | otherwise = do
                  ljk <- unsafeRead n l (j, k)
                  uki <- unsafeRead n u (k, i)
                  go (k+1) (s + ljk * uki)
        s' <- go 0 0
        ljj <- unsafeRead n l (j, j)
        unsafeWrite n u (j, i) (((orig U.! (j * n + i)) - s') / ljj)

      buildU !i !j = when (i /= n) $ do
        buildUVal i j
        buildU (i+1) j

      buildLU !j = when (j /= n && j /= m) $ do
        buildL j j
        buildU j j
        buildLU (j+1)

  buildLU 0
  finalL <- U.unsafeFreeze l
  finalU <- U.unsafeFreeze u
  pure (fromRowMajor m n finalL, fromRowMajor m n finalU)

forwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => Columns -> Target -> m Target
forwardSub a b = do
  let m = U.length b
      n = length a
      aMat = toRowMajor a
  x <- UM.replicate m 0
  let coeff !i !j !s
        | j == i = pure s
        | otherwise = do
            let aij = aMat U.! (i * n + j)
            xj <- UM.unsafeRead x j
            coeff i (j+1) (s + aij * xj)
      go !i = when (i /= m) $ do
        let bi = b U.! i
            aii = aMat U.! (i * n + i)
        c <- coeff i 0 0
        UM.unsafeWrite x i ((bi - c) / aii)
        go (i+1)
  go 0
  U.unsafeFreeze x

backwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => Columns -> Target -> m Target
backwardSub a b = do
  let m = U.length b
      n = length a
      aMat = toRowMajor a
  x <- UM.replicate m 0
  let coeff !i !j !s
        | j == m = pure s
        | otherwise = do
            let aij = aMat U.! (i * n + j)
            xj <- UM.unsafeRead x j
            coeff i (j+1) (s + aij * xj)
      go !i = when (i >= 0) $ do
        let bi = b U.! i
            aii = aMat U.! (i * n + i)
        c <- coeff i (i+1) 0
        UM.unsafeWrite x i ((bi - c) / aii)
        go (i-1)
  go (m-1)
  U.unsafeFreeze x

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

type PolyCos = (Double, Double, Double)

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' = U.fromList xdiff

    dydx :: U.Vector Double
    dydx = U.fromList $ 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' U.! (ix - 1)) * (2 - wi) + 2 * (xdiff' U.! ix)
                  wn = (xdiff' U.! 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' U.! (ix - 1)) * (2 - (w !! (ix - 1))) + 2 * (xdiff' U.! ix)
                  zn = (6 * ((dydx U.! ix) - (dydx U.! (ix - 1))) - (xdiff' U.! (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
  | length xs < 2 = x
  | x < head xs   = y1 + (x - x1) * (y2 - y1) / (x2 - x1)
  | x > last xs   = y_1 + (x - x_1) * (y_n - y_1) / (x_n - x_1)
  | otherwise     = go xs $ zip coefs (tail coefs)
  where
    xs = map fst pts
    ys = map snd pts
    coefs = cubicSplineCoefficients pts
    x1 = head xs;  y1 = head ys
    x2 = xs !! 1;  y2 = ys !! 1
    x_1 = xs !! (len - 2);  y_1 = ys !! (len - 2)
    x_n = last xs;          y_n = last ys
    len = length xs

    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 && x <= x2 = evalAt c1 c2 x
      | otherwise          = go (x2 : xs') cs