packages feed

moonlight-linalg-0.1.0.0: src-spectral/Moonlight/LinAlg/Pure/Krylov/Decomposition.hs

{-# LANGUAGE StrictData #-}

module Moonlight.LinAlg.Pure.Krylov.Decomposition
  ( ArnoldiDecomposition,
    mkArnoldiDecomposition,
    arnoldiBasisColumns,
    arnoldiHessenbergRows,
    arnoldiStepsCompleted,
    LanczosDecomposition,
    mkLanczosDecomposition,
    lanczosBasisColumns,
    lanczosProjectedTridiagonal,
    lanczosAlphaDiagonal,
    lanczosBetaOffDiagonal,
    lanczosResidualNorm,
    lanczosStepsCompleted,
    BlockLanczosDecomposition,
    mkBlockLanczosDecomposition,
    blockLanczosBasisColumns,
    blockLanczosProjectedBlockTridiagonal,
    blockLanczosBasisCount,
    blockLanczosBlockSteps,
  )
where

import Data.Kind (Type)
import Data.Vector qualified as Box
import Data.Vector.Unboxed qualified as U
import Moonlight.Core (MoonlightError (..))
import Moonlight.LinAlg.Pure.Structured.BlockTridiagonal
  ( SymmetricBlockTridiagonal,
    symmetricBlockTridiagonalDimension,
  )
import Moonlight.LinAlg.Pure.Structured.Tridiagonal
  ( SymmetricTridiagonal,
    symmetricTridiagonalDiagonalEntries,
    symmetricTridiagonalDimension,
    symmetricTridiagonalOffDiagonalEntries,
  )
import Prelude

type ArnoldiDecomposition :: Type
data ArnoldiDecomposition = ArnoldiDecomposition
  { arnoldiBasisColumns :: !(Box.Vector (U.Vector Double)),
    arnoldiHessenbergRows :: !(Box.Vector (U.Vector Double))
  }
  deriving stock (Eq, Show)

mkArnoldiDecomposition :: Box.Vector (U.Vector Double) -> Box.Vector (U.Vector Double) -> Either MoonlightError ArnoldiDecomposition
mkArnoldiDecomposition basisColumns hessenbergRows = do
  stepCount <- validateBasisColumns "Arnoldi" basisColumns
  if Box.length hessenbergRows /= stepCount + 1
    then
      Left
        ( InvariantViolation
            ( "Arnoldi Hessenberg row count mismatch: expected "
                <> show (stepCount + 1)
                <> " but received "
                <> show (Box.length hessenbergRows)
            )
        )
    else
      if any ((/= stepCount) . U.length) (Box.toList hessenbergRows)
        then Left (InvariantViolation "Arnoldi Hessenberg rows must have equal length matching the step count")
        else Right (ArnoldiDecomposition basisColumns hessenbergRows)

arnoldiStepsCompleted :: ArnoldiDecomposition -> Int
arnoldiStepsCompleted = Box.length . arnoldiBasisColumns

type LanczosDecomposition :: Type
data LanczosDecomposition = LanczosDecomposition
  { lanczosBasisColumns :: !(Box.Vector (U.Vector Double)),
    lanczosProjectedTridiagonal :: !SymmetricTridiagonal,
    lanczosResidualNorm :: !Double
  }
  deriving stock (Eq, Show)

mkLanczosDecomposition ::
  Box.Vector (U.Vector Double) ->
  SymmetricTridiagonal ->
  Double ->
  Either MoonlightError LanczosDecomposition
mkLanczosDecomposition basisColumns projectedTridiagonal residualNorm = do
  basisCount <- validateBasisColumns "Lanczos" basisColumns
  let projectedDimension = symmetricTridiagonalDimension projectedTridiagonal
  if projectedDimension /= basisCount
    then
      Left
        ( InvariantViolation
            ( "Lanczos projected tridiagonal dimension mismatch: expected "
                <> show basisCount
                <> " but received "
                <> show projectedDimension
            )
        )
    else Right (LanczosDecomposition basisColumns projectedTridiagonal residualNorm)

lanczosAlphaDiagonal :: LanczosDecomposition -> [Double]
lanczosAlphaDiagonal = symmetricTridiagonalDiagonalEntries . lanczosProjectedTridiagonal

lanczosBetaOffDiagonal :: LanczosDecomposition -> [Double]
lanczosBetaOffDiagonal = symmetricTridiagonalOffDiagonalEntries . lanczosProjectedTridiagonal

lanczosStepsCompleted :: LanczosDecomposition -> Int
lanczosStepsCompleted =
  symmetricTridiagonalDimension . lanczosProjectedTridiagonal

type BlockLanczosDecomposition :: Type
data BlockLanczosDecomposition = BlockLanczosDecomposition
  { blockLanczosBasisColumns :: !(Box.Vector (U.Vector Double)),
    blockLanczosProjectedBlockTridiagonal :: !SymmetricBlockTridiagonal,
    blockLanczosBlockSteps :: !Int
  }
  deriving stock (Eq, Show)

mkBlockLanczosDecomposition ::
  Box.Vector (U.Vector Double) ->
  SymmetricBlockTridiagonal ->
  Int ->
  Either MoonlightError BlockLanczosDecomposition
mkBlockLanczosDecomposition basisColumns projectedBlockTridiagonal blockStepCount = do
  basisCount <- validateBasisColumns "Block Lanczos" basisColumns
  let projectedDimension = symmetricBlockTridiagonalDimension projectedBlockTridiagonal
  if blockStepCount <= 0
    then Left (InvariantViolation "Block Lanczos step count must be positive")
    else
      if projectedDimension /= basisCount
        then
          Left
            ( InvariantViolation
                ( "Block Lanczos projected operator dimension mismatch: expected "
                    <> show basisCount
                    <> " but received "
                    <> show projectedDimension
                )
            )
        else Right (BlockLanczosDecomposition basisColumns projectedBlockTridiagonal blockStepCount)

blockLanczosBasisCount :: BlockLanczosDecomposition -> Int
blockLanczosBasisCount =
  symmetricBlockTridiagonalDimension . blockLanczosProjectedBlockTridiagonal

validateBasisColumns :: String -> Box.Vector (U.Vector Double) -> Either MoonlightError Int
validateBasisColumns algorithmName basisColumns =
  let basisCount = Box.length basisColumns
      basisDimensions = U.length <$> Box.toList basisColumns
      basisDimension =
        case basisDimensions of
          [] -> 0
          firstDimension : _ -> firstDimension
   in if basisCount <= 0
        then Left (InvariantViolation (algorithmName <> " basis must be non-empty"))
        else
          if any (/= basisDimension) basisDimensions
            then Left (InvariantViolation (algorithmName <> " basis columns must have equal length"))
            else Right basisCount