Problem
I am trying to use the Z3 optimization capabilities to take into account some objectives when checking satisfiability. When I ask Z3 to minimize x + y
with x > 0
and y > 0
, the obtained interpretation with get_model assigns 1
to both x
and y
. I am, however, interested in obtaining interpretations that are "closer" to the optimal solution. I tried a workaround (which I explain below), but I'm wondering whether there exists some "built-in" feature in Z3 that allows to "better" interpret unbounded objectives.
What I tried
I tried to use get_lower and get_upper, which gives a symbolic value to x + y
— that is 2 * epsilon
. Then, I use substitute_one to substitute epsilon
with some "very small" value of choice (for example: 0.001
). I could inject the obtained value of x + y
(which is the expression I am trying to minimize) into the solver. A get_model would then be enough.
This is the example I tried, to see the result of different Z3 functions:
open Z3open Z3.Expropen Z3.Booleanopen Z3.Arithmeticopen Z3.Arithmetic.Realopen Z3.Optimizeopen Z3.Solveropen Z3.Modelopen Printflet ctx = Z3.mk_context [] (* context object *)let opt = Optimize.mk_opt ctx (* optimization context *)let x = Real.mk_const_s ctx "x"let y = Real.mk_const_s ctx "y"let zero = Real.mk_numeral_i ctx 0let x_plus_y = Real.mk_const_s ctx "x_plus_y"let assertions = Arithmetic.mk_gt ctx x zero (* x > 0 *) :: Arithmetic.mk_gt ctx y zero (* y > 0 *) :: Boolean.mk_eq ctx x_plus_y (Arithmetic.mk_add ctx [x; y]) (* x_plus_y = x + y *) :: [](* assert constraints into the optimize solver *)let () = Optimize.add opt assertions(* ask the solver to minimize x_plus_y *)let handle = Optimize.minimize opt x_plus_y(* check sat *)let status = Optimize.check optlet () = Z3.Params.set_print_mode ctx PRINT_SMTLIB_FULLlet () = printf "- status: %s\n\n" @@ Solver.string_of_status status(* get a model when SAT *)let () = if status == SATISFIABLE then match Optimize.get_model opt with | None -> () | Some model -> printf "- model:\n%s\n" @@ Model.to_string model; printf "- objectives:"; List.iter (fun expr -> printf " %s\n" @@ Expr.to_string expr) @@ Optimize.get_objectives opt; printf "- lower_bound: %s\n" @@ Expr.to_string @@ Optimize.get_lower handle; printf "- upper_bound: %s\n\n" @@ Expr.to_string @@ Optimize.get_upper handle; printf "- substitute epsilon with 0.001: %s\n\n" @@ Expr.to_string @@ Expr.substitute_one (Optimize.get_lower handle) (Real.mk_const_s ctx "epsilon") (Real.mk_numeral_s ctx @@ string_of_float 0.001);
Which gives the following output:
- status: satisfiable- model:y -> 1.0x -> 1.0x_plus_y -> 2.0- objectives: x_plus_y- lower_bound: (* 2.0 epsilon)- upper_bound: (* 2.0 epsilon)- substitute epsilon with 0.001: (* 2.0 (/ 1.0 1000.0))
Questions
- I am not sure of the use of get_lower and get_upper. In my example they return the exact same value. Is there any resource explaining what lower and upper bounds mean in this context ?
- Is there any alternative to evaluate the objective (other than substituting
epsilon
, as I did) ?
Thanks.