packages feed

satplus-0.1.0.0: SAT.hs

{-|
Module      : SAT
Description : Basic SAT operations

This module provides basic functions for working with Solver objects. A simple
example of typical use is:

@
main :: IO ()
main = do s <- newSolver
          x <- newLit s
          y <- newLit s
          addClause s [neg x, neg y]
          addClause s [x, y]
          b <- solve s []
          if b then
            do putStrLn \"Found model!\"
               a <- modelValue s x
               b <- modelValue s y
               putStrLn (\"x=\" ++ show a ++ \", y=\" ++ show b)
           else
            do putStrLn \"No model found.\"
          deleteSolver s
@
-}
module SAT(
  -- * The Solver object
    Solver
  , newSolver
  , deleteSolver
  , withNewSolver
  , numAssigns
  , numClauses
  , numLearnts
  , numVars
  , numFreeVars
  , numConflicts

  -- * Literals
  , Lit
  , newLit
  , false, true
  , bool
  , neg
  , pos

  -- * Clauses
  , addClause

  -- * Solving
  , solve
  , modelValue
  , modelValueMaybe
  , conflict

  -- * Implied constants
  , valueMaybe
  )
 where

import qualified MiniSat as M
import Data.IORef
import Data.Maybe( fromMaybe )

------------------------------------------------------------------------------
-- The Solver object

-- | The type of a Solver object
data Solver = Solver M.Solver (IORef (Maybe Lit))

-- | Create a Solver object.
newSolver :: IO Solver
newSolver =
  do s <- M.newSolver
     ref <- newIORef Nothing
     return (Solver s ref)

-- | Delete a Solver object. Use only once!
deleteSolver :: Solver -> IO ()
deleteSolver (Solver s _) =
  do M.deleteSolver s

-- | Create a Solver object, and delete when done.
withNewSolver :: (Solver -> IO a) -> IO a
withNewSolver h =
  M.withNewSolver $ \s ->
    do ref <- newIORef Nothing
       h (Solver s ref)

-- | The current number of assigned literals.
numAssigns :: Solver -> IO Int
numAssigns (Solver m _) = M.minisat_num_assigns m

-- | The current number of original clauses.
numClauses :: Solver -> IO Int
numClauses (Solver m _) = M.minisat_num_clauses m

-- | The current number of learnt clauses.
numLearnts :: Solver -> IO Int
numLearnts (Solver m _) = M.minisat_num_learnts m

-- | The current number of variables.
numVars :: Solver -> IO Int
numVars (Solver m _) = M.minisat_num_vars m

numFreeVars :: Solver -> IO Int
numFreeVars (Solver m _) = M.minisat_num_freeVars m

numConflicts :: Solver -> IO Int
numConflicts (Solver m _) = M.minisat_num_conflicts m

------------------------------------------------------------------------------
-- Literals

-- | The type of a literal
data Lit = Bool Bool | Lit M.Lit
 deriving ( Eq, Ord )

instance Show Lit where
  show (Bool b) = show b
  show (Lit x)  = show x

-- | Create a fresh literal in a given Solver.
newLit :: Solver -> IO Lit
newLit (Solver s _) = Lit `fmap` M.newLit s

-- | Constant literal.
true, false :: Lit
true  = Bool True
false = Bool False

-- | Create a constant literal based on a Bool.
bool :: Bool -> Lit
bool = Bool

-- | Negate a literal.
neg :: Lit -> Lit
neg (Bool b) = Bool (not b)
neg (Lit x)  = Lit (M.neg x)

-- | Return the sign of a literal. The sign flips when a literal is negated.
pos :: Lit -> Bool
pos x = x < neg x

------------------------------------------------------------------------------
-- Clauses

-- | Add a clause in a given Solver. (The argument list is thus /disjunctive/.)
addClause :: Solver -> [Lit] -> IO ()
addClause (Solver s _) xs
  | true `elem` xs = do return ()
  | otherwise      = do M.addClause s [ x | Lit x <- xs ]; return ()

------------------------------------------------------------------------------
-- Solving

-- | Try to find a model of all clauses in the given Solver, under the
-- assumptions of the given arguments. (The argument list is thus /conjunctive/.)
-- Returns True if a model was found, False if no model was found.
solve :: Solver -> [Lit] -> IO Bool
solve (Solver s ref) xs
  | false `elem` xs =
    do writeIORef ref (Just true)
       return False

  | otherwise =
    do writeIORef ref Nothing
       M.solve s [ x | Lit x <- xs ]

-- | If the last call to 'solve' returned False: Return the conflict clause
-- that was the reason for the fact that no model was found under the
-- specified assumptions. The conflict clause only contains literals that
-- are negations of the assumptions given to 'solve'. The conflict
-- clause is always logically implied by the current set of clauses.
--
-- For example, if the returned clause is empty, there is a contradiction even
-- without any assumptions.
--
-- This function can be used to implement so-called \'unsatisfiable cores\'.
--
-- There are no guarantees about minimality of the returned clause.
-- (/Only use when 'solve' has previously returned False!/)
conflict :: Solver -> IO [Lit]
conflict (Solver s ref) =
  do mx <- readIORef ref
     case mx of
       Nothing -> do xs <- M.conflict s
                     return (map Lit xs)
       Just x  -> do return [x]

------------------------------------------------------------------------------

-- | If the last call to 'solve' returned True, return the value of
-- the specified literal in the found model.
-- (/Only use when 'solve' has previously returned True!/)
modelValue :: Solver -> Lit -> IO Bool
modelValue s x =
  do mb <- modelValueMaybe s x
     return (fromMaybe (not (pos x)) mb)

-- | If the last call to 'solve' returned True, return the value of
-- the specified literal in the found model, or Nothing if there is a model
-- regardless of the value of this literal.
-- There are no guarantees about when Nothing is returned.
-- (/Only use when 'solve' has previously returned True!/)
modelValueMaybe :: Solver -> Lit -> IO (Maybe Bool)
modelValueMaybe _ (Bool b) =
  do return (Just b)

modelValueMaybe (Solver s _) (Lit x) =
  do M.modelValue s x

------------------------------------------------------------------------------
-- Implied constants

-- | Check whether or not a given literal has received a top-level value
-- in the given Solver. This can happen when the literal is implied to be
-- False or True by the current set of clauses. There are no guarantees about
-- when this actually happens.
valueMaybe :: Solver -> Lit -> IO (Maybe Bool)
valueMaybe _            (Bool b) = return (Just b)
valueMaybe (Solver s _) (Lit x)  = M.value s x

------------------------------------------------------------------------------