packages feed

moonlight-pale-0.1.0.0: test/diagnostic/OutcomeSpec.hs

module OutcomeSpec
  ( tests,
  )
where

import Data.Foldable (traverse_)
import Data.List (sortOn)
import Data.Map.Strict qualified as Map
import Data.Ord (Down (..))
import Data.Sequence qualified as Seq
import Data.Set qualified as Set
import Moonlight.Pale.Diagnostic.Aggregation.Algebra
  ( OutcomeSummary,
    outcomeSummaryDiagnostics,
    outcomeSummaryFromProjectionOutcome,
    outcomeSummaryFromRestrictionOutcome,
    outcomeSummaryRestrictionOutcomes,
    restrictionIndexByCell,
    restrictionIndexByMismatch,
    restrictionIndexFromOutcomes,
    restrictionIndexStats,
    restrictionIndexTotal,
    topRestrictionHotspots,
  )
import Moonlight.Pale.Diagnostic.Local.Propagation
  ( ProjectionRunOutcome (..),
    RestrictionOutcomeStat (..),
    RestrictionRunOutcome (..),
  )
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertEqual, testCase)

type Cell = String

type ProjectionKey = String

type ProjectionValue = String

type ProjectionFailure = String

type Diagnostic = String

data Mismatch
  = ContextMismatch
  | PhaseMismatch
  | ShapeMismatch
  deriving stock (Eq, Ord, Show)

tests :: TestTree
tests =
  testGroup
    "pale.diagnostic.outcome"
    [ testCase "OutcomeSummary has mempty as a left identity" $
        assertEqual
          "left identity"
          outcomeSummaryA
          (mempty <> outcomeSummaryA),
      testCase "OutcomeSummary has mempty as a right identity" $
        assertEqual
          "right identity"
          outcomeSummaryA
          (outcomeSummaryA <> mempty),
      testCase "OutcomeSummary composition is associative" $
        assertEqual
          "associativity"
          ((outcomeSummaryA <> outcomeSummaryB) <> outcomeSummaryC)
          (outcomeSummaryA <> (outcomeSummaryB <> outcomeSummaryC)),
      testCase "OutcomeSummary preserves diagnostic and restriction order" $
        let summary = outcomeSummaryA <> outcomeSummaryC <> outcomeSummaryC
         in do
              assertEqual
                "diagnostics"
                (Seq.singleton "alpha adjusted")
                (outcomeSummaryDiagnostics summary)
              assertEqual
                "restrictions"
                (Seq.fromList [restrictionC, restrictionC])
                (outcomeSummaryRestrictionOutcomes summary),
      testCase "one restriction index derives every aggregate view" $
        let restrictionIndex = restrictionIndexFromOutcomes knownRestrictionOutcomes
         in do
              assertEqual "total" 6 (restrictionIndexTotal restrictionIndex)
              assertEqual
                "mismatch counts"
                [(ContextMismatch, 1), (PhaseMismatch, 3), (ShapeMismatch, 2)]
                (Map.toAscList (restrictionIndexByMismatch restrictionIndex))
              assertEqual
                "cell counts"
                [("alpha", 3), ("beta", 2), ("gamma", 1), ("omega", 6)]
                (Map.toAscList (restrictionIndexByCell restrictionIndex))
              assertEqual
                "ranked hotspot structure"
                [ ("alpha", "omega", PhaseMismatch),
                  ("beta", "omega", ShapeMismatch)
                ]
                (hotspotKey <$> topRestrictionHotspots 2 restrictionIndex),
      testCase "bounded hotspots equal the stable full-sort reference for every limit" $
        let restrictionIndex = restrictionIndexFromOutcomes differentialRestrictionOutcomes
            stableFullSort =
              sortOn
                (Down . rosOccurrences)
                (restrictionIndexStats restrictionIndex)
            limits = [-2 .. length stableFullSort + 2]
         in traverse_
              ( \limitValue ->
                  assertEqual
                    ("limit " <> show limitValue)
                    (take (max 0 limitValue) stableFullSort)
                    (topRestrictionHotspots limitValue restrictionIndex)
              )
              limits,
      testCase "hotspot benchmark matrix matches the stable full-sort reference" $
        traverse_
          assertHotspotMatrixAgreement
          hotspotBenchmarkScales
    ]

outcomeSummaryA :: OutcomeSummary Cell Mismatch ProjectionKey ProjectionValue ProjectionFailure Diagnostic
outcomeSummaryA =
  outcomeSummaryFromProjectionOutcome projectionAppliedA

outcomeSummaryB :: OutcomeSummary Cell Mismatch ProjectionKey ProjectionValue ProjectionFailure Diagnostic
outcomeSummaryB =
  outcomeSummaryFromProjectionOutcome projectionSkippedB

outcomeSummaryC :: OutcomeSummary Cell Mismatch ProjectionKey ProjectionValue ProjectionFailure Diagnostic
outcomeSummaryC =
  outcomeSummaryFromRestrictionOutcome restrictionC

projectionAppliedA :: ProjectionRunOutcome Cell ProjectionKey ProjectionValue ProjectionFailure Diagnostic
projectionAppliedA =
  ProjectionApplied
    "project-alpha"
    (Set.fromList ["alpha", "beta"])
    "projected"
    0.25
    (Seq.singleton "alpha adjusted")

projectionSkippedB :: ProjectionRunOutcome Cell ProjectionKey ProjectionValue ProjectionFailure Diagnostic
projectionSkippedB =
  ProjectionSkipped "project-beta" "already stable"

restrictionC :: RestrictionRunOutcome Cell Mismatch
restrictionC =
  RestrictionMismatch "beta" "omega" [ShapeMismatch]

knownRestrictionOutcomes :: [RestrictionRunOutcome Cell Mismatch]
knownRestrictionOutcomes =
  [ RestrictionMismatch "alpha" "omega" [PhaseMismatch, PhaseMismatch, PhaseMismatch],
    RestrictionMismatch "beta" "omega" [ShapeMismatch, ShapeMismatch],
    RestrictionMismatch "gamma" "omega" [ContextMismatch]
  ]

differentialRestrictionOutcomes :: [RestrictionRunOutcome Cell Mismatch]
differentialRestrictionOutcomes =
  [ RestrictionMismatch "alpha" "omega" [PhaseMismatch, PhaseMismatch],
    RestrictionMismatch "beta" "omega" [ShapeMismatch, ShapeMismatch, ShapeMismatch],
    RestrictionMismatch "gamma" "omega" [ContextMismatch, ContextMismatch, ContextMismatch],
    RestrictionMismatch "delta" "omega" [PhaseMismatch],
    RestrictionMismatch "epsilon" "omega" [ShapeMismatch, ShapeMismatch]
  ]

hotspotBenchmarkScales :: [(Int, [Int])]
hotspotBenchmarkScales =
  [ (2048, [1, 16, 45, 1023, 1024, 2048]),
    (16384, [1, 16, 128, 8191, 8192, 16384]),
    (65536, [1, 16, 256, 32767, 32768, 65536])
  ]

assertHotspotMatrixAgreement :: (Int, [Int]) -> IO ()
assertHotspotMatrixAgreement (uniqueAtomCount, hotspotCounts) =
  let restrictionIndex =
        restrictionIndexFromOutcomes
          (rankedRestrictionOutcomes uniqueAtomCount)
      stableFullSort =
        sortOn
          (Down . rosOccurrences)
          (restrictionIndexStats restrictionIndex)
   in traverse_
        ( \hotspotCount ->
            assertEqual
              ("K=" <> show uniqueAtomCount <> ", k=" <> show hotspotCount)
              (take hotspotCount stableFullSort)
              (topRestrictionHotspots hotspotCount restrictionIndex)
        )
        hotspotCounts

rankedRestrictionOutcomes :: Int -> [RestrictionRunOutcome Int Int]
rankedRestrictionOutcomes count =
  [ RestrictionMismatch
      index
      (index + 1)
      (replicate (1 + (index `mod` 7)) index)
    | index <- [1 .. count]
  ]

hotspotKey :: RestrictionOutcomeStat Cell Mismatch -> (Cell, Cell, Mismatch)
hotspotKey stat =
  (rosSourceCell stat, rosTargetCell stat, rosMismatch stat)