mirror of
https://github.com/Z3Prover/z3
synced 2026-06-12 03:45:38 +00:00
Fix NLA optimization regression and relax restore_x
- Relax restore_x() to handle backup/current size mismatches: when backup is shorter (new columns added), call move_non_basic_columns_to_bounds() to find a feasible solution. - Fix 100x performance regression in nonlinear optimization: save LP optimum before check_nla and return it as bound regardless of NLA result, so opt_solver::check_bound() can validate via full re-solve with accumulated NLA lemmas. - Refactor theory_lra::maximize() into three helpers: max_with_lp(), max_with_nl(), and max_result(). - Add mk_gt(theory_var, impq const&) overload for building blockers from saved LP optimum values. - Add BNH multi-objective optimization test (7/7 sat in <1s vs 1/7 in 30s before fix). - Add restore_x test for backup size mismatch handling. Fixes #8890 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
bb11a56a67
commit
6d890fb026
8 changed files with 357 additions and 61 deletions
|
|
@ -3983,12 +3983,86 @@ public:
|
|||
return inf_eps(rational(0), inf_rational(ival.x, ival.y));
|
||||
}
|
||||
|
||||
lp::lp_status max_with_lp(theory_var v, lpvar& vi, lp::impq& term_max) {
|
||||
if (!lp().is_feasible() || lp().has_changed_columns())
|
||||
make_feasible();
|
||||
vi = get_lpvar(v);
|
||||
auto st = lp().maximize_term(vi, term_max);
|
||||
if (has_int() && lp().has_inf_int()) {
|
||||
st = lp::lp_status::FEASIBLE;
|
||||
lp().restore_x();
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
// Returns true if NLA handled the result (blocker and result are set).
|
||||
// Returns false if maximize should fall through to the normal status switch.
|
||||
bool max_with_nl(theory_var v, lp::lp_status& st, unsigned level, expr_ref& blocker, inf_eps& result) {
|
||||
if (!m_nla || (st != lp::lp_status::OPTIMAL && st != lp::lp_status::UNBOUNDED))
|
||||
return false;
|
||||
// Save the LP optimum before NLA check may restore x.
|
||||
auto lp_val = value(v);
|
||||
auto lp_ival = get_ivalue(v);
|
||||
auto nla_st = check_nla(level);
|
||||
TRACE(opt, tout << "check_nla returned " << nla_st
|
||||
<< " lp_ival=" << lp_ival << "\n";
|
||||
if (nla_st == FC_CONTINUE) {
|
||||
tout << "LP assignment at maximize optimum:\n";
|
||||
for (unsigned j = 0; j < lp().column_count(); j++) {
|
||||
if (!lp().get_column_value(j).is_zero())
|
||||
tout << " x[" << j << "] = " << lp().get_column_value(j) << "\n";
|
||||
}
|
||||
});
|
||||
switch (nla_st) {
|
||||
case FC_DONE:
|
||||
// NLA satisfied: keep the optimal assignment, return LP value
|
||||
blocker = mk_gt(v);
|
||||
result = lp_val;
|
||||
st = lp::lp_status::FEASIBLE;
|
||||
return true;
|
||||
case FC_CONTINUE:
|
||||
// NLA found the LP optimum violates nonlinear constraints.
|
||||
// Restore x but return the LP optimum value and blocker
|
||||
// as a bound for the optimizer to validate via check_bound().
|
||||
lp().restore_x();
|
||||
blocker = mk_gt(v, lp_ival);
|
||||
result = lp_val;
|
||||
st = lp::lp_status::FEASIBLE;
|
||||
return true;
|
||||
case FC_GIVEUP:
|
||||
lp().restore_x();
|
||||
st = lp::lp_status::UNBOUNDED;
|
||||
return false;
|
||||
}
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
|
||||
theory_lra::inf_eps max_result(theory_var v, lpvar vi, lp::lp_status st, expr_ref& blocker, bool& has_shared) {
|
||||
switch (st) {
|
||||
case lp::lp_status::OPTIMAL:
|
||||
init_variable_values();
|
||||
TRACE(arith, display(tout << st << " v" << v << " vi: " << vi << "\n"););
|
||||
blocker = mk_gt(v);
|
||||
return value(v);
|
||||
case lp::lp_status::FEASIBLE:
|
||||
TRACE(arith, display(tout << st << " v" << v << " vi: " << vi << "\n"););
|
||||
blocker = mk_gt(v);
|
||||
return value(v);
|
||||
default:
|
||||
SASSERT(st == lp::lp_status::UNBOUNDED);
|
||||
TRACE(arith, display(tout << st << " v" << v << " vi: " << vi << "\n"););
|
||||
has_shared = false;
|
||||
blocker = m.mk_false();
|
||||
return inf_eps(rational::one(), inf_rational());
|
||||
}
|
||||
}
|
||||
|
||||
theory_lra::inf_eps maximize(theory_var v, expr_ref& blocker, bool& has_shared) {
|
||||
unsigned level = 2;
|
||||
lp::impq term_max;
|
||||
lp::lp_status st;
|
||||
lpvar vi = 0;
|
||||
unsigned size_of_backup = lp().column_count();
|
||||
if (has_int()) {
|
||||
lp().backup_x();
|
||||
}
|
||||
|
|
@ -4000,57 +4074,21 @@ public:
|
|||
st = lp::lp_status::UNBOUNDED;
|
||||
}
|
||||
else {
|
||||
if (!lp().is_feasible() || lp().has_changed_columns())
|
||||
make_feasible();
|
||||
|
||||
vi = get_lpvar(v);
|
||||
|
||||
st = lp().maximize_term(vi, term_max);
|
||||
|
||||
if (has_int() && lp().has_inf_int()) {
|
||||
st = lp::lp_status::FEASIBLE;
|
||||
if (lp().column_count() == size_of_backup)
|
||||
lp().restore_x();
|
||||
}
|
||||
if (m_nla && (st == lp::lp_status::OPTIMAL || st == lp::lp_status::UNBOUNDED)) {
|
||||
switch (check_nla(level)) {
|
||||
case FC_DONE:
|
||||
case FC_CONTINUE:
|
||||
st = lp::lp_status::FEASIBLE;
|
||||
break;
|
||||
case FC_GIVEUP:
|
||||
st = lp::lp_status::UNBOUNDED;
|
||||
break;
|
||||
}
|
||||
if (lp().column_count() == size_of_backup)
|
||||
lp().restore_x();
|
||||
}
|
||||
}
|
||||
switch (st) {
|
||||
case lp::lp_status::OPTIMAL: {
|
||||
init_variable_values();
|
||||
TRACE(arith, display(tout << st << " v" << v << " vi: " << vi << "\n"););
|
||||
auto val = value(v);
|
||||
blocker = mk_gt(v);
|
||||
return val;
|
||||
}
|
||||
case lp::lp_status::FEASIBLE: {
|
||||
auto val = value(v);
|
||||
TRACE(arith, display(tout << st << " v" << v << " vi: " << vi << "\n"););
|
||||
blocker = mk_gt(v);
|
||||
return val;
|
||||
}
|
||||
default:
|
||||
SASSERT(st == lp::lp_status::UNBOUNDED);
|
||||
TRACE(arith, display(tout << st << " v" << v << " vi: " << vi << "\n"););
|
||||
has_shared = false;
|
||||
blocker = m.mk_false();
|
||||
return inf_eps(rational::one(), inf_rational());
|
||||
st = max_with_lp(v, vi, term_max);
|
||||
inf_eps nl_result;
|
||||
if (max_with_nl(v, st, level, blocker, nl_result))
|
||||
return nl_result;
|
||||
}
|
||||
return max_result(v, vi, st, blocker, has_shared);
|
||||
}
|
||||
|
||||
expr_ref mk_gt(theory_var v) {
|
||||
lp::impq val = get_ivalue(v);
|
||||
return mk_gt(v, val);
|
||||
}
|
||||
|
||||
// Overload: create blocker from a saved impq value (used when x has been restored)
|
||||
expr_ref mk_gt(theory_var v, lp::impq const& val) {
|
||||
expr* obj = get_enode(v)->get_expr();
|
||||
rational r = val.x;
|
||||
expr_ref e(m);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue