packages feed

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

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}

module Moonlight.LinAlg.Internal.Primitives
  ( MatrixIndex,
    RowIndex,
    ColumnIndex,
    mkRowIndex,
    mkColumnIndex,
    rowIndexInt,
    columnIndexInt,
    rowIndices,
    columnIndices,
    natInt,
    epsilon,
    selectAt,
    selectAtIndex,
    requireAt,
    requireAtIndex,
    requireRow,
    requireColumnEntry,
    requireMatrixEntry,
    requireMatrixEntryAt,
    updateAt,
    replaceAt,
    replaceAtIndexChecked,
    replaceAtChecked,
    replaceRowChecked,
    replaceColumnEntryChecked,
    swapAtIndexChecked,
    swapAtChecked,
    swapRowsChecked,
    swapColumnsChecked,
    dotProduct,
    vectorNorm,
    scaleVector,
    addVector,
    subVector,
    matrixVectorProduct,
    matrixSubtract,
    scaleMatrix,
    outerProduct,
    basisVector,
    linearCombination,
  )
where

import Control.Monad (foldM)
import Data.Bifunctor (first)
import Data.Kind (Type)
import Data.Proxy (Proxy (..))
import GHC.TypeNats (KnownNat, natVal)
import Moonlight.Core (MoonlightError (..))
import Moonlight.LinAlg.Internal.DenseList (matrixVectorProductWith, outerProductWith, scaleMatrixWith)
import Prelude

type RowAxis :: Type
data RowAxis
type ColumnAxis :: Type
data ColumnAxis

type MatrixIndex :: Type -> Type
newtype MatrixIndex axis = MatrixIndex Int
  deriving stock (Eq, Ord)

instance Show (MatrixIndex axis) where
  show = show . matrixIndexInt

type RowIndex :: Type
type RowIndex = MatrixIndex RowAxis
type ColumnIndex :: Type
type ColumnIndex = MatrixIndex ColumnAxis

natInt :: forall n. KnownNat n => Int
natInt = fromIntegral (natVal (Proxy @n))

epsilon :: Double
epsilon = 1.0e-12

mkIndex :: MoonlightError -> Int -> Int -> Either MoonlightError (MatrixIndex axis)
mkIndex indexError upperBound candidateIndex
  | candidateIndex < 0 = Left indexError
  | candidateIndex >= upperBound = Left indexError
  | otherwise = Right (MatrixIndex candidateIndex)

mkRowIndex :: MoonlightError -> Int -> Int -> Either MoonlightError RowIndex
mkRowIndex = mkIndex

mkColumnIndex :: MoonlightError -> Int -> Int -> Either MoonlightError ColumnIndex
mkColumnIndex = mkIndex

matrixIndexInt :: MatrixIndex axis -> Int
matrixIndexInt (MatrixIndex indexValue) = indexValue

rowIndexInt :: RowIndex -> Int
rowIndexInt = matrixIndexInt

columnIndexInt :: ColumnIndex -> Int
columnIndexInt = matrixIndexInt

rowIndices :: Int -> [RowIndex]
rowIndices rowCount = map MatrixIndex [0 .. rowCount - 1]

columnIndices :: Int -> [ColumnIndex]
columnIndices columnCount = map MatrixIndex [0 .. columnCount - 1]

selectAt :: Int -> [a] -> Maybe a
selectAt targetIndex values
  | targetIndex < 0 = Nothing
  | otherwise =
      case drop targetIndex values of
        value : _ -> Just value
        [] -> Nothing

selectAtIndex :: MatrixIndex axis -> [a] -> Maybe a
selectAtIndex targetIndex = selectAt (matrixIndexInt targetIndex)

requireAt :: MoonlightError -> Int -> [a] -> Either MoonlightError a
requireAt lookupError targetIndex values =
  maybe
    (Left lookupError)
    Right
    (selectAt targetIndex values)

requireAtIndex :: MoonlightError -> MatrixIndex axis -> [a] -> Either MoonlightError a
requireAtIndex lookupError targetIndex values =
  maybe
    (Left lookupError)
    Right
    (selectAtIndex targetIndex values)

requireRow :: MoonlightError -> RowIndex -> [[a]] -> Either MoonlightError [a]
requireRow = requireAtIndex

requireColumnEntry :: MoonlightError -> ColumnIndex -> [a] -> Either MoonlightError a
requireColumnEntry = requireAtIndex

requireMatrixEntry :: MoonlightError -> Int -> Int -> [[a]] -> Either MoonlightError a
requireMatrixEntry lookupError rowIndex columnIndex matrixRows =
  requireAt lookupError rowIndex matrixRows
    >>= requireAt lookupError columnIndex

requireMatrixEntryAt :: MoonlightError -> RowIndex -> ColumnIndex -> [[a]] -> Either MoonlightError a
requireMatrixEntryAt lookupError rowIndex columnIndex matrixRows =
  requireRow lookupError rowIndex matrixRows
    >>= requireColumnEntry lookupError columnIndex

updateAt :: Int -> (a -> a) -> [a] -> [a]
updateAt targetIndex fn =
  map
    (\(indexValue, value) -> if indexValue == targetIndex then fn value else value)
    . zip [0 :: Int ..]

replaceAt :: Int -> a -> [a] -> [a]
replaceAt targetIndex replacement = updateAt targetIndex (const replacement)

replaceAtIndexChecked :: MoonlightError -> MatrixIndex axis -> a -> [a] -> Either MoonlightError [a]
replaceAtIndexChecked updateError targetIndex replacement values =
  requireAtIndex updateError targetIndex values
    >>= const (Right (replaceAt (matrixIndexInt targetIndex) replacement values))

replaceAtChecked :: MoonlightError -> Int -> a -> [a] -> Either MoonlightError [a]
replaceAtChecked updateError targetIndex replacement values =
  requireAt updateError targetIndex values
    >>= const (Right (replaceAt targetIndex replacement values))

replaceRowChecked :: MoonlightError -> RowIndex -> [a] -> [[a]] -> Either MoonlightError [[a]]
replaceRowChecked = replaceAtIndexChecked

replaceColumnEntryChecked :: MoonlightError -> ColumnIndex -> a -> [a] -> Either MoonlightError [a]
replaceColumnEntryChecked = replaceAtIndexChecked

swapAtIndexChecked :: MoonlightError -> MatrixIndex axis -> MatrixIndex axis -> [a] -> Either MoonlightError [a]
swapAtIndexChecked updateError leftIndex rightIndex values = do
  leftValue <- requireAtIndex updateError leftIndex values
  rightValue <- requireAtIndex updateError rightIndex values
  replaceAtIndexChecked updateError leftIndex rightValue values
    >>= replaceAtIndexChecked updateError rightIndex leftValue

swapAtChecked :: MoonlightError -> Int -> Int -> [a] -> Either MoonlightError [a]
swapAtChecked updateError leftIndex rightIndex values = do
  leftValue <- requireAt updateError leftIndex values
  rightValue <- requireAt updateError rightIndex values
  replaceAtChecked updateError leftIndex rightValue values
    >>= replaceAtChecked updateError rightIndex leftValue

swapRowsChecked :: MoonlightError -> RowIndex -> RowIndex -> [[a]] -> Either MoonlightError [[a]]
swapRowsChecked = swapAtIndexChecked

swapColumnsChecked :: MoonlightError -> ColumnIndex -> ColumnIndex -> [a] -> Either MoonlightError [a]
swapColumnsChecked = swapAtIndexChecked

dotProduct :: [Double] -> [Double] -> Either MoonlightError Double
dotProduct left right =
  go 0.0 left right
  where
    go !accumulator leftValues rightValues =
      case (leftValues, rightValues) of
        ([], []) -> Right accumulator
        (leftValue : leftRest, rightValue : rightRest) ->
          go
            (accumulator + leftValue * rightValue)
            leftRest
            rightRest
        _ ->
          Left
            ( InvariantViolation
                ( "dotProduct: length mismatch (left="
                    <> show (length left)
                    <> ", right="
                    <> show (length right)
                    <> ")"
                )
            )
{-# INLINE dotProduct #-}

vectorNorm :: [Double] -> Either MoonlightError Double
vectorNorm v = fmap sqrt (dotProduct v v)

scaleVector :: Double -> [Double] -> [Double]
scaleVector scalarValue = map (\value -> scalarValue * value)

addVector :: [Double] -> [Double] -> Either MoonlightError [Double]
addVector left right
  | length left /= length right =
      Left (InvariantViolation ("addVector: length mismatch (left=" <> show (length left) <> ", right=" <> show (length right) <> ")"))
  | otherwise = Right (zipWith (+) left right)

subVector :: [Double] -> [Double] -> Either MoonlightError [Double]
subVector left right
  | length left /= length right =
      Left (InvariantViolation ("subVector: length mismatch (left=" <> show (length left) <> ", right=" <> show (length right) <> ")"))
  | otherwise = Right (zipWith (-) left right)

matrixVectorProduct :: [[Double]] -> [Double] -> Either MoonlightError [Double]
matrixVectorProduct matrixRows vectorValue =
  first (\msg -> InvariantViolation ("matrixVectorProduct: " <> msg)) (matrixVectorProductWith (*) (+) 0.0 matrixRows vectorValue)

matrixSubtract :: [[Double]] -> [[Double]] -> Either MoonlightError [[Double]]
matrixSubtract left right
  | length left /= length right =
      Left (InvariantViolation ("matrixSubtract: row count mismatch (left=" <> show (length left) <> ", right=" <> show (length right) <> ")"))
  | otherwise = traverse (\(l, r) -> subVector l r) (zip left right)

scaleMatrix :: Double -> [[Double]] -> [[Double]]
scaleMatrix = scaleMatrixWith (*)

outerProduct :: [Double] -> [Double] -> [[Double]]
outerProduct = outerProductWith (*)

basisVector :: Int -> Int -> [Double]
basisVector size indexValue =
  map (\position -> if position == indexValue then 1.0 else 0.0) [0 .. size - 1]

linearCombination :: [(Double, [Double])] -> Either MoonlightError [Double]
linearCombination [] = Right []
linearCombination ((firstCoefficient, firstVector) : rest) =
  foldM
    (\accumulator (coefficient, vectorValue) -> addVector accumulator (scaleVector coefficient vectorValue))
    (scaleVector firstCoefficient firstVector)
    rest