packages feed

moonlight-algebra-0.1.0.0: test/abstract/OrderedLatticeSpec.hs

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

module OrderedLatticeSpec
  ( tests,
  )
where

import Control.Monad
  ( when,
  )
import Data.Foldable
  ( traverse_,
  )
import Data.List
  ( subsequences,
  )
import Data.Set qualified as Set
import Moonlight.Algebra
  ( JoinSemilattice (..),
    MeetSemilattice (..),
    OrderedLattice,
  )
import Moonlight.Algebra
  ( PowerSet,
    fromList,
  )
import Moonlight.Algebra
  ( mkProductAlgebra,
    toProductList,
  )
import Moonlight.Core
  ( FiniteUniverse (..),
    boundedEnumUniverse,
  )
import Moonlight.Core
  ( PartialOrder (..),
    comparable,
    incomparable,
  )
import Test.Tasty
  ( TestTree,
    testGroup,
  )
import Test.Tasty.HUnit
  ( Assertion,
    assertBool,
    assertEqual,
    assertFailure,
    testCase,
    (@?=),
  )

data Atom
  = AtomA
  | AtomB
  | AtomC
  deriving stock (Eq, Ord, Show, Enum, Bounded)

instance FiniteUniverse Atom where
  finiteUniverse =
    boundedEnumUniverse

tests :: TestTree
tests =
  testGroup
    "ordered lattice"
    [ testCase "Bool join and meet are least upper and greatest lower bounds" $
        assertOrderedLatticeLaws "Bool" [False, True],
      testCase "Set Int join and meet are least upper and greatest lower bounds" $
        assertOrderedLatticeLaws "Set Int" setSamples,
      testCase "PowerSet Atom obeys ordered lattice laws" $
        assertOrderedLatticeLaws "PowerSet Atom" powerSetSamples,
      testCase "ProductAlgebra 2 Bool matches DBSP product-time join and meet shape" testProductLattice
    ]

assertOrderedLatticeLaws ::
  (OrderedLattice lattice, Show lattice) =>
  String ->
  [lattice] ->
  Assertion
assertOrderedLatticeLaws label values = do
  traverse_ (uncurry (assertJoinMeetOrderCoherence label)) (pairs values)
  traverse_ (uncurry (assertLeastUpperBound label values)) (pairs values)
  traverse_ (uncurry (assertGreatestLowerBound label values)) (pairs values)
  traverse_ (assertIdempotence label) values
  traverse_ (uncurry (assertCommutativity label)) (pairs values)
  traverse_ (assertAssociativity label) (triples values)
  traverse_ (uncurry (assertAbsorption label)) (pairs values)

assertJoinMeetOrderCoherence ::
  (OrderedLattice lattice, Show lattice) =>
  String ->
  lattice ->
  lattice ->
  Assertion
assertJoinMeetOrderCoherence label left right = do
  leq left right @?= (join left right == right)
  leq left right @?= (meet left right == left)
  assertBool
    (label <> " join is not above left at " <> show (left, right))
    (leq left (join left right))
  assertBool
    (label <> " join is not above right at " <> show (left, right))
    (leq right (join left right))
  assertBool
    (label <> " meet is not below left at " <> show (left, right))
    (leq (meet left right) left)
  assertBool
    (label <> " meet is not below right at " <> show (left, right))
    (leq (meet left right) right)

assertLeastUpperBound ::
  (OrderedLattice lattice, Show lattice) =>
  String ->
  [lattice] ->
  lattice ->
  lattice ->
  Assertion
assertLeastUpperBound label values left right =
  traverse_ checkCandidate values
  where
    leastUpperBound =
      join left right

    checkCandidate candidate =
      when (leq left candidate && leq right candidate) $
        assertBool
          ( label
              <> " join is not least at "
              <> show (left, right, candidate)
          )
          (leq leastUpperBound candidate)

assertGreatestLowerBound ::
  (OrderedLattice lattice, Show lattice) =>
  String ->
  [lattice] ->
  lattice ->
  lattice ->
  Assertion
assertGreatestLowerBound label values left right =
  traverse_ checkCandidate values
  where
    greatestLowerBound =
      meet left right

    checkCandidate candidate =
      when (leq candidate left && leq candidate right) $
        assertBool
          ( label
              <> " meet is not greatest at "
              <> show (left, right, candidate)
          )
          (leq candidate greatestLowerBound)

assertIdempotence ::
  (Eq lattice, Show lattice, JoinSemilattice lattice, MeetSemilattice lattice) =>
  String ->
  lattice ->
  Assertion
assertIdempotence label value = do
  assertEqual (label <> " join idempotence at " <> show value) value (join value value)
  assertEqual (label <> " meet idempotence at " <> show value) value (meet value value)

assertCommutativity ::
  (Eq lattice, Show lattice, JoinSemilattice lattice, MeetSemilattice lattice) =>
  String ->
  lattice ->
  lattice ->
  Assertion
assertCommutativity label left right = do
  assertEqual
    (label <> " join commutativity at " <> show (left, right))
    (join left right)
    (join right left)
  assertEqual
    (label <> " meet commutativity at " <> show (left, right))
    (meet left right)
    (meet right left)

assertAssociativity ::
  (Eq lattice, Show lattice, JoinSemilattice lattice, MeetSemilattice lattice) =>
  String ->
  (lattice, lattice, lattice) ->
  Assertion
assertAssociativity label (left, middle, right) = do
  assertEqual
    (label <> " join associativity at " <> show (left, middle, right))
    (join (join left middle) right)
    (join left (join middle right))
  assertEqual
    (label <> " meet associativity at " <> show (left, middle, right))
    (meet (meet left middle) right)
    (meet left (meet middle right))

assertAbsorption ::
  (Eq lattice, Show lattice, JoinSemilattice lattice, MeetSemilattice lattice) =>
  String ->
  lattice ->
  lattice ->
  Assertion
assertAbsorption label left right = do
  assertEqual
    (label <> " join absorption at " <> show (left, right))
    left
    (join left (meet left right))
  assertEqual
    (label <> " meet absorption at " <> show (left, right))
    left
    (meet left (join left right))

testProductLattice :: Assertion
testProductLattice =
  case
    ( traverse (mkProductAlgebra @2) [[False, True], [True, False], [True, True], [False, False]],
      traverse (mkProductAlgebra @2) productBoolCoordinateLists
    ) of
    (Just [left, right, expectedJoin, expectedMeet], Just productBoolSamples) -> do
      toProductList (join left right) @?= toProductList expectedJoin
      toProductList (meet left right) @?= toProductList expectedMeet
      leq left expectedJoin @?= True
      leq right expectedJoin @?= True
      comparable left expectedJoin @?= True
      incomparable left right @?= True
      assertOrderedLatticeLaws "ProductAlgebra 2 Bool" productBoolSamples
    _ ->
      assertFailure "test fixture declared an invalid ProductAlgebra arity"

setSamples :: [Set.Set Int]
setSamples =
  Set.fromList <$> subsequences [1, 2, 3]

powerSetSamples :: [PowerSet Atom]
powerSetSamples =
  fromList <$> subsequences [AtomA, AtomB, AtomC]

productBoolCoordinateLists :: [[Bool]]
productBoolCoordinateLists =
  traverse (const [False, True]) [(), ()]

pairs :: [a] -> [(a, a)]
pairs values =
  (,) <$> values <*> values

triples :: [a] -> [(a, a, a)]
triples values =
  (,,) <$> values <*> values <*> values