@ -928,6 +928,8 @@ module Atom = struct
fold_map_terms a ~ init ~ f : ( fun acc t -> Term . fold_subst_variables t ~ init : acc ~ f )
let subst_variables l ~ f = fold_subst_variables l ~ init : () ~ f : ( fun () v -> ( () , f v ) ) | > snd
let has_var_notin vars atom =
let t1 , t2 = get_terms atom in
Term . has_var_notin vars t1 | | Term . has_var_notin vars t2
@ -958,11 +960,12 @@ let sat_of_eval_result (eval_result : Atom.eval_result) =
module VarUF =
UnionFind . Make
( struct
type t = Var . t [ @@ deriving compare ]
type t = Var . t [ @@ deriving compare , equal ]
let is_simpler_than ( v1 : Var . t ) ( v2 : Var . t ) = ( v1 :> int ) < ( v2 :> int )
end )
( Var . Set )
( Var . Map )
type new_eq = EqZero of Var . t | Equal of Var . t * Var . t
@ -1407,10 +1410,64 @@ let and_fold_subst_variables phi0 ~up_to_f:phi_foreign ~init ~f:f_var =
( acc , { known ; pruned ; both } , new_eqs )
(* * Intermediate step of [simplify]: build an ( undirected ) graph between variables where an edge
module QuantifierElimination : sig
val eliminate_vars : keep : Var . Set . t -> t -> t SatUnsat . t
(* * [eliminate_vars ~keep φ] substitutes every variable [x] in [φ] with [x'] whenever [x'] is a
distinguished representative of the equivalence class of [ x ] in [ φ ] such that [ x' ∈ keep ] * )
end = struct
exception Contradiction
let subst_f subst x = match Var . Map . find_opt x subst with Some y -> y | None -> x
let targetted_subst_var subst_var x = VarSubst ( subst_f subst_var x )
let subst_var_linear_eqs subst linear_eqs =
Var . Map . fold
( fun x l new_map ->
let x' = subst_f subst x in
let l' = LinArith . subst_variables ~ f : ( targetted_subst_var subst ) l in
match LinArith . solve_eq ( LinArith . of_var x' ) l' with
| Unsat ->
L . d_printfln " Contradiction found: %a=%a became %a=%a with is Unsat " Var . pp x
( LinArith . pp Var . pp ) l Var . pp x' ( LinArith . pp Var . pp ) l' ;
raise Contradiction
| Sat None ->
new_map
| Sat ( Some ( x'' , l'' ) ) ->
Var . Map . add x'' l'' new_map )
linear_eqs Var . Map . empty
let subst_var_atoms subst atoms =
Atom . Set . fold
( fun atom atoms ->
let atom' = Atom . subst_variables ~ f : ( targetted_subst_var subst ) atom in
Atom . Set . add atom' atoms )
atoms Atom . Set . empty
let subst_var_formula subst { Formula . var_eqs ; linear_eqs ; atoms } =
{ Formula . var_eqs = VarUF . apply_subst subst var_eqs
; linear_eqs = subst_var_linear_eqs subst linear_eqs
; atoms = subst_var_atoms subst atoms }
let subst_var subst phi =
{ known = subst_var_formula subst phi . known
; pruned = subst_var_atoms subst phi . pruned
; both = subst_var_formula subst phi . both }
let eliminate_vars ~ keep phi =
let subst = VarUF . reorient ~ keep phi . both . var_eqs in
try Sat ( subst_var subst phi ) with Contradiction -> Unsat
end
module DeadVariables = struct
(* * Intermediate step of [simplify]: build an ( undirected ) graph between variables where an edge
between two variables means that they appear together in an atom , a linear equation , or an
equivalence class . * )
let build_var_graph phi =
let build_var_graph phi =
(* pretty naive representation of an undirected graph: a map where a vertex maps to the set of
destination vertices and each edge has its symmetric in the map * )
(* unused but can be useful for debugging *)
@ -1459,9 +1516,9 @@ let build_var_graph phi =
graph
(* * Intermediate step of [simplify]: construct transitive closure of variables reachable from [vs]
(* * Intermediate step of [simplify]: construct transitive closure of variables reachable from [vs]
in [ graph ] . * )
let get_reachable_from graph vs =
let get_reachable_from graph vs =
(* HashSet represented as a [Hashtbl.t] mapping items to [ ( ) ], start with the variables in [vs] *)
let reachable = Caml . Hashtbl . create ( Var . Set . cardinal vs ) in
Var . Set . iter ( fun v -> Caml . Hashtbl . add reachable v () ) vs ;
@ -1485,15 +1542,12 @@ let get_reachable_from graph vs =
Caml . Hashtbl . to_seq_keys reachable | > Var . Set . of_seq
let simplify ~ keep phi =
let open SatUnsat . Import in
let + phi , new_eqs = normalize phi in
L . d_printfln_escaped " Simplifying %a wrt {%a} " pp phi Var . Set . pp keep ;
(* Get rid of atoms when they contain only variables that do not appear in atoms mentioning
variables in [ keep ] , or variables appearing in atoms together with variables in [ keep ] , and
so on . In other words , the variables to keep are all the ones transitively reachable from
variables in [ keep ] in the graph connecting two variables whenever they appear together in
a same atom of the formula . * )
(* * Get rid of atoms when they contain only variables that do not appear in atoms mentioning
variables in [ keep ] , or variables appearing in atoms together with variables in [ keep ] , and so
on . In other words , the variables to keep are all the ones transitively reachable from
variables in [ keep ] in the graph connecting two variables whenever they appear together in a
same atom of the formula . * )
let eliminate ~ keep phi =
(* We only consider [phi.both] when building the relation. Considering [phi.known] and
[ phi . pruned ] as well could lead to us keeping more variables around , but that's not necessarily
a good idea . Ignoring them means we err on the side of reporting potentially slightly more
@ -1517,7 +1571,18 @@ let simplify ~keep phi =
let known = simplify_phi phi . known in
let both = simplify_phi phi . both in
let pruned = Atom . Set . filter filter_atom phi . pruned in
( { known ; pruned ; both } , new_eqs )
{ known ; pruned ; both }
end
let simplify ~ keep phi =
let open SatUnsat . Import in
let * phi , new_eqs = normalize phi in
L . d_printfln_escaped " Simplifying %a wrt {%a} " pp phi Var . Set . pp keep ;
(* get rid of as many variables as possible *)
let + phi = QuantifierElimination . eliminate_vars ~ keep phi in
(* TODO: doing [QuantifierElimination.eliminate_vars; DeadVariables.eliminate] a few times may
eliminate even more variables * )
( DeadVariables . eliminate ~ keep phi , new_eqs )
let is_known_zero phi v =