moonlight-algebra-0.1.0.0: test/finite-lattice/FiniteLatticeSpec.hs
{-# LANGUAGE GHC2024 #-}
module FiniteLatticeSpec
( tests,
)
where
import Data.Bits ((.&.), (.|.))
import Data.Foldable (for_, traverse_)
import Data.Set qualified as Set
import Moonlight.FiniteLattice
( ContextHeyting,
ContextHeytingCompileError (..),
ContextCompileLimits (..),
ContextLattice (clBottom, clTop),
ContextLatticeCompileError (..),
ContextLatticeLookupError (..),
ContextMonotoneMapError (..),
ContextOrderDecl,
ContextRepresentation (..),
compileContextHeyting,
compileContextLattice,
compileContextLatticeWith,
contextLatticeElements,
contextLatticeFromClosedOrder,
contextLatticeFromClosedOrderWith,
contextOrderDecl,
coverPairs,
greatestContextFixpoint,
impliesContext,
joinContext,
leastContextFixpoint,
leqContext,
lowerCovers,
meetContext,
defaultContextCompileLimits,
residentContextElementForKey,
residentContextElementKey,
residentContextElementValue,
residentHeytingBaseContext,
residentImplies,
residentJoin,
residentJoinMeetKeys,
residentMeet,
residentSupportContainsElement,
residentSupportFromElements,
residentSupportReachableElements,
residentSupportWithClosure,
singletonContextLattice,
strictOrderPairs,
upperCovers,
checkResidentContext,
withResidentContext,
withResidentHeytingContext,
)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit
( Assertion,
assertBool,
assertEqual,
assertFailure,
testCase,
(@?=),
)
data DiamondContext
= DiamondBottom
| DiamondLeft
| DiamondRight
| DiamondTop
deriving stock (Eq, Ord, Show, Read)
data ChainContext
= ChainBottom
| ChainX
| ChainY
| ChainZ
| ChainTop
deriving stock (Eq, Ord, Show, Read)
data NonLatticeContext
= NBottom
| NA
| NB
| NL
| NM
| NU
| NV
| NTop
deriving stock (Eq, Ord, Show, Read)
data M3
= M3Bottom
| M3A
| M3B
| M3C
| M3Top
deriving stock (Eq, Ord, Show, Read)
data N5
= N5Bottom
| N5A
| N5B
| N5C
| N5Top
deriving stock (Eq, Ord, Show, Read)
tests :: TestTree
tests =
testGroup
"finite context lattice declarations"
[ testCase "diamond order compiles to the expected lattice" testDiamondLattice,
testCase "resident context handles branded total lattice operations" testResidentContextOperations,
testCase "resident support materializes upward closure without value lookups" testResidentSupportClosure,
testCase "boolean cube cover graph uses Boolean lattice semantics" testBooleanCubeLattice,
testCase "unknown context fails checked join" testUnknownJoinContext,
testCase "unknown context fails checked meet" testUnknownMeetContext,
testCase "unknown context fails checked order" testUnknownLeqContext,
testCase "non-monotone endomap is rejected before fixpoint iteration" testRejectsNonMonotoneFixpoint,
testCase "least context fixpoint ascends a finite chain" testLeastContextFixpointAscendsChain,
testCase "greatest context fixpoint descends a finite chain" testGreatestContextFixpointDescendsChain,
testCase "context fixpoint reports endomap closure obstruction" testContextFixpointClosureObstruction,
testCase "checked context implication derives the finite residuum when Heyting" testImpliesContextUnique,
testCase "M3 is a lattice but not Heyting" testRejectsM3Residual,
testCase "Boolean implication satisfies the adjunction" testBooleanAdjunction,
testCase "dense distributive Heyting implication satisfies the adjunction" testDenseHeytingAdjunction,
testCase "tableless distributive lattice agrees with dense operations" testTablelessDistributiveMatchesDense,
testCase "tableless N5 lattice matches dense operations and keeps Heyting witness" testTablelessN5MatchesDense,
testCase "tableless 64x2 product implication handles one-word masks" testTablelessProduct64ImplicationBoundary,
testCase "tableless 66x2 product implication handles multi-word masks" testTablelessProduct66ImplicationBoundary,
testCase "downset-generated distributive lattices satisfy Heyting adjunction" testDownsetGeneratedHeytingAdjunction,
testCase "dense N5 is rejected as non-Heyting by first missing residual" testRejectsDenseN5Residual,
testCase "strict pairs and covers are lower-to-upper" testCoverOrientation,
testCase "unknown top fails before closure" testUnknownTop,
testCase "unknown bottom fails before closure" testUnknownBottom,
testCase "unknown relation endpoint fails before closure" testUnknownRelationEndpoint,
testCase "antisymmetry violation fails after closure" testAntisymmetryViolation,
testCase "declared top must be greatest" testTopNotGreatest,
testCase "declared bottom must be least" testBottomNotLeast,
testCase "missing unique join is rejected" testMissingJoin,
testCase "closed-order constructor reproduces the compiled diamond lattice" testClosedOrderReproducesDiamond,
testCase "closed-order constructor rejects an invalid join function" testClosedOrderRejectsInvalidJoin,
testCase "closed-order constructor rejects joins outside the universe" testClosedOrderRejectsOutsideJoin,
testCase "closed-order constructor rejects meets outside the universe" testClosedOrderRejectsOutsideMeet,
testCase "closed-order constructor rejects an intransitive order" testClosedOrderRejectsIntransitiveOrder,
testCase "closed-order constructor rejects relation layouts over the byte limit" testClosedOrderRejectsRelationLimit
]
diamondLeq :: DiamondContext -> DiamondContext -> Bool
diamondLeq leftContext rightContext =
leftContext == rightContext
|| leftContext == DiamondBottom
|| rightContext == DiamondTop
diamondJoin :: DiamondContext -> DiamondContext -> DiamondContext
diamondJoin leftContext rightContext
| diamondLeq leftContext rightContext = rightContext
| diamondLeq rightContext leftContext = leftContext
| otherwise = DiamondTop
diamondMeet :: DiamondContext -> DiamondContext -> DiamondContext
diamondMeet leftContext rightContext
| diamondLeq leftContext rightContext = leftContext
| diamondLeq rightContext leftContext = rightContext
| otherwise = DiamondBottom
testClosedOrderReproducesDiamond :: Assertion
testClosedOrderReproducesDiamond =
case ( compileContextLattice diamondUniverse diamondDecl,
contextLatticeFromClosedOrder
DiamondTop
DiamondBottom
(Set.toAscList diamondUniverse)
diamondLeq
diamondJoin
diamondMeet
) of
(Right compiled, Right closed) -> do
clTop closed @?= clTop compiled
clBottom closed @?= clBottom compiled
contextLatticeElements closed @?= contextLatticeElements compiled
traverse_
( \(leftContext, rightContext) -> do
leqContext closed leftContext rightContext
@?= leqContext compiled leftContext rightContext
joinContext closed leftContext rightContext
@?= joinContext compiled leftContext rightContext
meetContext closed leftContext rightContext
@?= meetContext compiled leftContext rightContext
)
[ (leftContext, rightContext)
| leftContext <- contextLatticeElements compiled,
rightContext <- contextLatticeElements compiled
]
(Left err, _) ->
assertFailure ("expected compiled diamond lattice, got " <> show err)
(_, Left err) ->
assertFailure ("expected closed-order diamond lattice, got " <> show err)
testClosedOrderRejectsInvalidJoin :: Assertion
testClosedOrderRejectsInvalidJoin =
contextLatticeFromClosedOrder
DiamondTop
DiamondBottom
(Set.toAscList diamondUniverse)
diamondLeq
( \leftContext rightContext ->
if leftContext == DiamondLeft && rightContext == DiamondRight
then DiamondBottom
else diamondJoin leftContext rightContext
)
diamondMeet
`shouldFailWith` ContextLatticeInvalidJoin DiamondLeft DiamondRight DiamondBottom
testClosedOrderRejectsRelationLimit :: Assertion
testClosedOrderRejectsRelationLimit =
contextLatticeFromClosedOrderWith
defaultContextCompileLimits {cclMaximumRelationBytes = Just 0}
DiamondTop
DiamondBottom
(Set.toAscList diamondUniverse)
diamondLeq
diamondJoin
diamondMeet
`shouldFailWith` ContextLatticeRepresentationLimitExceeded ContextRelationWords 64 0
closedChainUniverse :: [ChainContext]
closedChainUniverse =
[ChainBottom, ChainX, ChainY, ChainTop]
closedChainLeq :: ChainContext -> ChainContext -> Bool
closedChainLeq leftContext rightContext =
leftContext == rightContext
|| leftContext == ChainBottom
|| rightContext == ChainTop
|| (leftContext == ChainX && rightContext == ChainY)
closedChainJoin :: ChainContext -> ChainContext -> ChainContext
closedChainJoin leftContext rightContext
| closedChainLeq leftContext rightContext = rightContext
| closedChainLeq rightContext leftContext = leftContext
| otherwise = ChainTop
closedChainMeet :: ChainContext -> ChainContext -> ChainContext
closedChainMeet leftContext rightContext
| closedChainLeq leftContext rightContext = leftContext
| closedChainLeq rightContext leftContext = rightContext
| otherwise = ChainBottom
testClosedOrderRejectsOutsideJoin :: Assertion
testClosedOrderRejectsOutsideJoin =
contextLatticeFromClosedOrder
ChainTop
ChainBottom
closedChainUniverse
closedChainLeq
( \leftContext rightContext ->
if leftContext == ChainX && rightContext == ChainY
then ChainZ
else closedChainJoin leftContext rightContext
)
closedChainMeet
`shouldFailWith` ContextLatticeJoinOutsideUniverse ChainX ChainY ChainZ
testClosedOrderRejectsOutsideMeet :: Assertion
testClosedOrderRejectsOutsideMeet =
contextLatticeFromClosedOrder
ChainTop
ChainBottom
closedChainUniverse
closedChainLeq
closedChainJoin
( \leftContext rightContext ->
if leftContext == ChainX && rightContext == ChainY
then ChainZ
else closedChainMeet leftContext rightContext
)
`shouldFailWith` ContextLatticeMeetOutsideUniverse ChainX ChainY ChainZ
testClosedOrderRejectsIntransitiveOrder :: Assertion
testClosedOrderRejectsIntransitiveOrder =
contextLatticeFromClosedOrder
ChainTop
ChainBottom
[ChainBottom, ChainX, ChainY, ChainZ, ChainTop]
intransitiveLeq
(\_ _ -> ChainTop)
(\_ _ -> ChainBottom)
`shouldFailWith` ContextLatticeNotTransitive ChainX ChainY ChainZ
where
intransitiveLeq leftContext rightContext =
leftContext == rightContext
|| leftContext == ChainBottom
|| rightContext == ChainTop
|| (leftContext == ChainX && rightContext == ChainY)
|| (leftContext == ChainY && rightContext == ChainZ)
testDiamondLattice :: Assertion
testDiamondLattice =
case compileContextLattice diamondUniverse diamondDecl of
Left err ->
assertFailure ("expected diamond lattice, got " <> show err)
Right lattice -> do
clTop lattice @?= DiamondTop
clBottom lattice @?= DiamondBottom
contextLatticeElements lattice @?= [DiamondBottom, DiamondLeft, DiamondRight, DiamondTop]
joinContext lattice DiamondLeft DiamondRight @?= Right DiamondTop
meetContext lattice DiamondLeft DiamondRight @?= Right DiamondBottom
leqContext lattice DiamondBottom DiamondTop @?= Right True
leqContext lattice DiamondLeft DiamondTop @?= Right True
leqContext lattice DiamondRight DiamondTop @?= Right True
assertBool "left and right are incomparable" $
leqContext lattice DiamondLeft DiamondRight == Right False
&& leqContext lattice DiamondRight DiamondLeft == Right False
testResidentContextOperations :: Assertion
testResidentContextOperations =
case compileContextLattice diamondUniverse diamondDecl of
Left err ->
assertFailure ("expected diamond lattice, got " <> show err)
Right lattice ->
case compileContextHeyting lattice of
Left err ->
assertFailure ("expected diamond Heyting proof, got " <> show err)
Right heyting ->
withResidentHeytingContext heyting $ \heytingContext -> do
let residentContext = residentHeytingBaseContext heytingContext
leftElement <- requireRight (checkResidentContext residentContext DiamondLeft)
rightElement <- requireRight (checkResidentContext residentContext DiamondRight)
bottomElement <- requireRight (checkResidentContext residentContext DiamondBottom)
let joinResult = residentJoin residentContext leftElement rightElement
meetResult = residentMeet residentContext leftElement rightElement
(joinKey, meetKey) =
residentJoinMeetKeys
residentContext
(residentContextElementKey leftElement)
(residentContextElementKey rightElement)
implicationResult = residentImplies heytingContext leftElement bottomElement
residentContextElementValue joinResult @?= DiamondTop
residentContextElementValue meetResult @?= DiamondBottom
residentContextElementValue (residentContextElementForKey residentContext joinKey) @?= DiamondTop
residentContextElementValue (residentContextElementForKey residentContext meetKey) @?= DiamondBottom
residentContextElementValue implicationResult @?= DiamondRight
testResidentSupportClosure :: Assertion
testResidentSupportClosure =
case compileContextLattice diamondUniverse diamondDecl of
Left err ->
assertFailure ("expected diamond lattice, got " <> show err)
Right lattice ->
withResidentContext lattice $ \residentContext -> do
support <- requireRight (residentSupportFromElements residentContext [DiamondLeft])
topElement <- requireRight (checkResidentContext residentContext DiamondTop)
rightElement <- requireRight (checkResidentContext residentContext DiamondRight)
let cachedSupport = residentSupportWithClosure residentContext support
residentSupportContainsElement residentContext cachedSupport topElement @?= True
residentSupportContainsElement residentContext cachedSupport rightElement @?= False
fmap residentContextElementValue (residentSupportReachableElements residentContext support)
@?= [DiamondLeft, DiamondTop]
testBooleanCubeLattice :: Assertion
testBooleanCubeLattice =
case compileContextLattice booleanCubeUniverse booleanCubeDecl of
Left err ->
assertFailure ("expected boolean cube lattice, got " <> show err)
Right lattice ->
case compileContextHeyting lattice of
Left err -> assertFailure ("expected boolean Heyting proof, got " <> show err)
Right heyting -> do
contextLatticeElements lattice @?= [0 .. 7]
joinContext lattice 3 5 @?= Right 7
meetContext lattice 3 5 @?= Right 1
impliesContext heyting 3 1 @?= Right 5
leqContext lattice 3 7 @?= Right True
leqContext lattice 3 5 @?= Right False
booleanCubeUniverse :: Set.Set Int
booleanCubeUniverse =
Set.fromAscList [0 .. 7]
booleanCubeDecl :: ContextOrderDecl Int
booleanCubeDecl =
contextOrderDecl 7 0 booleanCubeCoverEdges
booleanCubeCoverEdges :: [(Int, Int)]
booleanCubeCoverEdges =
[ (mask, mask + bitValue)
| mask <- [0 .. 7],
bitValue <- [1, 2, 4],
mask .&. bitValue == 0
]
testUnknownJoinContext :: Assertion
testUnknownJoinContext =
joinContext (singletonContextLattice DiamondBottom) DiamondBottom DiamondTop
@?= Left (ContextLatticeUnknownContext DiamondTop)
testUnknownMeetContext :: Assertion
testUnknownMeetContext =
meetContext (singletonContextLattice DiamondBottom) DiamondTop DiamondBottom
@?= Left (ContextLatticeUnknownContext DiamondTop)
testUnknownLeqContext :: Assertion
testUnknownLeqContext =
leqContext (singletonContextLattice DiamondBottom) DiamondBottom DiamondTop
@?= Left (ContextLatticeUnknownContext DiamondTop)
testRejectsNonMonotoneFixpoint :: Assertion
testRejectsNonMonotoneFixpoint =
case boolLattice of
Left compileError ->
assertFailure ("unexpected compile failure: " <> show compileError)
Right lattice ->
leastContextFixpoint lattice not
@?= Left (ContextEndomapNotMonotone False True True False)
testLeastContextFixpointAscendsChain :: Assertion
testLeastContextFixpointAscendsChain =
case chainLattice of
Left err ->
assertFailure ("expected chain lattice, got " <> show err)
Right lattice ->
leastContextFixpoint lattice advanceChain
@?= Right ChainTop
testGreatestContextFixpointDescendsChain :: Assertion
testGreatestContextFixpointDescendsChain =
case chainLattice of
Left err ->
assertFailure ("expected chain lattice, got " <> show err)
Right lattice ->
greatestContextFixpoint lattice retreatChain
@?= Right ChainBottom
testContextFixpointClosureObstruction :: Assertion
testContextFixpointClosureObstruction =
leastContextFixpoint (singletonContextLattice DiamondBottom) (const DiamondTop)
@?= Left (ContextEndomapOutsideUniverse DiamondBottom DiamondTop)
testImpliesContextUnique :: Assertion
testImpliesContextUnique =
case (compileContextLattice diamondUniverse diamondDecl, chainLattice) of
(Right diamond, Right chain) -> do
diamondHeyting <- requireRight (compileContextHeyting diamond)
chainHeyting <- requireRight (compileContextHeyting chain)
impliesContext diamondHeyting DiamondLeft DiamondRight @?= Right DiamondRight
impliesContext diamondHeyting DiamondLeft DiamondBottom @?= Right DiamondRight
impliesContext chainHeyting ChainX ChainY @?= Right ChainTop
(Left err, _) ->
assertFailure ("expected diamond lattice, got " <> show err)
(_, Left err) ->
assertFailure ("expected chain lattice, got " <> show err)
testRejectsM3Residual :: Assertion
testRejectsM3Residual =
case m3Lattice of
Left compileError ->
assertFailure ("unexpected lattice failure: " <> show compileError)
Right lattice ->
compileContextHeyting lattice
`shouldFailWith` ContextResidualDoesNotExist M3A M3Bottom M3Top
testBooleanAdjunction :: Assertion
testBooleanAdjunction =
case boolean4 of
Left compileError ->
assertFailure ("unexpected lattice failure: " <> show compileError)
Right lattice ->
case compileContextHeyting lattice of
Left heytingError ->
assertFailure ("unexpected Heyting failure: " <> show heytingError)
Right heyting -> do
impliesContext heyting 1 0 @?= Right 2
for_ [(a, b, x) | a <- [0 .. 3], b <- [0 .. 3], x <- [0 .. 3]] $
\(a, b, x) -> assertAdjunction lattice heyting a b x
testDenseHeytingAdjunction :: Assertion
testDenseHeytingAdjunction =
case divisor12Lattice of
Left compileError ->
assertFailure ("unexpected lattice failure: " <> show compileError)
Right lattice ->
case compileContextHeyting lattice of
Left heytingError ->
assertFailure ("unexpected Heyting failure: " <> show heytingError)
Right heyting -> do
impliesContext heyting 2 3 @?= Right 3
impliesContext heyting 4 2 @?= Right 6
impliesContext heyting 6 2 @?= Right 4
traverse_
( \(a, b, x) ->
assertAdjunction lattice heyting a b x
)
[ (a, b, x)
| a <- divisor12Universe,
b <- divisor12Universe,
x <- divisor12Universe
]
testTablelessDistributiveMatchesDense :: Assertion
testTablelessDistributiveMatchesDense = do
defaultLattice <- requireRight divisor12Lattice
tablelessLattice <- requireRight divisor12TablelessLattice
assertLatticeAgreement "divisor12" divisor12Universe defaultLattice tablelessLattice
defaultHeyting <- requireRight (compileContextHeyting defaultLattice)
tablelessHeyting <- requireRight (compileContextHeyting tablelessLattice)
assertHeytingAgreement "divisor12" divisor12Universe defaultHeyting tablelessHeyting
traverse_
( \(antecedent, consequent, candidate) ->
assertAdjunction tablelessLattice tablelessHeyting antecedent consequent candidate
)
[ (antecedent, consequent, candidate)
| antecedent <- divisor12Universe,
consequent <- divisor12Universe,
candidate <- divisor12Universe
]
testTablelessN5MatchesDense :: Assertion
testTablelessN5MatchesDense = do
defaultLattice <- requireRight n5Lattice
tablelessLattice <- requireRight n5TablelessLattice
assertLatticeAgreement "N5" (Set.toAscList n5Universe) defaultLattice tablelessLattice
compileContextHeyting defaultLattice
`shouldFailWith` ContextResidualDoesNotExist N5C N5A N5Top
compileContextHeyting tablelessLattice
`shouldFailWith` ContextResidualDoesNotExist N5C N5A N5Top
testTablelessProduct64ImplicationBoundary :: Assertion
testTablelessProduct64ImplicationBoundary =
assertProductTablelessImplicationBoundary
64
[ (productContext 63 1, productContext 61 0, productContext 61 0),
(productContext 62 0, productContext 63 1, productContext 63 1),
(productContext 63 0, productContext 62 1, productContext 62 1)
]
[61 .. 63]
testTablelessProduct66ImplicationBoundary :: Assertion
testTablelessProduct66ImplicationBoundary =
assertProductTablelessImplicationBoundary
66
[ (productContext 65 1, productContext 64 0, productContext 64 0),
(productContext 64 0, productContext 65 1, productContext 65 1),
(productContext 63 1, productContext 64 0, productContext 65 0)
]
[63 .. 65]
testRejectsDenseN5Residual :: Assertion
testRejectsDenseN5Residual =
case n5Lattice of
Left compileError ->
assertFailure ("unexpected lattice failure: " <> show compileError)
Right lattice ->
compileContextHeyting lattice
`shouldFailWith` ContextResidualDoesNotExist N5C N5A N5Top
assertAdjunction :: (Ord c, Show c) => ContextLattice c -> ContextHeyting c -> c -> c -> c -> Assertion
assertAdjunction lattice heyting antecedent consequent candidate =
case (impliesContext heyting antecedent consequent, meetContext lattice candidate antecedent) of
(Right residual, Right candidateMeet) ->
case (leqContext lattice candidate residual, leqContext lattice candidateMeet consequent) of
(Right leftSide, Right rightSide) -> leftSide @?= rightSide
(leftResult, rightResult) ->
assertFailure ("unexpected lookup failure: " <> show (leftResult, rightResult))
(residualResult, meetResult) ->
assertFailure ("unexpected lookup failure: " <> show (residualResult, meetResult))
assertLatticeAgreement :: (Ord c, Show c) => String -> [c] -> ContextLattice c -> ContextLattice c -> Assertion
assertLatticeAgreement label universe expected actual = do
assertEqual (label <> " elements") (contextLatticeElements expected) (contextLatticeElements actual)
assertEqual (label <> " top") (clTop expected) (clTop actual)
assertEqual (label <> " bottom") (clBottom expected) (clBottom actual)
for_ universe $ \contextValue -> do
assertEqual (label <> " upper covers of " <> show contextValue) (upperCovers expected contextValue) (upperCovers actual contextValue)
assertEqual (label <> " lower covers of " <> show contextValue) (lowerCovers expected contextValue) (lowerCovers actual contextValue)
for_ [(leftContext, rightContext) | leftContext <- universe, rightContext <- universe] $
\(leftContext, rightContext) -> do
assertEqual (label <> " leq " <> show (leftContext, rightContext)) (leqContext expected leftContext rightContext) (leqContext actual leftContext rightContext)
assertEqual (label <> " join " <> show (leftContext, rightContext)) (joinContext expected leftContext rightContext) (joinContext actual leftContext rightContext)
assertEqual (label <> " meet " <> show (leftContext, rightContext)) (meetContext expected leftContext rightContext) (meetContext actual leftContext rightContext)
assertHeytingAgreement :: (Ord c, Show c) => String -> [c] -> ContextHeyting c -> ContextHeyting c -> Assertion
assertHeytingAgreement label universe expected actual =
for_ [(antecedent, consequent) | antecedent <- universe, consequent <- universe] $
\(antecedent, consequent) ->
assertEqual (label <> " implication " <> show (antecedent, consequent)) (impliesContext expected antecedent consequent) (impliesContext actual antecedent consequent)
assertProductTablelessImplicationBoundary :: Int -> [(Int, Int, Int)] -> [Int] -> Assertion
assertProductTablelessImplicationBoundary rowCount implicationExamples adjunctionRows = do
lattice <- requireRight (productLatticeWith noBinaryOperationTablesLimits rowCount)
heyting <- requireRight (compileContextHeyting lattice)
for_ implicationExamples $ \(antecedent, consequent, residual) ->
assertEqual
("product implication " <> show (productCoordinates antecedent, productCoordinates consequent))
(Right residual)
(impliesContext heyting antecedent consequent)
traverse_
( \(antecedent, consequent, candidate) ->
assertAdjunction lattice heyting antecedent consequent candidate
)
[ (antecedent, consequent, candidate)
| antecedent <- targetedProductContexts adjunctionRows,
consequent <- targetedProductContexts adjunctionRows,
candidate <- targetedProductContexts adjunctionRows
]
targetedProductContexts :: [Int] -> [Int]
targetedProductContexts rows =
[productContext row column | row <- rows, column <- [0, 1]]
productLatticeWith :: ContextCompileLimits -> Int -> Either (ContextLatticeCompileError Int) (ContextLattice Int)
productLatticeWith limits rowCount =
contextLatticeFromClosedOrderWith
limits
(productContext (rowCount - 1) 1)
(productContext 0 0)
(productUniverse rowCount)
productLeq
productJoin
productMeet
productUniverse :: Int -> [Int]
productUniverse rowCount =
[productContext row column | row <- [0 .. rowCount - 1], column <- [0, 1]]
productContext :: Int -> Int -> Int
productContext row column =
row * 2 + column
productCoordinates :: Int -> (Int, Int)
productCoordinates contextValue =
(contextValue `div` 2, contextValue `mod` 2)
productLeq :: Int -> Int -> Bool
productLeq leftContext rightContext =
leftRow <= rightRow && leftColumn <= rightColumn
where
(leftRow, leftColumn) = productCoordinates leftContext
(rightRow, rightColumn) = productCoordinates rightContext
productJoin :: Int -> Int -> Int
productJoin leftContext rightContext =
productContext (max leftRow rightRow) (max leftColumn rightColumn)
where
(leftRow, leftColumn) = productCoordinates leftContext
(rightRow, rightColumn) = productCoordinates rightContext
productMeet :: Int -> Int -> Int
productMeet leftContext rightContext =
productContext (min leftRow rightRow) (min leftColumn rightColumn)
where
(leftRow, leftColumn) = productCoordinates leftContext
(rightRow, rightColumn) = productCoordinates rightContext
type PosetLeq = Int -> Int -> Bool
type DownsetCase = (String, Int, PosetLeq, Int, [(Int, Int, Int)])
testDownsetGeneratedHeytingAdjunction :: Assertion
testDownsetGeneratedHeytingAdjunction =
for_ downsetLatticeCases $ \(caseName, elementCount, posetLeq, expectedSize, implicationWitnesses) -> do
let universe = downsetUniverse elementCount posetLeq
assertEqual (caseName <> " downset count") expectedSize (length universe)
case downsetLattice elementCount posetLeq of
Left compileError ->
assertFailure (caseName <> " unexpected lattice failure: " <> show compileError)
Right lattice ->
case compileContextHeyting lattice of
Left heytingError ->
assertFailure (caseName <> " unexpected Heyting failure: " <> show heytingError)
Right heyting -> do
for_ implicationWitnesses $ \(antecedent, consequent, residual) ->
impliesContext heyting antecedent consequent @?= Right residual
traverse_
( \(antecedent, consequent, candidate) ->
assertAdjunction lattice heyting antecedent consequent candidate
)
[ (antecedent, consequent, candidate)
| antecedent <- universe,
consequent <- universe,
candidate <- universe
]
downsetLatticeCases :: [DownsetCase]
downsetLatticeCases =
[ ( "two-point antichain",
2,
discretePosetLeq,
4,
[(1, 0, 2)]
),
( "two-element chain",
2,
chain2PlusIsolatedPosetLeq,
3,
[(1, 0, 0)]
),
( "vee poset",
3,
veePosetLeq,
5,
[(1, 2, 2)]
),
( "2x3 grid from a chain plus an isolated point",
3,
chain2PlusIsolatedPosetLeq,
6,
[(1, 0, 4)]
)
]
downsetLattice :: Int -> PosetLeq -> Either (ContextLatticeCompileError Int) (ContextLattice Int)
downsetLattice elementCount posetLeq =
compileContextLattice
(Set.fromList universe)
(contextOrderDecl (downsetTop elementCount) 0 (downsetCoverEdges universe))
where
universe = downsetUniverse elementCount posetLeq
downsetUniverse :: Int -> PosetLeq -> [Int]
downsetUniverse elementCount posetLeq =
[mask | mask <- [0 .. downsetTop elementCount], isDownset mask]
where
elements = [0 .. elementCount - 1]
isDownset mask =
all
( \upper ->
not (maskContains mask upper)
|| all
( \lower ->
not (posetLeq lower upper) || maskContains mask lower
)
elements
)
elements
downsetCoverEdges :: [Int] -> [(Int, Int)]
downsetCoverEdges universe =
[ (lower, upper)
| lower <- universe,
upper <- universe,
lower /= upper,
downsetSubset lower upper,
not (hasStrictIntermediate lower upper)
]
where
hasStrictIntermediate lower upper =
any
( \candidate ->
candidate /= lower
&& candidate /= upper
&& downsetSubset lower candidate
&& downsetSubset candidate upper
)
universe
downsetTop :: Int -> Int
downsetTop elementCount =
2 ^ elementCount - 1
downsetSubset :: Int -> Int -> Bool
downsetSubset lower upper =
lower .&. upper == lower
maskContains :: Int -> Int -> Bool
maskContains mask elementIndex =
mask .&. (2 ^ elementIndex) /= 0
discretePosetLeq :: PosetLeq
discretePosetLeq left right =
left == right
chain2PlusIsolatedPosetLeq :: PosetLeq
chain2PlusIsolatedPosetLeq left right =
left == right || (left == 0 && right == 1)
veePosetLeq :: PosetLeq
veePosetLeq left right =
left == right || (left == 0 && right == 2) || (left == 1 && right == 2)
testCoverOrientation :: Assertion
testCoverOrientation =
case chain3 of
Left compileError ->
assertFailure ("unexpected lattice failure: " <> show compileError)
Right lattice -> do
strictOrderPairs lattice @?= [(0, 1), (0, 2), (1, 2)]
coverPairs lattice @?= [(0, 1), (1, 2)]
upperCovers lattice 0 @?= Right [1]
lowerCovers lattice 2 @?= Right [1]
testUnknownTop :: Assertion
testUnknownTop =
compileContextLattice
(Set.fromList [DiamondBottom, DiamondLeft])
(contextOrderDecl DiamondTop DiamondBottom [])
`shouldFailWith` ContextLatticeUnknownTop DiamondTop
testUnknownBottom :: Assertion
testUnknownBottom =
compileContextLattice
(Set.fromList [DiamondLeft, DiamondTop])
(contextOrderDecl DiamondTop DiamondBottom [])
`shouldFailWith` ContextLatticeUnknownBottom DiamondBottom
testUnknownRelationEndpoint :: Assertion
testUnknownRelationEndpoint =
compileContextLattice
(Set.fromList [DiamondBottom, DiamondTop])
(contextOrderDecl DiamondTop DiamondBottom [(DiamondBottom, DiamondLeft)])
`shouldFailWith` ContextLatticeUnknownRelationEndpoint (DiamondBottom, DiamondLeft)
testAntisymmetryViolation :: Assertion
testAntisymmetryViolation =
compileContextLattice
(Set.fromList [DiamondBottom, DiamondTop])
( contextOrderDecl
DiamondTop
DiamondBottom
[ (DiamondBottom, DiamondTop),
(DiamondTop, DiamondBottom)
]
)
`shouldFailWith` ContextLatticeAntisymmetryViolation DiamondBottom DiamondTop
testTopNotGreatest :: Assertion
testTopNotGreatest =
compileContextLattice
(Set.fromList [DiamondBottom, DiamondLeft, DiamondTop])
(contextOrderDecl DiamondTop DiamondBottom [(DiamondBottom, DiamondTop)])
`shouldFailWith` ContextLatticeTopNotGreatest DiamondLeft
testBottomNotLeast :: Assertion
testBottomNotLeast =
compileContextLattice
(Set.fromList [DiamondBottom, DiamondLeft, DiamondTop])
( contextOrderDecl
DiamondTop
DiamondBottom
[ (DiamondLeft, DiamondTop),
(DiamondBottom, DiamondTop)
]
)
`shouldFailWith` ContextLatticeBottomNotLeast DiamondLeft
testMissingJoin :: Assertion
testMissingJoin =
compileContextLattice
(Set.fromList [NBottom, NA, NB, NU, NV, NTop])
( contextOrderDecl
NTop
NBottom
[ (NBottom, NA),
(NBottom, NB),
(NA, NU),
(NB, NU),
(NA, NV),
(NB, NV),
(NU, NTop),
(NV, NTop)
]
)
`shouldFailWith` ContextLatticeJoinDoesNotExist NA NB (Set.fromList [NU, NV])
shouldFailWith :: (Eq errorValue, Show errorValue) => Either errorValue value -> errorValue -> Assertion
shouldFailWith result expected =
case result of
Left actual -> actual @?= expected
Right _ -> assertFailure "expected lattice compile failure"
requireRight :: Show errorValue => Either errorValue value -> IO value
requireRight result =
case result of
Left errorValue ->
assertFailure ("expected Right, got " <> show errorValue)
Right value ->
pure value
diamondUniverse :: Set.Set DiamondContext
diamondUniverse =
Set.fromList [DiamondBottom, DiamondLeft, DiamondRight, DiamondTop]
diamondDecl :: ContextOrderDecl DiamondContext
diamondDecl =
contextOrderDecl
DiamondTop
DiamondBottom
[ (DiamondBottom, DiamondLeft),
(DiamondBottom, DiamondRight),
(DiamondLeft, DiamondTop),
(DiamondRight, DiamondTop)
]
chainLattice :: Either (ContextLatticeCompileError ChainContext) (ContextLattice ChainContext)
chainLattice =
contextLatticeFromClosedOrder
ChainTop
ChainBottom
closedChainUniverse
closedChainLeq
closedChainJoin
closedChainMeet
advanceChain :: ChainContext -> ChainContext
advanceChain contextValue =
case contextValue of
ChainBottom -> ChainX
ChainX -> ChainY
ChainY -> ChainTop
ChainZ -> ChainTop
ChainTop -> ChainTop
retreatChain :: ChainContext -> ChainContext
retreatChain contextValue =
case contextValue of
ChainTop -> ChainY
ChainY -> ChainX
ChainX -> ChainBottom
ChainZ -> ChainBottom
ChainBottom -> ChainBottom
boolLattice :: Either (ContextLatticeCompileError Bool) (ContextLattice Bool)
boolLattice =
contextLatticeFromClosedOrder True False [False, True] (<=) (||) (&&)
noBinaryOperationTablesLimits :: ContextCompileLimits
noBinaryOperationTablesLimits =
defaultContextCompileLimits {cclMaximumBinaryTableBytes = Just 0}
m3Lattice :: Either (ContextLatticeCompileError M3) (ContextLattice M3)
m3Lattice =
compileContextLattice
(Set.fromList [M3Bottom, M3A, M3B, M3C, M3Top])
( contextOrderDecl
M3Top
M3Bottom
[ (M3Bottom, M3A),
(M3Bottom, M3B),
(M3Bottom, M3C),
(M3A, M3Top),
(M3B, M3Top),
(M3C, M3Top)
]
)
n5Universe :: Set.Set N5
n5Universe =
Set.fromList [N5Bottom, N5A, N5B, N5C, N5Top]
n5Decl :: ContextOrderDecl N5
n5Decl =
contextOrderDecl
N5Top
N5Bottom
[ (N5Bottom, N5A),
(N5A, N5C),
(N5C, N5Top),
(N5Bottom, N5B),
(N5B, N5Top)
]
n5Lattice :: Either (ContextLatticeCompileError N5) (ContextLattice N5)
n5Lattice =
compileContextLattice n5Universe n5Decl
n5TablelessLattice :: Either (ContextLatticeCompileError N5) (ContextLattice N5)
n5TablelessLattice =
compileContextLatticeWith noBinaryOperationTablesLimits n5Universe n5Decl
boolean4 :: Either (ContextLatticeCompileError Int) (ContextLattice Int)
boolean4 =
contextLatticeFromClosedOrder
3
0
[0 .. 3]
(\left right -> left .&. right == left)
(.|.)
(.&.)
divisor12Lattice :: Either (ContextLatticeCompileError Int) (ContextLattice Int)
divisor12Lattice =
contextLatticeFromClosedOrder
12
1
divisor12Universe
divisor12Leq
lcm
gcd
divisor12TablelessLattice :: Either (ContextLatticeCompileError Int) (ContextLattice Int)
divisor12TablelessLattice =
contextLatticeFromClosedOrderWith
noBinaryOperationTablesLimits
12
1
divisor12Universe
divisor12Leq
lcm
gcd
divisor12Universe :: [Int]
divisor12Universe =
[1, 2, 3, 4, 6, 12]
divisor12Leq :: Int -> Int -> Bool
divisor12Leq left right =
left > 0 && right `mod` left == 0
chain3 :: Either (ContextLatticeCompileError Int) (ContextLattice Int)
chain3 =
contextLatticeFromClosedOrder 2 0 [0, 1, 2] (<=) max min