sidekick/src/algos/lra/sidekick_arith_lra.ml
Simon Cruanes 6dca63b0ea
renamings
2022-07-18 23:27:12 -04:00

815 lines
26 KiB
OCaml
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(** Linear Rational Arithmetic *)
(* Reference:
http://smtlib.cs.uiowa.edu/logics-all.shtml#QF_LRA *)
open Sidekick_sigs_smt
module Predicate = Sidekick_simplex.Predicate
module Linear_expr = Sidekick_simplex.Linear_expr
module Linear_expr_intf = Sidekick_simplex.Linear_expr_intf
module type INT = Sidekick_arith.INT
module type RATIONAL = Sidekick_arith.RATIONAL
module S_op = Sidekick_simplex.Op
type pred = Linear_expr_intf.bool_op = Leq | Geq | Lt | Gt | Eq | Neq
type op = Linear_expr_intf.op = Plus | Minus
type ('num, 'a) lra_view =
| LRA_pred of pred * 'a * 'a
| LRA_op of op * 'a * 'a
| LRA_mult of 'num * 'a
| LRA_const of 'num
| LRA_other of 'a
let map_view f (l : _ lra_view) : _ lra_view =
match l with
| LRA_pred (p, a, b) -> LRA_pred (p, f a, f b)
| LRA_op (p, a, b) -> LRA_op (p, f a, f b)
| LRA_mult (n, a) -> LRA_mult (n, f a)
| LRA_const q -> LRA_const q
| LRA_other x -> LRA_other (f x)
module type ARG = sig
module S : SOLVER
module Z : INT
module Q : RATIONAL with type bigint = Z.t
type term = S.T.Term.t
type ty = S.T.Ty.t
val view_as_lra : term -> (Q.t, term) lra_view
(** Project the term into the theory view *)
val mk_bool : S.T.Term.store -> bool -> term
val mk_lra : S.T.Term.store -> (Q.t, term) lra_view -> term
(** Make a term from the given theory view *)
val ty_lra : S.T.Term.store -> ty
val mk_eq : S.T.Term.store -> term -> term -> term
(** syntactic equality *)
val has_ty_real : term -> bool
(** Does this term have the type [Real] *)
val lemma_lra : S.Lit.t Iter.t -> S.Proof_trace.A.rule
module Gensym : sig
type t
val create : S.T.Term.store -> t
val tst : t -> S.T.Term.store
val copy : t -> t
val fresh_term : t -> pre:string -> S.T.Ty.t -> term
(** Make a fresh term of the given type *)
end
end
module type S = sig
module A : ARG
(*
module SimpVar : Sidekick_simplex.VAR with type lit = A.S.Lit.t
module LE_ : Linear_expr_intf.S with module Var = SimpVar
module LE = LE_.Expr
*)
module SimpSolver : Sidekick_simplex.S
(** Simplexe *)
type state
val create : ?stat:Stat.t -> A.S.Solver_internal.t -> state
(* TODO: be able to declare some variables as ints *)
(*
val simplex : state -> Simplex.t
*)
val k_state : state A.S.Solver_internal.Registry.key
(** Key to access the state from outside,
available when the theory has been setup *)
val theory : A.S.theory
end
module Make (A : ARG) : S with module A = A = struct
module A = A
module Ty = A.S.T.Ty
module T = A.S.T.Term
module Lit = A.S.Solver_internal.Lit
module SI = A.S.Solver_internal
module N = SI.CC.Class
open struct
module Pr = SI.Proof_trace
end
module Tag = struct
type t = Lit of Lit.t | CC_eq of N.t * N.t
let pp out = function
| Lit l -> Fmt.fprintf out "(@[lit %a@])" Lit.pp l
| CC_eq (n1, n2) -> Fmt.fprintf out "(@[cc-eq@ %a@ %a@])" N.pp n1 N.pp n2
let to_lits si = function
| Lit l -> [ l ]
| CC_eq (n1, n2) ->
let r = SI.CC.explain_eq (SI.cc si) n1 n2 in
assert (not (SI.CC.Resolved_expl.is_semantic r));
r.lits
end
module SimpVar : Linear_expr.VAR with type t = A.term and type lit = Tag.t =
struct
type t = A.term
let pp = A.S.T.Term.pp
let compare = A.S.T.Term.compare
type lit = Tag.t
let pp_lit = Tag.pp
let not_lit = function
| Tag.Lit l -> Some (Tag.Lit (Lit.neg l))
| _ -> None
end
module LE_ = Linear_expr.Make (A.Q) (SimpVar)
module LE = LE_.Expr
module SimpSolver = Sidekick_simplex.Make (struct
module Z = A.Z
module Q = A.Q
module Var = SimpVar
let mk_lit _ _ _ = assert false
end)
module Subst = SimpSolver.Subst
module Comb_map = CCMap.Make (LE_.Comb)
(* turn the term into a linear expression. Apply [f] on leaves. *)
let rec as_linexp (t : T.t) : LE.t =
let open LE.Infix in
match A.view_as_lra t with
| LRA_other _ -> LE.monomial1 t
| LRA_pred _ ->
Error.errorf "type error: in linexp, LRA predicate %a" T.pp t
| LRA_op (op, t1, t2) ->
let t1 = as_linexp t1 in
let t2 = as_linexp t2 in
(match op with
| Plus -> t1 + t2
| Minus -> t1 - t2)
| LRA_mult (n, x) ->
let t = as_linexp x in
LE.(n * t)
| LRA_const q -> LE.of_const q
(* monoid to track linear expressions in congruence classes, to clash on merge *)
module Monoid_exprs = struct
module CC = SI.CC
let name = "lra.const"
type single = { le: LE.t; n: N.t }
type t = single list
let pp_single out { le = _; n } = N.pp out n
let pp out self =
match self with
| [] -> ()
| [ x ] -> pp_single out x
| _ -> Fmt.fprintf out "(@[exprs@ %a@])" (Util.pp_list pp_single) self
let of_term _cc n t =
match A.view_as_lra t with
| LRA_const _ | LRA_op _ | LRA_mult _ ->
let le = as_linexp t in
Some [ { n; le } ], []
| LRA_other _ | LRA_pred _ -> None, []
exception Confl of SI.CC.Expl.t
(* merge lists. If two linear expressions equal up to a constant are
merged, conflict. *)
let merge _cc n1 l1 n2 l2 expl_12 : _ result =
try
let i = Iter.(product (of_list l1) (of_list l2)) in
i (fun (s1, s2) ->
let le = LE.(s1.le - s2.le) in
if LE.is_const le && not (LE.is_zero le) then (
(* conflict: [le+c = le + d] is impossible *)
let expl =
let open SI.CC.Expl in
mk_list [ mk_merge s1.n n1; mk_merge s2.n n2; expl_12 ]
in
raise (Confl expl)
));
Ok (List.rev_append l1 l2)
with Confl expl -> Error expl
end
module ST_exprs = Sidekick_cc_plugin.Make (Monoid_exprs)
type state = {
tst: T.store;
ty_st: Ty.store;
proof: SI.Proof_trace.t;
gensym: A.Gensym.t;
in_model: unit T.Tbl.t; (* terms to add to model *)
encoded_eqs: unit T.Tbl.t;
(* [a=b] gets clause [a = b <=> (a >= b /\ a <= b)] *)
needs_th_combination: unit T.Tbl.t;
(* terms that require theory combination *)
simp_preds: (T.t * S_op.t * A.Q.t) T.Tbl.t;
(* term -> its simplex meaning *)
simp_defined: LE.t T.Tbl.t;
(* (rational) terms that are equal to a linexp *)
st_exprs: ST_exprs.t;
mutable encoded_le: T.t Comb_map.t; (* [le] -> var encoding [le] *)
simplex: SimpSolver.t;
mutable last_res: SimpSolver.result option;
}
let create ?(stat = Stat.create ()) (si : SI.t) : state =
let proof = SI.proof si in
let tst = SI.tst si in
let ty_st = SI.ty_st si in
{
tst;
ty_st;
proof;
in_model = T.Tbl.create 8;
st_exprs = ST_exprs.create_and_setup (SI.cc si);
gensym = A.Gensym.create tst;
simp_preds = T.Tbl.create 32;
simp_defined = T.Tbl.create 16;
encoded_eqs = T.Tbl.create 8;
needs_th_combination = T.Tbl.create 8;
encoded_le = Comb_map.empty;
simplex = SimpSolver.create ~stat ();
last_res = None;
}
let[@inline] reset_res_ (self : state) : unit = self.last_res <- None
let[@inline] n_levels self : int = ST_exprs.n_levels self.st_exprs
let push_level self =
ST_exprs.push_level self.st_exprs;
SimpSolver.push_level self.simplex;
()
let pop_levels self n =
reset_res_ self;
ST_exprs.pop_levels self.st_exprs n;
SimpSolver.pop_levels self.simplex n;
()
let fresh_term self ~pre ty = A.Gensym.fresh_term self.gensym ~pre ty
let fresh_lit (self : state) ~mk_lit ~pre : Lit.t =
let t = fresh_term ~pre self (Ty.bool self.ty_st) in
mk_lit t
let pp_pred_def out (p, l1, l2) : unit =
Fmt.fprintf out "(@[%a@ :l1 %a@ :l2 %a@])" Predicate.pp p LE.pp l1 LE.pp l2
let[@inline] t_const self n : T.t = A.mk_lra self.tst (LRA_const n)
let[@inline] t_zero self : T.t = t_const self A.Q.zero
let[@inline] is_const_ t =
match A.view_as_lra t with
| LRA_const _ -> true
| _ -> false
let[@inline] as_const_ t =
match A.view_as_lra t with
| LRA_const n -> Some n
| _ -> None
let[@inline] is_zero t =
match A.view_as_lra t with
| LRA_const n -> A.Q.(n = zero)
| _ -> false
let t_of_comb (self : state) (comb : LE_.Comb.t) ~(init : T.t) : T.t =
let[@inline] ( + ) a b = A.mk_lra self.tst (LRA_op (Plus, a, b)) in
let[@inline] ( * ) a b = A.mk_lra self.tst (LRA_mult (a, b)) in
let cur = ref init in
LE_.Comb.iter
(fun t c ->
let tc =
if A.Q.(c = of_int 1) then
t
else
c * t
in
cur :=
if is_zero !cur then
tc
else
!cur + tc)
comb;
!cur
(* encode back into a term *)
let t_of_linexp (self : state) (le : LE.t) : T.t =
let comb = LE.comb le in
let const = LE.const le in
t_of_comb self comb ~init:(A.mk_lra self.tst (LRA_const const))
(* return a variable that is equal to [le_comb] in the simplex. *)
let var_encoding_comb ~pre self (le_comb : LE_.Comb.t) : T.t =
assert (not (LE_.Comb.is_empty le_comb));
match LE_.Comb.as_singleton le_comb with
| Some (c, x) when A.Q.(c = one) -> x (* trivial linexp *)
| _ ->
(match Comb_map.find le_comb self.encoded_le with
| x -> x (* already encoded that *)
| exception Not_found ->
(* new variable to represent [le_comb] *)
let proxy = fresh_term self ~pre (A.ty_lra self.tst) in
(* TODO: define proxy *)
self.encoded_le <- Comb_map.add le_comb proxy self.encoded_le;
Log.debugf 50 (fun k ->
k "(@[lra.encode-linexp@ `@[%a@]`@ :into-var %a@])" LE_.Comb.pp
le_comb T.pp proxy);
LE_.Comb.iter (fun v _ -> SimpSolver.add_var self.simplex v) le_comb;
SimpSolver.define self.simplex proxy (LE_.Comb.to_list le_comb);
proxy)
let add_clause_lra_ ?using (module PA : SI.PREPROCESS_ACTS) lits =
let pr = Pr.add_step PA.proof @@ A.lemma_lra (Iter.of_list lits) in
let pr =
match using with
| None -> pr
| Some using ->
Pr.add_step PA.proof
@@ SI.P_core_rules.lemma_rw_clause pr ~res:(Iter.of_list lits) ~using
in
PA.add_clause lits pr
let s_op_of_pred pred : S_op.t =
match pred with
| Eq | Neq -> assert false (* unreachable *)
| Leq -> S_op.Leq
| Lt -> S_op.Lt
| Geq -> S_op.Geq
| Gt -> S_op.Gt
(* TODO: refactor that and {!var_encoding_comb} *)
(* turn a linear expression into a single constant and a coeff.
This might define a side variable in the simplex. *)
let le_comb_to_singleton_ (self : state) (le_comb : LE_.Comb.t) : T.t * A.Q.t
=
match LE_.Comb.as_singleton le_comb with
| Some (coeff, v) -> v, coeff
| None ->
(* non trivial linexp, give it a fresh name in the simplex *)
(match Comb_map.get le_comb self.encoded_le with
| Some x -> x, A.Q.one (* already encoded that *)
| None ->
let proxy = fresh_term self ~pre:"_le_comb" (A.ty_lra self.tst) in
self.encoded_le <- Comb_map.add le_comb proxy self.encoded_le;
LE_.Comb.iter (fun v _ -> SimpSolver.add_var self.simplex v) le_comb;
SimpSolver.define self.simplex proxy (LE_.Comb.to_list le_comb);
Log.debugf 50 (fun k ->
k "(@[lra.encode-linexp.to-term@ `@[%a@]`@ :new-t %a@])" LE_.Comb.pp
le_comb T.pp proxy);
proxy, A.Q.one)
(* look for subterms of type Real, for they will need theory combination *)
let on_subterm (self : state) (t : T.t) : unit =
Log.debugf 50 (fun k -> k "(@[lra.cc-on-subterm@ %a@])" T.pp t);
match A.view_as_lra t with
| LRA_other _ when not (A.has_ty_real t) -> ()
| LRA_pred _ | LRA_const _ -> ()
| LRA_op _ | LRA_other _ | LRA_mult _ ->
if not (T.Tbl.mem self.needs_th_combination t) then (
Log.debugf 5 (fun k -> k "(@[lra.needs-th-combination@ %a@])" T.pp t);
T.Tbl.add self.needs_th_combination t ()
)
(* preprocess linear expressions away *)
let preproc_lra (self : state) si (module PA : SI.PREPROCESS_ACTS) (t : T.t) :
unit =
Log.debugf 50 (fun k -> k "(@[lra.preprocess@ %a@])" T.pp t);
let tst = SI.tst si in
(* tell the CC this term exists *)
let declare_term_to_cc ~sub t =
Log.debugf 50 (fun k -> k "(@[lra.declare-term-to-cc@ %a@])" T.pp t);
ignore (SI.CC.add_term (SI.cc si) t : N.t);
if sub then on_subterm self t
in
match A.view_as_lra t with
| _ when T.Tbl.mem self.simp_preds t ->
() (* already turned into a simplex predicate *)
| LRA_pred (((Eq | Neq) as pred), t1, t2) when is_const_ t1 && is_const_ t2
->
(* comparison of constants: can decide right now *)
(match A.view_as_lra t1, A.view_as_lra t2 with
| LRA_const n1, LRA_const n2 ->
let is_eq = pred = Eq in
let t_is_true = is_eq = A.Q.equal n1 n2 in
let lit = PA.mk_lit ~sign:t_is_true t in
add_clause_lra_ (module PA) [ lit ]
| _ -> assert false)
| LRA_pred ((Eq | Neq), t1, t2) ->
(* equality: just punt to [t1 = t2 <=> (t1 <= t2 /\ t1 >= t2)] *)
let t, _ = T.abs tst t in
if not (T.Tbl.mem self.encoded_eqs t) then (
let u1 = A.mk_lra tst (LRA_pred (Leq, t1, t2)) in
let u2 = A.mk_lra tst (LRA_pred (Geq, t1, t2)) in
T.Tbl.add self.encoded_eqs t ();
(* encode [t <=> (u1 /\ u2)] *)
let lit_t = PA.mk_lit t in
let lit_u1 = PA.mk_lit u1 in
let lit_u2 = PA.mk_lit u2 in
add_clause_lra_ (module PA) [ SI.Lit.neg lit_t; lit_u1 ];
add_clause_lra_ (module PA) [ SI.Lit.neg lit_t; lit_u2 ];
add_clause_lra_
(module PA)
[ SI.Lit.neg lit_u1; SI.Lit.neg lit_u2; lit_t ]
)
| LRA_pred (pred, t1, t2) ->
let l1 = as_linexp t1 in
let l2 = as_linexp t2 in
let le = LE.(l1 - l2) in
let le_comb, le_const = LE.comb le, LE.const le in
let le_const = A.Q.neg le_const in
let op = s_op_of_pred pred in
(* now we have [le_comb op le_const] *)
(* obtain a single variable for the linear combination *)
let v, c_v = le_comb_to_singleton_ self le_comb in
declare_term_to_cc ~sub:false v;
LE_.Comb.iter (fun v _ -> declare_term_to_cc ~sub:true v) le_comb;
(* turn into simplex constraint. For example,
[c . v <= const] becomes a direct simplex constraint [v <= const/c]
(beware the sign) *)
(* make sure to swap sides if multiplying with a negative coeff *)
let q = A.Q.(le_const / c_v) in
let op =
if A.Q.(c_v < zero) then
S_op.neg_sign op
else
op
in
let lit = PA.mk_lit t in
let constr = SimpSolver.Constraint.mk v op q in
SimpSolver.declare_bound self.simplex constr (Tag.Lit lit);
T.Tbl.add self.simp_preds t (v, op, q);
Log.debugf 50 (fun k ->
k "(@[lra.preproc@ :t %a@ :to-constr %a@])" T.pp t
SimpSolver.Constraint.pp constr)
| LRA_op _ | LRA_mult _ ->
if not (T.Tbl.mem self.simp_defined t) then (
(* we define these terms so their value in the model make sense *)
let le = as_linexp t in
T.Tbl.add self.simp_defined t le
)
| LRA_const _n -> ()
| LRA_other t when A.has_ty_real t -> ()
| LRA_other _ -> ()
let simplify (self : state) (_recurse : _) (t : T.t) :
(T.t * SI.step_id Iter.t) option =
let proof_eq t u =
Pr.add_step self.proof
@@ A.lemma_lra (Iter.return (SI.Lit.atom self.tst (A.mk_eq self.tst t u)))
in
let proof_bool t ~sign:b =
let lit = SI.Lit.atom ~sign:b self.tst t in
Pr.add_step self.proof @@ A.lemma_lra (Iter.return lit)
in
match A.view_as_lra t with
| LRA_op _ | LRA_mult _ ->
let le = as_linexp t in
if LE.is_const le then (
let c = LE.const le in
let u = A.mk_lra self.tst (LRA_const c) in
let pr = proof_eq t u in
Some (u, Iter.return pr)
) else (
let u = t_of_linexp self le in
if t != u then (
let pr = proof_eq t u in
Some (u, Iter.return pr)
) else
None
)
| LRA_pred ((Eq | Neq), _, _) ->
(* never change equalities, it can affect theory combination *)
None
| LRA_pred (pred, l1, l2) ->
let le = LE.(as_linexp l1 - as_linexp l2) in
if LE.is_const le then (
let c = LE.const le in
let is_true =
match pred with
| Leq -> A.Q.(c <= zero)
| Geq -> A.Q.(c >= zero)
| Lt -> A.Q.(c < zero)
| Gt -> A.Q.(c > zero)
| Eq -> A.Q.(c = zero)
| Neq -> A.Q.(c <> zero)
in
let u = A.mk_bool self.tst is_true in
let pr = proof_bool t ~sign:is_true in
Some (u, Iter.return pr)
) else (
(* le <= const *)
let u =
A.mk_lra self.tst
(LRA_pred
( pred,
t_of_comb self (LE.comb le) ~init:(t_zero self),
t_const self (A.Q.neg @@ LE.const le) ))
in
if t != u then (
let pr = proof_eq t u in
Some (u, Iter.return pr)
) else
None
)
| _ -> None
(* raise conflict from certificate *)
let fail_with_cert si acts cert : 'a =
Profile.with1 "lra.simplex.check-cert" SimpSolver._check_cert cert;
let confl =
SimpSolver.Unsat_cert.lits cert
|> CCList.flat_map (Tag.to_lits si)
|> List.rev_map SI.Lit.neg
in
let pr = Pr.add_step (SI.proof si) @@ A.lemma_lra (Iter.of_list confl) in
SI.raise_conflict si acts confl pr
let on_propagate_ si acts lit ~reason =
match lit with
| Tag.Lit lit ->
(* TODO: more detailed proof certificate *)
SI.propagate si acts lit ~reason:(fun () ->
let lits = CCList.flat_map (Tag.to_lits si) reason in
let pr =
Pr.add_step (SI.proof si)
@@ A.lemma_lra Iter.(cons lit (of_list lits))
in
CCList.flat_map (Tag.to_lits si) reason, pr)
| _ -> ()
(** Check satisfiability of simplex, and sets [self.last_res] *)
let check_simplex_ self si acts : SimpSolver.Subst.t =
Log.debugf 5 (fun k ->
k "(@[lra.check-simplex@ :n-vars %d :n-rows %d@])"
(SimpSolver.n_vars self.simplex)
(SimpSolver.n_rows self.simplex));
let res =
Profile.with_ "lra.simplex.solve" @@ fun () ->
SimpSolver.check self.simplex ~on_propagate:(on_propagate_ si acts)
in
Log.debug 5 "(lra.check-simplex.done)";
self.last_res <- Some res;
match res with
| SimpSolver.Sat m -> m
| SimpSolver.Unsat cert ->
Log.debugf 10 (fun k ->
k "(@[lra.check.unsat@ :cert %a@])" SimpSolver.Unsat_cert.pp cert);
fail_with_cert si acts cert
(* TODO: trivial propagations *)
let add_local_eq_t (self : state) si acts t1 t2 ~tag : unit =
Log.debugf 20 (fun k -> k "(@[lra.add-local-eq@ %a@ %a@])" T.pp t1 T.pp t2);
reset_res_ self;
let t1, t2 =
if T.compare t1 t2 > 0 then
t2, t1
else
t1, t2
in
let le = LE.(as_linexp t1 - as_linexp t2) in
let le_comb, le_const = LE.comb le, LE.const le in
let le_const = A.Q.neg le_const in
if LE_.Comb.is_empty le_comb then (
if A.Q.(le_const <> zero) then (
(* [c=0] when [c] is not 0 *)
let lit = SI.Lit.neg @@ SI.mk_lit si acts @@ A.mk_eq self.tst t1 t2 in
let pr = Pr.add_step self.proof @@ A.lemma_lra (Iter.return lit) in
SI.add_clause_permanent si acts [ lit ] pr
)
) else (
let v = var_encoding_comb ~pre:"le_local_eq" self le_comb in
try
let c1 = SimpSolver.Constraint.geq v le_const in
SimpSolver.add_constraint self.simplex c1 tag
~on_propagate:(on_propagate_ si acts);
let c2 = SimpSolver.Constraint.leq v le_const in
SimpSolver.add_constraint self.simplex c2 tag
~on_propagate:(on_propagate_ si acts)
with SimpSolver.E_unsat cert -> fail_with_cert si acts cert
)
let add_local_eq (self : state) si acts n1 n2 : unit =
let t1 = N.term n1 in
let t2 = N.term n2 in
add_local_eq_t self si acts t1 t2 ~tag:(Tag.CC_eq (n1, n2))
(* evaluate a term directly, as a variable *)
let eval_in_subst_ subst t =
match A.view_as_lra t with
| LRA_const n -> n
| _ -> Subst.eval subst t |> Option.value ~default:A.Q.zero
(* evaluate a linear expression *)
let eval_le_in_subst_ subst (le : LE.t) = LE.eval (eval_in_subst_ subst) le
(* FIXME: rename, this is more "provide_model_to_cc" *)
let do_th_combination (self : state) _si _acts : _ Iter.t =
Log.debug 1 "(lra.do-th-combinations)";
let model =
match self.last_res with
| Some (SimpSolver.Sat m) -> m
| _ -> assert false
in
let vals = Subst.to_iter model |> T.Tbl.of_iter in
(* also include terms that occur under function symbols, if they're
not in the model already *)
T.Tbl.iter
(fun t () ->
if not (T.Tbl.mem vals t) then (
let v = eval_in_subst_ model t in
T.Tbl.add vals t v
))
self.needs_th_combination;
(* also consider subterms that are linear expressions,
and evaluate them using the value of each variable
in that linear expression. For example a term [a + 2b]
is evaluated as [eval(a) + 2 × eval(b)]. *)
T.Tbl.iter
(fun t le ->
if not (T.Tbl.mem vals t) then (
let v = eval_le_in_subst_ model le in
T.Tbl.add vals t v
))
self.simp_defined;
(* return whole model *)
T.Tbl.to_iter vals |> Iter.map (fun (t, v) -> t, t_const self v)
(* partial checks is where we add literals from the trail to the
simplex. *)
let partial_check_ self si acts trail : unit =
Profile.with_ "lra.partial-check" @@ fun () ->
reset_res_ self;
let changed = ref false in
let examine_lit lit =
let sign = SI.Lit.sign lit in
let lit_t = SI.Lit.term lit in
match T.Tbl.get self.simp_preds lit_t, A.view_as_lra lit_t with
| Some (v, op, q), _ ->
Log.debugf 50 (fun k ->
k "(@[lra.partial-check.add@ :lit %a@ :lit-t %a@])" SI.Lit.pp lit
T.pp lit_t);
(* need to account for the literal's sign *)
let op =
if sign then
op
else
S_op.not_ op
in
(* assert new constraint to Simplex *)
let constr = SimpSolver.Constraint.mk v op q in
Log.debugf 10 (fun k ->
k "(@[lra.partial-check.assert@ %a@])" SimpSolver.Constraint.pp
constr);
changed := true;
(try
SimpSolver.add_var self.simplex v;
SimpSolver.add_constraint self.simplex constr (Tag.Lit lit)
~on_propagate:(on_propagate_ si acts)
with SimpSolver.E_unsat cert ->
Log.debugf 10 (fun k ->
k "(@[lra.partial-check.unsat@ :cert %a@])"
SimpSolver.Unsat_cert.pp cert);
fail_with_cert si acts cert)
| None, LRA_pred (Eq, t1, t2) when sign ->
add_local_eq_t self si acts t1 t2 ~tag:(Tag.Lit lit)
| None, LRA_pred (Neq, t1, t2) when not sign ->
add_local_eq_t self si acts t1 t2 ~tag:(Tag.Lit lit)
| None, _ -> ()
in
Iter.iter examine_lit trail;
(* incremental check *)
if !changed then ignore (check_simplex_ self si acts : SimpSolver.Subst.t);
()
let final_check_ (self : state) si (acts : SI.theory_actions)
(_trail : _ Iter.t) : unit =
Log.debug 5 "(th-lra.final-check)";
Profile.with_ "lra.final-check" @@ fun () ->
reset_res_ self;
(* add equalities between linear-expressions merged in the congruence closure *)
ST_exprs.iter_all self.st_exprs (fun (_, l) ->
Iter.diagonal_l l (fun (s1, s2) -> add_local_eq self si acts s1.n s2.n));
(* TODO: jiggle model to reduce the number of variables that
have the same value *)
let model = check_simplex_ self si acts in
Log.debugf 20 (fun k -> k "(@[lra.model@ %a@])" SimpSolver.Subst.pp model);
Log.debug 5 "(lra: solver returns SAT)";
()
(* help generating model *)
let model_ask_ (self : state) ~recurse:_ _si n : _ option =
let t = N.term n in
match self.last_res with
| Some (SimpSolver.Sat m) ->
Log.debugf 50 (fun k -> k "(@[lra.model-ask@ %a@])" T.pp t);
(match A.view_as_lra t with
| LRA_const n -> Some n (* always eval constants to themselves *)
| _ -> SimpSolver.V_map.get t m)
|> Option.map (t_const self)
| _ -> None
(* help generating model *)
let model_complete_ (self : state) _si ~add : unit =
Log.debugf 30 (fun k -> k "(lra.model-complete)");
match self.last_res with
| Some (SimpSolver.Sat m) when T.Tbl.length self.in_model > 0 ->
Log.debugf 50 (fun k ->
k "(@[lra.in_model@ %a@])" (Util.pp_iter T.pp)
(T.Tbl.keys self.in_model));
let add_t t () =
match SimpSolver.V_map.get t m with
| None -> ()
| Some u -> add t (t_const self u)
in
T.Tbl.iter add_t self.in_model
| _ -> ()
let k_state = SI.Registry.create_key ()
let create_and_setup si =
Log.debug 2 "(th-lra.setup)";
let stat = SI.stats si in
let st = create ~stat si in
SI.Registry.set (SI.registry si) k_state st;
SI.add_simplifier si (simplify st);
SI.on_preprocess si (preproc_lra st);
SI.on_final_check si (final_check_ st);
SI.on_partial_check si (partial_check_ st);
SI.on_model si ~ask:(model_ask_ st) ~complete:(model_complete_ st);
SI.on_cc_is_subterm si (fun (_, _, t) -> on_subterm st t);
SI.on_cc_pre_merge si (fun (cc, acts, n1, n2, expl) ->
match as_const_ (N.term n1), as_const_ (N.term n2) with
| Some q1, Some q2 when A.Q.(q1 <> q2) ->
(* classes with incompatible constants *)
Log.debugf 30 (fun k ->
k "(@[lra.merge-incompatible-consts@ %a@ %a@])" N.pp n1 N.pp n2);
SI.CC.raise_conflict_from_expl cc acts expl
| _ -> ());
SI.on_th_combination si (do_th_combination st);
st
let theory =
A.S.mk_theory ~name:"th-lra" ~create_and_setup ~push_level ~pop_levels ()
end