packages feed

moonlight-linalg-0.1.0.0: src-spectral/Moonlight/LinAlg/Pure/Operator/Internal.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StrictData #-}

module Moonlight.LinAlg.Pure.Operator.Internal
  ( OperatorSymmetry (..),
    LinearOperator (..),
    OperatorSource (..),
    ApplyU,
    operatorShape,
    operatorDimension,
    mkVectorLinearOperator,
    declaredSelfAdjointVectorLinearOperator,
    runOperatorU,
    csrLinearOperator,
    selfAdjointCSRLinearOperator,
    graphLaplacianLinearOperator,
    diagonalLinearOperator,
    pathLaplacianLinearOperator,
    symmetricTridiagonalLinearOperator,
    packedSparseLinearOperator,
    scaleLinearOperator,
    addScaledIdentity,
    sigmaIdentityMinus,
    applyOperatorSource,
    operatorSourceShape,
  )
where

import Data.Kind (Type)
import Data.Map.Strict qualified as Map
import Data.Vector.Unboxed qualified as U
import Moonlight.Core (MoonlightError (..), fieldValueValid)
import Moonlight.LinAlg.Internal.VectorOps (csrMatVecValidatedU)
import Moonlight.LinAlg.Pure.Sparse.Packed
  ( PackedSparseApplyError (..),
    PackedSparseOperator,
    applyPackedSparseOperatorDense,
    packedSparseOperatorSourceCardinality,
    packedSparseOperatorTargetCardinality,
  )
import Moonlight.LinAlg.Pure.Sparse.Structured
  ( GraphEdge,
    graphLaplacianCSR,
    symmetricTridiagonalFromCSR,
  )
import Moonlight.LinAlg.Pure.Sparse.Types
  ( SparseCSR,
    cooEntries,
    csrCols,
    csrColumnIndicesVector,
    csrRows,
    csrRowOffsetsVector,
    csrToCOO,
    csrValuesVector,
  )
import Moonlight.LinAlg.Pure.Structured.Tridiagonal
  ( SymmetricTridiagonal,
    applyPathLaplacianValidatedU,
    applySymmetricTridiagonalValidatedU,
    isPathLaplacianTridiagonal,
    symmetricTridiagonalDimension,
  )
import Prelude

data OperatorSymmetry
  = GeneralOperator
  | SelfAdjointOperator

type ApplyU :: Type
type ApplyU = U.Vector Double -> Either MoonlightError (U.Vector Double)

type OperatorSource :: OperatorSymmetry -> Type
data OperatorSource symmetry where
  OpaqueGeneralSource ::
    !Int ->
    !Int ->
    !ApplyU ->
    OperatorSource 'GeneralOperator
  DeclaredSelfAdjointSource ::
    !Int ->
    !ApplyU ->
    OperatorSource 'SelfAdjointOperator
  CSRSource ::
    !(SparseCSR Double) ->
    OperatorSource 'GeneralOperator
  SelfAdjointCSRSource ::
    !(SparseCSR Double) ->
    OperatorSource 'SelfAdjointOperator
  GraphLaplacianCSRSource ::
    !(SparseCSR Double) ->
    OperatorSource 'SelfAdjointOperator
  DiagonalSource ::
    !(U.Vector Double) ->
    OperatorSource 'SelfAdjointOperator
  PathLaplacianSource ::
    !Int ->
    OperatorSource 'SelfAdjointOperator
  SymmetricTridiagonalSource ::
    !SymmetricTridiagonal ->
    OperatorSource 'SelfAdjointOperator
  PackedSparseSource ::
    !(PackedSparseOperator Double) ->
    OperatorSource 'GeneralOperator

type LinearOperator :: OperatorSymmetry -> Type
data LinearOperator symmetry = LinearOperator
  { operatorSourceScale :: !Double,
    operatorIdentityShift :: !Double,
    operatorSource :: !(OperatorSource symmetry)
  }

operatorShape :: LinearOperator symmetry -> (Int, Int)
operatorShape = operatorSourceShape . operatorSource

operatorDimension :: LinearOperator 'SelfAdjointOperator -> Int
operatorDimension operatorValue =
  case operatorShape operatorValue of
    (rowCount, _) -> rowCount

operatorSourceShape :: OperatorSource symmetry -> (Int, Int)
operatorSourceShape sourceValue =
  case sourceValue of
    OpaqueGeneralSource rowCount columnCount _ -> (rowCount, columnCount)
    DeclaredSelfAdjointSource dimension _ -> (dimension, dimension)
    CSRSource csrValue -> (csrRows csrValue, csrCols csrValue)
    SelfAdjointCSRSource csrValue -> (csrRows csrValue, csrCols csrValue)
    GraphLaplacianCSRSource csrValue -> (csrRows csrValue, csrCols csrValue)
    DiagonalSource diagonalEntries -> (U.length diagonalEntries, U.length diagonalEntries)
    PathLaplacianSource dimension -> (dimension, dimension)
    SymmetricTridiagonalSource tridiagonalValue ->
      let dimension = symmetricTridiagonalDimension tridiagonalValue
       in (dimension, dimension)
    PackedSparseSource packedOperator ->
      ( packedSparseOperatorTargetCardinality packedOperator,
        packedSparseOperatorSourceCardinality packedOperator
      )

mkVectorLinearOperator :: Int -> Int -> ApplyU -> Either MoonlightError (LinearOperator 'GeneralOperator)
mkVectorLinearOperator rowCount columnCount applyVector =
  validateRectangularDimensions "linear operator" rowCount columnCount
    *> pure
      LinearOperator
        { operatorSourceScale = 1.0,
          operatorIdentityShift = 0.0,
          operatorSource = OpaqueGeneralSource rowCount columnCount (checkedApply rowCount columnCount applyVector)
        }

declaredSelfAdjointVectorLinearOperator :: Int -> ApplyU -> Either MoonlightError (LinearOperator 'SelfAdjointOperator)
declaredSelfAdjointVectorLinearOperator dimension applyVector =
  validateDimension "declared self-adjoint linear operator" dimension
    *> pure
      LinearOperator
        { operatorSourceScale = 1.0,
          operatorIdentityShift = 0.0,
          operatorSource = DeclaredSelfAdjointSource dimension (checkedApply dimension dimension applyVector)
        }

csrLinearOperator :: SparseCSR Double -> LinearOperator 'GeneralOperator
csrLinearOperator csrValue =
  LinearOperator
    { operatorSourceScale = 1.0,
      operatorIdentityShift = 0.0,
      operatorSource = CSRSource csrValue
    }

selfAdjointCSRLinearOperator :: SparseCSR Double -> Either MoonlightError (LinearOperator 'SelfAdjointOperator)
selfAdjointCSRLinearOperator csrValue = do
  validateSelfAdjointCSR csrValue
  sourceValue <-
    classifyStructuredSelfAdjointCSR
      SelfAdjointCSRSource
      csrValue
  pure
    LinearOperator
      { operatorSourceScale = 1.0,
        operatorIdentityShift = 0.0,
        operatorSource = sourceValue
      }

graphLaplacianLinearOperator ::
  (Ord vertex, Show vertex) =>
  [vertex] ->
  [GraphEdge vertex] ->
  Either MoonlightError (LinearOperator 'SelfAdjointOperator)
graphLaplacianLinearOperator vertexOrder graphEdges = do
  csrValue <- graphLaplacianCSR vertexOrder graphEdges
  sourceValue <-
    classifyStructuredSelfAdjointCSR
      GraphLaplacianCSRSource
      csrValue
  pure
    LinearOperator
      { operatorSourceScale = 1.0,
        operatorIdentityShift = 0.0,
        operatorSource = sourceValue
      }

classifyStructuredSelfAdjointCSR ::
  (SparseCSR Double -> OperatorSource 'SelfAdjointOperator) ->
  SparseCSR Double ->
  Either MoonlightError (OperatorSource 'SelfAdjointOperator)
classifyStructuredSelfAdjointCSR fallbackSource csrValue = do
  classifiedStructure <- symmetricTridiagonalFromCSR csrValue
  pure
    ( case classifiedStructure of
        Right tridiagonalValue
          | isPathLaplacianTridiagonal tridiagonalValue ->
              PathLaplacianSource
                (symmetricTridiagonalDimension tridiagonalValue)
          | otherwise ->
              SymmetricTridiagonalSource tridiagonalValue
        Left _ -> fallbackSource csrValue
    )

diagonalLinearOperator :: U.Vector Double -> Either MoonlightError (LinearOperator 'SelfAdjointOperator)
diagonalLinearOperator diagonalEntries =
  if U.any (not . fieldValueValid) diagonalEntries
    then Left (InvariantViolation "diagonal linear operator requires finite entries")
    else
      pure
        LinearOperator
          { operatorSourceScale = 1.0,
            operatorIdentityShift = 0.0,
            operatorSource = DiagonalSource diagonalEntries
          }

pathLaplacianLinearOperator :: Int -> Either MoonlightError (LinearOperator 'SelfAdjointOperator)
pathLaplacianLinearOperator dimension =
  validateDimension "path Laplacian linear operator" dimension
    *> pure
      LinearOperator
        { operatorSourceScale = 1.0,
          operatorIdentityShift = 0.0,
          operatorSource = PathLaplacianSource dimension
        }

symmetricTridiagonalLinearOperator :: SymmetricTridiagonal -> LinearOperator 'SelfAdjointOperator
symmetricTridiagonalLinearOperator tridiagonalValue =
  LinearOperator
    { operatorSourceScale = 1.0,
      operatorIdentityShift = 0.0,
      operatorSource = SymmetricTridiagonalSource tridiagonalValue
    }

packedSparseLinearOperator :: PackedSparseOperator Double -> LinearOperator 'GeneralOperator
packedSparseLinearOperator packedOperator =
  LinearOperator
    { operatorSourceScale = 1.0,
      operatorIdentityShift = 0.0,
      operatorSource = PackedSparseSource packedOperator
    }

scaleLinearOperator :: Double -> LinearOperator symmetry -> LinearOperator symmetry
scaleLinearOperator scaleValue operatorValue =
  operatorValue {operatorSourceScale = scaleValue * operatorSourceScale operatorValue, operatorIdentityShift = scaleValue * operatorIdentityShift operatorValue}

addScaledIdentity :: Double -> LinearOperator 'SelfAdjointOperator -> LinearOperator 'SelfAdjointOperator
addScaledIdentity shiftValue operatorValue =
  operatorValue {operatorIdentityShift = shiftValue + operatorIdentityShift operatorValue}

sigmaIdentityMinus :: Double -> LinearOperator 'SelfAdjointOperator -> LinearOperator 'SelfAdjointOperator
sigmaIdentityMinus sigma operatorValue =
  operatorValue
    { operatorSourceScale = negate (operatorSourceScale operatorValue),
      operatorIdentityShift = sigma - operatorIdentityShift operatorValue
    }

runOperatorU :: LinearOperator symmetry -> U.Vector Double -> Either MoonlightError (U.Vector Double)
runOperatorU operatorValue inputVector = do
  sourceImage <- applyOperatorSource (operatorSource operatorValue) inputVector
  applyAffineImage (operatorSourceScale operatorValue) (operatorIdentityShift operatorValue) sourceImage inputVector

applyOperatorSource :: OperatorSource symmetry -> U.Vector Double -> Either MoonlightError (U.Vector Double)
applyOperatorSource sourceValue inputVector =
  case sourceValue of
    OpaqueGeneralSource rowCount columnCount applyVector -> checkedApply rowCount columnCount applyVector inputVector
    DeclaredSelfAdjointSource dimension applyVector -> checkedApply dimension dimension applyVector inputVector
    CSRSource csrValue -> applyCSR csrValue inputVector
    SelfAdjointCSRSource csrValue -> applyCSR csrValue inputVector
    GraphLaplacianCSRSource csrValue -> applyCSR csrValue inputVector
    DiagonalSource diagonalEntries -> applyDiagonal diagonalEntries inputVector
    PathLaplacianSource dimension ->
      if U.length inputVector == dimension
        then Right (applyPathLaplacianValidatedU dimension inputVector)
        else
          Left
            ( InvariantViolation
                ( "path Laplacian input dimension mismatch: expected "
                    <> show dimension
                    <> " but received "
                    <> show (U.length inputVector)
                )
            )
    SymmetricTridiagonalSource tridiagonalValue ->
      let dimension = symmetricTridiagonalDimension tridiagonalValue
       in if U.length inputVector == dimension
            then
              Right
                ( applySymmetricTridiagonalValidatedU
                    tridiagonalValue
                    inputVector
                )
            else
              Left
                ( InvariantViolation
                    ( "symmetric tridiagonal operator input dimension mismatch: expected "
                        <> show dimension
                        <> " but received "
                        <> show (U.length inputVector)
                    )
                )
    PackedSparseSource packedOperator ->
      case applyPackedSparseOperatorDense packedOperator inputVector of
        Right output -> Right output
        Left applyError -> Left (packedSparseApplyErrorToMoonlightError applyError)

applyAffineImage :: Double -> Double -> U.Vector Double -> U.Vector Double -> Either MoonlightError (U.Vector Double)
applyAffineImage scaleValue shiftValue sourceImage inputVector
  | shiftValue == 0.0 && scaleValue == 1.0 = Right sourceImage
  | shiftValue == 0.0 = Right (U.map (scaleValue *) sourceImage)
  | U.length sourceImage == U.length inputVector =
      Right (U.zipWith (\imageEntry inputEntry -> scaleValue * imageEntry + shiftValue * inputEntry) sourceImage inputVector)
  | otherwise = Left (InvariantViolation "identity shift requires a square operator image")

applyCSR :: SparseCSR Double -> U.Vector Double -> Either MoonlightError (U.Vector Double)
applyCSR csrValue inputVector =
  if U.length inputVector /= csrCols csrValue
    then Left (InvariantViolation ("CSR matvec dimension mismatch: expected " <> show (csrCols csrValue) <> " but received " <> show (U.length inputVector)))
    else Right (csrMatVecValidatedU (csrRows csrValue) (csrRowOffsetsVector csrValue) (csrColumnIndicesVector csrValue) (csrValuesVector csrValue) inputVector)

applyDiagonal :: U.Vector Double -> U.Vector Double -> Either MoonlightError (U.Vector Double)
applyDiagonal diagonalEntries inputVector =
  if U.length inputVector /= U.length diagonalEntries
    then Left (InvariantViolation ("diagonal operator input dimension mismatch: expected " <> show (U.length diagonalEntries) <> " but received " <> show (U.length inputVector)))
    else Right (U.zipWith (*) diagonalEntries inputVector)

checkedApply :: Int -> Int -> ApplyU -> ApplyU
checkedApply rowCount columnCount applyVector inputVector =
  if U.length inputVector /= columnCount
    then Left (InvariantViolation ("linear operator input dimension mismatch: expected " <> show columnCount <> " but received " <> show (U.length inputVector)))
    else do
      outputVector <- applyVector inputVector
      if U.length outputVector == rowCount
        then Right outputVector
        else Left (InvariantViolation ("linear operator output dimension mismatch: expected " <> show rowCount <> " but received " <> show (U.length outputVector)))

validateRectangularDimensions :: String -> Int -> Int -> Either MoonlightError ()
validateRectangularDimensions label rowCount columnCount =
  if rowCount < 0 || columnCount < 0
    then Left (InvariantViolation (label <> " dimensions must be non-negative"))
    else Right ()

validateDimension :: String -> Int -> Either MoonlightError ()
validateDimension label dimension =
  if dimension <= 0
    then Left (InvariantViolation (label <> " dimension must be positive, received " <> show dimension))
    else Right ()

validateSelfAdjointCSR :: SparseCSR Double -> Either MoonlightError ()
validateSelfAdjointCSR csrValue = do
  if csrRows csrValue /= csrCols csrValue
    then Left (InvariantViolation "self-adjoint CSR operator requires a square matrix")
    else do
      cooValue <- csrToCOO csrValue
      let entryMap = Map.fromList (((\(rowIndex, columnIndex, value) -> ((rowIndex, columnIndex), value)) <$> cooEntries cooValue))
          symmetricEntry ((rowIndex, columnIndex), value) = Map.lookup (columnIndex, rowIndex) entryMap == Just value
      if all symmetricEntry (Map.toList entryMap)
        then Right ()
        else Left (InvariantViolation "self-adjoint CSR operator requires exact symmetric storage")

packedSparseApplyErrorToMoonlightError :: PackedSparseApplyError -> MoonlightError
packedSparseApplyErrorToMoonlightError errorValue =
  case errorValue of
    PackedSparseInputLengthMismatch expectedLength actualLength ->
      InvariantViolation ("packed sparse operator input dimension mismatch: expected " <> show expectedLength <> " but received " <> show actualLength)