(*----------------------------------------------------------------------- ** Copyright (C) 2001, 2002 - Verimag. ** This file may only be copied under the terms of the GNU Library General ** Public License **----------------------------------------------------------------------- ** ** File: solver.ml ** Main author: jahier@imag.fr *) open List open Formula open Constraint open Util open Hashtbl open Gne open Value open Rnumsolver (****************************************************************************) let (lookup: env_in -> subst list -> var_name -> Value.t option) = fun input pre vn -> try Some(Hashtbl.find input vn) with Not_found -> try Some(List.assoc vn pre) with Not_found -> None (****************************************************************************) type comp = SupZero | SupEqZero | EqZero | NeqZero let rec (formula_to_bdd : env_in -> formula -> Bdd.t * bool) = fun input f -> (** Returns the bdd of [f] where input and pre variables have been repaced by their values. Also returns a flag that is true iff the formula depends on input and pre vars. If this flag is false, the formula is stored (cached) in a global table ([env_state.bdd_tbl_global]); otherwise, it is stored in a table that is cleared at each new step ([env_state.bdd_tbl]). *) try (Env_state.bdd f, true) with Not_found -> try (Env_state.bdd_global f, false) with Not_found -> let (bdd, dep) = match f with Not(f1) -> let (bdd_not, dep) = (formula_to_bdd input f1) in (Bdd.dnot bdd_not, dep) | Or(f1, f2) -> let (bdd1, dep1) = (formula_to_bdd input f1) and (bdd2, dep2) = (formula_to_bdd input f2) in (Bdd.dor bdd1 bdd2, dep1 || dep2) | And(f1, f2) -> let (bdd1, dep1) = (formula_to_bdd input f1) and (bdd2, dep2) = (formula_to_bdd input f2) in (Bdd.dand bdd1 bdd2, dep1 || dep2) | EqB(f1, f2) -> let (bdd1, dep1) = (formula_to_bdd input f1) and (bdd2, dep2) = (formula_to_bdd input f2) in (Bdd.eq bdd1 bdd2, dep1 || dep2) | IteB(f1, f2, f3) -> let (bdd1, dep1) = (formula_to_bdd input f1) and (bdd2, dep2) = (formula_to_bdd input f2) and (bdd3, dep3) = (formula_to_bdd input f3) in ((Bdd.dor (Bdd.dand bdd1 bdd2) (Bdd.dand (Bdd.dnot bdd1) bdd3)), dep1 || dep2 || dep3 ) | True -> (Bdd.dtrue (Env_state.bdd_manager ()), false) | False -> (Bdd.dfalse (Env_state.bdd_manager ()), false) | Bvar(vn) -> ( match (lookup input (Env_state.pre ()) vn) with Some(B(bool)) -> if bool then (Bdd.dtrue (Env_state.bdd_manager ()), true) else (Bdd.dfalse (Env_state.bdd_manager ()), true) | Some(_) -> print_string (vn ^ " is not a boolean!\n"); assert false | None -> if List.mem vn (Env_state.pre_var_names ()) then failwith ("*** " ^ vn ^ " is unknown at this stage.\n " ^ "*** Make sure you have not used " ^ "a pre on a output var at the 1st step, \n " ^ "*** or a pre on a input var at the second step in " ^ "your formula in the environment.\n ") else (Bdd.ithvar (Env_state.bdd_manager ()) (Env_state.linear_constraint_to_index (Bv(vn)) false), false) ) | Eq(e1, e2) -> let gne = expr_to_gne (Diff(e1, e2)) input in (gne_to_bdd gne EqZero) | Neq(e1, e2) -> let gne = expr_to_gne (Diff(e1, e2)) input in (gne_to_bdd gne NeqZero) | SupEq(e1, e2) -> let gne = expr_to_gne (Diff(e1, e2)) input in (gne_to_bdd gne SupEqZero) | Sup(e1, e2) -> let gne = expr_to_gne (Diff(e1, e2)) input in (gne_to_bdd gne SupZero) | InfEq(e1, e2) -> let gne = expr_to_gne (Diff(e2, e1)) input in (gne_to_bdd gne SupEqZero) | Inf(e1, e2) -> let gne = expr_to_gne (Diff(e2, e1)) input in (gne_to_bdd gne SupZero) in if dep then ( Env_state.set_bdd f bdd; match f with Not(nf) -> () (* Already in the tbl thanks to the rec call *) | _ -> Env_state.set_bdd (Not(f)) (Bdd.dnot bdd) ) else (* [f] does not depend on pre nor input vars *) ( Env_state.set_bdd_global f bdd ; match f with Not(nf) -> () (* Already in the table thanks to the rec call *) | _ -> Env_state.set_bdd_global (Not(f)) (Bdd.dnot bdd) ); (bdd, dep) and (expr_to_gne: expr -> env_in -> Gne.t) = fun e input -> (** Evaluates pre and input vars appearing in [e] and tranlates it into a so-called garded normal form. Also returns a flag that is true iff [e] depends on pre or input vars. *) let gne = match e with Sum(e1, e2) -> let gne1 = (expr_to_gne e1 input) and gne2 = (expr_to_gne e2 input) in Gne.add gne1 gne2 | Diff(e1, e2) -> let gne1 = (expr_to_gne e1 input) and gne2 = (expr_to_gne e2 input) in Gne.diff gne1 gne2 | Prod(e1, e2) -> let gne1 = (expr_to_gne e1 input) and gne2 = (expr_to_gne e2 input) in Gne.mult gne1 gne2 | Quot(e1, e2) -> let gne1 = (expr_to_gne e1 input) and gne2 = (expr_to_gne e2 input) in Gne.quot gne1 gne2 | Mod(e1, e2) -> let gne1 = (expr_to_gne e1 input) and gne2 = (expr_to_gne e2 input) in Gne.modulo gne1 gne2 | Ivar(str) -> ( match (lookup input (Env_state.pre ()) str) with Some(N(I(i))) -> (Gne.make (Ne.make "" (I i)) (Bdd.dtrue (Env_state.bdd_manager ())) true ) | None -> (Gne.make (Ne.make str (I(1))) (Bdd.dtrue (Env_state.bdd_manager ())) false ) | Some(N(F(f))) -> print_string ((string_of_float f) ^ "is a float, but an int is expected.\n"); assert false | Some(B(f)) -> print_string ((string_of_bool f) ^ "is a bool, but an int is expected.\n"); assert false ) | Fvar(str) -> ( match (lookup input (Env_state.pre ()) str) with Some(N(F(f))) -> ( Gne.make (Ne.make "" (F(f))) (Bdd.dtrue (Env_state.bdd_manager ())) true ) | None -> ( Gne.make (Ne.make str (F(1.))) (Bdd.dtrue (Env_state.bdd_manager ())) false ) | Some(N(I(i))) -> print_string ((string_of_int i) ^ "is an int, but a float is expected.\n"); assert false | Some(B(f)) -> print_string ((string_of_bool f) ^ "is a bool, not a float is expected.\n"); assert false ) | Ival(i) -> (Gne.make (Ne.make "" (I(i))) (Bdd.dtrue (Env_state.bdd_manager ())) false ) | Fval(f) -> ( Gne.make (Ne.make "" (F(f))) (Bdd.dtrue (Env_state.bdd_manager ())) false ) | Ite(f, e1, e2) -> let (add_formula_to_gne_acc : Bdd.t -> bool -> Ne.t -> Bdd.t * bool -> Gne.t -> Gne.t) = fun bdd dep1 nexpr (c, dep2) acc -> (* Used (by a Gne.fold) to add the condition [c] to every condition of a garded expression. *) let _ = assert ( try let _ = Gne.find nexpr acc in false with Not_found -> true ) in let new_bdd = (Bdd.dand bdd c) in if Bdd.is_false new_bdd then acc else Gne.add_ne nexpr new_bdd (dep1 || dep2) acc in let (bdd, depf) = formula_to_bdd input f in let bdd_not = Bdd.dnot bdd and gne_t = (expr_to_gne e1 input) and gne_e = (expr_to_gne e2 input) in let gne1 = Gne.fold (add_formula_to_gne_acc bdd depf) gne_t (Gne.empty ()) in let gne = Gne.fold (add_formula_to_gne_acc bdd_not depf) gne_e gne1 in gne in gne and (gne_to_bdd : Gne.t -> comp -> Bdd.t * bool) = fun gne cmp -> (** Use [cmp] to compare [gne] with 0 and returns the corresponding formula. E.g., if [gne] is bounded to [e1 -> c1; e2 -> c2], then [gne_to_bdd gne SupZero] returns (the bdd corresponding to) the formula [(c1 and (e1 > 0)) or (c2 and (e2 > 0))] *) match cmp with SupZero -> ( Gne.fold (fun nexpr (c, dep) (acc, dep_acc) -> let new_dep = dep || dep_acc and bdd = if Ne.is_a_constant nexpr then let cst = Ne.find "" nexpr in match cst with I(i) -> if i > 0 then (Bdd.dtrue (Env_state.bdd_manager ())) else (Bdd.dfalse (Env_state.bdd_manager ())) | F(f) -> if f > 0. then (Bdd.dtrue (Env_state.bdd_manager ())) else (Bdd.dfalse (Env_state.bdd_manager ())) else Bdd.ithvar (Env_state.bdd_manager ()) (Env_state.linear_constraint_to_index (Ineq(GZ(nexpr))) dep) in (Bdd.dor (Bdd.dand c bdd) acc, new_dep) ) gne ((Bdd.dfalse (Env_state.bdd_manager ())), false) ) | SupEqZero -> ( Gne.fold (fun nexpr (c, dep) (acc, dep_acc) -> let new_dep = dep || dep_acc and bdd = if Ne.is_a_constant nexpr then let cst = Ne.find "" nexpr in match cst with I(i) -> if i >= 0 then (Bdd.dtrue (Env_state.bdd_manager ())) else (Bdd.dfalse (Env_state.bdd_manager ())) | F(f) -> if f >= 0. then (Bdd.dtrue (Env_state.bdd_manager ())) else (Bdd.dfalse (Env_state.bdd_manager ())) else Bdd.ithvar (Env_state.bdd_manager ()) (Env_state.linear_constraint_to_index (Ineq(GeqZ(nexpr))) dep) in (Bdd.dor (Bdd.dand c bdd) acc, new_dep) ) gne ((Bdd.dfalse (Env_state.bdd_manager ())), false) ) | EqZero -> ( Gne.fold (fun nexpr (c, dep) (acc, dep_acc) -> let new_dep = dep || dep_acc and bdd = if Ne.is_a_constant nexpr then let cst = Ne.find "" nexpr in match cst with I(i) -> if i = 0 then (Bdd.dtrue (Env_state.bdd_manager ())) else (Bdd.dfalse (Env_state.bdd_manager ())) | F(f) -> if f = 0. then (Bdd.dtrue (Env_state.bdd_manager ())) else (Bdd.dfalse (Env_state.bdd_manager ())) else Bdd.ithvar (Env_state.bdd_manager ()) (Env_state.linear_constraint_to_index (EqZ(nexpr)) dep) in (Bdd.dor (Bdd.dand c bdd) acc, new_dep) ) gne ((Bdd.dfalse (Env_state.bdd_manager ())), false) ) | NeqZero -> ( Gne.fold (fun nexpr (c, dep) (acc, dep_acc) -> let new_dep = dep || dep_acc and bdd = if Ne.is_a_constant nexpr then let cst = Ne.find "" nexpr in match cst with I(i) -> if i = 0 then (Bdd.dfalse (Env_state.bdd_manager ())) else (Bdd.dtrue (Env_state.bdd_manager ())) | F(f) -> if f = 0. then (Bdd.dfalse (Env_state.bdd_manager ())) else (Bdd.dtrue (Env_state.bdd_manager ())) else Bdd.ithvar (Env_state.bdd_manager ()) (Env_state.linear_constraint_to_index (EqZ(nexpr)) dep) in (Bdd.dor (Bdd.dand c (Bdd.dnot bdd)) acc, new_dep) ) gne ((Bdd.dfalse (Env_state.bdd_manager ())), false) ) (* | EqZero -> *) (* ( Gne.fold *) (* (fun nexpr (c, dep) (acc, dep_acc) -> *) (* let bdd1 = *) (* if Ne.is_a_constant nexpr *) (* then *) (* let cst = Ne.find "" nexpr in *) (* match cst with *) (* I(i) -> *) (* if i >= 0 *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* | F(f) -> *) (* if f >= 0. *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* *) (* else *) (* Bdd.ithvar *) (* (Env_state.bdd_manager ()) *) (* (Env_state.linear_constraint_to_index (GeqZ(nexpr)) dep) *) (* in *) (* let bdd2 = *) (* if Ne.is_a_constant nexpr *) (* then *) (* let cst = Ne.find "" nexpr in *) (* match cst with *) (* I(i) -> *) (* if i <= 0 *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* | F(f) -> *) (* if f <= 0. *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* *) (* else *) (* Bdd.ithvar *) (* (Env_state.bdd_manager ()) *) (* (Env_state.linear_constraint_to_index *) (* (GeqZ(Ne.neg_nexpr nexpr)) dep) *) (* in *) (* let new_dep = dep || dep_acc *) (* and bdd = Bdd.dand bdd1 bdd2 in *) (* (* We transform [e1 = e2] into [e1 <= e2 ^ e1 >= e2] as the *) (* numeric solver can not handle equalities *) *) (* (Bdd.dor (Bdd.dand c bdd) acc, new_dep) *) (* ) *) (* gne *) (* ((Bdd.dfalse (Env_state.bdd_manager ())), false) *) (* ) *) (* *) (* | NeqZero -> *) (* *) (* ( Gne.fold *) (* (fun nexpr (c, dep) (acc, dep_acc) -> *) (* let bdd1 = *) (* if Ne.is_a_constant nexpr *) (* then *) (* let cst = Ne.find "" nexpr in *) (* match cst with *) (* I(i) -> *) (* if i > 0 *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* | F(f) -> *) (* if f > 0. *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* *) (* else *) (* Bdd.ithvar *) (* (Env_state.bdd_manager ()) *) (* (Env_state.linear_constraint_to_index (GZ(nexpr)) dep) *) (* in *) (* let bdd2 = *) (* if Ne.is_a_constant nexpr *) (* then *) (* let cst = Ne.find "" nexpr in *) (* match cst with *) (* I(i) -> *) (* if i < 0 *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* | F(f) -> *) (* if f < 0. *) (* then (Bdd.dtrue (Env_state.bdd_manager ())) *) (* else (Bdd.dfalse (Env_state.bdd_manager ())) *) (* *) (* else *) (* Bdd.ithvar *) (* (Env_state.bdd_manager ()) *) (* (Env_state.linear_constraint_to_index *) (* (GZ(Ne.neg_nexpr nexpr)) dep) *) (* in *) (* let new_dep = dep || dep_acc *) (* and bdd = Bdd.dor bdd1 bdd2 in *) (* (* We transform [e1 <> e2] into [e1 < e2 or e1 > e2] as the *) (* numeric solver can not handle disequalities *) *) (* (Bdd.dor (Bdd.dand c bdd) acc, new_dep) *) (* ) *) (* gne *) (* ((Bdd.dfalse (Env_state.bdd_manager ())), false) *) (* ) *) (****************************************************************************) (****************************************************************************) (* Exported *) let rec (is_satisfiable: env_in -> formula -> bool) = fun input f -> (* let _ = if Env_state.verbose () then *) (* ( *) (* print_string (formula_to_string f); *) (* print_string " ...test whether this formula is satisfiable...\n"; *) (* flush stdout *) (* ) *) (* in *) let (bdd, _) = formula_to_bdd input f in not (Bdd.is_false bdd) && ( try let (n, m) = Env_state.sol_number bdd in not ((zero_sol, zero_sol) = (n, m)) with Not_found -> true ) (****************************************************************************) (****************************************************************************) (** In the following, we call a comb the bdd of a conjunction of litterals (var). They provide the ordering in which litterals appear in the bdds we manipulate. *) type var = int let rec (build_sol_nb_table: Bdd.t -> Bdd.t -> sol_nb * sol_nb) = fun bdd comb -> (** Returns the relative (to which bbd points to it) number of solutions of [bdd] and the one of its negation. Also udpates the solution number table for [bdd] and its negation, and recursively for all its sub-bdds. [comb] is a positive cube that ougth to contain the indexes of boolean vars that are still to be generated, and the numerical indexes that appears in [bdd]. *) let _ = assert (not (Bdd.is_cst bdd) && (Bdd.topvar comb) = (Bdd.topvar bdd)) in let bdd_not = (Bdd.dnot bdd) in let (sol_nb, sol_nb_not) = try let (nt, ne) = Env_state.sol_number bdd and (not_nt, not_ne) = Env_state.sol_number bdd_not in (* solutions numbers in the table are absolute *) ((add_sol_nb nt ne), (add_sol_nb not_nt not_ne)) with Not_found -> let (nt, not_nt) = compute_absolute_sol_nb (Bdd.dthen bdd) comb in let (ne, not_ne) = compute_absolute_sol_nb (Bdd.delse bdd) comb in Env_state.set_sol_number bdd (nt, ne) ; Env_state.set_sol_number bdd_not (not_nt, not_ne) ; ((add_sol_nb nt ne), (add_sol_nb not_nt not_ne)) in (sol_nb, sol_nb_not) and (compute_absolute_sol_nb: Bdd.t -> Bdd.t -> sol_nb * sol_nb) = fun sub_bdd comb -> (* Returns the absolute number of solutions of [sub_bdd] (and its negation) w.r.t. [comb], where [comb] is the comb of the father of [sub_bdd]. The [comb] is used to know which output boolean variables are unconstraint along a path in the bdd. Indeed, the comb is made of all the boolean output var indexes plus the num contraints indexes that appears in the bdd; hence, if the topvar of the bdd is different from the topvar of the comb, it means that the topvar of the comb is unsconstraint and we need to multiply the number of solution of the branch by 2. *) if Bdd.is_cst sub_bdd then let sol_nb = if Bdd.is_true comb then one_sol else (two_power_of (List.length (Bdd.list_of_support (Bdd.dthen comb)))) in if Bdd.is_true sub_bdd then (sol_nb, zero_sol) else (zero_sol, sol_nb) else let topvar = Bdd.topvar sub_bdd in let rec (count_missing_vars: Bdd.t -> var -> int -> Bdd.t * int) = fun comb var cpt -> (* Returns [cpt] + the number of variables occurring in [comb] before reaching [var] ([var] excluded). Also returns the comb whch topvar is [var]. *) let _ = assert (not (Bdd.is_cst comb)) in let combvar = Bdd.topvar comb in if var = combvar then (comb, cpt) else count_missing_vars (Bdd.dthen comb) var (cpt+1) in let (sub_comb, missing_vars_nb) = count_missing_vars (Bdd.dthen comb) topvar 0 in let (n0, not_n0) = build_sol_nb_table sub_bdd sub_comb in let factor = (two_power_of missing_vars_nb) in (mult_sol_nb n0 factor, mult_sol_nb not_n0 factor) (****************************************************************************) (****************************************************************************) let (toss_up_one_var: var_name -> subst) = fun var -> (* *) let ran = Random.float 1. in if (ran < 0.5) then (var, B(true)) else (var, B(false)) let (toss_up_one_var_index: var -> subst option) = fun var -> (* if [var] is a index that corresponds to a boolean variable, this fonction performs a toss and returns a substitution for the corresponding boolean variable. It returns [None] otherwise. Indeed, if it happens that a numerical constraint does not appear along a path, we simply ignore it and hence it will not be added to the store. *) let cstr = Env_state.index_to_linear_constraint var in match cstr with Bv(vn) -> Some(toss_up_one_var vn) | _ -> None let rec (draw_in_bdd: subst list * store -> Bdd.t -> Bdd.t -> subst list * store) = fun (sl, store) bdd comb -> (** Returns [sl] appended to a draw of all the boolean variables bigger than the topvar of [bdd] according to the ordering induced by the comb [comb]. Also returns the (non empty) store obtained by adding to [store] all the numeric constraints that were encountered during this draw. Raises the [No_numeric_solution] exception whenever no valid path in [bdd] leads to a satisfiable set of numeric constraints. *) if Bdd.is_true bdd then (* Toss the remaining bool vars. *) ( (List.append sl (Util.list_map_option toss_up_one_var_index (Bdd.list_of_support comb))), store ) else let _ = assert (not (Bdd.is_false bdd)) in let _ = assert (Env_state.sol_number_exists bdd) in let bddvar = Bdd.topvar bdd in let cstr = (Env_state.index_to_linear_constraint bddvar) in match cstr with Bv(var) -> draw_in_bdd_bool var (sl, store) bdd comb | EqZ(e) -> draw_in_bdd_eq e (sl, store) bdd comb | Ineq(ineq) -> draw_in_bdd_ineq ineq (sl, store) bdd comb and (draw_in_bdd_bool: string -> subst list * store -> Bdd.t -> Bdd.t -> subst list * store) = fun var (sl, store) bdd comb -> let bddvar = Bdd.topvar bdd in let topvar_comb = Bdd.topvar comb in if bddvar <> topvar_comb then (* that condition means that topvar_comb is an unconstraint boolean var; hence we toss it up. *) let new_sl = match toss_up_one_var_index topvar_comb with Some(s) -> s::sl | None -> sl in draw_in_bdd (new_sl, store) bdd (Bdd.dthen comb) else (* bddvar = combvar *) let (n, m) = Env_state.sol_number bdd in let _ = if ((eq_sol_nb n zero_sol) && (eq_sol_nb m zero_sol)) then raise No_numeric_solution ; in let ( bdd1, bool1, sol_nb1, bdd2, bool2, sol_nb2 ) = let ran = Random.float 1. and sol_nb_ratio = ((float_of_sol_nb n) /. (float_of_sol_nb (add_sol_nb n m))) in if ran < sol_nb_ratio (* Depending on the result of a toss (based on the number of solution in each branch), we try the [then] or the [else] branch first. *) then ((Bdd.dthen bdd), true, n, (Bdd.delse bdd), false, m ) else ((Bdd.delse bdd), false, m, (Bdd.dthen bdd), true, n ) in let (sl1, sl2, new_comb) = ( ((var, B(bool1))::sl), ((var, B(bool2))::sl), (if Bdd.is_true comb then comb else Bdd.dthen comb) ) in (* A solution will be found in this branch iff there exists at least one path in the bdd that leads to a satisfiable set of numeric constraints. If it is not the case, [res_opt] is bound to [None]. *) try draw_in_bdd (sl1, store) bdd1 new_comb with No_numeric_solution -> if not (eq_sol_nb sol_nb2 zero_sol) then draw_in_bdd (sl2, store) bdd2 new_comb (* The second branch is now tried because no path in the first bdd leaded to a satisfiable set of numeric constraints. *) else raise No_numeric_solution and (draw_in_bdd_ineq: Constraint.ineq -> subst list * store -> Bdd.t -> Bdd.t -> subst list * store) = fun cstr (sl, store) bdd comb -> (* When we add to the store an inequality constraint GZ(ne) or GeqZ(ne) ([split_store]), 2 stores are returned. The first is a store where the inequality has been added; the second is a store where its negation has been added. Whether we choose the first one or the second depends on a toss made according the (boolean) solution number in both branches of the bdd. If no solution is found into that branch, the other branch with the other store is tried. *) let (n, m) = Env_state.sol_number bdd in let _ = if ((eq_sol_nb n zero_sol) && (eq_sol_nb m zero_sol)) then raise No_numeric_solution ; in let (store_plus_cstr, store_plus_not_cstr) = split_store store cstr in let (store1, bdd1, sol_nb1, store2, bdd2, sol_nb2) = let ran = Random.float 1. in if ran < ((float_of_sol_nb n) /. (float_of_sol_nb (add_sol_nb n m))) (* Depending on the result of a toss (based on the number of solution in each branch), we try the [then] or the [else] branch first. *) then (store_plus_cstr, (Bdd.dthen bdd), n, store_plus_not_cstr, (Bdd.delse bdd), m) else (store_plus_not_cstr, (Bdd.delse bdd), m, store_plus_cstr, (Bdd.dthen bdd), n ) in let call_choice_point _ = (* The second branch is tried if no path in the first bdd leaded to a satisfiable set of numeric constraints. *) if not (eq_sol_nb sol_nb2 zero_sol) then if is_store_satisfiable store2 then draw_in_bdd (sl, store2) bdd2 comb else raise No_numeric_solution else raise No_numeric_solution in (* A solution will be found in this branch iff there exists at least one path in the bdd that leads to a satisfiable set of numeric constraints. If it is not the case, [res_opt] is bound to [None]. *) if is_store_satisfiable store1 then try draw_in_bdd (sl, store1) bdd1 comb with No_numeric_solution -> call_choice_point () else call_choice_point () and (draw_in_bdd_eq: Ne.t -> subst list * store -> Bdd.t -> Bdd.t -> subst list * store) = fun ne (sl, store) bdd comb -> (* When we add to the store an equality constraint EqZ(ne) ([split_store_eq]), 3 stores are returned. The first is a store where the equilaty has been added; the second is a store where the inequality [ne > 0] has been added; the third is a store where [ne < 0] has been added. Whether we choose the first one on not depends on toss made according the (boolean) solution number in both branchs of the bdd. If the else branch is choose (if it is chosen in the first place, or if backtracking occurs because no solutions is found in the then branch) whether we try the second or the third store first is (fairly) tossed up. *) let (n, m) = Env_state.sol_number bdd in let _ = if ((eq_sol_nb n zero_sol) && (eq_sol_nb m zero_sol)) then raise No_numeric_solution ; in let (store_plus_cstr, store_plus_not_cstr, store_plus_not_cstr2) = split_store_eq store ne in let ( store1, bdd1, sol_nb1, store2, bdd2, sol_nb2, store3, bdd3, sol_nb3 ) = let ran0 = Random.float 1. and ran = Random.float 1. and sol_nb_ratio = ((float_of_sol_nb n) /. (float_of_sol_nb (add_sol_nb n m))) in if ran0 < 0.5 (* When taking the negation of an equality, we can either try > or <. Here, We toss which one we will try first. *) then if ran < sol_nb_ratio (* Depending on the result of a toss (based on the number of solution in each branch), we try the [then] or the [else] branch first. *) then (store_plus_cstr, (Bdd.dthen bdd), n, store_plus_not_cstr, (Bdd.delse bdd), m, store_plus_not_cstr2, (Bdd.delse bdd), m) else (store_plus_not_cstr, (Bdd.delse bdd), m, store_plus_cstr, (Bdd.dthen bdd), n, store_plus_not_cstr2, (Bdd.delse bdd), m) else if ran < sol_nb_ratio (* Ditto *) then (store_plus_cstr, (Bdd.dthen bdd), n, store_plus_not_cstr2, (Bdd.delse bdd), m, store_plus_not_cstr, (Bdd.delse bdd), m) else (store_plus_not_cstr2, (Bdd.delse bdd), m, store_plus_cstr, (Bdd.dthen bdd), n, store_plus_not_cstr, (Bdd.delse bdd), m) in let call_choice_point3 _ = (* The third possibility is tried if no path is found in the 2 previous ones. Note that there only is a third one if the current constraint is an equality. *) if not (eq_sol_nb sol_nb3 zero_sol) then if is_store_satisfiable store3 then draw_in_bdd (sl, store3) bdd3 comb else raise No_numeric_solution else raise No_numeric_solution in let call_choice_point2 _ = (* The second branch is tried if no path in the first bdd leaded to a satisfiable set of numeric constraints. *) if not (eq_sol_nb sol_nb2 zero_sol) then if is_store_satisfiable store2 then draw_in_bdd (sl, store2) bdd2 comb else call_choice_point3 () else call_choice_point3 () in (* A solution will be found in this branch iff there exists at least one path in the bdd that leads to a satisfiable set of numeric constraints. *) if is_store_satisfiable store1 then try draw_in_bdd (sl, store1) bdd1 comb with No_numeric_solution -> call_choice_point2 () else call_choice_point2 () (* exported *) let (draw : vn list -> vnt list -> Bdd.t -> Bdd.t -> subst list * subst list) = fun bool_vars_to_gen num_vnt_to_gen comb bdd -> (** Draw the output and local vars to be generated by the environnent. *) let (bool_subst_l, store) = draw_in_bdd ([], (new_store num_vnt_to_gen)) bdd comb in let num_subst_l = match Env_state.draw_mode () with Env_state.Verteces -> draw_verteces store | Env_state.Edges -> draw_edges store | Env_state.Inside -> draw_inside store in let subst_l = append bool_subst_l num_subst_l in let (out_vars, _) = List.split (Env_state.output_var_names ()) in assert ( (* Checks that we generated all variables. *) let (gen_vars, _) = List.split subst_l in let (num_vars_to_gen, _) = List.split num_vnt_to_gen in let vars_to_gen = append bool_vars_to_gen num_vars_to_gen in if (sort (compare) gen_vars) = (sort (compare) vars_to_gen) then true else ( output_string stderr " \ngen vars :"; List.iter (fun vn -> output_string stderr (vn ^ " ")) gen_vars; output_string stderr " \nvar to gen:"; List.iter (fun vn -> output_string stderr (vn ^ " ")) vars_to_gen; output_string stderr " \n"; false ) ); (* Splits output and local vars. *) List.partition (fun (vn, _) -> List.mem vn out_vars) subst_l (****************************************************************************) (****************************************************************************) (* Exported *) let (solve_formula: env_in -> int -> formula -> formula -> vnt list -> (subst list * subst list) list option) = fun input p f bool_vars_to_gen_f num_vars_to_gen -> let _ = if Env_state.verbose () then ( print_string (formula_to_string f); print_string "\n"; flush stdout ) in let bdd = (* The bdd of f has necessarily been computed (by is_satisfiable) *) try Env_state.bdd f with Not_found -> Env_state.bdd_global f in let (comb0, _) = formula_to_bdd input bool_vars_to_gen_f in let comb = (* All boolean vars should appear in the comb so that when we find that such a var is missing along a bdd path, we can perform a (fair) toss for it. On the contrary, if a numerical contraint disappear from a bdd (eg, consider [(f && false) || true]), it is not important; fairly tossing a (boolean) value for a num constaint [nc] and performing a fair toss in the resulting domain is equivalent to directly perform the toss in the (unconstraint wrt [nc]) initial domain. *) Bdd.dand (Bdd.support bdd) comb0 in let bool_vars_to_gen = Formula.support bool_vars_to_gen_f in let _ = if not (Env_state.sol_number_exists bdd) then let rec skip_unconstraint_bool_var_at_top comb v = (* [build_sol_nb_table] supposes that the bdd and its comb have the same top var. *) if Bdd.is_true comb then comb else let topvar = (Bdd.topvar comb) in if v = topvar then comb else skip_unconstraint_bool_var_at_top (Bdd.dthen comb) v in let comb2 = skip_unconstraint_bool_var_at_top comb (Bdd.topvar bdd) in let _ = build_sol_nb_table bdd comb2 in () in try Some(Util.unfold (draw bool_vars_to_gen num_vars_to_gen comb) bdd p) with No_numeric_solution -> Env_state.set_sol_number bdd (zero_sol, zero_sol); None