packages feed

moonlight-category-0.1.0.0: bench/finite/Invertibility.hs

module Invertibility
  ( invertibilityBenchmarks,
  )
where

import Control.DeepSeq (NFData (..))
import Data.Function ((&))
import Data.Map.Strict qualified as Map
import Data.Monoid (Sum (..))
import Data.Set qualified as Set
import Moonlight.Category.Pure.FinCat
  ( FinCat,
    FinGeneratorId (..),
    FinMorphismId (..),
    FinObjectId (..),
    FinMor,
    allObjects,
    finCatMorphismCount,
    objectCount,
    finMorId,
    finMorSourceId,
    finMorTargetId,
    finObjId,
    foldMapFinMorphisms,
    mkFinCat,
  )
import Moonlight.Category.Pure.Invertibility
  ( AutomorphismGroupoid,
    CoreGroupoid,
    InvertibilityIndex,
    automorphismGroupAt,
    automorphismGroupoid,
    automorphismGroupoidFromIndex,
    automorphismGroupoidObjects,
    coreGroupoid,
    coreGroupoidFromIndex,
    coreGroupoidMorphisms,
    coreGroupoidMorphismsBetween,
    coreGroupoidObjects,
    forgetAutomorphismGroupoidMorphism,
    forgetAutomorphismGroupoidObject,
    forgetCoreGroupoidMorphism,
    forgetCoreGroupoidObject,
    invertibilityIndex,
  )
import Test.Tasty.Bench (Benchmark, bench, bgroup, env, nf)

data PreparedInvertibilityCase = PreparedInvertibilityCase
  { preparedObjectCount :: !Int,
    preparedCategory :: !FinCat
  }

data PreparedIndexedInvertibilityCase = PreparedIndexedInvertibilityCase
  { preparedIndexedObjectCount :: !Int,
    preparedIndexedCategory :: !FinCat,
    preparedIndex :: InvertibilityIndex FinCat
  }

instance NFData PreparedInvertibilityCase where
  rnf prepared =
    preparedObjectCount prepared
      `seq` finCatDeepWeight (preparedCategory prepared)
      `seq` ()

instance NFData PreparedIndexedInvertibilityCase where
  rnf prepared =
    preparedIndexedObjectCount prepared
      `seq` invertibilityIndexWeight (preparedIndexedCategory prepared) (preparedIndex prepared)
      `seq` ()

invertibilityBenchmarks :: Benchmark
invertibilityBenchmarks =
  bgroup
    "invertibility / core groupoid"
    [ bgroup
        "complete pair-groupoid construction"
        (fmap completePairGroupoidBenchmark [4, 8, 12]),
      bgroup
        "index construction by size"
        (fmap preparedInvertibilityIndexBenchmark [4, 8, 12]),
      preparedIndexedViewBenchmarks 12
    ]

completePairGroupoidBenchmark :: Int -> Benchmark
completePairGroupoidBenchmark categoryObjectCount =
  bench ("n=" <> show categoryObjectCount) (nf completePairGroupoidWeight categoryObjectCount)

preparedInvertibilityIndexBenchmark :: Int -> Benchmark
preparedInvertibilityIndexBenchmark categoryObjectCount =
  env (prepareCompletePairGroupoid categoryObjectCount) $ \prepared ->
    bench ("n=" <> show categoryObjectCount) (nf preparedInvertibilityIndexWeight prepared)

preparedIndexedViewBenchmarks :: Int -> Benchmark
preparedIndexedViewBenchmarks categoryObjectCount =
  env (prepareIndexedInvertibilityCase categoryObjectCount) $ \prepared ->
    bgroup
      ("precomputed index views n=" <> show categoryObjectCount)
      [ bench "coreGroupoidFromIndex" (nf preparedCoreFromIndexWeight prepared),
        bench "automorphismGroupoidFromIndex" (nf preparedAutomorphismFromIndexWeight prepared),
        bench "coreGroupoidMorphismsBetween all object pairs" (nf preparedCoreBetweenWeight prepared),
        bench "automorphismGroupAt all objects" (nf preparedAutomorphismAtWeight prepared),
        bench "direct coreGroupoid rebuilds index" (nf preparedDirectCoreGroupoidWeight prepared),
        bench "direct automorphismGroupoid rebuilds index" (nf preparedDirectAutomorphismGroupoidWeight prepared)
      ]

prepareCompletePairGroupoid :: Int -> IO PreparedInvertibilityCase
prepareCompletePairGroupoid categoryObjectCount =
  case completePairGroupoid categoryObjectCount of
    Left failure -> ioError (userError failure)
    Right categoryValue ->
      finCatDeepWeight categoryValue `seq` pure (PreparedInvertibilityCase categoryObjectCount categoryValue)

prepareIndexedInvertibilityCase :: Int -> IO PreparedIndexedInvertibilityCase
prepareIndexedInvertibilityCase categoryObjectCount = do
  PreparedInvertibilityCase _ categoryValue <- prepareCompletePairGroupoid categoryObjectCount
  let indexValue = invertibilityIndex categoryValue
      forcedWeight = invertibilityIndexWeight categoryValue indexValue
  forcedWeight `seq` pure (PreparedIndexedInvertibilityCase categoryObjectCount categoryValue indexValue)

completePairGroupoidWeight :: Int -> Int
completePairGroupoidWeight categoryObjectCount =
  completePairGroupoid categoryObjectCount
    & either length finCatDeepWeight

preparedInvertibilityIndexWeight :: PreparedInvertibilityCase -> Int
preparedInvertibilityIndexWeight prepared =
  let categoryValue = preparedCategory prepared
   in invertibilityIndexWeight categoryValue (invertibilityIndex categoryValue)

preparedCoreFromIndexWeight :: PreparedIndexedInvertibilityCase -> Int
preparedCoreFromIndexWeight prepared =
  let categoryValue = preparedIndexedCategory prepared
      indexValue = preparedIndex prepared
   in coreGroupoidWeight (coreGroupoidFromIndex categoryValue indexValue)

preparedAutomorphismFromIndexWeight :: PreparedIndexedInvertibilityCase -> Int
preparedAutomorphismFromIndexWeight prepared =
  let categoryValue = preparedIndexedCategory prepared
      indexValue = preparedIndex prepared
   in automorphismGroupoidWeight (automorphismGroupoidFromIndex categoryValue indexValue)

preparedCoreBetweenWeight :: PreparedIndexedInvertibilityCase -> Int
preparedCoreBetweenWeight prepared =
  let categoryValue = preparedIndexedCategory prepared
      indexValue = preparedIndex prepared
      groupoidValue = coreGroupoidFromIndex categoryValue indexValue
      objects = coreGroupoidObjects groupoidValue
   in objects
        & fmap
          ( \sourceObject ->
              objects
                & fmap
                  ( \targetObject ->
                      coreGroupoidMorphismsBetween groupoidValue sourceObject targetObject
                        & fmap (finMorphismWeight . forgetCoreGroupoidMorphism)
                        & sum
                  )
                & sum
          )
        & sum

preparedAutomorphismAtWeight :: PreparedIndexedInvertibilityCase -> Int
preparedAutomorphismAtWeight prepared =
  let categoryValue = preparedIndexedCategory prepared
      indexValue = preparedIndex prepared
      groupoidValue = automorphismGroupoidFromIndex categoryValue indexValue
   in automorphismGroupoidObjects groupoidValue
        & fmap
          ( \objectValue ->
              automorphismGroupAt groupoidValue objectValue
                & fmap (finMorphismWeight . forgetAutomorphismGroupoidMorphism)
                & sum
          )
        & sum

preparedDirectCoreGroupoidWeight :: PreparedIndexedInvertibilityCase -> Int
preparedDirectCoreGroupoidWeight prepared =
  let categoryValue = preparedIndexedCategory prepared
   in coreGroupoidWeight (coreGroupoid categoryValue)

preparedDirectAutomorphismGroupoidWeight :: PreparedIndexedInvertibilityCase -> Int
preparedDirectAutomorphismGroupoidWeight prepared =
  let categoryValue = preparedIndexedCategory prepared
   in automorphismGroupoidWeight (automorphismGroupoid categoryValue)

invertibilityIndexWeight :: FinCat -> InvertibilityIndex FinCat -> Int
invertibilityIndexWeight categoryValue indexValue =
  coreGroupoidWeight (coreGroupoidFromIndex categoryValue indexValue)
    + automorphismGroupoidWeight (automorphismGroupoidFromIndex categoryValue indexValue)

coreGroupoidWeight :: CoreGroupoid FinCat -> Int
coreGroupoidWeight groupoidValue =
  sum (fmap (finObjectWeight . finObjId . forgetCoreGroupoidObject) (coreGroupoidObjects groupoidValue))
    + sum (fmap (finMorphismWeight . forgetCoreGroupoidMorphism) (coreGroupoidMorphisms groupoidValue))

automorphismGroupoidWeight :: AutomorphismGroupoid FinCat -> Int
automorphismGroupoidWeight groupoidValue =
  sum (fmap (finObjectWeight . finObjId . forgetAutomorphismGroupoidObject) (automorphismGroupoidObjects groupoidValue))
    + automorphismWeight
  where
    automorphismWeight =
      automorphismGroupoidObjects groupoidValue
        & fmap
          ( \objectValue ->
              automorphismGroupAt groupoidValue objectValue
                & fmap (finMorphismWeight . forgetAutomorphismGroupoidMorphism)
                & sum
          )
        & sum

finCatDeepWeight :: FinCat -> Int
finCatDeepWeight categoryValue =
  finCatShapeWeight categoryValue
    + getSum (foldMapFinMorphisms (Sum . finMorphismWeight) categoryValue)

finCatShapeWeight :: FinCat -> Int
finCatShapeWeight categoryValue =
  objectCount categoryValue
    + finCatMorphismCount categoryValue
    + sum (fmap (finObjectWeight . finObjId) (allObjects categoryValue))

completePairGroupoid :: Int -> Either String FinCat
completePairGroupoid categoryObjectCount =
  mkFinCat objects morphismMap compositionMap
    & either (Left . show) Right
  where
    objectIds = fmap FinObjectId [0 .. categoryObjectCount - 1]
    objects = Set.fromList objectIds
    nonIdentityEndpoints =
      [ (sourceId, targetId)
      | sourceId <- objectIds,
        targetId <- objectIds,
        sourceId /= targetId
      ]
    morphismMap =
      nonIdentityEndpoints
        & fmap (\(sourceId, targetId) -> ((sourceId, targetId), [generatorMorphismId categoryObjectCount sourceId targetId]))
        & Map.fromList
    compositionMap =
      [ ( (generatorMorphismId categoryObjectCount middleId targetId, generatorMorphismId categoryObjectCount sourceId middleId),
          resultMorphismId categoryObjectCount sourceId targetId
        )
      | sourceId <- objectIds,
        middleId <- objectIds,
        targetId <- objectIds,
        sourceId /= middleId,
        middleId /= targetId
      ]
        & Map.fromList

generatorMorphismId :: Int -> FinObjectId -> FinObjectId -> FinMorphismId
generatorMorphismId categoryObjectCount (FinObjectId sourceId) (FinObjectId targetId) =
  FinGeneratorMorphismId (FinGeneratorId (sourceId * categoryObjectCount + targetId))

resultMorphismId :: Int -> FinObjectId -> FinObjectId -> FinMorphismId
resultMorphismId categoryObjectCount sourceId targetId =
  if sourceId == targetId
    then FinIdentityId sourceId
    else generatorMorphismId categoryObjectCount sourceId targetId

finObjectWeight :: FinObjectId -> Int
finObjectWeight (FinObjectId objectId) = objectId

finMorphismWeight :: FinMor -> Int
finMorphismWeight morphismValue =
  finMorphismIdWeight (finMorId morphismValue)
    + finObjectWeight (finMorSourceId morphismValue)
    + finObjectWeight (finMorTargetId morphismValue)

finMorphismIdWeight :: FinMorphismId -> Int
finMorphismIdWeight morphismId =
  case morphismId of
    FinIdentityId objectId -> finObjectWeight objectId
    FinGeneratorMorphismId (FinGeneratorId generatorId) -> generatorId