packages feed

pubgrub-0.1.0.0: src/PubGrub/Relation.hs

module PubGrub.Relation
    ( Relation (..)
    , relation
    , IncompatRelation (..)
    , incompRelation
    ) where

import qualified Data.Map.Strict         as Map
import           PubGrub.Incompatibility (Domain (..), Incompatibility (..))
import           PubGrub.SetOps          (SetOps (..))
import           PubGrub.Term            (Term (..))

-- Specialised in the client, with the solver ("PubGrub.Solver" says why).
{-# INLINABLE relation #-}
{-# INLINABLE incompRelation #-}


-- | How a term stands relative to a reference term (both about the same
-- package). This is the primitive unit propagation reads off the partial
-- solution.
data Relation
    = -- | @subject ⊆ reference@: the reference already holds.
      Satisfied
    | -- | @subject ∩ reference = ∅@: they cannot both hold.
      Contradicted
    | -- | neither — the reference is still open.
      Inconclusive
    deriving (Eq, Show)

-- | Relation of @subject@ (typically what the partial solution has derived so
-- far for a package) to @reference@ (a term from an incompatibility). Both
-- tests are emptiness tests, so no equality on payloads is needed — and no
-- reliance on canonical representations either. Against a negative reference,
-- an exact subject either avoids the set or lands in it; a negative subject is
-- never satisfied by a positive reference, because the /absent/ state always
-- escapes it.
relation :: SetOps a => Term a -> Term a -> Relation
relation subject reference
    | subject `isSubsetOf` reference = Satisfied
    | subject `isDisjoint` reference = Contradicted
    | otherwise = Inconclusive

-- --------------------------------------------------------------------------
-- Judging a whole incompatibility
-- --------------------------------------------------------------------------

-- | Relation of a partial solution (seen as a term lookup) to a whole
-- incompatibility.
data IncompatRelation v
    = ISatisfied
    | IContradicted v
    | -- | all terms satisfied but one (the named variable): the unit case
      IAlmostSatisfied v
    | IInconclusive
    deriving (Eq, Show)

-- | Judge an incompatibility against the partial solution. @lookupTerm v@ is
-- the accumulated term the partial solution holds for @v@, or 'Nothing' if
-- @v@ is unmentioned (equivalent to ⊤, and always inconclusive).
incompRelation
    :: Domain v
    => (v -> Maybe (Term (SetOf v)))
    -> Incompatibility v
    -> IncompatRelation v
incompRelation lookupTerm (Incompatibility m _) = Map.foldlWithKey' step ISatisfied m
  where
    -- A strict fold over the map rather than a walk over 'Map.toList': this runs
    -- for every clause on every visit, and the list cost a tenth of a large
    -- solve's allocation. A clause has a handful of terms, so once the answer is
    -- final the remaining terms are skipped by the first two equations rather
    -- than by stopping. Terms are judged in key order, which decides the
    -- variable a contradiction or an open term is reported on.
    step acc@(IContradicted _) _ _ = acc
    step IInconclusive _ _ = IInconclusive
    step acc v it =
        case lookupTerm v of
            Just term ->
                case relation term it of
                    Satisfied    -> acc
                    Contradicted -> IContradicted v
                    Inconclusive -> almost v acc
            Nothing -> almost v acc
    -- absent or inconclusive term: at most one allowed for AlmostSatisfied
    almost v ISatisfied = IAlmostSatisfied v
    almost _ _          = IInconclusive