sidekick/solver/internal.ml
Guillaume Bury 9dadb67fc9 [bugfix] Sort false atoms by levels in new clauses
When a new clauses is added and it is a conflict (i.e all atoms
are false), one must take care of which literals to watch.
In order to work, the watched literals must be those with the higher
decision levels, or else the watched literals might not react when
needed. This is fixed by sorting the literals in decreasing order of
decision level when adding a new clause which happens to be false in the
current trail.
2016-07-16 15:55:26 +02:00

1140 lines
36 KiB
OCaml

(*
MSAT is free software, using the Apache license, see file LICENSE
Copyright 2014 Guillaume Bury
Copyright 2014 Simon Cruanes
*)
module Make
(St : Solver_types.S)
(Th : Plugin_intf.S with type term = St.term and type formula = St.formula and type proof = St.proof)
(Dummy: sig end)
= struct
module Proof = Res.Make(St)
open St
exception Sat
exception Unsat
exception UndecidedLit
exception Restart
exception Conflict of clause
(* a push/pop state *)
type user_level = {
(* User levels always refer to decision_level 0 *)
ul_elt_lvl : int; (* Number of atoms in trail at decision level 0 *)
ul_th_lvl : int; (* Number of atoms known by the theory at decicion level 0 *)
ul_th_env : Th.level; (* Theory state at level 0 *)
ul_clauses : int; (* number of clauses *)
ul_learnt : int; (* number of learnt clauses *)
}
(* Singleton type containing the current state *)
type env = {
clauses_hyps : clause Vec.t;
(* all currently active clauses *)
clauses_learnt : clause Vec.t;
(* learnt clauses *)
mutable unsat_conflict : clause option;
(* conflict clause at decision level 0, if any *)
mutable next_decision : atom option;
(* When the last conflict was a semantic one, this stores the next decision to make *)
elt_queue : t Vec.t;
(* decision stack + propagated elements (atoms or assignments) *)
elt_levels : int Vec.t;
(* decision levels in [trail] *)
th_levels : Th.level Vec.t;
(* theory states corresponding to elt_levels *)
user_levels : user_level Vec.t;
(* user-defined levels, for {!push} and {!pop} *)
mutable th_head : int;
(* Start offset in the queue of unit fact not yet seen by the theory *)
mutable elt_head : int;
(* Start offset in the queue of unit facts to propagate, within the trail *)
mutable simpDB_props : int;
(* remaining number of propagations before the next call to [simplify ()] *)
mutable simpDB_assigns : int;
(* number of toplevel assignments since last call to [simplify ()] *)
order : Iheap.t;
(* Heap ordered by variable activity *)
var_decay : float;
(* inverse of the activity factor for variables. Default 1/0.999 *)
clause_decay : float;
(* inverse of the activity factor for clauses. Default 1/0.95 *)
mutable var_incr : float;
(* increment for variables' activity *)
mutable clause_incr : float;
(* increment for clauses' activity *)
mutable progress_estimate : float;
(* progression estimate, updated by [search ()] *)
remove_satisfied : bool;
(* Wether to remove satisfied learnt clauses when simplifying *)
restart_inc : float;
(* multiplicative factor for restart limit, default 1.5 *)
mutable restart_first : int;
(* intial restart limit, default 100 *)
learntsize_inc : float;
(* multiplicative factor for [learntsize_factor] at each restart, default 1.1 *)
mutable learntsize_factor : float;
(* initial limit for the number of learnt clauses, 1/3 of initial
number of clauses by default *)
mutable starts : int;
mutable decisions : int;
mutable propagations : int;
mutable conflicts : int;
mutable clauses_literals : int;
mutable learnts_literals : int;
mutable nb_init_clauses : int;
}
let env = {
unsat_conflict = None;
next_decision = None;
clauses_hyps = Vec.make 0 dummy_clause;
clauses_learnt = Vec.make 0 dummy_clause;
th_head = 0;
elt_head = 0;
elt_queue = Vec.make 601 (of_atom dummy_atom);
elt_levels = Vec.make 601 (-1);
th_levels = Vec.make 100 Th.dummy;
user_levels = Vec.make 20 {
ul_elt_lvl = 0;
ul_th_lvl = 0;
ul_learnt = 0;
ul_clauses = 0;
ul_th_env = Th.dummy;
};
order = Iheap.init 0;
var_incr = 1.;
clause_incr = 1.;
var_decay = 1. /. 0.95;
clause_decay = 1. /. 0.999;
simpDB_assigns = -1;
simpDB_props = 0;
progress_estimate = 0.;
remove_satisfied = false;
restart_inc = 1.5;
restart_first = 100;
learntsize_factor = 1. /. 3. ;
learntsize_inc = 1.1;
starts = 0;
decisions = 0;
propagations = 0;
conflicts = 0;
clauses_literals = 0;
learnts_literals = 0;
nb_init_clauses = 0;
}
let is_unsat () =
match env.unsat_conflict with
| Some _ -> true
| None -> false
(* Push/Pop *)
let current_level () = Vec.size env.user_levels
let push () =
if is_unsat () then current_level ()
else begin
let res = current_level () in
let ul_elt_lvl, ul_th_lvl =
if Vec.is_empty env.elt_levels then
env.elt_head, env.th_head
else
let l = Vec.get env.elt_levels 0 in
l, l
and ul_th_env =
if Vec.is_empty env.th_levels then Th.current_level ()
else Vec.get env.th_levels 0
in
let ul_clauses = Vec.size env.clauses_hyps in
let ul_learnt = Vec.size env.clauses_learnt in
Vec.push env.user_levels {ul_elt_lvl; ul_th_lvl; ul_th_env; ul_clauses; ul_learnt;};
res
end
(* Level for push/pop operations *)
type level = int
let base_level =
let l = push () in
assert (l = 0);
l
(* Iteration over subterms *)
module Mi = Map.Make(struct type t = int let compare = Pervasives.compare end)
let iter_map = ref Mi.empty
let iter_sub f v =
try
List.iter f (Mi.find v.vid !iter_map)
with Not_found ->
let l = ref [] in
Th.iter_assignable (fun t -> l := add_term t :: !l) v.pa.lit;
iter_map := Mi.add v.vid !l !iter_map;
List.iter f !l
let atom lit =
let res = add_atom lit in
iter_sub ignore res.var;
res
(* Misc functions *)
let to_float i = float_of_int i
let to_int f = int_of_float f
(* Accessors for litterals *)
let f_weight i j =
get_elt_weight (St.get_elt j) < get_elt_weight (St.get_elt i)
let f_filter i =
get_elt_level (St.get_elt i) < 0
(* Var/clause activity *)
let insert_var_order = function
| Either.Left l -> Iheap.insert f_weight env.order l.lid
| Either.Right v ->
Iheap.insert f_weight env.order v.vid;
iter_sub (fun t -> Iheap.insert f_weight env.order t.lid) v
let var_decay_activity () =
env.var_incr <- env.var_incr *. env.var_decay
let clause_decay_activity () =
env.clause_incr <- env.clause_incr *. env.clause_decay
let var_bump_activity_aux v =
v.v_weight <- v.v_weight +. env.var_incr;
if v.v_weight > 1e100 then begin
for i = 0 to (St.nb_elt ()) - 1 do
set_elt_weight (St.get_elt i) ((get_elt_weight (St.get_elt i)) *. 1e-100)
done;
env.var_incr <- env.var_incr *. 1e-100;
end;
if Iheap.in_heap env.order v.vid then
Iheap.decrease f_weight env.order v.vid
let lit_bump_activity_aux l =
l.l_weight <- l.l_weight +. env.var_incr;
if l.l_weight > 1e100 then begin
for i = 0 to (St.nb_elt ()) - 1 do
set_elt_weight (St.get_elt i) ((get_elt_weight (St.get_elt i)) *. 1e-100)
done;
env.var_incr <- env.var_incr *. 1e-100;
end;
if Iheap.in_heap env.order l.lid then
Iheap.decrease f_weight env.order l.lid
let var_bump_activity v =
var_bump_activity_aux v;
iter_sub lit_bump_activity_aux v
let clause_bump_activity c =
c.activity <- c.activity +. env.clause_incr;
if c.activity > 1e20 then begin
for i = 0 to (Vec.size env.clauses_learnt) - 1 do
(Vec.get env.clauses_learnt i).activity <-
(Vec.get env.clauses_learnt i).activity *. 1e-20;
done;
env.clause_incr <- env.clause_incr *. 1e-20
end
(* Convenient access *)
let decision_level () = Vec.size env.elt_levels
let nb_clauses () = Vec.size env.clauses_hyps
let nb_learnts () = Vec.size env.clauses_learnt
let nb_vars () = St.nb_elt ()
(* Simplify clauses *)
exception Trivial
let simplify_zero atoms =
(* Eliminates dead litterals from clauses when at decision level 0 *)
assert (decision_level () = 0);
let aux (atoms, history) a =
if a.is_true then raise Trivial;
if a.neg.is_true then begin
match a.var.reason with
| None | Some Decision -> assert false
| Some (Bcp cl) -> atoms, cl :: history
| Some (Semantic 0) -> atoms, history
| Some (Semantic _) ->
Log.debugf 0 "Unexpected semantic propagation at level 0: %a"
(fun k->k St.pp_atom a);
assert false
end else
a::atoms, history
in
let atoms, init = List.fold_left aux ([], []) atoms in
List.fast_sort (fun a b -> a.var.vid - b.var.vid) atoms, init
let partition atoms =
(* Parittion litterals for new clauses *)
let rec partition_aux trues unassigned falses history = function
| [] ->
trues @ unassigned @ falses, history
| a :: r ->
if a.is_true then
if a.var.v_level = 0 then raise Trivial
else (a::trues) @ unassigned @ falses @ r, history
else if a.neg.is_true then
if a.var.v_level = 0 then begin
match a.var.reason with
| Some (Bcp cl) ->
partition_aux trues unassigned falses (cl :: history) r
| Some (Semantic 0) ->
partition_aux trues unassigned falses history r
| _ -> assert false
end else
partition_aux trues unassigned (a::falses) history r
else
partition_aux trues (a::unassigned) falses history r
in
if decision_level () = 0 then
simplify_zero atoms
else
partition_aux [] [] [] [] atoms
(* Compute a progess estimate *)
let progress_estimate () =
let prg = ref 0. in
let nbv = to_float (nb_vars()) in
let lvl = decision_level () in
let _F = 1. /. nbv in
for i = 0 to lvl do
let _beg = if i = 0 then 0 else Vec.get env.elt_levels (i-1) in
let _end = if i=lvl then Vec.size env.elt_queue else Vec.get env.elt_levels i in
prg := !prg +. _F**(to_float i) *. (to_float (_end - _beg))
done;
!prg /. nbv
(* Manipulating decision levels *)
let new_decision_level() =
assert (env.th_head = Vec.size env.elt_queue);
assert (env.elt_head = Vec.size env.elt_queue);
Vec.push env.elt_levels (Vec.size env.elt_queue);
Vec.push env.th_levels (Th.current_level ()); (* save the current tenv *)
()
(* Adding/removing clauses *)
let attach_clause c =
if not c.attached then begin
Log.debugf 60 "Attaching %a" (fun k -> k St.pp_clause c);
c.attached <- true;
Vec.push (Vec.get c.atoms 0).neg.watched c;
Vec.push (Vec.get c.atoms 1).neg.watched c;
end
let detach_clause c =
if c.attached then begin
c.attached <- false;
Log.debugf 10 "Removing clause @[%a@]" (fun k->k St.pp_clause c);
Vec.remove (Vec.get c.atoms 0).neg.watched c;
Vec.remove (Vec.get c.atoms 1).neg.watched c;
end
let remove_clause c = detach_clause c
let satisfied c =
Vec.exists (fun atom -> atom.is_true) c.atoms
(* cancel down to [lvl] excluded *)
let cancel_until lvl =
if decision_level () > lvl then begin
Log.debugf 5 "Backtracking to lvl %d" (fun k -> k lvl);
env.elt_head <- Vec.get env.elt_levels lvl;
env.th_head <- env.elt_head;
for c = env.elt_head to Vec.size env.elt_queue - 1 do
match (Vec.get env.elt_queue c) with
| Either.Left l ->
l.assigned <- None;
l.l_level <- -1;
insert_var_order (elt_of_lit l)
| Either.Right a ->
if a.var.v_level <= lvl then begin
Vec.set env.elt_queue env.elt_head (of_atom a);
env.elt_head <- env.elt_head + 1
end else begin
a.is_true <- false;
a.neg.is_true <- false;
a.var.v_level <- -1;
a.var.reason <- None;
insert_var_order (elt_of_var a.var)
end
done;
Th.backtrack (Vec.get env.th_levels lvl); (* recover the right tenv *)
Vec.shrink env.elt_queue ((Vec.size env.elt_queue) - env.elt_head);
Vec.shrink env.elt_levels ((Vec.size env.elt_levels) - lvl);
Vec.shrink env.th_levels ((Vec.size env.th_levels) - lvl);
end;
assert (Vec.size env.elt_levels = Vec.size env.th_levels);
()
let report_unsat ({atoms=atoms} as confl) =
Log.debugf 5 "@[Unsat conflict: %a@]" (fun k -> k St.pp_clause confl);
env.unsat_conflict <- Some confl;
raise Unsat
let simpl_reason = function
| (Bcp cl) as r ->
Log.debugf 90 "Simplifying reason: %a" (fun k -> k St.pp_clause cl);
let atoms = Vec.to_list cl.atoms in
let l, history = partition atoms in
begin match l with
| [ a ] ->
if history = [] then r
else
Bcp (make_clause (fresh_tname ()) l 1 (History (cl :: history)))
| _ -> assert false
end
| r -> r
let enqueue_bool a lvl reason =
if a.neg.is_true then begin
Log.debugf 0 "Trying to enqueue a false litteral: %a" (fun k->k St.pp_atom a);
assert false
end;
if not a.is_true then begin
assert (a.var.v_level < 0 && a.var.reason = None && lvl >= 0);
let reason =
if lvl > 0 then reason
else simpl_reason reason
in
a.is_true <- true;
a.var.v_level <- lvl;
a.var.reason <- Some reason;
Vec.push env.elt_queue (of_atom a);
Log.debugf 20 "Enqueue (%d): %a"
(fun k->k (Vec.size env.elt_queue) pp_atom a)
end
let enqueue_assign l value lvl =
l.assigned <- Some value;
l.l_level <- lvl;
Vec.push env.elt_queue (of_lit l);
()
let th_eval a =
if a.is_true || a.neg.is_true then None
else match Th.eval a.lit with
| Th.Unknown -> None
| Th.Valued (b, lvl) ->
let atom = if b then a else a.neg in
enqueue_bool atom lvl (Semantic lvl);
Some b
(* conflict analysis *)
let max_lvl_atoms l =
List.fold_left (fun (max_lvl, acc) a ->
if a.var.v_level = max_lvl then (max_lvl, a :: acc)
else if a.var.v_level > max_lvl then (a.var.v_level, [a])
else (max_lvl, acc)) (0, []) l
let backtrack_lvl is_uip = function
| [] -> 0
| a :: r when not is_uip -> max (a.var.v_level - 1) 0
| a :: [] -> 0
| a :: b :: r ->
assert(a.var.v_level <> b.var.v_level);
b.var.v_level
let analyze_mcsat c_clause =
let tr_ind = ref (Vec.size env.elt_queue) in
let is_uip = ref false in
let c = ref (Proof.to_list c_clause) in
let history = ref [c_clause] in
clause_bump_activity c_clause;
let is_semantic a = match a.var.reason with
| Some Semantic _ -> true
| _ -> false
in
try while true do
let lvl, atoms = max_lvl_atoms !c in
if lvl = 0 then raise Exit;
match atoms with
| [] | _ :: [] ->
is_uip := true;
raise Exit
| _ when List.for_all is_semantic atoms ->
raise Exit
| _ ->
decr tr_ind;
Log.debugf 20 "Looking at trail element %d" (fun k->k !tr_ind);
match Vec.get env.elt_queue !tr_ind with
| Either.Left _ -> ()
| Either.Right a ->
begin match a.var.reason with
| Some (Bcp d) ->
let tmp, res = Proof.resolve (Proof.merge !c (Proof.to_list d)) in
begin match tmp with
| [] -> ()
| [b] when b == a.var.pa ->
clause_bump_activity d;
var_bump_activity a.var;
history := d :: !history;
c := res
| _ -> assert false
end
| None | Some Decision | Some Semantic _ -> ()
end
done; assert false
with Exit ->
let learnt = List.fast_sort (
fun a b -> Pervasives.compare b.var.v_level a.var.v_level) !c in
let blevel = backtrack_lvl !is_uip learnt in
blevel, learnt, List.rev !history, !is_uip
let get_atom i =
match Vec.get env.elt_queue i with
| Either.Left _ -> assert false | Either.Right x -> x
let analyze_sat c_clause =
let pathC = ref 0 in
let learnt = ref [] in
let cond = ref true in
let blevel = ref 0 in
let seen = ref [] in
let c = ref c_clause in
let tr_ind = ref (Vec.size env.elt_queue - 1) in
let size = ref 1 in
let history = ref [] in
assert (decision_level () > 0);
while !cond do
begin match !c.cpremise with
| History _ -> clause_bump_activity !c
| Hyp _ | Lemma _ -> ()
end;
history := !c :: !history;
(* visit the current predecessors *)
for j = 0 to Vec.size !c.atoms - 1 do
let q = Vec.get !c.atoms j in
assert (q.is_true || q.neg.is_true && q.var.v_level >= 0); (* Pas sur *)
if q.var.v_level = 0 then begin
assert (q.neg.is_true);
match q.var.reason with
| Some Bcp cl -> history := cl :: !history
| _ -> assert false
end;
if not q.var.seen then begin
q.var.seen <- true;
seen := q :: !seen;
if q.var.v_level > 0 then begin
var_bump_activity q.var;
if q.var.v_level >= decision_level () then begin
incr pathC
end else begin
learnt := q :: !learnt;
incr size;
blevel := max !blevel q.var.v_level
end
end
end
done;
(* look for the next node to expand *)
while not (get_atom !tr_ind).var.seen do decr tr_ind done;
decr pathC;
let p = get_atom !tr_ind in
decr tr_ind;
match !pathC, p.var.reason with
| 0, _ ->
cond := false;
learnt := p.neg :: (List.rev !learnt)
| n, Some Bcp cl ->
c := cl
| n, _ -> assert false
done;
List.iter (fun q -> q.var.seen <- false) !seen;
!blevel, !learnt, List.rev !history, true
let analyze c_clause =
if St.mcsat then
analyze_mcsat c_clause
else
analyze_sat c_clause
let record_learnt_clause confl blevel learnt history is_uip =
begin match learnt with
| [] -> assert false
| [fuip] ->
assert (blevel = 0);
if fuip.neg.is_true then
report_unsat confl
else begin
let name = fresh_lname () in
let uclause = make_clause name learnt (List.length learnt) history in
Vec.push env.clauses_learnt uclause;
enqueue_bool fuip 0 (Bcp uclause)
end
| fuip :: _ ->
let name = fresh_lname () in
let lclause = make_clause name learnt (List.length learnt) history in
Vec.push env.clauses_learnt lclause;
attach_clause lclause;
clause_bump_activity lclause;
if is_uip then
enqueue_bool fuip blevel (Bcp lclause)
else begin
env.next_decision <- Some fuip.neg
end
end;
var_decay_activity ();
clause_decay_activity ()
let add_boolean_conflict confl =
env.next_decision <- None;
env.conflicts <- env.conflicts + 1;
if decision_level() = 0 || Vec.for_all (fun a -> a.var.v_level = 0) confl.atoms then
report_unsat confl; (* Top-level conflict *)
let blevel, learnt, history, is_uip = analyze confl in
cancel_until blevel;
record_learnt_clause confl blevel learnt (History history) is_uip
(* Add a new clause *)
let add_clause ?(force=false) init =
Log.debugf 90 "Adding clause:@[<hov>%a@]" (fun k -> k St.pp_clause init);
let vec = match init.cpremise with
| Hyp _ -> env.clauses_hyps
| Lemma _ -> env.clauses_learnt
| History _ -> assert false
in
try
let atoms, history = partition (Vec.to_list init.atoms) in
let size = List.length atoms in
let clause =
if history = [] then init
else make_clause ?tag:init.tag (fresh_name ())
atoms size (History (init :: history))
in
Log.debugf 4 "New clause:@ @[%a@]" (fun k->k St.pp_clause clause);
Vec.push vec clause;
match atoms with
| [] ->
report_unsat clause
| a::b::_ ->
if a.neg.is_true then begin
Vec.sort clause.atoms (fun a b ->
compare b.var.v_level a.var.v_level);
attach_clause clause;
add_boolean_conflict init
end else begin
attach_clause clause;
if b.neg.is_true && not a.is_true && not a.neg.is_true then begin
let lvl = List.fold_left (fun m a -> max m a.var.v_level) 0 atoms in
cancel_until lvl;
enqueue_bool a lvl (Bcp clause)
end
end
| [a] ->
Log.debugf 5 "New unit clause, propagating : %a" (fun k->k St.pp_atom a);
cancel_until 0;
enqueue_bool a 0 (Bcp clause)
with Trivial ->
Vec.push vec init;
Log.debugf 5 "Trivial clause ignored : @[%a@]" (fun k->k St.pp_clause init)
let propagate_in_clause a c i watched new_sz =
let atoms = c.atoms in
let first = Vec.get atoms 0 in
if first == a.neg then begin (* false lit must be at index 1 *)
Vec.set atoms 0 (Vec.get atoms 1);
Vec.set atoms 1 first
end;
let first = Vec.get atoms 0 in
if first.is_true then begin
(* true clause, keep it in watched *)
Vec.set watched !new_sz c;
incr new_sz;
end else
try (* look for another watch lit *)
for k = 2 to Vec.size atoms - 1 do
let ak = Vec.get atoms k in
if not (ak.neg.is_true) then begin
(* watch lit found: update and exit *)
Vec.set atoms 1 ak;
Vec.set atoms k a.neg;
Vec.push ak.neg.watched c;
raise Exit
end
done;
(* no watch lit found *)
if first.neg.is_true || (th_eval first = Some false) then begin
(* clause is false *)
env.elt_head <- Vec.size env.elt_queue;
for k = i to Vec.size watched - 1 do
Vec.set watched !new_sz (Vec.get watched k);
incr new_sz;
done;
raise (Conflict c)
end else begin
(* clause is unit *)
Vec.set watched !new_sz c;
incr new_sz;
enqueue_bool first (decision_level ()) (Bcp c)
end
with Exit -> ()
let propagate_atom a res =
let watched = a.watched in
let new_sz_w = ref 0 in
begin
try
for i = 0 to Vec.size watched - 1 do
let c = Vec.get watched i in
if c.attached then propagate_in_clause a c i watched new_sz_w
done;
with Conflict c ->
assert (!res = None);
res := Some c
end;
let dead_part = Vec.size watched - !new_sz_w in
Vec.shrink watched dead_part
(* Propagation (boolean and theory) *)
let new_atom f =
let a = atom f in
ignore (th_eval a);
a
let slice_get i =
match Vec.get env.elt_queue i with
| Either.Right a -> Th.Lit a.lit, a.var.v_level
| Either.Left {l_level; term; assigned = Some v} -> Th.Assign (term, v), l_level
| Either.Left _ -> assert false
let slice_push l lemma =
let atoms = List.rev_map (fun x -> new_atom x) l in
Iheap.grow_to_by_double env.order (St.nb_elt ());
List.iter (fun a -> insert_var_order (elt_of_var a.var)) atoms;
let c = make_clause (fresh_tname ()) atoms (List.length atoms) (Lemma lemma) in
add_clause c
let slice_propagate f lvl =
let a = atom f in
Iheap.grow_to_by_double env.order (St.nb_elt ());
enqueue_bool a lvl (Semantic lvl)
let current_slice () = Th.({
start = env.th_head;
length = (Vec.size env.elt_queue) - env.th_head;
get = slice_get;
push = slice_push;
propagate = slice_propagate;
})
let full_slice () = Th.({
start = 0;
length = Vec.size env.elt_queue;
get = slice_get;
push = slice_push;
propagate = (fun _ -> assert false);
})
let rec theory_propagate () =
assert (env.elt_head = Vec.size env.elt_queue);
if env.th_head >= env.elt_head then
None
else begin
let slice = current_slice () in
env.th_head <- env.elt_head;
match Th.assume slice with
| Th.Sat ->
propagate ()
| Th.Unsat (l, p) ->
let l = List.rev_map new_atom l in
Iheap.grow_to_by_double env.order (St.nb_elt ());
List.iter (fun a -> insert_var_order (elt_of_var a.var)) l;
let c = St.make_clause (St.fresh_tname ()) l (List.length l) (Lemma p) in
Some c
end
and propagate () =
if env.elt_head > Vec.size env.elt_queue then
assert false
else if env.elt_head = Vec.size env.elt_queue then
theory_propagate ()
else begin
let num_props = ref 0 in
let res = ref None in
while env.elt_head < Vec.size env.elt_queue do
begin match Vec.get env.elt_queue env.elt_head with
| Either.Left _ -> ()
| Either.Right a ->
incr num_props;
propagate_atom a res
end;
env.elt_head <- env.elt_head + 1;
done;
env.propagations <- env.propagations + !num_props;
env.simpDB_props <- env.simpDB_props - !num_props;
match !res with
| None -> theory_propagate ()
| _ -> !res
end
(*
(* heuristic comparison between clauses, by their size (unary/binary or not)
and activity *)
let f_sort_db c1 c2 =
let sz1 = Vec.size c1.atoms in
let sz2 = Vec.size c2.atoms in
let c = compare c1.activity c2.activity in
if sz1 = sz2 && c = 0 then 0
else
if sz1 > 2 && (sz2 = 2 || c < 0) then -1
else 1
(* returns true if the clause is used as a reason for a propagation,
and therefore can be needed in case of conflict. In this case
the clause can't be forgotten *)
let locked c = false (*
Vec.exists
(fun v -> match v.reason with
| Some c' -> c ==c'
| _ -> false
) env.vars
*)
*)
(* remove some learnt clauses *)
let reduce_db () = () (*
let extra_lim = env.clause_inc /. (to_float (Vec.size env.learnts)) in
Vec.sort env.learnts f_sort_db;
let lim2 = Vec.size env.learnts in
let lim1 = lim2 / 2 in
let j = ref 0 in
for i = 0 to lim1 - 1 do
let c = Vec.get env.learnts i in
if Vec.size c.atoms > 2 && not (locked c) then
remove_clause c
else
begin Vec.set env.learnts !j c; incr j end
done;
for i = lim1 to lim2 - 1 do
let c = Vec.get env.learnts i in
if Vec.size c.atoms > 2 && not (locked c) && c.activity < extra_lim then
remove_clause c
else
begin Vec.set env.learnts !j c; incr j end
done;
Vec.shrink env.learnts (lim2 - !j)
*)
(* remove from [vec] the clauses that are satisfied in the current trail *)
let remove_satisfied vec =
for i = 0 to Vec.size vec - 1 do
let c = Vec.get vec i in
if satisfied c then remove_clause c
done
(*
let simplify () =
assert (decision_level () = 0);
if is_unsat () then raise Unsat;
begin
match propagate () with
| Some confl -> report_unsat confl
| None -> ()
end;
if Vec.size env.elt_queue <> env.simpDB_assigns && env.simpDB_props <= 0 then begin
if Vec.size env.clauses_learnt > 0 then remove_satisfied env.clauses_learnt;
if env.remove_satisfied then remove_satisfied env.clauses_hyps;
(*Iheap.filter env.order f_filter f_weight;*)
env.simpDB_assigns <- Vec.size env.elt_queue;
env.simpDB_props <- env.clauses_literals + env.learnts_literals;
end
*)
(* Decide on a new litteral *)
let rec pick_branch_aux atom =
let v = atom.var in
if v.v_level >= 0 then begin
assert (v.pa.is_true || v.na.is_true);
pick_branch_lit ()
end else match Th.eval atom.lit with
| Th.Unknown ->
env.decisions <- env.decisions + 1;
new_decision_level();
let current_level = decision_level () in
enqueue_bool atom current_level Decision
| Th.Valued (b, lvl) ->
let a = if b then atom else atom.neg in
enqueue_bool a lvl (Semantic lvl)
and pick_branch_lit () =
match env.next_decision with
| Some atom ->
env.next_decision <- None;
pick_branch_aux atom
| None ->
begin try
begin match St.get_elt (Iheap.remove_min f_weight env.order) with
| Either.Left l ->
if l.l_level >= 0 then
pick_branch_lit ()
else begin
let value = Th.assign l.term in
env.decisions <- env.decisions + 1;
new_decision_level();
let current_level = decision_level () in
enqueue_assign l value current_level
end
| Either.Right v ->
pick_branch_aux v.pa
end
with Not_found -> raise Sat
end
let search n_of_conflicts n_of_learnts =
let conflictC = ref 0 in
env.starts <- env.starts + 1;
while true do
match propagate () with
| Some confl -> (* Conflict *)
incr conflictC;
add_boolean_conflict confl
| None -> (* No Conflict *)
assert (env.elt_head = Vec.size env.elt_queue);
if Vec.size env.elt_queue = St.nb_elt () (* env.nb_init_vars *) then raise Sat;
if n_of_conflicts > 0 && !conflictC >= n_of_conflicts then begin
env.progress_estimate <- progress_estimate();
cancel_until 0;
raise Restart
end;
(* if decision_level() = 0 then simplify (); *)
if n_of_learnts >= 0 &&
Vec.size env.clauses_learnt - Vec.size env.elt_queue >= n_of_learnts then
reduce_db();
pick_branch_lit ()
done
let check_clause c =
let b = ref false in
let atoms = c.atoms in
for i = 0 to Vec.size atoms - 1 do
let a = Vec.get atoms i in
b := !b || a.is_true
done;
assert (!b)
let check_vec vec =
for i = 0 to Vec.size vec - 1 do check_clause (Vec.get vec i) done
let add_clauses ?tag cnf =
let aux cl =
let c = make_clause ?tag (fresh_hname ())
cl (List.length cl) (Hyp (current_level ())) in
add_clause c;
(* Clauses can be added after search has begun (and thus we are not at level 0,
so better not do anything at this point.
match propagate () with
| None -> () | Some confl -> report_unsat confl
*)
in
List.iter aux cnf
(* fixpoint of propagation and decisions until a model is found, or a
conflict is reached *)
let solve () =
if is_unsat () then raise Unsat;
let n_of_conflicts = ref (to_float env.restart_first) in
let n_of_learnts = ref ((to_float (nb_clauses())) *. env.learntsize_factor) in
try
while true do
begin try
search (to_int !n_of_conflicts) (to_int !n_of_learnts)
with
| Restart ->
n_of_conflicts := !n_of_conflicts *. env.restart_inc;
n_of_learnts := !n_of_learnts *. env.learntsize_inc
| Sat ->
Th.if_sat (full_slice ());
if is_unsat () then raise Unsat
else if env.elt_head = Vec.size env.elt_queue (* sanity check *)
&& env.elt_head = St.nb_elt () (* this is the important test to know if the search is finished *) then
raise Sat
end
done
with
| Sat -> ()
let init_solver ?tag cnf =
let nbv = St.nb_elt () in
let nbc = env.nb_init_clauses + List.length cnf in
Iheap.grow_to_by_double env.order nbv;
(* List.iter (List.iter (fun a -> insert_var_order a.var)) cnf; *)
St.iter_elt insert_var_order;
Vec.grow_to_by_double env.clauses_hyps nbc;
Vec.grow_to_by_double env.clauses_learnt nbc;
env.nb_init_clauses <- nbc;
add_clauses ?tag cnf
let assume ?tag cnf =
let cnf = List.rev_map (List.rev_map atom) cnf in
init_solver ?tag cnf
let eval_level lit =
let var, negated = make_boolean_var lit in
if not var.pa.is_true && not var.na.is_true
then raise UndecidedLit
else assert (var.v_level >= 0);
let truth = var.pa.is_true in
let value = if negated then not truth else truth in
value, var.v_level
let eval lit = fst (eval_level lit)
let hyps () = env.clauses_hyps
let history () = env.clauses_learnt
let unsat_conflict () = env.unsat_conflict
let model () =
let opt = function Some a -> a | None -> assert false in
Vec.fold (fun acc e -> match e with
| Either.Left v -> (v.term, opt v.assigned) :: acc
| Either.Right _ -> acc
) [] env.elt_queue
(* Backtrack to decision_level 0, with trail_lim && theory env specified *)
let reset_until push_lvl elt_lvl th_lvl th_env =
Log.debug 1 "Resetting to decision level 0 (pop/forced)";
env.th_head <- th_lvl;
env.elt_head <- elt_lvl;
for c = env.elt_head to Vec.size env.elt_queue - 1 do
match Vec.get env.elt_queue c with
| Either.Left l ->
l.assigned <- None;
l.l_level <- -1;
insert_var_order (elt_of_lit l)
| Either.Right a ->
begin match a.var.reason with
| Some Bcp { c_level } when c_level > push_lvl ->
a.is_true <- false;
a.neg.is_true <- false;
a.var.v_level <- -1;
a.var.reason <- None;
insert_var_order (elt_of_var a.var)
| _ ->
if a.var.v_level = 0 then begin
Vec.set env.elt_queue env.elt_head (of_atom a);
env.elt_head <- env.elt_head + 1
end else begin
a.is_true <- false;
a.neg.is_true <- false;
a.var.v_level <- -1;
a.var.reason <- None;
insert_var_order (elt_of_var a.var)
end
end
done;
Th.backtrack th_env; (* recover the right theory env *)
Vec.shrink env.elt_queue ((Vec.size env.elt_queue) - env.elt_head);
Vec.clear env.elt_levels;
Vec.clear env.th_levels;
assert (Vec.size env.elt_levels = Vec.size env.th_levels);
assert (env.elt_head = Vec.size env.elt_queue);
()
let pop l =
(* Check sanity of pop *)
if l > current_level () then invalid_arg "cannot pop to level, it is too high"
else if l < current_level () then begin
let ul = Vec.get env.user_levels l in
Vec.shrink env.user_levels (max 0 (Vec.size env.user_levels - l - 1));
(* It is quite hard to check wether unsat status can be kept, so in doubt, we remove it *)
env.unsat_conflict <- None;
(* Backtrack to the level 0 with appropriate settings *)
reset_until l ul.ul_elt_lvl ul.ul_th_lvl ul.ul_th_env;
(* Log current assumptions for debugging purposes *)
Log.debugf 99 "@[<v2>Current trail:@ %a@]"
(fun k->
let pp out () =
for i = 0 to Vec.size env.elt_queue - 1 do
Format.fprintf out "%s%s%d -- %a@,"
(if i = ul.ul_elt_lvl then "*" else " ")
(if i = ul.ul_th_lvl then "*" else " ")
i (fun fmt e ->
match e with
| Either.Left l -> St.pp_lit fmt l
| Either.Right a -> St.pp_atom fmt a)
(Vec.get env.elt_queue i)
done
in
k pp ());
(* Clear hypothesis not valid anymore *)
for i = ul.ul_clauses to Vec.size env.clauses_hyps - 1 do
let c = Vec.get env.clauses_hyps i in
assert (c.c_level > l);
remove_clause c
done;
Vec.shrink env.clauses_hyps (Vec.size env.clauses_hyps - ul.ul_clauses);
(* Refresh the known tautologies simplified because of clauses that have been removed *)
let s = Stack.create () in
let new_sz = ref ul.ul_learnt in
for i = ul.ul_learnt to Vec.size env.clauses_learnt - 1 do
let c = Vec.get env.clauses_learnt i in
if c.c_level > l then begin
remove_clause c;
match c.cpremise with
| Lemma _ -> Stack.push c s
| History ({ cpremise = Lemma _ } as c' :: _ ) -> Stack.push c' s
| _ -> () (* Only simplified clauses can have a level > 0 *)
end else begin
Log.debugf 15 "Keeping intact clause %a" (fun k->k St.pp_clause c);
Vec.set env.clauses_learnt !new_sz c;
incr new_sz
end
done;
Vec.shrink env.clauses_learnt (Vec.size env.clauses_learnt - !new_sz);
Stack.iter (add_clause ~force:true) s
end
let reset () = pop base_level
end