You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1350 lines
45 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
(** Equality over uninterpreted functions and linear rational arithmetic *)
open Exp
(* Solution Substitutions =================================================*)
module Subst : sig
type t = Trm.t Trm.Map.t [@@deriving compare, equal, sexp]
val pp : t pp
val pp_diff : (t * t) pp
val empty : t
val is_empty : t -> bool
val length : t -> int
val mem : Trm.t -> t -> bool
val find : Trm.t -> t -> Trm.t option
val fold : t -> 's -> f:(key:Trm.t -> data:Trm.t -> 's -> 's) -> 's
val fold_eqs : t -> 's -> f:(Fml.t -> 's -> 's) -> 's
val iteri : t -> f:(key:Trm.t -> data:Trm.t -> unit) -> unit
val for_alli : t -> f:(key:Trm.t -> data:Trm.t -> bool) -> bool
val apply : t -> Trm.t -> Trm.t
val subst_ : t -> Trm.t -> Trm.t
val subst : t -> Term.t -> Term.t
val norm : t -> Trm.t -> Trm.t
val compose : t -> t -> t
val compose1 : key:Trm.t -> data:Trm.t -> t -> t
val extend : Trm.t -> t -> t option
val map_entries : f:(Trm.t -> Trm.t) -> t -> t
val to_iter : t -> (Trm.t * Trm.t) iter
val to_list : t -> (Trm.t * Trm.t) list
val partition_valid : Var.Set.t -> t -> t * Var.Set.t * t
(* direct representation manipulation *)
val add : key:Trm.t -> data:Trm.t -> t -> t
val remove : Trm.t -> t -> t
end = struct
type t = Trm.t Trm.Map.t [@@deriving compare, equal, sexp_of]
let t_of_sexp = Trm.Map.t_of_sexp Trm.t_of_sexp
let pp = Trm.Map.pp Trm.pp Trm.pp
let pp_diff = Trm.Map.pp_diff ~eq:Trm.equal Trm.pp Trm.pp Trm.pp_diff
let empty = Trm.Map.empty
let is_empty = Trm.Map.is_empty
let length = Trm.Map.length
let mem = Trm.Map.mem
let find = Trm.Map.find
let fold = Trm.Map.fold
let fold_eqs s z ~f =
Trm.Map.fold ~f:(fun ~key ~data -> f (Fml.eq key data)) s z
let iteri = Trm.Map.iteri
let for_alli = Trm.Map.for_alli
let to_iter = Trm.Map.to_iter
let to_list = Trm.Map.to_list
(** look up a term in a substitution *)
let apply s a = Trm.Map.find a s |> Option.value ~default:a
let rec subst_ s a = apply s (Trm.map ~f:(subst_ s) a)
let subst s e = Term.map_trms ~f:(subst_ s) e
(** apply a substitution to maximal non-interpreted subterms *)
let rec norm s a =
[%trace]
~call:(fun {pf} -> pf "@ %a" Trm.pp a)
~retn:(fun {pf} -> pf "%a" Trm.pp)
@@ fun () ->
if Theory.is_interpreted a then Trm.map ~f:(norm s) a else apply s a
(** compose two substitutions *)
let compose r s =
[%Trace.call fun {pf} -> pf "@ %a@ %a" pp r pp s]
;
( if is_empty s then r
else
let r' = Trm.Map.map_endo ~f:(norm s) r in
Trm.Map.union_absent r' s )
|>
[%Trace.retn fun {pf} r' ->
pf "%a" pp_diff (r, r') ;
assert (r' == r || not (equal r' r))]
(** compose a substitution with a mapping *)
let compose1 ~key ~data r =
if Trm.equal key data then r
else (
assert (
Option.for_all ~f:(Trm.equal key) (Trm.Map.find key r)
|| fail "domains intersect: %a ↦ %a in %a" Trm.pp key Trm.pp data
pp r () ) ;
let s = Trm.Map.singleton key data in
let r' = Trm.Map.map_endo ~f:(norm s) r in
Trm.Map.add ~key ~data r' )
(** add an identity entry if the term is not already present *)
let extend e s =
let exception Found in
match
Trm.Map.update e s ~f:(function
| Some _ -> raise_notrace Found
| None -> e )
with
| exception Found -> None
| s -> Some s
(** map over a subst, applying [f] to both domain and range, requires that
[f] is injective and for any set of terms [E], [f\[E\]] is disjoint
from [E] *)
let map_entries ~f s =
Trm.Map.fold s s ~f:(fun ~key ~data s ->
let key' = f key in
let data' = f data in
if Trm.equal key' key then
if Trm.equal data' data then s else Trm.Map.add ~key ~data:data' s
else
let s = Trm.Map.remove key s in
match (key' : Trm.t) with
| Z _ | Q _ -> s
| _ -> Trm.Map.add_exn ~key:key' ~data:data' s )
(** Holds only if [true ⊢ ∃xs. e=f]. Clients assume
[not (is_valid_eq xs e f)] implies [not (is_valid_eq ys e f)] for
[ys ⊆ xs]. *)
let is_valid_eq xs e f =
let is_var_in xs e =
Option.exists ~f:(fun x -> Var.Set.mem x xs) (Var.of_trm e)
in
( is_var_in xs e
|| is_var_in xs f
|| Theory.is_uninterpreted e
&& Iter.exists ~f:(is_var_in xs) (Trm.trms e)
|| Theory.is_uninterpreted f
&& Iter.exists ~f:(is_var_in xs) (Trm.trms f) )
$> fun b ->
[%Trace.info
"is_valid_eq %a%a=%a = %b" Var.Set.pp_xs xs Trm.pp e Trm.pp f b]
(** Partition ∃xs. σ into equivalent ∃xs. τ ∧ ∃ks. ν where ks
and ν are maximal where ∃ks. ν is universally valid, xs ⊇ ks and
ks ∩ fv(τ) = ∅. *)
let partition_valid xs s =
[%trace]
~call:(fun {pf} -> pf "@ @[%a@ %a@]" Var.Set.pp_xs xs pp s)
~retn:(fun {pf} (t, ks, u) ->
pf "%a@ %a@ %a" pp t Var.Set.pp_xs ks pp u )
@@ fun () ->
(* Move equations e=f from s to t when ∃ks.e=f fails to be provably
valid. When moving an equation, reduce ks by fv(e=f) to maintain ks ∩
fv(t) = ∅. This reduction may cause equations in s to no longer be
valid, so loop until no change. *)
let rec partition_valid_ t ks s =
let t', ks', s' =
Trm.Map.fold s (t, ks, s) ~f:(fun ~key ~data (t, ks, s) ->
if is_valid_eq ks key data then (t, ks, s)
else
let t = Trm.Map.add ~key ~data t
and ks =
Var.Set.diff ks (Var.Set.union (Trm.fv key) (Trm.fv data))
and s = Trm.Map.remove key s in
(t, ks, s) )
in
if s' != s then partition_valid_ t' ks' s' else (t', ks', s')
in
if Var.Set.is_empty xs then (s, Var.Set.empty, empty)
else partition_valid_ empty xs s
(* direct representation manipulation *)
let add = Trm.Map.add
let remove = Trm.Map.remove
end
(* Equality classes =======================================================*)
module Cls : sig
type t [@@deriving compare, equal, sexp]
val empty : t
val add : Trm.t -> t -> t
val remove : Trm.t -> t -> t
val union : t -> t -> t
val is_empty : t -> bool
val pop : t -> (Trm.t * t) option
val filter : t -> f:(Trm.t -> bool) -> t
val partition : t -> f:(Trm.t -> bool) -> t * t
val fold : t -> 's -> f:(Trm.t -> 's -> 's) -> 's
val map : t -> f:(Trm.t -> Trm.t) -> t
val to_iter : t -> Trm.t iter
val to_set : t -> Trm.Set.t
val of_set : Trm.Set.t -> t
val ppx : Trm.Var.strength -> t pp
val pp : t pp
val pp_raw : t pp
val pp_diff : (t * t) pp
end = struct
type t = Trm.t list [@@deriving compare, equal, sexp]
let empty = []
let add = List.cons
let remove = List.remove ~eq:Trm.equal
let union = List.rev_append
let is_empty = List.is_empty
let pop = function [] -> None | x :: xs -> Some (x, xs)
let filter = List.filter
let partition = List.partition
let fold = List.fold
let map = List.map_endo
let to_iter = List.to_iter
let to_set = Trm.Set.of_list
let of_set s = Iter.to_list (Trm.Set.to_iter s)
let ppx x fs es =
List.pp "@ = " (Trm.ppx x) fs (List.sort_uniq ~cmp:Trm.compare es)
let pp = ppx (fun _ -> None)
let pp_raw fs es =
Trm.Set.pp_full ~pre:"{@[" ~suf:"@]}" ~sep:",@ " Trm.pp fs (to_set es)
let pp_diff = List.pp_diff ~cmp:Trm.compare "@ = " Trm.pp
end
(* Conjunctions of atomic formula assumptions =============================*)
(** see also [invariant] *)
type t =
{ xs: Var.Set.t
(** existential variables that did not appear in input formulas *)
; sat: bool (** [false] only if constraints are inconsistent *)
; rep: Subst.t
(** functional set of oriented equations: map [a] to [a'],
indicating that [a = a'] holds, and that [a'] is the
'rep(resentative)' of [a] *)
; cls: Cls.t Trm.Map.t
(** map each representative to the set of terms in its class *)
; pnd: (Trm.t * Trm.t) list
(** pending equations to add (once invariants are reestablished) *)
}
[@@deriving compare, equal, sexp]
(* Pretty-printing ========================================================*)
let pp_eq fs (e, f) = Format.fprintf fs "@[%a = %a@]" Trm.pp e Trm.pp f
let pp_raw fs {sat; rep; cls; pnd} =
let pp_alist pp_k pp_v fs alist =
let pp_assoc fs (k, v) =
Format.fprintf fs "[@[%a@ @<2>↦ %a@]]" pp_k k pp_v (k, v)
in
Format.fprintf fs "[@[<hv>%a@]]" (List.pp ";@ " pp_assoc) alist
in
let pp_trm_v fs (k, v) = if not (Trm.equal k v) then Trm.pp fs v in
let pp_cls_v fs (_, cls) = Cls.pp_raw fs cls in
let pp_pnd fs pnd =
if not (List.is_empty pnd) then
Format.fprintf fs ";@ pnd= @[%a@]" (List.pp ";@ " pp_eq) pnd
in
Format.fprintf fs "@[{@[<hv>sat= %b;@ rep= %a;@ cls= %a%a@]}@]" sat
(pp_alist Trm.pp pp_trm_v)
(Subst.to_list rep)
(pp_alist Trm.pp pp_cls_v)
(Trm.Map.to_list cls) pp_pnd pnd
let pp_diff fs (r, s) =
let pp_sat fs =
if not (Bool.equal r.sat s.sat) then
Format.fprintf fs "sat= @[-- %b@ ++ %b@];@ " r.sat s.sat
in
let pp_rep fs =
if not (Subst.is_empty r.rep) then
Format.fprintf fs "rep= %a;@ " Subst.pp_diff (r.rep, s.rep)
in
let pp_cls fs =
if not (Trm.Map.equal Cls.equal r.cls s.cls) then
Format.fprintf fs "cls= %a;@ "
(Trm.Map.pp_diff ~eq:Cls.equal Trm.pp Cls.pp_raw Cls.pp_diff)
(r.cls, s.cls)
in
let pp_pnd fs =
List.pp_diff ~cmp:[%compare: Trm.t * Trm.t] ~pre:"pnd= @[" ~suf:"@]"
";@ " pp_eq fs (r.pnd, s.pnd)
in
Format.fprintf fs "@[{@[<hv>%t%t%t%t@]}@]" pp_sat pp_rep pp_cls pp_pnd
let ppx_classes x fs clss =
List.pp "@ @<2>∧ "
(fun fs (rep, cls) ->
if not (Cls.is_empty cls) then
Format.fprintf fs "@[%a@ = %a@]" (Trm.ppx x) rep (Cls.ppx x) cls )
fs (Trm.Map.to_list clss)
let pp_classes fs r = ppx_classes (fun _ -> None) fs r.cls
let pp fs r =
if Trm.Map.is_empty r.cls then
Format.fprintf fs (if r.sat then "tt" else "ff")
else pp_classes fs r
let ppx var_strength fs clss noneqs =
let without_anon_vars es =
Cls.filter es ~f:(fun e ->
match Var.of_trm e with
| Some v -> Poly.(var_strength v <> Some `Anonymous)
| None -> true )
in
let clss =
Trm.Map.fold clss Trm.Map.empty ~f:(fun ~key:rep ~data:cls m ->
let cls = without_anon_vars cls in
if not (Cls.is_empty cls) then Trm.Map.add ~key:rep ~data:cls m
else m )
in
let first = Trm.Map.is_empty clss in
if not first then Format.fprintf fs " " ;
ppx_classes var_strength fs clss ;
List.pp
~pre:(if first then "@[ " else "@ @[@<2>∧ ")
"@ @<2>∧ " (Fml.ppx var_strength) fs noneqs ~suf:"@]" ;
first && List.is_empty noneqs
let pp_diff_cls = Trm.Map.pp_diff ~eq:Cls.equal Trm.pp Cls.pp Cls.pp_diff
(* Basic representation queries ===========================================*)
let trms r =
Iter.flat_map ~f:(fun (k, v) -> Iter.doubleton k v) (Subst.to_iter r.rep)
let vars r = Iter.flat_map ~f:Trm.vars (trms r)
let fv r = Var.Set.of_iter (vars r)
(** test membership in carrier *)
let in_car r e = Subst.mem e r.rep
(** congruent specialized to assume subterms of [a'] are [Subst.norm]alized
wrt [r] (or canonized) *)
let semi_congruent r a' b = Trm.equal a' (Trm.map ~f:(Subst.norm r.rep) b)
(** terms are congruent if equal after normalizing subterms *)
let congruent r a b = semi_congruent r (Trm.map ~f:(Subst.norm r.rep) a) b
(* Invariant ==============================================================*)
let pre_invariant r =
let@ () = Invariant.invariant [%here] r [%sexp_of: t] in
Subst.iteri r.rep ~f:(fun ~key:trm ~data:rep ->
(* no interpreted terms in carrier *)
assert (
(not (Theory.is_interpreted trm))
|| fail "non-interp %a" Trm.pp trm () ) ;
(* carrier is closed under subterms *)
Iter.iter (Trm.trms trm) ~f:(fun subtrm ->
assert (
Theory.is_interpreted subtrm
|| (match subtrm with Z _ | Q _ -> true | _ -> false)
|| in_car r subtrm
|| fail "@[subterm %a@ of %a@ not in carrier of@ %a@]" Trm.pp
subtrm Trm.pp trm pp r () ) ) ;
(* rep is idempotent *)
assert (
let rep' = Subst.norm r.rep rep in
Trm.equal rep rep'
|| fail "not idempotent: %a != %a in@ %a" Trm.pp rep Trm.pp rep'
Subst.pp r.rep () ) ;
(* every term is in the class of its rep *)
assert (
Trm.equal trm rep
|| Trm.Set.mem trm
(Cls.to_set
(Trm.Map.find rep r.cls |> Option.value ~default:Cls.empty))
|| fail "%a not in cls of %a = {%a}@ %a" Trm.pp trm Trm.pp rep
Cls.pp
(Trm.Map.find rep r.cls |> Option.value ~default:Cls.empty)
pp_raw r () ) ) ;
Trm.Map.iteri r.cls ~f:(fun ~key:rep ~data:cls ->
(* each class does not include its rep *)
assert (not (Trm.Set.mem rep (Cls.to_set cls))) ;
(* representative of every element of [rep]'s class is [rep] *)
Iter.iter (Cls.to_iter cls) ~f:(fun elt ->
assert (Option.exists ~f:(Trm.equal rep) (Subst.find elt r.rep)) ) )
let invariant r =
let@ () = Invariant.invariant [%here] r [%sexp_of: t] in
assert (List.is_empty r.pnd) ;
pre_invariant r ;
assert (
(not r.sat)
|| Subst.for_alli r.rep ~f:(fun ~key:a ~data:a' ->
Subst.for_alli r.rep ~f:(fun ~key:b ~data:b' ->
Trm.compare a b >= 0
|| (not (congruent r a b))
|| Trm.equal a' b'
|| fail "not congruent %a@ %a@ in@ %a" Trm.pp a Trm.pp b pp r
() ) ) )
(* Extending the carrier ==================================================*)
let rec extend_ a s =
[%trace]
~call:(fun {pf} -> pf "@ %a@ %a" Trm.pp a Subst.pp s)
~retn:(fun {pf} s' -> pf "%a" Subst.pp_diff (s, s'))
@@ fun () ->
match (a : Trm.t) with
| Z _ | Q _ -> s
| _ -> (
if Theory.is_interpreted a then Iter.fold ~f:extend_ (Trm.trms a) s
else
(* add uninterpreted terms *)
match Subst.extend a s with
(* and their subterms if newly added *)
| Some s -> Iter.fold ~f:extend_ (Trm.trms a) s
| None -> s )
(** add a term to the carrier *)
let extend a x =
[%trace]
~call:(fun {pf} -> pf "@ %a@ %a" Trm.pp a pp x)
~retn:(fun {pf} x' ->
pf "%a" pp_diff (x, x') ;
pre_invariant x' )
@@ fun () ->
let rep = extend_ a x.rep in
if rep == x.rep then x else {x with rep}
(* Propagation ============================================================*)
(** add a=a' to x using a' as the representative *)
let propagate1 (a, a') x =
[%trace]
~call:(fun {pf} -> pf "@ @[%a ↦ %a@]@ %a" Trm.pp a Trm.pp a' pp_raw x)
~retn:(fun {pf} -> pf "%a" pp_raw)
@@ fun () ->
(* pending equations need not be between terms in the carrier *)
let x = extend a (extend a' x) in
let s = Trm.Map.singleton a a' in
Trm.Map.fold x.rep x ~f:(fun ~key:_ ~data:b0' x ->
let b' = Subst.norm s b0' in
if b' == b0' then x
else
let b0'_cls, cls =
Trm.Map.find_and_remove b0' x.cls
|> Option.value ~default:(Cls.empty, x.cls)
in
let b0'_cls, pnd =
if Theory.is_interpreted b0' then (b0'_cls, (b0', b') :: x.pnd)
else (Cls.add b0' b0'_cls, x.pnd)
in
let rep =
Cls.fold b0'_cls x.rep ~f:(fun c rep ->
Trm.Map.add ~key:c ~data:b' rep )
in
let cls =
Trm.Map.update b' cls ~f:(fun b'_cls ->
Cls.union b0'_cls (Option.value b'_cls ~default:Cls.empty) )
in
{x with rep; cls; pnd} )
let solve ~wrt ~xs d e pending =
[%trace]
~call:(fun {pf} -> pf "@ %a@ %a" Trm.pp d Trm.pp e)
~retn:(fun {pf} -> pf "%a" Theory.pp)
@@ fun () ->
Theory.solve d e
{wrt; no_fresh= false; fresh= xs; solved= Some []; pending}
let rec propagate ~wrt x =
[%trace]
~call:(fun {pf} -> pf "@ %a" pp_raw x)
~retn:(fun {pf} -> pf "%a" pp_raw)
@@ fun () ->
match x.pnd with
| (a, b) :: pnd -> (
let a' = Subst.norm x.rep a in
let b' = Subst.norm x.rep b in
match solve ~wrt ~xs:x.xs a' b' pnd with
| {solved= Some solved; wrt; fresh; pending} ->
let xs = Var.Set.union x.xs fresh in
let x = {x with xs; pnd= pending} in
propagate ~wrt (List.fold ~f:propagate1 solved x)
| {solved= None} -> {x with sat= false; pnd= []} )
| [] -> x
(* Core operations ========================================================*)
let empty =
let rep = Subst.empty in
{xs= Var.Set.empty; sat= true; rep; cls= Trm.Map.empty; pnd= []}
|> check invariant
let unsat = {empty with sat= false}
(** [lookup r a] is [b'] if [a ~ b = b'] for some equation [b = b'] in rep *)
let lookup r a =
([%Trace.call fun {pf} -> pf "@ %a" Trm.pp a]
;
Iter.find_map (Subst.to_iter r.rep) ~f:(fun (b, b') ->
Option.return_if (semi_congruent r a b) b' )
|> Option.value ~default:a)
|>
[%Trace.retn fun {pf} -> pf "%a" Trm.pp]
(** rewrite a term into canonical form using rep and, for non-interpreted
terms, congruence composed with rep *)
let rec canon r a =
[%Trace.call fun {pf} -> pf "@ %a" Trm.pp a]
;
( match Theory.classify a with
| InterpAtom -> a
| NonInterpAtom -> Subst.apply r.rep a
| InterpApp -> Trm.map ~f:(canon r) a
| UninterpApp -> (
let a' = Trm.map ~f:(canon r) a in
match Theory.classify a' with
| InterpAtom | InterpApp -> a'
| NonInterpAtom -> Subst.apply r.rep a'
| UninterpApp -> lookup r a' ) )
|>
[%Trace.retn fun {pf} -> pf "%a" Trm.pp]
let canon_f r b =
[%trace]
~call:(fun {pf} -> pf "@ %a@ %a" Fml.pp b pp_raw r)
~retn:(fun {pf} -> pf "%a" Fml.pp)
@@ fun () -> Fml.map_trms ~f:(canon r) b
let merge ~wrt a b x =
[%trace]
~call:(fun {pf} -> pf "@ %a@ %a@ %a" Trm.pp a Trm.pp b pp x)
~retn:(fun {pf} x' ->
pf "%a" pp_diff (x, x') ;
pre_invariant x' )
@@ fun () ->
let x = {x with pnd= (a, b) :: x.pnd} in
propagate ~wrt x
(** find an unproved equation between congruent terms *)
let find_missing r =
Iter.find_map (Subst.to_iter r.rep) ~f:(fun (a, a') ->
let a_subnorm = Trm.map ~f:(Subst.norm r.rep) a in
Iter.find_map (Subst.to_iter r.rep) ~f:(fun (b, b') ->
(* need to equate a' and b'? *)
let need_a'_eq_b' =
(* optimize: do not consider both a = b and b = a *)
Trm.compare a b < 0
(* a and b are not already equal *)
&& (not (Trm.equal a' b'))
(* a and b are congruent *)
&& semi_congruent r a_subnorm b
in
Option.return_if need_a'_eq_b' (a', b') ) )
let rec close ~wrt x =
if not x.sat then x
else
match find_missing x with
| Some (a', b') -> close ~wrt (merge ~wrt a' b' x)
| None -> x
let close ~wrt r =
[%Trace.call fun {pf} -> pf "@ %a" pp r]
;
close ~wrt r
|>
[%Trace.retn fun {pf} r' ->
pf "%a" pp_diff (r, r') ;
invariant r']
let and_eq_ ~wrt a b x =
[%trace]
~call:(fun {pf} -> pf "@ @[%a = %a@]@ %a" Trm.pp a Trm.pp b pp x)
~retn:(fun {pf} x' ->
pf "%a" pp_diff (x, x') ;
invariant x' )
@@ fun () ->
if not x.sat then x
else
let x0 = x in
let a' = canon x a in
let b' = canon x b in
if Trm.equal a' b' then extend a' (extend b' x)
else
let x = merge ~wrt a' b' x in
match (a, b) with
| (Var _ as v), _ when not (in_car x0 v) -> x
| _, (Var _ as v) when not (in_car x0 v) -> x
| _ -> close ~wrt x
let extract_xs r = (r.xs, {r with xs= Var.Set.empty})
(* Exposed interface ======================================================*)
let is_empty {sat; rep} =
sat && Subst.for_alli rep ~f:(fun ~key:a ~data:a' -> Trm.equal a a')
let is_unsat {sat} = not sat
let implies r b =
[%Trace.call fun {pf} -> pf "@ %a@ %a" Fml.pp b pp r]
;
Fml.equal Fml.tt (canon_f r b)
|>
[%Trace.retn fun {pf} -> pf "%b"]
let refutes r b = Fml.equal Fml.ff (canon_f r b)
let normalize r e = Term.map_trms ~f:(canon r) e
let cls_of r e =
let e' = Subst.apply r.rep e in
Cls.add e' (Trm.Map.find e' r.cls |> Option.value ~default:Cls.empty)
let class_of r e =
match Term.get_trm (normalize r e) with
| Some e' ->
Iter.to_list (Iter.map ~f:Term.of_trm (Cls.to_iter (cls_of r e')))
| None -> []
let diff_classes r s =
Trm.Map.filter_mapi r.cls ~f:(fun ~key:rep ~data:cls ->
let cls' =
Cls.filter cls ~f:(fun exp -> not (implies s (Fml.eq rep exp)))
in
if Cls.is_empty cls' then None else Some cls' )
let ppx_diff var_strength fs parent_ctx fml ctx =
let fml' = canon_f ctx fml in
ppx var_strength fs
(diff_classes ctx parent_ctx)
(if Fml.(equal tt fml') then [] else [fml'])
let fold_uses_of r t s ~f =
let rec fold_ e s ~f =
let s =
Iter.fold (Trm.trms e) s ~f:(fun sub s ->
fold_ ~f sub (if Trm.equal t sub then f e s else s) )
in
if Theory.is_interpreted e then Iter.fold ~f:(fold_ ~f) (Trm.trms e) s
else s
in
Subst.fold r.rep s ~f:(fun ~key:trm ~data:rep s ->
fold_ ~f trm (fold_ ~f rep s) )
let iter_uses_of t r ~f = fold_uses_of r t () ~f:(fun use () -> f use)
let uses_of t r = Iter.from_labelled_iter (iter_uses_of t r)
let apply_subst wrt s r =
[%Trace.call fun {pf} -> pf "@ %a@ %a" Subst.pp s pp r]
;
( if Subst.is_empty s then r
else
Trm.Map.fold r.cls {r with rep= Subst.empty; cls= Trm.Map.empty}
~f:(fun ~key:rep ~data:cls r ->
let rep' = Subst.subst_ s rep in
Cls.fold cls r ~f:(fun trm r ->
let trm' = Subst.subst_ s trm in
and_eq_ ~wrt trm' rep' r ) ) )
|> extract_xs
|>
[%Trace.retn fun {pf} (xs, r') ->
pf "%a%a" Var.Set.pp_xs xs pp_diff (r, r') ;
invariant r']
let union wrt r s =
[%Trace.call fun {pf} -> pf "@ @[<hv 1> %a@ @<2>∧ %a@]" pp r pp s]
;
( if not r.sat then r
else if not s.sat then s
else
let s, r =
if Subst.length s.rep <= Subst.length r.rep then (s, r) else (r, s)
in
Subst.fold s.rep r ~f:(fun ~key:e ~data:e' r -> and_eq_ ~wrt e e' r) )
|> extract_xs
|>
[%Trace.retn fun {pf} (_, r') ->
pf "%a" pp_diff (r, r') ;
invariant r']
let inter wrt r s =
[%Trace.call fun {pf} -> pf "@ @[<hv 1> %a@ @<2> %a@]" pp r pp s]
;
( if not s.sat then r
else if not r.sat then s
else
let merge_mems rs r s =
Trm.Map.fold s.cls rs ~f:(fun ~key:rep ~data:cls rs ->
Cls.fold cls
([rep], rs)
~f:(fun exp (reps, rs) ->
match
List.find ~f:(fun rep -> implies r (Fml.eq exp rep)) reps
with
| Some rep -> (reps, and_eq_ ~wrt exp rep rs)
| None -> (exp :: reps, rs) )
|> snd )
in
let rs = empty in
let rs = merge_mems rs r s in
let rs = merge_mems rs s r in
rs )
|> extract_xs
|>
[%Trace.retn fun {pf} (_, r') ->
pf "%a" pp_diff (r, r') ;
invariant r']
let interN us rs =
match rs with
| [] -> (us, unsat)
| r :: rs -> List.fold ~f:(fun r (us, s) -> inter us s r) rs (us, r)
let rec add_ wrt b r =
match (b : Fml.t) with
| Tt -> r
| Not Tt -> unsat
| And {pos; neg} -> Fml.fold_pos_neg ~f:(add_ wrt) ~pos ~neg r
| Eq (d, e) -> and_eq_ ~wrt d e r
| Eq0 e -> and_eq_ ~wrt Trm.zero e r
| Pos _ | Not _ | Or _ | Iff _ | Cond _ | Lit _ -> r
let add us b r =
[%Trace.call fun {pf} -> pf "@ %a@ %a" Fml.pp b pp r]
;
add_ us b r |> extract_xs
|>
[%Trace.retn fun {pf} (_, r') ->
pf "%a" pp_diff (r, r') ;
invariant r']
let dnf f =
let meet1 a (vs, p, x) =
let vs, x = add vs a x in
(vs, Fml.and_ p a, x)
in
let join1 = Iter.cons in
let top = (Var.Set.empty, Fml.tt, empty) in
let bot = Iter.empty in
Fml.fold_dnf ~meet1 ~join1 ~top ~bot f
let rename x sub =
[%trace]
~call:(fun {pf} -> pf "@ @[%a@]@ %a" Var.Subst.pp sub pp x)
~retn:(fun {pf} x' ->
pf "%a" pp_diff (x, x') ;
invariant x' )
@@ fun () ->
let apply_sub = Trm.map_vars ~f:(Var.Subst.apply sub) in
let rep = Subst.map_entries ~f:apply_sub x.rep in
let cls =
Trm.Map.fold x.cls x.cls ~f:(fun ~key:a0' ~data:a0'_cls cls ->
let a' = apply_sub a0' in
let a'_cls = Cls.map ~f:apply_sub a0'_cls in
Trm.Map.add ~key:a' ~data:a'_cls
(if a' == a0' then cls else Trm.Map.remove a0' cls) )
in
if rep == x.rep && cls == x.cls then x else {x with rep; cls}
let trivial vs r =
[%trace]
~call:(fun {pf} -> pf "@ %a@ %a" Var.Set.pp_xs vs pp_raw r)
~retn:(fun {pf} ks -> pf "%a" Var.Set.pp_xs ks)
@@ fun () ->
Var.Set.fold vs Var.Set.empty ~f:(fun v ks ->
let x = Trm.var v in
match Subst.find x r.rep with
| None -> Var.Set.add v ks
| Some x' when Trm.equal x x' && Iter.is_empty (uses_of x r) ->
Var.Set.add v ks
| _ -> ks )
let trim ks x =
[%trace]
~call:(fun {pf} -> pf "@ %a@ %a" Var.Set.pp_xs ks pp_raw x)
~retn:(fun {pf} x' ->
pf "%a" pp_raw x' ;
invariant x' ;
assert (Var.Set.disjoint ks (fv x')) )
@@ fun () ->
(* expand classes to include reps *)
let reps =
Subst.fold x.rep Trm.Set.empty ~f:(fun ~key:_ ~data:rep reps ->
Trm.Set.add rep reps )
in
let clss =
Trm.Set.fold reps x.cls ~f:(fun rep clss ->
Trm.Map.update rep clss ~f:(fun cls0 ->
Cls.add rep (Option.value cls0 ~default:Cls.empty) ) )
in
(* enumerate expanded classes and update solution subst *)
let kills = Trm.Set.of_vars ks in
Trm.Map.fold clss x ~f:(fun ~key:a' ~data:ecls x ->
(* remove mappings for non-rep class elements to kill *)
let keep, drop = Trm.Set.diff_inter (Cls.to_set ecls) kills in
if Trm.Set.is_empty drop then x
else
let rep = Trm.Set.fold ~f:Subst.remove drop x.rep in
let x = {x with rep} in
(* new class is keepers without rep *)
let keep' = Trm.Set.remove a' keep in
let ecls = Cls.of_set keep' in
if keep' != keep then
(* a' is to be kept: continue to use it as rep *)
let cls =
if Cls.is_empty ecls then Trm.Map.remove a' x.cls
else Trm.Map.add ~key:a' ~data:ecls x.cls
in
{x with cls}
else
(* a' is to be removed: choose new rep from the keepers *)
let cls = Trm.Map.remove a' x.cls in
let x = {x with cls} in
match
Trm.Set.reduce keep ~f:(fun x y ->
if Theory.prefer x y < 0 then x else y )
with
| Some b' ->
(* add mappings from each keeper to the new representative *)
let rep =
Trm.Set.fold keep x.rep ~f:(fun elt rep ->
Subst.add ~key:elt ~data:b' rep )
in
(* add trimmed class to new rep *)
let cls =
if Cls.is_empty ecls then x.cls
else Trm.Map.add ~key:b' ~data:ecls x.cls
in
{x with rep; cls}
| None ->
(* entire class removed *)
x )
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 ;
invariant r' ;
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 = trivial xs r in
let r = trim ks r in
(zs, r, ks)
(* Existential Witnessing and Elimination =================================*)
let rec max_solvables e =
if not (Theory.is_interpreted e) then Iter.return e
else Iter.flat_map ~f:max_solvables (Trm.trms e)
let fold_max_solvables e s ~f = Iter.fold ~f (max_solvables e) s
let subst_invariant us s0 s =
assert (s0 == s || not (Subst.equal s0 s)) ;
assert (
Subst.iteri s ~f:(fun ~key ~data ->
(* dom of new entries not ito us *)
assert (
Option.for_all ~f:(Trm.equal data) (Subst.find key s0)
|| not (Var.Set.subset (Trm.fv key) ~of_:us) ) ;
(* rep not ito us implies trm not ito us *)
assert (
Var.Set.subset (Trm.fv data) ~of_:us
|| not (Var.Set.subset (Trm.fv key) ~of_:us) ) ) ;
true )
type 'a zom = Zero | One of 'a | Many
(** try to solve [p = q] such that [fv (p - q) ⊆ us xs] and [p - q]
has at most one maximal solvable subterm, [kill], where
[fv kill ⊈ us]; solve [p = q] for [kill]; extend subst mapping [kill]
to the solution *)
let solve_poly_eq us p' q' subst =
[%Trace.call fun {pf} -> pf "@ %a = %a" Trm.pp p' Trm.pp q']
;
let diff = Trm.sub p' q' in
let max_solvables_not_ito_us =
fold_max_solvables diff Zero ~f:(fun solvable_subterm -> function
| Many -> Many
| zom when Var.Set.subset (Trm.fv solvable_subterm) ~of_:us -> zom
| One _ -> Many
| Zero -> One solvable_subterm )
in
( match max_solvables_not_ito_us with
| One kill ->
let+ kill, keep = Trm.Arith.solve_zero_eq diff ~for_:kill in
Subst.compose1 ~key:(Trm.arith kill) ~data:(Trm.arith keep) subst
| Many | Zero -> None )
|>
[%Trace.retn fun {pf} subst' ->
pf "@[%a@]" Subst.pp_diff (subst, Option.value subst' ~default:subst)]
let rec solve_pending (s : Theory.t) soln =
match s.pending with
| (a, b) :: pending -> (
let a' = Subst.norm soln a in
let b' = Subst.norm soln b in
match Theory.solve a' b' {s with pending} with
| {solved= Some solved} as s ->
solve_pending {s with solved= Some []}
(List.fold solved soln ~f:(fun (trm, rep) soln ->
Subst.compose1 ~key:trm ~data:rep soln ))
| {solved= None} -> None )
| [] -> Some soln
let solve_seq_eq us e' f' subst =
[%Trace.call fun {pf} -> pf "@ %a = %a" Trm.pp e' Trm.pp f']
;
let x_ito_us x u =
(not (Var.Set.subset (Trm.fv x) ~of_:us))
&& Var.Set.subset (Trm.fv u) ~of_:us
in
let solve_concat ms n a =
let a, n =
match Trm.seq_size a with
| Some n -> (a, n)
| None -> (Trm.sized ~siz:n ~seq:a, n)
in
solve_pending
(Theory.solve_concat ms a n
{ wrt= Var.Set.empty
; no_fresh= true
; fresh= Var.Set.empty
; solved= Some []
; pending= [] })
subst
in
( match ((e' : Trm.t), (f' : Trm.t)) with
| (Concat ms as c), a when x_ito_us c a ->
solve_concat ms (Trm.seq_size_exn c) a
| a, (Concat ms as c) when x_ito_us c a ->
solve_concat ms (Trm.seq_size_exn c) a
| (Sized {seq= Var _ as v} as m), u when x_ito_us m u ->
Some (Subst.compose1 ~key:v ~data:u subst)
| u, (Sized {seq= Var _ as v} as m) when x_ito_us m u ->
Some (Subst.compose1 ~key:v ~data:u subst)
| _ -> None )
|>
[%Trace.retn fun {pf} subst' ->
pf "@[%a@]" Subst.pp_diff (subst, Option.value subst' ~default:subst)]
let solve_interp_eq us e' (cls, subst) =
[%Trace.call fun {pf} ->
pf "@ trm: @[%a@]@ cls: @[%a@]@ subst: @[%a@]" Trm.pp e' Cls.pp cls
Subst.pp subst]
;
Iter.find_map (Cls.to_iter cls) ~f:(fun f ->
let f' = Subst.norm subst f in
match solve_seq_eq us e' f' subst with
| Some subst -> Some subst
| None -> solve_poly_eq us e' f' subst )
|>
[%Trace.retn fun {pf} subst' ->
pf "@[%a@]" Subst.pp_diff (subst, Option.value subst' ~default:subst) ;
Option.iter ~f:(subst_invariant us subst) subst']
(** move equations from [cls] to [subst] which are between interpreted terms
and can be expressed, after normalizing with [subst], as [x ↦ u] where
[us xs ⊇ fv x ⊈ us] and [fv u ⊆ us] or else
[fv u ⊆ us xs] *)
let rec solve_interp_eqs us (cls, subst) =
[%Trace.call fun {pf} ->
pf "@ cls: @[%a@]@ subst: @[%a@]" Cls.pp cls Subst.pp subst]
;
let rec solve_interp_eqs_ cls' (cls, subst) =
match Cls.pop cls with
| None -> (cls', subst)
| Some (trm, cls) ->
let trm' = Subst.norm subst trm in
if Theory.is_interpreted trm' then
match solve_interp_eq us trm' (cls, subst) with
| Some subst -> solve_interp_eqs_ cls' (cls, subst)
| None -> solve_interp_eqs_ (Cls.add trm' cls') (cls, subst)
else solve_interp_eqs_ (Cls.add trm' cls') (cls, subst)
in
let cls', subst' = solve_interp_eqs_ Cls.empty (cls, subst) in
( if subst' != subst then solve_interp_eqs us (cls', subst')
else (cls', subst') )
|>
[%Trace.retn fun {pf} (cls', subst') ->
pf "cls: @[%a@]@ subst: @[%a@]" Cls.pp_diff (cls, cls') Subst.pp_diff
(subst, subst')]
type cls_solve_state =
{ rep_us: Trm.t option (** rep, that is ito us, for class *)
; cls_us: Cls.t (** cls that is ito us, or interpreted *)
; rep_xs: Trm.t option (** rep, that is *not* ito us, for class *)
; cls_xs: Cls.t (** cls that is *not* ito us *) }
let dom_trm e =
match (e : Trm.t) with
| Sized {seq= Var _ as v} -> Some v
| _ when not (Theory.is_interpreted e) -> Some e
| _ -> None
(** move equations from [cls] (which is assumed to be normalized by [subst])
to [subst] which can be expressed as [x ↦ u] where [x] is
non-interpreted [us xs ⊇ fv x ⊈ us] and [fv u ⊆ us] or else
[fv u ⊆ us xs] *)
let solve_uninterp_eqs us (cls, subst) =
[%Trace.call fun {pf} ->
pf "@ cls: @[%a@]@ subst: @[%a@]" Cls.pp cls Subst.pp subst]
;
let compare e f =
[%compare: Theory.kind * Trm.t]
(Theory.classify e, e)
(Theory.classify f, f)
in
let {rep_us; cls_us; rep_xs; cls_xs} =
Cls.fold cls
{rep_us= None; cls_us= Cls.empty; rep_xs= None; cls_xs= Cls.empty}
~f:(fun trm ({rep_us; cls_us; rep_xs; cls_xs} as s) ->
if Var.Set.subset (Trm.fv trm) ~of_:us then
match rep_us with
| Some rep when compare rep trm <= 0 ->
{s with cls_us= Cls.add trm cls_us}
| Some rep -> {s with rep_us= Some trm; cls_us= Cls.add rep cls_us}
| None -> {s with rep_us= Some trm}
else
match rep_xs with
| Some rep -> (
if compare rep trm <= 0 then
match dom_trm trm with
| Some trm -> {s with cls_xs= Cls.add trm cls_xs}
| None -> {s with cls_us= Cls.add trm cls_us}
else
match dom_trm rep with
| Some rep ->
{s with rep_xs= Some trm; cls_xs= Cls.add rep cls_xs}
| None ->
{s with rep_xs= Some trm; cls_us= Cls.add rep cls_us} )
| None -> {s with rep_xs= Some trm} )
in
( match rep_us with
| Some rep_us ->
let cls = Cls.add rep_us cls_us in
let cls, cls_xs =
match rep_xs with
| Some rep -> (
match dom_trm rep with
| Some rep -> (cls, Cls.add rep cls_xs)
| None -> (Cls.add rep cls, cls_xs) )
| None -> (cls, cls_xs)
in
let subst =
Cls.fold cls_xs subst ~f:(fun trm_xs subst ->
let trm_xs = Subst.subst_ subst trm_xs in
let rep_us = Subst.subst_ subst rep_us in
Subst.compose1 ~key:trm_xs ~data:rep_us subst )
in
(cls, subst)
| None -> (
match rep_xs with
| Some rep_xs ->
let cls = Cls.add rep_xs cls_us in
let subst =
Cls.fold cls_xs subst ~f:(fun trm_xs subst ->
let trm_xs = Subst.subst_ subst trm_xs in
let rep_xs = Subst.subst_ subst rep_xs in
Subst.compose1 ~key:trm_xs ~data:rep_xs subst )
in
(cls, subst)
| None -> (cls, subst) ) )
|>
[%Trace.retn fun {pf} (cls', subst') ->
pf "cls: @[%a@]@ subst: @[%a@]" Cls.pp_diff (cls, cls') Subst.pp_diff
(subst, subst') ;
subst_invariant us subst subst']
(** move equations between terms in [rep]'s class [cls] from [classes] to
[subst] which can be expressed, after normalizing with [subst], as
[x ↦ u] where [us xs ⊇ fv x ⊈ us] and [fv u ⊆ us] or else
[fv u ⊆ us xs] *)
let solve_class us us_xs ~key:rep ~data:cls (classes, subst) =
let classes0 = classes in
[%Trace.call fun {pf} ->
pf "@ rep: @[%a@]@ cls: @[%a@]@ subst: @[%a@]" Trm.pp rep Cls.pp cls
Subst.pp subst]
;
let cls, cls_not_ito_us_xs =
Cls.partition
~f:(fun e -> Var.Set.subset (Trm.fv e) ~of_:us_xs)
(Cls.add rep cls)
in
let cls, subst = solve_interp_eqs us (cls, subst) in
let cls, subst = solve_uninterp_eqs us (cls, subst) in
let cls = Cls.union cls_not_ito_us_xs cls in
let cls = Cls.remove (Subst.norm subst rep) cls in
let classes =
if Cls.is_empty cls then Trm.Map.remove rep classes
else Trm.Map.add ~key:rep ~data:cls classes
in
(classes, subst)
|>
[%Trace.retn fun {pf} (classes', subst') ->
pf "subst: @[%a@]@ classes: @[%a@]" Subst.pp_diff (subst, subst')
pp_diff_cls (classes0, classes')]
let solve_concat_extracts_eq r x =
[%Trace.call fun {pf} -> pf "@ %a@ %a" Trm.pp x pp r]
;
let uses =
fold_uses_of r x [] ~f:(fun use uses ->
match use with
| Sized _ as m ->
fold_uses_of r m uses ~f:(fun use uses ->
match use with Extract _ as e -> e :: uses | _ -> uses )
| _ -> uses )
in
let find_extracts_at_off off =
List.filter uses ~f:(function
| Extract {off= o} -> implies r (Fml.eq o off)
| _ -> false )
in
let rec find_extracts full_rev_extracts rev_prefix off =
List.fold (find_extracts_at_off off) full_rev_extracts
~f:(fun e full_rev_extracts ->
match e with
| Extract {seq= Sized {siz= n}; off= o; len= l} ->
let o_l = Trm.add o l in
if implies r (Fml.eq n o_l) then
(e :: rev_prefix) :: full_rev_extracts
else find_extracts full_rev_extracts (e :: rev_prefix) o_l
| _ -> full_rev_extracts )
in
find_extracts [] [] Trm.zero
|>
[%Trace.retn fun {pf} ->
pf "@[[%a]@]" (List.pp ";@ " (List.pp ",@ " Trm.pp))]
let solve_concat_extracts r us x (classes, subst, us_xs) =
match
List.filter_map (solve_concat_extracts_eq r x) ~f:(fun rev_extracts ->
Iter.fold_opt (Iter.of_list rev_extracts) [] ~f:(fun e suffix ->
let+ rep_ito_us =
Cls.fold (cls_of r e) None ~f:(fun trm rep_ito_us ->
match rep_ito_us with
| Some rep when Trm.compare rep trm <= 0 -> rep_ito_us
| _ when Var.Set.subset (Trm.fv trm) ~of_:us -> Some trm
| _ -> rep_ito_us )
in
Trm.sized ~siz:(Trm.seq_size_exn e) ~seq:rep_ito_us :: suffix ) )
|> Iter.of_list
|> Iter.min ~lt:(fun xs ys -> [%compare: Trm.t list] xs ys < 0)
with
| Some extracts ->
let concat = Trm.concat (Array.of_list extracts) in
let subst = Subst.compose1 ~key:x ~data:concat subst in
(classes, subst, us_xs)
| None -> (classes, subst, us_xs)
let solve_for_xs r us xs =
Var.Set.fold xs ~f:(fun x (classes, subst, us_xs) ->
let x = Trm.var x in
if Subst.mem x subst then (classes, subst, us_xs)
else solve_concat_extracts r us x (classes, subst, us_xs) )
(** move equations from [classes] to [subst] which can be expressed, after
normalizing with [subst], as [x ↦ u] where [us xs ⊇ fv x ⊈ us]
and [fv u ⊆ us] or else [fv u ⊆ us xs]. *)
let solve_classes r xs (classes, subst, us) =
[%Trace.call fun {pf} ->
pf "@ us: {@[%a@]}@ xs: {@[%a@]}" Var.Set.pp us Var.Set.pp xs]
;
let rec solve_classes_ (classes0, subst0, us_xs) =
let classes, subst =
Trm.Map.fold ~f:(solve_class us us_xs) classes0 (classes0, subst0)
in
if subst != subst0 then solve_classes_ (classes, subst, us_xs)
else (classes, subst, us_xs)
in
(classes, subst, Var.Set.union us xs)
|> solve_classes_
|> solve_for_xs r us xs
|>
[%Trace.retn fun {pf} (classes', subst', _) ->
pf "subst: @[%a@]@ classes: @[%a@]" Subst.pp_diff (subst, subst')
pp_diff_cls (classes, classes')]
let pp_vss fs vss =
Format.fprintf fs "[@[%a@]]"
(List.pp ";@ " (fun fs vs -> Format.fprintf fs "{@[%a@]}" Var.Set.pp vs))
vss
(** enumerate variable contexts vᵢ in [v₁;…] and accumulate a solution
subst with entries [x ↦ u] where [r] entails [x = u] and
[⋃ⱼ₌₁ⁱ vⱼ ⊇ fv x ⊈ ⋃ⱼ₌₁ⁱ⁻¹ vⱼ] and
[fv u ⊆ ⋃ⱼ₌₁ⁱ⁻¹ vⱼ] if possible and otherwise
[fv u ⊆ ⋃ⱼ₌₁ⁱ vⱼ] *)
let solve_for_vars vss r =
[%Trace.call fun {pf} ->
pf "@ %a@ @[%a@]" pp_vss vss pp r ;
invariant r]
;
let us, vss =
match vss with us :: vss -> (us, vss) | [] -> (Var.Set.empty, vss)
in
List.fold ~f:(solve_classes r) vss (r.cls, Subst.empty, us) |> snd3
|>
[%Trace.retn fun {pf} subst ->
pf "%a" Subst.pp subst ;
Subst.iteri subst ~f:(fun ~key ~data ->
assert (
implies r (Fml.eq key data)
|| fail "@[%a@ = %a@ not entailed by@ @[%a@]@]" Trm.pp key Trm.pp
data pp_classes r () ) ;
assert (
Iter.fold_until (Iter.of_list vss) us
~f:(fun xs us ->
let us_xs = Var.Set.union us xs in
let ks = Trm.fv key in
let ds = Trm.fv data in
if
Var.Set.subset ks ~of_:us_xs
&& Var.Set.subset ds ~of_:us_xs
&& ( Var.Set.subset ds ~of_:us
|| not (Var.Set.subset ks ~of_:us) )
then `Stop true
else `Continue us_xs )
~finish:(fun _ -> false) ) )]
(* Replay debugging =======================================================*)
type call =
| Add of Var.Set.t * Formula.t * t
| Union of Var.Set.t * t * t
| Inter of Var.Set.t * t * t
| InterN of Var.Set.t * t list
| Dnf of Formula.t
| Rename of t * Var.Subst.t
| Is_unsat of t
| Implies of t * Formula.t
| Refutes of t * Formula.t
| Normalize of t * Term.t
| Apply_subst of Var.Set.t * Subst.t * t
| Solve_for_vars of Var.Set.t list * t
| Apply_and_elim of Var.Set.t * Var.Set.t * Subst.t * t
[@@deriving sexp]
let replay c =
match call_of_sexp (Sexp.of_string c) with
| Add (us, e, r) -> add us e r |> ignore
| Union (us, r, s) -> union us r s |> ignore
| Inter (us, r, s) -> inter us r s |> ignore
| InterN (us, rs) -> interN us rs |> ignore
| Dnf f -> dnf f |> ignore
| Rename (r, s) -> rename r s |> ignore
| Is_unsat r -> is_unsat r |> ignore
| Implies (r, f) -> implies r f |> ignore
| Refutes (r, f) -> refutes r f |> ignore
| Normalize (r, e) -> normalize r e |> ignore
| Apply_subst (us, s, r) -> apply_subst us s r |> ignore
| Solve_for_vars (vss, r) -> solve_for_vars vss r |> ignore
| Apply_and_elim (wrt, xs, s, r) -> apply_and_elim ~wrt xs s r |> ignore
(* Debug wrappers *)
let report ~name ~elapsed ~aggregate ~count =
Format.eprintf "%15s time: %12.3f ms %12.3f ms %12d calls@." name
elapsed aggregate count
let dump_threshold = ref 1000.
let wrap tmr f call =
let f () =
Timer.start tmr ;
let r = f () in
Timer.stop_report tmr (fun ~name ~elapsed ~aggregate ~count ->
report ~name ~elapsed ~aggregate ~count ;
if Float.(elapsed > !dump_threshold) then (
dump_threshold := 2. *. !dump_threshold ;
Format.eprintf "@\n%a@\n@." Sexp.pp_hum (sexp_of_call (call ())) ) ) ;
r
in
if not [%debug] then f ()
else
try f ()
with exn ->
let bt = Printexc.get_raw_backtrace () in
let exn = Replay (exn, sexp_of_call (call ())) in
Printexc.raise_with_backtrace exn bt
let add_tmr = Timer.create "add" ~at_exit:report
let union_tmr = Timer.create "union" ~at_exit:report
let inter_tmr = Timer.create "inter" ~at_exit:report
let interN_tmr = Timer.create "interN" ~at_exit:report
let dnf_tmr = Timer.create "dnf" ~at_exit:report
let rename_tmr = Timer.create "rename" ~at_exit:report
let is_unsat_tmr = Timer.create "is_unsat" ~at_exit:report
let implies_tmr = Timer.create "implies" ~at_exit:report
let refutes_tmr = Timer.create "refutes" ~at_exit:report
let normalize_tmr = Timer.create "normalize" ~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 apply_and_elim_tmr = Timer.create "apply_and_elim" ~at_exit:report
let add us e r =
wrap add_tmr (fun () -> add us e r) (fun () -> Add (us, e, r))
let union us r s =
wrap union_tmr (fun () -> union us r s) (fun () -> Union (us, r, s))
let inter us r s =
wrap inter_tmr (fun () -> inter us r s) (fun () -> Inter (us, r, s))
let interN us rs =
wrap interN_tmr (fun () -> interN us rs) (fun () -> InterN (us, rs))
let dnf f = wrap dnf_tmr (fun () -> dnf f) (fun () -> Dnf f)
let rename r s =
wrap rename_tmr (fun () -> rename r s) (fun () -> Rename (r, s))
let is_unsat r =
wrap is_unsat_tmr (fun () -> is_unsat r) (fun () -> Is_unsat r)
let implies r f =
wrap implies_tmr (fun () -> implies r f) (fun () -> Implies (r, f))
let refutes r f =
wrap refutes_tmr (fun () -> refutes r f) (fun () -> Refutes (r, f))
let normalize r e =
wrap normalize_tmr (fun () -> normalize r e) (fun () -> Normalize (r, e))
let apply_subst us s r =
wrap apply_subst_tmr
(fun () -> apply_subst us s r)
(fun () -> Apply_subst (us, s, r))
let solve_for_vars vss r =
wrap solve_for_vars_tmr
(fun () -> solve_for_vars vss r)
(fun () -> Solve_for_vars (vss, 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))