toysolver-0.8.0: app/toysat-ipasir/IPASIR.hs
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE ScopedTypeVariables #-}
module IPASIR () where
import Control.Exception
import Control.Monad
import Data.Int
import qualified Data.IntSet as IntSet
import Data.IORef
import Foreign
import Foreign.C
import qualified ToySolver.SAT as SAT
type Solver = Ptr ()
type SolverRep = (SAT.Solver, IORef [Int32], IORef [Int32])
withSolverRep :: Solver -> (SolverRep -> IO a) -> IO a
withSolverRep ptr k = do
let sptr :: StablePtr SolverRep
sptr = castPtrToStablePtr ptr
solver <- deRefStablePtr sptr
k solver
ensureVars :: SAT.Solver -> [Int32] -> IO ()
ensureVars _solver [] = return ()
ensureVars solver lits = do
let maxVar = maximum $ map (abs . fromIntegral) lits
nv <- SAT.getNVars solver
when (nv < maxVar) $ SAT.newVars_ solver (maxVar - nv)
foreign export ccall "toysat_ipasir_init" ipasir_init :: IO Solver
-- | Construct a new solver and return a pointer to it.
--
-- Use the returned pointer as the first parameter in each
-- of the following functions.
--
-- Required state: N/A
--
-- State after: @INPUT@
ipasir_init :: IO Solver
ipasir_init = do
solver <- SAT.newSolver
addBuf <- newIORef []
assumptionRef <- newIORef []
sptr <- newStablePtr (solver, addBuf, assumptionRef)
return (castStablePtrToPtr sptr)
foreign export ccall "toysat_ipasir_release" ipasir_release :: Solver -> IO ()
-- | Release the solver, i.e., all its resoruces and
-- allocated memory (destructor). The solver pointer
-- cannot be used for any purposes after this call.
--
-- Required state: @INPUT@ or @SAT@ or @UNSAT@
--
-- State after: undefined
ipasir_release :: Solver -> IO ()
ipasir_release ptr = do
let sptr :: StablePtr SolverRep
sptr = castPtrToStablePtr ptr
freeStablePtr sptr
foreign export ccall ipasir_add :: Solver -> Int32 -> IO ()
-- | Add the given literal into the currently added clause
-- or finalize the clause with a 0.
--
-- Clauses added this way cannot be removed. The addition of
-- removable clauses can be simulated using activation literals
-- and assumptions.
--
-- Required state: @INPUT@ or @SAT@ or @UNSAT@
--
-- State after: @INPUT@
--
-- Literals are encoded as (non-zero) integers as in the
-- DIMACS formats. They have to be smaller or equal to
-- @INT_MAX@ and strictly larger than @INT_MIN@ (to avoid
-- negation overflow). This applies to all the literal
-- arguments in API functions.
ipasir_add :: Solver -> Int32 -> IO ()
ipasir_add ptr lit = withSolverRep ptr $ \(solver, addBuf, _assumptionRef) -> do
if lit == 0 then do
lits <- readIORef addBuf
ensureVars solver lits
SAT.addClause solver (map fromIntegral (reverse lits))
else do
modifyIORef addBuf (lit :)
foreign export ccall ipasir_assume :: Solver -> Int32 -> IO ()
-- | Add an assumption for the next SAT search (the next call
-- of ipasir_solve).
--
-- After calling 'ipasir_solve' all the previously added assumptions
-- are cleared.
--
-- Required state: @INPUT@ or @SAT@ or @UNSAT@
--
-- State after: @INPUT@
ipasir_assume :: Solver -> Int32 -> IO ()
ipasir_assume ptr lit = withSolverRep ptr $ \(_solver, _addBuf, assumptionRef) -> do
modifyIORef assumptionRef (lit :)
foreign export ccall ipasir_solve :: Solver -> IO CInt
-- | Solve the formula with specified clauses under the specified assumptions.
--
-- * If the formula is satisfiable the function returns 10 and the state of the solver is changed to @SAT@.
-- * If the formula is unsatisfiable the function returns 20 and the state of the solver is changed to @UNSAT@.
-- * If the search is interrupted (see 'ipasir_set_terminate') the function returns 0 and the state of the solver is changed to @INPUT@.
--
-- This function can be called in any defined state of the solver.
-- Note that the state of the solver _during_ execution of @ipasir_solve@ is undefined.
--
-- Required state: @INPUT@ or @SAT@ or @UNSAT@
--
-- State after: @INPUT@ or @SAT@ or @UNSAT@
ipasir_solve :: Solver -> IO CInt
ipasir_solve ptr = withSolverRep ptr $ \(solver, _addBuf, assumptionRef) -> do
assumptions <- readIORef assumptionRef
writeIORef assumptionRef []
ensureVars solver (assumptions)
handle (\(_ :: SAT.Canceled) -> return 0) $ do
ret <- SAT.solveWith solver (map fromIntegral assumptions)
if ret then do
return 10
else do
return 20
foreign export ccall ipasir_val :: Solver -> Int32 -> IO CInt
-- | Get the truth value of the given literal in the found satisfying
-- assignment.
--
-- Return @lit@ if True, @-lit@ if False;
-- @ipasir_val(lit)@ may return 0 if the found assignment is satisfying for both valuations of lit.
-- Each solution that agrees with all non-zero values of @ipasir_val@ is a model of the formula.
--
-- This function can only be used if 'ipasir_solve' has returned @10@
-- and no 'ipasir_add' nor 'ipasir_assume' has been called
-- since then, i.e., the state of the solver is SAT.
--
-- Required state: @SAT@
--
-- State after: @SAT@
ipasir_val :: Solver -> Int32 -> IO CInt
ipasir_val ptr lit = withSolverRep ptr $ \(solver, _addBuf, _assumptionRef) -> do
nv <- SAT.getNVars solver
if nv < abs (fromIntegral lit) then do
return 0
else do
m <- SAT.getModel solver
if SAT.evalLit m (fromIntegral lit) then
return (fromIntegral lit)
else
return (- fromIntegral lit)
foreign export ccall ipasir_failed :: Solver -> Int32 -> IO CInt
-- | Check if the given assumption literal was used to prove the
-- unsatisfiability of the formula under the assumptions
-- used for the last SAT search. Return 1 if so, 0 otherwise.
--
-- The formula remains unsatisfiable even just under assumption literals for which @ipasir_failed@ returns 1.
-- Note that for literals @lit@ which are not assumption literals, the behavior of @ipasir_failed(lit)@ is not specified.
--
-- This function can only be used if 'ipasir_solve' has returned 20 and
-- no 'ipasir_add' or 'ipasir_assume' has been called since then, i.e.,
-- the state of the solver is @UNSAT@.
--
-- Required state: @UNSAT@
--
-- State after: @UNSAT@
ipasir_failed :: Solver -> Int32 -> IO CInt
ipasir_failed ptr lit = withSolverRep ptr $ \(solver, _addBuf, _assumptionRef) -> do
failedLits <- SAT.getFailedAssumptions solver
return $ if IntSet.member (fromIntegral lit) failedLits then 1 else 0
foreign export ccall ipasir_set_terminate :: Solver -> Ptr a -> FunPtr (Ptr a -> IO CInt) -> IO ()
-- | Set a callback function used to indicate a termination requirement to the
-- solver.
--
-- The solver will periodically call this function and check its return
-- value during the search. The @ipasir_set_terminate@ function can be called in any
-- state of the solver, the state remains unchanged after the call.
-- The callback function is of the form "@int terminate(void * data)@x"
--
-- * it returns a non-zero value if the solver should terminate.
-- * the solver calls the callback function with the parameter "data"
-- having the value passed in the @ipasir_set_terminate@ function (2nd parameter).
--
-- Required state: @INPUT@ or @SAT@ or @UNSAT@
--
-- State after: @INPUT@ or @SAT@ or @UNSAT@
ipasir_set_terminate :: Solver -> Ptr a -> FunPtr (Ptr a -> IO CInt) -> IO ()
ipasir_set_terminate ptr info funptr = withSolverRep ptr $ \(solver, _addBuf, _assumptionRef) -> do
if funptr == nullFunPtr then do
SAT.clearTerminateCallback solver
else do
let callback = mkTerminateCallback funptr
SAT.setTerminateCallback solver $ do
ret <- callback info
return (ret /= 0)
foreign export ccall ipasir_set_learn :: Solver -> Ptr a -> CInt -> FunPtr (Ptr a -> Ptr Int32 -> IO ()) -> IO ()
-- | Set a callback function used to extract learned clauses up to a given length from the
-- solver.
--
-- The solver will call this function for each learned clause that satisfies
-- the maximum length (literal count) condition. The @ipasir_set_learn@ function can be called in any
-- state of the solver, the state remains unchanged after the call.
-- The callback function is of the form "@void learn(void * data, int * clause)@"
--
-- * the solver calls the callback function with the parameter "data"
-- having the value passed in the @ipasir_set_learn@ function (2nd parameter).
-- * the argument "clause" is a pointer to a null terminated integer array containing the learned clause.
-- the solver can change the data at the memory location that "clause" points to after the function call.
--
-- Required state: @INPUT@ or @SAT@ or @UNSAT@
--
-- State after: @INPUT@ or @SAT@ or @UNSAT@
ipasir_set_learn :: Solver -> Ptr a -> CInt -> FunPtr (Ptr a -> Ptr Int32 -> IO ()) -> IO ()
ipasir_set_learn ptr info max_length funptr = withSolverRep ptr $ \(solver, _addBuf, _assumptionRef) -> do
if funptr == nullFunPtr then do
SAT.clearLearnCallback solver
else do
let callback = mkLearnCallback funptr
SAT.setLearnCallback solver $ \clause -> do
when (length clause <= fromIntegral max_length) $ do
withArray0 0 (map fromIntegral clause) (callback info)
foreign import ccall "dynamic" mkTerminateCallback :: FunPtr (Ptr a -> IO CInt) -> (Ptr a -> IO CInt)
foreign import ccall "dynamic" mkLearnCallback :: FunPtr (Ptr a -> Ptr Int32 -> IO ()) -> (Ptr a -> Ptr Int32 -> IO ())