packages feed

moonlight-linalg-0.1.0.0: src-sparse/Moonlight/LinAlg/Pure/Sparse/Solver/Common.hs

{-# LANGUAGE BangPatterns #-}

module Moonlight.LinAlg.Pure.Sparse.Solver.Common
  ( finiteDouble,
    validateSparseSystemInput,
    validateSparseSolverConfiguration,
    sparseDiagonal,
    solverEpsilon,
    shiftedDiagonalValue,
  )
where

import Data.Vector.Unboxed qualified as U
import Moonlight.Core (fieldValueValid)
import Moonlight.LinAlg.Pure.Sparse.Solver.Types (SparseIterativeFailure (..))
import Moonlight.LinAlg.Pure.Sparse.Types
  ( SparseCSR,
    csrCols,
    csrColumnIndicesVector,
    csrRows,
    csrRowOffsetsVector,
    csrValuesVector,
  )
import Prelude

finiteDouble :: Double -> Bool
finiteDouble = fieldValueValid
{-# INLINE finiteDouble #-}

validateSparseSystemInput ::
  SparseCSR Double ->
  U.Vector Double ->
  U.Vector Double ->
  Either SparseIterativeFailure ()
validateSparseSystemInput sparseMatrix rhsValues initialGuess
  | dimension /= csrCols sparseMatrix =
      Left (SparseInvalidInput "sparse iterative solver expects a square system matrix")
  | U.length rhsValues /= dimension =
      Left (SparseInvalidInput "sparse iterative solver expects RHS length equal to matrix dimension")
  | U.length initialGuess /= dimension =
      Left (SparseInvalidInput "sparse iterative solver expects initial guess length equal to matrix dimension")
  | U.any (not . fieldValueValid) (csrValuesVector sparseMatrix) =
      Left (SparseInvalidInput "sparse iterative solver matrix entries must be finite")
  | U.any (not . fieldValueValid) rhsValues =
      Left (SparseInvalidInput "sparse iterative solver right-hand side must be finite")
  | U.any (not . fieldValueValid) initialGuess =
      Left (SparseInvalidInput "sparse iterative solver initial guess must be finite")
  | otherwise = Right ()
  where
    dimension = csrRows sparseMatrix

validateSparseSolverConfiguration ::
  String ->
  Double ->
  Int ->
  Either SparseIterativeFailure ()
validateSparseSolverConfiguration methodName toleranceValue iterationLimit
  | not (fieldValueValid toleranceValue) =
      Left (SparseInvalidInput (methodName <> " tolerance must be finite"))
  | toleranceValue < 0.0 =
      Left (SparseInvalidInput (methodName <> " tolerance must be non-negative"))
  | iterationLimit < 0 =
      Left (SparseInvalidInput (methodName <> " iteration limit must be non-negative"))
  | otherwise = Right ()

sparseDiagonal ::
  SparseCSR Double ->
  Either SparseIterativeFailure (U.Vector Double)
sparseDiagonal sparseMatrix =
  Right
    ( U.generate
        (csrRows sparseMatrix)
        (diagonalAt sparseMatrix)
    )

diagonalAt :: SparseCSR Double -> Int -> Double
diagonalAt sparseMatrix rowIndex =
  findDiagonal startOffset
  where
    !rowOffsets = csrRowOffsetsVector sparseMatrix
    !columnIndices = csrColumnIndicesVector sparseMatrix
    !values = csrValuesVector sparseMatrix
    !startOffset = rowOffsets `U.unsafeIndex` rowIndex
    !endOffset = rowOffsets `U.unsafeIndex` (rowIndex + 1)

    findDiagonal !entryIndex
      | entryIndex >= endOffset = 0.0
      | otherwise =
          let !columnIndex =
                columnIndices `U.unsafeIndex` entryIndex
           in case compare columnIndex rowIndex of
                LT -> findDiagonal (entryIndex + 1)
                EQ -> values `U.unsafeIndex` entryIndex
                GT -> 0.0
{-# INLINE diagonalAt #-}

solverEpsilon :: Double
solverEpsilon = 1.0e-12

shiftedDiagonalValue :: Double -> Double -> Double
shiftedDiagonalValue shiftValue diagonalValue
  | diagonalValue >= 0.0 = diagonalValue + shiftValue
  | otherwise = diagonalValue - shiftValue