imp-ppl-0.1.0.0: src/Imp/BDD/Builder.hs
-- | Hash-consed BDD manager and the core BDD operations.
module Imp.BDD.Builder
( BDDManager
, nodeTable
, BDDM
-- Manager operations.
, emptyManager
, newVar
, lookupNode
-- BDD operations.
, bddNot
, bddAnd
, bddOr
, bddAny
, bddIte
, bddRestrict
) where
import Control.Monad.State.Strict
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IntMap
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Imp.BDD
-- | Hash-consed node store with memo caches.
data BDDManager = BDDManager
{ nextNodeId :: !Int
-- | The hash-consed node store, keyed by t'NodeId'.
, nodeTable :: !(IntMap BDDNode)
, uniqueTable :: !(Map BDDNode NodeId)
, iteCache :: !(Map (BDD, BDD, BDD) BDD)
, restrictCache :: !(Map (BDD, VarLabel, Bool) BDD)
, nextVarId :: !Int
} deriving stock (Show)
-- | A manager with no nodes or variables.
emptyManager :: BDDManager
emptyManager = BDDManager 0 IntMap.empty Map.empty Map.empty Map.empty 0
-- | BDD-manager state monad.
type BDDM = State BDDManager
-- | Allocate a fresh BDD variable and return its positive literal
-- together with its t'VarLabel'.
newVar :: BDDM (BDD, VarLabel)
newVar = do
v <- VarLabel <$> gets nextVarId
modify' (\mgr -> mgr { nextVarId = nextVarId mgr + 1 })
bdd <- mkNode (BDDNode v BDDFalse BDDTrue)
return (bdd, v)
-- | Negate a BDD in O(1) via complemented edges.
bddNot :: BDD -> BDD
bddNot BDDTrue = BDDFalse
bddNot BDDFalse = BDDTrue
bddNot (BDDRef n) = BDDComp n
bddNot (BDDComp n) = BDDRef n
-- | Conjunction of two BDDs.
bddAnd :: BDD -> BDD -> BDDM BDD
bddAnd a b = bddIte a b BDDFalse
-- | Disjunction of two BDDs.
bddOr :: BDD -> BDD -> BDDM BDD
bddOr a b = bddIte a BDDTrue b
-- | OR together a list of BDDs using a balanced tree fold.
bddAny :: [BDD] -> BDDM BDD
bddAny [] = return BDDFalse
bddAny [g] = return g
bddAny xs = do
let (l, r) = splitAt (length xs `div` 2) xs
lv <- bddAny l
rv <- bddAny r
bddOr lv rv
-- | BDD if-then-else terminal cases and memoisation.
bddIte :: BDD -> BDD -> BDD -> BDDM BDD
bddIte f g h
| f == BDDTrue = return g
| f == BDDFalse = return h
| g == BDDTrue && h == BDDFalse = return f
| g == BDDFalse && h == BDDTrue = return (bddNot f)
| g == h = return g
| otherwise = do
cached <- gets (Map.lookup (f, g, h) . iteCache)
case cached of
Just result -> return result
Nothing -> do
result <- bddIteExpand f g h
modify' $ \s -> s { iteCache = Map.insert (f, g, h) result (iteCache s) }
return result
-- | Shannon expansion on the topmost variable.
bddIteExpand :: BDD -> BDD -> BDD -> BDDM BDD
bddIteExpand f g h = do
nf <- lookupNode f
ng <- lookupNode g
nh <- lookupNode h
-- f is never constant here, so at least one node exists.
let topV = minimum [bddVar n | Just n <- [nf, ng, nh]]
cofactor branch bdd node = case node of
Just n | bddVar n == topV -> if branch then bddHigh n else bddLow n
_ -> bdd
lo <- bddIte (cofactor False f nf) (cofactor False g ng) (cofactor False h nh)
hi <- bddIte (cofactor True f nf) (cofactor True g ng) (cofactor True h nh)
mkNode (BDDNode topV lo hi)
-- | Restrict a BDD by fixing a variable to True or False, with memoisation.
bddRestrict :: BDD -> VarLabel -> Bool -> BDDM BDD
bddRestrict BDDTrue _ _ = return BDDTrue
bddRestrict BDDFalse _ _ = return BDDFalse
bddRestrict bdd var val = do
cached <- gets (Map.lookup (bdd, var, val) . restrictCache)
case cached of
Just result -> return result
Nothing -> do
result <- bddRestrictExpand bdd var val
modify' $ \s ->
s { restrictCache = Map.insert (bdd, var, val) result (restrictCache s) }
return result
-- | Restrict one level and recurse.
bddRestrictExpand :: BDD -> VarLabel -> Bool -> BDDM BDD
bddRestrictExpand bdd var val = do
mnode <- lookupNode bdd
case mnode of
Nothing -> return bdd
Just (BDDNode v lo hi)
| v == var -> return $ if val then hi else lo
| v > var -> return bdd -- var not in this sub-BDD
| otherwise -> do
lo' <- bddRestrict lo var val
hi' <- bddRestrict hi var val
mkNode (BDDNode v lo' hi')
-- | Look up a BDD reference.
lookupNode :: BDD -> BDDM (Maybe BDDNode)
lookupNode BDDTrue = return Nothing
lookupNode BDDFalse = return Nothing
lookupNode (BDDRef (NodeId n)) = IntMap.lookup n <$> gets nodeTable
lookupNode (BDDComp (NodeId n)) = do
node <- IntMap.lookup n <$> gets nodeTable
return $ (\(BDDNode v lo hi) -> BDDNode v (bddNot lo) (bddNot hi)) <$> node
-- | Hash-consed node creation with complemented-edge normalization.
mkNode :: BDDNode -> BDDM BDD
mkNode node@(BDDNode v lo hi)
| lo == hi = return lo
-- Normalize nodes to never have a complemented low edge.
| BDDTrue <- lo = bddNot <$> mkNode (BDDNode v (bddNot lo) (bddNot hi))
| BDDComp _ <- lo = bddNot <$> mkNode (BDDNode v (bddNot lo) (bddNot hi))
| otherwise = do
mgr <- get
case Map.lookup node (uniqueTable mgr) of
Just nid -> return (BDDRef nid)
Nothing -> do
let nidInt = nextNodeId mgr
nid = NodeId nidInt
put mgr { nextNodeId = nidInt + 1
, nodeTable = IntMap.insert nidInt node (nodeTable mgr)
, uniqueTable = Map.insert node nid (uniqueTable mgr)
}
return (BDDRef nid)