packages feed

moonlight-linalg-0.1.0.0: bench/support/Fixtures.hs

{-# LANGUAGE DataKinds #-}

module Fixtures
  ( benchmarkSeedBlock,
    bandedDenseRows,
    bandedSpdCSR,
    denseBenchmarkRows,
    denseBenchmarkVector,
    denseOperator,
    denseSpdRows,
    diagonalBenchmarkValues,
    genericBenchmarkTridiagonal,
    gf2BenchmarkValues,
    packedSparseBenchmarkOperator,
    pathLaplacianTridiagonal,
    projectedBenchmarkDimension,
    projectedBenchmarkRows,
    projectedBlockBenchmarkCases,
    reducibleBenchmarkTridiagonal,
    sparseKrylovBenchmarkCases,
    staticsBenchmarkNetwork,
  )
where

import Types
  ( ProjectedBlockBenchmarkCase (..),
    SparseKrylovBenchmarkCase (..),
    SpectrumProfile (..),
  )
import Env (BenchmarkSelection (..))
import Data.Bifunctor (first)
import Data.Foldable (traverse_)
import qualified Data.Vector as Box
import qualified Data.Vector.Unboxed as U
import Moonlight.LinAlg.Dense.GF2 (GF2 (..))
import Moonlight.LinAlg.Operator
  ( LinearOperator,
    OperatorSymmetry (..),
    declaredSelfAdjointVectorLinearOperator,
  )
import Moonlight.LinAlg.Pure.Structured.Tridiagonal
  ( SymmetricTridiagonal,
    mkSymmetricTridiagonal,
    pathLaplacianBands,
  )
import Moonlight.LinAlg.Sparse
  ( PackedSparseOperator,
    PackedSparseEntry,
    SparseCSR,
    canonicalCSRFromEntries,
    mkPackedSparseOperator,
    packedSparseEntry,
  )
import Moonlight.LinAlg.Statics
  ( ForceNetwork,
    NetworkDeclaration,
    Vec3 (..),
    load,
    member,
    network,
    support,
  )
import Prelude

sparseKrylovBenchmarkCases :: BenchmarkSelection -> [SparseKrylovBenchmarkCase]
sparseKrylovBenchmarkCases benchmarkSelection =
  [SparseKrylovBenchmarkCase "path-laplacian-10k" 10000 4]
    <> [SparseKrylovBenchmarkCase "path-laplacian-50k" 50000 4 | includeSparseLarge benchmarkSelection || includeSparse100k benchmarkSelection]
    <> [SparseKrylovBenchmarkCase "path-laplacian-100k" 100000 4 | includeSparse100k benchmarkSelection]

projectedBlockBenchmarkCases :: BenchmarkSelection -> [ProjectedBlockBenchmarkCase]
projectedBlockBenchmarkCases benchmarkSelection =
  [ ProjectedBlockBenchmarkCase "block-clustered-24" 4 6 6 4 ClusteredSpectrum,
    ProjectedBlockBenchmarkCase "block-separated-24" 4 6 6 4 SeparatedSpectrum
  ]
    <> if includeProjectedMedium benchmarkSelection || includeProjectedLarge benchmarkSelection
      then
        [ ProjectedBlockBenchmarkCase "block-clustered-144" 24 6 12 6 ClusteredSpectrum,
          ProjectedBlockBenchmarkCase "block-separated-144" 24 6 12 6 SeparatedSpectrum
        ]
      else []
    <> if includeProjectedLarge benchmarkSelection
      then
        [ ProjectedBlockBenchmarkCase "block-clustered-256" 32 8 16 8 ClusteredSpectrum,
          ProjectedBlockBenchmarkCase "block-separated-256" 32 8 16 8 SeparatedSpectrum
        ]
      else []

projectedBenchmarkDimension :: ProjectedBlockBenchmarkCase -> Int
projectedBenchmarkDimension benchmarkCase =
  projectedBenchmarkBlockCount benchmarkCase * projectedBenchmarkBlockSize benchmarkCase

benchmarkSeedBlock :: Int -> Int -> Box.Vector (U.Vector Double)
benchmarkSeedBlock dimension blockSize =
  Box.fromList
    [ U.fromList [seedEntry rowIndex columnIndex | rowIndex <- [0 .. dimension - 1]]
      | columnIndex <- [0 .. blockSize - 1]
    ]

seedEntry :: Int -> Int -> Double
seedEntry rowIndex columnIndex =
  let rowOffset = fromIntegral (rowIndex + 1)
      columnOffset = fromIntegral (columnIndex + 1)
      diagonalContribution = if rowIndex == columnIndex then 1.0 else 0.0
      smoothContribution = 1.0 / (rowOffset + columnOffset)
   in diagonalContribution + smoothContribution

denseOperator :: [[Double]] -> Either String (LinearOperator 'SelfAdjointOperator)
denseOperator rows =
  let rowVectors = Box.fromList (U.fromList <$> rows)
      rowCount = Box.length rowVectors
   in validateDenseOperatorRows rowCount rowVectors
        *> first show (declaredSelfAdjointVectorLinearOperator rowCount (denseOperatorApply rowVectors))

validateDenseOperatorRows :: Int -> Box.Vector (U.Vector Double) -> Either String ()
validateDenseOperatorRows dimension rowVectors =
  traverse_ validateRow rowVectors
  where
    validateRow rowVector
      | U.length rowVector /= dimension =
          Left "benchmark dense operator rows must form a square matrix"
      | U.any (not . isFiniteDouble) rowVector =
          Left "benchmark dense operator rows must contain finite entries"
      | otherwise = Right ()

denseOperatorApply :: Box.Vector (U.Vector Double) -> U.Vector Double -> Either errorValue (U.Vector Double)
denseOperatorApply rowVectors inputVector =
  Right
    ( U.generate
        (Box.length rowVectors)
        (\rowIndex -> dotDenseRow inputVector (rowVectors `Box.unsafeIndex` rowIndex))
    )

dotDenseRow :: U.Vector Double -> U.Vector Double -> Double
dotDenseRow inputVector rowVector =
  U.ifoldl'
    (\accumulator columnIndex rowEntry -> accumulator + rowEntry * (inputVector `U.unsafeIndex` columnIndex))
    0.0
    rowVector

isFiniteDouble :: Double -> Bool
isFiniteDouble value =
  not (isNaN value || isInfinite value)

projectedBenchmarkRows :: ProjectedBlockBenchmarkCase -> [[Double]]
projectedBenchmarkRows benchmarkCase =
  let dimension = projectedBenchmarkDimension benchmarkCase
      blockSize = projectedBenchmarkBlockSize benchmarkCase
      spectrumProfile = projectedBenchmarkSpectrumProfile benchmarkCase
   in [ [ projectedBenchmarkEntry spectrumProfile blockSize rowIndex columnIndex
          | columnIndex <- [0 .. dimension - 1]
        ]
        | rowIndex <- [0 .. dimension - 1]
      ]

projectedBenchmarkEntry :: SpectrumProfile -> Int -> Int -> Int -> Double
projectedBenchmarkEntry spectrumProfile blockSize rowIndex columnIndex =
  let (rowBlock, rowWithinBlock) = rowIndex `divMod` blockSize
      (columnBlock, columnWithinBlock) = columnIndex `divMod` blockSize
   in case compare rowBlock columnBlock of
        EQ -> diagonalBlockEntry spectrumProfile rowBlock rowWithinBlock columnWithinBlock
        LT ->
          if rowBlock + 1 == columnBlock
            then offDiagonalBlockEntry spectrumProfile rowBlock rowWithinBlock columnWithinBlock
            else 0.0
        GT ->
          if columnBlock + 1 == rowBlock
            then offDiagonalBlockEntry spectrumProfile columnBlock columnWithinBlock rowWithinBlock
            else 0.0

diagonalBlockEntry :: SpectrumProfile -> Int -> Int -> Int -> Double
diagonalBlockEntry spectrumProfile blockIndex rowWithinBlock columnWithinBlock =
  let separationBase =
        case spectrumProfile of
          ClusteredSpectrum -> 12.0 + 0.005 * fromIntegral blockIndex
          SeparatedSpectrum -> 2.0 + 1.5 * fromIntegral blockIndex
      localOffset = 0.02 * fromIntegral (rowWithinBlock + columnWithinBlock)
      entryWeight =
        if rowWithinBlock == columnWithinBlock
          then separationBase + 1.0 + localOffset
          else 0.04 / fromIntegral (1 + abs (rowWithinBlock - columnWithinBlock))
   in entryWeight

offDiagonalBlockEntry :: SpectrumProfile -> Int -> Int -> Int -> Double
offDiagonalBlockEntry spectrumProfile blockIndex rowWithinBlock columnWithinBlock =
  let couplingBase =
        case spectrumProfile of
          ClusteredSpectrum -> 0.06 + 0.002 * fromIntegral (blockIndex `mod` 3)
          SeparatedSpectrum -> 0.03 + 0.001 * fromIntegral (blockIndex `mod` 3)
   in couplingBase / fromIntegral (1 + abs (rowWithinBlock - columnWithinBlock))

pathLaplacianTridiagonal :: Int -> Either String SymmetricTridiagonal
pathLaplacianTridiagonal dimension =
  first show (pathLaplacianBands dimension >>= uncurry mkSymmetricTridiagonal)

genericBenchmarkTridiagonal :: Int -> Either String SymmetricTridiagonal
genericBenchmarkTridiagonal dimension =
  first show
    ( mkSymmetricTridiagonal
        (genericTridiagonalDiagonalEntry <$> [0 .. dimension - 1])
        (genericTridiagonalOffDiagonalEntry <$> [0 .. dimension - 2])
    )

genericTridiagonalDiagonalEntry :: Int -> Double
genericTridiagonalDiagonalEntry indexValue =
  2.0 + fromIntegral (indexValue `mod` 17) / 17.0

genericTridiagonalOffDiagonalEntry :: Int -> Double
genericTridiagonalOffDiagonalEntry indexValue =
  -0.35 - 0.01 * fromIntegral (indexValue `mod` 5)

reducibleBenchmarkTridiagonal :: Int -> Either String SymmetricTridiagonal
reducibleBenchmarkTridiagonal dimension =
  first show
    ( mkSymmetricTridiagonal
        (genericTridiagonalDiagonalEntry <$> [0 .. dimension - 1])
        (reducibleTridiagonalOffDiagonalEntry <$> [0 .. dimension - 2])
    )

reducibleTridiagonalOffDiagonalEntry :: Int -> Double
reducibleTridiagonalOffDiagonalEntry indexValue =
  if (indexValue + 1) `mod` 32 == 0
    then 0.0
    else genericTridiagonalOffDiagonalEntry indexValue

denseBenchmarkRows :: Int -> [[Double]]
denseBenchmarkRows dimension =
  [ [denseBenchmarkEntry rowIndex columnIndex | columnIndex <- [0 .. dimension - 1]]
    | rowIndex <- [0 .. dimension - 1]
  ]

denseBenchmarkEntry :: Int -> Int -> Double
denseBenchmarkEntry rowIndex columnIndex =
  let rowWeight = fromIntegral (rowIndex + 1)
      columnWeight = fromIntegral (columnIndex + 1)
      diagonalContribution =
        if rowIndex == columnIndex
          then 2.0 + 0.01 * rowWeight
          else 0.0
      smoothContribution = 1.0 / (rowWeight + 2.0 * columnWeight + 3.0)
   in diagonalContribution + smoothContribution

denseSpdRows :: Int -> [[Double]]
denseSpdRows dimension =
  [ [denseSpdEntry dimension rowIndex columnIndex | columnIndex <- [0 .. dimension - 1]]
    | rowIndex <- [0 .. dimension - 1]
  ]

denseSpdEntry :: Int -> Int -> Int -> Double
denseSpdEntry dimension rowIndex columnIndex =
  if rowIndex == columnIndex
    then fromIntegral dimension + 2.0 + 0.05 * fromIntegral rowIndex
    else 1.0 / fromIntegral (2 + abs (rowIndex - columnIndex))

denseBenchmarkVector :: Int -> [Double]
denseBenchmarkVector dimension =
  fmap (\indexValue -> 1.0 + fromIntegral (indexValue `mod` 7) / 7.0) [0 .. dimension - 1]

bandedDenseRows :: Int -> [[Double]]
bandedDenseRows dimension =
  [ [bandedDenseEntry dimension rowIndex columnIndex | columnIndex <- [0 .. dimension - 1]]
    | rowIndex <- [0 .. dimension - 1]
  ]

bandedDenseEntry :: Int -> Int -> Int -> Double
bandedDenseEntry dimension rowIndex columnIndex
  | rowIndex == columnIndex = 4.0 + 0.001 * fromIntegral dimension
  | abs (rowIndex - columnIndex) == 1 = -1.0
  | abs (rowIndex - columnIndex) == 2 = 0.25
  | otherwise = 0.0

bandedSpdCSR :: Int -> Either String (SparseCSR Double)
bandedSpdCSR dimension =
  case
    canonicalCSRFromEntries
      dimension
      dimension
      (bandedSpdEntries dimension)
    of
    Left err -> Left (show err)
    Right csrValue -> Right csrValue

bandedSpdEntries :: Int -> [(Int, Int, Double)]
bandedSpdEntries dimension =
  concatMap
    ( \rowIndex ->
        (\(columnIndex, value) -> (rowIndex, columnIndex, value))
          <$> bandedSpdRowEntries dimension rowIndex
    )
    [0 .. dimension - 1]

bandedSpdRowEntries :: Int -> Int -> [(Int, Double)]
bandedSpdRowEntries dimension rowIndex =
  filter
    (\(columnIndex, _) -> columnIndex >= 0 && columnIndex < dimension)
    [ (rowIndex - 2, 0.25),
      (rowIndex - 1, -1.0),
      (rowIndex, 4.0),
      (rowIndex + 1, -1.0),
      (rowIndex + 2, 0.25)
    ]

diagonalBenchmarkValues :: Int -> [Double]
diagonalBenchmarkValues dimension =
  let positiveDimension = max 1 dimension
   in fmap (\indexValue -> 2.0 + fromIntegral (indexValue `mod` positiveDimension) / fromIntegral positiveDimension) [0 .. dimension - 1]

packedSparseBenchmarkOperator :: Int -> Either String (PackedSparseOperator Double)
packedSparseBenchmarkOperator dimension =
  case mkPackedSparseOperator (fromIntegral dimension) (fromIntegral dimension) (packedSparseEntries dimension) of
    Left err -> Left (show err)
    Right operatorValue -> Right operatorValue

packedSparseEntries :: Int -> [PackedSparseEntry Double]
packedSparseEntries dimension =
  [ packedSparseEntry sourceOffset targetOffset (packedSparseCoefficient sourceOffset targetOffset)
    | targetOffset <- [0 .. dimension - 1],
      sourceOffset <- [targetOffset - 1, targetOffset, targetOffset + 1],
      sourceOffset >= 0,
      sourceOffset < dimension
  ]

packedSparseCoefficient :: Int -> Int -> Double
packedSparseCoefficient sourceOffset targetOffset =
  if sourceOffset == targetOffset
    then 2.0
    else (-0.5)

gf2BenchmarkValues :: Int -> Int -> [GF2]
gf2BenchmarkValues rowCount columnCount =
  [ if gf2BenchmarkBit rowIndex columnIndex then GF2One else GF2Zero
    | rowIndex <- [0 .. rowCount - 1],
      columnIndex <- [0 .. columnCount - 1]
  ]

gf2BenchmarkBit :: Int -> Int -> Bool
gf2BenchmarkBit rowIndex columnIndex =
  rowIndex == columnIndex
    || ((rowIndex * 17 + columnIndex * 31 + rowIndex * columnIndex) `mod` 23 == 0)

staticsBenchmarkNetwork :: Int -> Either String ForceNetwork
staticsBenchmarkNetwork spanCount =
  case network (concatMap spanDeclarations [0 .. spanCount - 1]) of
    Left err -> Left (show err)
    Right networkValue -> Right networkValue

spanDeclarations :: Int -> [NetworkDeclaration]
spanDeclarations spanIndex =
  let supportLabel = "support-" <> show spanIndex
      loadLabel = "load-" <> show spanIndex
      coordinate = fromIntegral spanIndex
   in [ support supportLabel (Vec3 coordinate 0.0 0.0),
        load loadLabel (Vec3 coordinate 1.0 0.0) (Vec3 0.0 (-10.0 - coordinate) 0.0),
        member supportLabel loadLabel
      ]