packages feed

moonlight-linalg-0.1.0.0: src-carrier/Moonlight/LinAlg/Internal/VectorOps.hs

{-# LANGUAGE BangPatterns #-}

module Moonlight.LinAlg.Internal.VectorOps
  ( dotU,
    normU,
    scaleU,
    subU,
    subScaledU,
    csrMatVecU,
    csrMatVecValidatedU,
    csrContiguousBandMatVecValidatedU,
    csrMatVecBoxedDouble,
    csrMatVecBoxedDoubleValidated,
  )
where

import qualified Data.Vector as Box
import Control.Monad.ST (runST)
import Data.Primitive (sizeOf)
import Data.Primitive.ByteArray
  ( indexByteArray,
    newByteArray,
    unsafeFreezeByteArray,
    writeByteArray,
  )
import qualified Data.Vector.Primitive as P
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as MU
import qualified Data.Vector.Unboxed.Base as UB
import Moonlight.Core (MoonlightError (..))
import Prelude

dotU :: U.Vector Double -> U.Vector Double -> Either MoonlightError Double
dotU left right =
  if U.length left == U.length right
    then Right (U.sum (U.zipWith (*) left right))
    else
      Left
        ( InvariantViolation
            ( "unboxed vector dot length mismatch: left "
                <> show (U.length left)
                <> " right "
                <> show (U.length right)
            )
        )

normU :: U.Vector Double -> Double
normU vectorValue =
  sqrt (U.sum (U.map (\entry -> entry * entry) vectorValue))

scaleU :: Double -> U.Vector Double -> U.Vector Double
scaleU factor = U.map (factor *)

subU :: U.Vector Double -> U.Vector Double -> Either MoonlightError (U.Vector Double)
subU left = subScaledU left 1.0

subScaledU :: U.Vector Double -> Double -> U.Vector Double -> Either MoonlightError (U.Vector Double)
subScaledU left factor right =
  if U.length left == U.length right
    then Right (U.zipWith (\leftEntry rightEntry -> leftEntry - factor * rightEntry) left right)
    else
      Left
        ( InvariantViolation
            ( "unboxed vector subtraction length mismatch: left "
                <> show (U.length left)
                <> " right "
                <> show (U.length right)
            )
        )

csrMatVecU ::
  Int ->
  U.Vector Int ->
  U.Vector Int ->
  U.Vector Double ->
  U.Vector Double ->
  Either MoonlightError (U.Vector Double)
csrMatVecU rowCount rowOffsets columnIndices values inputVector =
  validateCSRKernelVectors rowCount rowOffsets columnIndices values inputVector
    *> Right (csrMatVecValidatedU rowCount rowOffsets columnIndices values inputVector)

csrMatVecValidatedU ::
  Int ->
  U.Vector Int ->
  U.Vector Int ->
  U.Vector Double ->
  U.Vector Double ->
  U.Vector Double
csrMatVecValidatedU
  rowCount
  (UB.V_Int (P.Vector rowOffsetBase _ rowOffsetArray))
  (UB.V_Int (P.Vector columnBase _ columnArray))
  (UB.V_Double (P.Vector coefficientBase _ coefficientArray))
  (UB.V_Double (P.Vector inputBase _ inputArray)) =
    UB.V_Double
      ( P.Vector
          0
          rowCount
          ( runST $ do
              targetArray <-
                newByteArray
                  (rowCount * sizeOf (0.0 :: Double))

              let writeRows !rowIndex
                    | rowIndex >= rowCount =
                        unsafeFreezeByteArray targetArray
                    | otherwise = do
                        let !startIndex =
                              indexByteArray
                                rowOffsetArray
                                (rowOffsetBase + rowIndex)
                            !stopIndex =
                              indexByteArray
                                rowOffsetArray
                                (rowOffsetBase + rowIndex + 1)
                            !rowValue =
                              accumulateRow startIndex stopIndex (0.0 :: Double)
                        writeByteArray targetArray rowIndex rowValue
                        writeRows (rowIndex + 1)

                  accumulateRow :: Int -> Int -> Double -> Double
                  accumulateRow !entryIndex !stopIndex !accumulator
                    | entryIndex >= stopIndex = accumulator
                    | otherwise =
                        let !columnIndex =
                              indexByteArray
                                columnArray
                                (columnBase + entryIndex)
                            !coefficient =
                              ( indexByteArray
                                  coefficientArray
                                  (coefficientBase + entryIndex)
                                  :: Double
                              )
                            !inputValue =
                              ( indexByteArray
                                  inputArray
                                  (inputBase + columnIndex)
                                  :: Double
                              )
                         in accumulateRow
                              (entryIndex + 1)
                              stopIndex
                              (accumulator + coefficient * inputValue)

              writeRows 0
          )
      )
{-# INLINE csrMatVecValidatedU #-}

csrContiguousBandMatVecValidatedU ::
  Int ->
  Int ->
  Int ->
  U.Vector Double ->
  U.Vector Double ->
  U.Vector Double
csrContiguousBandMatVecValidatedU
  rowCount
  lowerBandwidth
  upperBandwidth
  coefficients
  inputVector
    | lowerBandwidth == 2
        && upperBandwidth == 2
        && rowCount >= 5 =
        csrPentadiagonalMatVecValidatedU
          rowCount
          coefficients
          inputVector
    | lowerBandwidth == 1
        && upperBandwidth == 1
        && rowCount >= 3 =
        csrTridiagonalMatVecValidatedU
          rowCount
          coefficients
          inputVector
    | otherwise =
        csrContiguousBandMatVecGeneralValidatedU
          rowCount
          lowerBandwidth
          upperBandwidth
          coefficients
          inputVector
{-# INLINE csrContiguousBandMatVecValidatedU #-}

csrPentadiagonalMatVecValidatedU ::
  Int ->
  U.Vector Double ->
  U.Vector Double ->
  U.Vector Double
csrPentadiagonalMatVecValidatedU
  rowCount
  (UB.V_Double (P.Vector coefficientBase _ coefficientArray))
  (UB.V_Double (P.Vector inputBase _ inputArray)) =
    UB.V_Double
      ( P.Vector
          0
          rowCount
          ( runST $ do
              targetArray <-
                newByteArray
                  (rowCount * sizeOf (0.0 :: Double))

              let coefficientAt !indexValue =
                    ( indexByteArray
                        coefficientArray
                        (coefficientBase + indexValue)
                        :: Double
                    )
                  inputAt !indexValue =
                    ( indexByteArray
                        inputArray
                        (inputBase + indexValue)
                        :: Double
                    )
                  !row0 =
                    coefficientAt 0 * inputAt 0
                      + coefficientAt 1 * inputAt 1
                      + coefficientAt 2 * inputAt 2
                  !row1 =
                    coefficientAt 3 * inputAt 0
                      + coefficientAt 4 * inputAt 1
                      + coefficientAt 5 * inputAt 2
                      + coefficientAt 6 * inputAt 3

              writeByteArray targetArray 0 row0
              writeByteArray targetArray 1 row1

              let writeInterior !rowIndex
                    | rowIndex + 2 >= rowCount = pure ()
                    | otherwise = do
                        let !entryIndex = 5 * rowIndex - 3
                            !rowValue =
                              coefficientAt entryIndex
                                * inputAt (rowIndex - 2)
                                + coefficientAt (entryIndex + 1)
                                  * inputAt (rowIndex - 1)
                                + coefficientAt (entryIndex + 2)
                                  * inputAt rowIndex
                                + coefficientAt (entryIndex + 3)
                                  * inputAt (rowIndex + 1)
                                + coefficientAt (entryIndex + 4)
                                  * inputAt (rowIndex + 2)
                        writeByteArray targetArray rowIndex rowValue
                        writeInterior (rowIndex + 1)

              writeInterior 2

              let !penultimateRow = rowCount - 2
                  !penultimateEntry = 5 * rowCount - 13
                  !penultimateValue =
                    coefficientAt penultimateEntry
                      * inputAt (rowCount - 4)
                      + coefficientAt (penultimateEntry + 1)
                        * inputAt (rowCount - 3)
                      + coefficientAt (penultimateEntry + 2)
                        * inputAt (rowCount - 2)
                      + coefficientAt (penultimateEntry + 3)
                        * inputAt (rowCount - 1)
                  !lastRow = rowCount - 1
                  !lastEntry = 5 * rowCount - 9
                  !lastValue =
                    coefficientAt lastEntry
                      * inputAt (rowCount - 3)
                      + coefficientAt (lastEntry + 1)
                        * inputAt (rowCount - 2)
                      + coefficientAt (lastEntry + 2)
                        * inputAt (rowCount - 1)

              writeByteArray targetArray penultimateRow penultimateValue
              writeByteArray targetArray lastRow lastValue
              unsafeFreezeByteArray targetArray
          )
      )
{-# INLINE csrPentadiagonalMatVecValidatedU #-}

csrTridiagonalMatVecValidatedU ::
  Int ->
  U.Vector Double ->
  U.Vector Double ->
  U.Vector Double
csrTridiagonalMatVecValidatedU
  rowCount
  (UB.V_Double (P.Vector coefficientBase _ coefficientArray))
  (UB.V_Double (P.Vector inputBase _ inputArray)) =
    UB.V_Double
      ( P.Vector
          0
          rowCount
          ( runST $ do
              targetArray <-
                newByteArray
                  (rowCount * sizeOf (0.0 :: Double))

              let coefficientAt !indexValue =
                    ( indexByteArray
                        coefficientArray
                        (coefficientBase + indexValue)
                        :: Double
                    )
                  inputAt !indexValue =
                    ( indexByteArray
                        inputArray
                        (inputBase + indexValue)
                        :: Double
                    )
                  !firstValue =
                    coefficientAt 0 * inputAt 0
                      + coefficientAt 1 * inputAt 1

              writeByteArray targetArray 0 firstValue

              let writeInterior !rowIndex
                    | rowIndex + 1 >= rowCount = pure ()
                    | otherwise = do
                        let !entryIndex = 3 * rowIndex - 1
                            !rowValue =
                              coefficientAt entryIndex
                                * inputAt (rowIndex - 1)
                                + coefficientAt (entryIndex + 1)
                                  * inputAt rowIndex
                                + coefficientAt (entryIndex + 2)
                                  * inputAt (rowIndex + 1)
                        writeByteArray targetArray rowIndex rowValue
                        writeInterior (rowIndex + 1)

              writeInterior 1

              let !lastRow = rowCount - 1
                  !lastEntry = 3 * rowCount - 4
                  !lastValue =
                    coefficientAt lastEntry
                      * inputAt (rowCount - 2)
                      + coefficientAt (lastEntry + 1)
                        * inputAt (rowCount - 1)
              writeByteArray targetArray lastRow lastValue
              unsafeFreezeByteArray targetArray
          )
      )
{-# INLINE csrTridiagonalMatVecValidatedU #-}

csrContiguousBandMatVecGeneralValidatedU ::
  Int ->
  Int ->
  Int ->
  U.Vector Double ->
  U.Vector Double ->
  U.Vector Double
csrContiguousBandMatVecGeneralValidatedU
  rowCount
  lowerBandwidth
  upperBandwidth
  (UB.V_Double (P.Vector coefficientBase _ coefficientArray))
  (UB.V_Double (P.Vector inputBase _ inputArray)) =
    UB.V_Double
      ( P.Vector
          0
          rowCount
          ( runST $ do
              targetArray <-
                newByteArray
                  (rowCount * sizeOf (0.0 :: Double))

              let writeRows !rowIndex !entryIndex
                    | rowIndex >= rowCount =
                        unsafeFreezeByteArray targetArray
                    | otherwise = do
                        let !firstColumn =
                              max 0 (rowIndex - lowerBandwidth)
                            !lastColumn =
                              min
                                (rowCount - 1)
                                (rowIndex + upperBandwidth)
                            !entryCount =
                              lastColumn - firstColumn + 1
                            !rowValue =
                              accumulateBand
                                entryIndex
                                firstColumn
                                entryCount
                                0.0
                        writeByteArray targetArray rowIndex rowValue
                        writeRows
                          (rowIndex + 1)
                          (entryIndex + entryCount)

                  accumulateBand :: Int -> Int -> Int -> Double -> Double
                  accumulateBand
                    !entryIndex
                    !columnIndex
                    !remaining
                    !accumulator
                      | remaining <= 0 = accumulator
                      | otherwise =
                          let !coefficient =
                                ( indexByteArray
                                    coefficientArray
                                    (coefficientBase + entryIndex)
                                    :: Double
                                )
                              !inputValue =
                                ( indexByteArray
                                    inputArray
                                    (inputBase + columnIndex)
                                    :: Double
                                )
                           in accumulateBand
                                (entryIndex + 1)
                                (columnIndex + 1)
                                (remaining - 1)
                                (accumulator + coefficient * inputValue)

              writeRows 0 0
          )
      )
{-# INLINE csrContiguousBandMatVecGeneralValidatedU #-}

csrMatVecBoxedDouble ::
  Int ->
  U.Vector Int ->
  U.Vector Int ->
  Box.Vector Double ->
  U.Vector Double ->
  Either MoonlightError (U.Vector Double)
csrMatVecBoxedDouble rowCount rowOffsets columnIndices values inputVector =
  validateCSRBoxedDoubleKernelVectors rowCount rowOffsets columnIndices values inputVector
    *> Right (csrMatVecBoxedDoubleValidated rowCount rowOffsets columnIndices values inputVector)

csrMatVecBoxedDoubleValidated ::
  Int ->
  U.Vector Int ->
  U.Vector Int ->
  Box.Vector Double ->
  U.Vector Double ->
  U.Vector Double
csrMatVecBoxedDoubleValidated rowCount rowOffsets columnIndices values inputVector =
  U.create $ do
    targetVector <- MU.unsafeNew rowCount
    let writeRows !rowIndex
          | rowIndex >= rowCount = pure targetVector
          | otherwise = do
              let !startIndex = rowOffsets `U.unsafeIndex` rowIndex
                  !stopIndex = rowOffsets `U.unsafeIndex` (rowIndex + 1)
                  !rowValue = accumulateRow startIndex stopIndex 0.0
              MU.unsafeWrite targetVector rowIndex rowValue
              writeRows (rowIndex + 1)

        accumulateRow !entryIndex !stopIndex !accumulator
          | entryIndex >= stopIndex = accumulator
          | otherwise =
              let !columnIndex = columnIndices `U.unsafeIndex` entryIndex
                  !coefficient = values `Box.unsafeIndex` entryIndex
                  !inputValue = inputVector `U.unsafeIndex` columnIndex
               in accumulateRow
                    (entryIndex + 1)
                    stopIndex
                    (accumulator + coefficient * inputValue)

    writeRows 0
{-# INLINE csrMatVecBoxedDoubleValidated #-}

validateCSRKernelVectors ::
  Int ->
  U.Vector Int ->
  U.Vector Int ->
  U.Vector Double ->
  U.Vector Double ->
  Either MoonlightError ()
validateCSRKernelVectors rowCount rowOffsets columnIndices values inputVector
  = validateCSRKernelShape rowCount rowOffsets columnIndices (U.length values) inputVector

validateCSRBoxedDoubleKernelVectors ::
  Int ->
  U.Vector Int ->
  U.Vector Int ->
  Box.Vector Double ->
  U.Vector Double ->
  Either MoonlightError ()
validateCSRBoxedDoubleKernelVectors rowCount rowOffsets columnIndices values inputVector =
  validateCSRKernelShape rowCount rowOffsets columnIndices (Box.length values) inputVector

validateCSRKernelShape ::
  Int ->
  U.Vector Int ->
  U.Vector Int ->
  Int ->
  U.Vector Double ->
  Either MoonlightError ()
validateCSRKernelShape rowCount rowOffsets columnIndices entryCount inputVector
  | rowCount < 0 = Left (InvariantViolation "CSR matvec row count must be non-negative")
  | U.length rowOffsets /= rowCount + 1 =
      Left
        ( InvariantViolation
            ( "CSR row offset length mismatch: expected "
                <> show (rowCount + 1)
                <> " but received "
                <> show (U.length rowOffsets)
            )
        )
  | U.length columnIndices /= entryCount =
      Left
        ( InvariantViolation
            ( "CSR column/value length mismatch: "
                <> show (U.length columnIndices)
                <> " columns but "
                <> show entryCount
                <> " values"
            )
        )
  | not offsetsValid = Left (InvariantViolation "CSR row offsets are not a valid nondecreasing range")
  | not columnsValid = Left (InvariantViolation "CSR column index out of input-vector bounds")
  | otherwise = Right ()
  where
    offsetsValid =
      maybe False (== 0) (rowOffsets U.!? 0)
        && maybe False (== entryCount) (rowOffsets U.!? rowCount)
        && U.and (U.zipWith (<=) rowOffsets (U.drop 1 rowOffsets))
        && U.all (\offsetValue -> offsetValue >= 0 && offsetValue <= entryCount) rowOffsets
    inputLength = U.length inputVector
    columnsValid = U.all (\columnIndex -> columnIndex >= 0 && columnIndex < inputLength) columnIndices