module ToySolver.SAT.TseitinEncoder
(
Encoder
, newEncoder
, setUsePB
, encSolver
, Formula
, evalFormula
, addFormula
, encodeConj
, encodeDisj
, getDefinitions
) where
import Control.Monad
import Data.IORef
import Data.Map (Map)
import qualified Data.Map as Map
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
import ToySolver.Data.Boolean
import ToySolver.Data.BoolExpr
import qualified ToySolver.SAT as SAT
import qualified ToySolver.SAT.Types as SAT
type Formula = BoolExpr SAT.Lit
evalFormula :: SAT.IModel m => m -> Formula -> Bool
evalFormula m = fold (SAT.evalLit m)
data Encoder =
Encoder
{ encSolver :: SAT.Solver
, encUsePB :: IORef Bool
, encConjTable :: !(IORef (Map SAT.LitSet SAT.Lit))
}
newEncoder :: SAT.Solver -> IO Encoder
newEncoder solver = do
usePBRef <- newIORef False
table <- newIORef Map.empty
return $
Encoder
{ encSolver = solver
, encUsePB = usePBRef
, encConjTable = table
}
setUsePB :: Encoder -> Bool -> IO ()
setUsePB encoder usePB = writeIORef (encUsePB encoder) usePB
addFormula :: Encoder -> Formula -> IO ()
addFormula encoder formula = do
let solver = encSolver encoder
case formula of
And xs -> mapM_ (addFormula encoder) xs
Equiv a b -> do
lit1 <- encodeToLit encoder a
lit2 <- encodeToLit encoder b
SAT.addClause solver [SAT.litNot lit1, lit2]
SAT.addClause solver [SAT.litNot lit2, lit1]
Not (Not a) -> addFormula encoder a
Not (Or xs) -> addFormula encoder (andB (map notB xs))
Not (Imply a b) -> addFormula encoder (a .&&. notB b)
Not (Equiv a b) -> do
lit1 <- encodeToLit encoder a
lit2 <- encodeToLit encoder b
SAT.addClause solver [lit1, lit2]
SAT.addClause solver [SAT.litNot lit1, SAT.litNot lit2]
_ -> do
c <- encodeToClause encoder formula
SAT.addClause solver c
encodeToClause :: Encoder -> Formula -> IO SAT.Clause
encodeToClause encoder formula =
case formula of
And [x] -> encodeToClause encoder x
Or xs -> do
cs <- mapM (encodeToClause encoder) xs
return $ concat cs
Not (Not x) -> encodeToClause encoder x
Not (And xs) -> do
encodeToClause encoder (orB (map notB xs))
Imply a b -> do
encodeToClause encoder (notB a .||. b)
_ -> do
l <- encodeToLit encoder formula
return [l]
encodeToLit :: Encoder -> Formula -> IO SAT.Lit
encodeToLit encoder formula = do
case formula of
Atom l -> return l
And xs -> encodeConj encoder =<< mapM (encodeToLit encoder) xs
Or xs -> encodeDisj encoder =<< mapM (encodeToLit encoder) xs
Not x -> liftM SAT.litNot $ encodeToLit encoder x
Imply x y -> do
encodeToLit encoder (notB x .||. y)
Equiv x y -> do
lit1 <- encodeToLit encoder x
lit2 <- encodeToLit encoder y
encodeToLit encoder $
(Atom lit1 .=>. Atom lit2) .&&. (Atom lit2 .=>. Atom lit1)
encodeConj :: Encoder -> [SAT.Lit] -> IO SAT.Lit
encodeConj _ [l] = return l
encodeConj encoder ls = do
let ls2 = IntSet.fromList ls
table <- readIORef (encConjTable encoder)
case Map.lookup ls2 table of
Just l -> return l
Nothing -> do
let sat = encSolver encoder
v <- SAT.newVar sat
addIsConjOf encoder v ls
return v
encodeDisj :: Encoder -> [SAT.Lit] -> IO SAT.Lit
encodeDisj _ [l] = return l
encodeDisj encoder ls = do
l <- encodeConj encoder [SAT.litNot li | li <- ls]
return $ SAT.litNot l
addIsConjOf :: Encoder -> SAT.Lit -> [SAT.Lit] -> IO ()
addIsConjOf encoder l ls = do
let solver = encSolver encoder
usePB <- readIORef (encUsePB encoder)
if usePB
then do
let n = length ls
SAT.addPBAtLeast solver (( fromIntegral n, l) : [(1,li) | li <- ls]) 0
else do
forM_ ls $ \li -> do
SAT.addClause solver [SAT.litNot l, li]
SAT.addClause solver (l : map SAT.litNot ls)
modifyIORef (encConjTable encoder) (Map.insert (IntSet.fromList ls) l)
getDefinitions :: Encoder -> IO [(SAT.Lit, Formula)]
getDefinitions encoder = do
t <- readIORef (encConjTable encoder)
return $ [(l, andB [Atom l1 | l1 <- IntSet.toList ls]) | (ls, l) <- Map.toList t]