satchmo-2.9.9: Satchmo/SAT/External.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE PatternSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# language TemplateHaskell #-}
-- | call an external solver as separate process,
-- communicate via pipes.
module Satchmo.SAT.External
( SAT
, fresh
, emit
, solve
-- , solve_with_timeout
)
where
import Satchmo.Data
import Satchmo.Boolean hiding ( not )
import Satchmo.Code
-- import Satchmo.MonadSAT
import Control.Monad.Reader
import Control.Monad.State
-- import Control.Monad.IO.Class
import System.IO
import Control.Lens
import Control.Applicative
import Control.Concurrent
import Control.DeepSeq (rnf)
import Foreign.C
-- import System.Exit (ExitCode(..))
import System.Process
-- import System.IO.Error
-- import System.Posix.Types
import Control.Exception
import GHC.IO.Exception ( IOErrorType(..), IOException(..) )
-- import System.Posix.Signals
import qualified Control.Exception as C
import qualified Data.ByteString.Char8 as BS
import qualified Data.Map.Strict as M
import Data.List (isPrefixOf)
tracing = False
report s = when tracing $ hPutStrLn stderr s
data S = S
{ _next_variable :: ! Int
, _solver_input :: ! Handle
}
$(makeLenses ''S)
newtype SAT a = SAT (StateT S IO a)
deriving (Functor, Applicative, Monad, MonadIO)
type Assignment = M.Map Int Bool
newtype Dec a = Dec (Reader Assignment a)
deriving (Functor, Applicative, Monad)
instance MonadSAT SAT where
fresh = SAT $ do
n <- use next_variable
next_variable .= succ n
return $ literal True $ fromEnum n
emit cl = SAT $ do
h <- use solver_input
let s = BS.pack $ show cl
-- liftIO $ BS.putStrLn s
liftIO $ BS.hPutStrLn h s
note msg = SAT $ liftIO $ hPutStrLn stderr msg
type Decoder SAT = Dec
instance Decode Dec Boolean Bool where
decode b = case b of
Constant c -> return c
Boolean l -> do
v <- dv $ variable l
return $ if positive l then v else not v
dv v = Dec $ do
assignment <- ask
return $ case M.lookup v assignment of
Just v -> v
Nothing -> error $ unwords [ "unassigned", "variable", show v ]
solve :: String -- ^ command, e.g., glucose
-> [String] -- ^ options, e.g., -model
-> SAT (Dec a) -- ^ action that builds the formula and returns the decoder
-> IO (Maybe a)
solve command opts (SAT action) = bracket
( do
report "Satchmo.SAT.External: creating process"
createProcess $ (proc command opts)
{ std_in = CreatePipe
, std_out = CreatePipe
, create_group = True
} )
( \ (Just sin, Just sout, _, ph) -> do
report "Satchmo.SAT.External: bracket closing"
interruptProcessGroupOf ph
)
$ \ (Just sin, Just sout, _, ph) -> do
dec <- newEmptyMVar
-- fork off a thread to start consuming the output
output <- hGetContents sout -- lazy IO
withForkWait (C.evaluate $ rnf output) $ \ waitOut ->
ignoreSigPipe $ do
report $ "S.S.External: waiter forked"
let s0 = S { _next_variable=1, _solver_input=sin}
report $ "S.S.External: writing output"
Dec decoder <- evalStateT action s0
putMVar dec decoder
hClose sin
waitOut
hClose sout
report $ "S.S.External: waiter done"
report "Satchmo.SAT.External: start waiting"
waitForProcess ph
decoder <- takeMVar dec
report "Satchmo.SAT.External: waiting done"
let vlines = do
line <- lines output
guard $ isPrefixOf "v" line
return line
report $ show vlines
let vs = do
line <- vlines
w <- tail $ words line
return (read w :: Int)
return $ do
guard $ not $ null vlines
let m = M.fromList $ do
v <- vs ; guard $ v /= 0 ; return (abs v, v>0)
return $ runReader decoder m
-- * code from System.Process
-- http://hackage.haskell.org/package/process-1.2.3.0/docs/src/System-Process.html#readProcess
-- but they are not exporting withForkWait, so I have to copy it
-- | Fork a thread while doing something else, but kill it if there's an
-- exception.
--
-- This is important in the cases above because we want to kill the thread
-- that is holding the Handle lock, because when we clean up the process we
-- try to close that handle, which could otherwise deadlock.
--
withForkWait :: IO () -> (IO () -> IO a) -> IO a
withForkWait async body = do
waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))
mask $ \restore -> do
tid <- forkIO $ try (restore async) >>= putMVar waitVar
let wait = takeMVar waitVar >>= either throwIO return
restore (body wait) `C.onException` killThread tid
ignoreSigPipe :: IO () -> IO ()
ignoreSigPipe = C.handle $ \e -> case e of
IOError { ioe_type = ResourceVanished
, ioe_errno = Just ioe }
| Errno ioe == ePIPE -> return ()
_ -> throwIO e