grisette-0.9.0.0: src/Grisette/Internal/Core/Data/Class/CEGISSolver.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE Trustworthy #-}
-- |
-- Module : Grisette.Internal.Core.Data.Class.CEGISSolver
-- Copyright : (c) Sirui Lu 2021-2023
-- License : BSD-3-Clause (see the LICENSE file)
--
-- Maintainer : siruilu@cs.washington.edu
-- Stability : Experimental
-- Portability : GHC only
module Grisette.Internal.Core.Data.Class.CEGISSolver
( -- * Note for the examples
--
-- | The examples assumes that the [z3](https://github.com/Z3Prover/z3)
-- solver is available in @PATH@.
-- * Generic CEGIS interface
VerifierResult (..),
SynthesisConstraintFun,
VerifierFun,
CEGISResult (..),
solverGenericCEGIS,
solverGenericCEGISWithRefinement,
genericCEGIS,
genericCEGISWithRefinement,
-- * CEGIS interfaces with pre/post conditions
CEGISCondition (..),
cegisPostCond,
cegisPrePost,
solverCegisMultiInputs,
solverCegis,
solverCegisExcept,
solverCegisExceptStdVC,
solverCegisExceptVC,
solverCegisExceptMultiInputs,
solverCegisExceptStdVCMultiInputs,
solverCegisExceptVCMultiInputs,
solverCegisForAll,
solverCegisForAllExcept,
solverCegisForAllExceptStdVC,
solverCegisForAllExceptVC,
cegisMultiInputs,
cegis,
cegisExcept,
cegisExceptStdVC,
cegisExceptVC,
cegisExceptMultiInputs,
cegisExceptStdVCMultiInputs,
cegisExceptVCMultiInputs,
cegisForAll,
cegisForAllExcept,
cegisForAllExceptStdVC,
cegisForAllExceptVC,
)
where
import Control.DeepSeq (NFData)
import qualified Data.Binary as Binary
import Data.Bytes.Serial (Serial (deserialize, serialize))
import Data.Hashable (Hashable)
import Data.List (foldl', partition)
import qualified Data.Serialize as Cereal
import GHC.Generics (Generic)
import Generics.Deriving (Default (Default))
import Grisette.Internal.Core.Control.Exception
( VerificationConditions (AssertionViolation, AssumptionViolation),
)
import Grisette.Internal.Core.Data.Class.EvalSym (EvalSym, evalSym)
import Grisette.Internal.Core.Data.Class.ExtractSym
( ExtractSym,
extractSym,
)
import Grisette.Internal.Core.Data.Class.LogicalOp (LogicalOp (symNot, (.&&)))
import Grisette.Internal.Core.Data.Class.Mergeable (Mergeable)
import Grisette.Internal.Core.Data.Class.ModelOps
( ModelOps (exact, exceptFor),
SymbolSetOps (isEmptySet),
)
import Grisette.Internal.Core.Data.Class.PPrint (PPrint (pformat), (<+>))
import Grisette.Internal.Core.Data.Class.PlainUnion
( PlainUnion,
simpleMerge,
)
import Grisette.Internal.Core.Data.Class.SimpleMergeable
( SimpleMergeable,
)
import Grisette.Internal.Core.Data.Class.Solvable (Solvable (con))
import Grisette.Internal.Core.Data.Class.Solver
( ConfigurableSolver,
Solver (solverResetAssertions),
SolvingFailure (Unsat),
UnionWithExcept (extractUnionExcept),
solverSolve,
withSolver,
)
import Grisette.Internal.Core.Data.Class.SymEq (SymEq)
import Grisette.Internal.SymPrim.Prim.Model (Model)
import Grisette.Internal.SymPrim.SymBool (SymBool)
import Language.Haskell.TH.Syntax (Lift)
-- $setup
-- >>> import Grisette.Core
-- >>> import Grisette.Lib.Base
-- >>> import Grisette.SymPrim
-- >>> import Grisette.Backend
-- | The response from a verifier.
data VerifierResult cex exception
= CEGISVerifierFoundCex cex
| -- | True indicates that the verifier is sure that there is no
-- counter-example, while False indicates that the verifier is not sure,
-- but it cannot find a counter-example.
CEGISVerifierNoCex Bool
| CEGISVerifierException exception
deriving (Show, Eq, Generic, Lift)
deriving anyclass (Hashable, NFData)
instance
(PPrint cex, PPrint exception) =>
PPrint (VerifierResult cex exception)
where
pformat = \case
CEGISVerifierFoundCex cex -> "Found cex:" <+> pformat cex
CEGISVerifierNoCex True -> "No cex"
CEGISVerifierNoCex False -> "Maybe no cex"
CEGISVerifierException e -> "Exception:" <+> pformat e
-- | Build the synthesizer constraint from the verfication result. The first
-- argument will be guaranteed to be distinct during each invocation of the
-- function in the CEGIS algorithm, so it can be used to instantiate the
-- identifiers for fresh variables.
type SynthesisConstraintFun cex = cex -> IO SymBool
-- | The verifier.
type VerifierFun cex exception = Model -> IO (VerifierResult cex exception)
type RefinementConditionFun = Model -> IO SymBool
-- | The result of the CEGIS procedure.
data CEGISResult exception
= CEGISSuccess Model
| CEGISVerifierFailure exception
| CEGISSolverFailure SolvingFailure
deriving (Show, Eq, Generic, Lift)
deriving anyclass (NFData, Hashable, Serial)
instance (Serial exception) => Cereal.Serialize (CEGISResult exception) where
put = serialize
get = deserialize
instance (Serial exception) => Binary.Binary (CEGISResult exception) where
put = serialize
get = deserialize
-- | Generic CEGIS procedure. See 'genericCEGIS' for more details.
--
-- The difference from 'genericCEGIS' is that this function accepts a solver
-- handle for the synthesizer, instead of a solver configuration.
solverGenericCEGIS ::
(Solver handle) =>
-- | Configuration of the solver.
handle ->
-- | Whether we should rerun the passed verifiers if any other verifier found
-- a counter-example.
Bool ->
-- | The initial synthesis constraint.
SymBool ->
-- | Synthesis constraint from counter-examples
SynthesisConstraintFun input ->
-- | The verifier functions.
[VerifierFun input exception] ->
IO ([input], CEGISResult exception)
solverGenericCEGIS solver rerun initConstr synthConstr verifiers = do
firstResult <- solverSolve solver initConstr
case firstResult of
Left err -> return ([], CEGISSolverFailure err)
Right model -> go model False numAllVerifiers 0 verifiers
where
numAllVerifiers = length verifiers
go prevModel _ 0 _ (_ : _) = return ([], CEGISSuccess prevModel)
go prevModel needRerun runBound nextBound (verifier : remainingVerifiers) = do
verifierResult <- verifier prevModel
case verifierResult of
CEGISVerifierFoundCex cex -> do
newResult <- solverSolve solver =<< synthConstr cex
case newResult of
Left err -> return ([cex], CEGISSolverFailure err)
Right model -> do
(cexes, result) <-
go
model
(needRerun || rerun)
(length remainingVerifiers + 1)
(numAllVerifiers - length remainingVerifiers - 1)
$ verifier : remainingVerifiers
return (cex : cexes, result)
CEGISVerifierNoCex {} ->
go prevModel needRerun (runBound - 1) nextBound remainingVerifiers
CEGISVerifierException exception ->
return ([], CEGISVerifierFailure exception)
go prevModel False _ _ [] = return ([], CEGISSuccess prevModel)
go prevModel True _runBound nextBound [] =
go prevModel False nextBound 0 verifiers
-- | Generic CEGIS procedure with refinement. See 'genericCEGISWithRefinement'
-- for more details.
--
-- The difference from 'genericCEGISWithRefinement' is that this function
-- accepts a solver handle for the synthesizer, instead of a solver
-- configuration.
solverGenericCEGISWithRefinement ::
(Solver handle) =>
-- | Configuration of the solver.
handle ->
-- | Whether we should rerun the passed verifiers if any other verifier found
-- a counter-example.
Bool ->
-- | The initial synthesis constraint.
SymBool ->
-- | Synthesis constraint from counter-examples
SynthesisConstraintFun input ->
-- | Refinement condition generator.
Maybe RefinementConditionFun ->
-- | The verifier functions.
[VerifierFun input exception] ->
IO ([input], CEGISResult exception)
solverGenericCEGISWithRefinement
solver
rerun
initConstr
synthConstr
refineCond
verifiers = do
(input, r) <-
solverGenericCEGIS solver rerun initConstr synthConstr verifiers
case r of
CEGISSuccess model -> refine solver input model
_ -> return (input, r)
where
refine solver input model = case refineCond of
Just f -> do
cond <- f model
newResult <-
solverGenericCEGIS solver rerun cond synthConstr verifiers
case newResult of
(newInputs, CEGISSuccess model) ->
refine solver (input ++ newInputs) model
_ -> return (input, CEGISSuccess model)
Nothing -> return (input, CEGISSuccess model)
-- | Generic CEGIS procedure.
--
-- The CEGIS procedure will try to find a model that satisfies the initial
-- synthesis constraint, and satisfies all the inputs generated by the verifier.
genericCEGIS ::
(ConfigurableSolver config handle) =>
-- | Configuration of the solver.
config ->
-- | Whether we should rerun the passed verifiers if any other verifier found
-- a counter-example.
Bool ->
-- | The initial synthesis constraint.
SymBool ->
-- | Synthesis constraint from counter-examples
SynthesisConstraintFun input ->
-- | The verifier functions.
[VerifierFun input exception] ->
IO ([input], CEGISResult exception)
genericCEGIS config rerun initConstr synthConstr verifier =
withSolver config $ \solver ->
solverGenericCEGIS solver rerun initConstr synthConstr verifier
-- | Generic CEGIS procedure.
--
-- The CEGIS procedure will try to find a model that satisfies the initial
-- synthesis constraint, and satisfies all the inputs generated by the verifier.
genericCEGISWithRefinement ::
(ConfigurableSolver config handle) =>
-- | Configuration of the solver.
config ->
-- | Whether we should rerun the passed verifiers if any other verifier found
-- a counter-example.
Bool ->
-- | The initial synthesis constraint.
SymBool ->
-- | Synthesis constraint from counter-examples
SynthesisConstraintFun input ->
-- | Refinement condition generator.
Maybe RefinementConditionFun ->
-- | The verifier functions.
[VerifierFun input exception] ->
IO ([input], CEGISResult exception)
genericCEGISWithRefinement
config
rerun
initConstr
synthConstr
refineCond
verifier =
withSolver config $ \solver -> do
solverGenericCEGISWithRefinement
solver
rerun
initConstr
synthConstr
refineCond
verifier
-- | The condition for CEGIS to solve.
--
-- The first argument is the pre-condition, and the second argument is the
-- post-condition.
--
-- The CEGIS procedures would try to find a model for the formula
--
-- \[
-- \forall P. (\exists I. \mathrm{pre}(P, I)) \wedge (\forall I. \mathrm{pre}(P, I)\implies \mathrm{post}(P, I))
-- \]
--
-- In program synthesis tasks, \(P\) is the symbolic constants in the symbolic
-- program, and \(I\) is the input. The pre-condition is used to restrict the
-- search space of the program. The procedure would only return programs that
-- meets the pre-conditions on every possible inputs, and there are at least
-- one possible input. The post-condition is used to specify the desired program
-- behaviors.
data CEGISCondition = CEGISCondition SymBool SymBool
deriving (Generic)
deriving (EvalSym) via (Default CEGISCondition)
-- | Construct a CEGIS condition with only a post-condition. The pre-condition
-- would be set to true, meaning that all programs in the program space are
-- allowed.
cegisPostCond :: SymBool -> CEGISCondition
cegisPostCond = CEGISCondition (con True)
-- | Construct a CEGIS condition with both pre- and post-conditions.
cegisPrePost :: SymBool -> SymBool -> CEGISCondition
cegisPrePost = CEGISCondition
deriving via (Default CEGISCondition) instance Mergeable CEGISCondition
deriving via (Default CEGISCondition) instance SimpleMergeable CEGISCondition
-- | CEGIS with multiple (possibly symbolic) inputs. See 'cegisMultiInputs' for
-- more details.
--
-- The difference from 'cegisMultiInputs' is that this function accepts two
-- solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisMultiInputs ::
( EvalSym input,
ExtractSym input,
Solver handle
) =>
-- The synthesizer solver handle
handle ->
-- The verifier solver handle
handle ->
-- | Initial symbolic inputs. The solver will try to find a
-- program that works on all the inputs representable by these inputs (see
-- t'CEGISCondition').
[input] ->
-- | The condition for the solver to solve. All the
-- symbolic constants that are not in the inputs will
-- be considered as part of the symbolic program.
(input -> CEGISCondition) ->
-- | The counter-examples generated
-- during the CEGIS loop, and the
-- model found by the solver.
IO ([input], CEGISResult SolvingFailure)
solverCegisMultiInputs
synthesizerSolver
verifierSolver
inputs
toCEGISCondition = do
solverGenericCEGIS
synthesizerSolver
True
(foldl' (\acc v -> acc .&& cexAssertFun v) (con True) conInputs)
(return . cexAssertFun)
$ getVerifier <$> symInputs
where
cexAssertFun input =
case toCEGISCondition input of
CEGISCondition pre post -> pre .&& post
getVerifier input md = do
let CEGISCondition pre post = toCEGISCondition input
let evaluated =
evalSym False (exceptFor (extractSym input) md) $
pre .&& symNot post
solverResetAssertions verifierSolver
r <- solverSolve verifierSolver evaluated
case r of
Left Unsat -> return $ CEGISVerifierNoCex True
Left err -> return $ CEGISVerifierException err
Right model -> do
let newCexInput =
evalSym True (exact (extractSym input) model) input
return $ CEGISVerifierFoundCex newCexInput
(conInputs, symInputs) = partition (isEmptySet . extractSym) inputs
-- | CEGIS with a single symbolic input to represent a set of inputs. See
-- 'cegis' for more details.
--
-- The difference from 'cegis' is that this function accepts two solver handles,
-- one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegis ::
( Solver handle,
EvalSym inputs,
ExtractSym inputs,
SymEq inputs
) =>
-- | The synthesizer solver handle
handle ->
-- | The verifier solver handle
handle ->
-- | Initial symbolic inputs. The solver will try to find a
-- program that works on all the inputs representable by it (see
-- t'CEGISCondition').
inputs ->
-- | The condition for the solver to solve. All the
-- symbolic constants that are not in the inputs will
-- be considered as part of the symbolic program.
(inputs -> CEGISCondition) ->
-- | The counter-examples generated
-- during the CEGIS loop, and the
-- model found by the solver.
IO ([inputs], CEGISResult SolvingFailure)
solverCegis synthesizerSolver verifierSolver inputs =
solverCegisMultiInputs synthesizerSolver verifierSolver [inputs]
-- |
-- CEGIS for symbolic programs with error handling, using multiple (possibly
-- symbolic) inputs to represent a set of inputs.
--
-- The difference from 'cegisExceptMultiInputs' is that this function accepts
-- two solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisExceptMultiInputs ::
( Solver handle,
EvalSym inputs,
ExtractSym inputs,
UnionWithExcept t u e v,
PlainUnion u,
Monad u
) =>
handle ->
handle ->
[inputs] ->
(Either e v -> CEGISCondition) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
solverCegisExceptMultiInputs
synthesizerSolver
verifierSolver
cexes
interpretFun
f =
solverCegisMultiInputs
synthesizerSolver
verifierSolver
cexes
(simpleMerge . (interpretFun <$>) . extractUnionExcept . f)
-- |
-- CEGIS for symbolic programs with error handling, using multiple (possibly
-- symbolic) inputs to represent a set of inputs.
--
-- The errors should be translated to assertion or assumption violations.
--
-- The difference from 'cegisExceptVCMultiInputs' is that this function accepts
-- two solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisExceptVCMultiInputs ::
( Solver handle,
EvalSym inputs,
ExtractSym inputs,
UnionWithExcept t u e v,
PlainUnion u,
Monad u
) =>
handle ->
handle ->
[inputs] ->
(Either e v -> u (Either VerificationConditions ())) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
solverCegisExceptVCMultiInputs
synthesizerSolver
verifierSolver
cexes
interpretFun
f =
solverCegisMultiInputs
synthesizerSolver
verifierSolver
cexes
( \v ->
simpleMerge
( ( \case
Left AssumptionViolation ->
cegisPrePost (con False) (con True)
Left AssertionViolation -> cegisPostCond (con False)
_ -> cegisPostCond (con True)
)
<$> (extractUnionExcept (f v) >>= interpretFun)
)
)
-- |
-- CEGIS for symbolic programs with error handling, using multiple (possibly
-- symbolic) inputs to represent a set of inputs. See
-- 'cegisExceptStdVCMultiInputs' for more details.
--
-- The difference from 'cegisExceptStdVCMultiInputs' is that this function
-- accepts two solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisExceptStdVCMultiInputs ::
( Solver handle,
EvalSym inputs,
ExtractSym inputs,
UnionWithExcept t u VerificationConditions (),
PlainUnion u,
Monad u
) =>
handle ->
handle ->
[inputs] ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
solverCegisExceptStdVCMultiInputs synthesizerSolver verifierSolver cexes =
solverCegisExceptVCMultiInputs synthesizerSolver verifierSolver cexes return
-- |
-- CEGIS for symbolic programs with error handling, using a single symbolic
-- input to represent a set of inputs. See 'cegisExcept' for more details.
--
-- The difference from 'cegisExcept' is that this function accepts two solver
-- handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisExcept ::
( UnionWithExcept t u e v,
PlainUnion u,
Functor u,
EvalSym inputs,
ExtractSym inputs,
Solver handle,
SymEq inputs
) =>
handle ->
handle ->
inputs ->
(Either e v -> CEGISCondition) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
solverCegisExcept synthesizerSolver verifierSolver inputs f v =
solverCegis synthesizerSolver verifierSolver inputs $
\i -> simpleMerge $ f <$> extractUnionExcept (v i)
-- |
-- CEGIS for symbolic programs with error handling, using a single symbolic
-- input to represent a set of inputs.
--
-- The errors should be translated to assertion or assumption violations.
--
-- The difference from 'cegisExceptVC' is that this function accepts two solver
-- handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisExceptVC ::
( UnionWithExcept t u e v,
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
Solver handle,
SymEq inputs
) =>
handle ->
handle ->
inputs ->
(Either e v -> u (Either VerificationConditions ())) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
solverCegisExceptVC synthesizerSolver verifierSolver inputs f v = do
solverCegis synthesizerSolver verifierSolver inputs $ \i ->
simpleMerge $
( \case
Left AssumptionViolation -> cegisPrePost (con False) (con True)
Left AssertionViolation -> cegisPostCond (con False)
_ -> cegisPostCond (con True)
)
<$> (extractUnionExcept (v i) >>= f)
-- |
-- CEGIS for symbolic programs with error handling, using a single symbolic
-- input to represent a set of inputs. See 'cegisExceptStdVC' for more details.
--
-- The difference from 'cegisExceptStdVC' is that this function accepts two
-- solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisExceptStdVC ::
( UnionWithExcept t u VerificationConditions (),
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
Solver handle,
SymEq inputs
) =>
handle ->
handle ->
inputs ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
solverCegisExceptStdVC synthesizerSolver verifierSolver inputs =
solverCegisExceptVC synthesizerSolver verifierSolver inputs return
-- |
-- CEGIS with a single symbolic input to represent a set of inputs. See
-- 'cegisForAll' for more details.
--
-- The difference from 'cegisForAll' is that this function accepts two solver
-- handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisForAll ::
( ExtractSym forallInput,
Solver handle
) =>
handle ->
handle ->
-- | A symbolic value. All the symbolic constants in the value are treated as
-- for-all variables.
forallInput ->
CEGISCondition ->
-- | First output are the counter-examples for all the for-all variables, and
-- the second output is the model for all other variables if CEGIS succeeds.
IO ([Model], CEGISResult SolvingFailure)
solverCegisForAll
synthesizerSolver
verifierSolver
input
(CEGISCondition pre post) = do
(models, result) <-
solverGenericCEGIS
synthesizerSolver
False
phi
(\md -> return $ evalSym False md phi)
[verifier]
let exactResult = case result of
CEGISSuccess model -> CEGISSuccess $ exceptFor forallSymbols model
_ -> result
return (models, exactResult)
where
phi = pre .&& post
negphi = pre .&& symNot post
forallSymbols = extractSym input
verifier candidate = do
let evaluated =
evalSym False (exceptFor forallSymbols candidate) negphi
solverResetAssertions verifierSolver
r <- solverSolve verifierSolver evaluated
case r of
Left Unsat -> return $ CEGISVerifierNoCex True
Left err -> return $ CEGISVerifierException err
Right model ->
return $ CEGISVerifierFoundCex (exact forallSymbols model)
-- |
-- CEGIS for symbolic programs with error handling, with a forall variable.
--
-- See 'cegisForAllExcept', 'cegisForAll' and 'cegisExcept'.
--
-- The difference from 'cegisForAllExcept' is that this function accepts two
-- solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisForAllExcept ::
( UnionWithExcept t u e v,
PlainUnion u,
Functor u,
EvalSym inputs,
ExtractSym inputs,
Solver handle,
SymEq inputs
) =>
handle ->
handle ->
inputs ->
(Either e v -> CEGISCondition) ->
t ->
IO ([Model], CEGISResult SolvingFailure)
solverCegisForAllExcept synthesizerSolver verifierSolver inputs f v =
solverCegisForAll synthesizerSolver verifierSolver inputs $
simpleMerge $
f <$> extractUnionExcept v
-- |
-- CEGIS for symbolic programs with error handling, with a forall variable.
--
-- See 'cegisForAllExceptVC' 'cegisForAll' and 'cegisExceptVC'.
--
-- The difference from 'cegisForAllExceptVC' is that this function accepts two
-- solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisForAllExceptVC ::
( UnionWithExcept t u e v,
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
Solver handle,
SymEq inputs
) =>
handle ->
handle ->
inputs ->
(Either e v -> u (Either VerificationConditions ())) ->
t ->
IO ([Model], CEGISResult SolvingFailure)
solverCegisForAllExceptVC synthesizerSolver verifierSolver inputs f v = do
solverCegisForAll synthesizerSolver verifierSolver inputs $
simpleMerge $
( \case
Left AssumptionViolation -> cegisPrePost (con False) (con True)
Left AssertionViolation -> cegisPostCond (con False)
_ -> cegisPostCond (con True)
)
<$> (extractUnionExcept v >>= f)
-- |
-- CEGIS for symbolic programs with error handling, with a forall variable.
--
-- See 'cegisForAllExceptStdVC' 'cegisForAll' and 'cegisExceptStdVC'.
--
-- The difference from 'cegisForAllExceptStdVC' is that this function accepts
-- two solver handles, one for the synthesizer and one for the verifier.
--
-- The synthesizer solver will **not** be reset, while the verifier solver will
-- be reset after each iteration.
solverCegisForAllExceptStdVC ::
( UnionWithExcept t u VerificationConditions (),
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
Solver handle,
SymEq inputs
) =>
handle ->
handle ->
inputs ->
t ->
IO ([Model], CEGISResult SolvingFailure)
solverCegisForAllExceptStdVC synthesizerSolver verifierSolver inputs =
solverCegisForAllExceptVC synthesizerSolver verifierSolver inputs return
-- |
-- CEGIS with multiple (possibly symbolic) inputs. Solves the following formula
-- (see t'CEGISCondition' for details).
--
-- \[
-- \forall P. (\exists I\in\mathrm{inputs}. \mathrm{pre}(P, I)) \wedge (\forall I\in\mathrm{inputs}. \mathrm{pre}(P, I)\implies \mathrm{post}(P, I))
-- \]
--
-- For simpler queries, where the inputs are representable by a single
-- symbolic value, you may want to use 'cegis' or 'cegisExcept' instead.
-- We have an example for the 'cegis' call.
cegisMultiInputs ::
( EvalSym input,
ExtractSym input,
ConfigurableSolver config handle
) =>
-- | The configuration of the solver
config ->
-- | Initial symbolic inputs. The solver will try to find a
-- program that works on all the inputs representable by these inputs (see
-- t'CEGISCondition').
[input] ->
-- | The condition for the solver to solve. All the
-- symbolic constants that are not in the inputs will
-- be considered as part of the symbolic program.
(input -> CEGISCondition) ->
-- | The counter-examples generated
-- during the CEGIS loop, and the
-- model found by the solver.
IO ([input], CEGISResult SolvingFailure)
cegisMultiInputs config inputs toCEGISCondition =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisMultiInputs
synthesizerSolver
verifierSolver
inputs
toCEGISCondition
-- |
-- CEGIS with a single symbolic input to represent a set of inputs.
--
-- The following example tries to find the value of @c@ such that for all
-- positive @x@, @x * c < 0 && c > -2@. The @c .> -2@ clause is used to make
-- the solution unique.
--
-- >>> let [x,c] = ["x","c"] :: [SymInteger]
-- >>> cegis z3 x (\x -> cegisPrePost (x .> 0) (x * c .< 0 .&& c .> -2))
-- (...,CEGISSuccess (Model {c -> -1 :: Integer}))
cegis ::
( ConfigurableSolver config handle,
EvalSym inputs,
ExtractSym inputs,
SymEq inputs
) =>
-- | The configuration of the solver
config ->
-- | Initial symbolic inputs. The solver will try to find a
-- program that works on all the inputs representable by it (see
-- t'CEGISCondition').
inputs ->
-- | The condition for the solver to solve. All the
-- symbolic constants that are not in the inputs will
-- be considered as part of the symbolic program.
(inputs -> CEGISCondition) ->
-- | The counter-examples generated
-- during the CEGIS loop, and the
-- model found by the solver.
IO ([inputs], CEGISResult SolvingFailure)
cegis config inputs condition =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegis synthesizerSolver verifierSolver inputs condition
-- |
-- CEGIS for symbolic programs with error handling, using multiple (possibly
-- symbolic) inputs to represent a set of inputs.
cegisExceptMultiInputs ::
( ConfigurableSolver config handle,
EvalSym inputs,
ExtractSym inputs,
UnionWithExcept t u e v,
PlainUnion u,
Monad u
) =>
config ->
[inputs] ->
(Either e v -> CEGISCondition) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
cegisExceptMultiInputs config cexes interpretFun f =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisExceptMultiInputs
synthesizerSolver
verifierSolver
cexes
interpretFun
f
-- |
-- CEGIS for symbolic programs with error handling, using multiple (possibly
-- symbolic) inputs to represent a set of inputs.
--
-- The errors should be translated to assertion or assumption violations.
cegisExceptVCMultiInputs ::
( ConfigurableSolver config handle,
EvalSym inputs,
ExtractSym inputs,
UnionWithExcept t u e v,
PlainUnion u,
Monad u
) =>
config ->
[inputs] ->
(Either e v -> u (Either VerificationConditions ())) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
cegisExceptVCMultiInputs config cexes interpretFun f =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisExceptVCMultiInputs
synthesizerSolver
verifierSolver
cexes
interpretFun
f
-- |
-- CEGIS for symbolic programs with error handling, using multiple (possibly
-- symbolic) inputs to represent a set of inputs. This function saves the
-- efforts to implement the translation function for the standard error type
-- 'VerificationConditions', and the standard result type @()@.
--
-- This function translates assumption violations to failed pre-conditions,
-- and translates assertion violations to failed post-conditions.
-- The @()@ result will not fail any conditions.
cegisExceptStdVCMultiInputs ::
( ConfigurableSolver config handle,
EvalSym inputs,
ExtractSym inputs,
UnionWithExcept t u VerificationConditions (),
PlainUnion u,
Monad u
) =>
config ->
[inputs] ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
cegisExceptStdVCMultiInputs config cexes f =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisExceptStdVCMultiInputs synthesizerSolver verifierSolver cexes f
-- |
-- CEGIS for symbolic programs with error handling, using a single symbolic
-- input to represent a set of inputs.
--
-- 'cegisExcept' is particularly useful when custom error types are used.
-- With 'cegisExcept', you define how the errors are interpreted to the
-- CEGIS conditions after the symbolic evaluation. This could increase the
-- readability and modularity of the code.
--
-- The following example tries to find the value of @c@ such that for all
-- positive @x@, @x * c < 0 && c > -2@. The @c .> -2@ assertion is used to make
-- the solution unique.
--
-- >>> let [x,c] = ["x","c"] :: [SymInteger]
-- >>> import Control.Monad.Except
-- >>> :{
-- res :: SymInteger -> ExceptT VerificationConditions Union ()
-- res x = do
-- symAssume $ x .> 0
-- symAssert $ x * c .< 0
-- symAssert $ c .> -2
-- :}
--
-- >>> :{
-- translation (Left AssumptionViolation) = cegisPrePost (con False) (con True)
-- translation (Left AssertionViolation) = cegisPostCond (con False)
-- translation _ = cegisPostCond (con True)
-- :}
--
-- >>> cegisExcept z3 x translation res
-- ([...],CEGISSuccess (Model {c -> -1 :: Integer}))
cegisExcept ::
( UnionWithExcept t u e v,
PlainUnion u,
Functor u,
EvalSym inputs,
ExtractSym inputs,
ConfigurableSolver config handle,
SymEq inputs
) =>
config ->
inputs ->
(Either e v -> CEGISCondition) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
cegisExcept config inputs f v =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisExcept synthesizerSolver verifierSolver inputs f v
-- |
-- CEGIS for symbolic programs with error handling, using a single symbolic
-- input to represent a set of inputs.
--
-- The errors should be translated to assertion or assumption violations.
cegisExceptVC ::
( UnionWithExcept t u e v,
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
ConfigurableSolver config handle,
SymEq inputs
) =>
config ->
inputs ->
(Either e v -> u (Either VerificationConditions ())) ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
cegisExceptVC config inputs f v =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisExceptVC synthesizerSolver verifierSolver inputs f v
-- |
-- CEGIS for symbolic programs with error handling, using a single symbolic
-- input to represent a set of inputs. This function saves the efforts to
-- implement the translation function for the standard error type
-- 'VerificationConditions', and the standard result type @()@.
--
-- This function translates assumption violations to failed pre-conditions,
-- and translates assertion violations to failed post-conditions.
-- The @()@ result will not fail any conditions.
--
-- The following example tries to find the value of @c@ such that for all
-- positive @x@, @x * c < 0 && c > -2@. The @c .> -2@ assertion is used to make
-- the solution unique.
--
-- >>> let [x,c] = ["x","c"] :: [SymInteger]
-- >>> import Control.Monad.Except
-- >>> :{
-- res :: SymInteger -> ExceptT VerificationConditions Union ()
-- res x = do
-- symAssume $ x .> 0
-- symAssert $ x * c .< 0
-- symAssert $ c .> -2
-- :}
--
-- >>> cegisExceptStdVC z3 x res
-- ([...],CEGISSuccess (Model {c -> -1 :: Integer}))
cegisExceptStdVC ::
( UnionWithExcept t u VerificationConditions (),
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
ConfigurableSolver config handle,
SymEq inputs
) =>
config ->
inputs ->
(inputs -> t) ->
IO ([inputs], CEGISResult SolvingFailure)
cegisExceptStdVC config inputs f =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisExceptStdVC synthesizerSolver verifierSolver inputs f
-- |
-- CEGIS with a single symbolic input to represent a set of inputs.
--
-- The following example tries to find the value of @c@ such that for all
-- positive @x@, @x * c < 0 && c > -2@. The @c .> -2@ clause is used to make
-- the solution unique.
--
-- >>> let [x,c] = ["x","c"] :: [SymInteger]
-- >>> cegisForAll z3 x $ cegisPrePost (x .> 0) (x * c .< 0 .&& c .> -2)
-- (...,CEGISSuccess (Model {c -> -1 :: Integer}))
cegisForAll ::
( ExtractSym forallInput,
ConfigurableSolver config handle
) =>
config ->
-- | A symbolic value. All the symbolic constants in the value are treated as
-- for-all variables.
forallInput ->
CEGISCondition ->
-- | First output are the counter-examples for all the for-all variables, and
-- the second output is the model for all other variables if CEGIS succeeds.
IO ([Model], CEGISResult SolvingFailure)
cegisForAll config input (CEGISCondition pre post) =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisForAll
synthesizerSolver
verifierSolver
input
(CEGISCondition pre post)
-- |
-- CEGIS for symbolic programs with error handling, with a forall variable.
--
-- See 'cegisForAll' and 'cegisExcept'.
cegisForAllExcept ::
( UnionWithExcept t u e v,
PlainUnion u,
Functor u,
EvalSym inputs,
ExtractSym inputs,
ConfigurableSolver config handle,
SymEq inputs
) =>
config ->
inputs ->
(Either e v -> CEGISCondition) ->
t ->
IO ([Model], CEGISResult SolvingFailure)
cegisForAllExcept config inputs f v =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisForAllExcept synthesizerSolver verifierSolver inputs f v
-- |
-- CEGIS for symbolic programs with error handling, with a forall variable.
--
-- See 'cegisForAll' and 'cegisExceptVC'.
cegisForAllExceptVC ::
( UnionWithExcept t u e v,
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
ConfigurableSolver config handle,
SymEq inputs
) =>
config ->
inputs ->
(Either e v -> u (Either VerificationConditions ())) ->
t ->
IO ([Model], CEGISResult SolvingFailure)
cegisForAllExceptVC config inputs f v =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisForAllExceptVC synthesizerSolver verifierSolver inputs f v
-- |
-- CEGIS for symbolic programs with error handling, with a forall variable.
--
-- See 'cegisForAll' and 'cegisExceptStdVC'.
cegisForAllExceptStdVC ::
( UnionWithExcept t u VerificationConditions (),
PlainUnion u,
Monad u,
EvalSym inputs,
ExtractSym inputs,
ConfigurableSolver config handle,
SymEq inputs
) =>
config ->
inputs ->
t ->
IO ([Model], CEGISResult SolvingFailure)
cegisForAllExceptStdVC config inputs u =
withSolver config $ \synthesizerSolver ->
withSolver config $ \verifierSolver ->
solverCegisForAllExceptStdVC synthesizerSolver verifierSolver inputs u