moonlight-linalg-0.1.0.0: src-spectral/Moonlight/LinAlg/Pure/Spectral/Solve.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE StrictData #-}
module Moonlight.LinAlg.Pure.Spectral.Solve
( EigenSolveConfig (..),
defaultEigenSolveConfig,
withEigenFallbackLanczosConfig,
withEigenFallbackInitialVector,
denseSpectralFallbackDimensionThreshold,
solveEigenRequest,
)
where
import Data.Bifunctor (first)
import Data.List (sortBy)
import Data.Ord (comparing)
import Data.Vector.Storable qualified as S
import Data.Vector.Unboxed qualified as U
import Moonlight.Core
( MoonlightError (..),
checkedNonNegativeProduct,
)
import Moonlight.LinAlg.Internal.Eigen.Symmetric
( SymmetricEigenResult (..),
symmetricEigenPairsDenseUnchecked,
)
import Moonlight.LinAlg.Pure.Dense.Flat
( DenseDoubleMatrix,
denseDoubleMatrixToRowMajorVector,
denseDoubleMatrixVectorProduct,
mkDenseDoubleMatrixRowMajor,
)
import Moonlight.LinAlg.Pure.Krylov.Config (LanczosConfig, defaultLanczosConfig, positiveCountValue)
import Moonlight.LinAlg.Pure.Krylov.CascadicGraph
( CascadicGraphObstruction (..),
cascadicGraphLaplacianEigenpairs,
)
import Moonlight.LinAlg.Pure.Krylov.Projected
( projectedEigenpairsFromRestartedLanczos,
projectedEigenvaluesFromRestartedLanczos,
)
import Moonlight.LinAlg.Pure.Krylov.SelectedTridiagonal
( symmetricTridiagonalFromCSR,
selectedSymmetricTridiagonalEigenpairsDirect,
selectedSymmetricTridiagonalEigenvaluesDirect,
)
import Moonlight.LinAlg.Pure.Krylov.Selection (SpectrumEnd (..))
import Moonlight.LinAlg.Pure.Sparse.Types (SparseCSR)
import Moonlight.LinAlg.Pure.Operator.Internal
( LinearOperator (..),
OperatorSource (..),
OperatorSymmetry (SelfAdjointOperator),
operatorDimension,
runOperatorU,
)
import Moonlight.LinAlg.Pure.Spectral.Request (EigenRequest (..))
import Moonlight.LinAlg.Pure.Spectral.Result
( Eigenpairs,
eigenpairValues,
eigenpairsFromColumns,
mapEigenpairValues,
)
import Prelude
data DiagonalOrder
= DiagonalAscending
| DiagonalDescending
data DiagonalOrderScan = DiagonalOrderScan
{ diagonalScanPrevious :: !Double,
diagonalScanAscending :: !Bool,
diagonalScanDescending :: !Bool
}
data EigenSolveConfig = EigenSolveConfig
{ eigenFallbackLanczosConfig :: !LanczosConfig,
eigenFallbackInitialVector :: !(Maybe (U.Vector Double))
}
deriving stock (Eq, Show)
defaultEigenSolveConfig :: EigenSolveConfig
defaultEigenSolveConfig =
EigenSolveConfig
{ eigenFallbackLanczosConfig = defaultLanczosConfig,
eigenFallbackInitialVector = Nothing
}
withEigenFallbackLanczosConfig :: LanczosConfig -> EigenSolveConfig -> EigenSolveConfig
withEigenFallbackLanczosConfig lanczosConfig config =
config {eigenFallbackLanczosConfig = lanczosConfig}
withEigenFallbackInitialVector :: U.Vector Double -> EigenSolveConfig -> EigenSolveConfig
withEigenFallbackInitialVector seedVector config =
config {eigenFallbackInitialVector = Just seedVector}
solveEigenRequest ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
EigenRequest result ->
Either MoonlightError result
solveEigenRequest config operatorValue requestValue = do
let dimension = operatorDimension operatorValue
requestedCount = eigenRequestCount requestValue
scaleValue = operatorSourceScale operatorValue
shiftValue = operatorIdentityShift operatorValue
validateSpectralCount requestedCount dimension
if scaleValue == 0.0
then solveZeroScale shiftValue dimension requestValue
else solveAffineRequest config operatorValue scaleValue shiftValue requestValue
solveAffineRequest ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
Double ->
Double ->
EigenRequest result ->
Either MoonlightError result
solveAffineRequest config operatorValue scaleValue shiftValue requestValue =
case requestValue of
EigenvaluesRequest spectrumEnd count ->
transformValues scaleValue shiftValue
<$> solveSourceEigenvalues config operatorValue (transportSpectrumEnd scaleValue spectrumEnd) (positiveCountValue count)
EigenpairsRequest spectrumEnd count ->
transformPairs scaleValue shiftValue
=<< solveSourceEigenpairs config operatorValue (transportSpectrumEnd scaleValue spectrumEnd) (positiveCountValue count)
solveSourceEigenvalues ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError (U.Vector Double)
solveSourceEigenvalues config operatorValue spectrumEnd requestedCount =
case operatorSource operatorValue of
DiagonalSource diagonalEntries -> diagonalValues spectrumEnd requestedCount diagonalEntries
PathLaplacianSource dimension -> pathLaplacianValues spectrumEnd requestedCount dimension
SymmetricTridiagonalSource tridiagonalValue ->
selectedSymmetricTridiagonalEigenvaluesDirect spectrumEnd requestedCount tridiagonalValue
SelfAdjointCSRSource csrValue ->
symmetricTridiagonalFromCSR csrValue >>= \case
Right tridiagonalValue -> selectedSymmetricTridiagonalEigenvaluesDirect spectrumEnd requestedCount tridiagonalValue
Left _ -> genericFallbackValues config (sourceOperator operatorValue) spectrumEnd requestedCount
GraphLaplacianCSRSource csrValue ->
graphLaplacianFallbackValues
config
(sourceOperator operatorValue)
csrValue
spectrumEnd
requestedCount
DeclaredSelfAdjointSource _ _ -> genericFallbackValues config (sourceOperator operatorValue) spectrumEnd requestedCount
solveSourceEigenpairs ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError Eigenpairs
solveSourceEigenpairs config operatorValue spectrumEnd requestedCount =
case operatorSource operatorValue of
DiagonalSource diagonalEntries -> diagonalPairs spectrumEnd requestedCount diagonalEntries
PathLaplacianSource dimension -> pathLaplacianPairs spectrumEnd requestedCount dimension
SymmetricTridiagonalSource tridiagonalValue ->
selectedSymmetricTridiagonalEigenpairsDirect spectrumEnd requestedCount tridiagonalValue
SelfAdjointCSRSource csrValue ->
symmetricTridiagonalFromCSR csrValue >>= \case
Right tridiagonalValue ->
selectedSymmetricTridiagonalEigenpairsDirect spectrumEnd requestedCount tridiagonalValue
Left _ -> genericFallbackPairs config (sourceOperator operatorValue) spectrumEnd requestedCount
GraphLaplacianCSRSource csrValue ->
graphLaplacianFallbackPairs
config
(sourceOperator operatorValue)
csrValue
spectrumEnd
requestedCount
DeclaredSelfAdjointSource _ _ -> genericFallbackPairs config (sourceOperator operatorValue) spectrumEnd requestedCount
graphLaplacianFallbackValues ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SparseCSR Double ->
SpectrumEnd ->
Int ->
Either MoonlightError (U.Vector Double)
graphLaplacianFallbackValues config operatorValue csrValue spectrumEnd requestedCount =
eigenpairValues
<$> graphLaplacianFallbackPairs
config
operatorValue
csrValue
spectrumEnd
requestedCount
graphLaplacianFallbackPairs ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SparseCSR Double ->
SpectrumEnd ->
Int ->
Either MoonlightError Eigenpairs
graphLaplacianFallbackPairs config operatorValue csrValue spectrumEnd requestedCount
| shouldUseCascadicGraphFallback spectrumEnd requestedCount operatorValue =
case
cascadicGraphLaplacianEigenpairs
(eigenFallbackLanczosConfig config)
requestedCount
csrValue
of
Right pairs -> Right pairs
Left (CascadicGraphCoarseningStalled _) ->
genericFallbackPairs config operatorValue spectrumEnd requestedCount
Left obstruction -> Left (cascadicGraphObstructionError obstruction)
| otherwise = genericFallbackPairs config operatorValue spectrumEnd requestedCount
shouldUseCascadicGraphFallback ::
SpectrumEnd ->
Int ->
LinearOperator 'SelfAdjointOperator ->
Bool
shouldUseCascadicGraphFallback spectrumEnd requestedCount operatorValue =
spectrumEnd == SmallestEigenvalues
&& operatorDimension operatorValue >= cascadicGraphFallbackDimensionThreshold
&& requestedCount <= cascadicGraphFallbackModeCountThreshold
cascadicGraphFallbackDimensionThreshold :: Int
cascadicGraphFallbackDimensionThreshold = 4096
cascadicGraphFallbackModeCountThreshold :: Int
cascadicGraphFallbackModeCountThreshold = 8
cascadicGraphObstructionError :: CascadicGraphObstruction -> MoonlightError
cascadicGraphObstructionError obstruction =
case obstruction of
CascadicGraphBackendFailure errorValue -> errorValue
CascadicGraphCoarseningStalled dimension ->
InvariantViolation
( "cascadic graph coarsening stalled at dimension "
<> show dimension
)
CascadicGraphIncompleteAssignment vertexIndex ->
InvariantViolation
( "cascadic graph coarsening omitted vertex "
<> show vertexIndex
)
CascadicGraphInvalidRequest requestedCount dimension ->
InvariantViolation
( "cascadic graph eigensolve requested "
<> show requestedCount
<> " modes from dimension "
<> show dimension
)
CascadicGraphRankLoss requiredCount actualCount ->
InvariantViolation
( "cascadic graph refinement lost block rank: required "
<> show requiredCount
<> " but retained "
<> show actualCount
)
CascadicGraphRefinementBudgetExceeded dimension residualTarget actualResidual ->
InvariantViolation
( "cascadic graph refinement exhausted its budget at dimension "
<> show dimension
<> ": residual target "
<> show residualTarget
<> ", actual "
<> show actualResidual
)
data GenericSpectralFallback
= DenseSpectralFallback
| RestartedLanczosSpectralFallback
-- | Densify generic self-adjoint fallback through n=512; measured banded SPD
-- benches favor dense below this cutoff.
denseSpectralFallbackDimensionThreshold :: Int
denseSpectralFallbackDimensionThreshold = 512
-- | High-demand requests remain cheaper as one bounded dense solve than as
-- hundreds of selected-mode restart cycles.
denseSpectralHighDemandDimensionThreshold :: Int
denseSpectralHighDemandDimensionThreshold = 1024
genericFallbackDispatch :: Int -> LinearOperator 'SelfAdjointOperator -> GenericSpectralFallback
genericFallbackDispatch requestedCount operatorValue
| dimension <= denseSpectralFallbackDimensionThreshold = DenseSpectralFallback
| dimension <= denseSpectralHighDemandDimensionThreshold
&& requestedCount >= denseRequestCardinalityFloor dimension = DenseSpectralFallback
| otherwise = RestartedLanczosSpectralFallback
where
dimension = operatorDimension operatorValue
denseRequestCardinalityFloor :: Int -> Int
denseRequestCardinalityFloor dimension =
max 1 ((dimension + 3) `quot` 4)
genericFallbackValues ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError (U.Vector Double)
genericFallbackValues config operatorValue spectrumEnd requestedCount =
case genericFallbackDispatch requestedCount operatorValue of
DenseSpectralFallback -> denseFallbackValues operatorValue spectrumEnd requestedCount
RestartedLanczosSpectralFallback -> lanczosFallbackValues config operatorValue spectrumEnd requestedCount
genericFallbackPairs ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError Eigenpairs
genericFallbackPairs config operatorValue spectrumEnd requestedCount =
case genericFallbackDispatch requestedCount operatorValue of
DenseSpectralFallback -> denseFallbackPairs operatorValue spectrumEnd requestedCount
RestartedLanczosSpectralFallback -> lanczosFallbackPairs config operatorValue spectrumEnd requestedCount
denseFallbackValues ::
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError (U.Vector Double)
denseFallbackValues operatorValue spectrumEnd requestedCount = do
(_, eigenResult) <- denseFallbackEigenResult operatorValue
let ascendingValues = symmetricEigenResultValues eigenResult
pure
( U.fromList
( (ascendingValues S.!)
<$> selectedSpectrumIndices spectrumEnd requestedCount (operatorDimension operatorValue)
)
)
denseFallbackPairs ::
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError Eigenpairs
denseFallbackPairs operatorValue spectrumEnd requestedCount = do
(denseMatrix, eigenResult) <- denseFallbackEigenResult operatorValue
let dimension = operatorDimension operatorValue
columns <-
traverse
(denseFallbackPairColumn denseMatrix eigenResult)
(selectedSpectrumIndices spectrumEnd requestedCount dimension)
eigenpairsFromColumns dimension columns
denseFallbackEigenResult ::
LinearOperator 'SelfAdjointOperator ->
Either MoonlightError (DenseDoubleMatrix, SymmetricEigenResult)
denseFallbackEigenResult operatorValue = do
let dimension = operatorDimension operatorValue
entryCount <-
first
(const (InvariantViolation "dense spectral fallback cardinality exceeds Int range"))
(checkedNonNegativeProduct dimension dimension)
imageColumns <- traverse (runOperatorU operatorValue . unitVector dimension) [0 .. dimension - 1]
let columnPayload = U.concat imageColumns
rowMajorPayload =
S.generate
entryCount
( \flatIndex ->
let (rowIndex, columnIndex) = flatIndex `quotRem` dimension
in columnPayload U.! (columnIndex * dimension + rowIndex)
)
denseMatrix <- mkDenseDoubleMatrixRowMajor dimension dimension rowMajorPayload
eigenResult <- symmetricEigenPairsDenseUnchecked dimension denseMatrix
pure (denseMatrix, eigenResult)
selectedSpectrumIndices :: SpectrumEnd -> Int -> Int -> [Int]
selectedSpectrumIndices spectrumEnd requestedCount dimension =
case spectrumEnd of
SmallestEigenvalues -> [0 .. requestedCount - 1]
LargestEigenvalues -> [dimension - 1, dimension - 2 .. dimension - requestedCount]
denseFallbackPairColumn ::
DenseDoubleMatrix ->
SymmetricEigenResult ->
Int ->
Either MoonlightError (Double, U.Vector Double, Double)
denseFallbackPairColumn denseMatrix eigenResult columnIndex = do
let eigenvalue = symmetricEigenResultValues eigenResult S.! columnIndex
vectorPayload = denseDoubleMatrixToRowMajorVector (symmetricEigenResultVectors eigenResult)
dimension = S.length (symmetricEigenResultValues eigenResult)
eigenvector = U.generate dimension (\rowIndex -> vectorPayload S.! (rowIndex * dimension + columnIndex))
imageVector <- denseDoubleMatrixVectorProduct denseMatrix (S.convert eigenvector)
pure (eigenvalue, eigenvector, residualNorm eigenvalue eigenvector (S.convert imageVector))
residualNorm :: Double -> U.Vector Double -> U.Vector Double -> Double
residualNorm eigenvalue eigenvector imageVector =
sqrt
( U.sum
( U.map
(\entryValue -> entryValue * entryValue)
(U.zipWith (\imageEntry vectorEntry -> imageEntry - eigenvalue * vectorEntry) imageVector eigenvector)
)
)
lanczosFallbackValues ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError (U.Vector Double)
lanczosFallbackValues config operatorValue spectrumEnd requestedCount =
projectedEigenvaluesFromRestartedLanczos
(eigenFallbackLanczosConfig config)
spectrumEnd
requestedCount
operatorValue
(fallbackSeed config (operatorDimension operatorValue))
lanczosFallbackPairs ::
EigenSolveConfig ->
LinearOperator 'SelfAdjointOperator ->
SpectrumEnd ->
Int ->
Either MoonlightError Eigenpairs
lanczosFallbackPairs config operatorValue spectrumEnd requestedCount =
projectedEigenpairsFromRestartedLanczos
(eigenFallbackLanczosConfig config)
spectrumEnd
requestedCount
operatorValue
(fallbackSeed config (operatorDimension operatorValue))
fallbackSeed :: EigenSolveConfig -> Int -> U.Vector Double
fallbackSeed config dimension =
case eigenFallbackInitialVector config of
Just seedVector -> seedVector
Nothing -> U.generate dimension (\indexValue -> if indexValue == 0 then 1.0 else 0.0)
sourceOperator :: LinearOperator 'SelfAdjointOperator -> LinearOperator 'SelfAdjointOperator
sourceOperator operatorValue =
operatorValue {operatorSourceScale = 1.0, operatorIdentityShift = 0.0}
diagonalValues :: SpectrumEnd -> Int -> U.Vector Double -> Either MoonlightError (U.Vector Double)
diagonalValues spectrumEnd requestedCount diagonalEntries =
Right (diagonalSelectedValues spectrumEnd requestedCount diagonalEntries)
diagonalPairs :: SpectrumEnd -> Int -> U.Vector Double -> Either MoonlightError Eigenpairs
diagonalPairs spectrumEnd requestedCount diagonalEntries =
eigenpairsFromColumns (U.length diagonalEntries)
( fmap
(\(entryIndex, eigenvalue) -> (eigenvalue, unitVector (U.length diagonalEntries) entryIndex, 0.0))
(diagonalSelectedEntries spectrumEnd requestedCount diagonalEntries)
)
diagonalSelectedValues :: SpectrumEnd -> Int -> U.Vector Double -> U.Vector Double
diagonalSelectedValues spectrumEnd requestedCount diagonalEntries =
case diagonalOrder diagonalEntries of
Just DiagonalAscending -> orderedAscendingValues spectrumEnd requestedCount diagonalEntries
Just DiagonalDescending -> orderedDescendingValues spectrumEnd requestedCount diagonalEntries
Nothing ->
U.fromList . fmap snd $
diagonalSelectedEntriesBySort spectrumEnd requestedCount diagonalEntries
diagonalSelectedEntries :: SpectrumEnd -> Int -> U.Vector Double -> [(Int, Double)]
diagonalSelectedEntries spectrumEnd requestedCount diagonalEntries =
case diagonalOrder diagonalEntries of
Just DiagonalAscending -> orderedAscendingEntries spectrumEnd requestedCount diagonalEntries
Just DiagonalDescending -> orderedDescendingEntries spectrumEnd requestedCount diagonalEntries
Nothing -> diagonalSelectedEntriesBySort spectrumEnd requestedCount diagonalEntries
diagonalOrder :: U.Vector Double -> Maybe DiagonalOrder
diagonalOrder diagonalEntries
| U.length diagonalEntries <= 1 = Just DiagonalAscending
| otherwise =
orderFromScan
( U.foldl'
scanDiagonalOrder
(DiagonalOrderScan (diagonalEntries `U.unsafeIndex` 0) True True)
(U.drop 1 diagonalEntries)
)
scanDiagonalOrder :: DiagonalOrderScan -> Double -> DiagonalOrderScan
scanDiagonalOrder scanValue entryValue =
DiagonalOrderScan
{ diagonalScanPrevious = entryValue,
diagonalScanAscending = diagonalScanAscending scanValue && diagonalScanPrevious scanValue <= entryValue,
diagonalScanDescending = diagonalScanDescending scanValue && diagonalScanPrevious scanValue >= entryValue
}
orderFromScan :: DiagonalOrderScan -> Maybe DiagonalOrder
orderFromScan scanValue
| diagonalScanAscending scanValue = Just DiagonalAscending
| diagonalScanDescending scanValue = Just DiagonalDescending
| otherwise = Nothing
orderedAscendingValues :: SpectrumEnd -> Int -> U.Vector Double -> U.Vector Double
orderedAscendingValues spectrumEnd requestedCount diagonalEntries =
case spectrumEnd of
SmallestEigenvalues -> U.take requestedCount diagonalEntries
LargestEigenvalues -> U.reverse (U.drop (U.length diagonalEntries - requestedCount) diagonalEntries)
orderedDescendingValues :: SpectrumEnd -> Int -> U.Vector Double -> U.Vector Double
orderedDescendingValues spectrumEnd requestedCount diagonalEntries =
case spectrumEnd of
SmallestEigenvalues -> U.reverse (U.drop (U.length diagonalEntries - requestedCount) diagonalEntries)
LargestEigenvalues -> U.take requestedCount diagonalEntries
orderedAscendingEntries :: SpectrumEnd -> Int -> U.Vector Double -> [(Int, Double)]
orderedAscendingEntries spectrumEnd requestedCount diagonalEntries =
diagonalEntriesAt
diagonalEntries
( case spectrumEnd of
SmallestEigenvalues -> [0 .. requestedCount - 1]
LargestEigenvalues -> [U.length diagonalEntries - 1, U.length diagonalEntries - 2 .. U.length diagonalEntries - requestedCount]
)
orderedDescendingEntries :: SpectrumEnd -> Int -> U.Vector Double -> [(Int, Double)]
orderedDescendingEntries spectrumEnd requestedCount diagonalEntries =
diagonalEntriesAt
diagonalEntries
( case spectrumEnd of
SmallestEigenvalues -> [U.length diagonalEntries - 1, U.length diagonalEntries - 2 .. U.length diagonalEntries - requestedCount]
LargestEigenvalues -> [0 .. requestedCount - 1]
)
diagonalEntriesAt :: U.Vector Double -> [Int] -> [(Int, Double)]
diagonalEntriesAt diagonalEntries =
fmap (\entryIndex -> (entryIndex, diagonalEntries `U.unsafeIndex` entryIndex))
diagonalSelectedEntriesBySort :: SpectrumEnd -> Int -> U.Vector Double -> [(Int, Double)]
diagonalSelectedEntriesBySort spectrumEnd requestedCount diagonalEntries =
take requestedCount (sortIndexedValues spectrumEnd (U.toList (U.indexed diagonalEntries)))
pathLaplacianValues :: SpectrumEnd -> Int -> Int -> Either MoonlightError (U.Vector Double)
pathLaplacianValues spectrumEnd requestedCount dimension =
Right
( U.generate
requestedCount
( \entryIndex ->
pathLaplacianEigenvalueAt dimension $
case spectrumEnd of
SmallestEigenvalues -> entryIndex
LargestEigenvalues -> dimension - entryIndex - 1
)
)
pathLaplacianPairs :: SpectrumEnd -> Int -> Int -> Either MoonlightError Eigenpairs
pathLaplacianPairs spectrumEnd requestedCount dimension =
eigenpairsFromColumns dimension $
pathLaplacianColumn dimension <$> selectedModeIndices spectrumEnd requestedCount dimension
pathLaplacianColumn :: Int -> Int -> (Double, U.Vector Double, Double)
pathLaplacianColumn dimension modeIndex =
let eigenvalue = pathLaplacianEigenvalueAt dimension modeIndex
theta = pi * fromIntegral modeIndex / fromIntegral (max 1 dimension)
eigenvector =
if modeIndex == 0
then U.replicate dimension (1.0 / sqrt (fromIntegral (max 1 dimension)))
else
let normalizer = sqrt (2.0 / fromIntegral dimension)
in U.generate dimension (\rowIndex -> normalizer * cos (theta * (fromIntegral rowIndex + 0.5)))
in (eigenvalue, eigenvector, pathLaplacianResidualNorm dimension eigenvalue eigenvector)
pathLaplacianResidualNorm :: Int -> Double -> U.Vector Double -> Double
pathLaplacianResidualNorm dimension eigenvalue eigenvector =
sqrt
( U.ifoldl'
( \squaredNorm rowIndex _ ->
let residualEntry = pathLaplacianResidualEntry dimension eigenvalue eigenvector rowIndex
in squaredNorm + residualEntry * residualEntry
)
0.0
eigenvector
)
pathLaplacianResidualEntry :: Int -> Double -> U.Vector Double -> Int -> Double
pathLaplacianResidualEntry dimension eigenvalue eigenvector rowIndex =
let centerValue = eigenvector `U.unsafeIndex` rowIndex
degree
| dimension == 1 = 0.0
| rowIndex == 0 || rowIndex + 1 == dimension = 1.0
| otherwise = 2.0
leftValue =
if rowIndex <= 0
then 0.0
else eigenvector `U.unsafeIndex` (rowIndex - 1)
rightValue =
if rowIndex + 1 >= dimension
then 0.0
else eigenvector `U.unsafeIndex` (rowIndex + 1)
imageValue = degree * centerValue - leftValue - rightValue
in imageValue - eigenvalue * centerValue
{-# INLINE pathLaplacianResidualEntry #-}
selectedModeIndices :: SpectrumEnd -> Int -> Int -> [Int]
selectedModeIndices spectrumEnd requestedCount dimension =
case spectrumEnd of
SmallestEigenvalues -> [0 .. requestedCount - 1]
LargestEigenvalues -> [dimension - 1, dimension - 2 .. dimension - requestedCount]
pathLaplacianEigenvalueAt :: Int -> Int -> Double
pathLaplacianEigenvalueAt matrixSize modeIndex =
2.0 - 2.0 * cos (pi * fromIntegral modeIndex / fromIntegral (max 1 matrixSize))
sortIndexedValues :: SpectrumEnd -> [(Int, Double)] -> [(Int, Double)]
sortIndexedValues spectrumEnd =
sortBy
( case spectrumEnd of
SmallestEigenvalues -> comparing snd
LargestEigenvalues -> flip (comparing snd)
)
transformValues :: Double -> Double -> U.Vector Double -> U.Vector Double
transformValues scaleValue shiftValue =
U.map (\eigenvalue -> scaleValue * eigenvalue + shiftValue)
transformPairs :: Double -> Double -> Eigenpairs -> Either MoonlightError Eigenpairs
transformPairs scaleValue shiftValue =
mapEigenpairValues
(\eigenvalue -> scaleValue * eigenvalue + shiftValue)
(abs scaleValue *)
transportSpectrumEnd :: Double -> SpectrumEnd -> SpectrumEnd
transportSpectrumEnd scaleValue spectrumEnd
| scaleValue < 0.0 =
case spectrumEnd of
SmallestEigenvalues -> LargestEigenvalues
LargestEigenvalues -> SmallestEigenvalues
| otherwise = spectrumEnd
solveZeroScale :: Double -> Int -> EigenRequest result -> Either MoonlightError result
solveZeroScale eigenvalue dimension requestValue =
case requestValue of
EigenvaluesRequest _ count -> Right (U.replicate (positiveCountValue count) eigenvalue)
EigenpairsRequest _ count ->
eigenpairsFromColumns
dimension
((\entryIndex -> (eigenvalue, unitVector dimension entryIndex, 0.0)) <$> [0 .. positiveCountValue count - 1])
eigenRequestCount :: EigenRequest result -> Int
eigenRequestCount requestValue =
case requestValue of
EigenvaluesRequest _ count -> positiveCountValue count
EigenpairsRequest _ count -> positiveCountValue count
validateSpectralCount :: Int -> Int -> Either MoonlightError ()
validateSpectralCount requestedCount dimension
| dimension <= 0 = Left (InvariantViolation "spectral solve requires a positive operator dimension")
| requestedCount <= 0 = Left (InvariantViolation "spectral request count must be positive")
| requestedCount > dimension = Left (InvariantViolation "spectral request count exceeds operator dimension")
| otherwise = Right ()
unitVector :: Int -> Int -> U.Vector Double
unitVector dimension selectedIndex =
U.generate dimension (\entryIndex -> if entryIndex == selectedIndex then 1.0 else 0.0)