packages feed

minisat-solver-0.1: SAT/MiniSat/Monadic.hs

-- Copyright (C) 2016 Peter Selinger.
--
-- This file is free software and may be distributed under the terms
-- of the MIT license. Please see the file LICENSE for details.

-- | A monadic interface to the functions of
-- "SAT.MiniSat.LowLevel". This layer:
--
-- * hooks into garbage collection;
--
-- * converts low-level types to Haskell types;
--
-- * replaces the 'IO' monad by a state monad;
-- 
-- * converts low-level errors to I/O exceptions.
--
-- All of the exported functions may potentially raise an exception.

module SAT.MiniSat.Monadic where

import SAT.MiniSat.LowLevel
import SAT.MiniSat.Literals

import Foreign
import Foreign.C.Error
import Control.Monad.Trans.State
import System.IO.Unsafe

-- ----------------------------------------------------------------------
-- * Error handling

-- | Check an assertion, and raise an 'IO' exception (using the current
-- errno and the supplied location string) if the assertion fails.
ensure :: String -> Bool -> IO ()
ensure s True = return ()
ensure s False = throwErrno s

-- ----------------------------------------------------------------------
-- * Conversions

-- | Marshall a C boolean to a Haskell boolean.
bool_of_cbool :: C_Bool -> Bool
bool_of_cbool 0 = False
bool_of_cbool n = True

-- | Marshall a Haskell boolean to a C boolean.
cbool_of_bool :: Bool -> C_Bool
cbool_of_bool True = 1
cbool_of_bool False = 0

-- | Marshall a Haskell literal to a MiniSat literal.
clit_of_lit :: Lit Int -> C_Lit
clit_of_lit (Pos n) = c_lit_new (fromIntegral n) (cbool_of_bool False)
clit_of_lit (Neg n) = c_lit_new (fromIntegral n) (cbool_of_bool True)

-- | Marshall an \"lbool\" to a 'Maybe' 'Bool'.
maybebool_of_lbool :: C_LBool -> Maybe Bool
maybebool_of_lbool 0 = Nothing
maybebool_of_lbool 1 = Just True
maybebool_of_lbool (-1) = Just False
maybebool_of_lbool _ = error "maybebool_of_lbool"

-- ----------------------------------------------------------------------
-- * High-level Solver type

-- | The type of solvers.
newtype Solver = Solver (ForeignPtr C_Solver)

-- | Convert a low-level pointer to a garbage-collected 'Solver'.
make_Solver :: Ptr C_Solver -> IO Solver
make_Solver p = do
  fptr <- newForeignPtr c_solver_delete_ptr p
  return (Solver fptr)

-- | Marshall a 'Solver' to a 'Ptr' 'C_Solver'.
with_Solver :: Solver -> (Ptr C_Solver -> IO a) -> IO a
with_Solver (Solver fptr) = withForeignPtr fptr

-- | Create a new solver.
solver_new :: IO Solver
solver_new = do
  p <- c_solver_new
  ensure "solver_new" (p /= nullPtr)
  make_Solver p

-- ----------------------------------------------------------------------
-- * State monad

-- $ The low-level C functions live in the 'IO' monad. However, the only
-- actual side effect of these functions is to update the 'Solver'
-- object. Therefore, it is safe to lift these functions to a state
-- monad. Moreover, in doing so, we are able to exploit laziness in a
-- way that is not possible in the 'IO' monad.

-- | The 'SolverState' monad is for functions whose only side effect
-- is to update the 'Solver' object.
type SolverState a = State Solver a

-- | Marshall a function from the 'IO' monad to the state monad. The
-- call to 'unsafePerformIO' is safe, provided that the 'IO'
-- computation's only side effect is to update the 'Solver' object.
stateful :: (Solver -> IO a) -> SolverState a
stateful body = state $ \s -> unsafePerformIO $ do
  r <- body s
  return (r, s)

-- | Run a 'SolverState' computation in the context of a newly created
-- solver. The use of 'unsafePerformIO' is safe because the only side
-- effect of 'solver_new' is to create a new solver state.
m_run :: SolverState a -> a
m_run body = unsafePerformIO $ do
  s <- solver_new
  let (a, s') = runState body s
  return a

-- ----------------------------------------------------------------------
-- * Marshalled API functions

-- | Add a clause to the solver. May return 'False' if the clause is
-- unsatisfiable, in which case the Solver should not be searched.
-- The clause is represented as a list of literals.
m_solver_addclause :: [Lit Int] -> SolverState Bool
m_solver_addclause lits = stateful $ \s -> do
  with_Solver s $ \s_ptr -> do
    withArrayLen clits $ \n clits_ptr -> do
      r <- c_solver_addclause s_ptr clits_ptr (fromIntegral n)
      return (bool_of_cbool r)
  where
    clits = map clit_of_lit lits

-- | Initialize the solver for searching.
m_solver_solve_start :: SolverState ()
m_solver_solve_start = stateful $ \s -> do
  with_Solver s $ \s_ptr -> do
    c_solver_solve_start s_ptr

-- | Search the next solution.
m_solver_solve_next :: SolverState Bool
m_solver_solve_next = stateful $ \s -> do
  with_Solver s $ \s_ptr -> do
    r <- c_solver_solve_next s_ptr
    return (bool_of_cbool r)

-- | Finalizer to run after search.
m_solver_solve_finish :: SolverState ()
m_solver_solve_finish = stateful $ \s -> do
  with_Solver s $ \s_ptr -> do
    c_solver_solve_finish s_ptr

-- | Retrieve the most recent solution (only valid if
-- 'm_solver_solve_next' returned 'True', and no other API functions
-- have been called since then). The solution is represented as a
-- list, where the /n/th element of the list represents the assignment
-- ('True', 'False', or 'Nothing') of variable /n/.
m_solver_solution :: SolverState [Maybe Bool]
m_solver_solution = stateful $ \s -> do
  with_Solver s $ \s_ptr -> do
    lbools_ptr <- c_solver_solution s_ptr
    n <- c_solver_nvars s_ptr
    lbools <- peekArray (fromIntegral n) lbools_ptr
    let mbools = map maybebool_of_lbool lbools
    return mbools

-- ----------------------------------------------------------------------
-- * Derived API functions

-- | Add a list of clauses to the solver. May return 'False' if the
-- clauses are unsatisfiable, in which case the Solver should not be
-- searched.
m_solver_addclauses :: [[Lit Int]] -> SolverState Bool
m_solver_addclauses [] = return True
m_solver_addclauses (h:t) = do
  r <- m_solver_addclause h
  if r == False then do
    return False
    else do
    m_solver_addclauses t

-- | Search and return the next solution.
m_solver_next_solution :: SolverState (Maybe [Maybe Bool])
m_solver_next_solution = do
  r <- m_solver_solve_next
  if r == False then do
    return Nothing
    else do
    sol <- m_solver_solution
    return (Just sol)

-- | Return all remaining solutions.
m_solver_next_solutions :: SolverState [[Maybe Bool]]
m_solver_next_solutions = do
  r <- m_solver_next_solution
  case r of
   Nothing -> do
     return []
   Just h -> do
     t <- m_solver_next_solutions
     return (h:t)