what4-1.3: src/What4/Expr/BoolMap.hs
{-|
Module : What4.Expr.BoolMap
Description : Datastructure for representing a conjunction of predicates
Copyright : (c) Galois Inc, 2019-2020
License : BSD3
Maintainer : rdockins@galois.com
Declares a datatype for representing n-way conjunctions or disjunctions
in a way that efficiently captures important algebraic
laws like commutativity, associativity and resolution.
-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ViewPatterns #-}
module What4.Expr.BoolMap
( BoolMap
, var
, addVar
, fromVars
, combine
, Polarity(..)
, negatePolarity
, contains
, isInconsistent
, isNull
, BoolMapView(..)
, viewBoolMap
, traverseVars
, reversePolarities
, removeVar
, Wrap(..)
) where
import Control.Lens (_1, over)
import Data.Hashable
import Data.List (foldl')
import Data.List.NonEmpty (NonEmpty(..))
import Data.Kind (Type)
import Data.Parameterized.Classes
import What4.BaseTypes
import qualified What4.Utils.AnnotatedMap as AM
import What4.Utils.IncrHash
-- | Describes the occurrence of a variable or expression, whether it is
-- negated or not.
data Polarity = Positive | Negative
deriving (Eq,Ord,Show)
instance Hashable Polarity where
hashWithSalt s Positive = hashWithSalt s (0::Int)
hashWithSalt s Negative = hashWithSalt s (1::Int)
-- | Swap a polarity value
negatePolarity :: Polarity -> Polarity
negatePolarity Positive = Negative
negatePolarity Negative = Positive
newtype Wrap (f :: k -> Type) (x :: k) = Wrap { unWrap:: f x }
instance TestEquality f => Eq (Wrap f x) where
Wrap a == Wrap b = isJust $ testEquality a b
instance OrdF f => Ord (Wrap f x) where
compare (Wrap a) (Wrap b) = toOrdering $ compareF a b
instance (HashableF f, TestEquality f) => Hashable (Wrap f x) where
hashWithSalt s (Wrap a) = hashWithSaltF s a
-- | This data structure keeps track of a collection of expressions
-- together with their polarities. Such a collection might represent
-- either a conjunction or a disjunction of expressions. The
-- implementation uses a map from expression values to their
-- polarities, and thus automatically implements the associative,
-- commutative and idempotency laws common to both conjunctions and
-- disjunctions. Moreover, if the same expression occurs in the
-- collection with opposite polarities, the entire collection
-- collapses via a resolution step to an \"inconsistent\" map. For
-- conjunctions this corresponds to a contradiction and
-- represents false; for disjunction, this corresponds to the law of
-- the excluded middle and represents true.
data BoolMap (f :: BaseType -> Type)
= InconsistentMap
| BoolMap !(AM.AnnotatedMap (Wrap f BaseBoolType) IncrHash Polarity)
instance OrdF f => Eq (BoolMap f) where
InconsistentMap == InconsistentMap = True
BoolMap m1 == BoolMap m2 = AM.eqBy (==) m1 m2
_ == _ = False
-- | Traverse the expressions in a bool map, and rebuild the map.
traverseVars :: (Applicative m, HashableF g, OrdF g) =>
(f BaseBoolType -> m (g (BaseBoolType))) ->
BoolMap f -> m (BoolMap g)
traverseVars _ InconsistentMap = pure InconsistentMap
traverseVars f (BoolMap m) =
fromVars <$> traverse (_1 (f . unWrap)) (AM.toList m)
elementHash :: HashableF f => f BaseBoolType -> Polarity -> IncrHash
elementHash x p = mkIncrHash (hashWithSaltF (hash p) x)
instance (OrdF f, HashableF f) => Hashable (BoolMap f) where
hashWithSalt s InconsistentMap = hashWithSalt s (0::Int)
hashWithSalt s (BoolMap m) =
case AM.annotation m of
Nothing -> hashWithSalt s (1::Int)
Just h -> hashWithSalt (hashWithSalt s (1::Int)) h
-- | Represents the state of a bool map
data BoolMapView f
= BoolMapUnit
-- ^ A bool map with no expressions, represents the unit of the corresponding operation
| BoolMapDualUnit
-- ^ An inconsistent bool map, represents the dual of the operation unit
| BoolMapTerms (NonEmpty (f BaseBoolType, Polarity))
-- ^ The terms appearing in the bool map, of which there is at least one
-- | Deconstruct the given bool map for later processing
viewBoolMap :: BoolMap f -> BoolMapView f
viewBoolMap InconsistentMap = BoolMapDualUnit
viewBoolMap (BoolMap m) =
case AM.toList m of
[] -> BoolMapUnit
(Wrap x,p):xs -> BoolMapTerms ((x,p):|(map (over _1 unWrap) xs))
-- | Returns true for an inconsistent bool map
isInconsistent :: BoolMap f -> Bool
isInconsistent InconsistentMap = True
isInconsistent _ = False
-- | Returns true for a \"null\" bool map with no terms
isNull :: BoolMap f -> Bool
isNull InconsistentMap = False
isNull (BoolMap m) = AM.null m
-- | Produce a singleton bool map, consisting of just the given term
var :: (HashableF f, OrdF f) => f BaseBoolType -> Polarity -> BoolMap f
var x p = BoolMap (AM.singleton (Wrap x) (elementHash x p) p)
-- | Add a variable to a bool map, performing a resolution step if possible
addVar :: (HashableF f, OrdF f) => f BaseBoolType -> Polarity -> BoolMap f -> BoolMap f
addVar _ _ InconsistentMap = InconsistentMap
addVar x p1 (BoolMap bm) = maybe InconsistentMap BoolMap $ AM.alterF f (Wrap x) bm
where
f Nothing = return (Just (elementHash x p1, p1))
f el@(Just (_,p2)) | p1 == p2 = return el
| otherwise = Nothing
-- | Generate a bool map from a list of terms and polarities by repeatedly
-- calling @addVar@.
fromVars :: (HashableF f, OrdF f) => [(f BaseBoolType, Polarity)] -> BoolMap f
fromVars = foldl' (\m (x,p) -> addVar x p m) (BoolMap AM.empty)
-- | Merge two bool maps, performing resolution as necessary.
combine :: OrdF f => BoolMap f -> BoolMap f -> BoolMap f
combine InconsistentMap _ = InconsistentMap
combine _ InconsistentMap = InconsistentMap
combine (BoolMap m1) (BoolMap m2) =
maybe InconsistentMap BoolMap $ AM.mergeA f m1 m2
where f _k (v,p1) (_,p2)
| p1 == p2 = Just (v,p1)
| otherwise = Nothing
-- | Test if the bool map contains the given term, and return the polarity
-- of that term if so.
contains :: OrdF f => BoolMap f -> f BaseBoolType -> Maybe Polarity
contains InconsistentMap _ = Nothing
contains (BoolMap m) x = snd <$> AM.lookup (Wrap x) m
-- | Swap the polarities of the terms in the given bool map.
reversePolarities :: OrdF f => BoolMap f -> BoolMap f
reversePolarities InconsistentMap = InconsistentMap
reversePolarities (BoolMap m) = BoolMap $! fmap negatePolarity m
-- | Remove the given term from the bool map. The map is unchanged
-- if inconsistent or if the term does not occur.
removeVar :: OrdF f => BoolMap f -> f BaseBoolType -> BoolMap f
removeVar InconsistentMap _ = InconsistentMap
removeVar (BoolMap m) x = BoolMap (AM.delete (Wrap x) m)