(** Core of the SMT solver using Sidekick_sat Sidekick_sat (in src/sat/) is a modular SAT solver in pure OCaml. This builds a {!Sidekick_core.SOLVER} on top of it. *) (** Argument to pass to the functor {!Make} in order to create a new Msat-based SMT solver. *) module type ARG = sig open Sidekick_core module T : TERM module Lit : LIT with module T = T type proof type proof_step module P : PROOF with type term = T.Term.t and type t = proof and type proof_step = proof_step and type lit = Lit.t val cc_view : T.Term.t -> (T.Fun.t, T.Term.t, T.Term.t Iter.t) CC_view.t val mk_eq : T.Term.store -> T.Term.t -> T.Term.t -> T.Term.t (** [mk_eq store t u] builds the term [t=u] *) val is_valid_literal : T.Term.t -> bool (** Is this a valid boolean literal? (e.g. is it a closed term, not inside a quantifier) *) end module type S = Sidekick_core.SOLVER module Registry : Sidekick_core.REGISTRY = struct (* registry keys *) module type KEY = sig type elt val id : int exception E of elt end type 'a key = (module KEY with type elt = 'a) type t = { tbl: exn Util.Int_tbl.t; } [@@unboxed] let create() : t = { tbl=Util.Int_tbl.create 8; } let n_ = ref 0 let create_key (type a) () : a key = let id = !n_ in incr n_; let module K = struct type elt = a exception E of a let id = id end in (module K) let get (type a) (self:t) (k:a key) : _ option = let (module K : KEY with type elt = a) = k in match Util.Int_tbl.get self.tbl K.id with | Some (K.E x) -> Some x | _ -> None let set (type a) (self:t) (k:a key) (v:a) : unit = let (module K) = k in Util.Int_tbl.replace self.tbl K.id (K.E v) end (** Main functor to get a solver. *) module Make(A : ARG) : S with module T = A.T and type proof = A.proof and type proof_step = A.proof_step and module Lit = A.Lit and module P = A.P = struct module T = A.T module P = A.P module Ty = T.Ty module Term = T.Term module Lit = A.Lit type term = Term.t type ty = Ty.t type proof = A.proof type proof_step = A.proof_step type lit = Lit.t (* actions from the sat solver *) type sat_acts = (lit, proof, proof_step) Sidekick_sat.acts (* the full argument to the congruence closure *) module CC_actions = struct module T = T module P = P module Lit = Lit type nonrec proof = proof type nonrec proof_step = proof_step let cc_view = A.cc_view let[@inline] mk_lit_eq ?sign store t u = A.Lit.atom ?sign store (A.mk_eq store t u) module Actions = struct module T = T module P = P module Lit = Lit type nonrec proof = proof type nonrec proof_step = proof_step type t = sat_acts let[@inline] proof (a:t) = let (module A) = a in A.proof let[@inline] raise_conflict (a:t) lits (pr:proof_step) = let (module A) = a in A.raise_conflict lits pr let[@inline] propagate (a:t) lit ~reason = let (module A) = a in let reason = Sidekick_sat.Consequence reason in A.propagate lit reason end end module CC = Sidekick_cc.Make(CC_actions) module N = CC.N (** Internal solver, given to theories and to Msat *) module Solver_internal = struct module T = T module P = P module Lit = Lit module CC = CC module N = CC.N type nonrec proof = proof type nonrec proof_step = proof_step type term = Term.t type ty = Ty.t type lit = Lit.t type term_store = Term.store type clause_pool type ty_store = Ty.store type th_states = | Ths_nil | Ths_cons : { st: 'a; push_level: 'a -> unit; pop_levels: 'a -> int -> unit; next: th_states; } -> th_states type theory_actions = sat_acts module Simplify = struct type t = { tst: term_store; ty_st: ty_store; proof: proof; mutable hooks: hook list; (* store [t --> u by proof_steps] in the cache. We use a bag for the proof steps because it gives us structural sharing of subproofs. *) cache: (Term.t * proof_step Bag.t) Term.Tbl.t; } and hook = t -> term -> (term * proof_step Iter.t) option let create tst ty_st ~proof : t = {tst; ty_st; proof; hooks=[]; cache=Term.Tbl.create 32;} let[@inline] tst self = self.tst let[@inline] ty_st self = self.ty_st let[@inline] proof self = self.proof let add_hook self f = self.hooks <- f :: self.hooks let clear self = Term.Tbl.clear self.cache let normalize (self:t) (t:Term.t) : (Term.t * proof_step) option = (* compute and cache normal form of [t] *) let rec loop t : Term.t * _ Bag.t = match Term.Tbl.find self.cache t with | res -> res | exception Not_found -> let steps_u = ref Bag.empty in let u = aux_rec ~steps:steps_u t self.hooks in Term.Tbl.add self.cache t (u, !steps_u); u, !steps_u and loop_add ~steps t = let u, pr_u = loop t in steps := Bag.append !steps pr_u; u (* try each function in [hooks] successively, and rewrite subterms *) and aux_rec ~steps t hooks : Term.t = match hooks with | [] -> let u = Term.map_shallow self.tst (loop_add ~steps) t in if Term.equal t u then t else loop_add ~steps u | h :: hooks_tl -> match h self t with | None -> aux_rec ~steps t hooks_tl | Some (u, _) when Term.equal t u -> aux_rec ~steps t hooks_tl | Some (u, pr_u) -> let bag_u = Bag.of_iter pr_u in steps := Bag.append !steps bag_u; let v, pr_v = loop u in (* fixpoint *) steps := Bag.append !steps pr_v; v in let u, pr_u = loop t in if Term.equal t u then None else ( (* proof: [sub_proofs |- t=u] by CC + subproof *) let step = P.lemma_preprocess t u ~using:(Bag.to_iter pr_u) self.proof in Some (u, step) ) let normalize_t self t = match normalize self t with | Some (u, pr_u) -> u, Some pr_u | None -> t, None end type simplify_hook = Simplify.hook module type PREPROCESS_ACTS = sig val proof : proof val mk_lit_nopreproc : ?sign:bool -> term -> lit val mk_lit : ?sign:bool -> term -> lit * proof_step option val add_clause : lit list -> proof_step -> unit val add_lit : ?default_pol:bool -> lit -> unit end type preprocess_actions = (module PREPROCESS_ACTS) module Registry = Registry type t = { tst: Term.store; (** state for managing terms *) ty_st: Ty.store; cc: CC.t lazy_t; (** congruence closure *) proof: proof; (** proof logger *) stat: Stat.t; count_axiom: int Stat.counter; count_preprocess_clause: int Stat.counter; count_conflict: int Stat.counter; count_propagate: int Stat.counter; registry: Registry.t; mutable on_progress: unit -> unit; simp: Simplify.t; mutable preprocess: preprocess_hook list; mutable model_ask: model_ask_hook list; mutable model_complete: model_completion_hook list; preprocess_cache: (Term.t * proof_step Bag.t) Term.Tbl.t; mutable t_defs : (term*term) list; (* term definitions *) mutable th_states : th_states; (** Set of theories *) mutable on_partial_check: (t -> theory_actions -> lit Iter.t -> unit) list; mutable on_final_check: (t -> theory_actions -> lit Iter.t -> unit) list; mutable level: int; mutable complete: bool; } and preprocess_hook = t -> preprocess_actions -> term -> (term * proof_step Iter.t) option and model_ask_hook = recurse:(t -> CC.N.t -> term) -> t -> CC.N.t -> term option and model_completion_hook = t -> add:(term -> term -> unit) -> unit type solver = t module Proof = P let[@inline] cc (t:t) = Lazy.force t.cc let[@inline] tst t = t.tst let[@inline] ty_st t = t.ty_st let[@inline] proof self = self.proof let stats t = t.stat let registry self = self.registry let simplifier self = self.simp let simplify_t self (t:Term.t) : _ option = Simplify.normalize self.simp t let simp_t self (t:Term.t) : Term.t * _ = Simplify.normalize_t self.simp t let add_simplifier (self:t) f : unit = Simplify.add_hook self.simp f let on_preprocess self f = self.preprocess <- f :: self.preprocess let on_model ?ask ?complete self = CCOpt.iter (fun f -> self.model_ask <- f :: self.model_ask) ask; CCOpt.iter (fun f -> self.model_complete <- f :: self.model_complete) complete; () let push_decision (_self:t) (acts:theory_actions) (lit:lit) : unit = let (module A) = acts in let sign = Lit.sign lit in A.add_decision_lit (Lit.abs lit) sign let[@inline] raise_conflict self (acts:theory_actions) c proof : 'a = let (module A) = acts in Stat.incr self.count_conflict; A.raise_conflict c proof let[@inline] propagate self (acts:theory_actions) p ~reason : unit = let (module A) = acts in Stat.incr self.count_propagate; A.propagate p (Sidekick_sat.Consequence reason) let[@inline] propagate_l self acts p cs proof : unit = propagate self acts p ~reason:(fun()->cs,proof) let add_sat_clause_ self (acts:theory_actions) ~keep lits (proof:proof_step) : unit = let (module A) = acts in Stat.incr self.count_axiom; A.add_clause ~keep lits proof let add_sat_clause_pool_ self (acts:theory_actions) ~pool lits (proof:proof_step) : unit = let (module A) = acts in Stat.incr self.count_axiom; A.add_clause_in_pool ~pool lits proof let add_sat_lit _self ?default_pol (acts:theory_actions) (lit:Lit.t) : unit = let (module A) = acts in A.add_lit ?default_pol lit (* actual preprocessing logic, acting on terms. this calls all the preprocessing hooks on subterms, ensuring a fixpoint. *) let preprocess_term_ (self:t) (module A0:PREPROCESS_ACTS) (t:term) : term * proof_step option = let mk_lit_nopreproc ?sign t = Lit.atom ?sign self.tst t in (* no further simplification *) (* compute and cache normal form [u] of [t]. Also cache a list of proofs [ps] such that [ps |- t=u] by CC. It is important that we cache the proofs here, because next time we preprocess [t], we will have to re-emit the same proofs, even though we will not do any actual preprocessing work. *) let rec preproc_rec ~steps t : term = match Term.Tbl.find self.preprocess_cache t with | u, pr_u -> steps := Bag.append pr_u !steps; u | exception Not_found -> (* try rewrite at root *) let steps = ref Bag.empty in let t1 = preproc_with_hooks ~steps t self.preprocess in (* map subterms *) let t2 = Term.map_shallow self.tst (preproc_rec ~steps) t1 in let u = if not (Term.equal t t2) then ( preproc_rec ~steps t2 (* fixpoint *) ) else ( t2 ) in (* signal boolean subterms, so as to decide them in the SAT solver *) if Ty.is_bool (Term.ty u) then ( Log.debugf 5 (fun k->k "(@[solver.map-bool-subterm-to-lit@ :subterm %a@])" Term.pp u); (* make a literal (already preprocessed) *) let lit = mk_lit_nopreproc u in (* ensure that SAT solver has a boolean atom for [u] *) A0.add_lit lit; (* also map [sub] to this atom in the congruence closure, for propagation *) let cc = cc self in CC.set_as_lit cc (CC.add_term cc u) lit; ); if t != u then ( Log.debugf 5 (fun k->k "(@[smt-solver.preprocess.term@ :from %a@ :to %a@])" Term.pp t Term.pp u); ); let pr_t_u = !steps in Term.Tbl.add self.preprocess_cache t (u, pr_t_u); u (* try each function in [hooks] successively *) and preproc_with_hooks ~steps t hooks : term = let[@inline] add_step s = steps := Bag.cons s !steps in match hooks with | [] -> t | h :: hooks_tl -> (* call hook, using [pacts] which will ensure all new literals and clauses are also preprocessed *) match h self (Lazy.force pacts) t with | None -> preproc_with_hooks ~steps t hooks_tl | Some (u, pr_u) -> Iter.iter add_step pr_u; preproc_rec ~steps u (* create literal and preprocess it with [pacts], which uses [A0] for the base operations, and preprocesses new literals and clauses recursively. *) and mk_lit ?sign t : _ * proof_step option = let steps = ref Bag.empty in let u = preproc_rec ~steps t in let pr_t_u = if not (Term.equal t u) then ( Log.debugf 10 (fun k->k "(@[smt-solver.preprocess.t@ :t %a@ :into %a@])" Term.pp t Term.pp u); let p = A.P.lemma_preprocess t u ~using:(Bag.to_iter !steps) self.proof in Some p ) else None in Lit.atom self.tst ?sign u, pr_t_u and preprocess_lit ~steps (lit:lit) : lit = let t = Lit.term lit in let sign = Lit.sign lit in let lit, pr = mk_lit ~sign t in CCOpt.iter (fun s -> steps := s :: !steps) pr; lit (* wrap [A0] so that all operations go throught preprocessing *) and pacts = lazy ( (module struct let proof = A0.proof let add_lit ?default_pol lit = (* just drop the proof *) (* TODO: add a clause instead [lit => preprocess(lit)]? *) let lit = preprocess_lit ~steps:(ref []) lit in A0.add_lit ?default_pol lit let add_clause c pr_c = Stat.incr self.count_preprocess_clause; let steps = ref [] in let c' = CCList.map (preprocess_lit ~steps) c in let pr_c' = A.P.lemma_rw_clause pr_c ~res:(Iter.of_list c') ~using:(Iter.of_list !steps) proof in A0.add_clause c' pr_c' let mk_lit_nopreproc = mk_lit_nopreproc let mk_lit = mk_lit end : PREPROCESS_ACTS) ) in let steps = ref Bag.empty in let[@inline] add_step s = steps := Bag.cons s !steps in (* simplify the term *) let t1, pr_1 = simp_t self t in CCOpt.iter add_step pr_1; (* preprocess *) let u = preproc_rec ~steps t1 in (* emit [|- t=u] *) let pr_u = if not (Term.equal t u) then ( let p = P.lemma_preprocess t u ~using:(Bag.to_iter !steps) self.proof in Some p ) else None in u, pr_u (* return preprocessed lit *) let preprocess_lit_ ~steps (self:t) (pacts:preprocess_actions) (lit:lit) : lit = let t = Lit.term lit in let sign = Lit.sign lit in let u, pr_u = preprocess_term_ self pacts t in CCOpt.iter (fun s -> steps := s :: !steps) pr_u; Lit.atom self.tst ~sign u (* add a clause using [acts] *) let add_clause_ self acts lits (proof:proof_step) : unit = add_sat_clause_ self acts ~keep:true lits proof let[@inline] add_lit _self (acts:theory_actions) ?default_pol lit : unit = let (module A) = acts in A.add_lit ?default_pol lit let preprocess_acts_of_acts (self:t) (acts:theory_actions) : preprocess_actions = (module struct let proof = self.proof let mk_lit_nopreproc ?sign t = Lit.atom self.tst ?sign t let mk_lit ?sign t = Lit.atom self.tst ?sign t, None let add_clause = add_clause_ self acts let add_lit = add_lit self acts end) let preprocess_clause_ (self:t) (acts:theory_actions) (c:lit list) (proof:proof_step) : lit list * proof_step = let steps = ref [] in let pacts = preprocess_acts_of_acts self acts in let c = CCList.map (preprocess_lit_ ~steps self pacts) c in let pr = P.lemma_rw_clause proof ~res:(Iter.of_list c) ~using:(Iter.of_list !steps) self.proof in c, pr (* make literal and preprocess it *) let[@inline] mk_plit (self:t) (pacts:preprocess_actions) ?sign (t:term) : lit = let lit = Lit.atom self.tst ?sign t in let steps = ref [] in preprocess_lit_ ~steps self pacts lit let[@inline] preprocess_term self (pacts:preprocess_actions) (t:term) : term * _ option = preprocess_term_ self pacts t let[@inline] add_clause_temp self acts c (proof:proof_step) : unit = let c, proof = preprocess_clause_ self acts c proof in add_sat_clause_ self acts ~keep:false c proof let[@inline] add_clause_permanent self acts c (proof:proof_step) : unit = let c, proof = preprocess_clause_ self acts c proof in add_sat_clause_ self acts ~keep:true c proof let[@inline] mk_lit (self:t) (acts:theory_actions) ?sign t : lit = let pacts = preprocess_acts_of_acts self acts in mk_plit self pacts ?sign t let add_lit_t self acts ?sign t = let pacts = preprocess_acts_of_acts self acts in let lit = mk_plit self pacts ?sign t in add_lit self acts lit let on_final_check self f = self.on_final_check <- f :: self.on_final_check let on_partial_check self f = self.on_partial_check <- f :: self.on_partial_check let on_cc_new_term self f = CC.on_new_term (cc self) f let on_cc_pre_merge self f = CC.on_pre_merge (cc self) f let on_cc_post_merge self f = CC.on_post_merge (cc self) f let on_cc_conflict self f = CC.on_conflict (cc self) f let on_cc_propagate self f = CC.on_propagate (cc self) f let on_cc_is_subterm self f = CC.on_is_subterm (cc self) f let cc_add_term self t = CC.add_term (cc self) t let cc_mem_term self t = CC.mem_term (cc self) t let cc_find self n = CC.find (cc self) n let cc_are_equal self t1 t2 = let n1 = cc_add_term self t1 in let n2 = cc_add_term self t2 in N.equal (cc_find self n1) (cc_find self n2) let cc_merge self _acts n1 n2 e = CC.merge (cc self) n1 n2 e let cc_merge_t self acts t1 t2 e = cc_merge self acts (cc_add_term self t1) (cc_add_term self t2) e let cc_raise_conflict_expl self acts e = CC.raise_conflict_from_expl (cc self) acts e (** {2 Interface with the SAT solver} *) let rec push_lvl_ = function | Ths_nil -> () | Ths_cons r -> r.push_level r.st; push_lvl_ r.next let rec pop_lvls_ n = function | Ths_nil -> () | Ths_cons r -> r.pop_levels r.st n; pop_lvls_ n r.next let push_level (self:t) : unit = self.level <- 1 + self.level; CC.push_level (cc self); push_lvl_ self.th_states let pop_levels (self:t) n : unit = self.level <- self.level - n; CC.pop_levels (cc self) n; pop_lvls_ n self.th_states exception E_loop_exit (* handle a literal assumed by the SAT solver *) let assert_lits_ ~final (self:t) (acts:theory_actions) (lits:Lit.t Iter.t) : unit = Log.debugf 2 (fun k->k "(@[@{smt-solver.assume_lits@}%s[lvl=%d]@ %a@])" (if final then "[final]" else "") self.level (Util.pp_iter ~sep:"; " Lit.pp) lits); (* transmit to CC *) let cc = cc self in if not final then ( CC.assert_lits cc lits; ); (* transmit to theories. *) CC.check cc acts; if final then ( try while true do (* TODO: theory combination *) List.iter (fun f -> f self acts lits) self.on_final_check; CC.check cc acts; if not @@ CC.new_merges cc then ( raise_notrace E_loop_exit ); done; with E_loop_exit -> () ) else ( List.iter (fun f -> f self acts lits) self.on_partial_check; ); () let[@inline] iter_atoms_ (acts:theory_actions) : _ Iter.t = fun f -> let (module A) = acts in A.iter_assumptions f (* propagation from the bool solver *) let check_ ~final (self:t) (acts: sat_acts) = let pb = if final then Profile.begin_ "solver.final-check" else Profile.null_probe in let iter = iter_atoms_ acts in Log.debugf 5 (fun k->k "(smt-solver.assume :len %d)" (Iter.length iter)); self.on_progress(); assert_lits_ ~final self acts iter; Profile.exit pb (* propagation from the bool solver *) let[@inline] partial_check (self:t) (acts:_ Sidekick_sat.acts) : unit = check_ ~final:false self acts (* perform final check of the model *) let[@inline] final_check (self:t) (acts:_ Sidekick_sat.acts) : unit = check_ ~final:true self acts let declare_pb_is_incomplete self = if self.complete then ( Log.debug 1 "(solver.declare-pb-is-incomplete)"; ); self.complete <- false let create ~stat ~proof (tst:Term.store) (ty_st:Ty.store) () : t = let rec self = { tst; ty_st; cc = lazy ( (* lazily tie the knot *) CC.create ~size:`Big self.tst self.proof; ); proof; th_states=Ths_nil; stat; simp=Simplify.create tst ty_st ~proof; on_progress=(fun () -> ()); preprocess=[]; model_ask=[]; model_complete=[]; registry=Registry.create(); preprocess_cache=Term.Tbl.create 32; count_axiom = Stat.mk_int stat "solver.th-axioms"; count_preprocess_clause = Stat.mk_int stat "solver.preprocess-clause"; count_propagate = Stat.mk_int stat "solver.th-propagations"; count_conflict = Stat.mk_int stat "solver.th-conflicts"; t_defs=[]; on_partial_check=[]; on_final_check=[]; level=0; complete=true; } in ignore (Lazy.force @@ self.cc : CC.t); self end (** the parametrized SAT Solver *) module Sat_solver = Sidekick_sat.Make_cdcl_t(Solver_internal) module Registry = Solver_internal.Registry module type THEORY = sig type t val name : string val create_and_setup : Solver_internal.t -> t val push_level : t -> unit val pop_levels : t -> int -> unit end type theory = (module THEORY) type 'a theory_p = (module THEORY with type t = 'a) (** {2 Result} *) module Unknown = struct type t = | U_timeout | U_max_depth | U_incomplete | U_asked_to_stop let pp out = function | U_timeout -> Fmt.string out "timeout" | U_max_depth -> Fmt.string out "max depth reached" | U_incomplete -> Fmt.string out "incomplete fragment" | U_asked_to_stop -> Fmt.string out "asked to stop by callback" end [@@ocaml.warning "-37"] module Model = struct type t = | Empty | Map of term Term.Tbl.t let empty = Empty let mem = function | Empty -> fun _ -> false | Map tbl -> Term.Tbl.mem tbl let find = function | Empty -> fun _ -> None | Map tbl -> Term.Tbl.get tbl let eval = find let pp out = function | Empty -> Fmt.string out "(model)" | Map tbl -> let pp_pair out (t,v) = Fmt.fprintf out "(@[<1>%a@ := %a@])" Term.pp t Term.pp v in Fmt.fprintf out "(@[model@ %a@])" (Util.pp_iter pp_pair) (Term.Tbl.to_iter tbl) end type res = | Sat of Model.t | Unsat of { unsat_core: unit -> lit Iter.t; (** Unsat core (subset of assumptions), or empty *) unsat_proof_step: unit -> proof_step option; (** Proof step for the empty clause *) } | Unknown of Unknown.t (** Result of solving for the current set of clauses *) (* main solver state *) type t = { si: Solver_internal.t; solver: Sat_solver.t; mutable last_res: res option; stat: Stat.t; proof: P.t; count_clause: int Stat.counter; count_solve: int Stat.counter; (* config: Config.t *) } type solver = t (** {2 Main} *) let add_theory_p (type a) (self:t) (th:a theory_p) : a = let (module Th) = th in Log.debugf 2 (fun k-> k "(@[smt-solver.add-theory@ :name %S@])" Th.name); let st = Th.create_and_setup self.si in (* add push/pop to the internal solver *) begin let open Solver_internal in self.si.th_states <- Ths_cons { st; push_level=Th.push_level; pop_levels=Th.pop_levels; next=self.si.th_states; }; end; st let add_theory (self:t) (th:theory) : unit = let (module Th) = th in ignore (add_theory_p self (module Th)) let add_theory_l self = List.iter (add_theory self) (* create a new solver *) let create ?(stat=Stat.global) ?size ~proof ~theories tst ty_st () : t = Log.debug 5 "smt-solver.create"; let si = Solver_internal.create ~stat ~proof tst ty_st () in let self = { si; proof; last_res=None; solver=Sat_solver.create ~proof ?size ~stat si; stat; count_clause=Stat.mk_int stat "solver.add-clause"; count_solve=Stat.mk_int stat "solver.solve"; } in add_theory_l self theories; (* assert [true] and [not false] *) begin let tst = Solver_internal.tst self.si in let t_true = Term.bool tst true in Sat_solver.add_clause self.solver [Lit.atom tst t_true] (P.lemma_true t_true self.proof) end; self let[@inline] solver self = self.solver let[@inline] cc self = Solver_internal.cc self.si let[@inline] stats self = self.stat let[@inline] tst self = Solver_internal.tst self.si let[@inline] ty_st self = Solver_internal.ty_st self.si let[@inline] proof self = self.si.proof let[@inline] last_res self = self.last_res let[@inline] registry self = Solver_internal.registry self.si let reset_last_res_ self = self.last_res <- None let preprocess_acts_of_solver_ (self:t) : (module Solver_internal.PREPROCESS_ACTS) = (module struct let proof = self.proof let mk_lit_nopreproc ?sign t = Lit.atom ?sign self.si.tst t let mk_lit ?sign t = Lit.atom ?sign self.si.tst t, None let add_lit ?default_pol lit = Sat_solver.add_lit self.solver ?default_pol lit let add_clause c pr = Sat_solver.add_clause self.solver c pr end) (* preprocess literal *) let preprocess_lit_ ~steps (self:t) (lit:lit) : lit = let pacts = preprocess_acts_of_solver_ self in Solver_internal.preprocess_lit_ ~steps self.si pacts lit (* preprocess clause, return new proof *) let preprocess_clause_ (self:t) (c:lit IArray.t) (pr:proof_step) : lit IArray.t * proof_step = let steps = ref [] in let c = IArray.map (preprocess_lit_ self ~steps) c in let pr = P.lemma_rw_clause pr ~res:(IArray.to_iter c) ~using:(Iter.of_list !steps) self.proof in c, pr (* make a literal from a term, ensuring it is properly preprocessed *) let mk_lit_t (self:t) ?sign (t:term) : lit = let pacts = preprocess_acts_of_solver_ self in Solver_internal.mk_plit self.si pacts ?sign t (** {2 Main} *) let pp_stats out (self:t) : unit = Stat.pp_all out (Stat.all @@ stats self) (* add [c], without preprocessing its literals *) let add_clause_nopreproc_ (self:t) (c:lit IArray.t) (proof:proof_step) : unit = Stat.incr self.count_clause; reset_last_res_ self; Log.debugf 50 (fun k-> k "(@[solver.add-clause@ %a@])" (Util.pp_iarray Lit.pp) c); let pb = Profile.begin_ "add-clause" in Sat_solver.add_clause_a self.solver (c:> lit array) proof; Profile.exit pb let add_clause_nopreproc_l_ self c p = add_clause_nopreproc_ self (IArray.of_list c) p let add_clause (self:t) (c:lit IArray.t) (proof:proof_step) : unit = let c, proof = preprocess_clause_ self c proof in add_clause_nopreproc_ self c proof let add_clause_l self c p = add_clause self (IArray.of_list c) p let assert_terms self c = let steps = ref [] in let c = CCList.map (fun t -> Lit.atom (tst self) t) c in let c = CCList.map (preprocess_lit_ ~steps self) c in (* TODO: if c != c0 then P.emit_redundant_clause c because we jsut preprocessed it away? *) let pr = P.emit_input_clause (Iter.of_list c) self.proof in let pr = P.lemma_rw_clause pr ~res:(Iter.of_list c) ~using:(Iter.of_list !steps) self.proof in add_clause_l self c pr let assert_term self t = assert_terms self [t] let mk_model (self:t) (lits:lit Iter.t) : Model.t = Log.debug 1 "(smt.solver.mk-model)"; Profile.with_ "smt-solver.mk-model" @@ fun () -> let module M = Term.Tbl in let model = M.create 128 in let {Solver_internal.tst; cc=lazy cc; model_ask; model_complete; _} = self.si in (* first, add all literals to the model using the given propositional model [lits]. *) lits (fun lit -> let t, sign = Lit.signed_term lit in M.replace model t (Term.bool tst sign)); (* complete model with theory specific values *) let complete_with f = f self.si ~add:(fun t u -> if not (M.mem model t) then ( M.replace model t u )); in List.iter complete_with model_complete; (* compute a value for [n]. *) let rec val_for_class (n:N.t) : term = let repr = CC.find cc n in (* see if a value is found already (always the case if it's a boolean) *) match M.get model (N.term repr) with | Some t_val -> t_val | None -> (* try each model hook *) let rec aux = function | [] -> N.term repr | h :: hooks -> begin match h ~recurse:(fun _ n -> val_for_class n) self.si repr with | None -> aux hooks | Some t -> t end in let t_val = aux model_ask in M.replace model (N.term repr) t_val; (* be sure to cache the value *) t_val in (* map terms of each CC class to the value computed for their class. *) Solver_internal.CC.all_classes (Solver_internal.cc self.si) (fun repr -> let t_val = val_for_class repr in (* value for this class *) N.iter_class repr (fun u -> let t_u = N.term u in if not (N.equal u repr) && not (Term.equal t_u t_val) then ( M.replace model t_u t_val; ))); Model.Map model exception Should_stop let solve ?(on_exit=[]) ?(check=true) ?(on_progress=fun _ -> ()) ?(should_stop=fun _ _ -> false) ~assumptions (self:t) : res = Profile.with_ "smt-solver.solve" @@ fun () -> let do_on_exit () = List.iter (fun f->f()) on_exit; in let on_progress = let resource_counter = ref 0 in fun() -> incr resource_counter; on_progress self; if should_stop self !resource_counter then raise_notrace Should_stop in self.si.on_progress <- on_progress; let res = match Stat.incr self.count_solve; Sat_solver.solve ~on_progress ~assumptions (solver self) with | Sat_solver.Sat _ when not self.si.complete -> Log.debugf 1 (fun k->k "(@[sidekick.smt-solver: SAT@ actual: UNKNOWN@ :reason incomplete-fragment@])"); Unknown Unknown.U_incomplete | Sat_solver.Sat (module SAT) -> Log.debug 1 "(sidekick.smt-solver: SAT)"; Log.debugf 50 (fun k-> let ppc out n = Fmt.fprintf out "{@[class@ %a@]}" (Util.pp_iter N.pp) (N.iter_class n) in k "(@[sidekick.smt-solver.classes@ (@[%a@])@])" (Util.pp_iter ppc) (CC.all_classes @@ Solver_internal.cc self.si)); let _lits f = SAT.iter_trail f in let m = mk_model self _lits in (* TODO: check model *) let _ = check in do_on_exit (); Sat m | Sat_solver.Unsat (module UNSAT) -> let unsat_core () = UNSAT.unsat_assumptions () in let unsat_proof_step () = Some (UNSAT.unsat_proof()) in do_on_exit (); Unsat {unsat_core; unsat_proof_step} | exception Should_stop -> Unknown Unknown.U_asked_to_stop in self.last_res <- Some res; res let push_assumption self a = reset_last_res_ self; Sat_solver.push_assumption self.solver a let pop_assumptions self n = reset_last_res_ self; Sat_solver.pop_assumptions self.solver n type propagation_result = | PR_sat | PR_conflict of { backtracked: int; } | PR_unsat of { unsat_core: unit -> lit Iter.t; } let check_sat_propagations_only ~assumptions self : propagation_result = reset_last_res_ self; match Sat_solver.check_sat_propagations_only ~assumptions self.solver with | Sat_solver.PR_sat -> PR_sat | Sat_solver.PR_conflict {backtracked} -> PR_conflict {backtracked} | Sat_solver.PR_unsat (module UNSAT) -> let unsat_core () = UNSAT.unsat_assumptions () in PR_unsat {unsat_core} let mk_theory (type st) ~name ~create_and_setup ?(push_level=fun _ -> ()) ?(pop_levels=fun _ _ -> ()) () : theory = let module Th = struct type t = st let name = name let create_and_setup = create_and_setup let push_level = push_level let pop_levels = pop_levels end in (module Th : THEORY) end