packages feed

moonlight-linalg-0.1.0.0: bench/sparse/SparseStorage.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}

module SparseStorage
  ( sparseStorageBenchmarks,
    sparseStorageOnceBenchmarks,
  )
where

import Control.DeepSeq (NFData (..))
import Data.Bifunctor (first)
import Data.Vector.Unboxed qualified as Unboxed
import Env (BenchmarkSelection (..))
import Fixtures
  ( bandedDenseRows,
    bandedSpdCSR,
    denseBenchmarkVector,
    packedSparseBenchmarkOperator,
  )
import Types
  ( BenchmarkSetup (..),
    BenchmarkWeight (..),
    OnceBenchmark,
    PreparedBenchmarkRow (..),
    eitherBenchmarkWeight,
    renderPreparedBenchmark,
    renderPreparedOnceBenchmark,
  )
import Moonlight.LinAlg.Dense
  ( Matrix,
    fromListMatrix,
  )
import Moonlight.LinAlg.Sparse
  ( GraphEdge (..),
    PackedSparseOperator,
    SparseCOO,
    SparseCSC,
    SparseCSR,
    applyPackedSparseOperatorDense,
    cooEntries,
    csrColumnIndicesVector,
    csrMatVecVector,
    csrRowOffsetsVector,
    csrToCOO,
    csrToCSC,
    csrValuesVector,
    cscColumnOffsetsVector,
    cscRowIndicesVector,
    cscValuesVector,
    denseToCOO,
    denseToCSC,
    denseToCSR,
    graphLaplacianCSR,
  )
import Test.Tasty.Bench (Benchmark, bgroup)
import Prelude

data SparseStorageCase = SparseStorageCase
  { sparseStorageLabel :: !String,
    sparseStorageDense32 :: !(Matrix 32 32 Double),
    sparseStorageCSR :: !(SparseCSR Double),
    sparseStoragePackedOperator :: !(PackedSparseOperator Double),
    sparseStoragePackedVector :: !(Unboxed.Vector Double),
    sparseStorageGraphVertices :: ![Int],
    sparseStorageGraphEdges :: ![GraphEdge Int]
  }

instance NFData SparseStorageCase where
  rnf benchmarkCase =
    rnf (sparseStorageLabel benchmarkCase)
      `seq` sparseStorageDense32 benchmarkCase
      `seq` rnf (csrRowOffsetsVector (sparseStorageCSR benchmarkCase))
      `seq` rnf (csrColumnIndicesVector (sparseStorageCSR benchmarkCase))
      `seq` rnf (csrValuesVector (sparseStorageCSR benchmarkCase))
      `seq` sparseStoragePackedOperator benchmarkCase
      `seq` rnf (sparseStoragePackedVector benchmarkCase)
      `seq` rnf (sparseStorageGraphVertices benchmarkCase)
      `seq` rnf
        ( fmap
            ( \edgeValue ->
                ( graphEdgeLeft edgeValue,
                  graphEdgeRight edgeValue,
                  graphEdgeWeight edgeValue
                )
            )
            (sparseStorageGraphEdges benchmarkCase)
        )
      `seq` ()

sparseStorageBenchmarks :: BenchmarkSelection -> Benchmark
sparseStorageBenchmarks benchmarkSelection =
  bgroup
    "sparse storage and packed kernels"
    (sparseStorageBenchmark <$> sparseStorageDimensions benchmarkSelection)

sparseStorageOnceBenchmarks :: BenchmarkSelection -> [OnceBenchmark]
sparseStorageOnceBenchmarks benchmarkSelection =
  sparseStorageOnceBenchmark =<< sparseStorageDimensions benchmarkSelection

sparseStorageDimensions :: BenchmarkSelection -> [Int]
sparseStorageDimensions benchmarkSelection =
  [512]
    <> [2048 | includeBroadMedium benchmarkSelection || includeBroadLarge benchmarkSelection]
    <> [8192 | includeBroadLarge benchmarkSelection]

sparseStorageBenchmark :: Int -> Benchmark
sparseStorageBenchmark dimension =
  bgroup
    ("n=" <> show dimension)
    (renderPreparedBenchmark (prepareSparseStorageCase dimension) <$> sparseStorageRows)

sparseStorageOnceBenchmark :: Int -> [OnceBenchmark]
sparseStorageOnceBenchmark dimension =
  renderPreparedOnceBenchmark ("sparse storage and packed kernels.n=" <> show dimension <> ".") (prepareSparseStorageCase dimension)
    <$> sparseStorageRows

sparseStorageRows :: [PreparedBenchmarkRow SparseStorageCase]
sparseStorageRows =
  [ PurePreparedBenchmarkRow "dense 32x32 -> COO" denseToCooWeight,
    PurePreparedBenchmarkRow "dense 32x32 -> CSR" denseToCsrWeight,
    PurePreparedBenchmarkRow "dense 32x32 -> CSC" denseToCscWeight,
    PurePreparedBenchmarkRow "CSR -> COO" csrToCooWeight,
    PurePreparedBenchmarkRow "CSR -> CSC" csrToCscWeight,
    PurePreparedBenchmarkRow "CSR matvec" csrMatvecWeight,
    PurePreparedBenchmarkRow "packed sparse apply" packedSparseWeight,
    PurePreparedBenchmarkRow "graph Laplacian construction" graphLaplacianWeight
  ]

prepareSparseStorageCase :: Int -> BenchmarkSetup SparseStorageCase
prepareSparseStorageCase dimension =
  BenchmarkSetup $ do
    dense32 <- first show (fromListMatrix @32 @32 @Double (concat (bandedDenseRows 32)))
    csrValue <- first (("banded sparse fixture failed: " <>)) (bandedSpdCSR dimension)
    packedOperator <- first (("packed sparse fixture failed: " <>)) (packedSparseBenchmarkOperator dimension)
    let vectorValues = denseBenchmarkVector dimension
    pure
      SparseStorageCase
        { sparseStorageLabel = "n=" <> show dimension,
          sparseStorageDense32 = dense32,
          sparseStorageCSR = csrValue,
          sparseStoragePackedOperator = packedOperator,
          sparseStoragePackedVector = Unboxed.fromList vectorValues,
          sparseStorageGraphVertices = [0 .. dimension - 1],
          sparseStorageGraphEdges = duplicatePathEdges dimension
        }

denseToCooWeight :: SparseStorageCase -> BenchmarkWeight
denseToCooWeight benchmarkCase =
  BenchmarkWeight (cooChecksum (denseToCOO (sparseStorageDense32 benchmarkCase)))

denseToCsrWeight :: SparseStorageCase -> BenchmarkWeight
denseToCsrWeight benchmarkCase =
  BenchmarkWeight (csrChecksum (denseToCSR (sparseStorageDense32 benchmarkCase)))

denseToCscWeight :: SparseStorageCase -> BenchmarkWeight
denseToCscWeight benchmarkCase =
  BenchmarkWeight (cscChecksum (denseToCSC (sparseStorageDense32 benchmarkCase)))

csrToCooWeight :: SparseStorageCase -> BenchmarkWeight
csrToCooWeight benchmarkCase =
  eitherBenchmarkWeight
    (sparseStorageLabel benchmarkCase <> " CSR -> COO")
    cooChecksum
    (csrToCOO (sparseStorageCSR benchmarkCase))

csrToCscWeight :: SparseStorageCase -> BenchmarkWeight
csrToCscWeight benchmarkCase =
  eitherBenchmarkWeight
    (sparseStorageLabel benchmarkCase <> " CSR -> CSC")
    cscChecksum
    (csrToCSC (sparseStorageCSR benchmarkCase))

csrMatvecWeight :: SparseStorageCase -> BenchmarkWeight
csrMatvecWeight benchmarkCase =
  eitherBenchmarkWeight
    (sparseStorageLabel benchmarkCase <> " CSR matvec")
    unboxedVectorChecksum
    (csrMatVecVector (sparseStorageCSR benchmarkCase) (sparseStoragePackedVector benchmarkCase))

packedSparseWeight :: SparseStorageCase -> BenchmarkWeight
packedSparseWeight benchmarkCase =
  eitherBenchmarkWeight
    (sparseStorageLabel benchmarkCase <> " packed sparse apply")
    unboxedVectorChecksum
    (applyPackedSparseOperatorDense (sparseStoragePackedOperator benchmarkCase) (sparseStoragePackedVector benchmarkCase))

graphLaplacianWeight :: SparseStorageCase -> BenchmarkWeight
graphLaplacianWeight benchmarkCase =
  eitherBenchmarkWeight
    (sparseStorageLabel benchmarkCase <> " graph Laplacian construction")
    csrChecksum
    ( graphLaplacianCSR
        (sparseStorageGraphVertices benchmarkCase)
        (sparseStorageGraphEdges benchmarkCase)
    )

duplicatePathEdges :: Int -> [GraphEdge Int]
duplicatePathEdges dimension =
  concatMap
    ( \vertexIndex ->
        [ GraphEdge vertexIndex (vertexIndex + 1) 0.75,
          GraphEdge (vertexIndex + 1) vertexIndex 0.25
        ]
    )
    [0 .. dimension - 2]

cooChecksum :: SparseCOO Double -> Double
cooChecksum cooValue =
  fromIntegral (length (cooEntries cooValue))
    + sum ((\(rowIndex, columnIndex, entryValue) -> fromIntegral rowIndex + fromIntegral columnIndex + abs entryValue) <$> cooEntries cooValue)

csrChecksum :: SparseCSR Double -> Double
csrChecksum csrValue =
  fromIntegral (Unboxed.length (csrValuesVector csrValue))
    + fromIntegral (Unboxed.sum (csrRowOffsetsVector csrValue))
    + fromIntegral (Unboxed.sum (csrColumnIndicesVector csrValue))
    + unboxedVectorChecksum (csrValuesVector csrValue)

cscChecksum :: SparseCSC Double -> Double
cscChecksum cscValue =
  fromIntegral (Unboxed.length (cscValuesVector cscValue))
    + fromIntegral (Unboxed.sum (cscColumnOffsetsVector cscValue))
    + fromIntegral (Unboxed.sum (cscRowIndicesVector cscValue))
    + unboxedVectorChecksum (cscValuesVector cscValue)

unboxedVectorChecksum :: Unboxed.Vector Double -> Double
unboxedVectorChecksum values =
  Unboxed.foldl' (\acc value -> acc + abs value) 0.0 values