packages feed

moonlight-category-0.1.0.0: test/simplicial/Laws/Suite.hs

module Laws.Suite
  ( LawSuiteConfig (..),
    mkLawfulCarrierSpec,
    simplicialLawSuite,
    lawfulCarrierSuite,
  )
where

import Data.Kind (Type)
import Data.List.NonEmpty qualified as NonEmpty
import Moonlight.Pale.Test.Laws.Suite
  ( LawSuite,
    lawGroup,
    quickCheckLaw,
    renderLawSuite,
  )
import Moonlight.Category.Simplicial
  ( SimplicialLawCheck,
    SimplicialLawEquality,
    SimplicialLawIndices (..),
    SimplicialLawObstruction (..),
    checkDegeneracyDegeneracyLawBy,
    checkFaceDegeneracyLawBy,
    checkFaceFaceLawBy,
    checkSimplicialLawsBy,
    lawObstructionKind,
  )
import Moonlight.Category.Simplicial (TruncatedNormalizedSSet)
import Test.Tasty (TestTree)
import qualified Test.Tasty.QuickCheck as QC

type LawSuiteConfig :: Type -> Type -> Type
data LawSuiteConfig carrier simplex = LawSuiteConfig
  { lawSuiteName :: String,
    lawSuiteMaxSuccess :: Int,
    lawSuiteCarrierToSSet :: carrier -> TruncatedNormalizedSSet simplex,
    lawSuiteEquality :: SimplicialLawEquality simplex,
    lawSuiteRenderSimplex :: simplex -> String
  }

mkLawfulCarrierSpec ::
  (QC.Arbitrary carrier, Show carrier) =>
  String ->
  LawSuiteConfig carrier simplex ->
  LawSuite
mkLawfulCarrierSpec carrierName config =
  lawGroup carrierName [simplicialLawSuite config]

renderMaybeSimplex :: (simplex -> String) -> Maybe simplex -> String
renderMaybeSimplex renderSimplex maybeSimplex =
  case maybeSimplex of
    Nothing -> "Nothing"
    Just simplexValue -> "Just " <> renderSimplex simplexValue

renderIndices :: SimplicialLawIndices -> String
renderIndices indices =
  case indices of
    FaceFaceIndices leftFaceIndex rightFaceIndex ->
      "leftFace=" <> show leftFaceIndex <> ", rightFace=" <> show rightFaceIndex
    DegeneracyDegeneracyIndices leftDegeneracyIndex rightDegeneracyIndex ->
      "leftDegeneracy=" <> show leftDegeneracyIndex <> ", rightDegeneracy=" <> show rightDegeneracyIndex
    FaceDegeneracyIndices faceIndex degeneracyIndex ->
      "face=" <> show faceIndex <> ", degeneracy=" <> show degeneracyIndex

renderObstruction :: (simplex -> String) -> SimplicialLawObstruction simplex -> String
renderObstruction renderSimplex obstruction =
  unlines
    [ "law=" <> show (lawObstructionKind obstruction),
      "dimension=" <> show (lawObstructionDimension obstruction),
      "indices=" <> renderIndices (lawObstructionIndices obstruction),
      "source=" <> renderSimplex (lawObstructionSimplex obstruction),
      "left=" <> renderMaybeSimplex renderSimplex (lawObstructionLeftResult obstruction),
      "right=" <> renderMaybeSimplex renderSimplex (lawObstructionRightResult obstruction)
    ]

renderCheckFailure :: (simplex -> String) -> NonEmpty.NonEmpty (SimplicialLawObstruction simplex) -> String
renderCheckFailure renderSimplex obstructions =
  unlines
    [ "simplicial law obstruction count=" <> show (length (NonEmpty.toList obstructions)),
      "first obstruction:",
      renderObstruction renderSimplex (NonEmpty.head obstructions)
    ]

lawCheckProperty ::
  (simplex -> String) ->
  SimplicialLawCheck simplex ->
  QC.Property
lawCheckProperty renderSimplex lawCheck =
  case lawCheck of
    Right () -> QC.property True
    Left obstructions -> QC.counterexample (renderCheckFailure renderSimplex obstructions) False

simplicialLawSuite ::
  (QC.Arbitrary carrier, Show carrier) =>
  LawSuiteConfig carrier simplex ->
  LawSuite
simplicialLawSuite config =
  let runLaw lawCheck carrierValue =
        lawCheckProperty
          (lawSuiteRenderSimplex config)
          ( lawCheck
              (lawSuiteEquality config)
              (lawSuiteCarrierToSSet config carrierValue)
          )
      lawProperty propertyName lawCheck =
        quickCheckLaw
          propertyName
          (QC.withNumTests (lawSuiteMaxSuccess config) (runLaw lawCheck))
      bundledLaws :: [(String, SimplicialLawEquality simplex -> TruncatedNormalizedSSet simplex -> SimplicialLawCheck simplex)]
      bundledLaws =
        [ ("face-face: d_i d_j = d_{j-1} d_i (i < j)", checkFaceFaceLawBy),
          ("degeneracy-degeneracy: s_i s_j = s_{j+1} s_i (i <= j)", checkDegeneracyDegeneracyLawBy),
          ("mixed: d_i s_j cases", checkFaceDegeneracyLawBy),
          ("all simplicial identities", checkSimplicialLawsBy)
        ]
   in lawGroup
        (lawSuiteName config)
        (map (uncurry lawProperty) bundledLaws)

lawfulCarrierSuite :: [LawSuite] -> TestTree
lawfulCarrierSuite carrierSpecs =
  renderLawSuite (lawGroup "lawful carriers" carrierSpecs)