refactor: change theory API to be simpler and more imperative

This commit is contained in:
Simon Cruanes 2019-01-23 08:36:07 -06:00 committed by Guillaume Bury
parent 53dd3acd4e
commit 9024b0f0a9
3 changed files with 112 additions and 75 deletions

View file

@ -1440,10 +1440,7 @@ module Make(Plugin : PLUGIN)
(* Get the correct vector to insert a clause in. *) (* Get the correct vector to insert a clause in. *)
let clause_vector st c = let clause_vector st c =
match c.cpremise with if Clause.learnt c then st.clauses_learnt else st.clauses_hyps
| Hyp | Lemma _ -> st.clauses_hyps
| History _ -> st.clauses_learnt
| Local -> assert false (* never added directly *)
(* Add a new clause, simplifying, propagating, and backtracking if (* Add a new clause, simplifying, propagating, and backtracking if
the clause is false in the current trail *) the clause is false in the current trail *)
@ -1598,6 +1595,8 @@ module Make(Plugin : PLUGIN)
ignore (th_eval st a); ignore (th_eval st a);
a a
exception Th_conflict of Clause.t
let slice_get st i = let slice_get st i =
match Vec.get st.trail i with match Vec.get st.trail i with
| Atom a -> | Atom a ->
@ -1606,12 +1605,18 @@ module Make(Plugin : PLUGIN)
Solver_intf.Assign (term, v) Solver_intf.Assign (term, v)
| Lit _ -> assert false | Lit _ -> assert false
let slice_push st (l:formula list) (lemma:lemma): unit = let slice_push st ?(keep=false) (l:formula list) (lemma:lemma): unit =
let atoms = List.rev_map (create_atom st) l in let atoms = List.rev_map (create_atom st) l in
let c = Clause.make atoms (Lemma lemma) in let c = Clause.make atoms (Lemma lemma) in
if not keep then Clause.set_learnt c true;
Log.debugf info (fun k->k "Pushing clause %a" Clause.debug c); Log.debugf info (fun k->k "Pushing clause %a" Clause.debug c);
Stack.push c st.clauses_to_add Stack.push c st.clauses_to_add
let slice_raise st (l:formula list) proof : 'a =
let atoms = List.rev_map (create_atom st) l in
let c = Clause.make atoms (Lemma proof) in
raise_notrace (Th_conflict c)
let slice_propagate (st:t) f = function let slice_propagate (st:t) f = function
| Solver_intf.Eval l -> | Solver_intf.Eval l ->
let a = mk_atom st f in let a = mk_atom st f in
@ -1633,23 +1638,44 @@ module Make(Plugin : PLUGIN)
invalid_arg "Msat.Internal.slice_propagate" invalid_arg "Msat.Internal.slice_propagate"
) )
let current_slice st : (_,_,_) Solver_intf.slice = { let[@specialise] slice_iter st ~full f : unit =
Solver_intf.start = st.th_head; for i = (if full then 0 else st.th_head) to Vec.size st.trail-1 do
length = Vec.size st.trail - st.th_head; let e = match Vec.get st.trail i with
get = slice_get st; | Atom a ->
Solver_intf.Lit a.lit
| Lit {term; assigned = Some v; _} ->
Solver_intf.Assign (term, v)
| Lit _ -> assert false
in
f e
done
let[@inline] current_slice st : (_,_,_) Solver_intf.slice = {
Solver_intf.
iter_assumptions=slice_iter st ~full:false;
push = slice_push st; push = slice_push st;
propagate = slice_propagate st; propagate = slice_propagate st;
raise_conflict=slice_raise st;
} }
(* full slice, for [if_sat] final check *) (* full slice, for [if_sat] final check *)
let full_slice st : (_,_,_) Solver_intf.slice = { let[@inline] full_slice st : (_,_,_) Solver_intf.slice = {
Solver_intf.start = 0; Solver_intf.
length = Vec.size st.trail; iter_assumptions=slice_iter st ~full:true;
get = slice_get st;
push = slice_push st; push = slice_push st;
propagate = (fun _ -> assert false); propagate = slice_propagate st;
raise_conflict=slice_raise st;
} }
(* Assert that the conflict is indeeed a conflict *)
let check_is_conflict_ (c:Clause.t) : unit =
if not @@ Array.for_all (Atom.is_false) c.atoms then (
let msg =
Format.asprintf "msat:core/internal: invalid conflict %a" Clause.debug c
in
raise (Invalid_argument msg);
)
(* some boolean literals were decided/propagated within Msat. Now we (* some boolean literals were decided/propagated within Msat. Now we
need to inform the theory of those assumptions, so it can do its job. need to inform the theory of those assumptions, so it can do its job.
@return the conflict clause, if the theory detects unsatisfiability *) @return the conflict clause, if the theory detects unsatisfiability *)
@ -1662,18 +1688,11 @@ module Make(Plugin : PLUGIN)
let slice = current_slice st in let slice = current_slice st in
st.th_head <- st.elt_head; (* catch up *) st.th_head <- st.elt_head; (* catch up *)
match Plugin.assume st.th slice with match Plugin.assume st.th slice with
| Solver_intf.Th_sat -> | () ->
propagate st propagate st
| Solver_intf.Th_unsat (l, p) -> | exception Th_conflict c ->
(* conflict *) check_is_conflict_ c;
let l = List.rev_map (create_atom st) l in Array.iter (fun a -> insert_var_order st (Elt.of_var a.var)) c.atoms;
(* Assert that the conflcit is indeeed a conflict *)
if not @@ List.for_all (fun a -> a.neg.is_true) l then (
raise (Invalid_argument "msat:core/internal: invalid conflict");
);
List.iter (fun a -> insert_var_order st (Elt.of_var a.var)) l;
(* Create the clause and return it. *)
let c = Clause.make l (Lemma p) in
Some c Some c
) )
@ -1892,14 +1911,18 @@ module Make(Plugin : PLUGIN)
| E_sat -> | E_sat ->
assert (st.elt_head = Vec.size st.trail); assert (st.elt_head = Vec.size st.trail);
begin match Plugin.if_sat st.th (full_slice st) with begin match Plugin.if_sat st.th (full_slice st) with
| Solver_intf.Th_sat -> () | () ->
| Solver_intf.Th_unsat (l, p) -> if st.elt_head = Vec.size st.trail &&
let atoms = List.rev_map (create_atom st) l in Stack.is_empty st.clauses_to_add then (
let c = Clause.make atoms (Lemma p) in raise_notrace E_sat
);
(* otherwise, keep on *)
| exception Th_conflict c ->
check_is_conflict_ c;
Array.iter (fun a -> insert_var_order st (Elt.of_var a.var)) c.atoms;
Log.debugf info (fun k -> k "Theory conflict clause: %a" Clause.debug c); Log.debugf info (fun k -> k "Theory conflict clause: %a" Clause.debug c);
Stack.push c st.clauses_to_add Stack.push c st.clauses_to_add
end; end;
if Stack.is_empty st.clauses_to_add then raise_notrace E_sat
end end
done done
with E_sat -> () with E_sat -> ()
@ -2082,8 +2105,8 @@ module Make_pure_sat(F: Solver_intf.FORMULA) =
type proof = Solver_intf.void type proof = Solver_intf.void
type level = unit type level = unit
let current_level () = () let current_level () = ()
let assume () _ = Solver_intf.Th_sat let assume () _ = ()
let if_sat () _ = Solver_intf.Th_sat let if_sat () _ = ()
let backtrack () _ = () let backtrack () _ = ()
let eval () _ = Solver_intf.Unknown let eval () _ = Solver_intf.Unknown
let assign () t = t let assign () t = t

View file

@ -27,9 +27,21 @@ type 'clause export = 'clause Solver_intf.export = {
hyps : 'clause Vec.t; hyps : 'clause Vec.t;
history : 'clause Vec.t; history : 'clause Vec.t;
} }
type ('formula, 'proof) th_res = ('formula, 'proof) Solver_intf.th_res =
| Th_sat type ('term, 'formula) assumption = ('term, 'formula) Solver_intf.assumption =
| Th_unsat of 'formula list * 'proof | Lit of 'formula
| Assign of 'term * 'term (** The first term is assigned to the second *)
type ('term, 'formula, 'proof) reason = ('term, 'formula, 'proof) Solver_intf.reason =
| Eval of 'term list
| Consequence of 'formula list * 'proof
type ('term, 'formula, 'proof) slice = ('term, 'formula, 'proof) Solver_intf.slice = {
iter_assumptions: (('term,'formula) assumption -> unit) -> unit;
push : ?keep:bool -> 'formula list -> 'proof -> unit;
raise_conflict: 'b. 'formula list -> 'proof -> 'b;
propagate : 'formula -> ('term, 'formula, 'proof) reason -> unit;
}
type negated = Solver_intf.negated = Negated | Same_sign type negated = Solver_intf.negated = Negated | Same_sign

View file

@ -68,39 +68,40 @@ type 'term eval_res =
- [Valued (false, [x; y])] if [x] and [y] are assigned to 1 (or any non-zero number) - [Valued (false, [x; y])] if [x] and [y] are assigned to 1 (or any non-zero number)
*) *)
type ('formula, 'proof) th_res =
| Th_sat
(** The current set of assumptions is satisfiable. *)
| Th_unsat of 'formula list * 'proof
(** The current set of assumptions is *NOT* satisfiable, and here is a
theory tautology (with its proof), for which every litteral is false
under the current assumptions. *)
(** Type returned by the theory. Formulas in the unsat clause must come from the
current set of assumptions, i.e must have been encountered in a slice. *)
type ('term, 'formula) assumption = type ('term, 'formula) assumption =
| Lit of 'formula (** The given formula is asserted true by the solver *) | Lit of 'formula (** The given formula is asserted true by the solver *)
| Assign of 'term * 'term (** The first term is assigned to the second *) | Assign of 'term * 'term (** The first term is assigned to the second *)
(** Asusmptions made by the core SAT solver. *) (** Asusmptions made by the core SAT solver. *)
type ('term, 'formula, 'proof) reason = type ('term, 'formula, 'proof) reason =
| Eval of 'term list (** The formula can be evalutaed using the terms in the list *) | Eval of 'term list
| Consequence of 'formula list * 'proof (** [Consequence (l, p)] means that the formulas in [l] imply (** The formula can be evalutaed using the terms in the list *)
the propagated formula [f]. The proof should be a proof of | Consequence of 'formula list * 'proof
the clause "[l] implies [f]". *) (** [Consequence (l, p)] means that the formulas in [l] imply the propagated
formula [f]. The proof should be a proof of the clause "[l] implies [f]".
*)
(** The type of reasons for propagations of a formula [f]. *) (** The type of reasons for propagations of a formula [f]. *)
(* TODO: find a way to use atoms instead of formulas here *)
type ('term, 'formula, 'proof) slice = { type ('term, 'formula, 'proof) slice = {
start : int; (** Start of the slice *) iter_assumptions: (('term,'formula) assumption -> unit) -> unit;
length : int; (** Length of the slice *) (** Traverse the new assumptions on the boolean trail. *)
get : int -> ('term, 'formula) assumption; (** Accessor for the assertions in the slice.
Should only be called on integers [i] s.t. push : ?keep:bool -> 'formula list -> 'proof -> unit;
[start <= i < start + length] *) (** Add a clause to the solver.
push : 'formula list -> 'proof -> unit; (** Add a clause to the solver. *) @param keep if true, the clause will be kept by the solver.
propagate : 'formula -> Otherwise the solver is allowed to GC the clause and propose this
('term, 'formula, 'proof) reason -> unit; (** Propagate a formula, i.e. the theory can partial model again.
evaluate the formula to be true (see the *)
definition of {!type:eval_res} *)
raise_conflict: 'b. 'formula list -> 'proof -> 'b;
(** Raise a conflict, yielding control back to the solver.
The list of atoms must be a valid theory lemma that is false in the
current trail. *)
propagate : 'formula -> ('term, 'formula, 'proof) reason -> unit;
(** Propagate a formula, i.e. the theory can evaluate the formula to be true
(see the definition of {!type:eval_res} *)
} }
(** The type for a slice of assertions to assume/propagate in the theory. *) (** The type for a slice of assertions to assume/propagate in the theory. *)
@ -174,19 +175,19 @@ module type PLUGIN_CDCL_T = sig
(** Return the current level of the theory (either the empty/beginning state, or the (** Return the current level of the theory (either the empty/beginning state, or the
last level returned by the [assume] function). *) last level returned by the [assume] function). *)
val assume : t -> (void, Formula.t, proof) slice -> (Formula.t, proof) th_res val assume : t -> (void, Formula.t, proof) slice -> unit
(** Assume the formulas in the slice, possibly pushing new formulas to be propagated, (** Assume the formulas in the slice, possibly pushing new formulas to be
and returns the result of the new assumptions. *) propagated or raising a conflict. *)
val if_sat : t -> (void, Formula.t, proof) slice -> (Formula.t, proof) th_res val if_sat : t -> (void, Formula.t, proof) slice -> unit
(** Called at the end of the search in case a model has been found. If no new clause is (** Called at the end of the search in case a model has been found.
pushed and the function returns [Sat], then proof search ends and 'sat' is returned, If no new clause is pushed, then proof search ends and 'sat' is returned;
else search is resumed. *) if lemmas are added, search is resumed;
if a conflict clause is added, search backtracks and then resumes. *)
val backtrack : t -> level -> unit val backtrack : t -> level -> unit
(** Backtrack to the given level. After a call to [backtrack l], the theory should be in the (** Backtrack to the given level. After a call to [backtrack l], the theory should be in the
same state as when it returned the value [l], *) same state as when it returned the value [l], *)
end end
(** Signature for theories to be given to the Model Constructing Solver. *) (** Signature for theories to be given to the Model Constructing Solver. *)
@ -203,14 +204,15 @@ module type PLUGIN_MCSAT = sig
(** Return the current level of the theory (either the empty/beginning state, or the (** Return the current level of the theory (either the empty/beginning state, or the
last level returned by the [assume] function). *) last level returned by the [assume] function). *)
val assume : t -> (Term.t, Formula.t, proof) slice -> (Formula.t, proof) th_res val assume : t -> (Term.t, Formula.t, proof) slice -> unit
(** Assume the formulas in the slice, possibly pushing new formulas to be propagated, (** Assume the formulas in the slice, possibly pushing new formulas to be
and returns the result of the new assumptions. *) propagated or raising a conflict. *)
val if_sat : t -> (Term.t, Formula.t, proof) slice -> (Formula.t, proof) th_res val if_sat : t -> (Term.t, Formula.t, proof) slice -> unit
(** Called at the end of the search in case a model has been found. If no new clause is (** Called at the end of the search in case a model has been found.
pushed and the function returns [Sat], then proof search ends and 'sat' is returned, If no new clause is pushed, then proof search ends and 'sat' is returned;
else search is resumed. *) if lemmas are added, search is resumed;
if a conflict clause is added, search backtracks and then resumes. *)
val backtrack : t -> level -> unit val backtrack : t -> level -> unit
(** Backtrack to the given level. After a call to [backtrack l], the theory should be in the (** Backtrack to the given level. After a call to [backtrack l], the theory should be in the