#include "api/z3.h"
#include "ast/reg_decl_plugins.h"
#include "opt/opt_context.h"
#include "opt/opt_geometric.h"
#include "opt/opt_search.h"
#include "smt/smt_context.h"
#include "util/common_msgs.h"
#include "util/debug.h"
#include <climits>
#include <cstring>
#include <initializer_list>
#include <iostream>
namespace {
static void tst_objective_search() {
using opt::inf_eps;
inf_eps infinity(rational(1), inf_rational(0));
opt::objective_search search(infinity);
opt::maximize_result result{false, inf_eps(rational(3)), l_undef, {}};
search.update(result);
ENSURE(search.upper() == infinity);
result.bound_valid = true;
result.hint_status = l_true;
search.update(result);
ENSURE(search.upper() == infinity);
result.upper_bound = rational(10);
search.update(result);
ENSURE(search.upper() == inf_eps(rational(10)));
result = {false, inf_eps(rational(7)), l_false, {}};
search.update(result);
ENSURE(search.upper() == inf_eps(rational(7)));
result.hint = infinity;
search.update(result);
ENSURE(search.upper() == inf_eps(rational(7)));
search.enter_region(inf_eps(rational(2)));
search.enter_region(inf_eps(rational(5)));
search.tighten_upper(inf_eps(rational(4)));
ENSURE(search.upper() == inf_eps(rational(5)));
search.leave_region(l_undef);
search.tighten_upper(inf_eps(rational(3)));
ENSURE(search.upper() == inf_eps(rational(3)));
search.reset_regions();
ENSURE(search.upper() == inf_eps(rational(3)));
for (lbool status : {l_true, l_false, l_undef}) {
opt::objective_search trial(infinity);
trial.enter_region(inf_eps(rational(2)));
trial.enter_region(inf_eps(rational(1)));
trial.leave_region(status);
ENSURE(trial.upper() == (status == l_false ? inf_eps(rational(2)) : infinity));
trial.leave_region(l_undef);
ENSURE(trial.upper() == (status == l_false ? inf_eps(rational(2)) : infinity));
}
opt::objective_search bounded(inf_eps(rational(1)));
bounded.enter_region(inf_eps(rational(2)));
bounded.leave_region(l_false);
ENSURE(bounded.upper() == inf_eps(rational(1)));
opt::objective_search blockers(infinity);
blockers.exclude_below(inf_eps(rational(1)));
blockers.enter_region(inf_eps(rational(2)));
blockers.exclude_below(inf_eps(rational(6)));
blockers.leave_region(l_false);
ENSURE(blockers.upper() == inf_eps(rational(6)));
blockers.tighten_upper(inf_eps(rational(3)));
ENSURE(blockers.upper() == inf_eps(rational(3)));
blockers.enter_region(inf_eps(rational(10)));
blockers.reset_regions();
blockers.refute_region();
ENSURE(blockers.upper() == inf_eps(rational(1)));
}
static void tst_maximize_result() {
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
params_ref p;
p.set_uint("arith.solver", 6);
generic_model_converter fm(m, "maximize result");
opt::opt_solver s(m, p, fm);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref y(m.mk_const(symbol("y"), a.mk_real()), m);
s.assert_expr(a.mk_le(x, a.mk_numeral(rational(3), false)));
s.assert_expr(a.mk_le(y, a.mk_numeral(rational(7), false)));
ENSURE(s.check_sat(0, nullptr) == l_true);
s.add_objective(to_app(x));
s.add_objective(to_app(y));
expr_ref blocker(m);
auto first = s.maximize_objective(0, blocker);
ENSURE(first.bound_valid && first.hint_status == l_true);
ENSURE(first.hint == opt::inf_eps(rational(3)));
ENSURE(blocker);
ENSURE(s.saved_objective_value(0) == first.hint);
ENSURE(s.check_sat(0, nullptr) == l_true);
auto second = s.maximize_objective(1, blocker);
ENSURE(second.bound_valid && second.hint_status == l_true);
ENSURE(second.hint == opt::inf_eps(rational(7)));
ENSURE(blocker);
ENSURE(s.saved_objective_value(1) == second.hint);
ENSURE(first.bound_valid && first.hint_status == l_true);
ENSURE(first.hint == opt::inf_eps(rational(3)));
}
static void tst_arithmetic_scope_exits() {
for (bool isolated : {false, true})
for (bool interrupt : {false, true}) {
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
params_ref p;
p.set_uint("arith.solver", 6);
generic_model_converter fm(m, "arithmetic scopes");
struct bound_exit_solver : opt::opt_solver {
using opt_solver::opt_solver;
bool armed = false;
bool stopped = false;
void assert_expr_core(expr* e) override {
opt_solver::assert_expr_core(e);
if (armed) {
armed = false;
stopped = true;
ENSURE(get_scope_level() == 1);
throw default_exception("arithmetic scope test");
}
}
} s(m, p, fm);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
s.assert_expr(a.mk_le(x, a.mk_numeral(rational(3), false)));
{
expr_ref lhs(a.mk_mul(x, x), m);
expr_ref rhs(a.mk_numeral(rational(2), false), m);
s.assert_expr(a.mk_le(lhs, rhs));
}
solver::scoped_push caller_scope(s);
s.assert_expr(a.mk_ge(x, zero));
ENSURE(s.check_sat(0, nullptr) == l_true);
model_ref baseline;
s.get_model(baseline);
s.add_objective(to_app(x));
unsigned base_level = s.get_context().get_base_level();
unsigned assertions = s.get_num_assertions();
expr_ref blocker(m);
auto maximize = [&]() {
if (isolated)
return s.maximize_objective_isolated(0, baseline, blocker);
auto result = s.maximize_objective(0, blocker);
ENSURE(result.hint_status == l_false);
return result.bound_valid;
};
auto ensure_scopes = [&]() {
ENSURE(s.get_scope_level() == 1);
ENSURE(s.get_context().get_base_level() == base_level);
ENSURE(s.get_num_assertions() == assertions);
};
s.armed = interrupt;
bool threw = false;
try {
ENSURE(!maximize());
}
catch (default_exception const& ex) {
ENSURE(std::strcmp(ex.what(), "arithmetic scope test") == 0);
threw = true;
}
ENSURE(threw == interrupt && s.stopped == interrupt);
ensure_scopes();
{
solver::scoped_push check_scope(s);
s.assert_expr(m.mk_eq(x, zero));
ENSURE(s.check_sat(0, nullptr) == l_true);
}
ENSURE(s.check_sat(0, nullptr) == l_true);
ENSURE(!maximize());
ensure_scopes();
}
}
static void tst_dual_bound_premises() {
for (bool conditional : {false, true})
for (bool equality : {false, true}) {
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
params_ref p;
p.set_uint("arith.solver", 6);
generic_model_converter fm(m, "dual premises");
opt::opt_solver s(m, p, fm);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref y(m.mk_const(symbol("y"), a.mk_real()), m);
expr_ref b(m.mk_const(symbol("b"), m.mk_bool_sort()), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
expr_ref one(a.mk_numeral(rational(1), false), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
s.assert_expr(a.mk_ge(x, zero));
s.assert_expr(a.mk_ge(y, zero));
s.assert_expr(a.mk_le(y, one));
{
expr_ref lhs(a.mk_mul(x, x), m);
expr_ref rhs(a.mk_numeral(rational(3), false), m);
s.assert_expr(a.mk_le(lhs, rhs));
}
expr_ref cap(equality ? m.mk_eq(x, y) : a.mk_le(x, one), m);
if (conditional) {
s.assert_expr(m.mk_implies(b, cap));
{
expr_ref lhs(m.mk_not(b), m);
expr_ref rhs(equality ? m.mk_eq(x, a.mk_mul(two, y)) : a.mk_le(x, two), m);
s.assert_expr(m.mk_implies(lhs, rhs));
}
}
else
s.assert_expr(cap);
expr* assumption = b;
ENSURE(s.check_sat(conditional ? 1 : 0, conditional ? &assumption : nullptr) == l_true);
s.add_objective(to_app(x));
expr_ref blocker(m);
auto result = s.maximize_objective(0, blocker, true);
ENSURE(result.bound_valid && result.hint == opt::inf_eps(rational(1)));
ENSURE(result.upper_bound.has_value() == !conditional);
if (result.upper_bound)
ENSURE(*result.upper_bound == rational(1));
solver::scoped_push scope(s);
s.assert_expr(a.mk_gt(x, one));
ENSURE(s.check_sat(0, nullptr) == (conditional ? l_true : l_false));
}
}
static void tst_dual_bound_cancellation() {
for (unsigned region : {0u, 1u, 2u})
for (unsigned probe_budget : {0u, 1u, 100u, 1000u})
for (bool known_upper : {false, true})
for (bool enabled : {false, true}) {
bool conditional = region != 0;
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
opt::context ctx(m);
params_ref p;
p.set_uint("arith.solver", 6);
p.set_bool("optsmt_dual_bounds", enabled);
p.set_uint("optsmt_dual_bound_rlimit", probe_budget);
generic_model_converter fm(m, "dual cancellation");
struct cancel_bound_solver : opt::opt_solver {
using opt_solver::opt_solver;
bool armed = false;
bool stopped = false;
void assert_expr_core(expr* e) override {
opt_solver::assert_expr_core(e);
arith_util a(get_manager());
expr *lhs, *rhs;
rational value;
if (armed && a.is_ge(e, lhs, rhs) && a.is_numeral(rhs, value) && value == rational(2)) {
armed = false;
stopped = true;
get_manager().limit().cancel();
}
}
} s(m, p, fm);
opt::optsmt optimizer(m, ctx);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref y(m.mk_const(symbol("y"), a.mk_real()), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
expr_ref one(a.mk_numeral(rational(1), false), m);
expr_ref sum(a.mk_add(x, y), m);
s.assert_expr(a.mk_ge(x, zero));
s.assert_expr(a.mk_ge(y, zero));
if (conditional) {
expr_ref b(m.mk_const(symbol("b"), m.mk_bool_sort()), m);
s.assert_expr(m.mk_implies(b, region == 2 ? m.mk_eq(x, y) : a.mk_le(x, one)));
{
expr_ref lhs1(m.mk_not(b), m);
expr_ref rhs1(region == 2 ? m.mk_eq(x, one) : a.mk_le(x, y), m);
s.assert_expr(m.mk_implies(lhs1, rhs1));
}
}
else
s.assert_expr(a.mk_le(x, one));
s.assert_expr(a.mk_le(y, one));
{
expr_ref lhs1(a.mk_mul(x, x), m);
expr_ref rhs1(a.mk_mul(y, y), m);
s.assert_expr(a.mk_le(a.mk_add(lhs1, rhs1), one));
}
solver::scoped_push caller_scope(s);
ENSURE(s.check_sat(0, nullptr) == l_true);
model_ref mdl;
s.get_model(mdl);
ctx.set_model(mdl);
unsigned h = optimizer.add(to_app(sum));
optimizer.setup(s);
optimizer.updt_params(p);
rational initial;
ENSURE(opt::model_value_bound(a, (*mdl)(sum), true, initial));
optimizer.update_lower(h, opt::inf_eps(initial));
if (known_upper)
optimizer.update_upper(h, opt::inf_eps(rational(2)));
auto probe_count = [&]() {
statistics st;
s.collect_statistics(st);
for (unsigned i = 0; i < st.size(); ++i)
if (std::strcmp(st.get_key(i), "opt-dual-bound-checks") == 0)
return st.get_uint_value(i);
return 0u;
};
unsigned base_level = s.get_context().get_base_level();
unsigned assertions = s.get_num_assertions();
s.armed = true;
ENSURE(optimizer.lex(h, true) == l_undef);
ENSURE(s.stopped && m.limit().is_canceled());
m.limit().reset_cancel();
ENSURE(s.get_scope_level() == 1);
ENSURE(s.get_context().get_base_level() == base_level);
ENSURE(s.get_num_assertions() == assertions);
auto lower = optimizer.get_lower(h).rational_bound();
auto upper = optimizer.get_upper(h).rational_bound();
ENSURE(lower.is_finite() && lower.get_rational() >= rational(0));
ENSURE(lower.get_rational() * lower.get_rational() <= rational(2));
bool certified = known_upper || (enabled && (!conditional || probe_budget >= 100));
if (certified ? upper != opt::inf_eps(rational(2)) : upper.is_finite() || !upper.is_pos())
std::cerr << "dual bound: region=" << region << " budget=" << probe_budget
<< " enabled=" << enabled << " upper=" << upper << "\n";
ENSURE(certified ? upper == opt::inf_eps(rational(2)) : !upper.is_finite() && upper.is_pos());
unsigned probes = probe_count();
if (known_upper || !enabled || probe_budget == 0)
ENSURE(probes == 0);
if (conditional && !known_upper && enabled && probe_budget != 0)
ENSURE(probes > 0);
svector<symbol> labels;
optimizer.get_model(mdl, labels);
rational value;
ENSURE(mdl && opt::model_value_bound(a, (*mdl)(sum), true, value));
ENSURE(value == lower.get_rational());
ENSURE(optimizer.lex(h, true) == l_true);
ENSURE(optimizer.get_lower(h).exact_finite());
ENSURE(optimizer.get_lower(h) == optimizer.get_upper(h));
if (certified)
ENSURE(probe_count() == probes);
}
}
static void tst_dual_bound_probe_exits() {
for (unsigned interruption : {0u, 1u, 2u, 3u}) {
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
params_ref p;
p.set_uint("arith.solver", 6);
generic_model_converter fm(m, "dual probe exits");
struct probe_exit_solver : opt::opt_solver {
using opt_solver::opt_solver;
bool armed = false;
unsigned interruption = 0;
void assert_expr_core(expr* e) override {
opt_solver::assert_expr_core(e);
if (!armed)
return;
armed = false;
if (interruption == 2) {
get_manager().limit().inc(2000);
throw default_exception(Z3_MAX_RESOURCE_MSG);
}
get_manager().limit().cancel();
if (interruption == 1)
throw default_exception("dual probe test");
if (interruption == 3)
throw default_exception(Z3_CANCELED_MSG);
}
} s(m, p, fm);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref b(m.mk_const(symbol("b"), m.mk_bool_sort()), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
expr_ref one(a.mk_numeral(rational(1), false), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
s.assert_expr(a.mk_ge(x, zero));
s.assert_expr(a.mk_le(a.mk_mul(x, x), two));
s.assert_expr(m.mk_implies(b, a.mk_le(x, one)));
{
expr_ref lhs(m.mk_not(b), m);
expr_ref rhs(a.mk_le(x, two), m);
s.assert_expr(m.mk_implies(lhs, rhs));
}
solver::scoped_push caller_scope(s);
expr* assumption = b;
ENSURE(s.check_sat(1, &assumption) == l_true);
s.add_objective(to_app(x));
unsigned base_level = s.get_context().get_base_level();
unsigned assertions = s.get_num_assertions();
s.armed = true;
s.interruption = interruption;
expr_ref blocker(m);
bool threw = false;
try {
auto result = s.maximize_objective(0, blocker, true);
ENSURE(!result.upper_bound);
ENSURE(result.bound_valid == (interruption == 2));
}
catch (default_exception const& ex) {
ENSURE(std::strcmp(ex.what(), "dual probe test") == 0);
threw = true;
}
ENSURE(threw == (interruption == 1) && !s.armed);
ENSURE(m.limit().is_canceled() == (interruption != 2));
m.limit().reset_cancel();
ENSURE(s.get_scope_level() == 1);
ENSURE(s.get_context().get_base_level() == base_level);
ENSURE(s.get_num_assertions() == assertions);
{
solver::scoped_push scope(s);
s.assert_expr(m.mk_eq(x, zero));
ENSURE(s.check_sat(0, nullptr) == l_true);
}
ENSURE(s.check_sat(1, &assumption) == l_true);
auto result = s.maximize_objective(0, blocker, true);
ENSURE(result.bound_valid && !result.upper_bound);
}
}
static void tst_geometric_step() {
opt::geometric_step step;
ENSURE(step.value() == rational(1));
for (unsigned repeat = 0; repeat < 2; ++repeat) {
for (unsigned expected : {1u, 2u, 2u, 2u, 4u, 4u, 4u, 4u, 8u, 8u, 8u, 8u, 8u, 16u}) {
step.update(true);
ENSURE(step.value() == rational(expected));
}
step.reset();
ENSURE(step.value() == rational(1));
}
step.update(true);
step.update(false);
ENSURE(step.value() == rational(1));
step.update(true);
ENSURE(step.value() == rational(2));
step.update(true);
step.update(false);
step.update(false);
ENSURE(step.value() == rational(1));
for (unsigned expected : {1u, 2u, 2u, 2u, 2u, 4u}) {
step.update(true);
ENSURE(step.value() == rational(expected));
}
step.update(true);
step.reset();
step.reset();
step.update(true);
ENSURE(step.value() == rational(1));
step.update(true);
ENSURE(step.value() == rational(2));
step.reset();
rational expected(1);
for (unsigned doubles = 0; doubles < 70; ++doubles) {
for (unsigned round = 0; round < doubles + 1; ++round) {
step.update(true);
ENSURE(step.value() == expected);
}
step.update(true);
expected *= rational(2);
ENSURE(step.value() == expected);
}
step.update(false);
ENSURE(step.value() == rational(1));
step.reset();
step.update(true);
ENSURE(step.value() == rational(1));
step.update(true);
ENSURE(step.value() == rational(2));
}
struct opt_fixture {
Z3_context ctx;
Z3_optimize opt;
opt_fixture(char const* priority = "lex", unsigned rounds = 64, bool nlsat = true,
unsigned supremum_rlimit = 100000, char const* engine = "basic") {
Z3_config cfg = Z3_mk_config();
ctx = Z3_mk_context(cfg);
Z3_del_config(cfg);
Z3_set_error_handler(ctx, [](Z3_context, Z3_error_code) {});
opt = Z3_mk_optimize(ctx);
Z3_optimize_inc_ref(ctx, opt);
Z3_params p = Z3_mk_params(ctx);
Z3_params_inc_ref(ctx, p);
Z3_params_set_symbol(ctx, p, symbol("priority"), symbol(priority));
Z3_params_set_symbol(ctx, p, symbol("optsmt_engine"), symbol(engine));
Z3_params_set_bool(ctx, p, symbol("optsmt_nlsat"), nlsat);
Z3_params_set_uint(ctx, p, symbol("optsmt_bisect_rounds"), rounds);
Z3_params_set_uint(ctx, p, symbol("optsmt_nlsat_supremum_rlimit"), supremum_rlimit);
Z3_params_set_uint(ctx, p, symbol("smt.arith.solver"), std::strcmp(engine, "symba") == 0 ? 5 : 6);
Z3_params_set_uint(ctx, p, symbol("timeout"), 0);
Z3_params_set_uint(ctx, p, symbol("rlimit"), 1000000);
Z3_optimize_set_params(ctx, opt, p);
ENSURE(Z3_get_error_code(ctx) == Z3_OK);
Z3_params_dec_ref(ctx, p);
}
~opt_fixture() {
Z3_optimize_dec_ref(ctx, opt);
Z3_del_context(ctx);
}
opt_fixture(opt_fixture const&) = delete;
opt_fixture& operator=(opt_fixture const&) = delete;
Z3_symbol symbol(char const* name) { return Z3_mk_string_symbol(ctx, name); }
Z3_ast real(char const* name) { return Z3_mk_const(ctx, symbol(name), Z3_mk_real_sort(ctx)); }
Z3_ast num(int n, int d = 1) { return Z3_mk_real(ctx, n, d); }
Z3_ast sum(Z3_ast a, Z3_ast b) {
Z3_ast args[] = {a, b};
return Z3_mk_add(ctx, 2, args);
}
Z3_ast sub(Z3_ast a, Z3_ast b) {
Z3_ast args[] = {a, b};
return Z3_mk_sub(ctx, 2, args);
}
Z3_ast square(Z3_ast a) {
Z3_ast args[] = {a, a};
return Z3_mk_mul(ctx, 2, args);
}
void add(Z3_ast a) {
Z3_optimize_assert(ctx, opt, a);
ENSURE(Z3_get_error_code(ctx) == Z3_OK);
}
unsigned objective(Z3_ast a, bool maximize = true) {
unsigned h = maximize ? Z3_optimize_maximize(ctx, opt, a) : Z3_optimize_minimize(ctx, opt, a);
ENSURE(Z3_get_error_code(ctx) == Z3_OK);
return h;
}
Z3_lbool check() {
Z3_lbool r = Z3_optimize_check(ctx, opt, 0, nullptr);
ENSURE(Z3_get_error_code(ctx) == Z3_OK);
return r;
}
};
static Z3_ast root(opt_fixture& f, unsigned degree = 2) {
Z3_ast r = Z3_algebraic_root(f.ctx, f.num(2), degree);
ENSURE(r && Z3_is_algebraic_number(f.ctx, r));
ENSURE(Z3_algebraic_is_pos(f.ctx, r));
ENSURE(Z3_algebraic_eq(f.ctx, Z3_algebraic_power(f.ctx, r, degree), f.num(2)));
return r;
}
static Z3_ast shifted_root(opt_fixture& f, Z3_ast offset, bool positive) {
return positive ? Z3_algebraic_add(f.ctx, offset, root(f)) :
Z3_algebraic_sub(f.ctx, offset, root(f));
}
static void ensure_value(opt_fixture& f, Z3_ast actual, Z3_ast expected) {
ENSURE(actual && Z3_algebraic_is_value(f.ctx, actual));
ENSURE(Z3_is_algebraic_number(f.ctx, actual) == Z3_is_algebraic_number(f.ctx, expected));
ENSURE(Z3_algebraic_eq(f.ctx, actual, expected));
}
static void ensure_sort(opt_fixture& f, Z3_ast a, Z3_sort_kind kind) {
ENSURE(Z3_get_sort_kind(f.ctx, Z3_get_sort(f.ctx, a)) == kind);
}
static Z3_ast scalar_bound(opt_fixture& f, unsigned h, bool lower) {
Z3_ast r = lower ? Z3_optimize_get_lower(f.ctx, f.opt, h) :
Z3_optimize_get_upper(f.ctx, f.opt, h);
ENSURE(r && Z3_get_error_code(f.ctx) == Z3_OK);
return r;
}
static void ensure_vector(opt_fixture& f, unsigned h, bool lower, int infinity,
Z3_ast finite, int epsilon, Z3_sort_kind finite_sort) {
Z3_ast_vector v = lower ? Z3_optimize_get_lower_as_vector(f.ctx, f.opt, h) :
Z3_optimize_get_upper_as_vector(f.ctx, f.opt, h);
ENSURE(v && Z3_get_error_code(f.ctx) == Z3_OK);
Z3_ast_vector_inc_ref(f.ctx, v);
ENSURE(Z3_ast_vector_size(f.ctx, v) == 3);
ensure_value(f, Z3_ast_vector_get(f.ctx, v, 0), f.num(infinity));
ensure_value(f, Z3_ast_vector_get(f.ctx, v, 1), finite);
ensure_value(f, Z3_ast_vector_get(f.ctx, v, 2), f.num(epsilon));
ensure_sort(f, Z3_ast_vector_get(f.ctx, v, 0), Z3_INT_SORT);
ensure_sort(f, Z3_ast_vector_get(f.ctx, v, 1), finite_sort);
ensure_sort(f, Z3_ast_vector_get(f.ctx, v, 2), Z3_INT_SORT);
Z3_ast_vector_dec_ref(f.ctx, v);
}
static void tst_dual_bound_api() {
for (bool conditional : {false, true})
for (bool maximize : {false, true}) {
opt_fixture f;
Z3_params p = Z3_mk_params(f.ctx);
Z3_params_inc_ref(f.ctx, p);
Z3_params_set_uint(f.ctx, p, f.symbol("rlimit"), conditional ? 3000 : 20000);
Z3_optimize_set_params(f.ctx, f.opt, p);
Z3_params_dec_ref(f.ctx, p);
Z3_ast sum = f.num(0), squares = f.num(0);
for (char const* name : {"x", "y", "z", "w"}) {
Z3_ast x = f.real(name);
f.add(Z3_mk_ge(f.ctx, x, f.num(0)));
if (conditional && std::strcmp(name, "x") == 0) {
Z3_ast b = Z3_mk_const(f.ctx, f.symbol("b"), Z3_mk_bool_sort(f.ctx));
f.add(Z3_mk_implies(f.ctx, b, Z3_mk_le(f.ctx, x, f.num(1))));
f.add(Z3_mk_implies(f.ctx, Z3_mk_not(f.ctx, b), Z3_mk_le(f.ctx, x, f.real("y"))));
}
else
f.add(Z3_mk_le(f.ctx, x, f.num(1)));
sum = f.sum(sum, x);
squares = f.sum(squares, f.square(x));
}
f.add(Z3_mk_le(f.ctx, squares, f.num(2)));
unsigned h = f.objective(maximize ? f.sum(f.num(7), sum) : f.sub(f.num(7), sum), maximize);
ENSURE(f.check() == Z3_L_UNDEF);
Z3_ast bound = f.num(maximize ? 11 : 3);
ensure_value(f, scalar_bound(f, h, !maximize), bound);
ensure_vector(f, h, !maximize, 0, bound, 0, Z3_INT_SORT);
Z3_ast optimum = Z3_algebraic_root(f.ctx, f.num(8), 2);
optimum = maximize ? Z3_algebraic_add(f.ctx, f.num(7), optimum) :
Z3_algebraic_sub(f.ctx, f.num(7), optimum);
ENSURE(Z3_algebraic_le(f.ctx, scalar_bound(f, h, true), optimum));
ENSURE(Z3_algebraic_le(f.ctx, optimum, scalar_bound(f, h, false)));
}
opt_fixture f;
Z3_params p = Z3_mk_params(f.ctx);
Z3_params_inc_ref(f.ctx, p);
Z3_params_set_uint(f.ctx, p, f.symbol("rlimit"), 2000);
Z3_optimize_set_params(f.ctx, f.opt, p);
Z3_params_dec_ref(f.ctx, p);
Z3_ast x = f.real("x");
f.add(Z3_mk_ge(f.ctx, x, f.num(0)));
f.add(Z3_mk_le(f.ctx, x, f.num(2)));
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(2)));
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_UNDEF);
Z3_ast hi = scalar_bound(f, h, false);
ENSURE(Z3_algebraic_is_value(f.ctx, hi));
ENSURE(Z3_algebraic_le(f.ctx, root(f), hi));
ENSURE(Z3_algebraic_le(f.ctx, hi, f.num(2)));
for (char const* constraints : {
"(assert (and (>= x 0) (>= y 0) (<= y 2) (< (* x x) 2)"
" (=> b (<= x 2)) (=> (not b) (<= x y))))",
"(assert (and (> x 0) (> y 0) (= (* x y) 1) (=> b (<= x 1))))"}) {
opt_fixture interrupted;
Z3_params params = Z3_mk_params(interrupted.ctx);
Z3_params_inc_ref(interrupted.ctx, params);
Z3_params_set_uint(interrupted.ctx, params, interrupted.symbol("rlimit"), 2000);
Z3_optimize_set_params(interrupted.ctx, interrupted.opt, params);
Z3_params_dec_ref(interrupted.ctx, params);
std::string problem = "(declare-const x Real)(declare-const y Real)(declare-const b Bool)";
problem += constraints;
problem += "(maximize x)";
Z3_optimize_from_string(interrupted.ctx, interrupted.opt, problem.c_str());
ENSURE(Z3_get_error_code(interrupted.ctx) == Z3_OK);
ENSURE(interrupted.check() == Z3_L_UNDEF);
ENSURE(std::strcmp(Z3_optimize_get_reason_unknown(interrupted.ctx, interrupted.opt), "unknown") != 0);
ENSURE(Z3_algebraic_is_value(interrupted.ctx, scalar_bound(interrupted, 0, true)));
ENSURE(scalar_bound(interrupted, 0, false));
}
}
static void ensure_finite_bounds(opt_fixture& f, unsigned h, Z3_ast expected,
Z3_sort_kind kind = Z3_REAL_SORT) {
for (bool lower : {true, false}) {
Z3_ast b = scalar_bound(f, h, lower);
ensure_value(f, b, expected);
ensure_sort(f, b, kind);
ensure_vector(f, h, lower, 0, expected, 0, kind);
}
}
static void ensure_open_bounds(opt_fixture& f, unsigned h, Z3_ast finite, int epsilon,
Z3_sort_kind finite_sort = Z3_REAL_SORT) {
ENSURE(epsilon == -1 || epsilon == 1);
Z3_ast ep = f.real("epsilon");
Z3_ast expected = epsilon < 0 ? f.sub(finite, ep) : f.sum(finite, ep);
for (bool lower : {true, false}) {
Z3_ast b = scalar_bound(f, h, lower);
ENSURE(!Z3_algebraic_is_value(f.ctx, b));
ensure_sort(f, b, Z3_REAL_SORT);
Z3_ast same = Z3_simplify(f.ctx, Z3_mk_eq(f.ctx, b, expected));
ENSURE(Z3_get_bool_value(f.ctx, same) == Z3_L_TRUE);
ensure_vector(f, h, lower, 0, finite, epsilon, finite_sort);
}
}
static void ensure_model_value(opt_fixture& f, Z3_ast objective, Z3_ast expected) {
Z3_model m = Z3_optimize_get_model(f.ctx, f.opt);
ENSURE(m);
Z3_model_inc_ref(f.ctx, m);
Z3_ast value = nullptr;
ENSURE(Z3_model_eval(f.ctx, m, objective, true, &value));
ensure_value(f, value, expected);
Z3_model_dec_ref(f.ctx, m);
}
static void ensure_open_model(opt_fixture& f, Z3_ast objective, Z3_ast limit, bool maximize) {
Z3_model m = Z3_optimize_get_model(f.ctx, f.opt);
ENSURE(m);
Z3_model_inc_ref(f.ctx, m);
Z3_ast_vector hard = Z3_optimize_get_assertions(f.ctx, f.opt);
ENSURE(hard);
Z3_ast_vector_inc_ref(f.ctx, hard);
Z3_ast value = nullptr;
for (unsigned i = 0; i < Z3_ast_vector_size(f.ctx, hard); ++i) {
ENSURE(Z3_model_eval(f.ctx, m, Z3_ast_vector_get(f.ctx, hard, i), true, &value));
ENSURE(Z3_get_bool_value(f.ctx, value) == Z3_L_TRUE);
}
ENSURE(Z3_model_eval(f.ctx, m, objective, true, &value));
ENSURE(Z3_algebraic_is_value(f.ctx, value));
ENSURE(maximize ? Z3_algebraic_lt(f.ctx, value, limit) : Z3_algebraic_gt(f.ctx, value, limit));
Z3_ast_vector_dec_ref(f.ctx, hard);
Z3_model_dec_ref(f.ctx, m);
}
static void tst_signed_offsets() {
struct objective_case { int numerator, denominator; bool negate; };
objective_case const cases[] = {{0, 1, false}, {3, 1, false}, {3, 1, true}, {-3, 2, false}};
for (auto const& t : cases) {
for (bool maximize : {true, false}) {
opt_fixture f;
Z3_ast x = f.real("x");
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(2)));
Z3_ast offset = f.num(t.numerator, t.denominator);
Z3_ast objective = t.negate ? f.sub(offset, x) : f.sum(x, offset);
unsigned h = f.objective(objective, maximize);
ENSURE(f.check() == Z3_L_TRUE);
Z3_ast expected = shifted_root(f, offset, maximize);
ensure_finite_bounds(f, h, expected);
ensure_model_value(f, objective, expected);
}
}
}
static void tst_open_bounds() {
for (bool outside : {false, true}) {
for (bool maximize : {true, false}) {
opt_fixture f("lex", 8);
Z3_ast x = f.real("x");
f.add(outside ? Z3_mk_gt(f.ctx, f.square(x), f.num(2)) :
Z3_mk_lt(f.ctx, f.square(x), f.num(2)));
if (outside)
f.add(maximize ? Z3_mk_lt(f.ctx, x, f.num(0)) : Z3_mk_gt(f.ctx, x, f.num(0)));
unsigned h = f.objective(x, maximize);
ENSURE(f.check() == Z3_L_TRUE);
Z3_ast limit = shifted_root(f, f.num(0), maximize != outside);
ensure_open_bounds(f, h, limit, maximize ? -1 : 1);
ensure_open_model(f, x, limit, maximize);
}
}
for (int limit : {2, -2, 0}) {
opt_fixture f("lex", 8);
Z3_ast x = f.real("x");
if (limit > 0) {
f.add(Z3_mk_lt(f.ctx, f.square(x), f.num(4)));
f.add(Z3_mk_ge(f.ctx, f.square(f.sub(x, f.num(1))), f.num(1, 4)));
}
else {
f.add(Z3_mk_gt(f.ctx, f.square(x), f.num(limit * limit)));
f.add(Z3_mk_lt(f.ctx, x, f.num(0)));
}
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_TRUE);
ensure_open_bounds(f, h, f.num(limit), -1, Z3_INT_SORT);
ensure_open_model(f, x, f.num(limit), true);
}
}
static void tst_open_polynomial_objectives() {
struct objective_case { int scale, numerator, denominator; };
objective_case const cases[] = {{1, 3, 1}, {2, 3, 1}, {-1, 3, 1}, {1, -3, 2}};
for (auto const& t : cases) {
for (bool maximize : {true, false}) {
opt_fixture f("lex", 8);
Z3_ast x = f.real("x");
f.add(Z3_mk_lt(f.ctx, f.square(x), f.num(2)));
Z3_ast offset = f.num(t.numerator, t.denominator);
Z3_ast factors[] = {f.num(t.scale), x};
Z3_ast objective = f.sum(Z3_mk_mul(f.ctx, 2, factors), offset);
unsigned h = f.objective(objective, maximize);
ENSURE(f.check() == Z3_L_TRUE);
Z3_ast radius = Z3_algebraic_mul(f.ctx, f.num(t.scale < 0 ? -t.scale : t.scale), root(f));
Z3_ast limit = maximize ? Z3_algebraic_add(f.ctx, offset, radius) :
Z3_algebraic_sub(f.ctx, offset, radius);
ensure_open_bounds(f, h, limit, maximize ? -1 : 1);
ensure_open_model(f, objective, limit, maximize);
}
}
{
opt_fixture f("lex", 8);
Z3_ast x = f.real("x");
Z3_ast factors[] = {x, x, x};
f.add(Z3_mk_lt(f.ctx, Z3_mk_mul(f.ctx, 3, factors), f.num(2)));
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_TRUE);
ensure_open_bounds(f, h, root(f, 3), -1);
ensure_open_model(f, x, root(f, 3), true);
}
{
opt_fixture f("lex", 8);
Z3_ast x = f.real("x"), y = f.real("y");
f.add(Z3_mk_lt(f.ctx, f.sum(f.square(x), f.square(y)), f.num(1)));
Z3_ast objective = f.sum(x, y);
unsigned h = f.objective(objective);
ENSURE(f.check() == Z3_L_TRUE);
ensure_open_bounds(f, h, root(f), -1);
ensure_open_model(f, objective, root(f), true);
}
{
opt_fixture f("lex", 8, true, 1000000);
Z3_ast x = f.real("x"), y = f.real("y");
f.add(Z3_mk_gt(f.ctx, f.sum(x, y), f.num(1)));
Z3_ast objective = f.sum(f.square(x), f.square(y));
unsigned h = f.objective(objective, false);
ENSURE(f.check() == Z3_L_TRUE);
ensure_open_bounds(f, h, f.num(1, 2), 1);
ensure_open_model(f, objective, f.num(1, 2), false);
}
}
static void tst_multiobjective(bool box) {
opt_fixture f(box ? "box" : "lex");
Z3_ast x = f.real("x");
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(2)));
Z3_ast p = Z3_mk_const(f.ctx, f.symbol("p"), Z3_mk_bool_sort(f.ctx));
f.add(Z3_mk_not(f.ctx, p));
unsigned soft = Z3_optimize_assert_soft(f.ctx, f.opt, p, "5/2", f.symbol("cost"));
unsigned hi = f.objective(x);
Z3_ast shifted = f.sum(x, f.num(3));
unsigned lo = f.objective(shifted, false);
ENSURE(soft == 0 && hi == 1 && lo == 2);
ENSURE(f.check() == Z3_L_TRUE);
Z3_ast high_value = root(f);
Z3_ast low_value = shifted_root(f, f.num(3), !box);
auto ensure_bounds = [&]() {
ensure_finite_bounds(f, soft, f.num(5, 2));
ensure_finite_bounds(f, hi, high_value);
ensure_finite_bounds(f, lo, low_value);
};
ensure_bounds();
if (box) {
ENSURE(f.check() == Z3_L_TRUE);
ensure_model_value(f, x, high_value);
ensure_bounds();
ENSURE(f.check() == Z3_L_TRUE);
ensure_model_value(f, shifted, low_value);
ensure_bounds();
ENSURE(f.check() == Z3_L_FALSE);
}
else {
ensure_model_value(f, x, high_value);
ensure_model_value(f, shifted, low_value);
}
}
static void tst_lex_box_search() {
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
opt::context ctx(m);
params_ref p;
p.set_uint("arith.solver", 6);
generic_model_converter fm(m, "lex and box");
struct solver_with_unknown : opt::opt_solver {
using opt_solver::opt_solver;
bool unknown_on_next_check = false;
lbool check_sat_core2(unsigned n, expr* const* assumptions) override {
if (unknown_on_next_check) {
unknown_on_next_check = false;
return l_undef;
}
return opt_solver::check_sat_core2(n, assumptions);
}
} s(m, p, fm);
opt::optsmt optimizer(m, ctx);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref y(m.mk_const(symbol("y"), a.mk_real()), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
s.assert_expr(a.mk_ge(x, zero));
s.assert_expr(a.mk_ge(y, zero));
solver::scoped_push problem_scope(s);
{
expr_ref lhs(a.mk_add(x, y), m);
expr_ref rhs(a.mk_numeral(rational(5), false), m);
s.assert_expr(a.mk_le(lhs, rhs));
}
ENSURE(s.check_sat(0, nullptr) == l_true);
model_ref mdl;
s.get_model(mdl);
ctx.set_model(mdl);
unsigned first = optimizer.add(to_app(x));
unsigned second = optimizer.add(to_app(y));
optimizer.setup(s);
optimizer.updt_params(p);
rational initial_x, initial_y;
ENSURE(a.is_numeral((*mdl)(x), initial_x));
ENSURE(a.is_numeral((*mdl)(y), initial_y));
optimizer.update_lower(first, opt::inf_eps(initial_x));
optimizer.update_lower(second, opt::inf_eps(initial_y));
unsigned assertions = s.get_num_assertions();
auto ensure_problem_unchanged = [&]() {
ENSURE(s.get_scope_level() == 1);
ENSURE(s.get_num_assertions() == assertions);
};
auto ensure_bounds = [&](unsigned h, int expected) {
ENSURE(optimizer.get_lower(h).rational_bound() == opt::inf_eps(rational(expected)));
ENSURE(optimizer.get_upper(h).rational_bound() == opt::inf_eps(rational(expected)));
};
auto ensure_model = [&](model* witness, int expected_x, int expected_y) {
ENSURE(witness);
rational q;
ENSURE(a.is_numeral((*witness)(x), q) && q == rational(expected_x));
ENSURE(a.is_numeral((*witness)(y), q) && q == rational(expected_y));
};
s.unknown_on_next_check = true;
ENSURE(optimizer.lex(first, true) == l_undef);
ENSURE(!s.unknown_on_next_check);
ENSURE(optimizer.get_lower(second).rational_bound() == opt::inf_eps(initial_y));
ensure_problem_unchanged();
ENSURE(optimizer.lex(first, true) == l_true);
ensure_bounds(first, 5);
ENSURE(!optimizer.get_lower(second).is_finite());
ENSURE(optimizer.get_lower(second).rational_bound().is_neg());
ensure_problem_unchanged();
ENSURE(optimizer.lex(second, true) == l_true);
ensure_bounds(first, 5);
ensure_bounds(second, 0);
svector<symbol> labels;
optimizer.get_model(mdl, labels);
ensure_model(mdl.get(), 5, 0);
ensure_problem_unchanged();
ENSURE(optimizer.box() == l_true);
ensure_bounds(first, 5);
ensure_bounds(second, 5);
ensure_model(optimizer.get_model(first), 5, 0);
ensure_model(optimizer.get_model(second), 0, 5);
ensure_problem_unchanged();
}
static void tst_open_multiobjective(bool box, bool open_first, bool with_soft) {
opt_fixture f(box ? "box" : "lex", 8);
Z3_ast x = f.real("x"), y = f.real("y");
f.add(Z3_mk_lt(f.ctx, f.square(x), f.num(2)));
f.add(Z3_mk_le(f.ctx, y, f.num(3)));
unsigned soft = UINT_MAX;
if (with_soft) {
Z3_ast p = Z3_mk_const(f.ctx, f.symbol("p"), Z3_mk_bool_sort(f.ctx));
f.add(Z3_mk_not(f.ctx, p));
soft = Z3_optimize_assert_soft(f.ctx, f.opt, p, "5/2", f.symbol("cost"));
ENSURE(soft == 0);
}
unsigned first = f.objective(open_first ? x : y);
unsigned second = f.objective(open_first ? y : x);
ENSURE(first == (with_soft ? 1u : 0u) && second == first + 1);
unsigned open = open_first ? first : second;
unsigned closed = open_first ? second : first;
bool incomplete = !box && open_first;
ENSURE(f.check() == (incomplete ? Z3_L_UNDEF : Z3_L_TRUE));
Z3_ast limit = root(f);
auto ensure_bounds = [&]() {
ensure_open_bounds(f, open, limit, -1);
if (with_soft)
ensure_finite_bounds(f, soft, f.num(5, 2));
if (!incomplete)
ensure_finite_bounds(f, closed, f.num(3), Z3_INT_SORT);
};
ensure_bounds();
ensure_open_model(f, x, limit, true);
if (incomplete) {
char const* reason = Z3_optimize_get_reason_unknown(f.ctx, f.opt);
ENSURE(reason && std::strstr(reason,
"later lexicographic objectives after an unattained nonlinear optimum are not supported"));
ENSURE(!Z3_is_eq_ast(f.ctx, scalar_bound(f, closed, true), scalar_bound(f, closed, false)));
for (bool lower : {true, false}) {
Z3_ast_vector v = lower ? Z3_optimize_get_lower_as_vector(f.ctx, f.opt, closed) :
Z3_optimize_get_upper_as_vector(f.ctx, f.opt, closed);
ENSURE(v && Z3_get_error_code(f.ctx) == Z3_OK);
Z3_ast_vector_inc_ref(f.ctx, v);
ENSURE(Z3_ast_vector_size(f.ctx, v) == 3);
Z3_ast finite = Z3_ast_vector_get(f.ctx, v, 1);
ENSURE(Z3_is_numeral_ast(f.ctx, finite) && !Z3_is_algebraic_number(f.ctx, finite));
ensure_value(f, Z3_ast_vector_get(f.ctx, v, 2), f.num(0));
Z3_ast_vector_dec_ref(f.ctx, v);
}
}
else if (box) {
for (unsigned index = 1; index < (with_soft ? 3u : 2u); ++index) {
ENSURE(f.check() == Z3_L_TRUE);
ensure_bounds();
ensure_open_model(f, x, limit, true);
if (index == closed)
ensure_model_value(f, y, f.num(3));
}
ENSURE(f.check() == Z3_L_FALSE);
}
else {
ensure_model_value(f, y, f.num(3));
}
}
static void ensure_rational_interval(opt_fixture& f, unsigned h) {
Z3_ast lo = scalar_bound(f, h, true);
Z3_ast hi = scalar_bound(f, h, false);
ENSURE(Z3_is_numeral_ast(f.ctx, lo) && !Z3_is_algebraic_number(f.ctx, lo));
ENSURE(Z3_is_numeral_ast(f.ctx, hi) && !Z3_is_algebraic_number(f.ctx, hi));
Z3_ast optimum = root(f);
ENSURE(Z3_algebraic_ge(f.ctx, lo, f.num(0)));
ENSURE(Z3_algebraic_lt(f.ctx, lo, optimum));
ENSURE(Z3_algebraic_lt(f.ctx, optimum, hi));
ENSURE(Z3_algebraic_le(f.ctx, hi, f.num(2)));
ensure_vector(f, h, true, 0, lo, 0, Z3_get_sort_kind(f.ctx, Z3_get_sort(f.ctx, lo)));
ensure_vector(f, h, false, 0, hi, 0, Z3_get_sort_kind(f.ctx, Z3_get_sort(f.ctx, hi)));
}
static void tst_open_proof_budget() {
for (unsigned proof_budget : {0u, 1u}) {
opt_fixture f("lex", 8, true, proof_budget);
Z3_ast x = f.real("x");
f.add(Z3_mk_ge(f.ctx, x, f.num(0)));
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(2)));
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, root(f));
Z3_optimize_push(f.ctx, f.opt);
f.add(Z3_mk_lt(f.ctx, f.square(x), f.num(2)));
ENSURE(f.check() == Z3_L_UNDEF);
ensure_rational_interval(f, h);
ensure_open_model(f, x, root(f), true);
Z3_optimize_pop(f.ctx, f.opt);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, root(f));
}
}
static void tst_recheck_and_scopes() {
opt_fixture f("lex", 8);
Z3_ast x = f.real("x");
f.add(Z3_mk_ge(f.ctx, x, f.num(0)));
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(2)));
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, root(f));
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, root(f));
Z3_optimize_push(f.ctx, f.opt);
f.add(Z3_mk_le(f.ctx, x, f.num(1)));
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, f.num(1), Z3_INT_SORT);
Z3_optimize_pop(f.ctx, f.opt);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, root(f));
Z3_optimize_push(f.ctx, f.opt);
f.add(Z3_mk_lt(f.ctx, f.square(x), f.num(2)));
ENSURE(f.check() == Z3_L_TRUE);
ensure_open_bounds(f, h, root(f), -1);
ensure_open_model(f, x, root(f), true);
ENSURE(f.check() == Z3_L_TRUE);
ensure_open_bounds(f, h, root(f), -1);
ensure_open_model(f, x, root(f), true);
Z3_optimize_pop(f.ctx, f.opt);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, root(f));
}
static void tst_incremental_callback_bounds() {
for (bool maximize : {false, true}) {
opt_fixture f;
Z3_params p = Z3_mk_params(f.ctx);
Z3_params_inc_ref(f.ctx, p);
Z3_params_set_bool(f.ctx, p, f.symbol("incremental"), true);
Z3_optimize_set_params(f.ctx, f.opt, p);
ENSURE(Z3_get_error_code(f.ctx) == Z3_OK);
Z3_params_dec_ref(f.ctx, p);
Z3_ast x = f.real("x");
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(2)));
unsigned first = f.objective(x, maximize);
unsigned second = f.objective(f.sum(x, f.num(3)), !maximize);
struct callback_state {
opt_fixture& f;
unsigned first;
bool maximize;
bool added = false;
} state{f, first, maximize};
Z3_model model = Z3_mk_model(f.ctx);
Z3_model_inc_ref(f.ctx, model);
Z3_optimize_register_model_eh(f.ctx, f.opt, model, &state, [](void* data) {
auto& s = *static_cast<callback_state*>(data);
auto& f = s.f;
bool lower = !s.maximize;
if (s.added || !Z3_is_algebraic_number(f.ctx, scalar_bound(f, s.first, lower)))
return;
s.added = true;
f.add(Z3_mk_true(f.ctx));
ensure_vector(f, s.first, lower, s.maximize ? 1 : -1, f.num(0), 0, Z3_INT_SORT);
Z3_ast oo = Z3_mk_const(f.ctx, f.symbol("oo"), Z3_mk_int_sort(f.ctx));
Z3_ast expected = s.maximize ? oo : Z3_mk_unary_minus(f.ctx, oo);
Z3_ast same = Z3_simplify(f.ctx, Z3_mk_eq(f.ctx, scalar_bound(f, s.first, lower), expected));
ENSURE(Z3_get_bool_value(f.ctx, same) == Z3_L_TRUE);
});
ENSURE(f.check() == Z3_L_TRUE);
ENSURE(state.added);
Z3_ast value = shifted_root(f, f.num(0), maximize);
ensure_value(f, scalar_bound(f, first, maximize), value);
ensure_finite_bounds(f, second, shifted_root(f, f.num(3), maximize));
ensure_model_value(f, x, value);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, first, value);
ensure_finite_bounds(f, second, shifted_root(f, f.num(3), maximize));
Z3_model_dec_ref(f.ctx, model);
}
}
static void tst_fallback_intervals() {
for (bool uf : {false, true}) {
opt_fixture f("lex", 8, uf);
Z3_ast x = f.real("x");
f.add(Z3_mk_ge(f.ctx, x, f.num(0)));
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(2)));
if (uf) {
Z3_sort real = Z3_mk_real_sort(f.ctx);
Z3_func_decl g = Z3_mk_func_decl(f.ctx, f.symbol("g"), 1, &real, real);
Z3_ast gx = Z3_mk_app(f.ctx, g, 1, &x);
f.add(Z3_mk_ge(f.ctx, gx, f.num(0)));
}
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_UNDEF);
ensure_rational_interval(f, h);
}
}
enum class search_exit { unknown, cancel, exception };
class search_exit_solver : public opt::opt_solver {
public:
search_exit kind;
unsigned probe = 0;
unsigned stop_at;
bool stopped = false;
search_exit_solver(ast_manager& m, params_ref const& p, generic_model_converter& fm,
search_exit kind, unsigned stop_at):
opt_solver(m, p, fm), kind(kind), stop_at(stop_at) {}
void assert_expr_core(expr* e) override {
opt_solver::assert_expr_core(e);
if (kind == search_exit::cancel && stop_here())
get_manager().limit().cancel();
}
lbool check_sat_core2(unsigned n, expr* const* assumptions) override {
if (kind != search_exit::cancel && stop_here()) {
if (kind == search_exit::exception)
throw default_exception("search scope test");
return l_undef;
}
return opt_solver::check_sat_core2(n, assumptions);
}
private:
bool stop_here() {
if (stopped || get_scope_level() <= 2 || ++probe != stop_at)
return false;
stopped = true;
return true;
}
};
struct refuted_hint_run {
ast_manager& m;
arith_util& a;
search_exit_solver& s;
opt::optsmt& optimizer;
expr_ref x;
expr_ref zero;
unsigned h = 0; unsigned assertions = 0; };
static params_ref refuted_hint_params() {
params_ref p;
p.set_bool("optsmt_nlsat", false);
p.set_bool("optsmt_dual_bounds", false);
p.set_uint("arith.solver", 6);
return p;
}
static void assert_refuted_hint_problem(refuted_hint_run& r) {
r.s.assert_expr(r.a.mk_le(r.x, r.a.mk_numeral(rational(3), false)));
{
expr_ref lhs(r.a.mk_mul(r.x, r.x), r.m);
expr_ref rhs(r.a.mk_numeral(rational(2), false), r.m);
r.s.assert_expr(r.a.mk_le(lhs, rhs));
}
}
static void seed_objective(refuted_hint_run& r, params_ref& p, opt::context& ctx) {
ENSURE(r.s.check_sat(0, nullptr) == l_true);
model_ref mdl;
r.s.get_model(mdl);
ctx.set_model(mdl);
r.h = r.optimizer.add(to_app(r.x));
r.optimizer.setup(r.s);
r.optimizer.updt_params(p);
rational initial;
ENSURE(opt::model_value_bound(r.a, (*mdl)(r.x), true, initial));
r.optimizer.update_lower(r.h, opt::inf_eps(initial));
}
static void run_interrupted_lex(refuted_hint_run& r, search_exit exit) {
bool threw = false;
try {
ENSURE(r.optimizer.lex(r.h, true) == l_undef);
}
catch (default_exception const& ex) {
ENSURE(std::strcmp(ex.what(), "search scope test") == 0);
threw = true;
}
ENSURE(r.s.stopped);
ENSURE(threw == (exit == search_exit::exception));
if (exit == search_exit::cancel) {
ENSURE(r.m.limit().is_canceled());
r.m.limit().reset_cancel();
}
}
static void check_refuted_cap(refuted_hint_run const& r) {
auto upper = r.optimizer.get_upper(r.h).rational_bound();
ENSURE(upper.is_finite() && upper <= opt::inf_eps(rational(3)));
ENSURE(upper.get_rational() * upper.get_rational() >= rational(2));
}
static void check_solver_state_restored(refuted_hint_run const& r) {
ENSURE(r.s.get_scope_level() == 1);
ENSURE(r.s.get_num_assertions() == r.assertions);
}
static void check_no_leaked_trial_bound(refuted_hint_run& r) {
solver::scoped_push check_scope(r.s);
r.s.assert_expr(r.m.mk_eq(r.x, r.zero));
ENSURE(r.s.check_sat(0, nullptr) == l_true);
}
static void check_best_model_matches_lower(refuted_hint_run& r) {
model_ref mdl;
svector<symbol> labels;
r.optimizer.get_model(mdl, labels);
rational value;
ENSURE(mdl && opt::model_value_bound(r.a, (*mdl)(r.x), true, value));
ENSURE(value == r.optimizer.get_lower(r.h).rational_bound().get_rational());
}
static void tst_refuted_hint_exits() {
for (search_exit exit : {search_exit::unknown, search_exit::cancel, search_exit::exception})
for (bool known_upper : {false, true}) {
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
opt::context ctx(m);
params_ref p = refuted_hint_params();
generic_model_converter fm(m, "refuted hint");
search_exit_solver s(m, p, fm, exit, 1);
opt::optsmt optimizer(m, ctx);
refuted_hint_run r{m, a, s, optimizer,
expr_ref(m.mk_const(symbol("x"), a.mk_real()), m),
expr_ref(a.mk_numeral(rational(0), false), m)};
assert_refuted_hint_problem(r);
solver::scoped_push caller_scope(s);
s.assert_expr(a.mk_ge(r.x, r.zero));
seed_objective(r, p, ctx);
if (known_upper)
optimizer.update_upper(r.h, opt::inf_eps(rational(5)));
r.assertions = s.get_num_assertions();
run_interrupted_lex(r, exit);
check_refuted_cap(r);
check_solver_state_restored(r);
check_best_model_matches_lower(r);
check_no_leaked_trial_bound(r);
ENSURE(optimizer.lex(r.h, true) == l_undef);
check_solver_state_restored(r);
}
}
static void tst_search_scope_exits() {
for (search_exit exit : {search_exit::unknown, search_exit::cancel, search_exit::exception})
for (unsigned stop_at : {1u, 2u}) {
ast_manager m;
reg_decl_plugins(m);
arith_util a(m);
opt::context ctx(m);
params_ref p;
p.set_bool("optsmt_nlsat", false);
p.set_uint("optsmt_bisect_rounds", 8);
p.set_uint("arith.solver", 6);
generic_model_converter fm(m, "search scopes");
search_exit_solver s(m, p, fm, exit, stop_at);
opt::optsmt optimizer(m, ctx);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
{
expr_ref lhs(a.mk_mul(x, x), m);
expr_ref rhs(a.mk_numeral(rational(2), false), m);
s.assert_expr(a.mk_le(lhs, rhs));
}
solver::scoped_push caller_scope(s);
s.assert_expr(a.mk_ge(x, zero));
ENSURE(s.check_sat(0, nullptr) == l_true);
model_ref mdl;
s.get_model(mdl);
ctx.set_model(mdl);
unsigned h = optimizer.add(to_app(x));
optimizer.setup(s);
optimizer.updt_params(p);
rational initial;
ENSURE(opt::model_value_bound(a, (*mdl)(x), true, initial));
optimizer.update_lower(h, opt::inf_eps(initial));
unsigned assertions = s.get_num_assertions();
bool threw = false;
try {
ENSURE(optimizer.lex(h, true) == l_undef);
}
catch (default_exception const& ex) {
ENSURE(std::strcmp(ex.what(), "search scope test") == 0);
threw = true;
}
ENSURE(s.stopped);
ENSURE(threw == (exit == search_exit::exception));
ENSURE(s.get_scope_level() == 1);
ENSURE(s.get_num_assertions() == assertions);
if (exit == search_exit::cancel) {
ENSURE(m.limit().is_canceled());
m.limit().reset_cancel();
}
{
solver::scoped_push check_scope(s);
s.assert_expr(m.mk_eq(x, zero));
ENSURE(s.check_sat(0, nullptr) == l_true);
}
ENSURE(optimizer.lex(h, true) == l_undef);
ENSURE(s.get_scope_level() == 1);
ENSURE(s.get_num_assertions() == assertions);
}
}
static void tst_open_integer_fallback() {
opt_fixture f("lex", 8);
Z3_ast x = f.real("x");
Z3_sort integer = Z3_mk_int_sort(f.ctx);
Z3_ast n = Z3_mk_const(f.ctx, f.symbol("n"), integer);
f.add(Z3_mk_lt(f.ctx, x, Z3_mk_int2real(f.ctx, n)));
f.add(Z3_mk_lt(f.ctx, n, Z3_mk_int(f.ctx, 2, integer)));
f.add(Z3_mk_lt(f.ctx, f.square(x), f.num(4)));
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_UNDEF);
ensure_open_model(f, x, f.num(1), true);
for (bool lower : {true, false}) {
scalar_bound(f, h, lower);
Z3_ast_vector v = lower ? Z3_optimize_get_lower_as_vector(f.ctx, f.opt, h) :
Z3_optimize_get_upper_as_vector(f.ctx, f.opt, h);
ENSURE(v && Z3_get_error_code(f.ctx) == Z3_OK);
Z3_ast_vector_inc_ref(f.ctx, v);
ENSURE(Z3_ast_vector_size(f.ctx, v) == 3);
Z3_ast infinity = Z3_ast_vector_get(f.ctx, v, 0);
Z3_ast finite = Z3_ast_vector_get(f.ctx, v, 1);
ENSURE(Z3_algebraic_is_value(f.ctx, finite));
ensure_sort(f, infinity, Z3_INT_SORT);
ensure_sort(f, Z3_ast_vector_get(f.ctx, v, 2), Z3_INT_SORT);
ENSURE(lower ? Z3_algebraic_le(f.ctx, infinity, f.num(0)) :
Z3_algebraic_ge(f.ctx, infinity, f.num(0)));
if (Z3_algebraic_eq(f.ctx, infinity, f.num(0)))
ENSURE(lower ? Z3_algebraic_le(f.ctx, finite, f.num(1)) :
Z3_algebraic_ge(f.ctx, finite, f.num(1)));
Z3_ast_vector_dec_ref(f.ctx, v);
}
}
static void ensure_index_error(opt_fixture& f) {
ENSURE(Z3_get_error_code(f.ctx) == Z3_EXCEPTION);
ENSURE(std::strcmp(Z3_get_error_msg(f.ctx, Z3_EXCEPTION), "index out of bounds") == 0);
}
static void ensure_invalid_index(opt_fixture& f, unsigned h) {
ENSURE(!Z3_optimize_get_lower(f.ctx, f.opt, h));
ensure_index_error(f);
ENSURE(!Z3_optimize_get_upper(f.ctx, f.opt, h));
ensure_index_error(f);
ENSURE(!Z3_optimize_get_lower_as_vector(f.ctx, f.opt, h));
ensure_index_error(f);
ENSURE(!Z3_optimize_get_upper_as_vector(f.ctx, f.opt, h));
ensure_index_error(f);
}
static void tst_reset_and_invalid_indices(bool open) {
opt_fixture f("lex", 8);
ensure_invalid_index(f, 0);
ensure_invalid_index(f, UINT_MAX);
Z3_ast x = f.real("x");
Z3_optimize_push(f.ctx, f.opt);
f.add(open ? Z3_mk_lt(f.ctx, f.square(x), f.num(2)) : Z3_mk_le(f.ctx, f.square(x), f.num(2)));
unsigned h = f.objective(x);
ENSURE(h == 0);
ENSURE(f.check() == Z3_L_TRUE);
ensure_invalid_index(f, h + 1);
ensure_invalid_index(f, UINT_MAX);
if (open) {
ensure_open_bounds(f, h, root(f), -1);
ensure_open_model(f, x, root(f), true);
}
else {
ensure_finite_bounds(f, h, root(f));
}
Z3_optimize_pop(f.ctx, f.opt);
ENSURE(f.check() == Z3_L_TRUE);
ensure_invalid_index(f, h);
ensure_invalid_index(f, UINT_MAX);
f.add(Z3_mk_le(f.ctx, x, f.num(-1)));
unsigned replacement = f.objective(x);
ENSURE(replacement == h);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, replacement, f.num(-1), Z3_INT_SORT);
}
static void tst_nested_objective() {
for (bool maximize : {false, true})
for (bool reverse : {false, true}) {
opt_fixture f;
Z3_ast p = Z3_mk_const(f.ctx, f.symbol("p"), Z3_mk_bool_sort(f.ctx));
Z3_ast q = Z3_mk_const(f.ctx, f.symbol("q"), Z3_mk_bool_sort(f.ctx));
Z3_sort ints = Z3_mk_int_sort(f.ctx);
Z3_ast zero = Z3_mk_int(f.ctx, 0, ints);
Z3_ast one = Z3_mk_int(f.ctx, 1, ints);
Z3_ast two = Z3_mk_int(f.ctx, 2, ints);
Z3_ast sum = f.sum(Z3_mk_ite(f.ctx, p, one, zero), Z3_mk_ite(f.ctx, q, two, zero));
Z3_ast objective = Z3_mk_ite(f.ctx, Z3_mk_ge(f.ctx, one, sum),
reverse ? zero : one, reverse ? one : zero);
Z3_ast expected = maximize ? one : zero;
Z3_ast forced = reverse ? one : zero;
unsigned h = f.objective(objective, maximize);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, expected, Z3_INT_SORT);
ensure_model_value(f, objective, expected);
Z3_optimize_push(f.ctx, f.opt);
f.add(q);
f.add(Z3_mk_not(f.ctx, p));
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, forced, Z3_INT_SORT);
ensure_model_value(f, objective, forced);
Z3_optimize_pop(f.ctx, f.opt);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, expected, Z3_INT_SORT);
ensure_model_value(f, objective, expected);
}
}
static void tst_rational_bounds() {
for (bool maximize : {true, false}) {
opt_fixture f;
Z3_ast x = f.real("x");
f.add(Z3_mk_ge(f.ctx, x, f.num(-5, 2)));
f.add(Z3_mk_le(f.ctx, x, f.num(7, 3)));
unsigned h = f.objective(x, maximize);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, maximize ? f.num(7, 3) : f.num(-5, 2));
}
opt_fixture f;
Z3_ast x = f.real("x");
f.add(Z3_mk_le(f.ctx, f.square(x), f.num(4)));
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, f.num(2), Z3_INT_SORT);
}
static void ensure_symbolic_bounds(opt_fixture& f, unsigned h, int infinity, int finite, int epsilon) {
Z3_sort sort = epsilon ? Z3_mk_real_sort(f.ctx) : Z3_mk_int_sort(f.ctx);
Z3_ast terms[3];
unsigned size = 0;
if (infinity) {
Z3_ast oo = Z3_mk_const(f.ctx, f.symbol("oo"), sort);
terms[size++] = infinity > 0 ? oo : Z3_mk_unary_minus(f.ctx, oo);
}
if (finite)
terms[size++] = Z3_mk_int(f.ctx, finite, sort);
if (epsilon) {
Z3_ast ep = Z3_mk_const(f.ctx, f.symbol("epsilon"), Z3_mk_real_sort(f.ctx));
terms[size++] = epsilon > 0 ? ep : Z3_mk_unary_minus(f.ctx, ep);
}
ENSURE(size > 0);
Z3_ast expected = size == 1 ? terms[0] : Z3_mk_add(f.ctx, size, terms);
for (bool lower : {true, false}) {
Z3_ast b = scalar_bound(f, h, lower);
ENSURE(!Z3_algebraic_is_value(f.ctx, b));
Z3_ast same = Z3_simplify(f.ctx, Z3_mk_eq(f.ctx, b, expected));
ENSURE(Z3_get_bool_value(f.ctx, same) == Z3_L_TRUE);
ensure_vector(f, h, lower, infinity, f.num(finite), epsilon, Z3_INT_SORT);
}
}
static void tst_infinity_and_epsilon() {
for (bool maximize : {true, false}) {
opt_fixture unbounded;
unsigned h = unbounded.objective(unbounded.real("x"), maximize);
ENSURE(unbounded.check() == Z3_L_TRUE);
ensure_symbolic_bounds(unbounded, h, maximize ? 1 : -1, 0, 0);
opt_fixture strict;
Z3_ast x = strict.real("x");
strict.add(maximize ? Z3_mk_lt(strict.ctx, x, strict.num(1)) :
Z3_mk_gt(strict.ctx, x, strict.num(1)));
h = strict.objective(x, maximize);
ENSURE(strict.check() == Z3_L_TRUE);
ensure_symbolic_bounds(strict, h, 0, 1, maximize ? -1 : 1);
}
}
static void tst_lex_after_unbounded() {
for (bool minimize_first : {false, true}) {
opt_fixture f;
Z3_ast x = f.real("x"), y = f.real("y"), z = f.real("z");
f.add(Z3_mk_le(f.ctx, y, f.num(10)));
f.add(minimize_first ? Z3_mk_le(f.ctx, f.sum(x, y), f.num(0)) : Z3_mk_ge(f.ctx, x, y));
f.add(Z3_mk_le(f.ctx, z, f.sub(y, f.num(3))));
unsigned hx = f.objective(x, !minimize_first);
unsigned hy = f.objective(y);
unsigned hz = f.objective(z);
ENSURE(f.check() == Z3_L_TRUE);
ensure_symbolic_bounds(f, hx, minimize_first ? -1 : 1, 0, 0);
ensure_finite_bounds(f, hy, f.num(10), Z3_INT_SORT);
ensure_finite_bounds(f, hz, f.num(7), Z3_INT_SORT);
ensure_model_value(f, y, f.num(10));
ensure_model_value(f, z, f.num(7));
}
{
opt_fixture f;
Z3_ast x = f.real("x"), y = f.real("y");
Z3_ast b = Z3_mk_const(f.ctx, f.symbol("b"), Z3_mk_bool_sort(f.ctx));
Z3_ast capped[] = {Z3_mk_le(f.ctx, y, f.num(20)), Z3_mk_le(f.ctx, x, f.num(100))};
f.add(Z3_mk_implies(f.ctx, b, Z3_mk_and(f.ctx, 2, capped)));
f.add(Z3_mk_implies(f.ctx, Z3_mk_not(f.ctx, b), Z3_mk_le(f.ctx, y, f.num(10))));
unsigned hx = f.objective(x);
unsigned hy = f.objective(y);
ENSURE(f.check() == Z3_L_TRUE);
ensure_symbolic_bounds(f, hx, 1, 0, 0);
ensure_finite_bounds(f, hy, f.num(10), Z3_INT_SORT);
}
{
opt_fixture f;
Z3_sort ints = Z3_mk_int_sort(f.ctx);
Z3_ast x = Z3_mk_const(f.ctx, f.symbol("x"), ints);
Z3_ast y = Z3_mk_const(f.ctx, f.symbol("y"), ints);
f.add(Z3_mk_le(f.ctx, y, Z3_mk_int(f.ctx, 10, ints)));
f.add(Z3_mk_ge(f.ctx, x, y));
unsigned hx = f.objective(x);
unsigned hy = f.objective(y);
ENSURE(f.check() == Z3_L_TRUE);
ensure_symbolic_bounds(f, hx, 1, 0, 0);
ensure_finite_bounds(f, hy, Z3_mk_int(f.ctx, 10, ints), Z3_INT_SORT);
}
{
opt_fixture f;
Z3_ast x = f.real("x"), y = f.real("y");
f.add(Z3_mk_ge(f.ctx, y, x));
unsigned hx = f.objective(x);
unsigned hy = f.objective(y);
ENSURE(f.check() == Z3_L_TRUE);
ensure_symbolic_bounds(f, hx, 1, 0, 0);
Z3_ast lo = scalar_bound(f, hy, true);
ENSURE(Z3_is_numeral_ast(f.ctx, lo) && Z3_algebraic_is_pos(f.ctx, lo));
Z3_ast_vector v = Z3_optimize_get_upper_as_vector(f.ctx, f.opt, hy);
ENSURE(v && Z3_get_error_code(f.ctx) == Z3_OK);
Z3_ast_vector_inc_ref(f.ctx, v);
ensure_value(f, Z3_ast_vector_get(f.ctx, v, 0), f.num(1));
Z3_ast_vector_dec_ref(f.ctx, v);
}
}
static void tst_symba_bounds() {
for (bool strict : {false, true}) {
opt_fixture f("lex", 64, true, 100000, "symba");
Z3_ast x = f.real("x");
Z3_ast n = Z3_mk_const(f.ctx, f.symbol("n"), Z3_mk_int_sort(f.ctx));
Z3_ast slack = Z3_mk_int2real(f.ctx, n);
f.add(Z3_mk_ge(f.ctx, slack, f.num(0)));
Z3_ast total = f.sum(x, slack);
f.add(strict ? Z3_mk_lt(f.ctx, total, f.num(2)) : Z3_mk_le(f.ctx, total, f.num(2)));
unsigned h = f.objective(x);
ENSURE(f.check() == Z3_L_TRUE);
if (strict)
ensure_symbolic_bounds(f, h, 0, 2, -1);
else
ensure_finite_bounds(f, h, f.num(2), Z3_INT_SORT);
}
opt_fixture f("lex", 64, true, 100000, "symba");
Z3_ast x = f.real("x"), y = f.real("y");
Z3_ast n = Z3_mk_const(f.ctx, f.symbol("n"), Z3_mk_int_sort(f.ctx));
Z3_ast slack = Z3_mk_int2real(f.ctx, n);
f.add(Z3_mk_ge(f.ctx, x, f.num(0)));
f.add(Z3_mk_ge(f.ctx, y, f.num(0)));
f.add(Z3_mk_ge(f.ctx, slack, f.num(0)));
f.add(Z3_mk_le(f.ctx, f.sum(f.sum(x, y), slack), f.num(5)));
unsigned first = f.objective(x), second = f.objective(y);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, first, f.num(5), Z3_INT_SORT);
ensure_finite_bounds(f, second, f.num(0), Z3_INT_SORT);
}
static void tst_bitvector_bounds() {
for (bool maximize : {true, false}) {
opt_fixture f;
Z3_sort bv = Z3_mk_bv_sort(f.ctx, 8);
Z3_ast x = Z3_mk_const(f.ctx, f.symbol("x"), bv);
f.add(Z3_mk_bvuge(f.ctx, x, Z3_mk_int(f.ctx, 3, bv)));
f.add(Z3_mk_bvule(f.ctx, x, Z3_mk_int(f.ctx, 9, bv)));
unsigned h = f.objective(x, maximize);
ENSURE(f.check() == Z3_L_TRUE);
ensure_finite_bounds(f, h, f.num(maximize ? 9 : 3), Z3_INT_SORT);
}
}
}
void tst_opt_bounds() {
tst_objective_search();
std::cout << "opt_bounds: per-call arithmetic results\n";
tst_maximize_result();
std::cout << "opt_bounds: arithmetic scope exits\n";
tst_arithmetic_scope_exits();
std::cout << "opt_bounds: certified nonlinear dual bounds\n";
tst_dual_bound_premises();
tst_dual_bound_cancellation();
tst_dual_bound_probe_exits();
tst_dual_bound_api();
std::cout << "opt_bounds: geometric step schedule\n";
tst_geometric_step();
std::cout << "opt_bounds: signed algebraic optima and offsets\n";
tst_signed_offsets();
std::cout << "opt_bounds: lexicographic and box handles\n";
tst_multiobjective(false);
tst_multiobjective(true);
tst_lex_box_search();
std::cout << "opt_bounds: lexicographic objectives after an unbounded objective\n";
tst_lex_after_unbounded();
std::cout << "opt_bounds: finite open bounds and polynomial objectives\n";
tst_open_bounds();
tst_open_polynomial_objectives();
std::cout << "opt_bounds: open lexicographic and box handles\n";
for (bool with_soft : {false, true}) {
tst_open_multiobjective(false, true, with_soft);
tst_open_multiobjective(true, true, with_soft);
tst_open_multiobjective(false, false, with_soft);
}
std::cout << "opt_bounds: recheck, scopes, and unknown intervals\n";
tst_recheck_and_scopes();
tst_incremental_callback_bounds();
tst_open_proof_budget();
tst_fallback_intervals();
tst_search_scope_exits();
tst_refuted_hint_exits();
tst_open_integer_fallback();
std::cout << "opt_bounds: reset and invalid indices\n";
tst_reset_and_invalid_indices(false);
tst_reset_and_invalid_indices(true);
std::cout << "opt_bounds: rational, infinity, epsilon, and BV compatibility\n";
tst_nested_objective();
tst_rational_bounds();
tst_infinity_and_epsilon();
tst_symba_bounds();
tst_bitvector_bounds();
}