mirror of
https://github.com/c-cube/sidekick.git
synced 2025-12-07 11:45:41 -05:00
880 lines
26 KiB
OCaml
880 lines
26 KiB
OCaml
(** {1 Main Environment}
|
|
|
|
Theories and concrete solvers rely on an environment that defines
|
|
several important types:
|
|
|
|
- sorts
|
|
- terms (to represent logic expressions and formulas)
|
|
- a congruence closure instance
|
|
*)
|
|
|
|
module Fmt = CCFormat
|
|
|
|
module CC_view = struct
|
|
type ('f, 't, 'ts) t =
|
|
| Bool of bool
|
|
| App_fun of 'f * 'ts
|
|
| App_ho of 't * 'ts
|
|
| If of 't * 't * 't
|
|
| Eq of 't * 't
|
|
| Not of 't
|
|
| Opaque of 't (* do not enter *)
|
|
|
|
let map_view ~f_f ~f_t ~f_ts (v:_ t) : _ t =
|
|
match v with
|
|
| Bool b -> Bool b
|
|
| App_fun (f, args) -> App_fun (f_f f, f_ts args)
|
|
| App_ho (f, args) -> App_ho (f_t f, f_ts args)
|
|
| Not t -> Not (f_t t)
|
|
| If (a,b,c) -> If (f_t a, f_t b, f_t c)
|
|
| Eq (a,b) -> Eq (f_t a, f_t b)
|
|
| Opaque t -> Opaque (f_t t)
|
|
|
|
let iter_view ~f_f ~f_t ~f_ts (v:_ t) : unit =
|
|
match v with
|
|
| Bool _ -> ()
|
|
| App_fun (f, args) -> f_f f; f_ts args
|
|
| App_ho (f, args) -> f_t f; f_ts args
|
|
| Not t -> f_t t
|
|
| If (a,b,c) -> f_t a; f_t b; f_t c;
|
|
| Eq (a,b) -> f_t a; f_t b
|
|
| Opaque t -> f_t t
|
|
end
|
|
|
|
module type TERM = sig
|
|
module Fun : sig
|
|
type t
|
|
val equal : t -> t -> bool
|
|
val hash : t -> int
|
|
val pp : t Fmt.printer
|
|
end
|
|
|
|
module Ty : sig
|
|
type t
|
|
|
|
val equal : t -> t -> bool
|
|
val hash : t -> int
|
|
val pp : t Fmt.printer
|
|
|
|
type state
|
|
|
|
val bool : state -> t
|
|
val is_bool : t -> bool
|
|
end
|
|
|
|
module Term : sig
|
|
type t
|
|
val equal : t -> t -> bool
|
|
val compare : t -> t -> int
|
|
val hash : t -> int
|
|
val pp : t Fmt.printer
|
|
|
|
type state
|
|
|
|
val ty : t -> Ty.t
|
|
val bool : state -> bool -> t (* build true/false *)
|
|
val as_bool : t -> bool option
|
|
|
|
val abs : state -> t -> t * bool
|
|
|
|
val map_shallow : state -> (t -> t) -> t -> t
|
|
(** Map function on immediate subterms *)
|
|
|
|
val iter_dag : t -> (t -> unit) -> unit
|
|
|
|
module Tbl : CCHashtbl.S with type key = t
|
|
end
|
|
end
|
|
|
|
module type PROOF = sig
|
|
type t
|
|
val pp : t Fmt.printer
|
|
|
|
val default : t
|
|
end
|
|
|
|
module type LIT = sig
|
|
module T : TERM
|
|
type t
|
|
|
|
val term : t -> T.Term.t
|
|
val sign : t -> bool
|
|
val neg : t -> t
|
|
val abs : t -> t
|
|
|
|
val equal : t -> t -> bool
|
|
val hash : t -> int
|
|
val pp : t Fmt.printer
|
|
end
|
|
|
|
module type CC_ACTIONS = sig
|
|
module T : TERM
|
|
module P : PROOF
|
|
module Lit : LIT with module T = T
|
|
type t
|
|
|
|
val raise_conflict : t -> Lit.t list -> P.t -> 'a
|
|
|
|
val propagate : t -> Lit.t -> reason:(unit -> Lit.t list) -> P.t -> unit
|
|
end
|
|
|
|
module type CC_ARG = sig
|
|
module T : TERM
|
|
module P : PROOF
|
|
module Lit : LIT with module T = T
|
|
module Actions : CC_ACTIONS with module T=T and module P = P and module Lit = Lit
|
|
|
|
val cc_view : T.Term.t -> (T.Fun.t, T.Term.t, T.Term.t Iter.t) CC_view.t
|
|
(** View the term through the lens of the congruence closure *)
|
|
end
|
|
|
|
module type CC_S = sig
|
|
module T : TERM
|
|
module P : PROOF
|
|
module Lit : LIT with module T = T
|
|
module Actions : CC_ACTIONS with module T = T and module Lit = Lit and module P = P
|
|
type term_state = T.Term.state
|
|
type term = T.Term.t
|
|
type fun_ = T.Fun.t
|
|
type lit = Lit.t
|
|
type proof = P.t
|
|
type actions = Actions.t
|
|
|
|
type t
|
|
(** Global state of the congruence closure *)
|
|
|
|
(** An equivalence class is a set of terms that are currently equal
|
|
in the partial model built by the solver.
|
|
The class is represented by a collection of nodes, one of which is
|
|
distinguished and is called the "representative".
|
|
|
|
All information pertaining to the whole equivalence class is stored
|
|
in this representative's node.
|
|
|
|
When two classes become equal (are "merged"), one of the two
|
|
representatives is picked as the representative of the new class.
|
|
The new class contains the union of the two old classes' nodes.
|
|
|
|
We also allow theories to store additional information in the
|
|
representative. This information can be used when two classes are
|
|
merged, to detect conflicts and solve equations à la Shostak.
|
|
*)
|
|
module N : sig
|
|
type t
|
|
|
|
val term : t -> term
|
|
val equal : t -> t -> bool
|
|
val hash : t -> int
|
|
val pp : t Fmt.printer
|
|
|
|
val is_root : t -> bool
|
|
(** Is the node a root (ie the representative of its class)? *)
|
|
|
|
val iter_class : t -> t Iter.t
|
|
(** Traverse the congruence class.
|
|
Precondition: [is_root n] (see {!find} below) *)
|
|
|
|
val iter_parents : t -> t Iter.t
|
|
(** Traverse the parents of the class.
|
|
Precondition: [is_root n] (see {!find} below) *)
|
|
|
|
type bitfield
|
|
(** A field in the bitfield of this node. This should only be
|
|
allocated when a theory is initialized.
|
|
|
|
All fields are initially 0, are backtracked automatically,
|
|
and are merged automatically when classes are merged. *)
|
|
|
|
val get_field : bitfield -> t -> bool
|
|
end
|
|
|
|
module Expl : sig
|
|
type t
|
|
val pp : t Fmt.printer
|
|
|
|
val mk_merge : N.t -> N.t -> t
|
|
val mk_merge_t : term -> term -> t
|
|
val mk_lit : lit -> t
|
|
val mk_list : t list -> t
|
|
val mk_theory : t -> t (* TODO: indicate what theory, or even provide a lemma *)
|
|
end
|
|
|
|
type node = N.t
|
|
(** A node of the congruence closure *)
|
|
|
|
type repr = N.t
|
|
(** Node that is currently a representative *)
|
|
|
|
type explanation = Expl.t
|
|
|
|
(** Accessors *)
|
|
|
|
val term_state : t -> term_state
|
|
|
|
val find : t -> node -> repr
|
|
(** Current representative *)
|
|
|
|
val add_term : t -> term -> node
|
|
(** Add the term to the congruence closure, if not present already.
|
|
Will be backtracked. *)
|
|
|
|
val mem_term : t -> term -> bool
|
|
(** Returns [true] if the term is explicitly present in the congruence closure *)
|
|
|
|
type ev_on_pre_merge = t -> actions -> N.t -> N.t -> Expl.t -> unit
|
|
type ev_on_post_merge = t -> actions -> N.t -> N.t -> unit
|
|
type ev_on_new_term = t -> N.t -> term -> unit
|
|
type ev_on_conflict = t -> th:bool -> lit list -> unit
|
|
type ev_on_propagate = t -> lit -> (unit -> lit list) -> unit
|
|
type ev_on_is_subterm = N.t -> term -> unit
|
|
|
|
val create :
|
|
?stat:Stat.t ->
|
|
?on_pre_merge:ev_on_pre_merge list ->
|
|
?on_post_merge:ev_on_post_merge list ->
|
|
?on_new_term:ev_on_new_term list ->
|
|
?on_conflict:ev_on_conflict list ->
|
|
?on_propagate:ev_on_propagate list ->
|
|
?on_is_subterm:ev_on_is_subterm list ->
|
|
?size:[`Small | `Big] ->
|
|
term_state ->
|
|
t
|
|
(** Create a new congruence closure. *)
|
|
|
|
val allocate_bitfield : descr:string -> t -> N.bitfield
|
|
(** Allocate a new bitfield for the nodes.
|
|
See {!N.bitfield}. *)
|
|
|
|
val set_bitfield : t -> N.bitfield -> bool -> N.t -> unit
|
|
(** Set the bitfield for the node. This will be backtracked.
|
|
See {!N.bitfield}. *)
|
|
|
|
(* TODO: remove? this is managed by the solver anyway? *)
|
|
val on_pre_merge : t -> ev_on_pre_merge -> unit
|
|
(** Add a function to be called when two classes are merged *)
|
|
|
|
val on_post_merge : t -> ev_on_post_merge -> unit
|
|
(** Add a function to be called when two classes are merged *)
|
|
|
|
val on_new_term : t -> ev_on_new_term -> unit
|
|
(** Add a function to be called when a new node is created *)
|
|
|
|
val on_conflict : t -> ev_on_conflict -> unit
|
|
(** Called when the congruence closure finds a conflict *)
|
|
|
|
val on_propagate : t -> ev_on_propagate -> unit
|
|
(** Called when the congruence closure propagates a literal *)
|
|
|
|
val on_is_subterm : t -> ev_on_is_subterm -> unit
|
|
(** Called on terms that are subterms of function symbols *)
|
|
|
|
val set_as_lit : t -> N.t -> lit -> unit
|
|
(** map the given node to a literal. *)
|
|
|
|
val find_t : t -> term -> repr
|
|
(** Current representative of the term.
|
|
@raise Not_found if the term is not already {!add}-ed. *)
|
|
|
|
val add_seq : t -> term Iter.t -> unit
|
|
(** Add a sequence of terms to the congruence closure *)
|
|
|
|
val all_classes : t -> repr Iter.t
|
|
(** All current classes. This is costly, only use if there is no other solution *)
|
|
|
|
val assert_lit : t -> lit -> unit
|
|
(** Given a literal, assume it in the congruence closure and propagate
|
|
its consequences. Will be backtracked.
|
|
|
|
Useful for the theory combination or the SAT solver's functor *)
|
|
|
|
val assert_lits : t -> lit Iter.t -> unit
|
|
(** Addition of many literals *)
|
|
|
|
val explain_eq : t -> N.t -> N.t -> lit list
|
|
(** Explain why the two nodes are equal.
|
|
Fails if they are not, in an unspecified way *)
|
|
|
|
val raise_conflict_from_expl : t -> actions -> Expl.t -> 'a
|
|
(** Raise a conflict with the given explanation
|
|
it must be a theory tautology that [expl ==> absurd].
|
|
To be used in theories. *)
|
|
|
|
val n_true : t -> N.t
|
|
val n_false : t -> N.t
|
|
val n_bool : t -> bool -> N.t
|
|
|
|
val merge : t -> N.t -> N.t -> Expl.t -> unit
|
|
(** Merge these two nodes given this explanation.
|
|
It must be a theory tautology that [expl ==> n1 = n2].
|
|
To be used in theories. *)
|
|
|
|
val merge_t : t -> term -> term -> Expl.t -> unit
|
|
(** Shortcut for adding + merging *)
|
|
|
|
val check : t -> actions -> unit
|
|
(** Perform all pending operations done via {!assert_eq}, {!assert_lit}, etc.
|
|
Will use the {!actions} to propagate literals, declare conflicts, etc. *)
|
|
|
|
val new_merges : t -> bool
|
|
(** Called after {!check}, returns [true] if some pairs of classes
|
|
were merged. *)
|
|
|
|
val push_level : t -> unit
|
|
(** Push backtracking level *)
|
|
|
|
val pop_levels : t -> int -> unit
|
|
(** Restore to state [n] calls to [push_level] earlier. Used during backtracking. *)
|
|
|
|
val get_model : t -> N.t Iter.t Iter.t
|
|
(** get all the equivalence classes so they can be merged in the model *)
|
|
|
|
(**/**)
|
|
module Debug_ : sig
|
|
val pp : t Fmt.printer
|
|
end
|
|
(**/**)
|
|
end
|
|
|
|
(** A view of the solver from a theory's point of view *)
|
|
module type SOLVER_INTERNAL = sig
|
|
module T : TERM
|
|
module P : PROOF
|
|
|
|
type ty = T.Ty.t
|
|
type term = T.Term.t
|
|
type term_state = T.Term.state
|
|
type ty_state = T.Ty.state
|
|
type proof = P.t
|
|
|
|
(** {3 Main type for a solver} *)
|
|
type t
|
|
type solver = t
|
|
|
|
val tst : t -> term_state
|
|
val ty_st : t -> ty_state
|
|
val stats : t -> Stat.t
|
|
|
|
(** {3 Actions for the theories} *)
|
|
|
|
type actions
|
|
|
|
(** {3 Literals}
|
|
|
|
A literal is a (preprocessed) term along with its sign.
|
|
It is directly manipulated by the SAT solver.
|
|
*)
|
|
module Lit : LIT with module T = T
|
|
|
|
type lit = Lit.t
|
|
|
|
(** {2 Congruence Closure} *)
|
|
|
|
module CC : CC_S
|
|
with module T = T
|
|
and module P = P
|
|
and module Lit = Lit
|
|
and type Actions.t = actions
|
|
|
|
val cc : t -> CC.t
|
|
(** Congruence closure for this solver *)
|
|
|
|
(** {3 Simplifiers} *)
|
|
|
|
module Simplify : sig
|
|
type t
|
|
|
|
val tst : t -> term_state
|
|
val ty_st : t -> ty_state
|
|
|
|
val clear : t -> unit
|
|
(** Reset internal cache, etc. *)
|
|
|
|
type hook = t -> term -> term option
|
|
(** Given a term, try to simplify it. Return [None] if it didn't change. *)
|
|
|
|
val normalize : t -> term -> term
|
|
(** Normalize a term using all the hooks. *)
|
|
end
|
|
|
|
type simplify_hook = Simplify.hook
|
|
|
|
val add_simplifier : t -> Simplify.hook -> unit
|
|
|
|
val simplifier : t -> Simplify.t
|
|
|
|
val simp_t : t -> term -> term
|
|
|
|
(** {3 hooks for the theory} *)
|
|
|
|
val propagate : t -> actions -> lit -> reason:(unit -> lit list) -> proof -> unit
|
|
(** Propagate a literal for a reason. This is similar to asserting
|
|
the clause [reason => lit], but more lightweight, and in a way
|
|
that is backtrackable. *)
|
|
|
|
val raise_conflict : t -> actions -> lit list -> proof -> 'a
|
|
(** Give a conflict clause to the solver *)
|
|
|
|
val push_decision : t -> actions -> lit -> unit
|
|
(** Ask the SAT solver to decide the given literal in an extension of the
|
|
current trail. This is useful for theory combination.
|
|
If the SAT solver backtracks, this (potential) decision is removed
|
|
and forgotten. *)
|
|
|
|
val propagate: t -> actions -> lit -> (unit -> lit list) -> unit
|
|
(** Propagate a boolean using a unit clause.
|
|
[expl => lit] must be a theory lemma, that is, a T-tautology *)
|
|
|
|
val propagate_l: t -> actions -> lit -> lit list -> unit
|
|
(** Propagate a boolean using a unit clause.
|
|
[expl => lit] must be a theory lemma, that is, a T-tautology *)
|
|
|
|
val add_clause_temp : t -> actions -> lit list -> unit
|
|
(** Add local clause to the SAT solver. This clause will be
|
|
removed when the solver backtracks. *)
|
|
|
|
val add_clause_permanent : t -> actions -> lit list -> unit
|
|
(** Add toplevel clause to the SAT solver. This clause will
|
|
not be backtracked. *)
|
|
|
|
val mk_lit : t -> actions -> ?sign:bool -> term -> lit
|
|
(** Create a literal. This automatically preprocesses the term. *)
|
|
|
|
val preprocess_term :
|
|
t -> add_clause:(Lit.t list -> unit) -> term -> term
|
|
(** Preprocess a term. *)
|
|
|
|
val add_lit : t -> actions -> lit -> unit
|
|
(** Add the given literal to the SAT solver, so it gets assigned
|
|
a boolean value *)
|
|
|
|
val add_lit_t : t -> actions -> ?sign:bool -> term -> unit
|
|
(** Add the given (signed) bool term to the SAT solver, so it gets assigned
|
|
a boolean value *)
|
|
|
|
val cc_raise_conflict_expl : t -> actions -> CC.Expl.t -> 'a
|
|
(** Raise a conflict with the given congruence closure explanation.
|
|
it must be a theory tautology that [expl ==> absurd].
|
|
To be used in theories. *)
|
|
|
|
val cc_find : t -> CC.N.t -> CC.N.t
|
|
(** Find representative of the node *)
|
|
|
|
val cc_are_equal : t -> term -> term -> bool
|
|
(** Are these two terms equal in the congruence closure? *)
|
|
|
|
val cc_merge : t -> actions -> CC.N.t -> CC.N.t -> CC.Expl.t -> unit
|
|
(** Merge these two nodes in the congruence closure, given this explanation.
|
|
It must be a theory tautology that [expl ==> n1 = n2].
|
|
To be used in theories. *)
|
|
|
|
val cc_merge_t : t -> actions -> term -> term -> CC.Expl.t -> unit
|
|
(** Merge these two terms in the congruence closure, given this explanation.
|
|
See {!cc_merge} *)
|
|
|
|
val cc_add_term : t -> term -> CC.N.t
|
|
(** Add/retrieve congruence closure node for this term.
|
|
To be used in theories *)
|
|
|
|
val cc_mem_term : t -> term -> bool
|
|
(** Return [true] if the term is explicitly in the congruence closure.
|
|
To be used in theories *)
|
|
|
|
val on_cc_pre_merge : t -> (CC.t -> actions -> CC.N.t -> CC.N.t -> CC.Expl.t -> unit) -> unit
|
|
(** Callback for when two classes containing data for this key are merged (called before) *)
|
|
|
|
val on_cc_post_merge : t -> (CC.t -> actions -> CC.N.t -> CC.N.t -> unit) -> unit
|
|
(** Callback for when two classes containing data for this key are merged (called after)*)
|
|
|
|
val on_cc_new_term : t -> (CC.t -> CC.N.t -> term -> unit) -> unit
|
|
(** Callback to add data on terms when they are added to the congruence
|
|
closure *)
|
|
|
|
val on_cc_is_subterm : t -> (CC.N.t -> term -> unit) -> unit
|
|
(** Callback for when a term is a subterm of another term in the
|
|
congruence closure *)
|
|
|
|
val on_cc_conflict : t -> (CC.t -> th:bool -> lit list -> unit) -> unit
|
|
(** Callback called on every CC conflict *)
|
|
|
|
val on_cc_propagate : t -> (CC.t -> lit -> (unit -> lit list) -> unit) -> unit
|
|
(** Callback called on every CC propagation *)
|
|
|
|
val on_partial_check : t -> (t -> actions -> lit Iter.t -> unit) -> unit
|
|
(** Register callbacked to be called with the slice of literals
|
|
newly added on the trail.
|
|
|
|
This is called very often and should be efficient. It doesn't have
|
|
to be complete, only correct. It's given only the slice of
|
|
the trail consisting in new literals. *)
|
|
|
|
val on_final_check: t -> (t -> actions -> lit Iter.t -> unit) -> unit
|
|
(** Register callback to be called during the final check.
|
|
|
|
Must be complete (i.e. must raise a conflict if the set of literals is
|
|
not satisfiable) and can be expensive. The function
|
|
is given the whole trail.
|
|
*)
|
|
|
|
(** {3 Preprocessors}
|
|
These preprocessors turn mixed, raw literals (possibly simplified) into
|
|
literals suitable for reasoning.
|
|
Typically some clauses are also added to the solver. *)
|
|
|
|
type preprocess_hook =
|
|
t ->
|
|
mk_lit:(term -> lit) ->
|
|
add_clause:(lit list -> unit) ->
|
|
term -> term option
|
|
(** Given a term, try to preprocess it. Return [None] if it didn't change.
|
|
Can also add clauses to define new terms.
|
|
@param mk_lit creates a new literal for a boolean term.
|
|
@param add_clause pushes a new clause into the SAT solver.
|
|
*)
|
|
|
|
val add_preprocess : t -> preprocess_hook -> unit
|
|
|
|
(** {3 Model production} *)
|
|
|
|
type model_hook =
|
|
recurse:(t -> CC.N.t -> term) ->
|
|
t -> CC.N.t -> term option
|
|
(** A model-production hook. It takes the solver, a class, and returns
|
|
a term for this class. *)
|
|
|
|
val add_model_hook : t -> model_hook -> unit
|
|
end
|
|
|
|
(** Public view of the solver *)
|
|
module type SOLVER = sig
|
|
module T : TERM
|
|
module P : PROOF
|
|
module Lit : LIT with module T = T
|
|
module Solver_internal
|
|
: SOLVER_INTERNAL
|
|
with module T = T
|
|
and module P = P
|
|
and module Lit = Lit
|
|
(** Internal solver, available to theories. *)
|
|
|
|
type t
|
|
type solver = t
|
|
type term = T.Term.t
|
|
type ty = T.Ty.t
|
|
type lit = Lit.t
|
|
type lemma = P.t
|
|
|
|
(** {3 A theory}
|
|
|
|
|
|
Theories are abstracted over the concrete implementation of the solver,
|
|
so they can work with any implementation.
|
|
|
|
Typically a theory should be a functor taking an argument containing
|
|
a [SOLVER_INTERNAL] or even a full [SOLVER],
|
|
and some additional views on terms, literals, etc.
|
|
that are specific to the theory (e.g. to map terms to linear
|
|
expressions).
|
|
The theory can then be instantiated on any kind of solver for any
|
|
term representation that also satisfies the additional theory-specific
|
|
requirements. Instantiated theories (ie values of type {!SOLVER.theory})
|
|
can be added to the solver.
|
|
*)
|
|
module type THEORY = sig
|
|
type t
|
|
(** The theory's state *)
|
|
|
|
val name : string
|
|
(** Name of the theory *)
|
|
|
|
val create_and_setup : Solver_internal.t -> t
|
|
(** Instantiate the theory's state for the given (internal) solver,
|
|
register callbacks, create keys, etc. *)
|
|
|
|
val push_level : t -> unit
|
|
(** Push backtracking level *)
|
|
|
|
val pop_levels : t -> int -> unit
|
|
(** Pop backtracking levels, restoring the theory to its former state *)
|
|
end
|
|
|
|
type theory = (module THEORY)
|
|
(** A theory that can be used for this particular solver. *)
|
|
|
|
type 'a theory_p = (module THEORY with type t = 'a)
|
|
(** A theory that can be used for this particular solver, with state
|
|
of type ['a]. *)
|
|
|
|
val mk_theory :
|
|
name:string ->
|
|
create_and_setup:(Solver_internal.t -> 'th) ->
|
|
?push_level:('th -> unit) ->
|
|
?pop_levels:('th -> int -> unit) ->
|
|
unit ->
|
|
theory
|
|
(** Helper to create a theory *)
|
|
|
|
(** {3 Boolean Atoms} *)
|
|
module Atom : sig
|
|
type t
|
|
|
|
val equal : t -> t -> bool
|
|
val hash : t -> int
|
|
val pp : t CCFormat.printer
|
|
|
|
val neg : t -> t
|
|
val formula : t -> lit
|
|
val sign : t -> bool
|
|
end
|
|
|
|
(* FIXME: just use terms instead? *)
|
|
(** {3 Semantic values} *)
|
|
module Value : sig
|
|
type t
|
|
|
|
val equal : t -> t -> bool
|
|
val hash : t -> int
|
|
val ty : t -> ty
|
|
val pp : t Fmt.printer
|
|
end
|
|
|
|
module Model : sig
|
|
type t
|
|
|
|
val empty : t
|
|
|
|
val mem : t -> term -> bool
|
|
|
|
val find : t -> term -> Value.t option
|
|
|
|
val eval : t -> term -> Value.t option
|
|
|
|
val pp : t Fmt.printer
|
|
end
|
|
|
|
module Unknown : sig
|
|
type t
|
|
val pp : t CCFormat.printer
|
|
|
|
(*
|
|
type unknown =
|
|
| U_timeout
|
|
| U_incomplete
|
|
*)
|
|
end
|
|
|
|
module Proof : sig
|
|
type t
|
|
val check : t -> unit
|
|
val pp_dot : t Fmt.printer
|
|
end
|
|
type proof = Proof.t
|
|
|
|
(** {3 Main API} *)
|
|
|
|
val stats : t -> Stat.t
|
|
val tst : t -> T.Term.state
|
|
val ty_st : t -> T.Ty.state
|
|
|
|
val create :
|
|
?stat:Stat.t ->
|
|
?size:[`Big | `Tiny | `Small] ->
|
|
(* TODO? ?config:Config.t -> *)
|
|
?store_proof:bool ->
|
|
theories:theory list ->
|
|
T.Term.state ->
|
|
T.Ty.state ->
|
|
unit ->
|
|
t
|
|
(** Create a new solver.
|
|
@param theories theories to load from the start. *)
|
|
|
|
val add_theory : t -> theory -> unit
|
|
(** Add a theory to the solver. This should be called before
|
|
any call to {!solve} or to {!add_clause} and the likes (otherwise
|
|
the theory will have a partial view of the problem). *)
|
|
|
|
val add_theory_p : t -> 'a theory_p -> 'a
|
|
(** Add the given theory and obtain its state *)
|
|
|
|
val add_theory_l : t -> theory list -> unit
|
|
|
|
val mk_atom_lit : t -> lit -> Atom.t
|
|
|
|
val mk_atom_t : t -> ?sign:bool -> term -> Atom.t
|
|
|
|
val add_clause : t -> Atom.t IArray.t -> unit
|
|
|
|
val add_clause_l : t -> Atom.t list -> unit
|
|
|
|
type res =
|
|
| Sat of Model.t
|
|
| Unsat of {
|
|
proof: proof option lazy_t;
|
|
unsat_core: Atom.t list lazy_t;
|
|
}
|
|
| Unknown of Unknown.t
|
|
(** Result of solving for the current set of clauses *)
|
|
|
|
val solve :
|
|
?on_exit:(unit -> unit) list ->
|
|
?check:bool ->
|
|
?on_progress:(t -> unit) ->
|
|
assumptions:Atom.t list ->
|
|
t ->
|
|
res
|
|
(** [solve s] checks the satisfiability of the statement added so far to [s]
|
|
@param check if true, the model is checked before returning
|
|
@param assumptions a set of atoms held to be true. The unsat core,
|
|
if any, will be a subset of [assumptions].
|
|
@param on_exit functions to be run before this returns *)
|
|
|
|
val pp_stats : t CCFormat.printer
|
|
end
|
|
|
|
(** Helper for keeping track of state for each class *)
|
|
|
|
module type MONOID_ARG = sig
|
|
module SI : SOLVER_INTERNAL
|
|
type t
|
|
val pp : t Fmt.printer
|
|
val name : string
|
|
(** name of the monoid's value (short) *)
|
|
|
|
val of_term :
|
|
SI.CC.N.t -> SI.T.Term.t ->
|
|
(t option * (SI.T.Term.t * t) list)
|
|
(** [of_term n t], where [t] is the term annotating node [n],
|
|
returns [maybe_m, l], where:
|
|
- [maybe_m = Some m] if [t] has monoid value [m];
|
|
otherwise [maybe_m=None]
|
|
- [l] is a list of [(u, m_u)] where each [u] is a direct subterm of [t]
|
|
and [m_u] is the monoid value attached to [u].
|
|
*)
|
|
|
|
val merge :
|
|
SI.CC.t ->
|
|
SI.CC.N.t -> t -> SI.CC.N.t -> t ->
|
|
SI.CC.Expl.t ->
|
|
(t, SI.CC.Expl.t) result
|
|
end
|
|
|
|
(** Keep track of monoid state per equivalence class *)
|
|
module Monoid_of_repr(M : MONOID_ARG) : sig
|
|
type t
|
|
val create_and_setup : ?size:int -> M.SI.t -> t
|
|
val push_level : t -> unit
|
|
val pop_levels : t -> int -> unit
|
|
val mem : t -> M.SI.CC.N.t -> bool
|
|
val get : t -> M.SI.CC.N.t -> M.t option
|
|
val iter_all : t -> (M.SI.CC.repr * M.t) Iter.t
|
|
val pp : t Fmt.printer
|
|
end = struct
|
|
module SI = M.SI
|
|
module T = SI.T.Term
|
|
module N = SI.CC.N
|
|
module CC = SI.CC
|
|
module N_tbl = Backtrackable_tbl.Make(N)
|
|
module Expl = SI.CC.Expl
|
|
|
|
type t = {
|
|
values: M.t N_tbl.t; (* repr -> value for the class *)
|
|
field_has_value: N.bitfield; (* bit in CC to filter out quickly classes without value *)
|
|
}
|
|
|
|
let push_level self = N_tbl.push_level self.values
|
|
let pop_levels self n = N_tbl.pop_levels self.values n
|
|
|
|
let mem self n =
|
|
let res = N.get_field self.field_has_value n in
|
|
assert (if res then N_tbl.mem self.values n else true);
|
|
res
|
|
|
|
let get self n =
|
|
if N.get_field self.field_has_value n
|
|
then N_tbl.get self.values n
|
|
else None
|
|
|
|
let on_new_term self cc n (t:T.t) : unit =
|
|
let maybe_m, l = M.of_term n t in
|
|
begin match maybe_m with
|
|
| Some v ->
|
|
Log.debugf 20
|
|
(fun k->k "(@[monoid[%s].on-new-term@ :n %a@ :value %a@])"
|
|
M.name N.pp n M.pp v);
|
|
SI.CC.set_bitfield cc self.field_has_value true n;
|
|
N_tbl.add self.values n v
|
|
| None -> ()
|
|
end;
|
|
List.iter
|
|
(fun (u,m_u) ->
|
|
Log.debugf 20
|
|
(fun k->k "(@[monoid[%s].on-new-term.sub@ :n %a@ :sub-t %a@ :value %a@])"
|
|
M.name N.pp n T.pp u M.pp m_u);
|
|
let n_u =
|
|
try CC.find_t cc u
|
|
with Not_found -> Error.errorf "subterm %a does not have a repr" T.pp u
|
|
in
|
|
if N.get_field self.field_has_value n_u then (
|
|
let m_u' =
|
|
try N_tbl.find self.values n_u
|
|
with Not_found ->
|
|
Error.errorf "node %a has bitfield but no value" N.pp n_u
|
|
in
|
|
match M.merge cc n_u m_u n_u m_u' (Expl.mk_list []) with
|
|
| Error expl ->
|
|
Error.errorf
|
|
"when merging@ @[for node %a@],@ \
|
|
values %a and %a:@ conflict %a"
|
|
N.pp n_u M.pp m_u M.pp m_u' CC.Expl.pp expl
|
|
| Ok m_u_merged ->
|
|
Log.debugf 20
|
|
(fun k->k "(@[monoid[%s].on-new-term.sub.merged@ \
|
|
:n %a@ :sub-t %a@ :value %a@])"
|
|
M.name N.pp n T.pp u M.pp m_u_merged);
|
|
N_tbl.add self.values n_u m_u_merged;
|
|
) else (
|
|
(* just add to [n_u] *)
|
|
SI.CC.set_bitfield cc self.field_has_value true n_u;
|
|
N_tbl.add self.values n_u m_u;
|
|
)
|
|
)
|
|
l;
|
|
()
|
|
|
|
let iter_all (self:t) : _ Iter.t =
|
|
N_tbl.to_iter self.values
|
|
|
|
let on_pre_merge (self:t) cc acts n1 n2 e_n1_n2 : unit =
|
|
begin match get self n1, get self n2 with
|
|
| Some v1, Some v2 ->
|
|
Log.debugf 5
|
|
(fun k->k
|
|
"(@[monoid[%s].on_pre_merge@ (@[:n1 %a@ :val1 %a@])@ (@[:n2 %a@ :val2 %a@])@])"
|
|
M.name N.pp n1 M.pp v1 N.pp n2 M.pp v2);
|
|
begin match M.merge cc n1 v1 n2 v2 e_n1_n2 with
|
|
| Ok v' ->
|
|
N_tbl.remove self.values n2; (* only keep repr *)
|
|
N_tbl.add self.values n1 v';
|
|
| Error expl -> SI.CC.raise_conflict_from_expl cc acts expl
|
|
end
|
|
| None, Some cr ->
|
|
SI.CC.set_bitfield cc self.field_has_value true n1;
|
|
N_tbl.add self.values n1 cr;
|
|
N_tbl.remove self.values n2; (* only keep reprs *)
|
|
| Some _, None -> () (* already there on the left *)
|
|
| None, None -> ()
|
|
end
|
|
|
|
let pp out (self:t) : unit =
|
|
let pp_e out (t,v) = Fmt.fprintf out "(@[%a@ :has %a@])" N.pp t M.pp v in
|
|
Fmt.fprintf out "(@[%a@])" (Fmt.iter pp_e) (iter_all self)
|
|
|
|
let create_and_setup ?size (solver:SI.t) : t =
|
|
let field_has_value =
|
|
SI.CC.allocate_bitfield ~descr:("monoid."^M.name^".has-value")
|
|
(SI.cc solver) in
|
|
let self = { values=N_tbl.create ?size (); field_has_value; } in
|
|
SI.on_cc_new_term solver (on_new_term self);
|
|
SI.on_cc_pre_merge solver (on_pre_merge self);
|
|
self
|
|
end
|