[sledge] Refactor QE in Sh.simplify to simplify Context interface

Summary:
The current implementation of quantifier elimination in Sh.simplify is
tightly coupled with the details of what the Context operations
support. In particular, successfully eliminating variables with
Context.elim effectively relies on being given a context that has been
transformed by Context.apply_subst. These operations are sound
independently, but achieving the desired result is delicate.

To simplify this situation, this diff refactors the tightly coupled
usage into a Context.apply_and_elim operation that hides the details
of the interaction inside the Context module. This enables an accurate
specification of apply_and_elim to be given much more simply than can
be done for the separate operations. This also simplifies the
implementation of Sh.simplify.

Reviewed By: jvillard

Differential Revision: D25756577

fbshipit-source-id: b344b3da6
master
Josh Berdine 4 years ago committed by Facebook GitHub Bot
parent d574b14dc7
commit 8f40a85cd8

@ -1270,6 +1270,22 @@ let elim xs r =
in in
(ks, {r with rep}) (ks, {r with rep})
let apply_and_elim ~wrt xs s r =
[%trace]
~call:(fun {pf} -> pf "%a%a@ %a" Var.Set.pp_xs xs Subst.pp s pp_raw r)
~retn:(fun {pf} (zs, r', ks) ->
pf "%a@ %a@ %a" Var.Set.pp_xs zs pp_raw r' Var.Set.pp_xs ks ;
assert (Var.Set.subset ks ~of_:xs) ;
assert (Var.Set.disjoint ks (fv r')) )
@@ fun () ->
if Subst.is_empty s then (Var.Set.empty, r, Var.Set.empty)
else
let zs, r = apply_subst wrt s r in
if is_unsat r then (Var.Set.empty, unsat, Var.Set.empty)
else
let ks, r = elim xs r in
(zs, r, ks)
(* (*
* Replay debugging * Replay debugging
*) *)
@ -1286,7 +1302,7 @@ type call =
| Normalize of t * Term.t | Normalize of t * Term.t
| Apply_subst of Var.Set.t * Subst.t * t | Apply_subst of Var.Set.t * Subst.t * t
| Solve_for_vars of Var.Set.t list * t | Solve_for_vars of Var.Set.t list * t
| Elim of Var.Set.t * t | Apply_and_elim of Var.Set.t * Var.Set.t * Subst.t * t
[@@deriving sexp] [@@deriving sexp]
let replay c = let replay c =
@ -1302,7 +1318,7 @@ let replay c =
| Normalize (r, e) -> normalize r e |> ignore | Normalize (r, e) -> normalize r e |> ignore
| Apply_subst (us, s, r) -> apply_subst us s r |> ignore | Apply_subst (us, s, r) -> apply_subst us s r |> ignore
| Solve_for_vars (vss, r) -> solve_for_vars vss r |> ignore | Solve_for_vars (vss, r) -> solve_for_vars vss r |> ignore
| Elim (ks, r) -> elim ks r |> ignore | Apply_and_elim (wrt, xs, s, r) -> apply_and_elim ~wrt xs s r |> ignore
(* Debug wrappers *) (* Debug wrappers *)
@ -1342,7 +1358,7 @@ let refutes_tmr = Timer.create "refutes" ~at_exit:report
let normalize_tmr = Timer.create "normalize" ~at_exit:report let normalize_tmr = Timer.create "normalize" ~at_exit:report
let apply_subst_tmr = Timer.create "apply_subst" ~at_exit:report let apply_subst_tmr = Timer.create "apply_subst" ~at_exit:report
let solve_for_vars_tmr = Timer.create "solve_for_vars" ~at_exit:report let solve_for_vars_tmr = Timer.create "solve_for_vars" ~at_exit:report
let elim_tmr = Timer.create "elim" ~at_exit:report let apply_and_elim_tmr = Timer.create "apply_and_elim" ~at_exit:report
let add us e r = let add us e r =
wrap add_tmr (fun () -> add us e r) (fun () -> Add (us, e, r)) wrap add_tmr (fun () -> add us e r) (fun () -> Add (us, e, r))
@ -1381,4 +1397,7 @@ let solve_for_vars vss r =
(fun () -> solve_for_vars vss r) (fun () -> solve_for_vars vss r)
(fun () -> Solve_for_vars (vss, r)) (fun () -> Solve_for_vars (vss, r))
let elim ks r = wrap elim_tmr (fun () -> elim ks r) (fun () -> Elim (ks, r)) let apply_and_elim ~wrt xs s r =
wrap apply_and_elim_tmr
(fun () -> apply_and_elim ~wrt xs s r)
(fun () -> Apply_and_elim (wrt, xs, s, r))

@ -108,12 +108,12 @@ val solve_for_vars : Var.Set.t list -> t -> Subst.t
terms [e] with free variables contained in as short a prefix of [uss] as terms [e] with free variables contained in as short a prefix of [uss] as
possible. *) possible. *)
val elim : Var.Set.t -> t -> Var.Set.t * t val apply_and_elim :
(** [elim vs x] is [(ks, y)] where [ks] is such that [vs ⊇ ks] and wrt:Var.Set.t -> Var.Set.t -> Subst.t -> t -> Var.Set.t * t * Var.Set.t
[ks fv y = ] and [y] is such that [vs-ks. y] is equivalent to (** Apply a solution substitution to eliminate the solved variables. That
[vs. x]. [elim] only removes terms from the existing representation, is, [apply_and_elim ~wrt vs s x] is [(zs, x', ks)] where
without performing any additional solving. This means that if a variable [zs. r' ks. s] is equivalent to [xs. r] where [zs] are
in [vs] occurs in an interpreted term in [x], it will not be eliminated. *) fresh with respect to [wrt] and [ks xs] and is maximal. *)
(**/**) (**/**)

@ -753,25 +753,19 @@ let remove_absent_xs ks q =
let ks = Var.Set.inter ks q.xs in let ks = Var.Set.inter ks q.xs in
if Var.Set.is_empty ks then q if Var.Set.is_empty ks then q
else else
let ks, ctx = Context.elim ks q.ctx in let xs = Var.Set.diff q.xs ks in
if Var.Set.is_empty ks then q let djns =
else let rec trim_ks ks djns =
let xs = Var.Set.diff q.xs ks in List.map_endo djns ~f:(fun djn ->
let djns = List.map_endo djn ~f:(fun sjn ->
let rec trim_ks ks djns = let us = Var.Set.diff sjn.us ks in
List.map djns ~f:(fun djn -> let djns = trim_ks ks sjn.djns in
List.map djn ~f:(fun sjn -> if us == sjn.us && djns == sjn.djns then sjn
let ks, ctx = Context.elim ks sjn.ctx in else {sjn with us; djns} ) )
if Var.Set.is_empty ks then sjn
else
{ sjn with
us= Var.Set.diff sjn.us ks
; ctx
; djns= trim_ks ks sjn.djns } ) )
in
trim_ks ks q.djns
in in
{q with xs; ctx; djns} trim_ks ks q.djns
in
{q with xs; djns}
let rec simplify_ us rev_xss q = let rec simplify_ us rev_xss q =
[%Trace.call fun {pf} -> pf "%a@ %a" pp_vss (List.rev rev_xss) pp_raw q] [%Trace.call fun {pf} -> pf "%a@ %a" pp_vss (List.rev rev_xss) pp_raw q]
@ -788,31 +782,32 @@ let rec simplify_ us rev_xss q =
) )
in in
(* try to solve equations in ctx for variables in xss *) (* try to solve equations in ctx for variables in xss *)
let subst = Context.solve_for_vars (us :: List.rev rev_xss) q.ctx in let xss = List.rev rev_xss in
let removed, q = let subst = Context.solve_for_vars (us :: xss) q.ctx in
if Context.Subst.is_empty subst then (Var.Set.empty, q) let union_xss = Var.Set.union_list rev_xss in
else let wrt = Var.Set.union us union_xss in
(* normalize wrt solutions *) let fresh, ctx, removed =
let q = norm subst q in Context.apply_and_elim ~wrt union_xss subst q.ctx
if is_false q then (Var.Set.empty, false_ q.us)
else
let removed, ctx =
Context.elim (Var.Set.union_list rev_xss) q.ctx
in
let q = {q with ctx} in
let removed =
Var.Set.diff removed (fv ~ignore_ctx:() (elim_exists q.xs q))
in
let keep, removed, _ =
Context.Subst.partition_valid removed subst
in
let q = and_subst keep q in
(removed, q)
in in
(* re-quantify existentials *) ( if Context.is_unsat ctx then false_ q.us
let q = exists xs q in else if Context.Subst.is_empty subst then exists xs q
(* remove the eliminated variables from xs and subformulas' us *) else
remove_absent_xs removed q (* normalize wrt solutions *)
let q = extend_us fresh q in
(* opt: ctx already normalized wrt subst, so just preserve it *)
let q = {(norm subst {q with ctx= Context.empty}) with ctx} in
if is_false q then false_ q.us
else
(* opt: removed already disjoint from ctx, so ignore it *)
let removed =
Var.Set.diff removed (fv ~ignore_ctx:() (elim_exists q.xs q))
in
let keep, removed, _ = Context.Subst.partition_valid removed subst in
let q = and_subst keep q in
(* (re)quantify existentials *)
let q = exists (Var.Set.union fresh xs) q in
(* remove the eliminated variables from xs and subformulas' us *)
remove_absent_xs removed q )
|> |>
[%Trace.retn fun {pf} q' -> [%Trace.retn fun {pf} q' ->
pf "%a@ %a" Context.Subst.pp subst pp_raw q' ; pf "%a@ %a" Context.Subst.pp subst pp_raw q' ;

Loading…
Cancel
Save