{-# LANGUAGE DeriveDataTypeable #-} {-# OPTIONS_GHC -fglasgow-exts #-} -- A Haskell implementation of the basic algorithm (with non-chronological -- backtracking) from "SAT-MICRO: petit mais costaud!" by Sylvain Conchon, -- Johannes Kanig, and Stephane Lescuyer. -- To compile: -- ghc --make -O -W -o ms -main-is MicroSat MicroSat.hs -- Backtracking uses the control stack, so, you may want to invoke with -- something like -- ms +RTS -K768M -RTS -- depending on the size of the SAT instance. module MicroSat where import Control.Monad.Cont import Control.Monad.State.Strict hiding ((>=>)) import Data.Foldable hiding (sequence_) import Data.List hiding (elem, concat, foldl', any, all) import Data.Map (Map) import Data.Set (Set) import Data.Typeable import Debug.Trace() import Prelude hiding (or, and, all, any, elem, minimum, foldr, splitAt, concatMap, foldl, catch) import System(getArgs) import System.Exit import Text.PrettyPrint.HughesPJ import qualified Data.Foldable as Foldable import qualified Data.List as L import qualified Data.Map as Map import qualified Data.Set as Set import qualified ParseCnf usage = "Usage:\nms " main = getArgs >>= \args -> case args of [] -> putStrLn usage "-verify":_ -> do putStrLn "*Testing*" undefined path:_ -> readFile path >>= parseAndSolve where parseAndSolve contents = let cnf = asCnf $ ParseCnf.parseCNF path contents in do putStrLn ("Solving " ++ path ++ "...") let result = dpll cnf when (not (verifyResult result cnf)) $ do print "VERIFICATION ERROR" exitWith (ExitFailure 1) print result type Cnf = [[Lit]] asCnf :: ParseCnf.CNF -> Cnf asCnf (ParseCnf.CNF _ _ is) = map (nub . map fromInteger) $ is data Result = Sat [Lit] | Unsat instance Show Result where show (Sat lits) = "satisfiable: " ++ intercalate " " (map show lits) show Unsat = "unsatisfiable" newtype Lit = L {unLit :: Int} deriving (Eq, Ord, Typeable) inLit f = L . f . unLit instance Show Lit where show = show . unLit instance Read Lit where readsPrec i s = map (\(i,s) -> (L i, s)) (readsPrec i s :: [(Int, String)]) vari :: Lit -> Int vari = abs . unLit instance Num Lit where _ + _ = error "+ doesn't make sense for literals" _ - _ = error "- doesn't make sense for literals" _ * _ = error "* doesn't make sense for literals" signum _ = error "signum doesn't make sense for literals" negate = inLit negate abs = inLit abs fromInteger l | l == 0 = error "0 is not a literal" | otherwise = L $ fromInteger l -- gamma = annotated assignment literals -- delta = annotated cnf data StateContents = S { gamma :: Map Lit (Set Lit), delta :: [([Lit], Set Lit)] } instance Show StateContents where show = render . stateDoc where stateDoc (S {gamma=gamma, delta=delta}) = brackets (hcat . intersperse space . map (text . show) $ Map.keys gamma) <+> braces (hcat . intersperse (comma <> space) . map (\(c, a) -> braces (hcat . intersperse space . map (text . show) $ c) <> tups (hcat . intersperse comma . map (text . show) $ Set.toList a)) $ delta) where tups p = char '<' <> p <> char '>' dpll :: Cnf -> Result dpll f = (`runCont` id) $ do r <- callCC $ \bj -> do (Right env) <- bcp bj (initialState f) unsat env return either (const $ return Unsat) (return . Sat) r dispatch d = map (\l -> (l, d)) initialState f = S Map.empty (dispatch Set.empty f) fromRight (Right a) = a fromRight (Left _) = error "fromRight: Left" getGamma l e = Map.findWithDefault (error $ show l ++ ": annotation not found") l (gamma e) -- bcp either: -- 1. finds a conflict and returns annotation literals (Left) -- 2. computes a new environment (Right) assume bj env (l, s) = -- update only if not present if l `Map.member` gamma env then return (Right env) else bcp bj env{gamma = Map.insert l s (gamma env)} -- | If @bcp@ finds a conflict, calls @bj@ with set of literals, the -- contributors to the conflict. bcp bj env = do env' <- foldM (\env' (cl, a) -> do let (cl_neg, cl_pos) = partition (\l -> negate l `Map.member` gamma env') cl if any (`Map.member` gamma env') cl then return env' else do -- update clause annotation let a' = foldl' (\set l -> set `Set.union` getGamma (negate l) env') a cl_neg case cl_pos of [] -> bj (Left a') [f] -> assume bj env' (f, a') >>= return . fromRight _ -> return $ env'{delta = (cl_pos, a'):(delta env')}) (env{delta = []}) (delta env) return $ Right env' -- unsat either: -- 1. returns annotation literals (Left) -- 2. finds sat assignment -- bj is the 'back jump' function, allowing conflicts to backtrack to the -- point where the last involved literal was decided. unsat env bj = case delta env of [] -> return $ Right $ Map.keys (gamma env) ([_],_):_ -> error "unpropagated unit literal" ([],_):_ -> error "conflict unresolved" ((a:_),_):_ -> do r <- callCC $ \innerBj -> do (Right env') <- assume innerBj env (a, Set.singleton a) -- done propagating, no conflicts: continue unsat env' return case r of Left d -> if not $ a `elem` d then bj (Left d) else (callCC $ \innerBj -> do (Right env') <- assume innerBj env (negate a, Set.delete a d) unsat env' bj) >>= either (bj . Left) (return . Right) Right _ -> return r paper1 :: Cnf = [[-1, -3, -4] ,[-1, -3, 4] ,[2, 3, 5] ,[3, 5] ,[3, -5]] verifyResult :: Result -> Cnf -> Bool verifyResult (Sat m) cnf = -- m is well-formed all (\l -> not $ negate l `elem` m) m && all (\cl -> any (`elem` cl) m) cnf verifyResult Unsat _ = True