(************************************************************************) (* * The Coq Proof Assistant / The Coq Development Team *) (* v * Copyright INRIA, CNRS and contributors *) (* x.proof_idx) [c1;...;cn] - [pos] is the number of positive values of the vector - [neg] is the number of negative values of the vector ( [neg] + [pos] is therefore the length of the vector) [v] is an upper-bound of the set of variables which appear in [s]. *) (** To be thrown when a system has no solution *) exception SystemContradiction of proof (** Pretty printing *) let rec pp_proof o prf = match prf with | Assum i -> Printf.fprintf o "H%i" i | Elim (v, prf1, prf2) -> Printf.fprintf o "E(%i,%a,%a)" v pp_proof prf1 pp_proof prf2 | And (prf1, prf2) -> Printf.fprintf o "A(%a,%a)" pp_proof prf1 pp_proof prf2 let pp_cstr o (vect, bnd) = let l, r = bnd in ( match l with | None -> () | Some n -> Printf.fprintf o "%s <= " (Q.to_string n) ); Vect.pp o vect; match r with | None -> output_string o "\n" | Some n -> Printf.fprintf o "<=%s\n" (Q.to_string n) let pp_system o sys = System.iter (fun vect ibnd -> pp_cstr o (vect, !ibnd.bound)) sys (** [merge_cstr_info] takes: - the intersection of bounds and - the union of proofs - [pos] and [neg] fields should be identical *) let merge_cstr_info i1 i2 = let {pos = p1; neg = n1; bound = i1; prf = prf1} = i1 and {pos = p2; neg = n2; bound = i2; prf = prf2} = i2 in assert (Int.equal p1 p2 && Int.equal n1 n2); match inter i1 i2 with | None -> None (* Could directly raise a system contradiction exception *) | Some bnd -> Some {pos = p1; neg = n1; bound = bnd; prf = And (prf1, prf2)} (** [xadd_cstr vect cstr_info] loads an constraint into the system. The constraint is neither redundant nor contradictory. @raise SystemContradiction if [cstr_info] returns [None] *) let xadd_cstr vect cstr_info sys = try let info = System.find sys vect in match merge_cstr_info cstr_info !info with | None -> raise (SystemContradiction (And (cstr_info.prf, !info.prf))) | Some info' -> info := info' with Not_found -> System.replace sys vect (ref cstr_info) exception TimeOut let xadd_cstr vect cstr_info sys = if debug && Int.equal (System.length sys mod 1000) 0 then ( print_string "*"; flush stdout ); if System.length sys < !max_nb_cstr then xadd_cstr vect cstr_info sys else raise TimeOut type cstr_ext = | Contradiction (** The constraint is contradictory. Typically, a [SystemContradiction] exception will be raised. *) | Redundant (** The constrain is redundant. Typically, the constraint will be dropped *) | Cstr of vector * cstr_info (** Taken alone, the constraint is neither contradictory nor redundant. Typically, it will be added to the constraint system. *) (** [normalise_cstr] : vector -> cstr_info -> cstr_ext *) let normalise_cstr vect cinfo = match norm_itv cinfo.bound with | None -> Contradiction | Some (l, r) -> ( match Vect.choose vect with | None -> if Itv.in_bound (l, r) Q.zero then Redundant else Contradiction | Some (_, n, _) -> Cstr ( Vect.div n vect , let divn x = x // n in if Int.equal (Q.sign n) 1 then {cinfo with bound = (Option.map divn l, Option.map divn r)} else { cinfo with pos = cinfo.neg ; neg = cinfo.pos ; bound = (Option.map divn r, Option.map divn l) } ) ) (** For compatibility, there is an external representation of constraints *) let count v = Vect.fold (fun (n, p) _ vl -> let sg = Q.sign vl in assert (sg <> 0); if Int.equal sg 1 then (n, p + 1) else (n + 1, p)) (0, 0) v let norm_cstr {coeffs = v; op = o; cst = c} idx = let n, p = count v in normalise_cstr v { pos = p ; neg = n ; bound = ( match o with | Eq -> (Some c, Some c) | Ge -> (Some c, None) | Gt -> raise Polynomial.Strict ) ; prf = Assum idx } (** [load_system l] takes a list of constraints of type [cstr_compat] @return a system of constraints @raise SystemContradiction if a contradiction is found *) let load_system l = let sys = System.create 1000 in let li = List.mapi (fun i e -> (e, i)) l in let vars = List.fold_left (fun vrs (cstr, i) -> match norm_cstr cstr i with | Contradiction -> raise (SystemContradiction (Assum i)) | Redundant -> vrs | Cstr (vect, info) -> xadd_cstr vect info sys; Vect.fold (fun s v _ -> ISet.add v s) vrs cstr.coeffs) ISet.empty li in {sys; vars} let system_list sys = let {sys = s; vars = v} = sys in System.fold (fun k bi l -> (k, !bi) :: l) s [] (** [add (v1,c1) (v2,c2) ] precondition: (c1 <>/ Q.zero && c2 <>/ Q.zero) @return a pair [(v,ln)] such that [v] is the sum of vector [v1] divided by [c1] and vector [v2] divided by [c2] Note that the resulting vector is not normalised. *) let add (v1, c1) (v2, c2) = assert (c1 <>/ Q.zero && c2 <>/ Q.zero); (* XXX Can use Q.inv now *) let res = mul_add (Q.one // c1) v1 (Q.one // c2) v2 in (res, count res) let add (v1, c1) (v2, c2) = let res = add (v1, c1) (v2, c2) in (* Printf.printf "add(%a,%s,%a,%s) -> %a\n" pp_vect v1 (Q.to_string c1) pp_vect v2 (Q.to_string c2) pp_vect (fst res) ;*) res (** To perform Fourier elimination, constraints are categorised depending on the sign of the variable to eliminate. *) (** [split x vect info (l,m,r)] @param v is the variable to eliminate @param l contains constraints such that (e + a*x) // a >= c / a @param r contains constraints such that (e + a*x) // - a >= c / -a @param m contains constraints which do not mention [x] *) let split x (vect : vector) info (l, m, r) = let vl = get x vect in if Q.zero =/ vl then (* The constraint does not mention [x], store it in m *) (l, (vect, info) :: m, r) else (* otherwise *) let cons_bound lst bd = match bd with | None -> lst | Some bnd -> (vl, vect, {info with bound = (Some bnd, None)}) :: lst in let lb, rb = info.bound in if Int.equal (Q.sign vl) 1 then (cons_bound l lb, m, cons_bound r rb) else (* sign_num vl = -1 *) (cons_bound l rb, m, cons_bound r lb) (** [project vr sys] projects system [sys] over the set of variables [ISet.remove vr sys.vars ]. This is a one step Fourier elimination. *) let project vr sys = let l, m, r = System.fold (fun vect rf l_m_r -> split vr vect !rf l_m_r) sys.sys ([], [], []) in let new_sys = System.create (System.length sys.sys) in (* Constraints in [m] belong to the projection - for those [vr] is already projected out *) List.iter (fun (vect, info) -> System.replace new_sys vect (ref info)) m; let elim (v1, vect1, info1) (v2, vect2, info2) = let {neg = n1; pos = p1; bound = bound1; prf = prf1} = info1 and {neg = n2; pos = p2; bound = bound2; prf = prf2} = info2 in let bnd1 = Option.get (fst bound1) and bnd2 = Option.get (fst bound2) in let bound = (bnd1 // v1) +/ (bnd2 // Q.neg v2) in let vres, (n, p) = add (vect1, v1) (vect2, Q.neg v2) in ( vres , { neg = n ; pos = p ; bound = (Some bound, None) ; prf = Elim (vr, info1.prf, info2.prf) } ) in List.iter (fun l_elem -> List.iter (fun r_elem -> let vect, info = elim l_elem r_elem in match normalise_cstr vect info with | Redundant -> () | Contradiction -> raise (SystemContradiction info.prf) | Cstr (vect, info) -> xadd_cstr vect info new_sys) r) l; {sys = new_sys; vars = ISet.remove vr sys.vars} (** [project_using_eq] performs elimination by pivoting using an equation. This is the counter_part of the [elim] sub-function of [!project]. @param vr is the variable to be used as pivot @param c is the coefficient of variable [vr] in vector [vect] @param len is the length of the equation @param bound is the bound of the equation @param prf is the proof of the equation *) let project_using_eq vr c vect bound prf (vect', info') = let c2 = get vr vect' in if Q.zero =/ c2 then (vect', info') else let c1 = if c2 >=/ Q.zero then Q.neg c else c in let c2 = Q.abs c2 in let vres, (n, p) = add (vect, c1) (vect', c2) in let cst = bound // c1 in let bndres = let f x = cst +/ (x // c2) in let l, r = info'.bound in (Option.map f l, Option.map f r) in (vres, {neg = n; pos = p; bound = bndres; prf = Elim (vr, prf, info'.prf)}) let elim_var_using_eq vr vect cst prf sys = let c = get vr vect in let elim_var = project_using_eq vr c vect cst prf in let new_sys = System.create (System.length sys.sys) in System.iter (fun vect iref -> let vect', info' = elim_var (vect, !iref) in match normalise_cstr vect' info' with | Redundant -> () | Contradiction -> raise (SystemContradiction info'.prf) | Cstr (vect, info') -> xadd_cstr vect info' new_sys) sys.sys; {sys = new_sys; vars = ISet.remove vr sys.vars} (** [size sys] computes the number of entries in the system of constraints *) let size sys = System.fold (fun v iref s -> s + !iref.neg + !iref.pos) sys 0 module IMap = CMap.Make (Int) (** [eval_vect map vect] evaluates vector [vect] using the values of [map]. If [map] binds all the variables of [vect], we get [eval_vect map [(x1,v1);...;(xn,vn)] = (IMap.find x1 map * v1) + ... + (IMap.find xn map) * vn , []] The function returns as second argument, a sub-vector consisting in the variables that are not in [map]. *) let eval_vect map vect = Vect.fold (fun (sum, rst) v vl -> try let val_v = IMap.find v map in (sum +/ (val_v */ vl), rst) with Not_found -> (sum, Vect.set v vl rst)) (Q.zero, Vect.null) vect (** [restrict_bound n sum itv] returns the interval of [x] given that (fst itv) <= x * n + sum <= (snd itv) *) let restrict_bound n sum (itv : interval) = let f x = (x -/ sum) // n in let l, r = itv in match Q.sign n with | 0 -> if in_bound itv sum then (None, None) (* redundant *) else failwith "SystemContradiction" | 1 -> (Option.map f l, Option.map f r) | _ -> (Option.map f r, Option.map f l) (** [bound_of_variable map v sys] computes the interval of [v] in [sys] given a mapping [map] binding all the other variables *) let bound_of_variable map v sys = System.fold (fun vect iref bnd -> let sum, rst = eval_vect map vect in let vl = Vect.get v rst in match inter bnd (restrict_bound vl sum !iref.bound) with | None -> Printf.fprintf stdout "bound_of_variable: eval_vecr %a = %s,%a\n" Vect.pp vect (Q.to_string sum) Vect.pp rst; Printf.fprintf stdout "current interval: %a\n" Itv.pp !iref.bound; failwith "bound_of_variable: impossible" | Some itv -> itv) sys (None, None) (** [pick_small_value bnd] picks a value being closed to zero within the interval *) let pick_small_value bnd = match bnd with | None, None -> Q.zero | None, Some i -> if Q.zero <=/ Q.floor i then Q.zero else Q.floor i | Some i, None -> if i <=/ Q.zero then Q.zero else Q.ceiling i | Some i, Some j -> if i <=/ Q.zero && Q.zero <=/ j then Q.zero else if Q.ceiling i <=/ Q.floor j then Q.ceiling i (* why not *) else i (** [solution s1 sys_l = Some(sn,\[(vn-1,sn-1);...; (v1,s1)\]\@sys_l)] then [sn] is a system which contains only [black_v] -- if it existed in [s1] and [sn+1] is obtained by projecting [vn] out of [sn] @raise SystemContradiction if system [s] has no solution *) let solve_sys black_v choose_eq choose_variable sys sys_l = let rec solve_sys sys sys_l = if debug then Printf.printf "S #%i size %i\n" (System.length sys.sys) (size sys.sys); if debug then Printf.printf "solve_sys :\n %a" pp_system sys.sys; let eqs = choose_eq sys in try let v, vect, cst, ln = fst (List.find (fun ((v, _, _, _), _) -> v <> black_v) eqs) in if debug then ( Printf.printf "\nE %a = %s variable %i\n" Vect.pp vect (Q.to_string cst) v; flush stdout ); let sys' = elim_var_using_eq v vect cst ln sys in solve_sys sys' ((v, sys) :: sys_l) with Not_found -> ( let vars = choose_variable sys in try let v, est = List.find (fun (v, _) -> v <> black_v) vars in if debug then ( Printf.printf "\nV : %i estimate %f\n" v est; flush stdout ); let sys' = project v sys in solve_sys sys' ((v, sys) :: sys_l) with Not_found -> (* we are done *) Inl (sys, sys_l) ) in solve_sys sys sys_l let solve black_v choose_eq choose_variable cstrs = try let sys = load_system cstrs in if debug then Printf.printf "solve :\n %a" pp_system sys.sys; solve_sys black_v choose_eq choose_variable sys [] with SystemContradiction prf -> Inr prf (** The purpose of module [EstimateElimVar] is to try to estimate the cost of eliminating a variable. The output is an ordered list of (variable,cost). *) module EstimateElimVar = struct type sys_list = (vector * cstr_info) list let abstract_partition (v : int) (l : sys_list) = let rec xpart (l : sys_list) (ltl : sys_list) (n : int list) (z : int) (p : int list) = match l with | [] -> (ltl, n, z, p) | (l1, info) :: rl -> ( match Vect.choose l1 with | None -> xpart rl ((Vect.null, info) :: ltl) n (info.neg + info.pos + z) p | Some (vr, vl, rl1) -> if Int.equal v vr then let cons_bound lst bd = match bd with | None -> lst | Some bnd -> (info.neg + info.pos) :: lst in let lb, rb = info.bound in if Int.equal (Q.sign vl) 1 then xpart rl ((rl1, info) :: ltl) (cons_bound n lb) z (cons_bound p rb) else xpart rl ((rl1, info) :: ltl) (cons_bound n rb) z (cons_bound p lb) else (* the variable is greater *) xpart rl ((l1, info) :: ltl) n (info.neg + info.pos + z) p ) in let sys', n, z, p = xpart l [] [] 0 [] in let ln = float_of_int (List.length n) in let sn = float_of_int (List.fold_left ( + ) 0 n) in let lp = float_of_int (List.length p) in let sp = float_of_int (List.fold_left ( + ) 0 p) in (sys', float_of_int z +. (lp *. sn) +. (ln *. sp) -. (lp *. ln)) let choose_variable sys = let {sys = s; vars = v} = sys in let sl = system_list sys in let evals = fst (ISet.fold (fun v (eval, s) -> let ts, vl = abstract_partition v s in ((v, vl) :: eval, ts)) v ([], sl)) in List.sort (fun x y -> compare_float (snd x) (snd y)) evals end open EstimateElimVar (** The module [EstimateElimEq] is similar to [EstimateElimVar] but it orders equations. *) module EstimateElimEq = struct let itv_point bnd = match bnd with Some a, Some b -> a =/ b | _ -> false let rec unroll_until v l = match Vect.choose l with | None -> (false, Vect.null) | Some (i, _, rl) -> if Int.equal i v then (true, rl) else if i < v then unroll_until v rl else (false, l) let rec choose_simple_equation eqs = match eqs with | [] -> None | (vect, a, prf, ln) :: eqs -> ( match Vect.choose vect with | Some (i, v, rst) -> if Vect.is_null rst then Some (i, vect, a, prf, ln) else choose_simple_equation eqs | _ -> choose_simple_equation eqs ) let choose_primal_equation eqs (sys_l : (Vect.t * cstr_info) list) = (* Counts the number of equations referring to variable [v] -- It looks like nb_cst is dead... *) let is_primal_equation_var v = List.fold_left (fun nb_eq (vect, info) -> if fst (unroll_until v vect) then if itv_point info.bound then nb_eq + 1 else nb_eq else nb_eq) 0 sys_l in let rec find_var vect = match Vect.choose vect with | None -> None | Some (i, _, vect) -> let nb_eq = is_primal_equation_var i in if Int.equal nb_eq 2 then Some i else find_var vect in let rec find_eq_var eqs = match eqs with | [] -> None | (vect, a, prf, ln) :: l -> ( match find_var vect with | None -> find_eq_var l | Some r -> Some (r, vect, a, prf, ln) ) in match choose_simple_equation eqs with | None -> find_eq_var eqs | Some res -> Some res let choose_equality_var sys = let sys_l = system_list sys in let equalities = List.fold_left (fun l (vect, info) -> match info.bound with | Some a, Some b -> if a =/ b then (* This an equation *) (vect, a, info.prf, info.neg + info.pos) :: l else l | _ -> l) [] sys_l in let rec estimate_cost v ct sysl acc tlsys = match sysl with | [] -> (acc, tlsys) | (l, info) :: rsys -> ( let ln = info.pos + info.neg in let b, l = unroll_until v l in match b with | true -> if itv_point info.bound then estimate_cost v ct rsys (acc + ln) ((l, info) :: tlsys) (* this is free *) else estimate_cost v ct rsys (acc + ln + ct) ((l, info) :: tlsys) (* should be more ? *) | false -> estimate_cost v ct rsys (acc + ln) ((l, info) :: tlsys) ) in match choose_primal_equation equalities sys_l with | None -> let cost_eq eq const prf ln acc_costs = let rec cost_eq eqr sysl costs = match Vect.choose eqr with | None -> costs | Some (v, _, eqr) -> let cst, tlsys = estimate_cost v (ln - 1) sysl 0 [] in cost_eq eqr tlsys (((v, eq, const, prf), cst) :: costs) in cost_eq eq sys_l acc_costs in let all_costs = List.fold_left (fun all_costs (vect, const, prf, ln) -> cost_eq vect const prf ln all_costs) [] equalities in (* pp_list (fun o ((v,eq,_,_),cst) -> Printf.fprintf o "((%i,%a),%i)\n" v pp_vect eq cst) stdout all_costs ; *) List.sort (fun x y -> Int.compare (snd x) (snd y)) all_costs | Some (v, vect, const, prf, _) -> [((v, vect, const, prf), 0)] end open EstimateElimEq module Fourier = struct let optimise vect l = (* We add a dummy (fresh) variable for vector *) let fresh = List.fold_left (fun fr c -> max fr (Vect.fresh c.coeffs)) 0 l in let cstr = {coeffs = Vect.set fresh Q.minus_one vect; op = Eq; cst = Q.zero} in match solve fresh choose_equality_var choose_variable (cstr :: l) with | Inr prf -> None (* This is an unsatisfiability proof *) | Inl (s, _) -> ( try Some (bound_of_variable IMap.empty fresh s.sys) with x when CErrors.noncritical x -> Printf.printf "optimise Exception : %s" (Printexc.to_string x); None ) let find_point cstrs = match solve max_int choose_equality_var choose_variable cstrs with | Inr prf -> Inr prf | Inl (_, l) -> let rec rebuild_solution l map = match l with | [] -> map | (v, e) :: l -> let itv = bound_of_variable map v e.sys in let map = IMap.add v (pick_small_value itv) map in rebuild_solution l map in let map = rebuild_solution l IMap.empty in let vect = IMap.fold (fun v i vect -> Vect.set v i vect) map Vect.null in if debug then Printf.printf "SOLUTION %a" Vect.pp vect; let res = Inl vect in res end module Proof = struct (** A proof term in the sense of a ZMicromega.RatProof is a positive combination of the hypotheses which leads to a contradiction. The proofs constructed by Fourier elimination are more like execution traces: - certain facts are recorded but are useless - certain inferences are implicit. The following code implements proof reconstruction. *) let add x y = fst (add x y) let forall_pairs f l1 l2 = List.fold_left (fun acc e1 -> List.fold_left (fun acc e2 -> match f e1 e2 with None -> acc | Some v -> v :: acc) acc l2) [] l1 let add_op x y = match (x, y) with Eq, Eq -> Eq | _ -> Ge let pivot v (p1, c1) (p2, c2) = let {coeffs = v1; op = op1; cst = n1} = c1 and {coeffs = v2; op = op2; cst = n2} = c2 in let a, b = (Vect.get v v1, Vect.get v v2) in if Q.zero =/ a || Q.zero =/ b then None else if Int.equal (Q.sign a * Q.sign b) (-1) then Some ( add (p1, Q.abs a) (p2, Q.abs b) , { coeffs = add (v1, Q.abs a) (v2, Q.abs b) ; op = add_op op1 op2 ; cst = (n1 // Q.abs a) +/ (n2 // Q.abs b) } ) else if op1 == Eq then Some ( add (p1, Q.neg (a // b)) (p2, Q.one) , { coeffs = add (v1, Q.neg (a // b)) (v2, Q.one) ; op = add_op op1 op2 ; cst = (n1 // Q.neg (a // b)) +/ (n2 // Q.one) } ) else if op2 == Eq then Some ( add (p2, Q.neg (b // a)) (p1, Q.one) , { coeffs = add (v2, Q.neg (b // a)) (v1, Q.one) ; op = add_op op1 op2 ; cst = (n2 // Q.neg (b // a)) +/ (n1 // Q.one) } ) else None (* op2 could be Eq ... this might happen *) let normalise_proofs l = List.fold_left (fun acc (prf, cstr) -> match acc with | Inr _ -> acc (* I already found a contradiction *) | Inl acc -> ( match norm_cstr cstr 0 with | Redundant -> Inl acc | Contradiction -> Inr (prf, cstr) | Cstr (v, info) -> Inl ((prf, cstr, v, info) :: acc) )) (Inl []) l type oproof = (vector * cstr * Q.t) option let merge_proof (oleft : oproof) (prf, cstr, v, info) (oright : oproof) = let l, r = info.bound in let keep p ob bd = match (ob, bd) with | None, None -> None | None, Some b -> Some (prf, cstr, b) | Some _, None -> ob | Some (prfl, cstrl, bl), Some b -> if p bl b then Some (prf, cstr, b) else ob in let oleft = keep ( <=/ ) oleft l in let oright = keep ( >=/ ) oright r in (* Now, there might be a contradiction *) match (oleft, oright) with | None, _ | _, None -> Inl (oleft, oright) | Some (prfl, cstrl, l), Some (prfr, cstrr, r) -> ( if l <=/ r then Inl (oleft, oright) else (* There is a contradiction - it should show up by scaling up the vectors - any pivot should do*) match Vect.choose cstrr.coeffs with | None -> Inr (add (prfl, Q.one) (prfr, Q.one), cstrr) (* this is wrong *) | Some (v, _, _) -> ( match pivot v (prfl, cstrl) (prfr, cstrr) with | None -> failwith "merge_proof : pivot is not possible" | Some x -> Inr x ) ) let mk_proof hyps prf = (* I am keeping list - I might have a proof for the left bound and a proof for the right bound. If I perform aggressive elimination of redundancies, I expect the list to be of length at most 2. For each proof list, all the vectors should be of the form a.v for different constants a. *) let rec mk_proof prf = match prf with | Assum i -> [(Vect.set i Q.one Vect.null, List.nth hyps i)] | Elim (v, prf1, prf2) -> let prfsl = mk_proof prf1 and prfsr = mk_proof prf2 in (* I take only the pairs for which the elimination is meaningful *) forall_pairs (pivot v) prfsl prfsr | And (prf1, prf2) -> ( let prfsl1 = mk_proof prf1 and prfsl2 = mk_proof prf2 in (* detect trivial redundancies and contradictions *) match normalise_proofs (prfsl1 @ prfsl2) with | Inr x -> [x] (* This is a contradiction - this should be the end of the proof *) | Inl l -> ( (* All the vectors are the same *) let prfs = List.fold_left (fun acc e -> match acc with | Inr _ -> acc (* I have a contradiction *) | Inl (oleft, oright) -> merge_proof oleft e oright) (Inl (None, None)) l in match prfs with | Inr x -> [x] | Inl (oleft, oright) -> ( match (oleft, oright) with | None, None -> [] | None, Some (prf, cstr, _) | Some (prf, cstr, _), None -> [(prf, cstr)] | Some (prf1, cstr1, _), Some (prf2, cstr2, _) -> [(prf1, cstr1); (prf2, cstr2)] ) ) ) in mk_proof prf end