#include "opt/opt_nlsat.h"
#include "ast/reg_decl_plugins.h"
#include "ast/rewriter/th_rewriter.h"
#include "math/polynomial/algebraic_numbers.h"
#include "util/rlimit.h"
#include <initializer_list>
static expr_ref exact_root(ast_manager& m, int radicand, unsigned degree = 2, bool negate = false) {
arith_util a(m);
auto& am = a.am();
scoped_anum value(am);
am.set(value, radicand);
am.root(value, degree, value);
if (negate)
am.neg(value);
return expr_ref(a.mk_numeral(am, value, false), m);
}
static void ensure_exact(ast_manager& m, expr* fact, bool expected = true) {
expr_ref simplified(fact, m);
th_rewriter rw(m);
rw(simplified);
ENSURE(expected ? m.is_true(simplified) : m.is_false(simplified));
}
static void ensure_witness(ast_manager& m, expr_ref_vector const& hard, expr* obj,
rational const& lo, opt::nlsat_opt::result const& res) {
ENSURE(res.m_model && res.m_value);
expr_ref value(m);
for (expr* f : hard) {
ENSURE(res.m_model->eval_expr(f, value, true));
ensure_exact(m, value);
}
ENSURE(res.m_model->eval_expr(obj, value, true));
ensure_exact(m, m.mk_eq(value, res.m_value));
arith_util a(m);
ENSURE(res.m_lower <= res.m_upper);
ensure_exact(m, a.mk_le(a.mk_numeral(lo, false), res.m_value));
ensure_exact(m, a.mk_le(a.mk_numeral(res.m_lower, false), res.m_value));
ensure_exact(m, a.mk_le(res.m_value, a.mk_numeral(res.m_upper, false)));
}
static void tst_attained() {
std::cout << "opt_nlsat: attained\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
expr_ref_vector hard(m);
hard.push_back(a.mk_le(a.mk_mul(x, x), two));
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
lbool r = opt.maximize(hard, x, rational(0), rational(2), 64, res);
ENSURE(r == l_true);
ENSURE(res.m_attained);
ENSURE(!res.m_unbounded && !res.m_has_sup && !res.m_open && !res.m_sup);
ENSURE(a.is_irrational_algebraic_numeral(res.m_value));
expr_ref expected = exact_root(m, 2);
ensure_exact(m, m.mk_eq(res.m_value, expected));
ensure_witness(m, hard, x, rational(0), res);
}
static void tst_unbounded() {
std::cout << "opt_nlsat: unbounded\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
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 one(a.mk_numeral(rational(1), false), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
expr_ref_vector hard(m);
hard.push_back(m.mk_eq(a.mk_mul(x, y), one));
hard.push_back(a.mk_gt(y, zero));
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
lbool r = opt.maximize(hard, x, rational(1), std::nullopt, 64, res);
ENSURE(r == l_true);
ENSURE(res.m_unbounded);
ENSURE(!res.m_attained);
ENSURE(!res.m_has_sup);
ENSURE(!res.m_open && !res.m_sup);
ensure_witness(m, hard, x, rational(1), res);
ENSURE(res.m_rounds <= 8);
}
static void tst_prove_unbounded() {
std::cout << "opt_nlsat: prove_unbounded\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
sort* real = a.mk_real();
expr_ref x(m.mk_const(symbol("x"), real), m);
expr_ref y(m.mk_const(symbol("y"), real), m);
expr_ref one(a.mk_numeral(rational(1), false), m);
expr_ref zero(a.mk_numeral(rational(0), false), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
params_ref p;
opt::nlsat_opt opt(m, p);
expr_ref_vector unbounded(m);
unbounded.push_back(m.mk_eq(a.mk_mul(x, y), one));
unbounded.push_back(a.mk_gt(y, zero));
ENSURE(opt.prove_unbounded(unbounded, x, rational(1)) == l_true);
expr_ref_vector division(m);
division.push_back(m.mk_eq(x, a.mk_div(one, y)));
division.push_back(a.mk_gt(y, zero));
ENSURE(opt.prove_unbounded(division, x, rational(1)) == l_true);
expr_ref_vector bounded(m);
bounded.push_back(a.mk_le(a.mk_mul(x, x), two));
ENSURE(opt.prove_unbounded(bounded, x, rational(0)) == l_false);
func_decl_ref f(m.mk_func_decl(symbol("f"), real, real), m);
expr_ref_vector outside(m);
outside.push_back(a.mk_le(m.mk_app(f, x.get()), two));
ENSURE(opt.prove_unbounded(outside, x, rational(0)) == l_undef);
expr_ref n(m.mk_const(symbol("n"), a.mk_int()), m);
expr_ref_vector mixed(m);
mixed.push_back(m.mk_eq(a.mk_mul(x, a.mk_sub(a.mk_to_real(n), one)), one));
mixed.push_back(a.mk_gt(n, a.mk_int(0)));
{
expr_ref lhs(a.mk_mul(n, n), m);
expr_ref rhs(a.mk_int(2), m);
mixed.push_back(a.mk_lt(lhs, rhs));
}
ENSURE(opt.prove_unbounded(mixed, x, rational(0)) == l_undef);
}
static void tst_open_sup() {
std::cout << "opt_nlsat: open supremum\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
expr_ref_vector hard(m);
hard.push_back(a.mk_lt(a.mk_mul(x, x), two));
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
lbool r = opt.maximize(hard, x, rational(0), rational(2), 8, res);
ENSURE(r == l_true);
ENSURE(!res.m_attained);
ENSURE(!res.m_unbounded);
ENSURE(res.m_has_sup && res.m_open && res.m_sup);
ENSURE(res.m_rounds == 8);
expr_ref expected = exact_root(m, 2);
ensure_exact(m, m.mk_eq(res.m_sup, expected));
ensure_witness(m, hard, x, rational(0), res);
ensure_exact(m, a.mk_lt(res.m_value, res.m_sup));
ensure_exact(m, a.mk_le(a.mk_numeral(res.m_sup_lower, false), res.m_sup));
ensure_exact(m, a.mk_le(res.m_sup, a.mk_numeral(res.m_sup_upper, false)));
}
static void tst_can_approach_from_below() {
std::cout << "opt_nlsat: can_approach_from_below\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref zero(a.mk_real(0), m), one(a.mk_real(1), m), two(a.mk_real(2), m);
expr_ref root = exact_root(m, 2);
params_ref p;
opt::nlsat_opt opt(m, p);
expr_ref_vector hard(m);
hard.push_back(a.mk_lt(a.mk_mul(x, x), two));
ENSURE(opt.can_approach_from_below(hard, x, rational(0), root) == l_true);
ENSURE(opt.can_approach_from_below(hard, x, rational(2), root) == l_false);
ENSURE(opt.can_approach_from_below(hard, x, rational(0), two) == l_false);
ENSURE(opt.can_approach_from_below(hard, x, rational(0), one) == l_true);
expr_ref above(a.mk_real(rational(5, 4)), m);
ensure_exact(m, a.mk_gt(above, one));
ensure_exact(m, a.mk_lt(a.mk_mul(above, above), two));
expr_ref_vector rational_limit(m);
{
expr_ref lhs(a.mk_mul(x, x), m);
expr_ref rhs(a.mk_real(4), m);
rational_limit.push_back(a.mk_lt(lhs, rhs));
}
ENSURE(opt.can_approach_from_below(rational_limit, x, rational(0), two) == l_true);
expr_ref negative_root = exact_root(m, 2, 2, true);
expr_ref_vector negative(m);
negative.push_back(a.mk_gt(a.mk_mul(x, x), two));
negative.push_back(a.mk_lt(x, zero));
ENSURE(opt.can_approach_from_below(negative, x, rational(-2), negative_root) == l_true);
expr_ref_vector isolated(m);
isolated.push_back(m.mk_eq(a.mk_mul(x, x), two));
isolated.push_back(a.mk_gt(x, zero));
ENSURE(opt.can_approach_from_below(isolated, x, rational(0), root) == l_false);
}
static void tst_projected_limits() {
std::cout << "opt_nlsat: projected limits with moving witnesses\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
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 one(a.mk_real(1), m), zero(a.mk_real(0), m), two(a.mk_real(2), m);
params_ref p;
opt::nlsat_opt opt(m, p);
expr_ref_vector hard(m);
hard.push_back(m.mk_eq(a.mk_mul(a.mk_sub(one, x), y), one));
hard.push_back(a.mk_gt(y, zero));
ENSURE(opt.can_approach_from_below(hard, x, rational(0), one) == l_true);
ENSURE(opt.can_approach_from_below(hard, x, rational(0), two) == l_false);
ENSURE(opt.prove_unbounded(hard, x, rational(0)) == l_false);
expr_ref point(m.mk_eq(x, zero), m);
expr_ref lhs(m.mk_eq(a.mk_mul(x, y), one), m);
expr_ref rhs(a.mk_gt(y, zero), m);
expr_ref ray(m.mk_and(lhs, rhs), m);
hard.reset();
hard.push_back(m.mk_or(point, ray));
ENSURE(opt.prove_unbounded(hard, x, rational(0)) == l_true);
ENSURE(opt.can_approach_from_below(hard, x, rational(-1), zero) == l_false);
ENSURE(opt.can_approach_from_below(hard, x, rational(0), two) == l_true);
}
static void tst_algebraic_eq() {
std::cout << "opt_nlsat: exact algebraic equality\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref q(a.mk_real(rational(-3, 2)), m), other(a.mk_real(2), m);
ensure_exact(m, opt::mk_algebraic_eq(m, q, q));
ensure_exact(m, opt::mk_algebraic_eq(m, other, q), false);
auto& am = a.am();
scoped_anum two(am), sqrt3(am), inner(am), outer(am);
am.set(two, 2);
am.set(sqrt3, 3);
am.root(sqrt3, 2, sqrt3);
expr_ref_vector roots(m);
for (bool plus : {false, true}) {
if (plus)
am.add(two, sqrt3, inner);
else
am.sub(two, sqrt3, inner);
am.root(inner, 2, outer);
roots.push_back(a.mk_numeral(am, outer, false));
am.neg(outer);
roots.push_back(a.mk_numeral(am, outer, false));
}
for (unsigned i = 0; i < roots.size(); ++i) {
ENSURE(a.is_irrational_algebraic_numeral(roots.get(i)));
for (unsigned j = 0; j < roots.size(); ++j)
ensure_exact(m, opt::mk_algebraic_eq(m, roots.get(i), roots.get(j)), i == j);
}
}
static void tst_open_proof_budget() {
std::cout << "opt_nlsat: open proof budget\n";
for (unsigned proof_budget : {0u, 1u}) {
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref_vector hard(m);
{
expr_ref lhs(a.mk_mul(x, x), m);
expr_ref rhs(a.mk_real(2), m);
hard.push_back(a.mk_lt(lhs, rhs));
}
expr_ref expected = exact_root(m, 2);
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
ENSURE(opt.maximize(hard, x, rational(0), rational(2), 8, res, proof_budget) == l_undef);
ENSURE(res.m_has_sup && res.m_sup);
ENSURE(!res.m_attained && !res.m_unbounded && !res.m_open);
ENSURE(res.m_rounds == 8);
ensure_exact(m, m.mk_eq(res.m_sup, expected));
ensure_witness(m, hard, x, rational(0), res);
ensure_exact(m, a.mk_lt(res.m_value, res.m_sup));
ENSURE(m.inc());
ENSURE(opt.maximize(hard, x, rational(0), rational(2), 8, res) == l_true);
ENSURE(res.m_open && res.m_has_sup && !res.m_attained && !res.m_unbounded);
ensure_exact(m, m.mk_eq(res.m_sup, expected));
ensure_witness(m, hard, x, rational(0), res);
ensure_exact(m, a.mk_lt(res.m_value, res.m_sup));
}
}
static void tst_result_reuse() {
std::cout << "opt_nlsat: result reuse and reset\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref square(a.mk_mul(x, x), m), two(a.mk_real(2), m);
expr_ref expected = exact_root(m, 2);
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
for (bool strict : {true, false, true}) {
expr_ref_vector hard(m);
hard.push_back(strict ? a.mk_lt(square, two) : a.mk_le(square, two));
ENSURE(opt.maximize(hard, x, rational(0), rational(2), 8, res) == l_true);
ENSURE(res.m_open == strict && res.m_has_sup == strict);
ENSURE(res.m_attained == !strict && !res.m_unbounded);
ENSURE(strict ? res.m_sup.get() != nullptr : res.m_sup.get() == nullptr);
ensure_exact(m, m.mk_eq(strict ? res.m_sup.get() : res.m_value.get(), expected));
ensure_witness(m, hard, x, rational(0), res);
if (strict)
ensure_exact(m, a.mk_lt(res.m_value, res.m_sup));
}
res.reset();
ENSURE(!res.m_value && !res.m_sup && !res.m_model);
ENSURE(!res.m_attained && !res.m_unbounded && !res.m_has_sup && !res.m_open);
ENSURE(res.m_rounds == 0);
}
static void tst_approach_outside_fragment() {
std::cout << "opt_nlsat: approachability outside fragment\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref n(m.mk_const(symbol("n"), a.mk_int()), m);
expr_ref two(a.mk_real(2), m);
params_ref p;
opt::nlsat_opt opt(m, p);
expr_ref_vector hard(m);
hard.push_back(a.mk_lt(a.mk_mul(x, x), two));
ENSURE(opt.can_approach_from_below(hard, x, rational(0), x) == l_undef);
ENSURE(opt.can_approach_from_below(hard, x, rational(0), nullptr) == l_undef);
sort_ref rhs(a.mk_real(), m);
sort_ref third(a.mk_real(), m);
func_decl_ref f(m.mk_func_decl(symbol("f"), rhs, third), m);
expr_ref fx(m.mk_app(f, x.get()), m);
ENSURE(opt.can_approach_from_below(hard, fx, rational(0), two) == l_undef);
hard.push_back(a.mk_lt(fx, two));
ENSURE(opt.can_approach_from_below(hard, x, rational(0), two) == l_undef);
expr_ref_vector mixed(m);
mixed.push_back(a.mk_lt(x, a.mk_to_real(n)));
mixed.push_back(a.mk_lt(n, a.mk_int(2)));
{
expr_ref lhs(a.mk_mul(x, x), m);
expr_ref rhs(a.mk_real(4), m);
mixed.push_back(a.mk_lt(lhs, rhs));
}
ENSURE(opt.can_approach_from_below(mixed, x, rational(0), two) == l_undef);
ENSURE(opt.can_approach_from_below(mixed, n, rational(0), two) == l_undef);
}
static void tst_approach_resource_scopes() {
std::cout << "opt_nlsat: approachability resource scopes\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref root = exact_root(m, 2);
expr_ref_vector hard(m);
{
expr_ref lhs(a.mk_mul(x, x), m);
expr_ref rhs(a.mk_real(2), m);
hard.push_back(a.mk_lt(lhs, rhs));
}
params_ref p;
opt::nlsat_opt opt(m, p);
uint64_t start = m.limit().count();
ENSURE(opt.can_approach_from_below(hard, x, rational(0), root, 0) == l_undef);
ENSURE(m.limit().count() == start);
ENSURE(opt.can_approach_from_below(hard, x, rational(0), root, 1) == l_undef);
ENSURE(m.inc());
ENSURE(opt.can_approach_from_below(hard, x, rational(0), root) == l_true);
{
scoped_rlimit enclosing(m.limit(), 1);
ENSURE(opt.can_approach_from_below(hard, x, rational(0), root) == l_undef);
ENSURE(!m.inc());
}
ENSURE(m.inc());
m.limit().cancel();
ENSURE(opt.can_approach_from_below(hard, x, rational(0), root) == l_undef);
ENSURE(m.limit().is_canceled());
m.limit().reset_cancel();
ENSURE(opt.can_approach_from_below(hard, x, rational(0), root) == l_true);
}
static void tst_cubic_roots() {
std::cout << "opt_nlsat: cubic roots\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
expr_ref_vector hard(m);
{
expr_ref lhs(a.mk_mul(x, a.mk_mul(x, x)), m);
expr_ref rhs(a.mk_mul(two, x), m);
hard.push_back(m.mk_eq(lhs, rhs));
}
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
lbool r = opt.maximize(hard, x, rational(-2), rational(2), 64, res);
ENSURE(r == l_true);
ENSURE(res.m_attained);
ENSURE(!res.m_unbounded && !res.m_has_sup && !res.m_open && !res.m_sup);
ENSURE(a.is_irrational_algebraic_numeral(res.m_value));
expr_ref expected = exact_root(m, 2);
ensure_exact(m, m.mk_eq(res.m_value, expected));
ensure_witness(m, hard, x, rational(-2), res);
}
static void tst_outside_fragment() {
std::cout << "opt_nlsat: outside fragment\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
sort* real = a.mk_real();
expr_ref x(m.mk_const(symbol("x"), real), m);
func_decl_ref f(m.mk_func_decl(symbol("f"), real, real), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
expr_ref_vector hard(m);
hard.push_back(a.mk_le(a.mk_mul(x, x), two));
hard.push_back(a.mk_le(m.mk_app(f, x.get()), two));
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
lbool r = opt.maximize(hard, x, rational(0), rational(2), 64, res);
ENSURE(r == l_undef);
ENSURE(!res.m_model);
ENSURE(!res.m_attained);
ENSURE(!res.m_value && !res.m_sup && !res.m_has_sup && !res.m_open && !res.m_unbounded);
}
static void tst_infeasible() {
std::cout << "opt_nlsat: infeasible\n";
ast_manager m;
reg_decl_plugins(m);
scoped_rlimit budget(m.limit(), 1000000);
arith_util a(m);
expr_ref x(m.mk_const(symbol("x"), a.mk_real()), m);
expr_ref two(a.mk_numeral(rational(2), false), m);
expr_ref_vector hard(m);
hard.push_back(a.mk_le(a.mk_mul(x, x), two));
params_ref p;
opt::nlsat_opt opt(m, p);
opt::nlsat_opt::result res(m);
lbool r = opt.maximize(hard, x, rational(3), rational(4), 64, res);
ENSURE(r == l_false);
ENSURE(!res.m_model);
ENSURE(!res.m_value && !res.m_sup && !res.m_attained && !res.m_has_sup && !res.m_open && !res.m_unbounded);
}
void tst_opt_nlsat() {
tst_attained();
tst_cubic_roots();
tst_unbounded();
tst_prove_unbounded();
tst_open_sup();
tst_can_approach_from_below();
tst_projected_limits();
tst_algebraic_eq();
tst_open_proof_budget();
tst_result_reuse();
tst_approach_outside_fragment();
tst_approach_resource_scopes();
tst_outside_fragment();
tst_infeasible();
}