packages feed

moonlight-linalg-0.1.0.0: src-dense/Moonlight/LinAlg/Internal/Backend/RREF.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

module Moonlight.LinAlg.Internal.Backend.RREF
  ( RREF (..),
    KernelBasis (..),
    rrefRowsFrom,
    rrefFromMatrix,
    rankPure,
    kernelPure,
  )
where

import Data.Kind (Type)
import GHC.TypeNats (KnownNat, Nat)
import Moonlight.Core
  ( AdditiveGroup (..),
    AdditiveMonoid (..),
    Field (..),
    MoonlightError (..),
    MultiplicativeMonoid (..),
    requireInvertible,
  )
import Moonlight.LinAlg.Internal.Backend.Elimination
  ( EliminationConfig (..),
    EliminationScope (..),
    EliminationState (..),
    PivotResult (..),
    runElimination,
  )
import Moonlight.LinAlg.Internal.Backend.RowOps
  ( findPivotRow,
  )
import Moonlight.LinAlg.Internal.Primitives
  ( ColumnIndex,
    RowIndex,
    columnIndexInt,
    columnIndices,
    mkRowIndex,
    replaceAt,
    requireColumnEntry,
    requireRow,
    rowIndexInt,
  )
import Moonlight.LinAlg.Pure.Dense.Types
  ( Matrix,
    Vector,
    fromListVector,
  )
import qualified Moonlight.LinAlg.Pure.Dense.Types as DenseTypes
import Prelude

type RREF :: Type -> Type -> Type
data RREF pivot a = RREF
  { rrefPivotColumns :: [pivot],
    rrefRows :: [[a]]
  }
  deriving stock (Eq, Show)

type KernelBasis :: Nat -> Type -> Type
newtype KernelBasis c a = KernelBasis
  { kernelBasisVectors :: [Vector c a]
  }

rrefConfigAt ::
  (Field a, Eq a) =>
  Int ->
  Int ->
  EliminationConfig a ()
rrefConfigAt rowCount pivotRow =
  EliminationConfig
    { elimSelectPivot = \step col rs -> do
        pivotRowIndex <-
          mkRowIndex
            (InvariantViolation ("RREF pivot row out of bounds at index " <> show (pivotRow + step)))
            rowCount
            (pivotRow + step)
        findPivotRow pivotRowIndex col rs >>= \maybePivot ->
          case maybePivot of
            Nothing -> Right NoPivotSkip
            Just pivotIndex -> Right (PivotFound pivotIndex),
      elimCandidateColumns = \_ cols -> cols,
      elimNormalizePivot = rrefNormalizePivot,
      elimScope = FullReduction,
      elimReduceRow = rrefReduceRow,
      elimOnSwap = \_ _ s -> Right s,
      elimMaxSteps = rowCount - pivotRow
    }

rrefNormalizePivot ::
  (Field a) =>
  RowIndex ->
  ColumnIndex ->
  [[a]] ->
  Either MoonlightError [[a]]
rrefNormalizePivot pivotRow pivotColumn rows = do
  pivotRowValues <-
    requireRow
      (InvariantViolation ("RREF pivot row missing after swap at row " <> show pivotRow))
      pivotRow
      rows
  pivotValue <-
    requireColumnEntry
      (InvariantViolation ("RREF pivot column missing at column " <> show pivotColumn))
      pivotColumn
      pivotRowValues
  pivotInverse <-
    requireInvertible
      (InvariantViolation ("RREF pivot at column " <> show pivotColumn <> " is not invertible"))
      pivotValue
  let normalizedRow = map (\entry -> entry `mul` pivotInverse) pivotRowValues
  Right (replaceAt (rowIndexInt pivotRow) normalizedRow rows)

rrefReduceRow ::
  (Field a, Eq a) =>
  [a] ->
  [a] ->
  ColumnIndex ->
  RowIndex ->
  RowIndex ->
  () ->
  Either MoonlightError ([a], ())
rrefReduceRow pivotRowValues targetRowValues pivotColumn _ _ sideState = do
  factorEntry <-
    requireColumnEntry
      (InvariantViolation ("RREF elimination missing pivot column " <> show pivotColumn))
      pivotColumn
      targetRowValues
  if factorEntry == zero
    then Right (targetRowValues, sideState)
    else Right (zipWith (\entry pivotEntry -> entry `sub` (factorEntry `mul` pivotEntry)) targetRowValues pivotRowValues, sideState)

rrefRowsFrom :: (Field a, Eq a) => Int -> Int -> Int -> [[a]] -> Either MoonlightError (RREF Int a)
rrefRowsFrom rowCount columnCount pivotRow rows
  | pivotRow < 0 = Left (InvariantViolation ("RREF pivot row cannot be negative: " <> show pivotRow))
  | otherwise = do
      result <-
        runElimination
          (rrefConfigAt rowCount pivotRow)
          rows
          ()
          (columnIndices columnCount)
      Right
        RREF
          { rrefPivotColumns = map columnIndexInt (elimPivots result),
            rrefRows = elimRows result
          }

rrefFromMatrixIndexed ::
  forall r c a.
  (KnownNat r, KnownNat c, Eq a, Field a) =>
  Matrix r c a ->
  Either MoonlightError (RREF ColumnIndex a)
rrefFromMatrixIndexed matrixValue = do
  rows <- DenseTypes.matrixToRows matrixValue
  let (rowCount, columnCount) = DenseTypes.matrixShape matrixValue
  result <-
    runElimination
      (rrefConfigAt rowCount 0)
      rows
      ()
      (columnIndices columnCount)
  Right
    RREF
      { rrefPivotColumns = elimPivots result,
        rrefRows = elimRows result
      }

rrefFromMatrix ::
  forall r c a.
  (KnownNat r, KnownNat c, Eq a, Field a) =>
  Matrix r c a ->
  Either MoonlightError (RREF Int a)
rrefFromMatrix matrixValue =
  fmap
    ( \rrefValue ->
        RREF
          { rrefPivotColumns = map columnIndexInt (rrefPivotColumns rrefValue),
            rrefRows = rrefRows rrefValue
          }
    )
    (rrefFromMatrixIndexed matrixValue)

rankPure ::
  forall r c a.
  (KnownNat r, KnownNat c, Eq a, Field a) =>
  Matrix r c a ->
  Either MoonlightError Int
rankPure matrixValue =
  fmap (length . rrefPivotColumns) (rrefFromMatrixIndexed matrixValue)

kernelPure ::
  forall r c a.
  (KnownNat r, KnownNat c, Eq a, Field a) =>
  Matrix r c a ->
  Either MoonlightError (KernelBasis c a)
kernelPure matrixValue = do
  rrefValue <- rrefFromMatrixIndexed matrixValue
  let (_, columnCount) = DenseTypes.matrixShape matrixValue
      allColumns = columnIndices columnCount
      pivotColumns = rrefPivotColumns rrefValue
      reducedRows = rrefRows rrefValue
      pivotRows = zip pivotColumns reducedRows
      freeColumns = filter (\columnIndex -> columnIndex `notElem` pivotColumns) allColumns
      basisVector freeColumn =
        let entryAt columnIndex =
              if columnIndex == freeColumn
                then one
                else
                  case lookup columnIndex pivotRows of
                    Nothing -> zero
                    Just pivotRowValues ->
                      maybe zero neg (lookup freeColumn (zip allColumns pivotRowValues))
         in fromListVector (map entryAt allColumns)
  KernelBasis <$> traverse basisVector freeColumns