3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-10-08 00:41:56 +00:00

generate more proper proof format

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2025-09-30 07:42:27 -07:00
parent 4162d89170
commit 3b1ac52ff9
3 changed files with 292 additions and 195 deletions

View file

@ -35,6 +35,7 @@ BraceWrapping:
AfterControlStatement: false AfterControlStatement: false
AfterNamespace: false AfterNamespace: false
AfterStruct: false AfterStruct: false
BeforeElse : true
# Spacing # Spacing
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false SpaceAfterLogicalNot: false

View file

@ -35,29 +35,6 @@ Potential auxiliary methods:
- tangent, ordering lemmas (lemmas that use values from the current model) - tangent, ordering lemmas (lemmas that use values from the current model)
- with internal bounded backtracking. - with internal bounded backtracking.
Currently missed lemma:
(30) j17 >= 0
(15) j4 - j7 >= 1
(51) j25 - j27 >= 1
(38) j11 - j23 >= 0
(9) j4 >= 1
(26) j5*j7 - j4*j11_ - j17 >= 0
(57) j11 >= 1
(10) j5 - j4 >= 0
(12) j7 >= 1
(46) - j5 + j23 + j27 >= 0
(44) j4 - j7 - j25 >= 0
j4 - j7 - j27 >= 1 (from (51) + (46))
j4 - j5 - j7 + j23 >= 1 (from (46))
j4 - j5 + j23 >= 2 (from (12))
j4 - j5 + j11 >= 2 (from (38))
j4j4 - j4j5 + j4j11 >= 2j4 (multiply by j4)
j4j11 >= 2j4 (subtract 10 multiplied by j4)
..
..
*/ */
#include "math/lp/nla_core.h" #include "math/lp/nla_core.h"
@ -91,8 +68,8 @@ namespace nla {
m_vars2mon.reset(); m_vars2mon.reset();
m_mon2vars.reset(); m_mon2vars.reset();
m_values.reset(); m_values.reset();
m_resolvents.reset(); m_multiplications.reset();
m_assumptions.reset(); m_justifications.reset();
m_monomials_to_refine.reset(); m_monomials_to_refine.reset();
m_false_constraints.reset(); m_false_constraints.reset();
m_factored_constraints.reset(); m_factored_constraints.reset();
@ -115,7 +92,7 @@ namespace nla {
m_values.push_back(c().val(v)); m_values.push_back(c().val(v));
if (m_coi.terms().contains(v)) { if (m_coi.terms().contains(v)) {
auto const& t = src.get_term(v); auto const& t = src.get_term(v);
// Assumption: variables in coefficients are always declared before term variable. // justification: variables in coefficients are always declared before term variable.
SASSERT(all_of(t, [&](auto p) { return p.j() < v; })); SASSERT(all_of(t, [&](auto p) { return p.j() < v; }));
w = dst.add_term(t.coeffs_as_vector(), v); w = dst.add_term(t.coeffs_as_vector(), v);
} }
@ -131,7 +108,7 @@ namespace nla {
auto rhs = lo_bound.x; auto rhs = lo_bound.x;
auto dep = src.get_column_lower_bound_witness(v); auto dep = src.get_column_lower_bound_witness(v);
auto ci = dst.add_var_bound(v, k, rhs); auto ci = dst.add_var_bound(v, k, rhs);
m_assumptions.insert(ci, assumptions{dep}); add_justification(ci, external_justification(dep));
} }
if (src.column_has_upper_bound(v)) { if (src.column_has_upper_bound(v)) {
auto hi_bound = src.get_upper_bound(v); auto hi_bound = src.get_upper_bound(v);
@ -140,7 +117,7 @@ namespace nla {
auto rhs = hi_bound.x; auto rhs = hi_bound.x;
auto dep = src.get_column_upper_bound_witness(v); auto dep = src.get_column_upper_bound_witness(v);
auto ci = dst.add_var_bound(v, k, rhs); auto ci = dst.add_var_bound(v, k, rhs);
m_assumptions.insert(ci, assumptions{dep}); add_justification(ci, external_justification(dep));
} }
} }
for (auto const &[v, vars] : m_mon2vars) for (auto const &[v, vars] : m_mon2vars)
@ -160,10 +137,10 @@ namespace nla {
// convert a conflict from m_solver.lra()/lia() into // convert a conflict from m_solver.lra()/lia() into
// a conflict for c().lra_solver() // a conflict for c().lra_solver()
// 1. constraints that are obtained by multiplication are explained from the original constraint // 1. constraints that are obtained by multiplication are explained from the original constraint
// 2. bounds assumptions are added as assumptions to the lemma. // 2. bounds justifications are added as justifications to the lemma.
// //
void stellensatz::add_lemma(lp::explanation const& ex1, vector<ineq> const& ineqs) { void stellensatz::add_lemma(lp::explanation const& ex1, vector<ineq> const& ineqs) {
TRACE(arith, display(tout)); TRACE(arith, display(tout); display_lemma(tout, ex1, ineqs));
auto& lra = c().lra_solver(); auto& lra = c().lra_solver();
lp::explanation ex2; lp::explanation ex2;
lemma_builder new_lemma(c(), "stellensatz"); lemma_builder new_lemma(c(), "stellensatz");
@ -179,34 +156,20 @@ namespace nla {
} }
// //
// a constraint can be explained by a set of bounds used as assumptions // a constraint can be explained by a set of bounds used as justifications
// and by an original constraint. // and by an original constraint.
// //
void stellensatz::explain_constraint(lemma_builder &new_lemma, lp::constraint_index ci, lp::explanation &ex) { void stellensatz::explain_constraint(lemma_builder &new_lemma, lp::constraint_index ci, lp::explanation &ex) {
if (m_constraints_in_conflict.contains(ci)) if (m_constraints_in_conflict.contains(ci))
return; return;
m_constraints_in_conflict.insert(ci); m_constraints_in_conflict.insert(ci);
if (!m_assumptions.contains(ci)) auto just = m_justifications.get(ci);
if (just == nullptr)
return; return;
auto const &bounds = m_assumptions[ci]; auto add_ineq = [&](bound_assumption const& i) {
for (auto const &b : bounds) { auto [j, k, rhs] = i;
if (std::holds_alternative<u_dependency *>(b)) {
auto dep = *std::get_if<u_dependency *>(&b);
m_solver.lra().push_explanation(dep, ex);
}
else if (std::holds_alternative<lp::constraint_index>(b)) {
auto c = *std::get_if<lp::constraint_index>(&b);
explain_constraint(new_lemma, c, ex);
}
else {
//
// the inequality was posted as an assumption
// negate it to add to the lemma
// recall that lemmas are represented in the form: /\ Assumptions => \/ C
//
auto [v, k, rhs] = *std::get_if<bound>(&b);
k = negate(k); k = negate(k);
if (m_solver.lra().var_is_int(v)) { if (m_solver.lra().var_is_int(j)) {
if (k == lp::lconstraint_kind::GT) { if (k == lp::lconstraint_kind::GT) {
rhs += 1; rhs += 1;
k = lp::lconstraint_kind::GE; k = lp::lconstraint_kind::GE;
@ -216,15 +179,44 @@ namespace nla {
k = lp::lconstraint_kind::LE; k = lp::lconstraint_kind::LE;
} }
} }
new_lemma |= ineq(v, k, rhs); new_lemma |= ineq(j, k, rhs);
};
auto &b = *just;
if (std::holds_alternative<external_justification>(b)) {
auto dep = *std::get_if<external_justification>(&b);
m_solver.lra().push_explanation(dep.dep, ex);
} }
else if (std::holds_alternative<internal_justification>(b)) {
auto c = *std::get_if<internal_justification>(&b);
svector<lp::constraint_index> cis;
m_solver.lra().dep_manager().linearize(c.dep, cis);
for (auto ci : cis)
explain_constraint(new_lemma, ci, ex);
} }
else if (std::holds_alternative<multiplication_justification>(b)) {
auto m = *std::get_if<multiplication_justification>(&b);
explain_constraint(new_lemma, m.ci, ex);
for (auto const &i : m.assumptions)
add_ineq(i);
}
else if (std::holds_alternative<resolvent>(b)) {
auto m = *std::get_if<resolvent>(&b);
explain_constraint(new_lemma, m.ci1, ex);
explain_constraint(new_lemma, m.ci2, ex);
}
else if (std::holds_alternative<bound_assumptions>(b)) {
auto ba = *std::get_if<bound_assumptions>(&b);
for (auto const &i : ba.bounds)
add_ineq(i);
}
else
UNREACHABLE();
} }
// //
// Emulate linearization within stellensatz to enforce simple axioms. // Emulate linearization within stellensatz to enforce simple axioms.
// Incremental linearization in the main procedure produces new atoms that are pushed to lemmas // Incremental linearization in the main procedure produces new atoms that are pushed to lemmas
// Here, it creates potentially new atoms from bounds assumptions, but it checks linear // Here, it creates potentially new atoms from bounds justifications, but it checks linear
// feasibility first. // feasibility first.
// //
// Use to_refine and model from c().lra_solver() for initial pass. // Use to_refine and model from c().lra_solver() for initial pass.
@ -262,28 +254,28 @@ namespace nla {
// //
// Sign axioms: // Sign axioms:
// x = 0 => x*y = 0 // x = 0 => x*y = 0
// the equation x*y = 0 is asserted with assumption x = 0 // the equation x*y = 0 is asserted with justification x = 0
// x > 0 & y < 0 => x*y < 0 // x > 0 & y < 0 => x*y < 0
// //
void stellensatz::saturate_signs(lpvar j, rational const& val_j, svector<lpvar> const& vars, void stellensatz::saturate_signs(lpvar j, rational const& val_j, svector<lpvar> const& vars,
rational const& val_vars) { rational const& val_vars) {
if (val_vars == 0 && val_j != 0) { if (val_vars == 0 && val_j != 0) {
assumptions bounds; bound_assumptions bounds{"signs = 0"};
lbool sign = add_bounds(vars, bounds); lbool sign = add_bounds(vars, bounds.bounds);
VERIFY(sign == l_undef); VERIFY(sign == l_undef);
add_ineq("signs = 0", bounds, j, lp::lconstraint_kind::EQ, rational(0)); add_ineq(bounds, j, lp::lconstraint_kind::EQ, rational(0));
} }
else if (val_j <= 0 && 0 < val_vars) { else if (val_j <= 0 && 0 < val_vars) {
assumptions bounds; bound_assumptions bounds{"signs > 0"};
lbool sign = add_bounds(vars, bounds); lbool sign = add_bounds(vars, bounds.bounds);
VERIFY(sign == l_false); VERIFY(sign == l_false);
add_ineq("signs > 0", bounds, j, lp::lconstraint_kind::GT, rational(0)); add_ineq(bounds, j, lp::lconstraint_kind::GT, rational(0));
} }
else if (val_j >= 0 && 0 > val_vars) { else if (val_j >= 0 && 0 > val_vars) {
assumptions bounds; bound_assumptions bounds{"signs < 0"};
lbool sign = add_bounds(vars, bounds); lbool sign = add_bounds(vars, bounds.bounds);
VERIFY(sign == l_true); VERIFY(sign == l_true);
add_ineq("signs < 0", bounds, j, lp::lconstraint_kind::LT, rational(0)); add_ineq(bounds, j, lp::lconstraint_kind::LT, rational(0));
} }
} }
@ -306,11 +298,11 @@ namespace nla {
return; return;
non_unit = v; non_unit = v;
} }
assumptions bounds; bound_assumptions bounds{"units"};
for (auto v : vars) { for (auto v : vars) {
if (m_values[v] == 1 || m_values[v] == -1) { if (m_values[v] == 1 || m_values[v] == -1) {
bound b(v, lp::lconstraint_kind::EQ, m_values[v]); bound_assumption b(v, lp::lconstraint_kind::EQ, m_values[v]);
bounds.push_back(b); bounds.bounds.push_back(b);
} }
} }
// assert j = +/- non_unit // assert j = +/- non_unit
@ -318,9 +310,9 @@ namespace nla {
lhs.add_monomial(rational(1), j); lhs.add_monomial(rational(1), j);
if (non_unit != lp::null_lpvar) { if (non_unit != lp::null_lpvar) {
lhs.add_monomial(rational(-sign), non_unit); lhs.add_monomial(rational(-sign), non_unit);
add_ineq("unit mul", bounds, lhs, lp::lconstraint_kind::EQ, rational(0)); add_ineq(bounds, lhs, lp::lconstraint_kind::EQ, rational(0));
} else { } else {
add_ineq("unit mul", bounds, lhs, lp::lconstraint_kind::EQ, rational(sign)); add_ineq(bounds, lhs, lp::lconstraint_kind::EQ, rational(sign));
} }
} }
@ -359,15 +351,15 @@ namespace nla {
// x > 1, y > 0 => xy > y // x > 1, y > 0 => xy > y
// x > 1, y < 1 => -xy > -y // x > 1, y < 1 => -xy > -y
void stellensatz::saturate_monotonicity(lpvar j, rational const& val_j, lpvar x, int sign_x, lpvar y, int sign_y) { void stellensatz::saturate_monotonicity(lpvar j, rational const& val_j, lpvar x, int sign_x, lpvar y, int sign_y) {
assumptions bounds; bound_assumptions bounds{"monotonicity"};
bound b1(x, sign_x < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::GT, rational(sign_x)); bound_assumption b1(x, sign_x < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::GT, rational(sign_x));
bound b2(y, sign_y < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::GT, rational(0)); bound_assumption b2(y, sign_y < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::GT, rational(0));
bounds.push_back(b1); bounds.bounds.push_back(b1);
bounds.push_back(b2); bounds.bounds.push_back(b2);
lp::lar_term lhs; lp::lar_term lhs;
lhs.add_monomial(rational(sign_x * sign_y), j); lhs.add_monomial(rational(sign_x * sign_y), j);
lhs.add_monomial(rational(-sign_y), y); lhs.add_monomial(rational(-sign_y), y);
add_ineq("monotonicity", bounds, lhs, lp::lconstraint_kind::GT, rational(0)); add_ineq(bounds, lhs, lp::lconstraint_kind::GT, rational(0));
} }
void stellensatz::saturate_squares(lpvar j, rational const& val_j, svector<lpvar> const& vars) { void stellensatz::saturate_squares(lpvar j, rational const& val_j, svector<lpvar> const& vars) {
@ -380,8 +372,8 @@ namespace nla {
return; return;
} }
// it is a square. // it is a square.
assumptions bounds; bound_assumptions bounds{"squares"};
add_ineq("squares", bounds, j, lp::lconstraint_kind::GE, rational(0)); add_ineq(bounds, j, lp::lconstraint_kind::GE, rational(0));
} }
rational stellensatz::value(lp::lar_term const &t) const { rational stellensatz::value(lp::lar_term const &t) const {
@ -398,8 +390,8 @@ namespace nla {
return p; return p;
} }
lp::constraint_index stellensatz::add_ineq(char const* rule, lp::constraint_index stellensatz::add_ineq(
assumptions const& bounds, justification const& bounds,
lp::lar_term const& t, lp::lconstraint_kind k, lp::lar_term const& t, lp::lconstraint_kind k,
rational const& rhs) { rational const& rhs) {
SASSERT(!t.coeffs_as_vector().empty()); SASSERT(!t.coeffs_as_vector().empty());
@ -407,18 +399,19 @@ namespace nla {
m_values.push_back(value(t)); m_values.push_back(value(t));
auto new_ci = m_solver.lra().add_var_bound(ti, k, rhs); auto new_ci = m_solver.lra().add_var_bound(ti, k, rhs);
TRACE(arith, TRACE(arith,
display_constraint(tout << rule << ": ", new_ci) << "\n"; // display_constraint(tout << bounds.rule << ": ", new_ci) << "\n";
if (!bounds.empty()) display(tout << "\t <- ", bounds) << "\n";); // if (!bounds.empty()) display(tout << "\t <- ", bounds) << "\n";
);
SASSERT(m_values.size() - 1 == ti); SASSERT(m_values.size() - 1 == ti);
m_assumptions.insert(new_ci, bounds); add_justification(new_ci, bounds);
init_occurs(new_ci); init_occurs(new_ci);
return new_ci; return new_ci;
} }
lp::constraint_index stellensatz::add_ineq(char const* rule, assumptions const& bounds, lpvar j, lp::lconstraint_kind k, lp::constraint_index stellensatz::add_ineq(justification const& just, lpvar j, lp::lconstraint_kind k,
rational const& rhs) { rational const& rhs) {
auto new_ci = m_solver.lra().add_var_bound(j, k, rhs); auto new_ci = m_solver.lra().add_var_bound(j, k, rhs);
m_assumptions.insert(new_ci, bounds); add_justification(new_ci, just);
init_occurs(new_ci); init_occurs(new_ci);
return new_ci; return new_ci;
} }
@ -467,33 +460,33 @@ namespace nla {
return i == a.size(); return i == a.size();
}; };
auto &constraints = m_solver.lra().constraints();
unsigned initial_false_constraints = m_false_constraints.size(); unsigned initial_false_constraints = m_false_constraints.size();
for (unsigned it = 0; it < m_false_constraints.size(); ++it) { for (unsigned it = 0; it < m_false_constraints.size(); ++it) {
if (it > 5 * initial_false_constraints) if (it > 5 * initial_false_constraints)
break; break;
auto ci1 = m_false_constraints[it]; auto ci1 = m_false_constraints[it];
auto const &con = m_solver.lra().constraints()[ci1]; auto const &con = constraints[ci1];
lpvar j = find_max_lex_monomial(con.lhs()); lpvar j = find_max_lex_monomial(con.lhs());
if (j == lp::null_lpvar) if (j == lp::null_lpvar)
continue; continue;
auto vars = m_mon2vars[j]; auto vars = m_mon2vars[j];
if (vars.size() > m_max_monomial_degree) if (vars.size() > m_max_monomial_degree)
continue; continue;
for (auto v : vars) { for (auto v : vars) {
if (v >= m_occurs.size()) if (v >= m_occurs.size())
continue; continue;
svector<lpvar> _vars;
_vars.push_back(v);
for (unsigned cidx = 0; cidx < m_occurs[v].size(); ++cidx) { for (unsigned cidx = 0; cidx < m_occurs[v].size(); ++cidx) {
auto ci2 = m_occurs[v][cidx]; auto ci2 = m_occurs[v][cidx];
if (ci1 == ci2) if (ci1 == ci2)
continue; continue;
for (auto const &cv2 : m_solver.lra().constraints()[ci2].lhs()) { for (auto const &cv2 : constraints[ci2].lhs()) {
auto u = cv2.j(); auto u = cv2.j();
if (u == v) if (u == v)
resolve(j, ci1, saturate_constraint(ci2, j, _vars)); resolve(j, ci1, saturate_multiply(ci2, j, u));
else if (is_mon_var(u) && is_subset(m_mon2vars[u], vars)) else if (is_mon_var(u) && is_subset(m_mon2vars[u], vars))
resolve(j, ci1, saturate_constraint(ci2, j, m_mon2vars[u])); resolve(j, ci1, saturate_multiply(ci2, j, u));
} }
} }
} }
@ -583,10 +576,8 @@ namespace nla {
auto rhs = mul1 * con1.rhs() + mul2 * con2.rhs(); auto rhs = mul1 * con1.rhs() + mul2 * con2.rhs();
if (lhs.size() == 0) // trivial conflict, will be found using LIA if (lhs.size() == 0) // trivial conflict, will be found using LIA
return; return;
assumptions bounds; resolvent r = {ci1, ci2, j};
bounds.push_back(ci1); auto new_ci = add_ineq(r, lhs, k1, rhs);
bounds.push_back(ci2);
auto new_ci = add_ineq("resolve", bounds, lhs, k1, rhs);
insert_monomials_from_constraint(new_ci); insert_monomials_from_constraint(new_ci);
IF_VERBOSE(3, verbose_stream() << "resolve on " << m_mon2vars[j] << " c1: " << c1 << " c2: " << c2 << "\n"; IF_VERBOSE(3, verbose_stream() << "resolve on " << m_mon2vars[j] << " c1: " << c1 << " c2: " << c2 << "\n";
display_constraint(verbose_stream(), ci1) << "\n"; display_constraint(verbose_stream(), ci1) << "\n";
@ -595,11 +586,12 @@ namespace nla {
} }
// multiply by remaining vars // multiply by remaining vars
lp::constraint_index stellensatz::saturate_constraint(lp::constraint_index old_ci, lpvar mi, svector<lpvar> const& xs) { lp::constraint_index stellensatz::saturate_multiply(lp::constraint_index old_ci, lpvar mi, lpvar j2) {
resolvent r = {old_ci, mi, xs}; multiplication m = {old_ci, mi, j2};
if (m_resolvents.contains(r)) if (m_multiplications.contains(m))
return lp::null_ci; return lp::null_ci;
m_resolvents.insert(r); m_multiplications.insert(m);
lp::lar_base_constraint const& con = m_solver.lra().constraints()[old_ci]; lp::lar_base_constraint const& con = m_solver.lra().constraints()[old_ci];
auto const& lhs = con.lhs(); auto const& lhs = con.lhs();
auto const& rhs = con.rhs(); auto const& rhs = con.rhs();
@ -610,17 +602,23 @@ namespace nla {
// xs is a proper subset of vars in mi // xs is a proper subset of vars in mi
svector<lpvar> vars(m_mon2vars[mi]); svector<lpvar> vars(m_mon2vars[mi]);
if (is_mon_var(j2)) {
auto const &xs = m_mon2vars[j2];
for (auto x : xs) { for (auto x : xs) {
SASSERT(vars.contains(x)); SASSERT(vars.contains(x));
vars.erase(x); vars.erase(x);
} }
SASSERT(!vars.empty()); SASSERT(!vars.empty());
SASSERT(vars.size() + xs.size() == m_mon2vars[mi].size()); SASSERT(vars.size() + xs.size() == m_mon2vars[mi].size());
}
else {
SASSERT(vars.contains(j2));
vars.erase(j2);
}
assumptions bounds; multiplication_justification just{old_ci, mi, j2};
// compute bounds constraints and sign of vars // compute bounds constraints and sign of vars
lbool sign = add_bounds(vars, bounds); lbool sign = add_bounds(vars, just.assumptions);
lp::lar_term new_lhs; lp::lar_term new_lhs;
rational new_rhs(rhs); rational new_rhs(rhs);
for (auto const & cv : lhs) { for (auto const & cv : lhs) {
@ -654,8 +652,7 @@ namespace nla {
} }
} }
bounds.push_back(old_ci); auto new_ci = add_ineq(just, new_lhs, k, new_rhs);
auto new_ci = add_ineq("superpose", bounds, new_lhs, k, new_rhs);
IF_VERBOSE(4, display_constraint(verbose_stream(), old_ci) << " -> "; IF_VERBOSE(4, display_constraint(verbose_stream(), old_ci) << " -> ";
display_constraint(verbose_stream(), new_lhs.coeffs_as_vector(), k, new_rhs) << "\n"); display_constraint(verbose_stream(), new_lhs.coeffs_as_vector(), k, new_rhs) << "\n");
insert_monomials_from_constraint(new_ci); insert_monomials_from_constraint(new_ci);
@ -763,8 +760,8 @@ namespace nla {
bool is_square = all_of(factors, [&](auto const &f) { return f.second % 2 == 0; }); bool is_square = all_of(factors, [&](auto const &f) { return f.second % 2 == 0; });
if (is_square) { if (is_square) {
auto v = add_term(t); auto v = add_term(t);
assumptions bounds; bound_assumptions bounds{"square >= 0"};
add_ineq("squares", bounds, v, lp::lconstraint_kind::GE, rational(0)); add_ineq(bounds, v, lp::lconstraint_kind::GE, rational(0));
} }
IF_VERBOSE( IF_VERBOSE(
@ -776,7 +773,7 @@ namespace nla {
// //
// p * q >= 0 => (p >= 0 & q >= 0) or (p <= 0 & q <= 0) // p * q >= 0 => (p >= 0 & q >= 0) or (p <= 0 & q <= 0)
// assert both factors with bound assumptions, and reference to original constraint // assert both factors with bound justifications, and reference to original constraint
// p >= 0 <- q >= 0 and // p >= 0 <- q >= 0 and
// q >= 0 <- p >= 0 // q >= 0 <- p >= 0
// the values of p, q in the current interpretation may not satisfy p*q >= 0 // the values of p, q in the current interpretation may not satisfy p*q >= 0
@ -797,10 +794,10 @@ namespace nla {
SASSERT(k < factors.size()); SASSERT(k < factors.size());
auto v = add_term(factors[k].first); auto v = add_term(factors[k].first);
assumptions bounds; bound_assumptions bounds{"factor = 0"};
bound b(v, lp::lconstraint_kind::EQ, rational(0)); bound_assumption b(v, lp::lconstraint_kind::EQ, rational(0));
bounds.push_back(b); bounds.bounds.push_back(b);
add_ineq("factor = 0", bounds, v, lp::lconstraint_kind::EQ, rational(0)); add_ineq(bounds, v, lp::lconstraint_kind::EQ, rational(0));
return true; return true;
} }
@ -810,11 +807,11 @@ namespace nla {
for (auto & f : factors) { for (auto & f : factors) {
if (f.second % 2 == 0) if (f.second % 2 == 0)
continue; continue;
assumptions bounds; bound_assumptions bounds{"factor >= 0"};
auto v = add_term(f.first); auto v = add_term(f.first);
auto k = m_values[v] > 0 ? lp::lconstraint_kind::GE : lp::lconstraint_kind::LE; auto k = m_values[v] > 0 ? lp::lconstraint_kind::GE : lp::lconstraint_kind::LE;
bounds.push_back(bound(v, k, rational(0))); bounds.bounds.push_back(bound_assumption(v, k, rational(0)));
add_ineq("factor >= 0", bounds, v, k, rational(0)); add_ineq(bounds, v, k, rational(0));
} }
return true; return true;
} }
@ -841,15 +838,15 @@ namespace nla {
} }
// //
// add bound assumptions from vars // add bound justifications from vars
// return the sign of the product of vars under the current interpretation. // return the sign of the product of vars under the current interpretation.
// //
lbool stellensatz::add_bounds(svector<lpvar> const& vars, assumptions& bounds) { lbool stellensatz::add_bounds(svector<lpvar> const& vars, vector<bound_assumption>& bounds) {
bool sign = false; bool sign = false;
auto &src = c().lra_solver(); auto &src = c().lra_solver();
auto &dst = m_solver.lra(); auto &dst = m_solver.lra();
auto add_bound = [&](lpvar v, lp::lconstraint_kind k, int r) { auto add_bound = [&](lpvar v, lp::lconstraint_kind k, int r) {
bound b(v, k, rational(r)); bound_assumption b(v, k, rational(r));
bounds.push_back(b); bounds.push_back(b);
}; };
for (auto v : vars) { for (auto v : vars) {
@ -859,19 +856,7 @@ namespace nla {
return l_undef; return l_undef;
} }
for (auto v : vars) { for (auto v : vars) {
// retrieve bounds of v if (m_values[v] < 0) {
// if v has positive lower bound add as positive
// if v has negative upper bound add as negative
// otherwise look at the current value of v and add bounds assumption based on current sign.
// todo: detect squares, allow for EQ but skip bounds assumptions.
if (src.number_of_vars() > v && src.column_has_lower_bound(v) && src.get_lower_bound(v).is_pos()) {
bounds.push_back(src.get_column_lower_bound_witness(v));
}
else if (src.number_of_vars() > v && src.column_has_upper_bound(v) && src.get_upper_bound(v).is_neg()) {
bounds.push_back(src.get_column_upper_bound_witness(v));
sign = !sign;
}
else if (m_values[v] < 0) {
add_bound(v, lp::lconstraint_kind::LT, 0); add_bound(v, lp::lconstraint_kind::LT, 0);
sign = !sign; sign = !sign;
} }
@ -912,6 +897,8 @@ namespace nla {
m_values.push_back(t.second); m_values.push_back(t.second);
m_solver.lra().add_var_bound(w, lp::lconstraint_kind::GE, t.second); m_solver.lra().add_var_bound(w, lp::lconstraint_kind::GE, t.second);
m_solver.lra().add_var_bound(w, lp::lconstraint_kind::LE, t.second); m_solver.lra().add_var_bound(w, lp::lconstraint_kind::LE, t.second);
m_justifications.push_back(nullptr);
m_justifications.push_back(nullptr);
t.first.add_monomial(rational(1), w); t.first.add_monomial(rational(1), w);
t.second = 0; t.second = 0;
} }
@ -980,33 +967,52 @@ namespace nla {
display_constraint(out, ci); display_constraint(out, ci);
bool is_true = constraint_is_true(ci); bool is_true = constraint_is_true(ci);
out << (is_true ? " [true]" : " [false]") << "\n"; out << (is_true ? " [true]" : " [false]") << "\n";
if (!m_assumptions.contains(ci)) auto j = m_justifications.get(ci);
if (!j)
continue; continue;
out << "\t<- "; out << "\t<- ";
display(out, m_assumptions[ci]); display(out, *j);
out << "\n"; out << "\n";
} }
return out; return out;
} }
std::ostream& stellensatz::display(std::ostream& out, assumptions const& bounds) const { std::ostream& stellensatz::display(std::ostream& out, justification const& b) const {
for (auto b : bounds) { if (std::holds_alternative<external_justification>(b)) {
if (std::holds_alternative<u_dependency *>(b)) { auto dep = *std::get_if<external_justification>(&b);
auto dep = *std::get_if<u_dependency *>(&b);
unsigned_vector cs; unsigned_vector cs;
c().lra_solver().dep_manager().linearize(dep, cs); c().lra_solver().dep_manager().linearize(dep.dep, cs);
for (auto c : cs) for (auto c : cs)
out << "[o " << c << "] "; // constraint index from c().lra_solver. out << "[o " << c << "] "; // constraint index from c().lra_solver.
} }
else if (std::holds_alternative<lp::constraint_index>(b)) { else if (std::holds_alternative<internal_justification>(b)) {
auto ci = *std::get_if<lp::constraint_index>(&b); auto c = *std::get_if<internal_justification>(&b);
svector<lp::constraint_index> cis;
m_solver.lra().dep_manager().linearize(c.dep, cis);
for (auto ci : cis)
out << "(" << ci << ") "; // constraint index from this solver. out << "(" << ci << ") "; // constraint index from this solver.
} }
else { else if (std::holds_alternative<multiplication_justification>(b)) {
auto [v, k, rhs] = *std::get_if<bound>(&b); auto m = *std::get_if<multiplication_justification>(&b);
out << "[(" << m.ci << ") *= " << pp_j(*this, m.j1) << " / " << pp_j(*this, m.j2) << "] ";
for (auto const &ineq : m.assumptions) {
auto [v, k, rhs] = ineq;
out << "[j" << v << " " << k << " " << rhs << "] "; out << "[j" << v << " " << k << " " << rhs << "] ";
} }
} }
else if (std::holds_alternative<resolvent>(b)) {
auto m = *std::get_if<resolvent>(&b);
out << "[resolve (" << m.ci1 << ") (" << m.ci2 << ") on " << pp_j(*this, m.j) << "] ";
}
else if (std::holds_alternative<bound_assumptions>(b)) {
auto ba = *std::get_if<bound_assumptions>(&b);
out << ba.rule << " ";
for (auto const &ineq : ba.bounds) {
auto [v, k, rhs] = ineq;
out << "[j" << v << " " << k << " " << rhs << "] ";
}
} else
UNREACHABLE();
return out; return out;
} }
@ -1027,16 +1033,21 @@ namespace nla {
for (auto [v, coeff] : t.first) { for (auto [v, coeff] : t.first) {
c().display_coeff(out, first, coeff); c().display_coeff(out, first, coeff);
first = false; first = false;
if (is_mon_var(v)) out << pp_j(*this, v);
display_product(out, m_mon2vars[v]);
else
out << "j" << v;
} }
if (t.second != 0) if (t.second != 0)
out << " + " << t.second; out << " + " << t.second;
return out; return out;
} }
std::ostream& stellensatz::display_var(std::ostream& out, lpvar j) const {
if (is_mon_var(j))
display_product(out, m_mon2vars[j]);
else
out << "j" << j;
return out;
}
std::ostream& stellensatz::display_constraint(std::ostream& out, lp::constraint_index ci) const { std::ostream& stellensatz::display_constraint(std::ostream& out, lp::constraint_index ci) const {
auto const& con = m_solver.lra().constraints()[ci]; auto const& con = m_solver.lra().constraints()[ci];
return display_constraint(out, con.lhs(), con.kind(), con.rhs()); return display_constraint(out, con.lhs(), con.kind(), con.rhs());
@ -1058,6 +1069,48 @@ namespace nla {
return out << " " << k << " " << rhs; return out << " " << k << " " << rhs;
} }
std::ostream& stellensatz::display_lemma(std::ostream& out, lp::explanation const& ex,
vector<ineq> const& ineqs) {
m_constraints_in_conflict.reset();
svector<lp::constraint_index> todo;
for (auto c : ex)
todo.push_back(c.ci());
for (unsigned i = 0; i < todo.size(); ++i) {
auto ci = todo[i];
if (m_constraints_in_conflict.contains(ci))
continue;
m_constraints_in_conflict.insert(ci);
out << "(" << ci << ") ";
display_constraint(out, ci) << " ";
auto j = m_justifications.get(ci);
if (!j)
continue;
display(out, *j) << "\n";
if (std::holds_alternative<multiplication_justification>(*j)) {
auto m = *std::get_if<multiplication_justification>(j);
todo.push_back(m.ci);
}
else if (std::holds_alternative<resolvent>(*j)) {
auto m = *std::get_if<resolvent>(j);
todo.push_back(m.ci1);
todo.push_back(m.ci2);
}
else if (std::holds_alternative<internal_justification>(*j)) {
auto m = *std::get_if<internal_justification>(j);
svector<lp::constraint_index> cis;
m_solver.lra().dep_manager().linearize(m.dep, cis);
for (auto ci : cis)
todo.push_back(ci);
}
}
for (auto ineq : ineqs) {
term_offset t(ineq.term(), rational(0));
display(out, t) << " " << ineq.cmp() << " " << ineq.rs() << "\n";
}
return out;
}
// Solver component // Solver component
// check LRA feasibilty // check LRA feasibilty
// (partial) check LIA feasibility // (partial) check LIA feasibility
@ -1085,10 +1138,10 @@ namespace nla {
return l_true; return l_true;
return l_undef; return l_undef;
// At this point it is not sound to use value assumptions // At this point it is not sound to use value justifications
// because the model changed from the outer LRA solver. // because the model changed from the outer LRA solver.
// Also value assumptions made in a previous iteration may no longer be valid. // Also value justifications made in a previous iteration may no longer be valid.
// this can be fixed by asserting the value assumptions as bounds directly and // this can be fixed by asserting the value justifications as bounds directly and
// making them depend on themselves. // making them depend on themselves.
TRACE(arith, s.display(tout)); TRACE(arith, s.display(tout));
if (!s.saturate_factors(ex, ineqs)) if (!s.saturate_factors(ex, ineqs))

View file

@ -4,6 +4,7 @@
--*/ --*/
#pragma once #pragma once
#include "util/scoped_ptr_vector.h"
#include "math/lp/nla_coi.h" #include "math/lp/nla_coi.h"
#include "math/lp/int_solver.h" #include "math/lp/int_solver.h"
#include "math/polynomial/polynomial.h" #include "math/polynomial/polynomial.h"
@ -34,16 +35,60 @@ namespace nla {
solver m_solver; solver m_solver;
struct bound { struct bound_assumption {
lpvar v = lp::null_lpvar; lpvar v = lp::null_lpvar;
lp::lconstraint_kind k; lp::lconstraint_kind k;
rational rhs; rational rhs;
}; };
using assumption = std::variant<u_dependency*, bound, lp::constraint_index>; struct external_justification {
using assumptions = vector<assumption>; u_dependency *dep = nullptr;
external_justification(u_dependency *d) : dep(d) {}
};
struct internal_justification {
u_dependency *dep = nullptr;
internal_justification(u_dependency *d) : dep(d) {}
};
struct multiplication {
lp::constraint_index ci = lp::null_ci;
lpvar j1 = lp::null_lpvar; // multiply ci by j1
lpvar j2 = lp::null_lpvar; // divide ci by j2
struct eq {
bool operator()(multiplication const &a, multiplication const &b) const {
return a.ci == b.ci && a.j1 == b.j1 && a.j2 == b.j2;
}
};
struct hash {
unsigned operator()(multiplication const &a) const {
return hash_u_u(a.ci, hash_u_u(a.j1, a.j2));
}
};
};
struct multiplication_justification : public multiplication {
vector<bound_assumption> assumptions;
};
struct resolvent {
lp::constraint_index ci1 = lp::null_ci;
lp::constraint_index ci2 = lp::null_ci;
lpvar j = lp::null_lpvar; // variable being resolved on
};
struct bound_assumptions {
char const *rule = nullptr;
vector<bound_assumption> bounds;
};
using justification = std::variant<
external_justification,
internal_justification,
multiplication_justification,
resolvent,
bound_assumptions>;
coi m_coi; coi m_coi;
u_map<assumptions> m_assumptions; // map from constraint to set of assumptions scoped_ptr_vector<justification> m_justifications;
void add_justification(lp::constraint_index ci, justification const &j) {
VERIFY(ci == m_justifications.size());
m_justifications.push_back(alloc(justification, j));
}
indexed_uint_set m_monomials_to_refine; indexed_uint_set m_monomials_to_refine;
indexed_uint_set m_false_constraints; // constraints that are false in the current model indexed_uint_set m_false_constraints; // constraints that are false in the current model
vector<rational> m_values; vector<rational> m_values;
@ -67,22 +112,7 @@ namespace nla {
unsynch_mpq_manager m_qm; unsynch_mpq_manager m_qm;
polynomial::manager m_pm; polynomial::manager m_pm;
struct resolvent { hashtable<multiplication, multiplication::hash, multiplication::eq> m_multiplications;
lp::constraint_index old_ci;
lpvar mi;
svector<lpvar> xs;
struct eq {
bool operator()(resolvent const& a, resolvent const& b) const {
return a.old_ci == b.old_ci && a.mi == b.mi && a.xs == b.xs;
}
};
struct hash {
unsigned operator()(resolvent const& a) const {
return hash_u_u(a.old_ci, hash_u_u(a.mi, svector_hash<unsigned_hash>()(a.xs)));
}
};
};
hashtable<resolvent, resolvent::hash, resolvent::eq> m_resolvents;
// initialization // initialization
void init_solver(); void init_solver();
@ -98,17 +128,17 @@ namespace nla {
using term_offset = std::pair<lp::lar_term, rational>; // term and its offset using term_offset = std::pair<lp::lar_term, rational>; // term and its offset
lpvar add_monomial(svector<lp::lpvar> const& vars); lpvar add_monomial(svector<lp::lpvar> const& vars);
lpvar add_term(term_offset &t); lpvar add_term(term_offset &t);
lp::constraint_index add_ineq(char const* rule, assumptions const& bounds, lp::lar_term const &t, lp::lconstraint_kind k, rational const &rhs); lp::constraint_index add_ineq(justification const& just, lp::lar_term const &t, lp::lconstraint_kind k, rational const &rhs);
lp::constraint_index add_ineq(char const* rule, assumptions const &bounds, lpvar j, lp::lconstraint_kind k, lp::constraint_index add_ineq(justification const &just, lpvar j, lp::lconstraint_kind k,
rational const &rhs); rational const &rhs);
bool is_int(svector<lp::lpvar> const& vars) const; bool is_int(svector<lp::lpvar> const& vars) const;
rational value(lp::lar_term const &t) const; rational value(lp::lar_term const &t) const;
rational value(svector<lpvar> const &prod) const; rational value(svector<lpvar> const &prod) const;
lpvar add_var(bool is_int); lpvar add_var(bool is_int);
lbool add_bounds(svector<lpvar> const &vars, assumptions &bounds); lbool add_bounds(svector<lpvar> const &vars, vector<bound_assumption> &bounds);
void saturate_constraints(); void saturate_constraints();
lp::constraint_index saturate_constraint(lp::constraint_index con_id, lp::lpvar mi, svector<lpvar> const & xs); lp::constraint_index saturate_multiply(lp::constraint_index con_id, lpvar j1, lpvar j2);
void resolve(lpvar j, lp::constraint_index ci1, lp::constraint_index ci2); void resolve(lpvar j, lp::constraint_index ci1, lp::constraint_index ci2);
void saturate_basic_linearize(); void saturate_basic_linearize();
@ -134,13 +164,26 @@ namespace nla {
indexed_uint_set m_constraints_in_conflict; indexed_uint_set m_constraints_in_conflict;
void explain_constraint(lemma_builder& new_lemma, lp::constraint_index ci, lp::explanation &ex); void explain_constraint(lemma_builder& new_lemma, lp::constraint_index ci, lp::explanation &ex);
struct pp_j {
stellensatz const &s;
lpvar j;
pp_j(stellensatz const&s, lpvar j) : s(s), j(j) {}
std::ostream &display(std::ostream &out) const {
return s.display_var(out, j);
}
};
friend std::ostream &operator<<(std::ostream &out, pp_j const &p) {
return p.display(out);
}
std::ostream& display(std::ostream& out) const; std::ostream& display(std::ostream& out) const;
std::ostream& display_product(std::ostream& out, svector<lpvar> const& vars) const; std::ostream& display_product(std::ostream& out, svector<lpvar> const& vars) const;
std::ostream& display_constraint(std::ostream& out, lp::constraint_index ci) const; std::ostream& display_constraint(std::ostream& out, lp::constraint_index ci) const;
std::ostream& display_constraint(std::ostream& out, lp::lar_term const& lhs, std::ostream& display_constraint(std::ostream& out, lp::lar_term const& lhs,
lp::lconstraint_kind k, rational const& rhs) const; lp::lconstraint_kind k, rational const& rhs) const;
std::ostream& display(std::ostream &out, term_offset const &t) const; std::ostream& display(std::ostream &out, term_offset const &t) const;
std::ostream& display(std::ostream &out, assumptions const &bounds) const; std::ostream& display(std::ostream &out, justification const &bounds) const;
std::ostream &display_var(std::ostream &out, lpvar j) const;
std::ostream &display_lemma(std::ostream &out, lp::explanation const &ex, vector<ineq> const &ineqs);
public: public:
stellensatz(core* core); stellensatz(core* core);