packages feed

g2-0.2.0.0: src/G2/Solver/Maximize.hs

module G2.Solver.Maximize ( MaximizeSolver
                          , mkMaximizeSolver) where

import G2.Solver.Converters
import G2.Solver.Language
import G2.Solver.Solver

import Control.Concurrent
import Data.IORef
import Data.List as L
import qualified Data.Map as M
import Text.Builder

data MaximizeSolver con = MaxSolver (MVar ThreadId) (MVar (Result () () ())) (IORef [SMTHeader]) con

mkMaximizeSolver :: SMTConverter con => con -> IO (MaximizeSolver con)
mkMaximizeSolver con = do
    thread_mvar <- newEmptyMVar
    res_mvar <- newEmptyMVar
    headers_io_ref <- newIORef []
    return $ MaxSolver thread_mvar res_mvar headers_io_ref con

instance SMTConverter con => Solver (MaximizeSolver con) where
    check solver _ pc = checkConstraintsPC solver pc
    solve (MaxSolver _ _ _ con) = solve con
    close = closeIO

instance SMTConverter con => SMTConverter (MaximizeSolver con) where
    closeIO (MaxSolver thread_ioref _ _ con) = do
        maybe (return ()) killThread =<< tryReadMVar thread_ioref
        closeIO con

    reset (MaxSolver thread_mvar res_mvar headers_io_ref con) = do
        maybe (return ()) killThread =<< tryReadMVar thread_mvar
        _ <- tryTakeMVar thread_mvar
        _ <- tryTakeMVar res_mvar
        writeIORef headers_io_ref []
        reset con

    checkSatInstr (MaxSolver thread_mvar res_mvar headers_io_ref con) = do
        maybe (return ()) killThread =<< tryReadMVar thread_mvar
        added <- readIORef headers_io_ref
        thread <- forkIO (do
                            res <- solveSoftAsserts con added
                            definitelyPutMVar res_mvar res)
        definitelyPutMVar thread_mvar thread
        return ()

    maybeCheckSatResult (MaxSolver _ res_ioref _ _) = tryReadMVar res_ioref

    getModelInstrResult (MaxSolver _ _ _ con) = getModelInstrResult con
    getUnsatCoreInstrResult (MaxSolver _ _ _ con) = getUnsatCoreInstrResult con

    setProduceUnsatCores (MaxSolver _ _ _ con) = setProduceUnsatCores con

    addFormula (MaxSolver _ _ headers_io_ref _) form = modifyIORef' headers_io_ref (form ++)

    checkSatGetModelOrUnsatCoreNoReset (MaxSolver _ _ headers_io_ref con) headers vs = do
        added <- readIORef headers_io_ref
        res <- solveSoftAsserts con (added ++ headers)
        case res of
            SAT _ -> do
                mdl <- getModelInstrResult con vs
                return (SAT mdl)
            UNSAT _ -> do
                uc <- getUnsatCoreInstrResult con
                return (UNSAT uc)
            Unknown err _ -> return (Unknown err ())

    -- We don't need to produce a model, because this resets, so we can just ignore all soft assertions
    checkSat max_solver@(MaxSolver _ _ _ con) headers = do
        reset max_solver
        checkSat con $ filter (\h -> case h of AssertSoft _ _ -> False; _ -> True) headers

    checkSatGetModel con@(MaxSolver _ _ _ _) headers vs = do
        reset con
        res <- solveSoftAsserts con headers
        case res of
            SAT _ -> do
                mdl <- getModelInstrResult con vs
                return (SAT mdl)
            UNSAT _ -> return (UNSAT ())
            Unknown err _ -> return (Unknown err ())

    push (MaxSolver _ _ _ con) = push con
    pop (MaxSolver _ _ _ con) = pop con

solveSoftAsserts :: SMTConverter con => con -> [SMTHeader] -> IO (Result () () ())
solveSoftAsserts con headers = do
    let (soft_asserts, other_headers) =
            partition (\h -> case h of AssertSoft _ _ -> True; _ -> False) $ elimSetLogic headers
        set_logic = getSetLogic headers
        soft_assert_sum =
              foldr (:+) (VInt 0)
            $ map (\(AssertSoft assrt _) -> Ite assrt (VInt 1) (VInt 0)) soft_asserts
        new_assert = Assert $ V totalVarName SortInt := soft_assert_sum
        var_decl = VarDecl (string totalVarName) SortInt
    setProduceUnsatCores con
    addHeaders con (set_logic ++ var_decl:other_headers ++ [new_assert])
    solveSoftAsserts' con Nothing 0 0 (genericLength soft_asserts)

type Minimum = Integer
type Maximum = Integer

solveSoftAsserts' :: SMTConverter con =>
                     con
                  -> Maybe SMTModel
                  -> Int
                  -> Minimum
                  -> Maximum
                  -> IO (Result () () ())
solveSoftAsserts' con mb_mdl fresh min_ max_ = do
    let (target_q, target_r) = (min_ + max_) `quotRem` 2
        target = target_q + target_r
        target_assert = Assert $ V totalVarName SortInt :>= VInt target

    putStrLn $ "min = " ++ show min_ ++ ", max = " ++ show max_ ++ ", target = " ++ show target
    push con
    res <- constraintsToModelOrUnsatCoreNoReset con [target_assert] [(totalVarName, SortInt)]
    case res of
        SAT mdl | Just (VInt new_min_) <- m_new_min
                , new_min_ == max_ -> return $ SAT ()
                | Just (VInt new_min_) <- m_new_min -> do
                    -- If we are increasing the minimum depth, we do NOT want to remove
                    -- our previous limit assertion, so that if we later get a model, that
                    -- limit assertion is still in place.
                    solveSoftAsserts' con (Just mdl) (fresh + 1) (new_min_ + 1) max_
                -- Should be unreachable because totalVarName should always be in the model
                | otherwise -> error "solveSoftAsserts': Impossible case"
            where
               m_new_min = M.lookup totalVarName mdl
        UNSAT _ | target == 0 -> return $ UNSAT ()
                | min_ == max_ -> do
                    pop con
                    -- get-model is only valid after a check-sat call that returns sat,
                    -- so we must ensure that the last check-sat did indeed return sat.
                    _ <- checkSatNoReset con []
                    return $ SAT ()
                -- Should be unreachable, because if min_ is not 0, we have found a model.
                -- But if min_ == max_ == 0, target == 0, and we hit the first case.
                | min_ == max_ -> error "solveSoftAsserts': Impossible case"
                | otherwise -> do
                  pop con
                  solveSoftAsserts' con mb_mdl (fresh + 1) min_ (target - 1)
        Unknown err _ -> return $ Unknown err ()

definitelyPutMVar :: MVar a -> a -> IO ()
definitelyPutMVar mvar a = do
    r <- tryPutMVar mvar a
    case r of
      True -> return ()
      False -> modifyMVar_ mvar (\_ -> return a)

totalVarName :: SMTName
totalVarName = "solveSoftAsserts_SUM_VAR"

getSetLogic :: [SMTHeader] -> [SMTHeader]
getSetLogic = filter (\h -> case h of SetLogic _ -> True; _ -> False)

elimSetLogic :: [SMTHeader] -> [SMTHeader]
elimSetLogic = filter (\h -> case h of SetLogic _ -> False; _ -> True)