refactor(sat): wip: simpler clauses

This commit is contained in:
Simon Cruanes 2018-05-20 13:38:39 -05:00
parent f69d5cd9f1
commit 4a39192846
4 changed files with 63 additions and 116 deletions

View file

@ -28,18 +28,7 @@ module Make(St : Sidekick_sat.S) = struct
Format.fprintf fmt "c Local assumptions@,a %a@," St.Clause.pp_dimacs vec Format.fprintf fmt "c Local assumptions@,a %a@," St.Clause.pp_dimacs vec
let map_filter_learnt c = let map_filter_learnt c =
match St.Clause.premise c with if St.Clause.is_learnt c then Some c else None
| St.Hyp -> assert false
| St.Lemma _ -> Some c
| St.History l ->
begin match l with
| [] -> assert false
| d :: _ ->
begin match St.Clause.premise d with
| St.Lemma _ -> Some d
| St.Hyp | St.History _ -> None
end
end
let filter_vec learnt = let filter_vec learnt =
let lemmas = Vec.make (Vec.size learnt) St.Clause.dummy in let lemmas = Vec.make (Vec.size learnt) St.Clause.dummy in

View file

@ -58,6 +58,7 @@ module Make (Th : Theory_intf.S) = struct
and premise = and premise =
| Hyp | Hyp
| Lemma of lemma | Lemma of lemma
| Simplified of clause
| History of clause list | History of clause list
type proof = clause type proof = clause
@ -96,10 +97,11 @@ module Make (Th : Theory_intf.S) = struct
(* Constructors *) (* Constructors *)
module MF = Hashtbl.Make(Th.Form) module MF = Hashtbl.Make(Th.Form)
let name_of_clause c = match c.c_premise with let rec name_of_clause c = match c.c_premise with
| Hyp -> "H" ^ string_of_int c.name | Hyp -> "H" ^ string_of_int c.name
| Lemma _ -> "T" ^ string_of_int c.name | Lemma _ -> "T" ^ string_of_int c.name
| History _ -> "C" ^ string_of_int c.name | History _ -> "C" ^ string_of_int c.name
| Simplified d -> name_of_clause d
module H = Heap.Make(struct module H = Heap.Make(struct
type t = var type t = var
@ -156,9 +158,6 @@ module Make (Th : Theory_intf.S) = struct
elt_levels : int Vec.t; elt_levels : int Vec.t;
(* decision levels in [trail] *) (* decision levels in [trail] *)
user_levels : int Vec.t;
(* user levels in [clause_tmp] *)
backtrack_levels : int Vec.t; backtrack_levels : int Vec.t;
(* user levels in [backtrack] *) (* user levels in [backtrack] *)
@ -193,6 +192,9 @@ module Make (Th : Theory_intf.S) = struct
mutable clause_incr : float; mutable clause_incr : float;
(* increment for clauses' activity *) (* increment for clauses' activity *)
tmp_vec_seen : atom Vec.t;
(* temporary vector used for conflict resolution *)
to_add : (bool * clause) Vec.t; to_add : (bool * clause) Vec.t;
(* clauses to add, from the user *) (* clauses to add, from the user *)
} }
@ -217,10 +219,10 @@ module Make (Th : Theory_intf.S) = struct
backtrack_levels = Vec.make size_lvl (-1); backtrack_levels = Vec.make size_lvl (-1);
backtrack = Vec.make size_lvl (fun () -> ()); backtrack = Vec.make size_lvl (fun () -> ());
to_redo_after_backtrack = Vec.make 10 (fun () -> ()); to_redo_after_backtrack = Vec.make 10 (fun () -> ());
user_levels = Vec.make 0 (-1);
order = H.create(); order = H.create();
to_add = Vec.make_empty (true, dummy_clause); to_add = Vec.make_empty (true, dummy_clause);
tmp_vec_seen = Vec.make_empty dummy_atom;
var_incr = 1.; var_incr = 1.;
clause_incr = 1.; clause_incr = 1.;
@ -422,6 +424,12 @@ module Make (Th : Theory_intf.S) = struct
let[@inline] activity c = c.activity let[@inline] activity c = c.activity
let[@inline] set_activity c w = c.activity <- w let[@inline] set_activity c w = c.activity <- w
let rec is_learnt c =
match c.c_premise with
| Simplified c' -> is_learnt c'
| History _ -> true
| Hyp | Lemma _ -> false
module Tbl = Hashtbl.Make(struct module Tbl = Hashtbl.Make(struct
type t = clause type t = clause
let hash = hash let hash = hash
@ -430,9 +438,10 @@ module Make (Th : Theory_intf.S) = struct
let pp fmt c = Format.fprintf fmt "%s : %a" (name c) Atom.pp_a c.atoms let pp fmt c = Format.fprintf fmt "%s : %a" (name c) Atom.pp_a c.atoms
let debug_premise out = function let rec debug_premise out = function
| Hyp -> Format.fprintf out "hyp" | Hyp -> Format.fprintf out "hyp"
| Lemma _ -> Format.fprintf out "th_lemma" | Lemma _ -> Format.fprintf out "th_lemma"
| Simplified d -> debug_premise out d.c_premise
| History v -> | History v ->
Format.fprintf out "[@[%a@]]" Format.fprintf out "[@[%a@]]"
(Util.pp_list @@ CCFormat.of_to_string name_of_clause) v (Util.pp_list @@ CCFormat.of_to_string name_of_clause) v
@ -457,6 +466,7 @@ module Make (Th : Theory_intf.S) = struct
let pp = print let pp = print
end end
(* TODO: simplify this *)
module Proof = struct module Proof = struct
type t = proof type t = proof
@ -562,16 +572,6 @@ module Make (Th : Theory_intf.S) = struct
Log.debugf 1 (fun k -> k "Error while proving atom %a" Atom.debug a); Log.debugf 1 (fun k -> k "Error while proving atom %a" Atom.debug a);
raise (Resolution_error "Cannot prove atom") raise (Resolution_error "Cannot prove atom")
let prove_unsat conflict =
if Array.length conflict.atoms = 0 then conflict
else (
Log.debugf 2 (fun k -> k "Proving unsat from: @[%a@]" Clause.debug conflict);
let l = Array.fold_left (fun acc a -> set_atom_proof a :: acc) [] conflict.atoms in
let res = Clause.make [| |] (History (conflict :: l)) in
Log.debugf 2 (fun k -> k "Proof found: @[%a@]" Clause.debug res);
res
)
let prove_atom a = let prove_atom a =
if (a.is_true && a.var.v_level = 0) then if (a.is_true && a.var.v_level = 0) then
Some (set_atom_proof a) Some (set_atom_proof a)
@ -624,7 +624,7 @@ module Make (Th : Theory_intf.S) = struct
| History [] -> | History [] ->
Log.debugf 1 (fun k -> k "Empty history for clause: %a" Clause.debug conclusion); Log.debugf 1 (fun k -> k "Empty history for clause: %a" Clause.debug conclusion);
raise (Resolution_error "Empty history") raise (Resolution_error "Empty history")
| History [ c ] -> | Simplified c ->
let duplicates, res = analyze (list c) in let duplicates, res = analyze (list c) in
assert (cmp_cl res (list conclusion) = 0); assert (cmp_cl res (list conclusion) = 0);
{ conclusion; step = Duplicate (c, List.concat duplicates) } { conclusion; step = Duplicate (c, List.concat duplicates) }
@ -673,6 +673,7 @@ module Make (Th : Theory_intf.S) = struct
Clause.set_visited c true; Clause.set_visited c true;
match c.c_premise with match c.c_premise with
| Hyp | Lemma _ -> aux (c :: res) acc r | Hyp | Lemma _ -> aux (c :: res) acc r
| Simplified c -> aux res acc [c]
| History h -> | History h ->
let l = List.fold_left (fun acc c -> let l = List.fold_left (fun acc c ->
if not (Clause.visited c) then c :: acc else acc) r h in if not (Clause.visited c) then c :: acc else acc) r h in
@ -733,7 +734,7 @@ module Make (Th : Theory_intf.S) = struct
let[@inline] nb_clauses st = Vec.size st.clauses let[@inline] nb_clauses st = Vec.size st.clauses
let[@inline] decision_level st = Vec.size st.elt_levels let[@inline] decision_level st = Vec.size st.elt_levels
let[@inline] base_level st = Vec.size st.user_levels let[@inline] base_level _st = 0
(* [redo_down_to_level_0 f ~undo] performs [f] now. Upon backtracking (* [redo_down_to_level_0 f ~undo] performs [f] now. Upon backtracking
before current level, some undo actions scheduled by [f] before current level, some undo actions scheduled by [f]
@ -786,9 +787,9 @@ module Make (Th : Theory_intf.S) = struct
let var_bump_activity st v = let var_bump_activity st v =
v.v_weight <- v.v_weight +. st.var_incr; v.v_weight <- v.v_weight +. st.var_incr;
if v.v_weight > 1e100 then ( if v.v_weight > 1e100 then (
for i = 0 to n_vars st - 1 do Vec.iter
Var.set_weight (get_var st i) ((Var.weight (get_var st i)) *. 1e-100) (fun v -> Var.set_weight v (Var.weight v *. 1e-100))
done; st.vars;
st.var_incr <- st.var_incr *. 1e-100; st.var_incr <- st.var_incr *. 1e-100;
); );
if H.in_heap v then ( if H.in_heap v then (
@ -805,21 +806,10 @@ module Make (Th : Theory_intf.S) = struct
st.clause_incr <- st.clause_incr *. 1e-20 st.clause_incr <- st.clause_incr *. 1e-20
) )
(* Simplification of clauses.
When adding new clauses, it is desirable to 'simplify' them, i.e
minimize the amount of literals in it, because it greatly reduces
the search space for new watched literals during propagation.
Additionally, we have to partition the lits, to ensure the watched
literals (which are the first two lits of the clause) are appropriate.
Indeed, it is better to watch true literals, and then unassigned literals.
Watching false literals should be a last resort, and come with constraints
(see {!add_clause}).
*)
exception Trivial exception Trivial
(* [arr_to_list a i] converts [a.(i), ... a.(length a-1)] into a list *) (* [arr_to_list a i] converts [a.(i), ... a.(length a-1)] into a list *)
let arr_to_list arr i : _ list = let array_slice_to_list arr i : _ list =
if i >= Array.length arr then [] if i >= Array.length arr then []
else Array.to_list (Array.sub arr i (Array.length arr - i)) else Array.to_list (Array.sub arr i (Array.length arr - i))
@ -865,7 +855,7 @@ module Make (Th : Theory_intf.S) = struct
if l = 0 then if l = 0 then
raise Trivial (* A var true at level 0 gives a trivially true clause *) raise Trivial (* A var true at level 0 gives a trivially true clause *)
else else
(a :: trues) @ unassigned @ falses @ (arr_to_list atoms (i + 1)) (a :: trues) @ unassigned @ falses @ (array_slice_to_list atoms (i + 1))
) else if a.neg.is_true then ( ) else if a.neg.is_true then (
partition_aux trues unassigned (a::falses) (i + 1) partition_aux trues unassigned (a::falses) (i + 1)
) else ( ) else (
@ -1071,7 +1061,8 @@ module Make (Th : Theory_intf.S) = struct
let learnt = ref [] in let learnt = ref [] in
let cond = ref true in let cond = ref true in
let blevel = ref 0 in let blevel = ref 0 in
let seen = ref [] in let seen = st.tmp_vec_seen in
Vec.clear seen;
let c = ref c_clause in let c = ref c_clause in
let tr_ind = ref (Vec.size st.trail - 1) in let tr_ind = ref (Vec.size st.trail - 1) in
let history = ref [] in let history = ref [] in
@ -1085,7 +1076,7 @@ module Make (Th : Theory_intf.S) = struct
let clause = !c in let clause = !c in
Log.debugf 5 (fun k->k"(@[sat.resolving-clause@ %a@])" Clause.debug clause); Log.debugf 5 (fun k->k"(@[sat.resolving-clause@ %a@])" Clause.debug clause);
begin match clause.c_premise with begin match clause.c_premise with
| History _ -> clause_bump_activity st clause | History _ | Simplified _ -> clause_bump_activity st clause
| Hyp | Lemma _ -> () | Hyp | Lemma _ -> ()
end; end;
history := clause :: !history; history := clause :: !history;
@ -1102,7 +1093,7 @@ module Make (Th : Theory_intf.S) = struct
if not (Var.seen_both q.var) then ( if not (Var.seen_both q.var) then (
Atom.mark q; Atom.mark q;
Atom.mark q.neg; Atom.mark q.neg;
seen := q :: !seen; Vec.push seen q;
if q.var.v_level > 0 then ( if q.var.v_level > 0 then (
var_bump_activity st q.var; var_bump_activity st q.var;
if q.var.v_level >= conflict_level then ( if q.var.v_level >= conflict_level then (
@ -1136,7 +1127,8 @@ module Make (Th : Theory_intf.S) = struct
c := cl c := cl
| _ -> assert false | _ -> assert false
done; done;
List.iter (fun q -> Var.clear q.var) !seen; Vec.iter (fun q -> Var.clear q.var) seen;
Vec.clear seen;
let l = List.fast_sort (fun p q -> compare q.var.v_level p.var.v_level) !learnt in let l = List.fast_sort (fun p q -> compare q.var.v_level p.var.v_level) !learnt in
let level, is_uip = backtrack_lvl st l in let level, is_uip = backtrack_lvl st l in
{ cr_backtrack_lvl = level; { cr_backtrack_lvl = level;
@ -1148,55 +1140,6 @@ module Make (Th : Theory_intf.S) = struct
let[@inline] analyze st c_clause : conflict_res = let[@inline] analyze st c_clause : conflict_res =
analyze_sat st c_clause analyze_sat st c_clause
(* FIXME: this shouldn't go through the backtracking stack, but directly
into one vector where it can be GC'd later *)
(* add the learnt clause to the clause database, propagate, etc. *)
let record_learnt_clause st (confl:clause) (cr:conflict_res): unit =
begin match cr.cr_learnt with
| [] -> assert false
| [fuip] ->
assert (cr.cr_backtrack_lvl = 0);
if fuip.neg.is_true then (
report_unsat st confl
) else (
let uclause = Clause.make_l cr.cr_learnt (History cr.cr_history) in
Vec.push st.clauses uclause;
(* no need to attach [uclause], it is true at level 0 *)
enqueue_bool st fuip (Bcp uclause)
)
| fuip :: _ ->
let lclause = Clause.make_l cr.cr_learnt (History cr.cr_history) in
Vec.push st.clauses lclause;
(* clause will stay attached *)
redo_down_to_level_0 st (fun () -> attach_clause st lclause);
clause_bump_activity st lclause;
if cr.cr_is_uip then (
enqueue_bool st fuip (Bcp lclause)
) else (
st.next_decision <- Some fuip.neg
)
end;
var_decay_activity st;
clause_decay_activity st
(* process a conflict:
- learn clause
- backtrack
- report unsat if conflict at level 0
*)
let add_boolean_conflict st (confl:clause): unit =
Log.debugf 3 (fun k -> k "(@[@{<Yellow>sat.boolean-conflict@}@ %a@])" Clause.debug confl);
st.next_decision <- None;
assert (decision_level st >= base_level st);
if decision_level st = base_level st ||
Array.for_all (fun a -> a.var.v_level <= base_level st) confl.atoms then (
(* Top-level conflict *)
report_unsat st confl;
);
let cr = analyze st confl in
cancel_until st (max cr.cr_backtrack_lvl (base_level st));
record_learnt_clause st confl cr
(* Add clause, accounting for backtracking semantics. (* Add clause, accounting for backtracking semantics.
- always add literals to queue if not decided yet - always add literals to queue if not decided yet
- if clause is already true, probably just do nothing - if clause is already true, probably just do nothing
@ -1204,7 +1147,7 @@ module Make (Th : Theory_intf.S) = struct
but do not add clause but do not add clause
- permanent stuff: just re-add it on backtrack - permanent stuff: just re-add it on backtrack
*) *)
let add_clause_ st (c:clause) : unit = let rec add_clause_ st (c:clause) : unit =
Log.debugf 5 Log.debugf 5
(fun k -> k "(@[@{<Yellow>sat.add_clause@}@ %a@])" Clause.debug c); (fun k -> k "(@[@{<Yellow>sat.add_clause@}@ %a@])" Clause.debug c);
assert (not @@ Clause.dead c); assert (not @@ Clause.dead c);
@ -1250,23 +1193,43 @@ module Make (Th : Theory_intf.S) = struct
if a.neg.is_true then ( if a.neg.is_true then (
(* conflict, even the last literal is false *) (* conflict, even the last literal is false *)
add_boolean_conflict st c add_boolean_conflict st c
) else ( ) else if b.neg.is_true && not a.is_true && not a.neg.is_true then (
if b.neg.is_true && not a.is_true && not a.neg.is_true then ( let lvl = List.fold_left (fun m a -> max m a.var.v_level) 0 atoms in
let lvl = List.fold_left (fun m a -> max m a.var.v_level) 0 atoms in cancel_until st (max lvl (base_level st));
cancel_until st (max lvl (base_level st)); enqueue_bool st a (Bcp c)
enqueue_bool st a (Bcp c)
)
) )
(* 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 *)
let add_clause ~permanent st (c:clause) : unit = and add_clause ~permanent st (c:clause) : unit =
if permanent then ( if permanent then (
redo_down_to_level_0 st (fun () -> add_clause_ st (Clause.copy c)) redo_down_to_level_0 st (fun () -> add_clause_ st (Clause.copy c))
) else ( ) else (
add_clause_ st c add_clause_ st c
) )
(* process a conflict:
- learn clause
- backtrack
- report unsat if conflict at level 0
*)
and add_boolean_conflict st (confl:clause): unit =
Log.debugf 3 (fun k -> k "(@[@{<Yellow>sat.boolean-conflict@}@ %a@])" Clause.debug confl);
st.next_decision <- None;
assert (decision_level st >= base_level st);
if decision_level st = base_level st ||
Array.for_all (fun a -> a.var.v_level <= base_level st) confl.atoms then (
(* Top-level conflict *)
report_unsat st confl;
);
let cr = analyze st confl in
cancel_until st (max cr.cr_backtrack_lvl (base_level st));
(* make some progress *)
var_decay_activity st;
clause_decay_activity st;
(* add the clause *)
add_clause ~permanent:true st confl
let[@inline] add_clause_user ~permanent st (c:clause): unit = let[@inline] add_clause_user ~permanent st (c:clause): unit =
Vec.push st.to_add (permanent,c) Vec.push st.to_add (permanent,c)
@ -1550,7 +1513,6 @@ module Make (Th : Theory_intf.S) = struct
) )
) )
in in
assert (base_level st > 0);
match st.unsat_conflict with match st.unsat_conflict with
| None -> | None ->
Log.debug 3 "(sat.adding_local_assumptions)"; Log.debug 3 "(sat.adding_local_assumptions)";

View file

@ -24,6 +24,7 @@ module Make (Th : Theory_intf.S) = struct
type premise = S.premise = type premise = S.premise =
| Hyp | Hyp
| Lemma of lemma | Lemma of lemma
| Simplified of clause
| History of clause list | History of clause list
type t = S.t type t = S.t
@ -62,10 +63,7 @@ module Make (Th : Theory_intf.S) = struct
| None -> assert false | None -> assert false
| Some c -> c | Some c -> c
in in
let get_proof () = let get_proof () = unsat_conflict () in
let c = unsat_conflict () in
S.Proof.prove_unsat c
in
Unsat_state { unsat_conflict; get_proof; } Unsat_state { unsat_conflict; get_proof; }
let theory = S.theory let theory = S.theory

View file

@ -68,6 +68,7 @@ module type S = sig
type premise = type premise =
| Hyp | Hyp
| Lemma of lemma | Lemma of lemma
| Simplified of clause
| History of clause list | History of clause list
type t type t
@ -189,10 +190,6 @@ module type S = sig
(** Given a clause, return a proof of that clause. (** Given a clause, return a proof of that clause.
@raise Insuficient_hyps if it does not succeed. *) @raise Insuficient_hyps if it does not succeed. *)
val prove_unsat : clause -> t
(** Given a conflict clause [c], returns a proof of the empty clause.
@raise Insuficient_hyps if it does not succeed. *)
val prove_atom : atom -> t option val prove_atom : atom -> t option
(** Given an atom [a], returns a proof of the clause [[a]] if [a] is true at level 0 *) (** Given an atom [a], returns a proof of the clause [[a]] if [a] is true at level 0 *)
@ -259,6 +256,7 @@ module type S = sig
val of_formulas : solver -> ?tag:int -> formula list -> t val of_formulas : solver -> ?tag:int -> formula list -> t
val premise : t -> premise val premise : t -> premise
val is_learnt : t -> bool
val name : t -> string val name : t -> string
val pp : t printer val pp : t printer