-
François Bobot authoredFrançois Bobot authored
fourier.ml 10.06 KiB
(*************************************************************************)
(* This file is part of Colibri2. *)
(* *)
(* Copyright (C) 2014-2021 *)
(* CEA (Commissariat à l'énergie atomique et aux énergies *)
(* alternatives) *)
(* *)
(* you can redistribute it and/or modify it under the terms of the GNU *)
(* Lesser General Public License as published by the Free Software *)
(* Foundation, version 2.1. *)
(* *)
(* It is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU Lesser General Public License for more details. *)
(* *)
(* See the GNU Lesser General Public License version 2.1 *)
(* for more details (enclosed in the file licenses/LGPLv2.1). *)
(*************************************************************************)
open Colibri2_popop_lib
open Colibri2_core
let comparisons = Datastructure.Push.create Ground.pp "LRA.fourier.comparisons"
let scheduled =
Datastructure.Ref.create Base.Bool.pp "LRA.fourier.scheduled" false
let debug =
Debug.register_info_flag ~desc:"Reasoning about <= < in LRA" "fourier"
let stats_run = Debug.register_stats_int ~name:"Fourier.run" ~init:0
type eq = { p : Polynome.t; bound : Bound.t; origins : Ground.S.t }
[@@deriving show]
(** p <= 0 or p < 0 *)
let divide d (p : Polynome.t) =
try
(match Polynome.is_cst p with None -> () | Some _ -> raise Exit);
if Q.sign p.cst <> 0 then raise Exit;
let l = Node.M.bindings p.poly in
let l =
List.fold_left
(fun acc (e, q) ->
match Dom_product.get_repr d e with
| None when Egraph.is_equal d RealValue.zero e -> acc
| None -> raise Exit
| Some p -> (p, q) :: acc)
[] l
in
Debug.dprintf4 debug "@[eq:%a@ %a@]" Polynome.pp p
Fmt.(list ~sep:(any "+") (using CCPair.swap (pair Q.pp Product.pp)))
l;
match l with
| [] -> raise Exit
| (hd, _) :: _ ->
let common =
List.fold_left
(fun acc (p, _) ->
Node.M.inter
(fun _ a b -> if Q.equal a b then Some a else None)
acc p.Product.poly)
hd.Product.poly l
in
let common, pos =
Node.M.fold_left
(fun (acc, pos) n v ->
if Dom_interval.is_strictly_positive d n then
(Node.M.add n v acc, pos)
else if Dom_interval.is_strictly_negative d n then
(Node.M.add n v acc, not pos)
else (acc, pos))
(Node.M.empty, true) common
in
if Node.M.is_empty common then raise Exit;
Debug.dprintf4 debug "[Fourier] found possible division: %a / %a"
Polynome.pp p (Node.M.pp Q.pp) common;
let cst, l =
List.fold_left
(fun (cst, acc) (p, v) ->
let p = Product.of_map (Node.M.set_diff p.Product.poly common) in
let v = if pos then v else Q.neg v in
match Product.classify p with
| ONE -> (Q.add v cst, acc)
| NODE n -> (cst, (n, v) :: acc)
| PRODUCT ->
let n = Dom_product.node_of_product p in
(Q.add Q.one cst, (n, v) :: acc))
(Q.zero, []) l
in
Some (Polynome.of_list cst l, common, pos)
with Exit -> None
let delta d eq =
(* add info to delta *)
if Node.M.is_num_elt 2 eq.p.poly then
match Node.M.bindings eq.p.poly with
| [ (src, a); (dst, b) ] when Q.equal Q.one a && Q.equal Q.minus_one b ->
Delta.add d src (Q.neg eq.p.cst) eq.bound dst
| [ (dst, b); (src, a) ] when Q.equal Q.one a && Q.equal Q.minus_one b ->
Delta.add d src (Q.neg eq.p.cst) eq.bound dst
| _ -> ()
let apply d ~f truth g =
let aux d bound a b =
let bound =
if truth then bound
else match bound with Bound.Large -> Strict | Strict -> Large
in
let a, b = if truth then (a, b) else (b, a) in
f d bound a b
in
match Ground.sem g with
| { app = { builtin = Expr.Lt }; tyargs = []; args; _ } ->
let a, b = IArray.extract2_exn args in
aux d Bound.Strict a b
| { app = { builtin = Expr.Leq }; tyargs = []; args; _ } ->
let a, b = IArray.extract2_exn args in
aux d Large a b
| { app = { builtin = Expr.Geq }; tyargs = []; args; _ } ->
let a, b = IArray.extract2_exn args in
aux d Large b a
| { app = { builtin = Expr.Gt }; tyargs = []; args; _ } ->
let a, b = IArray.extract2_exn args in
aux d Strict b a
| _ -> assert false
let add_eq d eqs p bound origins =
match (Polynome.is_cst p, bound) with
| None, _ ->
let eq = { p; bound; origins } in
delta d eq;
eq :: eqs
| Some p, Bound.Large when Q.sign p = 0 ->
Ground.S.iter
(fun g ->
let truth = Base.Option.value_exn (Boolean.is d (Ground.node g)) in
apply d truth g ~f:(fun d bound a b ->
match bound with
| Strict
when Dom_interval.is_integer d a && Dom_interval.is_integer d b
->
Dom_polynome.assume_equality d a
(Polynome.of_list Q.minus_one [ (b, Q.one) ])
| Large ->
Dom_polynome.assume_equality d a (Polynome.monome Q.one b)
| Strict -> assert false))
origins;
eqs
| Some p, _ when Q.sign p < 0 ->
Debug.dprintf2 debug "[Fourier] discard %a" Ground.S.pp origins;
eqs
| Some p, bound ->
Debug.dprintf4 Debug.contradiction "[LRA/Fourier] Found %a%a0" Q.pp p
Bound.pp bound;
Egraph.contradiction d
let mk_eq d bound a b =
let ( ! ) n =
match Dom_polynome.get_repr d n with
| None -> Polynome.monome Q.one (Egraph.find_def d n)
| Some p -> p
in
let p, bound' =
match bound with
| Bound.Strict
when Dom_interval.is_integer d a && Dom_interval.is_integer d b ->
(Polynome.add_cst (Polynome.sub !a !b) Q.one, Bound.Large)
| _ -> (Polynome.sub !a !b, bound)
in
( p,
bound',
Polynome.sub (Polynome.monome Q.one a) (Polynome.monome Q.one b),
bound )
let make_equations d (eqs, vars) g =
Debug.dprintf2 debug "[Fourier] %a" Ground.pp g;
match Boolean.is d (Ground.node g) with
| None -> (eqs, vars)
| Some truth -> (
let p, bound, p_non_norm, bound_non_norm = apply d ~f:mk_eq truth g in
Debug.dprintf6 debug "[Fourier] %a(%a)%a0" Polynome.pp p Polynome.pp
p_non_norm Bound.pp bound;
let eqs, vars =
( add_eq d eqs p bound (Ground.S.singleton g),
Node.M.union_merge (fun _ _ _ -> Some ()) vars p.Polynome.poly )
in
match divide d p_non_norm with
| Some (p', _, _) ->
let p' = Dom_polynome.normalize d p' in
( add_eq d eqs p' bound_non_norm Ground.S.empty,
Node.M.union_merge (fun _ _ _ -> Some ()) vars p'.Polynome.poly )
| None -> (eqs, vars))
let fm (d : Egraph.wt) =
Debug.dprintf0 debug "[Fourier]";
Debug.incr stats_run;
Datastructure.Ref.set scheduled d false;
let eqs, vars =
Datastructure.Push.fold comparisons d ~f:(make_equations d)
~init:([], Node.S.empty)
in
let rec split n positive negative absent = function
| [] -> (positive, negative, absent)
| eq :: l -> (
match Node.M.find_opt n eq.p.poly with
| None -> split n positive negative (eq :: absent) l
| Some q ->
if Q.sign q > 0 then split n ((q, eq) :: positive) negative absent l
else split n positive ((q, eq) :: negative) absent l)
in
let rec aux eqs vars =
match Node.S.choose vars with
| exception Not_found -> ()
| n -> (
let vars = Node.S.remove n vars in
let positive, negative, absent = split n [] [] [] eqs in
match (positive, negative) with
| [], _ | _, [] -> aux absent vars
| _, _ ->
let eqs =
Lists.fold_product
(fun eqs (pq, peq) (nq, neq) ->
let q = Q.div pq (Q.neg nq) in
let p = Polynome.cx_p_cy Q.one peq.p q neq.p in
let bound =
match (peq.bound, neq.bound) with
| Large, Large -> Bound.Large
| _ -> Bound.Strict
in
add_eq d eqs p bound (Ground.S.union peq.origins neq.origins))
absent positive negative
in
aux eqs vars)
in
aux eqs vars
module Daemon = struct
let key =
Events.Dem.create
(module struct
type t = unit
let name = "LRA.fourier"
end)
let enqueue d _ =
if Datastructure.Ref.get scheduled d then Events.EnqAlready
else (
Datastructure.Ref.set scheduled d true;
Events.EnqRun (key, (), None))
let delay = Events.Delayed_by 64
type runable = unit
let print_runable = Unit.pp
let run d () = fm d
end
let () = Events.register (module Daemon)
(** {2 Initialization} *)
let converter d (f : Ground.t) =
match Ground.sem f with
| {
app = { builtin = Expr.Lt | Expr.Leq | Expr.Geq | Expr.Gt };
tyargs = [];
args;
} ->
let attach n =
Dom_polynome.events_repr_change d ~node:n Daemon.enqueue;
Events.attach_repr d n Daemon.enqueue
in
IArray.iter ~f:attach args;
Events.attach_value d (Ground.node f) Boolean.dom (fun d n _ ->
Daemon.enqueue d n);
Datastructure.Push.push comparisons d f;
Events.new_pending_daemon d Daemon.key ();
Choice.register d (Boolean.chobool (Ground.node f))
| _ -> ()
let init env = Ground.register_converter env converter