How to properly track z3::optimize unsat core?
The Z3 C++ z3::optimize does not find the expected solution when I add unsat_core tracking (based on these examples) (gcc 10.1.0).
Take the following problem: There are three consecutive points A, B, and C, where A and C are fixed to 0 and 200, respectively. Determine B's position such that B - A >= 10, C - B >= 15, and our optimization goal is minimize(C - B). The solution for this problem should be B = C - 15 = 200 - 15 = 185.
The untracked code below gives the correct solution.
#include <iostream>
#include <z3++.h>
int main()
{
z3::context ctx;
z3::optimize opt(ctx);
opt.add(ctx.int_const("A") == 0);
opt.add(ctx.int_const("B") - ctx.int_const("A") >= 10);
opt.add(ctx.int_const("C") - ctx.int_const("B") >= 15);
opt.add(ctx.int_const("C") == 200);
auto h = opt.minimize(ctx.int_const("C") - ctx.int_const("B"));
if (opt.check() != z3::sat)
std::cout << "unsat problem!\n" << opt.unsat_core() << std::endl;
else
std::cout << "model!\n" << opt.get_model() << std::endl;
return 0;
}
On the other hand, tracking the unsat_core with void add(expr const& e, expr const& t) return B=10, which is not the expect solution. Nonetheless, I am able to track the unsat core if need - e.g. adding opt.add(ctx.int_const("B") == 200, ctx.bool_const("t4")); creates an unsat. problem.
#include <iostream>
#include <z3++.h>
int main()
{
z3::context ctx;
z3::optimize opt(ctx);
opt.add(ctx.int_const("A") == 0, ctx.bool_const("t0"));
opt.add(ctx.int_const("B") - ctx.int_const("A") >= 10, ctx.bool_const("t1"));
opt.add(ctx.int_const("C") - ctx.int_const("B") >= 15, ctx.bool_const("t2"));
opt.add(ctx.int_const("C") == 200, ctx.bool_const("t3"));
auto h = opt.minimize(ctx.int_const("C") - ctx.int_const("B"));
if (opt.check() != z3::sat)
std::cout << "unsat problem!\n" << opt.unsat_core() << std::endl;
else
std::cout << "model!\n" << opt.get_model() << std::endl;
return 0;
}
Using z3::implies to track expressions does not work as expected either, but still provides unsat_core tracking capability.
#include <iostream>
#include <z3++.h>
int main()
{
z3::context ctx;
z3::optimize opt(ctx);
opt.add(z3::implies(ctx.bool_const("t0"), ctx.int_const("A") == 0));
opt.add(z3::implies(ctx.bool_const("t1"), ctx.int_const("B") - ctx.int_const("A") >= 10));
opt.add(z3::implies(ctx.bool_const("t2"), ctx.int_const("C") - ctx.int_const("B") >= 15));
opt.add(z3::implies(ctx.bool_const("t3"), ctx.int_const("C") == 200));
auto h = opt.minimize(ctx.int_const("C") - ctx.int_const("B"));
z3::expr_vector asv(ctx);
asv.push_back(ctx.bool_const("t0"));
asv.push_back(ctx.bool_const("t1"));
asv.push_back(ctx.bool_const("t2"));
asv.push_back(ctx.bool_const("t3"));
if (opt.check(asv) != z3::sat)
std::cout << "unsat problem!\n" << opt.unsat_core() << std::endl;
else
std::cout << "model!\n" << opt.get_model() << std::endl;
return 0;
}
Interestingly, adding a weight - i.e. handle add(expr const& e, unsigned weight) - to the expressions above - e.g. opt.add(z3::implies(ctx.bool_const("t0"), ctx.int_const("A") == 0), 1);, "forces" the optimizer to reach the correct solution.
What am I missing here?
EDIT: Curiously, if I add tracking variables t[0-4] to the optimizer - i.e. opt.add(ctx.bool_const("t0")); and so on, the optimizer finds the correct solution, but it loses the capability of tracking the unsat core expressions. That seems to make sense, considering I am changing the expression's purpose.