logic-classes 1.4.6 → 1.4.7
raw patch · 11 files changed
+385/−39 lines, 11 filesdep −incremental-sat-solver
Dependencies removed: incremental-sat-solver
Files
- Data/Boolean.hs +157/−0
- Data/Boolean/SatSolver.hs +183/−0
- Data/Logic/Classes/Literal.hs +1/−2
- Data/Logic/Failing.hs +27/−0
- Data/Logic/Harrison/Lib.hs +3/−26
- Data/Logic/Harrison/Normal.hs +2/−1
- Data/Logic/Harrison/PropExamples.hs +1/−1
- Data/Logic/Harrison/Resolution.hs +2/−2
- Data/Logic/Harrison/Unif.hs +2/−2
- Data/Logic/Tests/Harrison/Unif.hs +1/−2
- logic-classes.cabal +6/−3
+ Data/Boolean.hs view
@@ -0,0 +1,157 @@+{-# OPTIONS -fno-warn-incomplete-patterns #-}+-- |+-- Module : Data.Boolean+-- Copyright : Sebastian Fischer+-- License : BSD3+-- +-- Maintainer : Sebastian Fischer (sebf@informatik.uni-kiel.de)+-- Stability : experimental+-- Portability : portable+-- +-- This library provides a representation of boolean formulas that is+-- used by the solver in "Data.Boolean.SatSolver".+-- +-- We also define a function to simplify formulas, a type for+-- conjunctive normalforms, and a function that creates them from+-- boolean formulas.+-- +module Data.Boolean ( ++ Boolean(..), ++ Literal(..), literalVar, invLiteral, isPositiveLiteral, ++ CNF, Clause, booleanToCNF++ ) where++import Data.Maybe ( mapMaybe )+import qualified Data.IntMap as IM++import Control.Monad ( guard, liftM )++-- | Boolean formulas are represented as values of type @Boolean@.+-- +data Boolean+ -- | Variables are labeled with an @Int@,+ = Var Int+ -- | @Yes@ represents /true/,+ | Yes+ -- | @No@ represents /false/,+ | No+ -- | @Not@ constructs negated formulas,+ | Not Boolean+ -- | and finally we provide conjunction+ | Boolean :&&: Boolean+ -- | and disjunction of boolean formulas.+ | Boolean :||: Boolean+ deriving Show++-- | Literals are variables that occur either positively or negatively.+-- +data Literal = Pos Int | Neg Int deriving (Eq, Show)++-- | This function returns the name of the variable in a literal.+-- +literalVar :: Literal -> Int+literalVar (Pos n) = n+literalVar (Neg n) = n++-- | This function negates a literal.+-- +invLiteral :: Literal -> Literal+invLiteral (Pos n) = Neg n+invLiteral (Neg n) = Pos n++-- | This predicate checks whether the given literal is positive.+-- +isPositiveLiteral :: Literal -> Bool+isPositiveLiteral (Pos _) = True+isPositiveLiteral _ = False++-- | Conjunctive normalforms are lists of lists of literals.+-- +type CNF = [Clause]+type Clause = [Literal]++-- | +-- We convert boolean formulas to conjunctive normal form by pushing+-- negations down to variables and repeatedly applying the+-- distributive laws.+-- +booleanToCNF :: Boolean -> CNF+booleanToCNF+ = mapMaybe (simpleClause . map literal . disjunction)+ . conjunction+ . asLongAsPossible distribute+ . asLongAsPossible pushNots+ . asLongAsPossible elim+ where+ elim (Not Yes) = Just No+ elim (Not No) = Just Yes+ elim (No :&&: _) = Just No+ elim (Yes :&&: x) = Just x+ elim (_ :&&: No) = Just No+ elim (x :&&: Yes) = Just x + elim (Yes :||: _) = Just Yes+ elim (No :||: x) = Just x+ elim (_ :||: Yes) = Just Yes+ elim (x :||: No) = Just x+ elim _ = Nothing++ pushNots (Not (Not x)) = Just x+ pushNots (Not (x:&&:y)) = Just (Not x :||: Not y)+ pushNots (Not (x:||:y)) = Just (Not x :&&: Not y)+ pushNots _ = Nothing++ distribute (x:||:(y:&&:z)) = Just ((x:||:y):&&:(x:||:z))+ distribute ((x:&&:y):||:z) = Just ((x:||:z):&&:(y:||:z))+ distribute _ = Nothing++ literal (Var x) = Pos x+ literal (Not (Var x)) = Neg x+++-- private helper functions++-- remove duplicate literals from clauses and drop clauses that+-- contain one literal both positively and negatively.+--+simpleClause :: Clause -> Maybe Clause+simpleClause = liftM (map lit . IM.toList) . foldl add (Just IM.empty)+ where+ lit (x,True) = Pos x+ lit (x,False) = Neg x++ add mm l = do+ m <- mm+ let x = literalVar l; kind = isPositiveLiteral l+ maybe (Just (IM.insert x kind m))+ (\b -> guard (b==kind) >> Just m)+ (IM.lookup x m)++conjunction :: Boolean -> [Boolean]+conjunction b = flat b []+ where flat Yes = id+ flat (x:&&:y) = flat x . flat y+ flat x = (x:)++disjunction :: Boolean -> [Boolean]+disjunction b = flat b []+ where flat No = id+ flat (x:||:y) = flat x . flat y+ flat x = (x:)++asLongAsPossible :: (Boolean -> Maybe Boolean) -> Boolean -> Boolean+asLongAsPossible f = everywhere g+ where g x = maybe x (everywhere g) (f x)++everywhere :: (Boolean -> Boolean) -> Boolean -> Boolean+everywhere f = f . atChildren (everywhere f)++atChildren :: (Boolean -> Boolean) -> Boolean -> Boolean+atChildren f (Not x) = Not (f x)+atChildren f (x:&&:y) = f x :&&: f y+atChildren f (x:||:y) = f x :||: f y+atChildren _ x = x+
+ Data/Boolean/SatSolver.hs view
@@ -0,0 +1,183 @@+-- |+-- Module : Data.Boolean.SatSolver+-- Copyright : Sebastian Fischer+-- License : BSD3+-- +-- Maintainer : Sebastian Fischer (sebf@informatik.uni-kiel.de)+-- Stability : experimental+-- Portability : portable+-- +-- This Haskell library provides an implementation of the+-- Davis-Putnam-Logemann-Loveland algorithm+-- (cf. <http://en.wikipedia.org/wiki/DPLL_algorithm>) for the boolean+-- satisfiability problem. It not only allows to solve boolean+-- formulas in one go but also to add constraints and query bindings+-- of variables incrementally.+-- +-- The implementation is not sophisticated at all but uses the basic+-- DPLL algorithm with unit propagation.+-- +module Data.Boolean.SatSolver (++ Boolean(..), SatSolver, Literal(..), literalVar, invLiteral, isPositiveLiteral, CNF, Clause, booleanToCNF,++ newSatSolver, isSolved, ++ lookupVar, assertTrue, assertTrue', branchOnVar, selectBranchVar, solve, isSolvable++ ) where++import Data.List+import Data.Boolean++import Control.Monad.Writer++import qualified Data.IntMap as IM++-- | A @SatSolver@ can be used to solve boolean formulas.+-- +data SatSolver = SatSolver { clauses :: CNF, bindings :: IM.IntMap Bool }+ deriving Show++-- | A new SAT solver without stored constraints.+-- +newSatSolver :: SatSolver+newSatSolver = SatSolver [] IM.empty++-- | This predicate tells whether all constraints are solved.+-- +isSolved :: SatSolver -> Bool+isSolved = null . clauses++-- |+-- We can lookup the binding of a variable according to the currently+-- stored constraints. If the variable is unbound, the result is+-- @Nothing@.+-- +lookupVar :: Int -> SatSolver -> Maybe Bool+lookupVar name = IM.lookup name . bindings++-- | +-- We can assert boolean formulas to update a @SatSolver@. The+-- assertion may fail if the resulting constraints are unsatisfiable.+-- +assertTrue :: MonadPlus m => Boolean -> SatSolver -> m SatSolver+assertTrue formula solver = do+ newClauses <- foldl (addClause (bindings solver))+ (return (clauses solver))+ (booleanToCNF formula)+ simplify (solver { clauses = newClauses })++assertTrue' :: MonadPlus m => CNF -> SatSolver -> m SatSolver+assertTrue' formula solver = do+ newClauses <- foldl (addClause (bindings solver))+ (return (clauses solver))+ formula+ simplify (solver { clauses = newClauses })++-- |+-- This function guesses a value for the given variable, if it is+-- currently unbound. As this is a non-deterministic operation, the+-- resulting solvers are returned in an instance of @MonadPlus@.+-- +branchOnVar :: MonadPlus m => Int -> SatSolver -> m SatSolver+branchOnVar name solver =+ maybe (branchOnUnbound name solver)+ (const (return solver))+ (lookupVar name solver)++-- |+-- We select a variable from the shortest clause hoping to produce a+-- unit clause.+--+selectBranchVar :: SatSolver -> Int+selectBranchVar = literalVar . head . head . sortBy shorter . clauses++-- | +-- This function guesses values for variables such that the stored+-- constraints are satisfied. The result may be non-deterministic and+-- is, hence, returned in an instance of @MonadPlus@.+-- +solve :: MonadPlus m => SatSolver -> m SatSolver+solve solver+ | isSolved solver = return solver+ | otherwise = branchOnUnbound (selectBranchVar solver) solver >>= solve++-- |+-- This predicate tells whether the stored constraints are+-- solvable. Use with care! This might be an inefficient operation. It+-- tries to find a solution using backtracking and returns @True@ if+-- and only if that fails.+-- +isSolvable :: SatSolver -> Bool+isSolvable = not . null . solve+++-- private helper functions++addClause :: MonadPlus m => IM.IntMap Bool -> m [Clause] -> Clause -> m [Clause]+addClause binds mclauses newClause = do+ oldClauses <- mclauses+ let unboundLits = foldl (addUnbound binds) (Just []) newClause+ maybe (return oldClauses)+ (\lits -> guard (not (null lits)) >> return (lits:oldClauses))+ unboundLits++addUnbound :: IM.IntMap Bool -> Maybe Clause -> Literal -> Maybe Clause+addUnbound binds mlits lit = do+ lits <- mlits+ maybe (Just (lit:lits))+ (\b -> guard (b /= isPositiveLiteral lit) >> return lits)+ (IM.lookup (literalVar lit) binds)++updateSolver :: MonadPlus m => CNF -> [(Int,Bool)] -> SatSolver -> m SatSolver+updateSolver cs bs solver = do+ bs' <- foldr (uncurry insertBinding) (return (bindings solver)) bs+ return $ solver { clauses = cs, bindings = bs' }++insertBinding :: MonadPlus m+ => Int -> Bool -> m (IM.IntMap Bool) -> m (IM.IntMap Bool)+insertBinding name newValue binds = do+ bs <- binds+ maybe (return (IM.insert name newValue bs))+ (\oldValue -> do guard (oldValue==newValue); return bs)+ (IM.lookup name bs)++simplify :: MonadPlus m => SatSolver -> m SatSolver+simplify solver = do+ (cs,bs) <- runWriterT . simplifyClauses . clauses $ solver+ updateSolver cs bs solver++simplifyClauses :: MonadPlus m => CNF -> WriterT [(Int,Bool)] m CNF+simplifyClauses [] = return []+simplifyClauses allClauses = do+ let shortestClause = head . sortBy shorter $ allClauses+ guard (not (null shortestClause))+ if null (tail shortestClause)+ then propagate (head shortestClause) allClauses >>= simplifyClauses+ else return allClauses++propagate :: MonadPlus m => Literal -> CNF -> WriterT [(Int,Bool)] m CNF+propagate literal allClauses = do+ tell [(literalVar literal, isPositiveLiteral literal)]+ return (foldr prop [] allClauses)+ where+ prop c cs | literal `elem` c = cs+ | otherwise = filter (invLiteral literal/=) c : cs++branchOnUnbound :: MonadPlus m => Int -> SatSolver -> m SatSolver+branchOnUnbound name solver =+ guess (Pos name) solver `mplus` guess (Neg name) solver++guess :: MonadPlus m => Literal -> SatSolver -> m SatSolver+guess literal solver = do+ (cs,bs) <- runWriterT (propagate literal (clauses solver) >>= simplifyClauses)+ updateSolver cs bs solver++shorter :: [a] -> [a] -> Ordering+shorter [] [] = EQ+shorter [] _ = LT+shorter _ [] = GT+shorter (_:xs) (_:ys) = shorter xs ys++
Data/Logic/Classes/Literal.hs view
@@ -10,7 +10,6 @@ , foldAtomsLiteral ) where -import Control.Applicative.Error (Failing(..)) import Data.Logic.Classes.Combine (Combination(..)) import Data.Logic.Classes.Constants import qualified Data.Logic.Classes.FirstOrder as FOF@@ -18,7 +17,7 @@ import Data.Logic.Classes.Pretty (HasFixity(..), Fixity(..), FixityDirection(..)) import qualified Data.Logic.Classes.Propositional as P import Data.Logic.Classes.Negate-import Data.Logic.Harrison.Lib ({- instance Monad Failing -})+import Data.Logic.Failing (Failing(..)) import Text.PrettyPrint (Doc, (<>), text, parens, nest) -- |Literals are the building blocks of the clause and implicative normal
+ Data/Logic/Failing.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE DeriveDataTypeable, StandaloneDeriving #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Data.Logic.Failing+ ( Failing(Success, Failure)+ , failing+ ) where++import Control.Applicative.Error+import Data.Generics++failing :: ([String] -> b) -> (a -> b) -> Failing a -> b+failing f _ (Failure errs) = f errs+failing _ f (Success a) = f a++instance Monad Failing where+ return = Success+ m >>= f =+ case m of+ (Failure errs) -> (Failure errs)+ (Success a) -> f a+ fail errMsg = Failure [errMsg]++deriving instance Typeable1 Failing+deriving instance Data a => Data (Failing a)+deriving instance Read a => Read (Failing a)+deriving instance Eq a => Eq (Failing a)+deriving instance Ord a => Ord (Failing a)
Data/Logic/Harrison/Lib.hs view
@@ -1,8 +1,7 @@ {-# LANGUAGE DeriveDataTypeable, RankNTypes, StandaloneDeriving #-} {-# OPTIONS_GHC -Wall -fno-warn-unused-binds #-} module Data.Logic.Harrison.Lib- ( failing- , tests+ ( tests , setAny , setAll -- , itlist2@@ -35,35 +34,12 @@ , (∅) ) where -import Control.Applicative.Error (Failing(..), ErrorMsg)-import Data.Generics+import Data.Logic.Failing (Failing(..), failing) import qualified Data.Map as Map import Data.Maybe import qualified Data.Set as Set import Test.HUnit (Test(TestCase, TestList, TestLabel), assertEqual) --- | Case analysis for the 'Failing' type, from unpublished changes to --- the applicative-extras packages. If the value is @'Failure'@, apply --- the first function to @[ErrorMsg]@; if it is @'Success' a@, apply --- the second function to @a@.-failing :: ([ErrorMsg] -> b) -> (a -> b) -> Failing a -> b-failing f _ (Failure errs) = f errs-failing _ f (Success a) = f a- -instance Monad Failing where- return = Success- m >>= f =- case m of- (Failure errs) -> (Failure errs)- (Success a) -> f a- fail errMsg = Failure [errMsg]- -deriving instance Typeable1 Failing-deriving instance Data a => Data (Failing a)-deriving instance Read a => Read (Failing a)-deriving instance Eq a => Eq (Failing a)-deriving instance Ord a => Ord (Failing a)- (∅) :: Set.Set a (∅) = Set.empty @@ -117,6 +93,7 @@ -- let can f x = try f x; true with Failure _ -> false;; can :: (t -> Failing a) -> t -> Bool can f x = failing (const True) (const False) (f x)+ {- let rec repeat f x = try repeat f (f x) with Failure _ -> x;;
Data/Logic/Harrison/Normal.hs view
@@ -15,7 +15,8 @@ import Data.Logic.Classes.Formula (Formula(atomic)) import Data.Logic.Classes.Literal (Literal, fromFirstOrder) import Data.Logic.Classes.Negate (Negatable, negated, (.~.))-import Data.Logic.Harrison.Lib (setAny, allpairs, failing)+import Data.Logic.Failing (failing)+import Data.Logic.Harrison.Lib (setAny, allpairs) import Data.Logic.Harrison.Skolem (nnf) import qualified Data.Set.Extra as Set import Prelude hiding (negate)
Data/Logic/Harrison/PropExamples.hs view
@@ -352,7 +352,7 @@ -- For large examples, should use "num" instead of "int" in these functions. -- ------------------------------------------------------------------------- -bitlength :: forall b a. (Bits b, Num a) => b -> a+bitlength :: forall b a. (Num a, Num b, Bits b) => b -> a bitlength x = if x == 0 then 0 else 1 + bitlength (shiftR x 1);; bit :: forall a b. (Num a, Eq a, Bits b, Integral b) => a -> b -> Bool
Data/Logic/Harrison/Resolution.hs view
@@ -8,7 +8,6 @@ , matchAtomsEq ) where -import Control.Applicative.Error (Failing(..)) import Data.Logic.Classes.Atom (Atom(match)) import Data.Logic.Classes.Combine (Combination(..)) import Data.Logic.Classes.Equals (AtomEq, zipAtomsEq)@@ -18,9 +17,10 @@ import Data.Logic.Classes.Propositional (PropositionalFormula) import Data.Logic.Classes.Term (Term(vt, foldTerm)) import Data.Logic.Classes.Variable (Variable(prefix))+import Data.Logic.Failing (Failing(..), failing) import Data.Logic.Harrison.FOL (subst, fv, generalize, list_disj, list_conj) import Data.Logic.Harrison.Lib (settryfind, allpairs, allsubsets, setAny, setAll,- allnonemptysubsets, (|->), apply, defined, failing)+ allnonemptysubsets, (|->), apply, defined) import Data.Logic.Harrison.Normal (simpdnf, simpcnf, trivial) import Data.Logic.Harrison.Skolem (pnf, SkolemT, askolemize, specialize) import Data.Logic.Harrison.Tableaux (unify_literals)
Data/Logic/Harrison/Unif.hs view
@@ -6,9 +6,8 @@ , unifyAndApply ) where -import Control.Applicative.Error (Failing(..)) import Data.Logic.Classes.Term (Term(..), tsubst)-import Data.Logic.Harrison.Lib (failing)+import Data.Logic.Failing (Failing(..), failing) import qualified Data.Map as Map {- (* ========================================================================= *)@@ -34,6 +33,7 @@ if any (failing (const False) id . isTrivial env x) args then Failure ["cyclic"] else Success False+ {- foldT (\ y -> y == x || (defined env y && istriv env x (apply env y))) (\ _ args -> if any (istriv env x) args then error "cyclic" else False)
Data/Logic/Tests/Harrison/Unif.hs view
@@ -4,9 +4,8 @@ ( tests ) where -import Control.Applicative.Error (Failing(..)) import Data.Logic.Classes.Term (Term(fApp, vt), tsubst)-import Data.Logic.Harrison.Lib (failing)+import Data.Logic.Failing (Failing(..), failing) import Data.Logic.Harrison.Unif (fullUnify) import Data.Logic.Tests.HUnit () import Data.Logic.Types.Harrison.FOL (TermType)
logic-classes.cabal view
@@ -1,5 +1,5 @@ Name: logic-classes-Version: 1.4.6+Version: 1.4.7 Synopsis: Framework for propositional and first order logic, theorem proving Description: Package to support Propositional and First Order Logic. It includes classes representing the different types of formulas and terms, some instances of@@ -34,6 +34,7 @@ Data.Logic.Classes.Skolem Data.Logic.Classes.Term Data.Logic.Classes.Variable+ Data.Logic.Failing Data.Logic.Harrison.DefCNF Data.Logic.Harrison.DP Data.Logic.Harrison.Equal@@ -70,13 +71,15 @@ Data.Logic.Types.Harrison.Formulas.Propositional Data.Logic.Types.Harrison.Prop Data.Logic.Types.Propositional- Build-Depends: applicative-extras, base >= 4.3 && < 5, containers, fgl, HUnit, incremental-sat-solver,+ Data.Boolean+ Data.Boolean.SatSolver+ Build-Depends: applicative-extras, base >= 4.3 && < 5, containers, fgl, HUnit, mtl, syb-with-class, text, PropLogic >= 0.9.0.3, pretty, safecopy, set-extra, syb, template-haskell Executable tests GHC-Options: -Wall -O2 Main-Is: Data/Logic/Tests/Main.hs- Build-Depends: applicative-extras, base, containers, HUnit, incremental-sat-solver, mtl,+ Build-Depends: applicative-extras, base, containers, HUnit, mtl, pretty, PropLogic, safecopy, set-extra, syb, template-haskell Other-Modules: Data.Logic.Tests.Chiou0 Data.Logic.Tests.Common