refactor(sat): wip: simplify SAT solver

This commit is contained in:
Simon Cruanes 2018-05-20 13:09:51 -05:00
parent 5860612cd9
commit f69d5cd9f1
6 changed files with 69 additions and 229 deletions

View file

@ -13,16 +13,8 @@ module type S = sig
val export :
st ->
Format.formatter ->
hyps:clause Vec.t ->
history:clause Vec.t ->
clauses:clause Vec.t ->
unit
val export_icnf :
Format.formatter ->
hyps:clause Vec.t ->
history:clause Vec.t ->
unit
end
module Make(St : Sidekick_sat.S) = struct
@ -35,21 +27,9 @@ module Make(St : Sidekick_sat.S) = struct
let export_assumption fmt vec =
Format.fprintf fmt "c Local assumptions@,a %a@," St.Clause.pp_dimacs vec
let export_icnf_aux r name map_filter fmt vec =
let aux fmt _ =
for i = !r to (Vec.size vec) - 1 do
let x = Vec.get vec i in
match map_filter x with
| None -> ()
| Some _ -> Format.fprintf fmt "%a@," St.Clause.pp_dimacs (Vec.get vec i)
done;
r := Vec.size vec
in
Format.fprintf fmt "c %s@,%a" name aux vec
let map_filter_learnt c =
match St.Clause.premise c with
| St.Hyp | St.Local -> assert false
| St.Hyp -> assert false
| St.Lemma _ -> Some c
| St.History l ->
begin match l with
@ -57,7 +37,7 @@ module Make(St : Sidekick_sat.S) = struct
| d :: _ ->
begin match St.Clause.premise d with
| St.Lemma _ -> Some d
| St.Hyp | St.Local | St.History _ -> None
| St.Hyp | St.History _ -> None
end
end
@ -70,33 +50,12 @@ module Make(St : Sidekick_sat.S) = struct
) learnt;
lemmas
let export st fmt ~hyps ~history : unit =
assert (Vec.for_all (fun c -> St.Clause.premise c = St.Hyp) hyps);
(* Learnt clauses, then filtered to only keep only
the theory lemmas; all other learnt clauses should be logical
consequences of the rest. *)
let lemmas = filter_vec history in
let export st fmt ~clauses : unit =
(* Number of atoms and clauses *)
let n = St.n_vars st in
let m = Vec.size hyps + Vec.size lemmas in
let m = Vec.size clauses in
Format.fprintf fmt
"@[<v>p cnf %d %d@,%a%a@]@." n m
(export_vec "Hypotheses") hyps
(export_vec "Lemmas") lemmas
(* Refs to remember what portion of a problem has been printed *)
let icnf_hyp = ref 0
let icnf_lemmas = ref 0
let export_icnf fmt ~hyps ~history =
assert (Vec.for_all (fun c -> St.Clause.premise c = St.Hyp) hyps);
let lemmas = history in
(* Number of atoms and clauses *)
Format.fprintf fmt
"@[<v>%s@,%a%a@]@."
(if !icnf_hyp = 0 && !icnf_lemmas = 0 then "p inccnf" else "")
(export_icnf_aux icnf_hyp "Hypotheses" (fun x -> Some x)) hyps
(export_icnf_aux icnf_lemmas "Lemmas" map_filter_learnt) lemmas
"@[<v>p cnf %d %d@,%a@]@." n m
(export_vec "Clauses") clauses
end

View file

@ -19,25 +19,12 @@ module type S = sig
val export :
st ->
Format.formatter ->
hyps:clause Vec.t ->
history:clause Vec.t ->
clauses:clause Vec.t ->
unit
(** Export the given clause vectors to the dimacs format.
The arguments should be transmitted directly from the corresponding
function of the {Internal} module. *)
val export_icnf :
Format.formatter ->
hyps:clause Vec.t ->
history:clause Vec.t ->
unit
(** Export the given clause vectors to the dimacs format.
The arguments should be transmitted directly from the corresponding
function of the {Internal} module.
This function may be called multiple times in order to add
new clauses (and new local hyps) to the problem.
*)
end
module Make(St: Sidekick_sat.S) : S with type clause := St.clause and type st = St.t

View file

@ -53,10 +53,10 @@ module Make (Th : Theory_intf.S) = struct
and reason =
| Decision
| Bcp of clause
| Local
and premise =
| Hyp
| Local
| Lemma of lemma
| History of clause list
@ -98,7 +98,6 @@ module Make (Th : Theory_intf.S) = struct
let name_of_clause c = match c.c_premise with
| Hyp -> "H" ^ string_of_int c.name
| Local -> "L" ^ string_of_int c.name
| Lemma _ -> "T" ^ string_of_int c.name
| History _ -> "C" ^ string_of_int c.name
@ -144,18 +143,7 @@ module Make (Th : Theory_intf.S) = struct
th: Th.t lazy_t;
(* Clauses are simplified for eficiency purposes. In the following
vectors, the comments actually refer to the original non-simplified
clause. *)
clauses_hyps : clause Vec.t;
(* clauses added by the user *)
clauses_learnt : clause Vec.t;
(* learnt clauses (tautologies true at any time, whatever the user level) *)
clauses_temp : clause Vec.t;
(* Temp clauses, corresponding to the local assumptions. This vec is used
only to have an efficient way to access the list of local assumptions. *)
(* TODO: remove. We only need clauses_hyps for that. *)
clauses: clause Vec.t;
mutable unsat_conflict : clause option;
(* conflict clause at [base_level], if any *)
@ -205,13 +193,8 @@ module Make (Th : Theory_intf.S) = struct
mutable clause_incr : float;
(* increment for clauses' activity *)
to_add : (bool * clause) CCVector.vector;
to_add : (bool * clause) Vec.t;
(* clauses to add, from the user *)
(* TODO: remove *)
mutable dirty: bool;
(* is there a [pop()] on top of the stack for examining
current model/proof? *)
}
(* Starting environment. *)
@ -224,9 +207,7 @@ module Make (Th : Theory_intf.S) = struct
unsat_conflict = None;
next_decision = None;
clauses_hyps = Vec.make 0 dummy_clause;
clauses_learnt = Vec.make 0 dummy_clause;
clauses_temp = Vec.make 0 dummy_clause;
clauses = Vec.make 0 dummy_clause;
th_head = 0;
elt_head = 0;
@ -239,11 +220,10 @@ module Make (Th : Theory_intf.S) = struct
user_levels = Vec.make 0 (-1);
order = H.create();
to_add = CCVector.create();
to_add = Vec.make_empty (true, dummy_clause);
var_incr = 1.;
clause_incr = 1.;
dirty=false;
}
type state = t
@ -370,6 +350,8 @@ module Make (Th : Theory_intf.S) = struct
Format.fprintf fmt "%%"
| n, None ->
Format.fprintf fmt "%d" n
| n, Some Local ->
Format.fprintf fmt "L%d" n
| n, Some Decision ->
Format.fprintf fmt "@@%d" n
| n, Some Bcp c ->
@ -450,7 +432,6 @@ module Make (Th : Theory_intf.S) = struct
let debug_premise out = function
| Hyp -> Format.fprintf out "hyp"
| Local -> Format.fprintf out "local"
| Lemma _ -> Format.fprintf out "th_lemma"
| History v ->
Format.fprintf out "[@[%a@]]"
@ -640,8 +621,6 @@ module Make (Th : Theory_intf.S) = struct
{conclusion; step = Lemma l; }
| Hyp ->
{ conclusion; step = Hypothesis; }
| Local ->
{ conclusion; step = Assumption; }
| History [] ->
Log.debugf 1 (fun k -> k "Empty history for clause: %a" Clause.debug conclusion);
raise (Resolution_error "Empty history")
@ -693,7 +672,7 @@ module Make (Th : Theory_intf.S) = struct
if not (Clause.visited c) then (
Clause.set_visited c true;
match c.c_premise with
| Hyp | Local | Lemma _ -> aux (c :: res) acc r
| Hyp | Lemma _ -> aux (c :: res) acc r
| History h ->
let l = List.fold_left (fun acc c ->
if not (Clause.visited c) then c :: acc else acc) r h in
@ -752,7 +731,7 @@ module Make (Th : Theory_intf.S) = struct
Vec.push st.backtrack f
)
let[@inline] nb_clauses st = Vec.size st.clauses_hyps
let[@inline] nb_clauses st = Vec.size st.clauses
let[@inline] decision_level st = Vec.size st.elt_levels
let[@inline] base_level st = Vec.size st.user_levels
@ -820,10 +799,9 @@ module Make (Th : Theory_intf.S) = struct
let clause_bump_activity st (c:clause) : unit =
c.activity <- c.activity +. st.clause_incr;
if c.activity > 1e20 then (
for i = 0 to Vec.size st.clauses_learnt - 1 do
(Vec.get st.clauses_learnt i).activity <-
(Vec.get st.clauses_learnt i).activity *. 1e-20;
done;
Vec.iter
(fun c -> c.activity <- c.activity *. 1e-20)
st.clauses;
st.clause_incr <- st.clause_incr *. 1e-20
)
@ -1108,7 +1086,7 @@ module Make (Th : Theory_intf.S) = struct
Log.debugf 5 (fun k->k"(@[sat.resolving-clause@ %a@])" Clause.debug clause);
begin match clause.c_premise with
| History _ -> clause_bump_activity st clause
| Hyp | Local | Lemma _ -> ()
| Hyp | Lemma _ -> ()
end;
history := clause :: !history;
(* visit the current predecessors *)
@ -1182,13 +1160,13 @@ module Make (Th : Theory_intf.S) = struct
report_unsat st confl
) else (
let uclause = Clause.make_l cr.cr_learnt (History cr.cr_history) in
Vec.push st.clauses_learnt uclause;
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_learnt lclause;
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;
@ -1219,14 +1197,6 @@ module Make (Th : Theory_intf.S) = struct
cancel_until st (max cr.cr_backtrack_lvl (base_level st));
record_learnt_clause st confl cr
(* Get the correct vector to insert a clause in. *)
let rec clause_vector st c =
match c.c_premise with
| Hyp -> st.clauses_hyps
| Local -> st.clauses_temp
| History [c'] -> clause_vector st c' (* simplified version of [d] *)
| Lemma _ | History _ -> st.clauses_learnt
(* Add clause, accounting for backtracking semantics.
- always add literals to queue if not decided yet
- if clause is already true, probably just do nothing
@ -1238,7 +1208,6 @@ module Make (Th : Theory_intf.S) = struct
Log.debugf 5
(fun k -> k "(@[@{<Yellow>sat.add_clause@}@ %a@])" Clause.debug c);
assert (not @@ Clause.dead c);
let vec_for_clause = clause_vector st c in
match eliminate_duplicates c with
| exception Trivial ->
Log.debugf 3 (fun k->k "(@[sat.add_clause.ignore-trivial@ %a@])" Clause.debug c)
@ -1270,8 +1239,8 @@ module Make (Th : Theory_intf.S) = struct
enqueue_bool st a (Bcp c)
)
| _::_::_ ->
on_backtrack st (fun () -> Vec.pop vec_for_clause);
Vec.push vec_for_clause c;
on_backtrack st (fun () -> Vec.pop st.clauses);
Vec.push st.clauses c;
(* Atoms need to be sorted in decreasing order of decision level,
or we might watch the wrong literals. *)
put_high_level_atoms_first c.atoms;
@ -1299,7 +1268,7 @@ module Make (Th : Theory_intf.S) = struct
)
let[@inline] add_clause_user ~permanent st (c:clause): unit =
CCVector.push st.to_add (permanent,c)
Vec.push st.to_add (permanent,c)
type watch_res =
| Watch_kept
@ -1538,7 +1507,7 @@ module Make (Th : Theory_intf.S) = struct
(* if decision_level() = 0 then simplify (); *)
if n_of_learnts >= 0 &&
Vec.size st.clauses_learnt - Vec.size st.trail >= n_of_learnts
Vec.size st.clauses - Vec.size st.trail >= n_of_learnts
then reduce_db();
pick_branch_lit st
@ -1564,9 +1533,35 @@ module Make (Th : Theory_intf.S) = struct
(fun k -> k "(@[<v>sat.current_trail:@ %a@])"
(Vec.print ~sep:"" Atom.debug) st.trail)
(* Add local hyps to the current decision level *)
let local (st:t) (assumptions:formula list) : unit =
let add_lit lit : unit =
let a = Atom.make st lit in
Log.debugf 3 (fun k-> k "(@[sat.local_assumption@ %a@])" Atom.debug a);
assert (decision_level st = base_level st);
if not a.is_true then (
if a.neg.is_true then (
(* conflict between assumptions: UNSAT *)
let c = Clause.make [|a|] Hyp in
report_unsat st c;
) else (
(* make a decision, propagate *)
enqueue_bool st a Local;
)
)
in
assert (base_level st > 0);
match st.unsat_conflict with
| None ->
Log.debug 3 "(sat.adding_local_assumptions)";
cancel_until st (base_level st);
List.iter add_lit assumptions
| Some _ ->
Log.debug 2 "(sat.local_assumptions.1: already unsat)"
(* fixpoint of propagation and decisions until a model is found, or a
conflict is reached *)
let solve (st:t) : unit =
let solve ?(assumptions=[]) (st:t) : unit =
Log.debug 5 "(sat.solve)";
if is_unsat st then raise Unsat;
let n_of_conflicts = ref (float_of_int restart_first) in
@ -1574,7 +1569,7 @@ module Make (Th : Theory_intf.S) = struct
(* add clauses that are waiting in [to_add] *)
let rec add_clauses () =
match
CCVector.filter' (fun (permanent,c) -> add_clause ~permanent st c; false) st.to_add
Vec.filter_in_place (fun (permanent,c) -> add_clause ~permanent st c; false) st.to_add
with
| () -> call_solve()
| exception Conflict c ->
@ -1610,7 +1605,9 @@ module Make (Th : Theory_intf.S) = struct
add_clause ~permanent:false st c
end
in
try add_clauses()
try
local st assumptions;
add_clauses()
with Sat -> ()
let assume ~permanent st ?tag cnf =
@ -1621,70 +1618,6 @@ module Make (Th : Theory_intf.S) = struct
add_clause_user st ~permanent c)
cnf
(* TODO: remove push/pop *)
(* create a factice decision level for local assumptions *)
let push st : unit =
Log.debug 5 "(sat.push-new-user-level)";
cancel_until st (base_level st);
Log.debugf 5
(fun k -> k "(@[<v>sat.status@ :trail %d - %d@ %a@])"
st.elt_head st.th_head (Vec.print ~sep:"" Atom.debug) st.trail);
begin match propagate st with
| Some confl ->
report_unsat st confl
| None ->
pp_trail st;
Log.debug 3 "(sat.create-new-user-level)";
new_decision_level st;
Vec.push st.user_levels (Vec.size st.clauses_temp);
assert (decision_level st = base_level st)
end
(* pop the last factice decision level *)
let pop st : unit =
if base_level st = 0 then (
Log.debug 2 "(sat.1: cannot pop (already at level 0))"
) else (
Log.debug 3 "(sat.pop-user-level)";
assert (base_level st > 0);
st.unsat_conflict <- None;
let n = Vec.last st.user_levels in
Vec.pop st.user_levels; (* before the [cancel_until]! *)
(* remove from env.clauses_temp the now invalid caluses. *)
Vec.shrink st.clauses_temp n;
assert (Vec.for_all (fun c -> Array.length c.atoms = 1) st.clauses_temp);
assert (Vec.for_all (fun c -> c.atoms.(0).var.v_level <= base_level st) st.clauses_temp);
cancel_until st (base_level st)
)
(* Add local hyps to the current decision level *)
let local (st:t) (assumptions:formula list) : unit =
let add_lit lit : unit =
let a = Atom.make st lit in
Log.debugf 3 (fun k-> k "(@[sat.local_assumption@ %a@])" Atom.debug a);
assert (decision_level st = base_level st);
if not a.is_true then (
let c = Clause.make [|a|] Local in
Log.debugf 5 (fun k -> k "(@[sat.add_temp_clause@ %a@])" Clause.debug c);
Vec.push st.clauses_temp c;
if a.neg.is_true then (
(* conflict between assumptions: UNSAT *)
report_unsat st c;
) else (
(* make a decision, propagate *)
enqueue_bool st a (Bcp c);
)
)
in
assert (base_level st > 0);
match st.unsat_conflict with
| None ->
Log.debug 3 "(sat.adding_local_assumptions)";
cancel_until st (base_level st);
List.iter add_lit assumptions
| Some _ ->
Log.debug 2 "(sat.local_assumptions.1: already unsat)"
(* Check satisfiability *)
let check_clause c =
let tmp = Array.map (fun a ->
@ -1709,21 +1642,11 @@ module Make (Th : Theory_intf.S) = struct
with Exit ->
false
let check st : bool =
check_vec st.clauses_hyps &&
check_vec st.clauses_learnt &&
check_vec st.clauses_temp
let check st : bool = check_vec st.clauses
(* Unsafe access to internal data *)
let hyps env = env.clauses_hyps
let history env = env.clauses_learnt
let temp env = env.clauses_temp
let trail env = env.trail
let trail st = st.trail
end
[@@inline]

View file

@ -31,9 +31,7 @@ type ('clause, 'proof) unsat_state = ('clause, 'proof) Solver_intf.unsat_state =
}
type 'clause export = 'clause Solver_intf.export = {
hyps : 'clause Vec.t;
history : 'clause Vec.t;
local : 'clause Vec.t;
clauses : 'clause Vec.t;
}
type ('form, 'proof) actions = ('form,'proof) Theory_intf.actions = Actions of {

View file

@ -23,7 +23,6 @@ module Make (Th : Theory_intf.S) = struct
type lemma = S.lemma
type premise = S.premise =
| Hyp
| Local
| Lemma of lemma
| History of clause list
@ -41,12 +40,10 @@ module Make (Th : Theory_intf.S) = struct
Log.debugf lvl
(fun k -> k
"@[<v>%s - Full resume:@,@[<hov 2>Trail:@\n%a@]@,\
@[<hov 2>Temp:@\n%a@]@,@[<hov 2>Hyps:@\n%a@]@,@[<hov 2>Lemmas:@\n%a@]@,@]@."
@[<hov 2>Clauses:@\n%a@]@]@."
status
(Vec.print ~sep:"" S.Atom.debug) (S.trail st)
(Vec.print ~sep:"" S.Clause.debug) (S.temp st)
(Vec.print ~sep:"" S.Clause.debug) (S.hyps st)
(Vec.print ~sep:"" S.Clause.debug) (S.history st)
(Vec.print ~sep:"" S.Clause.debug) st.S.clauses
)
let mk_sat (st:S.t) : _ sat_state =
@ -71,43 +68,22 @@ module Make (Th : Theory_intf.S) = struct
in
Unsat_state { unsat_conflict; get_proof; }
(* clean local state *)
let[@inline] cleanup_ (st:t) : unit =
if st.S.dirty then (
S.pop st; (* reset *)
st.S.dirty <- false;
)
let theory = S.theory
(* Wrappers around internal functions*)
let[@inline] assume ?(permanent=true) st ?tag cls : unit =
cleanup_ st;
S.assume ~permanent st ?tag cls
let[@inline] add_clause ~permanent st c : unit =
cleanup_ st;
S.add_clause_user ~permanent st c
let solve (st:t) ?(assumptions=[]) () =
cleanup_ st;
try
S.push st;
st.S.dirty <- true; (* to call [pop] before any other action *)
S.local st assumptions;
S.solve st;
S.solve ~assumptions st;
Sat (mk_sat st)
with S.Unsat ->
Unsat (mk_unsat st)
let[@inline] push st =
cleanup_ st;
S.push st
let[@inline] pop st =
cleanup_ st;
S.pop st
let n_vars = S.n_vars
let unsat_core = S.Proof.unsat_core
@ -122,10 +98,8 @@ module Make (Th : Theory_intf.S) = struct
let actions = S.actions
let export (st:t) : S.clause export =
let hyps = S.hyps st in
let history = S.history st in
let local = S.temp st in
{hyps; history; local}
let clauses = st.S.clauses in
{clauses}
module Atom = S.Atom

View file

@ -36,9 +36,7 @@ type ('clause, 'proof) unsat_state = Unsat_state of {
(** The type of values returned when the solver reaches an UNSAT state. *)
type 'clause export = {
hyps: 'clause Vec.t;
history: 'clause Vec.t;
local: 'clause Vec.t;
clauses: 'clause Vec.t;
}
(** Export internal state *)
@ -69,7 +67,6 @@ module type S = sig
type premise =
| Hyp
| Local
| Lemma of lemma
| History of clause list
@ -124,6 +121,7 @@ module type S = sig
val get_tag : clause -> int option
(** Recover tag from a clause, if any *)
(* FIXME
val push : t -> unit
(** Push a new save point. Clauses added after this call to [push] will
be added as normal, but the corresponding call to [pop] will
@ -132,6 +130,7 @@ module type S = sig
val pop : t -> unit
(** Return to last save point, discarding clauses added since last
call to [push] *)
*)
val actions : t -> (formula,lemma) Theory_intf.actions
(** Obtain actions *)