packages feed

moonlight-algebra-0.1.0.0: src-finite-lattice/Moonlight/FiniteLattice/Heyting.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE GHC2024 #-}
{-# LANGUAGE RoleAnnotations #-}

-- | Heyting implication over compiled finite context lattices: the relative
-- pseudocomplement as a precompiled query, with resident-key variants.
module Moonlight.FiniteLattice.Heyting
  ( ContextHeyting,
    contextHeytingLattice,
    ContextHeytingCompileError (..),
    compileContextHeyting,
    impliesContext,
    ResidentHeytingContext,
    residentHeytingBaseContext,
    withResidentHeytingContext,
    residentImpliesKey,
    residentImplies,
  )
where

import Data.Bits
  ( (.&.),
    (.|.),
    complement,
  )
import Data.Foldable (asum)
import Data.Kind (Type)
import Moonlight.FiniteLattice.Internal.Distributive
  ( ContextDistributivePlan,
    distributivePlanFromDenseComponents,
    distributiveResidualKey,
  )
import Moonlight.FiniteLattice.Internal.Key
  ( ContextKey (..),
    ContextKeySet,
    contextKeySetAll,
    contextKeySetChunkCount,
    contextKeySetDifference,
    contextKeySetFind,
    contextKeySetFoldr,
    contextKeySetUnionImages,
  )
import Moonlight.FiniteLattice.Internal.Plan
  ( ContextBooleanPlan (..),
    ContextBoundedFanPlan (..),
    ContextDenseTablePlan (..),
    ContextMaskPlan (..),
    ContextPlan (..),
    ContextTotalOrderPlan (..),
    booleanKeyForMask,
    booleanMaskForKey,
    contextPlanJoinKey,
    contextPlanLeq,
    contextPlanLowerKeys,
    contextPlanMeetKey,
    contextPlanUpperKeys,
    totalOrderKeyRank,
  )
import Moonlight.FiniteLattice.Internal.Types
  ( ContextLattice (..),
    ContextLatticeLookupError (..),
    ResidentContext (..),
    ResidentContextElement (..),
    ResidentContextKey,
    contextKeyForMaybe,
    contextKeyFromResidentKey,
    contextValueForKey,
    residentContextElementForKey,
    residentKeyFromContextKey,
  )

type ContextHeyting :: Type -> Type
data ContextHeyting c = ContextHeyting !(ContextLattice c) !ContextResidualPlan

type role ContextHeyting nominal

contextHeytingLattice :: ContextHeyting c -> ContextLattice c
contextHeytingLattice (ContextHeyting lattice _) =
  lattice
{-# INLINE contextHeytingLattice #-}

type ContextResidualPlan :: Type
data ContextResidualPlan
  = LazyDenseResidualPlan !ContextKeySet !Int
  | BirkhoffResidualPlan !ContextDistributivePlan
  | OrdinalTotalOrderResidualPlan !Int
  | TotalOrderResidualPlan !ContextTotalOrderPlan
  | BooleanResidualPlan !ContextBooleanPlan

type ContextHeytingCompileError :: Type -> Type
data ContextHeytingCompileError c
  -- | Antecedent, consequent, and the join of every candidate
  -- @x@ satisfying @antecedent ∧ x <= consequent@. The join is not itself a
  -- candidate, which proves that no greatest candidate exists.
  = ContextResidualDoesNotExist !c !c !c
  deriving stock (Eq, Ord, Show, Read)

type role ContextHeytingCompileError nominal

compileContextHeyting ::
  ContextLattice c ->
  Either (ContextHeytingCompileError c) (ContextHeyting c)
compileContextHeyting lattice =
  ContextHeyting lattice <$> compileResidualPlan lattice

compileResidualPlan ::
  ContextLattice c ->
  Either (ContextHeytingCompileError c) ContextResidualPlan
compileResidualPlan lattice =
  case clPlan lattice of
    OrdinalTotalOrderPlan size ->
      Right (OrdinalTotalOrderResidualPlan size)
    TotalOrderPlan plan ->
      Right (TotalOrderResidualPlan plan)
    MaskPlan (BooleanPlan plan) ->
      Right (BooleanResidualPlan plan)
    MaskPlan (DistributivePlan distributivePlan) ->
      Right (BirkhoffResidualPlan distributivePlan)
    MaskPlan (DenseRowsPlan _) ->
      validateDenseResidual lattice
    OrdinalBoundedFanPlan size ->
      rejectNonHeytingOrdinalFan lattice size
    BoundedFanPlan plan ->
      rejectNonHeytingFan lattice plan
    DensePlan tablePlan ->
      maybe
        (validateDenseResidual lattice)
        (Right . BirkhoffResidualPlan)
        ( distributivePlanFromDenseComponents
            (cdtpSize tablePlan)
            (clTopKey lattice)
            (clBottomKey lattice)
            (cdtpUpperRows tablePlan)
            (cdtpLowerRows tablePlan)
            (cdtpJoinTable tablePlan)
            (cdtpMeetTable tablePlan)
        )

-- In a fan with at least three atoms, choose an atom a and b = bottom. Every
-- other atom is a candidate for a => b; the join of two such atoms is top, but
-- a ∧ top = a is not <= bottom. Therefore the residual does not exist.
rejectNonHeytingFan ::
  ContextLattice c ->
  ContextBoundedFanPlan ->
  Either (ContextHeytingCompileError c) ContextResidualPlan
rejectNonHeytingFan lattice plan =
  case contextKeySetFind (const True) (cbfAtomKeys plan) of
    Nothing -> validateDenseResidual lattice
    Just atomOrdinal ->
      Left
        ( ContextResidualDoesNotExist
            (contextValueForKey lattice (ContextKey atomOrdinal))
            (contextValueForKey lattice (cbfBottomKey plan))
            (contextValueForKey lattice (cbfTopKey plan))
        )

rejectNonHeytingOrdinalFan ::
  ContextLattice c ->
  Int ->
  Either (ContextHeytingCompileError c) ContextResidualPlan
rejectNonHeytingOrdinalFan lattice size =
  Left
    ( ContextResidualDoesNotExist
        (contextValueForKey lattice (ContextKey 1))
        (contextValueForKey lattice (ContextKey 0))
        (contextValueForKey lattice (ContextKey (size - 1)))
    )

validateDenseResidual ::
  ContextLattice c ->
  Either (ContextHeytingCompileError c) ContextResidualPlan
validateDenseResidual lattice =
  case asum (residualObstruction <$> residualPairs) of
    Just obstruction -> Left obstruction
    Nothing -> Right (LazyDenseResidualPlan allKeys chunkCount)
  where
    size = clSize lattice
    chunkCount = contextKeySetChunkCount size
    allKeys = contextKeySetAll size
    plan = clPlan lattice
    residualPairs =
      [ (ContextKey antecedentOrdinal, ContextKey consequentOrdinal)
      | antecedentOrdinal <- [0 .. size - 1],
        consequentOrdinal <- [0 .. size - 1]
      ]

    residualObstruction (antecedentKey, consequentKey) =
      let candidateJoin =
            residualCandidateJoin allKeys chunkCount lattice antecedentKey consequentKey
          candidateMeet = contextPlanMeetKey plan antecedentKey candidateJoin
       in if contextPlanLeq plan candidateMeet consequentKey
            then Nothing
            else
              Just
                ( ContextResidualDoesNotExist
                    (contextValueForKey lattice antecedentKey)
                    (contextValueForKey lattice consequentKey)
                    (contextValueForKey lattice candidateJoin)
                )

-- C = {x | a ∧ x <= b}, r = join C. Bottom ∈ C, so C is nonempty; the residual
-- exists iff r ∈ C, where it is then the greatest member. Candidates avoid
-- recomputing meets: a ∧ x <= b iff lower(a) ∩ lower(x) ⊆ lower(b) iff x is
-- above no y ∈ lower(a) \\ lower(b). So C is all keys minus the upward closure
-- of those forbidden lower keys.
residualCandidateJoin ::
  ContextKeySet ->
  Int ->
  ContextLattice c ->
  ContextKey ->
  ContextKey ->
  ContextKey
residualCandidateJoin allKeys chunkCount lattice antecedentKey consequentKey =
  contextKeySetFoldr joinCandidate (clBottomKey lattice) candidateKeys
  where
    plan = clPlan lattice
    candidateKeys =
      residualCandidateKeys allKeys chunkCount lattice antecedentKey consequentKey

    joinCandidate !candidateOrdinal !candidateJoin =
      contextPlanJoinKey plan candidateJoin (ContextKey candidateOrdinal)

residualCandidateKeys ::
  ContextKeySet ->
  Int ->
  ContextLattice c ->
  ContextKey ->
  ContextKey ->
  ContextKeySet
residualCandidateKeys allKeys chunkCount lattice antecedentKey consequentKey =
  contextKeySetDifference allKeys rejectedKeys
  where
    plan = clPlan lattice
    forbiddenLowerKeys =
      contextKeySetDifference
        (contextPlanLowerKeys plan antecedentKey)
        (contextPlanLowerKeys plan consequentKey)
    rejectedKeys =
      contextKeySetUnionImages
        chunkCount
        (contextPlanUpperKeys plan . ContextKey)
        forbiddenLowerKeys

impliesContext ::
  Ord c =>
  ContextHeyting c ->
  c ->
  c ->
  Either (ContextLatticeLookupError c) c
impliesContext (ContextHeyting lattice residualPlan) antecedent consequent = do
  antecedentKey <- lookupContextKey lattice antecedent
  consequentKey <- lookupContextKey lattice consequent
  pure
    ( contextValueForKey
        lattice
        (residualKey lattice residualPlan antecedentKey consequentKey)
    )

lookupContextKey ::
  Ord c =>
  ContextLattice c ->
  c ->
  Either (ContextLatticeLookupError c) ContextKey
lookupContextKey lattice contextValue =
  maybe
    (Left (ContextLatticeUnknownContext contextValue))
    Right
    (contextKeyForMaybe lattice contextValue)

type ResidentHeytingContext :: Type -> Type -> Type
data ResidentHeytingContext s c = ResidentHeytingContext
  { residentHeytingBaseContext :: !(ResidentContext s c),
    residentHeytingResidualPlan :: !ContextResidualPlan
  }

type role ResidentHeytingContext nominal nominal

withResidentHeytingContext ::
  ContextHeyting c ->
  (forall s. ResidentHeytingContext s c -> result) ->
  result
withResidentHeytingContext (ContextHeyting lattice residualPlan) continuation =
  continuation
    ResidentHeytingContext
      { residentHeytingBaseContext = ResidentContext lattice,
        residentHeytingResidualPlan = residualPlan
      }

residentImpliesKey ::
  ResidentHeytingContext s c ->
  ResidentContextKey s ->
  ResidentContextKey s ->
  ResidentContextKey s
residentImpliesKey context antecedentKey consequentKey =
  case residentHeytingBaseContext context of
    ResidentContext lattice ->
      residentKeyFromContextKey
        ( residualKey
            lattice
            (residentHeytingResidualPlan context)
            (contextKeyFromResidentKey antecedentKey)
            (contextKeyFromResidentKey consequentKey)
        )
{-# INLINE residentImpliesKey #-}

residentImplies ::
  ResidentHeytingContext s c ->
  ResidentContextElement s c ->
  ResidentContextElement s c ->
  ResidentContextElement s c
residentImplies context antecedent consequent =
  residentContextElementForKey
    (residentHeytingBaseContext context)
    ( residentImpliesKey
        context
        (residentContextElementKey antecedent)
        (residentContextElementKey consequent)
    )

residualKey ::
  ContextLattice c ->
  ContextResidualPlan ->
  ContextKey ->
  ContextKey ->
  ContextKey
residualKey lattice residualPlan antecedentKey consequentKey =
  case residualPlan of
    LazyDenseResidualPlan allKeys chunkCount ->
      residualCandidateJoin allKeys chunkCount lattice antecedentKey consequentKey
    BirkhoffResidualPlan plan ->
      distributiveResidualKey plan antecedentKey consequentKey
    OrdinalTotalOrderResidualPlan size ->
      if contextKeyOrdinal antecedentKey <= contextKeyOrdinal consequentKey
            then ContextKey (size - 1)
            else consequentKey
    TotalOrderResidualPlan plan ->
      if
            totalOrderKeyRank plan antecedentKey
              <= totalOrderKeyRank plan consequentKey
            then ctoTopKey plan
            else consequentKey
    BooleanResidualPlan plan ->
      booleanKeyForMask
            plan
            ( (complement (booleanMaskForKey plan antecedentKey) .&. cboFullMask plan)
                .|. booleanMaskForKey plan consequentKey
            )
{-# INLINE residualKey #-}