packages feed

moonlight-algebra-0.1.0.0: bench/abstract/AbstractBench.hs

module AbstractBench
  ( abstractBenchmarks,
  )
where

import Control.DeepSeq (NFData (..))
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Vector.Unboxed qualified as UVector
import Data.Word (Word64)
import Moonlight.Algebra.Pure.Lattice qualified as Lattice
import Moonlight.Algebra.Pure.LaneVector
  ( LaneVector,
    laneCount,
    laneVectorFromLanes,
    laneVectorLanes,
  )
import Moonlight.Algebra.Pure.Polynomial (Polynomial)
import Moonlight.Algebra.Pure.Polynomial qualified as Polynomial
import Moonlight.Algebra.Pure.PowerSet qualified as PowerSet
import Moonlight.Algebra.Pure.SparseVec (SparseVec)
import Moonlight.Algebra.Pure.SparseVec qualified as SparseVec
import Moonlight.Core
  ( AdditiveGroup (..),
    AdditiveMonoid (..),
    MultiplicativeMonoid (..),
  )
import Test.Tasty.Bench
  ( Benchmark,
    bench,
    bgroup,
    env,
    nf,
  )

type SparseBenchVector = SparseVec Int Int

type PolynomialBench = Polynomial Int

abstractBenchmarks :: Benchmark
abstractBenchmarks =
  bgroup
    "abstract"
    [ laneVectorBenchmarks,
      sparseVecWorldBenchmarks,
      polynomialWorldBenchmarks,
      powerSetWorldBenchmarks
    ]

newtype LaneVectorBenchOperands = LaneVectorBenchOperands [(LaneVector, LaneVector)]

instance NFData LaneVectorBenchOperands where
  rnf (LaneVectorBenchOperands operands) =
    foldr forceLaneVectorPair () operands

laneVectorBenchmarks :: Benchmark
laneVectorBenchmarks =
  bgroup
    "lane-vector/group-arithmetic"
    (fmap laneVectorBenchmark laneVectorEditCounts)

laneVectorBenchmark :: Int -> Benchmark
laneVectorBenchmark editCount =
  env (pure (laneVectorOperands editCount)) $ \operands ->
    bgroup
      ("edits=" <> show editCount <> " lanes-per-edit=" <> show laneCount)
      [ bench "add" (nf laneVectorAddWeight operands),
        bench "sub" (nf laneVectorSubWeight operands),
        bench "neg" (nf laneVectorNegWeight operands)
      ]

laneVectorOperands :: Int -> LaneVectorBenchOperands
laneVectorOperands editCount =
  LaneVectorBenchOperands (fmap laneVectorOperandPair (keys editCount))

laneVectorOperandPair :: Int -> (LaneVector, LaneVector)
laneVectorOperandPair editIndex =
  ( laneVectorOperand 0x243f6a8885a308d3 editIndex,
    laneVectorOperand 0x13198a2e03707344 editIndex
  )

laneVectorOperand :: Word64 -> Int -> LaneVector
laneVectorOperand seed editIndex =
  laneVectorFromLanes
    (UVector.generate laneCount (laneValue seed editIndex))

laneValue :: Word64 -> Int -> Int -> Word64
laneValue seed editIndex laneIndex =
  seed
    + 0x9e3779b97f4a7c15 * fromIntegral editIndex
    + 0xbf58476d1ce4e5b9 * fromIntegral laneIndex

laneVectorAddWeight :: LaneVectorBenchOperands -> Word64
laneVectorAddWeight (LaneVectorBenchOperands operands) =
  foldr (combineLaneVectorPair add) 0 operands

laneVectorSubWeight :: LaneVectorBenchOperands -> Word64
laneVectorSubWeight (LaneVectorBenchOperands operands) =
  foldr (combineLaneVectorPair sub) 0 operands

laneVectorNegWeight :: LaneVectorBenchOperands -> Word64
laneVectorNegWeight (LaneVectorBenchOperands operands) =
  foldr (\(left, _right) accumulated -> laneVectorWeight (neg left) + accumulated) 0 operands

combineLaneVectorPair :: (LaneVector -> LaneVector -> LaneVector) -> (LaneVector, LaneVector) -> Word64 -> Word64
combineLaneVectorPair operation (left, right) accumulated =
  laneVectorWeight (operation left right) + accumulated

laneVectorWeight :: LaneVector -> Word64
laneVectorWeight =
  UVector.foldl' (+) 0 . laneVectorLanes

forceLaneVectorPair :: (LaneVector, LaneVector) -> () -> ()
forceLaneVectorPair (left, right) forcedRest =
  rnf (laneVectorLanes left) `seq` rnf (laneVectorLanes right) `seq` forcedRest

laneVectorEditCounts :: [Int]
laneVectorEditCounts =
  [128, 512, 2048]

sparseVecWorldBenchmarks :: Benchmark
sparseVecWorldBenchmarks =
  bgroup
    "sparse-vec/us-vs-world"
    (fmap sparseVecWorldBenchmark sparseVecSizes)

sparseVecWorldBenchmark :: Int -> Benchmark
sparseVecWorldBenchmark size =
  env (pure (sparseEntryPair size)) $ \entryPair ->
    bgroup
      (caseLabel "normalize+add" size)
      [ bench "moonlight: SparseVec.fromEntries/add" (nf moonlightSparseVecAddWeight entryPair),
        bench "world: containers Map.fromListWith/unionWith" (nf containersMapAddWeight entryPair)
      ]

moonlightSparseVecAddWeight :: ([(Int, Int)], [(Int, Int)]) -> Int
moonlightSparseVecAddWeight (leftEntries, rightEntries) =
  sparseEntriesWeight
    ( SparseVec.toEntries
        ( add
            (SparseVec.fromEntries leftEntries :: SparseBenchVector)
            (SparseVec.fromEntries rightEntries :: SparseBenchVector)
        )
    )

containersMapAddWeight :: ([(Int, Int)], [(Int, Int)]) -> Int
containersMapAddWeight (leftEntries, rightEntries) =
  mapEntriesWeight
    ( normalizeMap
        (Map.unionWith (+) (containersMapFromEntries leftEntries) (containersMapFromEntries rightEntries))
    )

containersMapFromEntries :: [(Int, Int)] -> Map Int Int
containersMapFromEntries =
  normalizeMap . Map.fromListWith (+)

normalizeMap :: Map Int Int -> Map Int Int
normalizeMap =
  Map.mapMaybe nonZero

nonZero :: Int -> Maybe Int
nonZero value
  | value == 0 = Nothing
  | otherwise = Just value

polynomialWorldBenchmarks :: Benchmark
polynomialWorldBenchmarks =
  bgroup
    "polynomial/us-vs-world"
    (fmap polynomialWorldBenchmark polynomialSizes)

polynomialWorldBenchmark :: Int -> Benchmark
polynomialWorldBenchmark size =
  env (pure (polynomialCoefficientPair size)) $ \coefficientPair ->
    bgroup
      (caseLabel "multiply+evaluate" size)
      [ bench "moonlight: Polynomial.mul/evaluatePolynomial" (nf moonlightPolynomialWeight coefficientPair),
        bench "world: containers Map convolution/Horner" (nf containersPolynomialWeight coefficientPair)
      ]

moonlightPolynomialWeight :: ([Int], [Int]) -> Int
moonlightPolynomialWeight (leftCoefficients, rightCoefficients) =
  let productPolynomial =
        mul
          (Polynomial.fromCoefficients leftCoefficients :: PolynomialBench)
          (Polynomial.fromCoefficients rightCoefficients :: PolynomialBench)
   in Polynomial.evaluatePolynomial 3 productPolynomial
        + coefficientsWeight (Polynomial.toCoefficients productPolynomial)

containersPolynomialWeight :: ([Int], [Int]) -> Int
containersPolynomialWeight (leftCoefficients, rightCoefficients) =
  let productTerms =
        containersPolynomialMultiply
          (polynomialMapFromCoefficients leftCoefficients)
          (polynomialMapFromCoefficients rightCoefficients)
   in denseHornerEvaluate 3 (coefficientsFromPolynomialMap productTerms)
        + mapEntriesWeight productTerms

containersPolynomialMultiply :: Map Int Int -> Map Int Int -> Map Int Int
containersPolynomialMultiply leftTerms rightTerms =
  normalizeMap
    ( Map.fromListWith
        (+)
        [ (leftDegree + rightDegree, leftCoefficient * rightCoefficient)
        | (leftDegree, leftCoefficient) <- Map.toAscList leftTerms,
          (rightDegree, rightCoefficient) <- Map.toAscList rightTerms
        ]
    )

polynomialMapFromCoefficients :: [Int] -> Map Int Int
polynomialMapFromCoefficients =
  normalizeMap . Map.fromAscList . zip [0 ..]

coefficientsFromPolynomialMap :: Map Int Int -> [Int]
coefficientsFromPolynomialMap =
  denseCoefficientsFromTerms 0 . Map.toAscList

denseCoefficientsFromTerms :: Int -> [(Int, Int)] -> [Int]
denseCoefficientsFromTerms expectedDegree remainingTerms =
  case remainingTerms of
    [] -> []
    (degreeValue, coefficientValue) : restTerms
      | expectedDegree < degreeValue ->
          replicate (degreeValue - expectedDegree) zero
            <> denseCoefficientsFromTerms degreeValue remainingTerms
      | otherwise ->
          coefficientValue : denseCoefficientsFromTerms (expectedDegree + 1) restTerms

denseHornerEvaluate :: Int -> [Int] -> Int
denseHornerEvaluate value =
  foldr (\coefficient accumulator -> coefficient + value * accumulator) 0

powerSetWorldBenchmarks :: Benchmark
powerSetWorldBenchmarks =
  bgroup
    "power-set-lattice/us-vs-world"
    (fmap powerSetWorldBenchmark powerSetSizes)

powerSetWorldBenchmark :: Int -> Benchmark
powerSetWorldBenchmark size =
  env (pure (powerSetListPair size)) $ \setPair ->
    bgroup
      (caseLabel "join+meet" size)
      [ bench "moonlight: PowerSet join/meet" (nf moonlightPowerSetJoinMeetWeight setPair),
        bench "world: containers Set union/intersection" (nf containersSetJoinMeetWeight setPair)
      ]

moonlightPowerSetJoinMeetWeight :: ([Int], [Int]) -> Int
moonlightPowerSetJoinMeetWeight (leftValues, rightValues) =
  let leftSet = PowerSet.fromList leftValues
      rightSet = PowerSet.fromList rightValues
      joinedSet = Lattice.join leftSet rightSet
      metSet = Lattice.meet leftSet rightSet
   in listWeight (PowerSet.toPowerSetList joinedSet)
        + listWeight (PowerSet.toPowerSetList metSet)

containersSetJoinMeetWeight :: ([Int], [Int]) -> Int
containersSetJoinMeetWeight (leftValues, rightValues) =
  let leftSet = Set.fromList leftValues
      rightSet = Set.fromList rightValues
   in setWeight (Set.union leftSet rightSet)
        + setWeight (Set.intersection leftSet rightSet)

sparseEntryPair :: Int -> ([(Int, Int)], [(Int, Int)])
sparseEntryPair size =
  ( sparseEntries size,
    fmap (\(basisKey, coefficient) -> (basisKey + size `quot` 4, negate coefficient)) (sparseEntries size)
  )

sparseEntries :: Int -> [(Int, Int)]
sparseEntries size =
  [ (key `mod` max 1 (size `quot` 2), coefficientForKey key)
  | key <- keys (size * 2)
  ]

polynomialCoefficientPair :: Int -> ([Int], [Int])
polynomialCoefficientPair size =
  (fmap coefficientForKey (keys size), fmap (coefficientForKey . (+ size)) (keys size))

powerSetListPair :: Int -> ([Int], [Int])
powerSetListPair size =
  ( [key | key <- keys size, key `mod` 2 == 0],
    [key | key <- keys size, key `mod` 3 /= 0]
  )

coefficientForKey :: Int -> Int
coefficientForKey key =
  case key `mod` 7 of
    0 -> 0
    residue -> residue - 3

sparseEntriesWeight :: [(Int, Int)] -> Int
sparseEntriesWeight =
  foldr (\(basisKey, coefficient) accumulated -> accumulated + basisKey * 31 + coefficient) 0

mapEntriesWeight :: Map Int Int -> Int
mapEntriesWeight =
  Map.foldlWithKey' (\accumulated key value -> accumulated + key * 31 + value) 0

coefficientsWeight :: [Int] -> Int
coefficientsWeight =
  foldr (\coefficient accumulated -> accumulated * 33 + coefficient) 5381

listWeight :: [Int] -> Int
listWeight =
  foldr (\value accumulated -> accumulated * 16777619 + value) 146959810

setWeight :: Set.Set Int -> Int
setWeight =
  listWeight . Set.toAscList

keys :: Int -> [Int]
keys size =
  [0 .. size - 1]

sparseVecSizes :: [Int]
sparseVecSizes =
  [128, 512, 2048]

polynomialSizes :: [Int]
polynomialSizes =
  [16, 64, 128]

powerSetSizes :: [Int]
powerSetSizes =
  [128, 512, 2048]

caseLabel :: String -> Int -> String
caseLabel label size =
  label <> " n=" <> show size