packages feed

decision-diagrams 0.1.0.0 → 0.2.0.0

raw patch · 10 files changed

+1596/−311 lines, 10 filesdep +deepseqdep +doctestdep +quickcheck-instancesdep ~QuickCheckdep ~basedep ~containersPVP ok

version bump matches the API change (PVP)

Dependencies added: deepseq, doctest, quickcheck-instances, vector

Dependency ranges changed: QuickCheck, base, containers, hashable, primitive, statistics, tasty, tasty-quickcheck

API changes (from Hackage documentation)

- Data.DecisionDiagram.BDD: NodeBranch :: !Int -> Int -> Int -> Node
- Data.DecisionDiagram.BDD: NodeF :: Node
- Data.DecisionDiagram.BDD: NodeT :: Node
- Data.DecisionDiagram.BDD: data Node
- Data.DecisionDiagram.BDD: instance GHC.Classes.Eq Data.DecisionDiagram.BDD.Node
- Data.DecisionDiagram.BDD: instance GHC.Read.Read Data.DecisionDiagram.BDD.Node
- Data.DecisionDiagram.BDD: instance GHC.Show.Show Data.DecisionDiagram.BDD.Node
- Data.DecisionDiagram.BDD: pattern F :: BDD a
- Data.DecisionDiagram.BDD: pattern T :: BDD a
- Data.DecisionDiagram.ZDD: NodeBase :: Node
- Data.DecisionDiagram.ZDD: NodeBranch :: !Int -> Int -> Int -> Node
- Data.DecisionDiagram.ZDD: NodeEmpty :: Node
- Data.DecisionDiagram.ZDD: data Node
- Data.DecisionDiagram.ZDD: instance GHC.Classes.Eq Data.DecisionDiagram.ZDD.Node
- Data.DecisionDiagram.ZDD: instance GHC.Read.Read Data.DecisionDiagram.ZDD.Node
- Data.DecisionDiagram.ZDD: instance GHC.Show.Show Data.DecisionDiagram.ZDD.Node
+ Data.DecisionDiagram.BDD: SBranch :: !Int -> a -> a -> Sig a
+ Data.DecisionDiagram.BDD: SLeaf :: !Bool -> Sig a
+ Data.DecisionDiagram.BDD: allSat :: BDD a -> [IntMap Bool]
+ Data.DecisionDiagram.BDD: allSatComplete :: ItemOrder a => IntSet -> BDD a -> [IntMap Bool]
+ Data.DecisionDiagram.BDD: anySat :: BDD a -> Maybe (IntMap Bool)
+ Data.DecisionDiagram.BDD: anySatComplete :: ItemOrder a => IntSet -> BDD a -> Maybe (IntMap Bool)
+ Data.DecisionDiagram.BDD: countSat :: forall a b. (ItemOrder a, Num b, Bits b, HasCallStack) => IntSet -> BDD a -> b
+ Data.DecisionDiagram.BDD: data Sig a
+ Data.DecisionDiagram.BDD: gfp :: ItemOrder a => (BDD a -> BDD a) -> BDD a
+ Data.DecisionDiagram.BDD: inSig :: Sig (BDD a) -> BDD a
+ Data.DecisionDiagram.BDD: lfp :: ItemOrder a => (BDD a -> BDD a) -> BDD a
+ Data.DecisionDiagram.BDD: numNodes :: BDD a -> Int
+ Data.DecisionDiagram.BDD: outSig :: BDD a -> Sig (BDD a)
+ Data.DecisionDiagram.BDD: pattern Leaf :: Bool -> BDD a
+ Data.DecisionDiagram.BDD: pbAtLeast :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> BDD a
+ Data.DecisionDiagram.BDD: pbAtMost :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> BDD a
+ Data.DecisionDiagram.BDD: pbExactly :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> BDD a
+ Data.DecisionDiagram.BDD: pbExactlyIntegral :: forall a w. (ItemOrder a, Real w, Integral w) => IntMap w -> w -> BDD a
+ Data.DecisionDiagram.BDD: unfoldHashable :: forall a b. (ItemOrder a, Eq b, Hashable b) => (b -> Sig b) -> b -> BDD a
+ Data.DecisionDiagram.BDD: unfoldOrd :: forall a b. (ItemOrder a, Ord b) => (b -> Sig b) -> b -> BDD a
+ Data.DecisionDiagram.BDD: uniformSatM :: forall a g m. (ItemOrder a, StatefulGen g m, HasCallStack) => IntSet -> BDD a -> g -> m (IntMap Bool)
+ Data.DecisionDiagram.BDD.Internal.ItemOrder: OrderedItem :: Int -> OrderedItem a
+ Data.DecisionDiagram.BDD.Internal.ItemOrder: instance Data.DecisionDiagram.BDD.Internal.ItemOrder.ItemOrder a => GHC.Classes.Ord (Data.DecisionDiagram.BDD.Internal.ItemOrder.OrderedItem a)
+ Data.DecisionDiagram.BDD.Internal.ItemOrder: instance GHC.Classes.Eq (Data.DecisionDiagram.BDD.Internal.ItemOrder.OrderedItem a)
+ Data.DecisionDiagram.BDD.Internal.ItemOrder: instance GHC.Show.Show (Data.DecisionDiagram.BDD.Internal.ItemOrder.OrderedItem a)
+ Data.DecisionDiagram.BDD.Internal.ItemOrder: newtype OrderedItem a
+ Data.DecisionDiagram.ZDD: SBranch :: !Int -> a -> a -> Sig a
+ Data.DecisionDiagram.ZDD: SLeaf :: !Bool -> Sig a
+ Data.DecisionDiagram.ZDD: combinations :: forall a. (ItemOrder a, HasCallStack) => IntSet -> Int -> ZDD a
+ Data.DecisionDiagram.ZDD: data Sig a
+ Data.DecisionDiagram.ZDD: inSig :: Sig (ZDD a) -> ZDD a
+ Data.DecisionDiagram.ZDD: numNodes :: ZDD a -> Int
+ Data.DecisionDiagram.ZDD: outSig :: ZDD a -> Sig (ZDD a)
+ Data.DecisionDiagram.ZDD: pattern Leaf :: Bool -> ZDD a
+ Data.DecisionDiagram.ZDD: pattern SBase :: Sig a
+ Data.DecisionDiagram.ZDD: pattern SEmpty :: Sig a
+ Data.DecisionDiagram.ZDD: subsetsAtLeast :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> ZDD a
+ Data.DecisionDiagram.ZDD: subsetsAtMost :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> ZDD a
+ Data.DecisionDiagram.ZDD: subsetsExactly :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> ZDD a
+ Data.DecisionDiagram.ZDD: subsetsExactlyIntegral :: forall a w. (ItemOrder a, Real w, Integral w) => IntMap w -> w -> ZDD a
+ Data.DecisionDiagram.ZDD: unfoldHashable :: forall a b. (ItemOrder a, Eq b, Hashable b) => (b -> Sig b) -> b -> ZDD a
+ Data.DecisionDiagram.ZDD: unfoldOrd :: forall a b. (ItemOrder a, Ord b) => (b -> Sig b) -> b -> ZDD a
- Data.DecisionDiagram.BDD: fold :: b -> b -> (Int -> b -> b -> b) -> BDD a -> b
+ Data.DecisionDiagram.BDD: fold :: (Int -> b -> b -> b) -> (Bool -> b) -> BDD a -> b
- Data.DecisionDiagram.BDD: fold' :: b -> b -> (Int -> b -> b -> b) -> BDD a -> b
+ Data.DecisionDiagram.BDD: fold' :: (Int -> b -> b -> b) -> (Bool -> b) -> BDD a -> b
- Data.DecisionDiagram.BDD: fromGraph :: (Graph, Int) -> BDD a
+ Data.DecisionDiagram.BDD: fromGraph :: HasCallStack => (Graph Sig, Int) -> BDD a
- Data.DecisionDiagram.BDD: fromGraph' :: Graph -> IntMap (BDD a)
+ Data.DecisionDiagram.BDD: fromGraph' :: HasCallStack => Graph Sig -> IntMap (BDD a)
- Data.DecisionDiagram.BDD: toGraph :: BDD a -> (Graph, Int)
+ Data.DecisionDiagram.BDD: toGraph :: BDD a -> (Graph Sig, Int)
- Data.DecisionDiagram.BDD: toGraph' :: Traversable t => t (BDD a) -> (Graph, t Int)
+ Data.DecisionDiagram.BDD: toGraph' :: Traversable t => t (BDD a) -> (Graph Sig, t Int)
- Data.DecisionDiagram.BDD: type Graph = IntMap Node
+ Data.DecisionDiagram.BDD: type Graph f = IntMap (f Int)
- Data.DecisionDiagram.ZDD: findMaxSum :: forall a w. (ItemOrder a, Num w, Ord w) => (Int -> w) -> ZDD a -> (w, IntSet)
+ Data.DecisionDiagram.ZDD: findMaxSum :: forall a w. (ItemOrder a, Num w, Ord w, HasCallStack) => (Int -> w) -> ZDD a -> (w, IntSet)
- Data.DecisionDiagram.ZDD: findMinSum :: forall a w. (ItemOrder a, Num w, Ord w) => (Int -> w) -> ZDD a -> (w, IntSet)
+ Data.DecisionDiagram.ZDD: findMinSum :: forall a w. (ItemOrder a, Num w, Ord w, HasCallStack) => (Int -> w) -> ZDD a -> (w, IntSet)
- Data.DecisionDiagram.ZDD: fold :: b -> b -> (Int -> b -> b -> b) -> ZDD a -> b
+ Data.DecisionDiagram.ZDD: fold :: (Int -> b -> b -> b) -> (Bool -> b) -> ZDD a -> b
- Data.DecisionDiagram.ZDD: fold' :: b -> b -> (Int -> b -> b -> b) -> ZDD a -> b
+ Data.DecisionDiagram.ZDD: fold' :: (Int -> b -> b -> b) -> (Bool -> b) -> ZDD a -> b
- Data.DecisionDiagram.ZDD: fromGraph :: (Graph, Int) -> ZDD a
+ Data.DecisionDiagram.ZDD: fromGraph :: HasCallStack => (Graph Sig, Int) -> ZDD a
- Data.DecisionDiagram.ZDD: fromGraph' :: Graph -> IntMap (ZDD a)
+ Data.DecisionDiagram.ZDD: fromGraph' :: HasCallStack => Graph Sig -> IntMap (ZDD a)
- Data.DecisionDiagram.ZDD: toGraph :: ZDD a -> (Graph, Int)
+ Data.DecisionDiagram.ZDD: toGraph :: ZDD a -> (Graph Sig, Int)
- Data.DecisionDiagram.ZDD: toGraph' :: Traversable t => t (ZDD a) -> (Graph, t Int)
+ Data.DecisionDiagram.ZDD: toGraph' :: Traversable t => t (ZDD a) -> (Graph Sig, t Int)
- Data.DecisionDiagram.ZDD: type Graph = IntMap Node
+ Data.DecisionDiagram.ZDD: type Graph f = IntMap (f Int)
- Data.DecisionDiagram.ZDD: uniformM :: forall a g m. (ItemOrder a, StatefulGen g m) => ZDD a -> g -> m IntSet
+ Data.DecisionDiagram.ZDD: uniformM :: forall a g m. (ItemOrder a, StatefulGen g m, HasCallStack) => ZDD a -> g -> m IntSet

Files

ChangeLog.md view
@@ -1,3 +1,45 @@-# Changelog for decision-diagrams+# Changelog for `decision-diagrams` package -## Unreleased changes+## 0.2.0.0++### Changes++* Make `Leaf :: Bool -> BDD a` as a basic constructor instead of+  `F`/`T` (in case of BDD) and `Empty`/`Base` (in case of ZDD), and+  remove `F`/`T`.++* `ZDD.toList` now returns sorted list++* Change signature of `fold` and `fold'` of BDD+  * Before: `b -> b -> (Int -> b -> b -> b) -> BDD a -> b`+  * After: `(Int -> b -> b -> b) -> (Bool -> b) -> BDD a -> b`++* Change signature of `fold` and `fold'` of ZDD (ditto)++* Add `HasCallStack` to some functions that are expected to raise excpetions++### Additions++* Introduce signature functor type (`Sig`)++* Add new operations:+  * BDD:+    * fixed point operators `lfp` and `gfp`+    * satisfiability related functions: `anySat`, `allSat`, `anySatComplete`, `allSatComplete`, `countSat`, `uniformSatM`+	* pseudo-boolean constraint functions: `pbAtLeast`, `pbAtMost`, `pbExactly`, `pbExactlyIntegral`+  * ZDD:+    * `combinations`+	* pseudo-boolean constraint functions: `subsetsAtLeast`, `subsetsAtMost`, `subsetsExactly`, `subsetsExactlyIntegral`+  * Both BDD and ZDD+    * `numNodes`+    * `unfoldHashable` and `unfoldOrd`++### Bug fixes++* Fix laziness of `fold` and `fold'`++### Other changes++* Introduced `doctest`++* Add other-extensions fields to `package.yaml`
README.md view
@@ -1,8 +1,31 @@ # decision-diagrams +Hackage:+[![Hackage](https://img.shields.io/hackage/v/decision-diagrams.svg)](https://hackage.haskell.org/package/decision-diagrams)+[![Hackage Deps](https://img.shields.io/hackage-deps/v/decision-diagrams.svg)](https://packdeps.haskellers.com/feed?needle=decision-diagrams)+[![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/decision-diagrams/badge)](https://matrix.hackage.haskell.org/#/package/decision-diagrams)++Dev: [![Build Status](https://github.com/msakai/haskell-decision-diagrams/actions/workflows/build.yaml/badge.svg)](https://github.com/msakai/haskell-decision-diagrams/actions/workflows/build.yaml) [![Coverage Status](https://coveralls.io/repos/msakai/haskell-decision-diagrams/badge.svg)](https://coveralls.io/r/msakai/haskell-decision-diagrams) -Binary Decision Diagrams (BDD) and Zero-suppressed Binary Decision Diagrams (ZDD) implementation in Haskell.+[Binary Decision Diagrams (BDD)](https://en.wikipedia.org/wiki/Binary_decision_diagram) and [Zero-suppressed Binary Decision Diagrams (ZDD)](https://en.wikipedia.org/wiki/Zero-suppressed_decision_diagram) implementation in Haskell. -Hash-consing is implemented using [intern](https://hackage.haskell.org/package/intern) package.+BDD is a data stucture suitable for representing boolean functions (can be thought as a compressed representation of truth tables) and many operations on boolean functions can be performed efficiently.  ZDD is a variant of BDD suitable for representing (sparse) families of sets compactly.++BDD/ZDD uses hash-consing for compact representation and efficient comparison, and we use [intern](https://hackage.haskell.org/package/intern) package for implementing hash-consing.++## Comparison with other BDD packages for Haskell++|Package name|Repository|BDD|ZDD|Style|Implementation|Hash-consing / Fast equality test|+|------------|----------|---|---|-----|--------------|---------------------------------|+|[decision-diagrams](https://hackage.haskell.org/package/decision-diagrams) (this package)|[GitHub](https://github.com/msakai/haskell-decision-diagrams/)|✔️|✔️|pure|pure Haskell|✔️|+|[zsdd](https://hackage.haskell.org/package/zsdd)|[GitHub](https://github.com/eddiejones2108/decision-diagrams) (deleted?)|✔️|✔️|monadic|pure Haskell|✔️|+|[obdd](https://hackage.haskell.org/package/obdd)|[GitHub](https://github.com/jwaldmann/haskell-obdd)|✔️|-|pure|pure Haskell|-|+|[HasCacBDD](https://hackage.haskell.org/package/HasCacBDD)|[GitHub](https://github.com/m4lvin/HasCacBDD)|✔️|-|pure|FFI|✔️|+|[hBDD](https://hackage.haskell.org/package/hBDD) ([hBDD-CUDD](https://hackage.haskell.org/package/hBDD-CUDD), [hBDD-CMUBDD](https://hackage.haskell.org/package/hBDD-CMUBDD))|[GitHub](https://github.com/peteg/hBDD)|✔️|-|pure|FFI|✔️|+|[cudd](https://hackage.haskell.org/package/cudd)|[GitHub](https://github.com/adamwalker/haskell_cudd)|✔️|-|both pure\*1 and monadic|FFI|✔️|++\*1: `cudd`'s pure interface is different from normal Haskell data types (like ones in the [containers](https://hackage.haskell.org/package/containers) package, for example) because it requires `DDManager` argument.++Please feel free to make a pull request for addition or correction to the comparision.
decision-diagrams.cabal view
@@ -5,10 +5,10 @@ -- see: https://github.com/sol/hpack  name:           decision-diagrams-version:        0.1.0.0+version:        0.2.0.0 synopsis:       Binary Decision Diagrams (BDD) and Zero-suppressed Binary Decision Diagrams (ZDD) description:    Please see the README on GitHub at <https://github.com/msakai/haskell-decision-diagrams#readme>-category:       Data, Logic+category:       Data, Data Structures, Logic homepage:       https://github.com/msakai/haskell-decision-diagrams#readme bug-reports:    https://github.com/msakai/haskell-decision-diagrams/issues author:         Masahiro Sakai@@ -34,19 +34,54 @@       Data.DecisionDiagram.BDD.Internal.Node   hs-source-dirs:       src+  other-extensions:+      BangPatterns+      FlexibleContexts+      FlexibleInstances+      RankNTypes+      ScopedTypeVariables+      CPP+      DeriveGeneric+      DeriveTraversable+      GeneralizedNewtypeDeriving+      PatternSynonyms+      TypeFamilies+      UndecidableInstances+      ViewPatterns   build-depends:-      base >=4.7 && <5+      base >=4.11.0.0 && <5     , containers >=0.5.11.0 && <0.7-    , hashable >=1.2.7.0 && <1.4-    , hashtables >=1.2.3.1 && <1.3+    , hashable >=1.2.7.0 && <1.5+    , hashtables >=1.2.3.1 && <1.4     , intern >=0.9.1.2 && <1.0.0.0     , mwc-random >=0.13.6.0 && <0.16     , primitive >=0.6.3.0 && <0.8     , random >=1.1 && <1.3     , reflection >=2.1.4 && <2.2     , unordered-containers >=0.2.9.0 && <0.3+    , vector >=0.12.0.2 && <0.13   default-language: Haskell2010 +test-suite decision-diagrams-doctest+  type: exitcode-stdio-1.0+  main-is: test/doctests.hs+  other-modules:+      Paths_decision_diagrams+  other-extensions:+      BangPatterns+      FlexibleContexts+      FlexibleInstances+      RankNTypes+      ScopedTypeVariables+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.11.0.0 && <5+    , containers >=0.5.11.0 && <0.7+    , decision-diagrams+    , doctest+    , mwc-random >=0.13.6.0 && <0.16+  default-language: Haskell2010+ test-suite decision-diagrams-test   type: exitcode-stdio-1.0   main-is: TestSuite.hs@@ -57,16 +92,26 @@       Paths_decision_diagrams   hs-source-dirs:       test+  other-extensions:+      BangPatterns+      FlexibleContexts+      FlexibleInstances+      RankNTypes+      ScopedTypeVariables+      TemplateHaskell   ghc-options: -threaded -rtsopts -with-rtsopts=-N   build-depends:       QuickCheck >=2.11.3 && <2.15-    , base >=4.7 && <5+    , base >=4.11.0.0 && <5     , containers >=0.5.11.0 && <0.7     , decision-diagrams+    , deepseq >=1.4.3.0 && <1.5     , mwc-random >=0.13.6.0 && <0.16+    , quickcheck-instances >=0.3.19 && <0.4     , statistics >=0.14.0.2 && <0.16     , tasty >=1.1.0.4 && <1.5     , tasty-hunit >=0.10.0.1 && <0.11     , tasty-quickcheck ==0.10.*     , tasty-th >=0.1.7 && <0.2+    , vector >=0.12.0.2 && <0.13   default-language: Haskell2010
src/Data/DecisionDiagram/BDD.hs view
@@ -1,5 +1,6 @@ {-# OPTIONS_GHC -Wall #-} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-}@@ -28,7 +29,7 @@ module Data.DecisionDiagram.BDD   (   -- * The BDD type-    BDD (F, T, Branch)+    BDD (Leaf, Branch)    -- * Item ordering   , ItemOrder (..)@@ -53,6 +54,12 @@   , andB   , orB +  -- * Pseudo-boolean constraints+  , pbAtLeast+  , pbAtMost+  , pbExactly+  , pbExactlyIntegral+   -- * Quantification   , forAll   , exists@@ -64,6 +71,7 @@   -- * Query   , support   , evaluate+  , numNodes    -- * Restriction / Cofactor   , restrict@@ -74,13 +82,33 @@   , subst   , substSet +  -- * Satisfiability+  , anySat+  , allSat+  , anySatComplete+  , allSatComplete+  , countSat+  , uniformSatM++  -- * (Co)algebraic structure+  , Sig (..)+  , inSig+  , outSig+   -- * Fold   , fold   , fold' +  -- * Unfold+  , unfoldHashable+  , unfoldOrd++  -- * Fixpoints+  , lfp+  , gfp+   -- * Conversion from/to graphs   , Graph-  , Node (..)   , toGraph   , toGraph'   , fromGraph@@ -89,10 +117,16 @@  import Control.Exception (assert) import Control.Monad+#if !MIN_VERSION_mwc_random(0,15,0)+import Control.Monad.Primitive+#endif import Control.Monad.ST+import Control.Monad.ST.Unsafe+import Data.Bits (Bits (shiftL))+import qualified Data.Foldable as Foldable import Data.Function (on)-import Data.Functor.Identity import Data.Hashable+import qualified Data.HashMap.Lazy as HashMap import qualified Data.HashTable.Class as H import qualified Data.HashTable.ST.Cuckoo as C import Data.IntMap (IntMap)@@ -100,11 +134,24 @@ import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet import Data.List (sortBy)+import Data.Map.Lazy (Map)+import qualified Data.Map.Lazy as Map import Data.Proxy-import Data.STRef+import Data.Ratio+import qualified Data.Vector as V+import GHC.Stack+import Numeric.Natural+#if MIN_VERSION_mwc_random(0,15,0)+import System.Random.MWC (Uniform (..))+import System.Random.Stateful (StatefulGen (..))+#else+import System.Random.MWC (Gen, Variate (..))+#endif+import System.Random.MWC.Distributions (bernoulli) import Text.Read  import Data.DecisionDiagram.BDD.Internal.ItemOrder+import Data.DecisionDiagram.BDD.Internal.Node (Sig (..), Graph) import qualified Data.DecisionDiagram.BDD.Internal.Node as Node  infixr 3 .&&.@@ -124,11 +171,14 @@   deriving (Eq, Hashable)  pattern F :: BDD a-pattern F = BDD Node.F+pattern F = Leaf False  pattern T :: BDD a-pattern T = BDD Node.T+pattern T = Leaf True +pattern Leaf :: Bool -> BDD a+pattern Leaf b = BDD (Node.Leaf b)+ -- | Smart constructor that takes the BDD reduction rules into account pattern Branch :: Int -> BDD a -> BDD a -> BDD a pattern Branch x lo hi <- BDD (Node.Branch x (BDD -> lo) (BDD -> hi)) where@@ -137,6 +187,7 @@     | otherwise = BDD (Node.Branch x lo hi)  {-# COMPLETE T, F, Branch #-}+{-# COMPLETE Leaf, Branch #-}  nodeId :: BDD a -> Int nodeId (BDD node) = Node.nodeId node@@ -155,15 +206,11 @@     EQ -> BDDCase2EQ ptop p0 p1 q0 q1 bddCase2 _ (Branch ptop p0 p1) _ = BDDCase2LT ptop p0 p1 bddCase2 _ _ (Branch qtop q0 q1) = BDDCase2GT qtop q0 q1-bddCase2 _ T T = BDDCase2EQ2 True True-bddCase2 _ T F = BDDCase2EQ2 True False-bddCase2 _ F T = BDDCase2EQ2 False True-bddCase2 _ F F = BDDCase2EQ2 False False+bddCase2 _ (Leaf b1) (Leaf b2) = BDDCase2EQ2 b1 b2  level :: BDD a -> Level a-level T = Terminal-level F = Terminal level (Branch x _ _) = NonTerminal x+level (Leaf _) = Terminal  -- ------------------------------------------------------------------------ @@ -197,8 +244,7 @@ notB :: BDD a -> BDD a notB bdd = runST $ do   h <- C.newSized defaultTableSize-  let f T = return F-      f F = return T+  let f (Leaf b) = return (Leaf (not b))       f n@(Branch ind lo hi) = do         m <- H.lookup h n         case m of@@ -293,10 +339,7 @@ (.<=>.) :: forall a. ItemOrder a => BDD a -> BDD a -> BDD a (.<=>.) = apply True f   where-    f T T = Just T-    f T F = Just F-    f F T = Just F-    f F F = Just T+    f (Leaf b1) (Leaf b2) = Just (Leaf (b1 == b2))     f a b | a == b = Just T     f _ _ = Nothing @@ -304,8 +347,7 @@ ite :: forall a. ItemOrder a => BDD a -> BDD a -> BDD a -> BDD a ite c' t' e' = runST $ do   h <- C.newSized defaultTableSize-  let f T t _ = return t-      f F _ e = return e+  let f (Leaf b) t e = if b then return t else return e       f _ t e | t == e = return t       f c t e = do         case minimum [level c, level t, level e] of@@ -338,6 +380,88 @@  -- ------------------------------------------------------------------------ +-- | Pseudo-boolean constraint, /w1*x1 + w2*x2 + … ≥ k/.+pbAtLeast :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> BDD a+pbAtLeast xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (k <= ub) = SLeaf False+      | i == V.length xs' && 0 >= k = SLeaf True+      | lb >= k = SLeaf True -- all remaining variables are don't-care+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i++-- | Pseudo-boolean constraint, /w1*x1 + w2*x2 + … ≤ k/.+pbAtMost :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> BDD a+pbAtMost xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (lb <= k) = SLeaf False+      | i == V.length xs' && 0 <= k = SLeaf True+      | ub <= k = SLeaf True -- all remaining variables are don't-care+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i++-- | Pseudo-boolean constraint, /w1*x1 + w2*x2 + … = k/.+--+-- If weight type is 'Integral', 'pbExactlyIntegral' is more efficient.+pbExactly :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> BDD a+pbExactly xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (lb <= k && k <= ub) = SLeaf False+      | i == V.length xs' && 0 == k = SLeaf True+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i++-- | Similar to 'pbExactly' but more efficient.+pbExactlyIntegral :: forall a w. (ItemOrder a, Real w, Integral w) => IntMap w -> w -> BDD a+pbExactlyIntegral xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'+    ds :: V.Vector w+    ds = V.scanr1 (\w d -> if w /= 0 then gcd w d else d) (V.map snd xs')++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (lb <= k && k <= ub) = SLeaf False+      | i == V.length xs' && 0 == k = SLeaf True+      | d /= 0 && k `mod` d /= 0 = SLeaf False+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i+        d = ds V.! i++-- ------------------------------------------------------------------------+ -- | Universal quantification (∀) forAll :: forall a. ItemOrder a => Int -> BDD a -> BDD a forAll x bdd = runST $ do@@ -465,48 +589,59 @@  -- | Fold over the graph structure of the BDD. ----- It takes values for substituting 'false' ('F') and 'true' ('T'),--- and a function for substiting non-terminal node ('Branch').-fold :: b -> b -> (Int -> b -> b -> b) -> BDD a -> b-fold ff tt br bdd = runST $ do-  h <- C.newSized defaultTableSize-  let f F = return ff-      f T = return tt-      f p@(Branch top lo hi) = do-        m <- H.lookup h p-        case m of-          Just ret -> return ret-          Nothing -> do-            r0 <- f lo-            r1 <- f hi-            let ret = br top r0 r1-            H.insert h p ret-            return ret-  f bdd+-- It takes two functions that substitute 'Branch'  and 'Leaf' respectively.+--+-- Note that its type is isomorphic to @('Sig' b -> b) -> BDD a -> b@.+fold :: (Int -> b -> b -> b) -> (Bool -> b) -> BDD a -> b+fold br lf (BDD node) = Node.fold br lf node  -- | Strict version of 'fold'-fold' :: b -> b -> (Int -> b -> b -> b) -> BDD a -> b-fold' ff tt br bdd = runST $ do-  op <- mkFold'Op ff tt br-  op bdd+fold' :: (Int -> b -> b -> b) -> (Bool -> b) -> BDD a -> b+fold' br lf (BDD node) = Node.fold' br lf node -mkFold'Op :: b -> b -> (Int -> b -> b -> b) -> ST s (BDD a -> ST s b)-mkFold'Op !ff !tt br = do+mkFold'Op :: (Int -> b -> b -> b) -> (Bool -> b) -> ST s (BDD a -> ST s b)+mkFold'Op br lf = do+  op <- Node.mkFold'Op br lf+  return $ \(BDD node) -> op node++-- ------------------------------------------------------------------------++-- | Top-down construction of BDD, memoising internal states using 'Hashable' instance.+unfoldHashable :: forall a b. (ItemOrder a, Eq b, Hashable b) => (b -> Sig b) -> b -> BDD a+unfoldHashable f b = runST $ do   h <- C.newSized defaultTableSize-  let f F = return ff-      f T = return tt-      f p@(Branch top lo hi) = do-        m <- H.lookup h p-        case m of-          Just ret -> return ret+  let g [] = return ()+      g (x : xs) = do+        r <- H.lookup h x+        case r of+          Just _ -> g xs           Nothing -> do-            r0 <- f lo-            r1 <- f hi-            let ret = br top r0 r1-            seq ret $ H.insert h p ret-            return ret-  return f+            let fx = f x+            H.insert h x fx+            g (xs ++ Foldable.toList fx)+  g [b]+  xs <- H.toList h+  let h2 = HashMap.fromList [(x, inSig (fmap (h2 HashMap.!) s)) | (x,s) <- xs]+  return $ h2 HashMap.! b +-- | Top-down construction of BDD, memoising internal states using 'Ord' instance.+unfoldOrd :: forall a b. (ItemOrder a, Ord b) => (b -> Sig b) -> b -> BDD a+unfoldOrd f b = m2 Map.! b+  where+    m1 :: Map b (Sig b)+    m1 = g Map.empty [b]++    m2 :: Map b (BDD a)+    m2 = Map.map (inSig . fmap (m2 Map.!)) m1++    g m [] = m+    g m (x : xs) =+      case Map.lookup x m of+        Just _ -> g m xs+        Nothing ->+          let fx = f x+           in g (Map.insert x fx m) (xs ++ Foldable.toList fx)+ -- ------------------------------------------------------------------------  -- | All the variables that this BDD depends on.@@ -516,9 +651,10 @@   op bdd  mkSupportOp :: ST s (BDD a -> ST s IntSet)-mkSupportOp = mkFold'Op IntSet.empty IntSet.empty f+mkSupportOp = mkFold'Op f g   where     f x lo hi = IntSet.insert x (lo `IntSet.union` hi)+    g _ = IntSet.empty  -- | Evaluate a boolean function represented as BDD under the valuation -- given by @(Int -> Bool)@, i.e. it lifts a valuation function from@@ -526,20 +662,31 @@ evaluate :: (Int -> Bool) -> BDD a -> Bool evaluate f = g   where-    g F = False-    g T = True+    g (Leaf b) = b     g (Branch x lo hi)       | f x = g hi       | otherwise = g lo +-- | Count the number of nodes in a BDD viewed as a rooted directed acyclic graph.+--+-- See also 'toGraph'.+numNodes :: BDD a -> Int+numNodes (BDD node) = Node.numNodes node+ -- ------------------------------------------------------------------------ --- | Compute \(F_x \) or \(F_{\neg x} \).+-- | Compute \(F|_{x_i} \) or \(F|_{\neg x_i} \).+--+-- \[+-- F|_{x_i}(\ldots, x_{i-1}, x_{i+1}, \ldots) = F(\ldots, x_{i-1}, \mathrm{True}, x_{i+1}, \ldots)+-- \]+-- \[+-- F|_{\neg x_i}(\ldots, x_{i-1}, x_{i+1}, \ldots) = F(\ldots, x_{i-1}, \mathrm{False}, x_{i+1}, \ldots)+-- \] restrict :: forall a. ItemOrder a => Int -> Bool -> BDD a -> BDD a restrict x val bdd = runST $ do   h <- C.newSized defaultTableSize-  let f T = return T-      f F = return F+  let f n@(Leaf _) = return n       f n@(Branch ind lo hi) = do         m <- H.lookup h n         case m of@@ -553,13 +700,12 @@             return ret   f bdd --- | Compute \(F_{\{x_i = v_i\}_i} \).+-- | Compute \(F|_{\{x_i = v_i\}_i} \). restrictSet :: forall a. ItemOrder a => IntMap Bool -> BDD a -> BDD a restrictSet val bdd = runST $ do   h <- C.newSized defaultTableSize   let f [] n = return n-      f _ T = return T-      f _ F = return F+      f _ n@(Leaf _) = return n       f xxs@((x,v) : xs) n@(Branch ind lo hi) = do         m <- H.lookup h n         case m of@@ -581,8 +727,7 @@   h <- C.newSized defaultTableSize   let f T n = return n       f F _ = return T  -- Is this correct?-      f _ F = return F-      f _ T = return T+      f _ n@(Leaf _) = return n       f n1 n2 | n1 == n2 = return T       f n1 n2 = do         m <- H.lookup h (n1, n2)@@ -606,8 +751,9 @@  -- ------------------------------------------------------------------------ --- | @subst x N M@ computes substitution \(M_{x = N}\).+-- | @subst x N M@ computes substitution M[x ↦ N]. --+-- Note the order of the arguments. -- This operation is also known as /Composition/. subst :: forall a. ItemOrder a => Int -> BDD a -> BDD a -> BDD a subst x n m = runST $ do@@ -643,7 +789,9 @@                 return ret   f m m n --- | Simultaneous substitution+-- | Simultaneous substitution.+--+-- Note that this is not the same as repeated application of 'subst'. substSet :: forall a. ItemOrder a => IntMap (BDD a) -> BDD a -> BDD a substSet s m = runST $ do   supportOp <- mkSupportOp@@ -705,87 +853,220 @@         fixed = IntMap.mapMaybe asBool conditions      asBool :: BDD a -> Maybe Bool-    asBool a =-      case a of-        T -> Just True-        F -> Just False-        _ -> Nothing+    asBool (Leaf b) = Just b+    asBool _ = Nothing  -- ------------------------------------------------------------------------ -type Graph = IntMap Node+-- | Least fixed point.+--+-- Monotonicity of the operator is assumed but not checked.+lfp :: ItemOrder a => (BDD a ->  BDD a) -> BDD a+lfp f = go false+  where+    go curr+      | curr == next = curr+      | otherwise = go next+      where+        next = f curr -data Node-  = NodeF-  | NodeT-  | NodeBranch !Int Int Int-  deriving (Eq, Show, Read)+-- | Greatest fixed point.+--+-- Monotonicity of the operator is assumed but not checked.+gfp :: ItemOrder a => (BDD a ->  BDD a) -> BDD a+gfp f = go true+  where+    go curr+      | curr == next = curr+      | otherwise = go next+      where+        next = f curr --- | Convert a BDD into a pointed graph-toGraph :: BDD a -> (Graph, Int)-toGraph bdd =-  case toGraph' (Identity bdd) of-    (g, Identity v) -> (g, v)+-- ------------------------------------------------------------------------ --- | Convert multiple BDDs into a graph-toGraph' :: Traversable t => t (BDD a) -> (Graph, t Int)-toGraph' bs = runST $ do+findSatM :: MonadPlus m => BDD a -> m (IntMap Bool)+findSatM = fold f g+  where+    f x lo hi = mplus (liftM (IntMap.insert x False) lo) (liftM (IntMap.insert x True) hi)+    g b = if b then return IntMap.empty else mzero++-- | Find one satisfying partial assignment+anySat :: BDD a -> Maybe (IntMap Bool)+anySat = findSatM++-- | Enumerate all satisfying partial assignments+allSat :: BDD a -> [IntMap Bool]+allSat = findSatM++findSatCompleteM :: forall a m. (MonadPlus m, ItemOrder a, HasCallStack) => IntSet -> BDD a -> m (IntMap Bool)+findSatCompleteM xs0 bdd = runST $ do   h <- C.newSized defaultTableSize-  H.insert h F 0-  H.insert h T 1-  counter <- newSTRef 2-  ref <- newSTRef $ IntMap.fromList [(0, NodeF), (1, NodeT)]+  let f _ (Leaf False) = return $ mzero+      f xs (Leaf True) = return $ foldM (\m x -> msum [return (IntMap.insert x v m) | v <- [False, True]]) IntMap.empty xs+      f xs n@(Branch x lo hi) = do+        case span (\x2 -> compareItem (Proxy :: Proxy a) x2 x == LT) xs of+          (ys, (x':xs')) | x == x' -> do+            r <- H.lookup h n+            ps <- case r of+              Just ret -> return ret+              Nothing -> do+                r0 <- f xs' lo+                r1 <- unsafeInterleaveST $ f xs' hi+                let ret = liftM (IntMap.insert x False) r0 `mplus` liftM (IntMap.insert x True) r1+                H.insert h n ret+                return ret+            return $ do+              p <- ps+              foldM (\m y -> msum [return (IntMap.insert y v m) | v <- [False, True]]) p ys+          _ -> error ("findSatCompleteM: " ++ show x ++ " should not occur")+  f (sortBy (compareItem (Proxy :: Proxy a)) (IntSet.toList xs0)) bdd -  let f F = return 0-      f T = return 1-      f p@(Branch x lo hi) = do-        m <- H.lookup h p-        case m of-          Just ret -> return ret-          Nothing -> do-            r0 <- f lo-            r1 <- f hi-            n <- readSTRef counter-            writeSTRef counter $! n+1-            H.insert h p n-            modifySTRef' ref (IntMap.insert n (NodeBranch x r0 r1))-            return n+-- | Find one satisfying (complete) assignment over a given set of variables+--+-- The set of variables must be a superset of 'support'.+anySatComplete :: ItemOrder a => IntSet -> BDD a -> Maybe (IntMap Bool)+anySatComplete = findSatCompleteM -  vs <- mapM f bs-  g <- readSTRef ref-  return (g, vs)+-- | Enumerate all satisfying (complete) assignment over a given set of variables+--+-- The set of variables must be a superset of 'support'.+allSatComplete :: ItemOrder a => IntSet -> BDD a -> [IntMap Bool]+allSatComplete = findSatCompleteM --- | Convert a pointed graph into a BDD-fromGraph :: (Graph, Int) -> BDD a-fromGraph (g, v) =-  case IntMap.lookup v (fromGraph' g) of-    Nothing -> error ("Data.DecisionDiagram.BDD.fromGraph: invalid node id " ++ show v)-    Just bdd -> bdd+{-# SPECIALIZE countSat :: ItemOrder a => IntSet -> BDD a -> Int #-}+{-# SPECIALIZE countSat :: ItemOrder a => IntSet -> BDD a -> Integer #-}+{-# SPECIALIZE countSat :: ItemOrder a => IntSet -> BDD a -> Natural #-}+-- | Count the number of satisfying (complete) assignment over a given set of variables.+--+-- The set of variables must be a superset of 'support'.+--+-- It is polymorphic in return type, but it is recommended to use 'Integer' or 'Natural'+-- because the size can be larger than fixed integer types such as @Int64@.+--+-- >>> countSat (IntSet.fromList [1..128]) (true :: BDD AscOrder)+-- 340282366920938463463374607431768211456+-- >>> import Data.Int+-- >>> maxBound :: Int64+-- 9223372036854775807+countSat :: forall a b. (ItemOrder a, Num b, Bits b, HasCallStack) => IntSet -> BDD a -> b+countSat xs bdd = runST $ do+  h <- C.newSized defaultTableSize+  let f _ (Leaf False) = return $ 0+      f ys (Leaf True) = return $! 1 `shiftL` length ys+      f ys node@(Branch x lo hi) = do+        case span (\x2 -> compareItem (Proxy :: Proxy a) x2 x == LT) ys of+          (zs, y' : ys') | x == y' -> do+            m <- H.lookup h node+            n <- case m of+              Just n -> return n+              Nothing -> do+                n <- liftM2 (+) (f ys' lo) (f ys' hi)+                H.insert h node n+                return n+            return $! n `shiftL` length zs+          (_, _) -> error ("countSat: " ++ show x ++ " should not occur")+  f (sortBy (compareItem (Proxy :: Proxy a)) (IntSet.toList xs)) bdd --- | Convert nodes of a graph into BDDs-fromGraph' :: Graph -> IntMap (BDD a)-fromGraph' g = ret+-- | Sample an assignment from uniform distribution over complete satisfiable assignments ('allSatComplete') of the BDD.+--+-- The function constructs a table internally and the table is shared across+-- multiple use of the resulting action (@m IntSet@).+-- Therefore, the code+--+-- @+-- let g = uniformSatM xs bdd gen+-- s1 <- g+-- s2 <- g+-- @+--+-- is more efficient than+--+-- @+-- s1 <- uniformSatM xs bdd gen+-- s2 <- uniformSatM xs bdd gen+-- @+-- .+#if MIN_VERSION_mwc_random(0,15,0)+uniformSatM :: forall a g m. (ItemOrder a, StatefulGen g m, HasCallStack) => IntSet -> BDD a -> g -> m (IntMap Bool)+#else+uniformSatM :: forall a m. (ItemOrder a, PrimMonad m, HasCallStack) => IntSet -> BDD a -> Gen (PrimState m) -> m (IntMap Bool)+#endif+uniformSatM xs0 bdd0 = func IntMap.empty   where-    ret = IntMap.map f g-    f NodeF = F-    f NodeT = T-    f (NodeBranch x lo hi) =-      case (IntMap.lookup lo ret, IntMap.lookup hi ret) of-        (Nothing, _) -> error ("Data.DecisionDiagram.BDD.fromGraph': invalid node id " ++ show lo)-        (_, Nothing) -> error ("Data.DecisionDiagram.BDD.fromGraph': invalid node id " ++ show hi)-        (Just lo', Just hi') -> Branch x lo' hi'+    func = runST $ do+      h <- C.newSized defaultTableSize+      let f xs bdd =+            case span (\x2 -> NonTerminal x2 < level bdd) xs of+              (ys, xxs') -> do+                xs' <- case (bdd, xxs') of+                         (Branch x _ _, x' : xs') | x == x' -> return xs'+                         (Branch x _ _, _) -> error ("uniformSatM: " ++ show x ++ " should not occur")+                         (Leaf _, []) -> return []+                         (Leaf _, _:_) -> error ("uniformSatM: should not happen")+                (s, func0) <- g xs' bdd+                let func' !m !gen = do+#if MIN_VERSION_mwc_random(0,15,0)+                      vals <- replicateM (length ys) (uniformM gen)+#else+                      vals <- replicateM (length ys) (uniform gen)+#endif+                      func0 (m `IntMap.union` IntMap.fromList (zip ys vals)) gen+                return (s `shiftL` length ys, func')+          g _ (Leaf True) = return (1 :: Integer, \a _gen -> return a)+          g _ (Leaf False) = return (0 :: Integer, \_a _gen -> error "uniformSatM: should not happen")+          g xs bdd@(Branch x lo hi) = do+            m <- H.lookup h bdd+            case m of+              Just ret -> return ret+              Nothing -> do+                (n0, func0) <- f xs lo+                (n1, func1) <- f xs hi+                let s = n0 + n1+                    r :: Double+                    r = realToFrac (n1 % s)+                seq r $ return ()+                let func' !a !gen = do+                      b <- bernoulli r gen+                      if b then+                        func1 (IntMap.insert x True a) gen+                      else+                        func0 (IntMap.insert x False a) gen+                H.insert h bdd (s, func')+                return (s, func')+      liftM snd $ f (sortBy (compareItem (Proxy :: Proxy a)) (IntSet.toList xs0)) bdd0  -- ------------------------------------------------------------------------ --- https://ja.wikipedia.org/wiki/%E4%BA%8C%E5%88%86%E6%B1%BA%E5%AE%9A%E5%9B%B3-_test_bdd :: BDD AscOrder-_test_bdd = (notB x1 .&&. notB x2 .&&. notB x3) .||. (x1 .&&. x2) .||. (x2 .&&. x3)-  where-    x1 = var 1-    x2 = var 2-    x3 = var 3-{--BDD (Node 880 (UBranch 1 (Node 611 (UBranch 2 (Node 836 UT) (Node 215 UF))) (Node 806 (UBranch 2 (Node 842 (UBranch 3 (Node 836 UT) (Node 215 UF))) (Node 464 (UBranch 3 (Node 215 UF) (Node 836 UT)))))))--}+-- | 'Sig'-algebra stucture of 'BDD', \(\mathrm{in}_\mathrm{Sig}\).+inSig :: Sig (BDD a) -> BDD a+inSig (SLeaf b) = Leaf b+inSig (SBranch x lo hi) = Branch x lo hi++-- | 'Sig'-coalgebra stucture of 'BDD', \(\mathrm{out}_\mathrm{Sig}\).+outSig :: BDD a -> Sig (BDD a)+outSig (Leaf b) = SLeaf b+outSig (Branch x lo hi) = SBranch x lo hi++-- ------------------------------------------------------------------------++-- | Convert a BDD into a pointed graph+--+-- Nodes @0@ and @1@ are reserved for @SLeaf False@ and @SLeaf True@+-- even if they are not actually used. Therefore the result may be+-- larger than 'numNodes' if the leaf nodes are not used.+toGraph :: BDD a -> (Graph Sig, Int)+toGraph (BDD node) = Node.toGraph node++-- | Convert multiple BDDs into a graph+toGraph' :: Traversable t => t (BDD a) -> (Graph Sig, t Int)+toGraph' bs = Node.toGraph' (fmap (\(BDD node) -> node) bs)++-- | Convert a pointed graph into a BDD+fromGraph :: HasCallStack => (Graph Sig, Int) -> BDD a+fromGraph = Node.foldGraph inSig++-- | Convert nodes of a graph into BDDs+fromGraph' :: HasCallStack => Graph Sig -> IntMap (BDD a)+fromGraph' = Node.foldGraphNodes inSig  -- ------------------------------------------------------------------------
src/Data/DecisionDiagram/BDD/Internal/ItemOrder.hs view
@@ -25,6 +25,9 @@   , withDescOrder   , withCustomOrder +  -- * Ordered item+  , OrderedItem (..)+   -- * Level   , Level (..)   ) where@@ -64,6 +67,14 @@  withCustomOrder :: forall r. (Int -> Int -> Ordering) -> (forall a. ItemOrder a => Proxy a -> r) -> r withCustomOrder cmp k = reify cmp (\(_ :: Proxy s) -> k (Proxy :: Proxy (CustomOrder s)))++-- ------------------------------------------------------------------------++newtype OrderedItem a = OrderedItem Int+  deriving (Eq, Show)++instance ItemOrder a => Ord (OrderedItem a) where+  compare (OrderedItem x) (OrderedItem y) = compareItem (Proxy :: Proxy a) x y  -- ------------------------------------------------------------------------ 
src/Data/DecisionDiagram/BDD/Internal/Node.hs view
@@ -1,5 +1,6 @@ {-# OPTIONS_GHC -Wall #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PatternSynonyms #-}@@ -22,13 +23,40 @@ module Data.DecisionDiagram.BDD.Internal.Node   (   -- * Low level node type-    Node (T, F, Branch)+    Node (Leaf, Branch)   , nodeId++  , numNodes++  -- * Fold+  , fold+  , fold'+  , mkFold'Op++  -- * (Co)algebraic structure+  , Sig (..)++  -- * Graph+  , Graph+  , toGraph+  , toGraph'+  , foldGraph+  , foldGraphNodes   ) where +import Control.Monad+import Control.Monad.ST+import Control.Monad.ST.Unsafe+import Data.Functor.Identity import Data.Hashable+import qualified Data.HashTable.Class as H+import qualified Data.HashTable.ST.Cuckoo as C import Data.Interned+import Data.IntMap.Lazy (IntMap)+import qualified Data.IntMap.Lazy as IntMap+import Data.STRef import GHC.Generics+import GHC.Stack  -- ------------------------------------------------------------------------ @@ -50,11 +78,22 @@ pattern F <- (unintern -> UF) where   F = intern UF +pattern Leaf :: Bool -> Node+pattern Leaf b <- (asBool -> Just b) where+  Leaf True = T+  Leaf False = F++asBool :: Node -> Maybe Bool+asBool T = Just True+asBool F = Just False+asBool _ = Nothing+ pattern Branch :: Int -> Node -> Node -> Node pattern Branch ind lo hi <- (unintern -> UBranch ind lo hi) where   Branch ind lo hi = intern (UBranch ind lo hi)  {-# COMPLETE T, F, Branch #-}+{-# COMPLETE Leaf, Branch #-}  data UNode   = UT@@ -86,5 +125,143 @@  nodeId :: Node -> Id nodeId (Node id_ _) = id_++-- ------------------------------------------------------------------------++defaultTableSize :: Int+defaultTableSize = 256++-- | Counts the number of nodes when viewed as a rooted directed acyclic graph+numNodes :: Node -> Int+numNodes node0 = runST $ do+  h <- C.newSized defaultTableSize+  let f node = do+        m <- H.lookup h node+        case m of+          Just _ -> return ()+          Nothing -> do+            H.insert h node ()+            case node of+              Branch _ lo hi -> f lo >> f hi+              _ -> return ()+  f node0+  liftM length $ H.toList h++-- ------------------------------------------------------------------------++-- | Signature functor of binary decision trees, BDD, and ZDD.+data Sig a+  = SLeaf !Bool+  | SBranch !Int a a+  deriving (Eq, Ord, Show, Read, Generic, Functor, Foldable, Traversable)++instance Hashable a => Hashable (Sig a)++-- ------------------------------------------------------------------------++-- | Fold over the graph structure of Node.+--+-- It takes two functions that substitute 'Branch'  and 'Leaf' respectively.+--+-- Note that its type is isomorphic to @('Sig' a -> a) -> 'Node' -> a@.+fold :: (Int -> a -> a -> a) -> (Bool -> a) -> Node -> a+fold br lf bdd = runST $ do+  h <- C.newSized defaultTableSize+  let f (Leaf b) = return (lf b)+      f p@(Branch top lo hi) = do+        m <- H.lookup h p+        case m of+          Just ret -> return ret+          Nothing -> do+            r0 <- unsafeInterleaveST $ f lo+            r1 <- unsafeInterleaveST $ f hi+            let ret = br top r0 r1+            H.insert h p ret  -- Note that H.insert is value-strict+            return ret+  f bdd++-- | Strict version of 'fold'+fold' :: (Int -> a -> a -> a) -> (Bool -> a) -> Node -> a+fold' br lf bdd = runST $ do+  op <- mkFold'Op br lf+  op bdd++mkFold'Op :: (Int -> a -> a -> a) -> (Bool -> a) -> ST s (Node -> ST s a)+mkFold'Op br lf = do+  h <- C.newSized defaultTableSize+  let f (Leaf b) = return $! lf b+      f p@(Branch top lo hi) = do+        m <- H.lookup h p+        case m of+          Just ret -> return ret+          Nothing -> do+            r0 <- f lo+            r1 <- f hi+            let ret = br top r0 r1+            H.insert h p ret  -- Note that H.insert is value-strict+            return ret+  return f++-- ------------------------------------------------------------------------++-- | Graph where nodes are decorated using a functor @f@.+--+-- The occurrences of the parameter of @f@ represent out-going edges.+type Graph f = IntMap (f Int)++-- | Convert a node into a pointed graph+--+-- Nodes @0@ and @1@ are reserved for @SLeaf False@ and @SLeaf True@ even if+-- they are not actually used. Therefore the result may be larger than+-- 'numNodes' if the leaf nodes are not used.+toGraph :: Node -> (Graph Sig, Int)+toGraph bdd =+  case toGraph' (Identity bdd) of+    (g, Identity v) -> (g, v)++-- | Convert multiple nodes into a graph+toGraph' :: Traversable t => t Node -> (Graph Sig, t Int)+toGraph' bs = runST $ do+  h <- C.newSized defaultTableSize+  H.insert h (Leaf False) 0+  H.insert h (Leaf True) 1+  counter <- newSTRef 2+  ref <- newSTRef $ IntMap.fromList [(0, SLeaf False), (1, SLeaf True)]++  let f (Leaf False) = return 0+      f (Leaf True) = return 1+      f p@(Branch x lo hi) = do+        m <- H.lookup h p+        case m of+          Just ret -> return ret+          Nothing -> do+            r0 <- f lo+            r1 <- f hi+            n <- readSTRef counter+            writeSTRef counter $! n+1+            H.insert h p n+            modifySTRef' ref (IntMap.insert n (SBranch x r0 r1))+            return n++  vs <- mapM f bs+  g <- readSTRef ref+  return (g, vs)++-- | Fold over pointed graph+foldGraph :: (Functor f, HasCallStack) => (f a -> a) -> (Graph f, Int) -> a+foldGraph f (g, v) =+  case IntMap.lookup v (foldGraphNodes f g) of+    Just x -> x+    Nothing -> error ("foldGraphNodes: invalid node id " ++ show v)++-- | Fold over graph nodes+foldGraphNodes :: (Functor f, HasCallStack) => (f a -> a) -> Graph f -> IntMap a+foldGraphNodes f m = ret+  where+    ret = IntMap.map (f . fmap h) m+    h v =+      case IntMap.lookup v ret of+        Just x -> x+        Nothing -> error ("foldGraphNodes: invalid node id " ++ show v)  -- ------------------------------------------------------------------------
src/Data/DecisionDiagram/ZDD.hs view
@@ -30,7 +30,9 @@ module Data.DecisionDiagram.ZDD   (   -- * ZDD type-    ZDD (Empty, Base, Branch)+    ZDD (Leaf, Branch)+  , pattern Empty+  , pattern Base    -- * Item ordering   , ItemOrder (..)@@ -46,9 +48,16 @@   , base   , singleton   , subsets+  , combinations   , fromListOfIntSets   , fromSetOfIntSets +  -- ** Pseudo-boolean constraints+  , subsetsAtLeast+  , subsetsAtMost+  , subsetsExactly+  , subsetsExactlyIntegral+   -- * Insertion   , insert @@ -63,6 +72,7 @@   , isSubsetOf   , isProperSubsetOf   , disjoint+  , numNodes    -- * Combine   , union@@ -81,10 +91,21 @@   , mapDelete   , change +  -- * (Co)algebraic structure+  , Sig (..)+  , pattern SEmpty+  , pattern SBase+  , inSig+  , outSig+   -- * Fold   , fold   , fold' +  -- * Unfold+  , unfoldHashable+  , unfoldOrd+   -- * Minimal hitting sets   , minimalHittingSets   , minimalHittingSetsToda@@ -107,7 +128,6 @@    -- ** Conversion from/to graphs   , Graph-  , Node (..)   , toGraph   , toGraph'   , fromGraph@@ -121,7 +141,8 @@ import Control.Monad.Primitive #endif import Control.Monad.ST-import Data.Functor.Identity+import qualified Data.Foldable as Foldable+import Data.Function (on) import Data.Hashable import Data.HashMap.Lazy (HashMap) import qualified Data.HashMap.Lazy as HashMap@@ -132,13 +153,16 @@ import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet import Data.List (foldl', sortBy)+import Data.Map.Lazy (Map)+import qualified Data.Map.Lazy as Map import Data.Maybe import Data.Proxy import Data.Ratio import Data.Set (Set) import qualified Data.Set as Set-import Data.STRef+import qualified Data.Vector as V import qualified GHC.Exts as Exts+import GHC.Stack import Numeric.Natural #if MIN_VERSION_mwc_random(0,15,0) import System.Random.Stateful (StatefulGen (..))@@ -149,6 +173,7 @@ import Text.Read  import Data.DecisionDiagram.BDD.Internal.ItemOrder+import Data.DecisionDiagram.BDD.Internal.Node (Sig (..), Graph) import qualified Data.DecisionDiagram.BDD.Internal.Node as Node import qualified Data.DecisionDiagram.BDD as BDD @@ -163,12 +188,17 @@ newtype ZDD a = ZDD Node.Node   deriving (Eq, Hashable) +-- | Synonym of @'Leaf' False@ pattern Empty :: ZDD a-pattern Empty = ZDD Node.F+pattern Empty = Leaf False +-- | Synonym of @'Leaf' True@ pattern Base :: ZDD a-pattern Base = ZDD Node.T+pattern Base = Leaf True +pattern Leaf :: Bool -> ZDD a+pattern Leaf b = ZDD (Node.Leaf b)+ -- | Smart constructor that takes the ZDD reduction rules into account pattern Branch :: Int -> ZDD a -> ZDD a -> ZDD a pattern Branch x lo hi <- ZDD (Node.Branch x (ZDD -> lo) (ZDD -> hi)) where@@ -176,7 +206,13 @@   Branch x (ZDD lo) (ZDD hi) = ZDD (Node.Branch x lo hi)  {-# COMPLETE Empty, Base, Branch #-}+{-# COMPLETE Leaf, Branch #-} +-- Hack for avoiding spurious incomplete patterns warning on the above Branch pattern definition.+#if __GLASGOW_HASKELL__ < 810+{-# COMPLETE ZDD #-}+#endif+ nodeId :: ZDD a -> Int nodeId (ZDD node) = Node.nodeId node @@ -202,7 +238,7 @@       f :: IntSet -> [Int]       f = sortBy (compareItem (Proxy :: Proxy a)) . IntSet.toList -  toList = fold' [] [IntSet.empty] (\top lo hi -> lo <> map (IntSet.insert top) hi)+  toList = toListOfIntSets  -- ------------------------------------------------------------------------ @@ -225,15 +261,24 @@ zddCase2 _ Empty Base = ZDDCase2EQ2 False True zddCase2 _ Empty Empty = ZDDCase2EQ2 False False --- | The empty set (∅).+-- | The empty family (∅).+--+-- >>> toSetOfIntSets (empty :: ZDD AscOrder)+-- fromList [] empty :: ZDD a empty = Empty --- | The set containing only the empty set ({∅}).+-- | The family containing only the empty set ({∅}).+--+-- >>> toSetOfIntSets (base :: ZDD AscOrder)+-- fromList [fromList []] base :: ZDD a base = Base  -- | Create a ZDD that contains only a given set.+--+-- >>> toSetOfIntSets (singleton (IntSet.fromList [1,2,3]) :: ZDD AscOrder)+-- fromList [fromList [1,2,3]] singleton :: forall a. ItemOrder a => IntSet -> ZDD a singleton xs = insert xs empty @@ -243,7 +288,107 @@   where     f zdd x = Branch x zdd zdd +-- | Set of all k-combination of a set+combinations :: forall a. (ItemOrder a, HasCallStack) => IntSet -> Int -> ZDD a+combinations xs k+  | k < 0 = error "Data.DecisionDiagram.ZDD.combinations: negative size"+  | otherwise = unfoldOrd f (0, k)+  where+    table = V.fromList $ sortBy (compareItem (Proxy :: Proxy a)) $ IntSet.toList xs+    n = V.length table++    f :: (Int, Int) -> Sig (Int, Int)+    f (!_, !0) = SLeaf True+    f (!i, !k')+      | i + k' > n = SLeaf False+      | otherwise  = SBranch (table V.! i) (i+1, k') (i+1, k'-1)++-- | Set of all subsets whose sum of weights is at least k.+subsetsAtLeast :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> ZDD a+subsetsAtLeast xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (k <= ub) = SLeaf False+      | i == V.length xs' && 0 >= k = SLeaf True+      | lb >= k = SBranch x (i+1, lb) (i+1, lb) -- all remaining variables are don't-care+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i++-- | Set of all subsets whose sum of weights is at most k.+subsetsAtMost :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> ZDD a+subsetsAtMost xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (lb <= k) = SLeaf False+      | i == V.length xs' && 0 <= k = SLeaf True+      | ub <= k = SBranch x (i+1, ub) (i+1, ub) -- all remaining variables are don't-care+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i++-- | Set of all subsets whose sum of weights is exactly k.+--+-- Note that 'combinations' is a special case where all weights are 1.+--+-- If weight type is 'Integral', 'subsetsExactlyIntegral' is more efficient.+subsetsExactly :: forall a w. (ItemOrder a, Real w) => IntMap w -> w -> ZDD a+subsetsExactly xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (lb <= k && k <= ub) = SLeaf False+      | i == V.length xs' && 0 == k = SLeaf True+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i++-- | Similar to 'subsetsExactly' but more efficient.+subsetsExactlyIntegral :: forall a w. (ItemOrder a, Real w, Integral w) => IntMap w -> w -> ZDD a+subsetsExactlyIntegral xs k0 = unfoldOrd f (0, k0)+  where+    xs' :: V.Vector (Int, w)+    xs' = V.fromList $ sortBy (compareItem (Proxy :: Proxy a) `on` fst) $ IntMap.toList xs+    ys :: V.Vector (w, w)+    ys = V.scanr (\(_, w) (lb,ub) -> if w >= 0 then (lb, ub+w) else (lb+w, ub)) (0,0) xs'+    ds :: V.Vector w+    ds = V.scanr1 (\w d -> if w /= 0 then gcd w d else d) (V.map snd xs')++    f :: (Int, w) -> Sig (Int, w)+    f (!i, !k)+      | not (lb <= k && k <= ub) = SLeaf False+      | i == V.length xs' && 0 == k = SLeaf True+      | d /= 0 && k `mod` d /= 0 = SLeaf False+      | otherwise = SBranch x (i+1, k) (i+1, k-w)+      where+        (lb,ub) = ys V.! i+        (x, w) = xs' V.! i+        d = ds V.! i+ -- | Select subsets that contain a particular element and then remove the element from them+--+-- >>> toSetOfIntSets $ subset1 2 (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [2,4]]) :: ZDD AscOrder)+-- fromList [fromList [1,3],fromList [4]] subset1 :: forall a. ItemOrder a => Int -> ZDD a -> ZDD a subset1 var zdd = runST $ do   h <- C.newSized defaultTableSize@@ -263,6 +408,9 @@   f zdd  -- | Subsets that does not contain a particular element+--+-- >>> toSetOfIntSets $ subset0 2 (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [2,4], [3,4]]) :: ZDD AscOrder)+-- fromList [fromList [1,3],fromList [3,4]] subset0 :: forall a. ItemOrder a => Int -> ZDD a -> ZDD a subset0 var zdd = runST $ do   h <- C.newSized defaultTableSize@@ -282,11 +430,13 @@   f zdd  -- | Insert a set into the ZDD.+--+-- >>> toSetOfIntSets (insert (IntSet.fromList [1,2,3]) (fromListOfIntSets (map IntSet.fromList [[1,3], [2,4]])) :: ZDD AscOrder)+-- fromList [fromList [1,2,3],fromList [1,3],fromList [2,4]] insert :: forall a. ItemOrder a => IntSet -> ZDD a -> ZDD a insert xs = f (sortBy (compareItem (Proxy :: Proxy a)) (IntSet.toList xs))   where-    f [] Empty = Base-    f [] Base = Base+    f [] (Leaf _) = Base     f [] (Branch top p0 p1) = Branch top (f [] p0) p1     f (y : ys) Empty = Branch y Empty (f ys Empty)     f (y : ys) Base = Branch y Base (f ys Empty)@@ -297,14 +447,15 @@         EQ -> Branch top p0 (f ys p1)  -- | Delete a set from the ZDD.+--+-- >>> toSetOfIntSets (delete (IntSet.fromList [1,3]) (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [2,4]])) :: ZDD AscOrder)+-- fromList [fromList [1,2,3],fromList [2,4]] delete :: forall a. ItemOrder a => IntSet -> ZDD a -> ZDD a delete xs = f (sortBy (compareItem (Proxy :: Proxy a)) (IntSet.toList xs))   where-    f [] Empty = Empty-    f [] Base = Empty+    f [] (Leaf _) = Empty     f [] (Branch top p0 p1) = Branch top (f [] p0) p1-    f (_ : _) Empty = Empty-    f (_ : _) Base = Base+    f (_ : _) l@(Leaf _) = l     f yys@(y : ys) p@(Branch top p0 p1) =       case compareItem (Proxy :: Proxy a) y top of         LT -> p@@ -312,6 +463,9 @@         EQ -> Branch top p0 (f ys p1)  -- | Insert an item into each element set of ZDD.+--+-- >>> toSetOfIntSets (mapInsert 2 (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [1,4]])) :: ZDD AscOrder)+-- fromList [fromList [1,2,3],fromList [1,2,4]] mapInsert :: forall a. ItemOrder a => Int -> ZDD a -> ZDD a mapInsert var zdd = runST $ do   unionOp <- mkUnionOp@@ -332,12 +486,14 @@   f zdd  -- | Delete an item from each element set of ZDD.+--+-- >>> toSetOfIntSets (mapDelete 2 (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [1,2,4]])) :: ZDD AscOrder)+-- fromList [fromList [1,3],fromList [1,4]] mapDelete :: forall a. ItemOrder a => Int -> ZDD a -> ZDD a mapDelete var zdd = runST $ do   unionOp <- mkUnionOp   h <- C.newSized defaultTableSize-  let f Base = return Base-      f Empty = return Empty+  let f l@(Leaf _) = return l       f p@(Branch top p0 p1) = do         m <- H.lookup h p         case m of@@ -352,6 +508,9 @@   f zdd  -- | @change x p@ returns {if x∈s then s∖{x} else s∪{x} | s∈P}+--+-- >>> toSetOfIntSets (change 2 (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [1,2,4]])) :: ZDD AscOrder)+-- fromList [fromList [1,2,3],fromList [1,3],fromList [1,4]] change :: forall a. ItemOrder a => Int -> ZDD a -> ZDD a change var zdd = runST $ do   h <- C.newSized defaultTableSize@@ -463,14 +622,17 @@ -- | Given a family P and Q, it computes {S∈P | ∀X∈Q. X⊈S} -- -- Sometimes it is denoted as /P ↘ Q/.+--+-- >>> toSetOfIntSets (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [3,4]]) `nonSuperset` singleton (IntSet.fromList [1,3]) :: ZDD AscOrder)+-- fromList [fromList [3,4]] nonSuperset :: forall a. ItemOrder a => ZDD a -> ZDD a -> ZDD a nonSuperset zdd1 zdd2 = runST $ do-  op <- mkNonSueprsetOp+  op <- mkNonSupersetOp   op zdd1 zdd2 -mkNonSueprsetOp :: forall a s. ItemOrder a => ST s (ZDD a -> ZDD a -> ST s (ZDD a))-mkNonSueprsetOp = do-  intersectionOp <- mkIntersectionOp +mkNonSupersetOp :: forall a s. ItemOrder a => ST s (ZDD a -> ZDD a -> ST s (ZDD a))+mkNonSupersetOp = do+  intersectionOp <- mkIntersectionOp   h <- C.newSized defaultTableSize   let f Empty _ = return Empty       f _ Base = return Empty@@ -496,7 +658,7 @@ minimalHittingSetsKnuth' :: forall a. ItemOrder a => Bool -> ZDD a -> ZDD a minimalHittingSetsKnuth' imai zdd = runST $ do   unionOp <- mkUnionOp-  diffOp <- if imai then mkDifferenceOp else mkNonSueprsetOp+  diffOp <- if imai then mkDifferenceOp else mkNonSupersetOp   h <- C.newSized defaultTableSize   let f Empty = return Base       f Base = return Empty@@ -540,14 +702,13 @@ minimalHittingSetsToda = minimal . hittingSetsBDD  hittingSetsBDD :: forall a. ItemOrder a => ZDD a -> BDD.BDD a-hittingSetsBDD = fold' BDD.true BDD.false (\top h0 h1 -> h0 BDD..&&. BDD.Branch top h1 BDD.true)+hittingSetsBDD = fold' (\top h0 h1 -> h0 BDD..&&. BDD.Branch top h1 BDD.true) (\b -> BDD.Leaf (not b))  minimal :: forall a. ItemOrder a => BDD.BDD a -> ZDD a minimal bdd = runST $ do   diffOp <- mkDifferenceOp   h <- C.newSized defaultTableSize-  let f BDD.F = return Empty-      f BDD.T = return Base+  let f (BDD.Leaf b) = return (Leaf b)       f p@(BDD.Branch x lo hi) = do         m <- H.lookup h p         case m of@@ -561,6 +722,9 @@   f bdd  -- | See 'minimalHittingSetsToda'.+--+-- >>> toSetOfIntSets (minimalHittingSets (fromListOfIntSets (map IntSet.fromList [[1], [2,3,5], [2,3,6], [2,4,5], [2,4,6]]) :: ZDD AscOrder))+-- fromList [fromList [1,2],fromList [1,3,4],fromList [1,5,6]] minimalHittingSets :: forall a. ItemOrder a => ZDD a -> ZDD a minimalHittingSets = minimalHittingSetsToda @@ -584,7 +748,7 @@ notMember :: forall a. (ItemOrder a) => IntSet -> ZDD a -> Bool notMember xs = not . member xs --- | Is this the empty set?+-- | Is this the empty family? null :: ZDD a -> Bool null = (empty ==) @@ -592,24 +756,45 @@ {-# SPECIALIZE size :: ZDD a -> Integer #-} {-# SPECIALIZE size :: ZDD a -> Natural #-} -- | The number of sets in the family.+--+-- Any 'Integral' type can be used as a result type, but it is recommended to use+-- 'Integer' or 'Natural' because the size can be larger than @Int64@ for example:+--+-- >>> size (subsets (IntSet.fromList [1..128]) :: ZDD AscOrder) :: Integer+-- 340282366920938463463374607431768211456+-- >>> import Data.Int+-- >>> maxBound :: Int64+-- 9223372036854775807+-- size :: (Integral b) => ZDD a -> b-size = fold' 0 1 (\_ n0 n1 -> n0 + n1)+size = fold' (\_ n0 n1 -> n0 + n1) (\b -> if b then 1 else 0) --- | @(s1 `isSubsetOf` s2)@ indicates whether @s1@ is a subset of @s2@.+-- | @(s1 \`isSubsetOf\` s2)@ indicates whether @s1@ is a subset of @s2@. isSubsetOf :: ItemOrder a => ZDD a -> ZDD a -> Bool isSubsetOf a b = union a b == b --- | @(s1 `isProperSubsetOf` s2)@ indicates whether @s1@ is a proper subset of @s2@.+-- | @(s1 \`isProperSubsetOf\` s2)@ indicates whether @s1@ is a proper subset of @s2@. isProperSubsetOf :: ItemOrder a => ZDD a -> ZDD a -> Bool isProperSubsetOf a b = a `isSubsetOf` b && a /= b --- | Check whether two sets are disjoint (i.e., their intersection is empty).+-- | Check whether two families are disjoint (i.e., their intersection is empty). disjoint :: ItemOrder a => ZDD a -> ZDD a -> Bool disjoint a b = null (a `intersection` b) ---- | Unions of all member sets+-- | Count the number of nodes in a ZDD viewed as a rooted directed acyclic graph.+--+-- Please do not confuse it with 'size'.+--+-- See also 'toGraph'.+numNodes :: ZDD a -> Int+numNodes (ZDD node) = Node.numNodes node++-- | Unions of all member sets+--+-- >>> flatten (fromListOfIntSets (map IntSet.fromList [[1,2,3], [1,3], [3,4]]) :: ZDD AscOrder)+-- fromList [1,2,3,4] flatten :: ItemOrder a => ZDD a -> IntSet-flatten = fold' IntSet.empty IntSet.empty (\top lo hi -> IntSet.insert top (lo `IntSet.union` hi))+flatten = fold' (\top lo hi -> IntSet.insert top (lo `IntSet.union` hi)) (const IntSet.empty)  -- | Create a ZDD from a set of 'IntSet' fromSetOfIntSets :: forall a. ItemOrder a => Set IntSet -> ZDD a@@ -617,7 +802,7 @@  -- | Convert the family to a set of 'IntSet'. toSetOfIntSets :: ZDD a -> Set IntSet-toSetOfIntSets = fold' Set.empty (Set.singleton IntSet.empty) (\top lo hi -> lo <> Set.map (IntSet.insert top) hi)+toSetOfIntSets = fold' (\top lo hi -> lo <> Set.map (IntSet.insert top) hi) (\b -> if b then Set.singleton IntSet.empty else Set.empty)  -- | Create a ZDD from a list of 'IntSet' fromListOfIntSets :: forall a. ItemOrder a => [IntSet] -> ZDD a@@ -628,7 +813,11 @@  -- | Convert the family to a list of 'IntSet'. toListOfIntSets :: ZDD a -> [IntSet]-toListOfIntSets = fold [] [IntSet.empty] (\top lo hi -> lo <> map (IntSet.insert top) hi)+toListOfIntSets = g . fold' f (\b -> (b,[]))+  where+    f top (b, xss) hi = (b, map (IntSet.insert top) (g hi) <> xss)+    g (True, xss) = IntSet.empty : xss+    g (False, xss) = xss  fromListOfSortedList :: forall a. ItemOrder a => [[Int]] -> ZDD a fromListOfSortedList = unions . map f@@ -638,43 +827,54 @@  -- | Fold over the graph structure of the ZDD. ----- It takes values for substituting 'empty' and 'base',--- and a function for substiting non-terminal node.-fold :: b -> b -> (Int -> b -> b -> b) -> ZDD a -> b-fold ff tt br zdd = runST $ do-  h <- C.newSized defaultTableSize-  let f Empty = return ff-      f Base = return tt-      f p@(Branch top p0 p1) = do-        m <- H.lookup h p-        case m of-          Just ret -> return ret-          Nothing -> do-            r0 <- f p0-            r1 <- f p1-            let ret = br top r0 r1-            H.insert h p ret-            return ret-  f zdd+-- It takes two functions that substitute 'Branch'  and 'Leaf' respectively.+--+-- Note that its type is isomorphic to @('Sig' b -> b) -> ZDD a -> b@.+fold :: (Int -> b -> b -> b) -> (Bool -> b) -> ZDD a -> b+fold br lf (ZDD node) = Node.fold br lf node  -- | Strict version of 'fold'-fold' :: b -> b -> (Int -> b -> b -> b) -> ZDD a -> b-fold' !ff !tt br zdd = runST $ do+fold' :: (Int -> b -> b -> b) -> (Bool -> b) -> ZDD a -> b+fold' br lf (ZDD node) = Node.fold' br lf node++-- ------------------------------------------------------------------------++-- | Top-down construction of ZDD, memoising internal states using 'Hashable' instance.+unfoldHashable :: forall a b. (ItemOrder a, Eq b, Hashable b) => (b -> Sig b) -> b -> ZDD a+unfoldHashable f b = runST $ do   h <- C.newSized defaultTableSize-  let f Empty = return ff-      f Base = return tt-      f p@(Branch top p0 p1) = do-        m <- H.lookup h p-        case m of-          Just ret -> return ret+  let g [] = return ()+      g (x : xs) = do+        r <- H.lookup h x+        case r of+          Just _ -> g xs           Nothing -> do-            r0 <- f p0-            r1 <- f p1-            let ret = br top r0 r1-            seq ret $ H.insert h p ret-            return ret-  f zdd+            let fx = f x+            H.insert h x fx+            g (xs ++ Foldable.toList fx)+  g [b]+  xs <- H.toList h+  let h2 = HashMap.fromList [(x, inSig (fmap (h2 HashMap.!) s)) | (x,s) <- xs]+  return $ h2 HashMap.! b +-- | Top-down construction of ZDD, memoising internal states using 'Ord' instance.+unfoldOrd :: forall a b. (ItemOrder a, Ord b) => (b -> Sig b) -> b -> ZDD a+unfoldOrd f b = m2 Map.! b+  where+    m1 :: Map b (Sig b)+    m1 = g Map.empty [b]++    m2 :: Map b (ZDD a)+    m2 = Map.map (inSig . fmap (m2 Map.!)) m1++    g m [] = m+    g m (x : xs) =+      case Map.lookup x m of+        Just _ -> g m xs+        Nothing ->+          let fx = f x+           in g (Map.insert x fx m) (xs ++ Foldable.toList fx)+ -- ------------------------------------------------------------------------  -- | Sample a set from uniform distribution over elements of the ZDD.@@ -697,9 +897,9 @@ -- @ -- . #if MIN_VERSION_mwc_random(0,15,0)-uniformM :: forall a g m. (ItemOrder a, StatefulGen g m) => ZDD a -> g -> m IntSet+uniformM :: forall a g m. (ItemOrder a, StatefulGen g m, HasCallStack) => ZDD a -> g -> m IntSet #else-uniformM :: forall a m. (ItemOrder a, PrimMonad m) => ZDD a -> Gen (PrimState m) -> m IntSet+uniformM :: forall a m. (ItemOrder a, PrimMonad m, HasCallStack) => ZDD a -> Gen (PrimState m) -> m IntSet #endif uniformM Empty = error "Data.DecisionDiagram.ZDD.uniformM: empty ZDD" uniformM zdd = func@@ -743,10 +943,10 @@ -- \[ -- \min_{X\in S} \sum_{x\in X} w(x) -- \]-findMinSum :: forall a w. (ItemOrder a, Num w, Ord w) => (Int -> w) -> ZDD a -> (w, IntSet)+findMinSum :: forall a w. (ItemOrder a, Num w, Ord w, HasCallStack) => (Int -> w) -> ZDD a -> (w, IntSet) findMinSum weight =   fromMaybe (error "Data.DecisionDiagram.ZDD.findMinSum: empty ZDD") .-    fold' Nothing (Just (0, IntSet.empty)) f+    fold' f (\b -> if b then Just (0, IntSet.empty) else Nothing)   where     f _ _ Nothing = undefined     f x z1 (Just (w2, s2)) =@@ -762,10 +962,13 @@ -- \[ -- \max_{X\in S} \sum_{x\in X} w(x) -- \]-findMaxSum :: forall a w. (ItemOrder a, Num w, Ord w) => (Int -> w) -> ZDD a -> (w, IntSet)+--+-- >>> findMaxSum (IntMap.fromList [(1,2),(2,4),(3,-3)] IntMap.!) (fromListOfIntSets (map IntSet.fromList [[1], [2], [3], [1,2,3]]) :: ZDD AscOrder)+-- (4,fromList [2])+findMaxSum :: forall a w. (ItemOrder a, Num w, Ord w, HasCallStack) => (Int -> w) -> ZDD a -> (w, IntSet) findMaxSum weight =   fromMaybe (error "Data.DecisionDiagram.ZDD.findMinSum: empty ZDD") .-    fold' Nothing (Just (0, IntSet.empty)) f+    fold' f (\b -> if b then Just (0, IntSet.empty) else Nothing)   where     f _ _ Nothing = undefined     f x z1 (Just (w2, s2)) =@@ -778,66 +981,44 @@  -- ------------------------------------------------------------------------ -type Graph = IntMap Node+-- | Synonym of @'SLeaf' False@+pattern SEmpty :: Sig a+pattern SEmpty = SLeaf False -data Node-  = NodeEmpty-  | NodeBase-  | NodeBranch !Int Int Int-  deriving (Eq, Show, Read)+-- | Synonym of @'SLeaf' True@+pattern SBase :: Sig a+pattern SBase = SLeaf True --- | Convert a ZDD into a pointed graph-toGraph :: ZDD a -> (Graph, Int)-toGraph bdd =-  case toGraph' (Identity bdd) of-    (g, Identity v) -> (g, v)+-- | 'Sig'-algebra stucture of 'ZDD', \(\mathrm{in}_\mathrm{Sig}\).+inSig :: Sig (ZDD a) -> ZDD a+inSig (SLeaf b) = Leaf b+inSig (SBranch x lo hi) = Branch x lo hi --- | Convert multiple ZDDs into a graph-toGraph' :: Traversable t => t (ZDD a) -> (Graph, t Int)-toGraph' bs = runST $ do-  h <- C.newSized defaultTableSize-  H.insert h Empty 0-  H.insert h Base 1-  counter <- newSTRef 2-  ref <- newSTRef $ IntMap.fromList [(0, NodeEmpty), (1, NodeBase)]+-- | 'Sig'-coalgebra stucture of 'ZDD', \(\mathrm{out}_\mathrm{Sig}\).+outSig :: ZDD a -> Sig (ZDD a)+outSig (Leaf b) = SLeaf b+outSig (Branch x lo hi) = SBranch x lo hi -  let f Empty = return 0-      f Base = return 1-      f p@(Branch x lo hi) = do-        m <- H.lookup h p-        case m of-          Just ret -> return ret-          Nothing -> do-            r0 <- f lo-            r1 <- f hi-            n <- readSTRef counter-            writeSTRef counter $! n+1-            H.insert h p n-            modifySTRef' ref (IntMap.insert n (NodeBranch x r0 r1))-            return n+-- ------------------------------------------------------------------------ -  vs <- mapM f bs-  g <- readSTRef ref-  return (g, vs)+-- | Convert a ZDD into a pointed graph+--+-- Nodes @0@ and @1@ are reserved for @SLeaf False@ and @SLeaf True@ even if+-- they are not actually used. Therefore the result may be larger than+-- 'numNodes' if the leaf nodes are not used.+toGraph :: ZDD a -> (Graph Sig, Int)+toGraph (ZDD node) = Node.toGraph node +-- | Convert multiple ZDDs into a graph+toGraph' :: Traversable t => t (ZDD a) -> (Graph Sig, t Int)+toGraph' bs = Node.toGraph' (fmap (\(ZDD node) -> node) bs)+ -- | Convert a pointed graph into a ZDD-fromGraph :: (Graph, Int) -> ZDD a-fromGraph (g, v) =-  case IntMap.lookup v (fromGraph' g) of-    Nothing -> error ("Data.DecisionDiagram.ZDD.fromGraph: invalid node id " ++ show v)-    Just bdd -> bdd+fromGraph :: HasCallStack => (Graph Sig, Int) -> ZDD a+fromGraph = Node.foldGraph inSig  -- | Convert nodes of a graph into ZDDs-fromGraph' :: Graph -> IntMap (ZDD a)-fromGraph' g = ret-  where-    ret = IntMap.map f g-    f NodeEmpty = Empty-    f NodeBase = Base-    f (NodeBranch x lo hi) =-      case (IntMap.lookup lo ret, IntMap.lookup hi ret) of-        (Nothing, _) -> error ("Data.DecisionDiagram.ZDD.fromGraph': invalid node id " ++ show lo)-        (_, Nothing) -> error ("Data.DecisionDiagram.ZDD.fromGraph': invalid node id " ++ show hi)-        (Just lo', Just hi') -> Branch x lo' hi'+fromGraph' :: HasCallStack => Graph Sig -> IntMap (ZDD a)+fromGraph' = Node.foldGraphNodes inSig  -- ------------------------------------------------------------------------
test/TestBDD.hs view
@@ -4,12 +4,24 @@ module TestBDD (bddTestGroup) where  import Control.Monad-import qualified Data.IntMap as IntMap+import Control.Monad.ST+import Data.IntMap.Lazy (IntMap)+import qualified Data.IntMap.Lazy as IntMap import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet+import Data.IORef import Data.List+import qualified Data.Map.Lazy as Map import Data.Proxy+import qualified Data.Set as Set+import Data.Vector (Vector)+import Data.Word+import Statistics.Distribution+import Statistics.Distribution.ChiSquared (chiSquared)+import System.IO.Unsafe+import qualified System.Random.MWC as Rand import Test.QuickCheck.Function (apply)+import Test.QuickCheck.Instances.Vector () import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck@@ -23,10 +35,9 @@ -- ------------------------------------------------------------------------  instance BDD.ItemOrder a => Arbitrary (BDD a) where-  arbitrary = arbitraryBDDOver =<< liftM IntSet.fromList arbitrary+  arbitrary = arbitraryBDDOver =<< arbitrary -  shrink (BDD.F) = []-  shrink (BDD.T) = []+  shrink (BDD.Leaf _) = []   shrink (BDD.Branch x p0 p1) =     [p0, p1]     ++@@ -50,6 +61,25 @@         ]   sized $ f (sortBy (BDD.compareItem (Proxy :: Proxy a)) $ IntSet.toList xs) +arbitrarySatisfyingAssignment :: forall a. BDD.ItemOrder a => BDD a -> IntSet -> Gen (IntMap Bool)+arbitrarySatisfyingAssignment bdd xs = do+  m1 <- arbitrarySatisfyingPartialAssignment bdd+  let ys = xs `IntSet.difference` IntMap.keysSet m1+  m2 <- liftM(IntMap.fromAscList) $ forM (IntSet.toAscList ys) $ \y -> do+    v <- arbitrary+    return (y,v)+  return $ m1 `IntMap.union` m2++arbitrarySatisfyingPartialAssignment :: forall a. BDD.ItemOrder a => BDD a -> Gen (IntMap Bool)+arbitrarySatisfyingPartialAssignment = f+  where+    f (BDD.Leaf True) = return IntMap.empty+    f (BDD.Leaf False) = undefined+    f (BDD.Branch x lo hi) = oneof $+      [liftM (IntMap.insert x False) (f lo) | lo /= BDD.Leaf False]+      +++      [liftM (IntMap.insert x True) (f hi) | hi /= BDD.Leaf False]+ -- ------------------------------------------------------------------------ -- conjunction -- ------------------------------------------------------------------------@@ -305,6 +335,83 @@       (d BDD..||. BDD.ite c t e) === BDD.ite c (d BDD..||. t) (d BDD..||. e)  -- ------------------------------------------------------------------------+-- Pseudo-Boolean+-- ------------------------------------------------------------------------++prop_pbAtLeast :: Property+prop_pbAtLeast =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        let a :: BDD o+            a = BDD.pbAtLeast xs k+         in counterexample (show a) $+              if a == BDD.Leaf False then+                property (k > sum [max 0 w | (_,w) <- IntMap.toList xs])+              else+                forAll (arbitrarySatisfyingAssignment a (IntMap.keysSet xs)) $ \ys ->+                  (IntMap.keysSet ys `IntSet.isSubsetOf` IntMap.keysSet xs)+                  .&&.+                  sum [xs IntMap.! y | (y,b) <- IntMap.toList ys, b] >= k++prop_pbAtMost :: Property+prop_pbAtMost =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        let a :: BDD o+            a = BDD.pbAtMost xs k+         in counterexample (show a) $+              if a == BDD.Leaf False then+                property (k < sum [min 0 w | (_,w) <- IntMap.toList xs])+              else+                forAll (arbitrarySatisfyingAssignment a (IntMap.keysSet xs)) $ \ys ->+                  (IntMap.keysSet ys `IntSet.isSubsetOf` IntMap.keysSet xs)+                  .&&.+                  sum [xs IntMap.! y | (y,b) <- IntMap.toList ys, b] <= k++prop_pbExactly :: Property+prop_pbExactly =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        let a :: BDD o+            a = BDD.pbExactly xs k+         in counterexample (show a) $+              if a == BDD.Leaf False then+                property True+              else+                forAll (arbitrarySatisfyingAssignment a (IntMap.keysSet xs)) $ \ys ->+                  (IntMap.keysSet ys `IntSet.isSubsetOf` IntMap.keysSet xs)+                  .&&.+                  sum [xs IntMap.! y | (y,b) <- IntMap.toList ys, b] === k++prop_pbExactly_2 :: Property+prop_pbExactly_2 =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll (gen xs) $ \(m, k) ->+        let a :: BDD o+            a = BDD.pbExactly xs k+         in counterexample (show a) $ BDD.evaluate (m IntMap.!) a+  where+    gen :: IntMap Integer -> Gen (IntMap Bool, Integer)+    gen xs = do+      ys <- sublistOf (IntMap.toList xs)+      let ys' = IntSet.fromList [y | (y,_) <- ys]+      return+        ( IntMap.mapWithKey (\x _ -> x `IntSet.member` ys') xs+        , sum [w | (_,w) <- ys]+        )++prop_pbExactlyIntegral :: Property+prop_pbExactlyIntegral =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        (BDD.pbExactlyIntegral xs k :: BDD o) === BDD.pbExactly xs k++-- ------------------------------------------------------------------------ -- Quantification -- ------------------------------------------------------------------------ @@ -416,6 +523,55 @@  -- ------------------------------------------------------------------------ +case_fold_laziness :: Assertion+case_fold_laziness = do+  let bdd :: BDD BDD.AscOrder+      bdd = BDD.Branch 0 (BDD.Branch 1 (BDD.Leaf False) (BDD.Leaf True)) (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True))+      f x lo _hi =+        if x == 2 then+          error "unused value should not be evaluated"+        else+          lo+  seq (BDD.fold f id bdd) $ return ()++case_fold'_strictness :: Assertion+case_fold'_strictness = do+  ref <- newIORef False+  let bdd :: BDD BDD.AscOrder+      bdd = BDD.Branch 0 (BDD.Branch 1 (BDD.Leaf False) (BDD.Leaf True)) (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True))+      f x lo _hi = unsafePerformIO $ do+        when (x==2) $ writeIORef ref True+        return lo+  seq (BDD.fold' f id bdd) $ do+    flag <- readIORef ref+    assertBool "unused value should be evaluated" flag++prop_fold_inSig :: Property+prop_fold_inSig =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(bdd :: BDD o) ->+      BDD.fold (\x lo hi -> BDD.inSig (BDD.SBranch x lo hi)) (BDD.inSig . BDD.SLeaf) bdd+      ===+      bdd++prop_fold'_inSig :: Property+prop_fold'_inSig =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(bdd :: BDD o) ->+      BDD.fold' (\x lo hi -> BDD.inSig (BDD.SBranch x lo hi)) (BDD.inSig . BDD.SLeaf) bdd+      ===+      bdd++-- ------------------------------------------------------------------------++prop_unfoldHashable_outSig :: Property+prop_unfoldHashable_outSig =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(bdd :: BDD o) ->+      BDD.unfoldHashable BDD.outSig bdd === bdd++-- ------------------------------------------------------------------------+ case_support_false :: Assertion case_support_false = BDD.support BDD.false @?= IntSet.empty @@ -487,6 +643,15 @@  -- ------------------------------------------------------------------------ +case_numNodes :: Assertion+case_numNodes = do+  let bdd, bdd1 :: BDD BDD.AscOrder+      bdd = BDD.Branch 0 (BDD.Branch 1 BDD.false bdd1) (BDD.Branch 2 BDD.false bdd1)+      bdd1 = BDD.Branch 3 BDD.true BDD.false+  BDD.numNodes bdd @?= 6++-- ------------------------------------------------------------------------+ prop_restrict :: Property prop_restrict =   forAllItemOrder $ \(_ :: Proxy o) ->@@ -613,8 +778,8 @@ case_restrictLaw_case_0 = (law BDD..&&. BDD.restrictLaw law a) @?= (law BDD..&&. a)   where     a, law :: BDD BDD.AscOrder-    a = BDD.Branch 2 BDD.F BDD.T-    law = BDD.Branch 1 (BDD.Branch 2 BDD.T BDD.F) (BDD.Branch 2 BDD.F BDD.T)+    a = BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True)+    law = BDD.Branch 1 (BDD.Branch 2 (BDD.Leaf True) (BDD.Leaf False)) (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True))  prop_restrictLaw_true :: Property prop_restrictLaw_true =@@ -657,13 +822,13 @@ case_restrictLaw_case_1 :: Assertion case_restrictLaw_case_1 = do   -- BDD.restrictLaw val a @?= BDD.restrictLaw val2 (BDD.restrictLaw val1 a)-  BDD.restrictLaw val a @?= BDD.Branch 2 BDD.F BDD.T-  BDD.restrictLaw val2 (BDD.restrictLaw val1 a) @?= BDD.Branch 1 BDD.T (Branch 2 BDD.F BDD.T)+  BDD.restrictLaw val a @?= BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True)+  BDD.restrictLaw val2 (BDD.restrictLaw val1 a) @?= BDD.Branch 1 (BDD.Leaf True) (Branch 2 (BDD.Leaf False) (BDD.Leaf True))   where     a :: BDD BDD.AscOrder-    a = Branch 2 BDD.F BDD.T -- x2-    val1 = BDD.Branch 1 BDD.F BDD.T -- x1-    val2 = BDD.Branch 1 (BDD.Branch 2 BDD.F BDD.T) BDD.T -- x1 ∨ x2+    a = Branch 2 (BDD.Leaf False) (BDD.Leaf True) -- x2+    val1 = BDD.Branch 1 (BDD.Leaf False) (BDD.Leaf True) -- x1+    val2 = BDD.Branch 1 (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True)) (BDD.Leaf True) -- x1 ∨ x2     val = val1 BDD..&&. val2 -- x1  prop_restrictLaw_or_condition :: Property@@ -722,9 +887,9 @@   BDD.restrictLaw law a @?= b -- should be 'a'?   where     law, a :: BDD BDD.AscOrder-    law = BDD.Branch 1 (BDD.Branch 2 BDD.F BDD.T) BDD.T -- x1 ∨ x2-    a = BDD.Branch 2 BDD.T BDD.F -- ¬x2-    b = BDD.Branch 1 BDD.F (BDD.Branch 2 BDD.T BDD.F) -- x1 ∧ ¬x2+    law = BDD.Branch 1 (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True)) (BDD.Leaf True) -- x1 ∨ x2+    a = BDD.Branch 2 (BDD.Leaf True) (BDD.Leaf False) -- ¬x2+    b = BDD.Branch 1 (BDD.Leaf False) (BDD.Branch 2 (BDD.Leaf True) (BDD.Leaf False)) -- x1 ∧ ¬x2  case_restrictLaw_non_minimal_2 :: Assertion case_restrictLaw_non_minimal_2 = do@@ -732,9 +897,9 @@   BDD.restrictLaw law a @?= b -- should be 'a'?   where     law, a, b :: BDD BDD.AscOrder-    law = BDD.Branch 1 BDD.T (BDD.Branch 2 BDD.F BDD.T) -- ¬x1 ∨ x2-    a = BDD.Branch 2 BDD.F BDD.T -- x2-    b = BDD.Branch 1 (BDD.Branch 2 BDD.F BDD.T) BDD.T -- x1 ∨ x2+    law = BDD.Branch 1 (BDD.Leaf True) (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True)) -- ¬x1 ∨ x2+    a = BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True) -- x2+    b = BDD.Branch 1 (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True)) (BDD.Leaf True) -- x1 ∨ x2  -- ------------------------------------------------------------------------ @@ -799,9 +964,9 @@   BDD.substSet (IntMap.singleton x m1) m @?= BDD.subst x m1 m   where     m :: BDD BDD.AscOrder-    m = BDD.Branch 1 (BDD.Branch 2 BDD.T BDD.F) (BDD.Branch 2 BDD.F BDD.F)+    m = BDD.Branch 1 (BDD.Branch 2 (BDD.Leaf True) (BDD.Leaf False)) (BDD.Branch 2 (BDD.Leaf False) (BDD.Leaf True))     x = 1-    m1 = BDD.Branch 1 BDD.T BDD.F+    m1 = BDD.Branch 1 (BDD.Leaf True) (BDD.Leaf False)  prop_substSet_same_vars :: Property prop_substSet_same_vars =@@ -825,8 +990,8 @@ prop_substSet_compose :: Property prop_substSet_compose =   forAllItemOrder $ \(_ :: Proxy o) ->-    forAll (liftM IntSet.fromList arbitrary) $ \xs ->-    forAll (liftM IntSet.fromList arbitrary) $ \ys ->+    forAll arbitrary $ \xs ->+    forAll arbitrary $ \ys ->     forAll (liftM IntMap.fromList $ mapM (\x -> (,) <$> pure x <*> arbitraryBDDOver ys) (IntSet.toList xs)) $ \s1 ->     forAll (liftM IntMap.fromList $ mapM (\y -> (,) <$> pure y <*> arbitrary) (IntSet.toList ys)) $ \s2 ->     forAll (arbitraryBDDOver xs) $ \(m :: BDD o) ->@@ -841,11 +1006,172 @@  -- ------------------------------------------------------------------------ +data MonotoneExpr a+  = MVar a+  | MAnd (MonotoneExpr a) (MonotoneExpr a)+  | MOr (MonotoneExpr a) (MonotoneExpr a)+  | MConst Bool+  deriving (Show)++arbitraryMonotoneExpr :: forall a. Gen a -> Gen (MonotoneExpr a)+arbitraryMonotoneExpr gen = sized f+  where+    f :: Int -> Gen (MonotoneExpr a)+    f n = oneof $+      [ liftM MConst arbitrary+      , liftM MVar gen+      ]+      +++      concat+      [ [liftM2 MAnd sub sub, liftM2 MOr sub sub]+      | n > 0, let sub = f (n `div` 2)+      ]++evalMonotoneExpr :: ItemOrder a => (b -> BDD a) -> MonotoneExpr b -> BDD a+evalMonotoneExpr f = g+  where+    g (MVar a) = f a+    g (MConst v) = BDD.Leaf v+    g (MOr a b) = g a BDD..||. g b+    g (MAnd a b) = g a BDD..&&. g b++forAllMonotonicFunction :: forall o prop. (ItemOrder o, Testable prop) => IntSet -> ((BDD o -> BDD o) -> prop) -> Property+forAllMonotonicFunction xs k =+  forAll (arbitraryMonotoneExpr (elements (Nothing : map Just (IntSet.toList xs)))) $ \e -> do+    let f :: BDD o -> BDD o+        f x = evalMonotoneExpr g e+          where+            g Nothing = x+            g (Just v) = BDD.var v+     in k f++prop_lfp_is_fixed_point :: Property+prop_lfp_is_fixed_point =+ forAll arbitrary $ \(xs :: IntSet) ->+   forAllItemOrder $ \(_ :: Proxy o) ->+     forAllMonotonicFunction xs $ \(f :: BDD o -> BDD o) -> do+       let a = BDD.lfp f+        in counterexample (show a) $ f a === a+++prop_gfp_is_fixed_point :: Property+prop_gfp_is_fixed_point =+ forAll arbitrary $ \(xs :: IntSet) ->+   forAllItemOrder $ \(_ :: Proxy o) ->+     forAllMonotonicFunction xs $ \(f :: BDD o -> BDD o) -> do+       let a = BDD.gfp f+        in counterexample (show a) $ f a === a++prop_lfp_imply_gfp :: Property+prop_lfp_imply_gfp =+ forAll arbitrary $ \(xs :: IntSet) ->+   forAllItemOrder $ \(_ :: Proxy o) ->+     forAllMonotonicFunction xs $ \(f :: BDD o -> BDD o) -> do+       let a = BDD.lfp f+           b = BDD.gfp f+        in counterexample (show (a, b)) $ (a BDD..=>. b) === BDD.true++-- ------------------------------------------------------------------------++prop_anySat :: Property+prop_anySat =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(bdd :: BDD o) ->+      case BDD.anySat bdd of+        Just p -> counterexample (show p) $ BDD.evaluate (p IntMap.!) bdd+        Nothing -> bdd === BDD.Leaf False++prop_allSat :: Property+prop_allSat =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntSet $ \xs ->+      forAll (arbitraryBDDOver xs) $ \(bdd :: BDD o) ->+         let ps = BDD.allSat bdd+         in null ps === (bdd == BDD.Leaf False)+            .&&.+            conjoin [counterexample (show p) $ BDD.evaluate (p IntMap.!) bdd |  p <- ps]++prop_anySatComplete :: Property+prop_anySatComplete =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \xs ->+      forAll (arbitraryBDDOver xs) $ \(bdd :: BDD o) ->+        case BDD.anySatComplete xs bdd of+          Just p -> counterexample (show p) $+            IntMap.keysSet p === xs+            .&&.+            BDD.evaluate (p IntMap.!) bdd+          Nothing -> bdd === BDD.Leaf False++prop_allSatComplete :: Property+prop_allSatComplete =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntSet $ \xs ->+      forAll (arbitraryBDDOver xs) $ \(bdd :: BDD o) ->+        let ps = BDD.allSatComplete xs bdd+            qs = [q | q <- foldM (\m x -> [IntMap.insert x v m | v <- [False, True]]) IntMap.empty (IntSet.toList xs)+                    , BDD.evaluate (q IntMap.!) bdd]+         in conjoin [counterexample (show p) (IntMap.keysSet p === xs) | p <- ps]+            .&&.+            Set.fromList ps === Set.fromList qs++prop_countSat_allSatComplete :: Property+prop_countSat_allSatComplete =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntSet $ \xs ->+      forAll (arbitraryBDDOver xs) $ \(bdd :: BDD o) ->+        let ps = BDD.allSatComplete xs bdd+            n = BDD.countSat xs bdd+         in counterexample (show n) $+              if bdd == BDD.Leaf False then+                n === 0+              else+                -- Note that the number of partial assignments is smaller than the number of total assignments+                (n > 0) .&&. n === length ps++prop_uniformSatM :: Property+prop_uniformSatM =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll (arbitrarySmallIntSet `suchThat` ((>= 2) . IntSet.size)) $ \xs ->+      forAll (arbitraryBDDOver xs `suchThat` ((>= (2::Integer)) . BDD.countSat xs)) $ \(bdd :: BDD o) ->+        forAll arbitrary $ \(seed :: Vector Word32) ->+          let m :: Integer+              m = BDD.countSat xs bdd+              n = 1000+              samples = runST $ do+                gen <- Rand.initialize seed+                replicateM n $ BDD.uniformSatM xs bdd gen+              hist_actual = Map.fromListWith (+) [(s, 1 :: Double) | s <- samples]+              hist_expected = [(s, fromIntegral n / fromIntegral m :: Double) | s <- BDD.allSatComplete xs bdd]+              chi_sq = sum [(Map.findWithDefault 0 s hist_actual - cnt) ** 2 / cnt | (s, cnt) <- hist_expected]+              threshold = complQuantile (chiSquared (fromIntegral m - 1)) 0.0001+           in counterexample (show hist_actual ++ " /= " ++ show (Map.fromList hist_expected)) $+                and [BDD.evaluate (a IntMap.!) bdd | a <- Map.keys hist_actual]+                .&&.+                counterexample ("χ² = " ++ show chi_sq ++ " >= " ++ show threshold) (chi_sq < threshold)++-- ------------------------------------------------------------------------+ prop_toGraph_fromGraph :: Property prop_toGraph_fromGraph = do   forAllItemOrder $ \(_ :: Proxy o) ->     forAll arbitrary $ \(a :: BDD o) ->       BDD.fromGraph (BDD.toGraph a) === a++-- ------------------------------------------------------------------------++arbitrarySmallIntSet :: Gen IntSet+arbitrarySmallIntSet = do+  n <- choose (0, 12)+  liftM IntSet.fromList $ replicateM n arbitrary++arbitrarySmallIntMap :: Arbitrary a => Gen (IntMap a)+arbitrarySmallIntMap = do+  n <- choose (0, 12)+  liftM IntMap.fromList $ replicateM n $ do+    k <- arbitrary+    v <- arbitrary+    return (k, v)  -- ------------------------------------------------------------------------ 
test/TestZDD.hs view
@@ -3,25 +3,34 @@ {-# LANGUAGE TemplateHaskell #-} module TestZDD (zddTestGroup) where +import Control.DeepSeq import Control.Monad+import Control.Monad.ST+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet+import Data.IORef import Data.List import qualified Data.Map.Strict as Map import Data.Proxy import Data.Set (Set) import qualified Data.Set as Set+import Data.Vector (Vector)+import Data.Word import qualified GHC.Exts as Exts import Statistics.Distribution import Statistics.Distribution.ChiSquared (chiSquared)+import System.IO.Unsafe import qualified System.Random.MWC as Rand import Test.QuickCheck.Function (apply)-import qualified Test.QuickCheck.Monadic as QM+import Test.QuickCheck.Instances.Vector () import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck import Test.Tasty.TH +import Data.DecisionDiagram.BDD.Internal.ItemOrder (OrderedItem (..)) import Data.DecisionDiagram.ZDD (ZDD (..), ItemOrder (..)) import qualified Data.DecisionDiagram.ZDD as ZDD @@ -31,7 +40,7 @@  instance ZDD.ItemOrder a => Arbitrary (ZDD a) where   arbitrary = do-    vars <- liftM (sortBy (ZDD.compareItem (Proxy :: Proxy a)) . IntSet.toList . IntSet.fromList) arbitrary+    vars <- liftM (sortBy (ZDD.compareItem (Proxy :: Proxy a)) . IntSet.toList) arbitrary     let f vs n = oneof $           [ return ZDD.empty           , return ZDD.base@@ -55,6 +64,13 @@     | (p0', p1') <- shrink (p0, p1), p1' /= ZDD.empty     ] +arbitraryMember :: ZDD.ItemOrder a => ZDD a -> Gen IntSet+arbitraryMember zdd = do+  (seed :: Vector Word32) <- arbitrary+  return $ runST $ do+    gen <- Rand.initialize seed+    ZDD.uniformM zdd gen+ -- ------------------------------------------------------------------------ -- Union -- ------------------------------------------------------------------------@@ -292,7 +308,7 @@ prop_singleton :: Property prop_singleton =   forAllItemOrder $ \(_ :: Proxy o) ->-    forAll (liftM IntSet.fromList arbitrary) $ \xs ->+    forAll arbitrary $ \xs ->       let a :: ZDD o           a = ZDD.singleton xs        in counterexample (show a) $ ZDD.toSetOfIntSets a === Set.singleton xs@@ -303,7 +319,7 @@     forAll arbitrary $ \xs ->       let a :: ZDD o           a = ZDD.subsets xs-       in counterexample (show a) $ forAll (liftM IntSet.fromList (sublistOf (IntSet.toList xs))) $ \ys ->+       in counterexample (show a) $ forAll (subsetOf xs) $ \ys ->             ys `ZDD.member` a  prop_subsets_member_empty :: Property@@ -330,6 +346,110 @@           a = ZDD.subsets xs        in counterexample (show a) $ ZDD.size a === (2 :: Integer) ^ (IntSet.size xs) +prop_subsetsAtLeast :: Property+prop_subsetsAtLeast =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        let a :: ZDD o+            a = ZDD.subsetsAtLeast xs k+         in counterexample (show a) $+              if ZDD.null a then+                property (k > sum [max 0 w | (_,w) <- IntMap.toList xs])+              else+                forAll (arbitraryMember a) $ \ys ->+                  (ys `IntSet.isSubsetOf` IntMap.keysSet xs)+                  .&&.+                  sum [xs IntMap.! y | y <- IntSet.toList ys] >= k++prop_subsetsAtMost :: Property+prop_subsetsAtMost =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        let a :: ZDD o+            a = ZDD.subsetsAtMost xs k+         in counterexample (show a) $+              if ZDD.null a then+                property (k < sum [min 0 w | (_,w) <- IntMap.toList xs])+              else+                forAll (arbitraryMember a) $ \ys ->+                  (ys `IntSet.isSubsetOf` IntMap.keysSet xs)+                  .&&.+                  sum [xs IntMap.! y | y <- IntSet.toList ys] <= k++prop_subsetsExactly :: Property+prop_subsetsExactly =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        let a :: ZDD o+            a = ZDD.subsetsExactly xs k+         in counterexample (show a) $+              if ZDD.null a then+                property True+              else+                forAll (arbitraryMember a) $ \ys ->+                  (ys `IntSet.isSubsetOf` IntMap.keysSet xs)+                  .&&.+                  sum [xs IntMap.! y | y <- IntSet.toList ys] === k++prop_subsetsExactly_2 :: Property+prop_subsetsExactly_2 =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll (gen xs) $ \(ys, k) ->+        let a :: ZDD o+            a = ZDD.subsetsExactly xs k+         in counterexample (show a) $ ys `ZDD.member` a+  where+    gen xs = do+      ys <- sublistOf (IntMap.toList xs)+      return (IntSet.fromList [y | (y,_) <- ys], sum [w | (_,w) <- ys])++prop_subsetsExactlyIntegral :: Property+prop_subsetsExactlyIntegral =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrarySmallIntMap $ \(xs :: IntMap Integer) ->+      forAll arbitrary $ \k ->+        (ZDD.subsetsExactlyIntegral xs k :: ZDD o) === ZDD.subsetsExactly xs k++prop_combinations_are_combinations :: Property+prop_combinations_are_combinations =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \xs ->+      forAll arbitrary $ \(NonNegative k) ->+        let a :: ZDD o+            a = ZDD.combinations xs k+         in counterexample (show a) $+              not (ZDD.null a)+              ==>+              (forAll (arbitraryMember a) $ \ys -> (ys `IntSet.isSubsetOf` xs) .&&. (IntSet.size ys === k))++prop_combinations_size :: Property+prop_combinations_size =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \xs ->+      forAll arbitrary $ \(NonNegative k) ->+        let a :: ZDD o+            a = ZDD.combinations xs k+            n = toInteger $ IntSet.size xs+         in counterexample (show a) $ ZDD.size a === (product [(n - toInteger k + 1)..n] `div` (product [1..toInteger k]))++case_toList_lazyness :: Assertion+case_toList_lazyness = do+  let xss :: ZDD ZDD.AscOrder+      xss = ZDD.subsets (IntSet.fromList [1..128])+  deepseq (take 100 (Exts.toList xss)) $ return ()++prop_toList_sorted :: Property+prop_toList_sorted =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(xss :: ZDD o) ->+      let yss :: [[OrderedItem o]]+          yss = map (sort . map OrderedItem . IntSet.toList) $ take 100 $ Exts.toList xss+       in yss ===  sort yss+ prop_toList_fromList :: Property prop_toList_fromList =   forAllItemOrder $ \(_ :: Proxy o) ->@@ -349,7 +469,7 @@ prop_toSetOfIntSets_fromSetOfIntSets :: Property prop_toSetOfIntSets_fromSetOfIntSets =   forAllItemOrder $ \(_ :: Proxy o) ->-    forAll (liftM (Set.fromList . map IntSet.fromList) arbitrary) $ \xss ->+    forAll arbitrary $ \xss ->       let a :: ZDD o           a = ZDD.fromSetOfIntSets xss        in counterexample (show a) $ ZDD.toSetOfIntSets a === xss@@ -365,14 +485,14 @@ prop_insert =   forAllItemOrder $ \(_ :: Proxy o) ->     forAll arbitrary $ \(a :: ZDD o) ->-      forAll (liftM IntSet.fromList arbitrary) $ \xs ->+      forAll arbitrary $ \xs ->         ZDD.toSetOfIntSets (ZDD.insert xs a) === Set.insert xs (ZDD.toSetOfIntSets a)  prop_insert_idempotent :: Property prop_insert_idempotent =   forAllItemOrder $ \(_ :: Proxy o) ->     forAll arbitrary $ \(a :: ZDD o) ->-      forAll (liftM IntSet.fromList arbitrary) $ \xs ->+      forAll arbitrary $ \xs ->         let b = ZDD.insert xs a          in counterexample (show b) $ ZDD.insert xs b === b @@ -380,14 +500,14 @@ prop_delete =   forAllItemOrder $ \(_ :: Proxy o) ->     forAll arbitrary $ \(a :: ZDD o) ->-      forAll (liftM IntSet.fromList $ sublistOf (IntSet.toList (ZDD.flatten a))) $ \xs ->+      forAll (subsetOf (ZDD.flatten a)) $ \xs ->         ZDD.toSetOfIntSets (ZDD.delete xs a) === Set.delete xs (ZDD.toSetOfIntSets a)  prop_delete_idempotent :: Property prop_delete_idempotent =   forAllItemOrder $ \(_ :: Proxy o) ->     forAll arbitrary $ \(a :: ZDD o) ->-      forAll (liftM IntSet.fromList $ sublistOf (IntSet.toList (ZDD.flatten a))) $ \xs ->+      forAll (subsetOf (ZDD.flatten a)) $ \xs ->         let b = ZDD.delete xs a          in counterexample (show b) $ ZDD.delete xs b === b @@ -462,7 +582,7 @@ prop_member_2 =   forAllItemOrder $ \(_ :: Proxy o) ->     forAll arbitrary $ \(a :: ZDD o) ->-      forAll (liftM IntSet.fromList $ sublistOf (IntSet.toList (ZDD.flatten a))) $ \s2 ->+      forAll (subsetOf (ZDD.flatten a)) $ \s2 ->         (s2 `ZDD.member` a) === (s2 `Set.member` ZDD.toSetOfIntSets a)  prop_size :: Property@@ -507,6 +627,13 @@     forAll arbitrary $ \(a :: ZDD o, b) ->       ZDD.disjoint a b === ZDD.null (a `ZDD.intersection` b) +case_numNodes :: Assertion+case_numNodes = do+  let bdd, bdd1 :: ZDD ZDD.AscOrder+      bdd = ZDD.Branch 0 (ZDD.Branch 1 ZDD.empty bdd1) (ZDD.Branch 2 ZDD.empty bdd1)+      bdd1 = ZDD.Branch 3 ZDD.empty ZDD.base+  ZDD.numNodes bdd @?= 6+ prop_flatten :: Property prop_flatten =   forAllItemOrder $ \(_ :: Proxy o) ->@@ -517,20 +644,20 @@ prop_uniformM =   forAllItemOrder $ \(_ :: Proxy o) ->     forAll (arbitrary `suchThat` ((>= (2::Integer)) . ZDD.size)) $ \(a :: ZDD o) ->-      QM.monadicIO $ do-        gen <- QM.run Rand.create+      forAll arbitrary $ \(seed :: Vector Word32) ->         let m :: Integer             m = ZDD.size a             n = 1000-        samples <- QM.run $ replicateM n $ ZDD.uniformM a gen-        let hist_actual = Map.fromListWith (+) [(s, 1) | s <- samples]+            samples = runST $ do+              gen <- Rand.initialize seed+              replicateM n $ ZDD.uniformM a gen+            hist_actual = Map.fromListWith (+) [(s, 1) | s <- samples]             hist_expected = [(s, fromIntegral n / fromIntegral m) | s <- ZDD.toListOfIntSets a]             chi_sq = sum [(Map.findWithDefault 0 s hist_actual - cnt) ** 2 / cnt | (s, cnt) <- hist_expected]-            threshold = complQuantile (chiSquared (fromIntegral m - 1)) 0.001-        QM.monitor $ counterexample $ show hist_actual ++ " /= " ++ show (Map.fromList hist_expected)-        QM.assert $ and [xs `ZDD.member` a | xs <- Map.keys hist_actual]-        QM.monitor $ counterexample $ "χ² = " ++ show chi_sq ++ " >= " ++ show threshold-        QM.assert $ chi_sq < threshold+            threshold = complQuantile (chiSquared (fromIntegral m - 1)) 0.0001+         in counterexample (show hist_actual ++ " /= " ++ show (Map.fromList hist_expected)) $+              and [xs `ZDD.member` a | xs <- Map.keys hist_actual] .&&.+              counterexample ("χ² = " ++ show chi_sq ++ " >= " ++ show threshold) (chi_sq < threshold)  prop_findMinSum :: Property prop_findMinSum =@@ -562,6 +689,55 @@  -- ------------------------------------------------------------------------ +case_fold_laziness :: Assertion+case_fold_laziness = do+  let bdd :: ZDD ZDD.AscOrder+      bdd = ZDD.Branch 0 (ZDD.Branch 1 ZDD.Empty ZDD.Base) (ZDD.Branch 2 ZDD.Empty ZDD.Base)+      f x lo _hi =+        if x == 2 then+          error "unused value should not be evaluated"+        else+          lo+  seq (ZDD.fold f id bdd) $ return ()++case_fold'_strictness :: Assertion+case_fold'_strictness = do+  ref <- newIORef False+  let bdd :: ZDD ZDD.AscOrder+      bdd = ZDD.Branch 0 (ZDD.Branch 1 ZDD.Empty ZDD.Base) (ZDD.Branch 2 ZDD.Empty ZDD.Base)+      f x lo _hi = unsafePerformIO $ do+        when (x==2) $ writeIORef ref True+        return lo+  seq (ZDD.fold' f id bdd) $ do+    flag <- readIORef ref+    assertBool "unused value should be evaluated" flag++prop_fold_inSig :: Property+prop_fold_inSig =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(zdd :: ZDD o) ->+      ZDD.fold (\x lo hi -> ZDD.inSig (ZDD.SBranch x lo hi)) (ZDD.inSig . ZDD.SLeaf) zdd+      ===+      zdd++prop_fold'_inSig :: Property+prop_fold'_inSig =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(zdd :: ZDD o) ->+      ZDD.fold' (\x lo hi -> ZDD.inSig (ZDD.SBranch x lo hi)) (ZDD.inSig . ZDD.SLeaf) zdd+      ===+      zdd++-- ------------------------------------------------------------------------++prop_unfoldHashable_outSig :: Property+prop_unfoldHashable_outSig =+  forAllItemOrder $ \(_ :: Proxy o) ->+    forAll arbitrary $ \(zdd :: ZDD o) ->+      ZDD.unfoldHashable ZDD.outSig zdd === zdd++-- ------------------------------------------------------------------------+ prop_toGraph_fromGraph :: Property prop_toGraph_fromGraph = do   forAllItemOrder $ \(_ :: Proxy o) ->@@ -674,6 +850,19 @@   ZDD.union tmp tmp2 @?= ZDD.union tmp ZDD.base   -- 3. DIFF   ZDD.difference tmp tmp2 @?= ZDD.fromListOfIntSets (map IntSet.fromList [[1], [2]])++-- ------------------------------------------------------------------------++subsetOf :: IntSet -> Gen IntSet+subsetOf = liftM IntSet.fromList . sublistOf . IntSet.toList++arbitrarySmallIntMap :: Arbitrary a => Gen (IntMap a)+arbitrarySmallIntMap = do+  n <- choose (0, 12)+  liftM IntMap.fromList $ replicateM n $ do+    k <- arbitrary+    v <- arbitrary+    return (k, v)  -- ------------------------------------------------------------------------ 
+ test/doctests.hs view
@@ -0,0 +1,10 @@+module Main (main) where++import Test.DocTest++main :: IO ()+main = doctest+  [ "-isrc"+  , "src/Data/DecisionDiagram/BDD.hs"+  , "src/Data/DecisionDiagram/ZDD.hs"+  ]