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

add factorization

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2025-09-29 04:29:54 -07:00
parent 184fae6fcc
commit 81cffee736
2 changed files with 190 additions and 52 deletions

View file

@ -27,29 +27,34 @@
necessarily derivable with the old constraints. necessarily derivable with the old constraints.
Strategy 3: The lemma uses the new constraints. Strategy 3: The lemma uses the new constraints.
Auxiliary methods:
- basic lemmas
- factoring
Add also factoring: Potential auxiliary methods:
- tangent, ordering lemmas (lemmas that use values from the current model)
- with internal bounded backtracking.
(16) - 16384*j6 - j8 + j10 >= 0 Currently missed lemma:
(65) j32 - j36 >= 1
(18) j8 >= 0
(66) j34 - 16384*j36 - j38 <= 0
(52) - j18 + j30 <= 0
(72) j38 <= 16383
(58) j4 - j12 - j32 >= 0
(139) - 32769*j4 + j4*j18 <= -1
(12) j4 - j6 <= 0
(108) 2*j10 - j10*j12 <= 0
(60) j10 - j30 - j34 <= 0
(111) j4 >= 6
==> false
Note that: (30) j17 >= 0
j10 > 0 => 2 - j12 <= 0 (15) j4 - j7 >= 1
j10 < 0 => 2 - j12 >= 0 (51) j25 - j27 >= 1
Then add assumption j10 > 0 or j10 < 0 (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
p <= 0, factor into p*q <= 0, then assume p <= 0, q >= 0 j4 - j7 - j27 >= 1
j4 - j5 - j7 + j23 >= 1 (from (46))
j4 - j5 + j23 >= 2
j4 - j5 + j11 >= 2
j4 - j5 >= 3
..
*/ */
@ -68,13 +73,14 @@ namespace nla {
lbool stellensatz::saturate() { lbool stellensatz::saturate() {
lp::explanation ex; lp::explanation ex;
vector<ineq> ineqs;
init_solver(); init_solver();
saturate_constraints(); saturate_constraints();
saturate_basic_linearize(); saturate_basic_linearize();
TRACE(arith, display(tout << "stellensatz after saturation\n")); TRACE(arith, display(tout << "stellensatz after saturation\n"));
lbool r = m_solver.solve(ex); lbool r = m_solver.solve(ex, ineqs);
if (r == l_false) if (r == l_false)
add_lemma(ex); add_lemma(ex, ineqs);
return r; return r;
} }
@ -150,14 +156,16 @@ namespace nla {
// 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 assumptions are added as assumptions to the lemma.
// //
void stellensatz::add_lemma(lp::explanation const& ex1) { void stellensatz::add_lemma(lp::explanation const& ex1, vector<ineq> const& 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");
m_processed_constraints.reset(); m_constraints_in_conflict.reset();
for (auto p : ex1) for (auto p : ex1)
explain_constraint(new_lemma, p.ci(), ex2); explain_constraint(new_lemma, p.ci(), ex2);
new_lemma &= ex2; new_lemma &= ex2;
for (auto const &ineq : ineqs)
new_lemma |= ineq;
IF_VERBOSE(5, verbose_stream() << "unsat \n" << new_lemma << "\n"); IF_VERBOSE(5, verbose_stream() << "unsat \n" << new_lemma << "\n");
TRACE(arith, tout << "unsat\n" << new_lemma << "\n"); TRACE(arith, tout << "unsat\n" << new_lemma << "\n");
c().lra_solver().settings().stats().m_nla_stellensatz++; c().lra_solver().settings().stats().m_nla_stellensatz++;
@ -168,9 +176,9 @@ namespace nla {
// 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_processed_constraints.contains(ci)) if (m_constraints_in_conflict.contains(ci))
return; return;
m_processed_constraints.insert(ci); m_constraints_in_conflict.insert(ci);
auto dep = m_ci2dep[ci]; auto dep = m_ci2dep[ci];
m_solver.lra().push_explanation(dep, ex); m_solver.lra().push_explanation(dep, ex);
lp::constraint_index old_ci; lp::constraint_index old_ci;
@ -542,6 +550,7 @@ namespace nla {
insert_monomials_from_constraint(new_ci); insert_monomials_from_constraint(new_ci);
m_ci2dep.setx(new_ci, nullptr, nullptr); m_ci2dep.setx(new_ci, nullptr, nullptr);
m_old_constraints.insert(new_ci, old_ci); m_old_constraints.insert(new_ci, old_ci);
m_factored_constraints.insert(new_ci); // don't bother to factor this because it comes from factors already
} }
// call polynomial factorization using the polynomial manager // call polynomial factorization using the polynomial manager
@ -550,67 +559,111 @@ namespace nla {
// term -> polynomial translation // term -> polynomial translation
// polynomial -> term translation // polynomial -> term translation
// the call to factorization // the call to factorization
bool stellensatz::get_factors(term_offset& t, vector<term_offset>& factors) { bool stellensatz::get_factors(term_offset& t, vector<std::pair<term_offset, unsigned>>& factors) {
auto p = to_poly(t); auto p = to_poly(t);
polynomial::factors fs(m_pm); polynomial::factors fs(m_pm);
m_pm.factor(p, fs); m_pm.factor(p, fs);
if (fs.distinct_factors() <= 1) if (fs.total_factors() <= 1)
return false; return false;
unsigned df = fs.distinct_factors(); unsigned df = fs.distinct_factors();
for (unsigned i = 0; i < df; ++i) { for (unsigned i = 0; i < df; ++i) {
auto f = fs[i]; auto f = fs[i];
auto degree = fs.get_degree(i); auto degree = fs.get_degree(i);
term_offset tf = to_term(*f); factors.push_back({to_term(*f), degree});
for (unsigned j = 0; j < degree; ++j)
factors.push_back(tf);
} }
SASSERT(!factors.empty());
return true; return true;
} }
polynomial::polynomial_ref stellensatz::to_poly(term_offset const& t) { polynomial::polynomial_ref stellensatz::to_poly(term_offset const& t) {
ptr_vector<polynomial::monomial> ms; ptr_vector<polynomial::monomial> ms;
vector<rational> coeffs; polynomial::scoped_numeral_vector coeffs(m_pm.m());
rational den(1); rational den(1);
for (lp::lar_term::ival kv : t.first) { for (lp::lar_term::ival kv : t.first) {
// TODO add to ms auto v = kv.j();
while (v >= m_pm.num_vars())
m_pm.mk_var();
if (m_mon2vars.contains(v)) {
auto const &vars = m_mon2vars[v];
ms.push_back(m_pm.mk_monomial(vars.size(), vars.data()));
}
else
ms.push_back(m_pm.mk_monomial(v));
den = lcm(den, denominator(kv.coeff())); den = lcm(den, denominator(kv.coeff()));
} }
for (auto kv : t.first) for (auto kv : t.first)
coeffs.push_back(den * kv.coeff()); coeffs.push_back((den * kv.coeff()).to_mpq().numerator());
coeffs.push_back(den * t.second); coeffs.push_back((den * t.second).to_mpq().numerator());
ms.push_back(m_pm.mk_monomial(0, nullptr));
polynomial::polynomial_ref p(m_pm); polynomial::polynomial_ref p(m_pm);
p = m_pm.mk_polynomial(ms.size(), coeffs.data(), ms.data()); p = m_pm.mk_polynomial(ms.size(), coeffs.data(), ms.data());
return p; return p;
} }
stellensatz::term_offset stellensatz::to_term(polynomial::polynomial const& p) { stellensatz::term_offset stellensatz::to_term(polynomial::polynomial const& p) {
throw nullptr; term_offset t;
auto sz = m_pm.size(&p);
for (unsigned i = 0; i < sz; ++i) {
auto m = m_pm.get_monomial(&p, i);
auto const &c = m_pm.coeff(&p, i);
svector<lpvar> vars;
for (unsigned j = 0; j < m_pm.size(m); ++j)
vars.push_back(m_pm.get_var(m, j));
if (vars.empty())
t.second += c;
else
t.first.add_monomial(c, add_monomial(vars));
}
return t;
} }
void stellensatz::saturate_factors(lp::constraint_index ci) { bool stellensatz::saturate_factors(lp::explanation &ex, vector<ineq> &ineqs) {
auto indices = m_solver.lra().constraints().indices();
return all_of(indices, [&](auto ci) { return saturate_factors(ci, ex, ineqs); });
}
bool stellensatz::saturate_factors(lp::constraint_index ci, lp::explanation& ex, vector<ineq>& ineqs) {
if (m_factored_constraints.contains(ci)) if (m_factored_constraints.contains(ci))
return; return true;
m_factored_constraints.insert(ci); m_factored_constraints.insert(ci);
auto const &con = m_solver.lra().constraints()[ci]; auto const &con = m_solver.lra().constraints()[ci];
if (all_of(con.coeffs(), [&](auto const& cv) { return !m_mon2vars.contains(cv.second); })) if (all_of(con.coeffs(), [&](auto const& cv) { return !m_mon2vars.contains(cv.second); }))
return; return true;
term_offset t; term_offset t;
// ignore nested terms and nested polynomials.. // ignore nested terms and nested polynomials..
for (auto const& [coeff, v] : con.coeffs()) for (auto const& [coeff, v] : con.coeffs())
t.first.add_monomial(coeff, v); t.first.add_monomial(coeff, v);
t.second = -con.rhs(); t.second = -con.rhs();
vector<term_offset> factors; vector<std::pair<term_offset, unsigned>> factors;
if (get_factors(t, factors)) if (!get_factors(t, factors))
return; return true;
vector<rational> values; vector<rational> values;
for (auto const &t : factors) rational prod(1);
for (auto const & [t, degree] : factors) {
values.push_back(value(t.first) + t.second); values.push_back(value(t.first) + t.second);
prod *= values.back();
if (degree % 2 == 0)
prod *= values.back();
}
bool is_square = all_of(factors, [&](auto const &f) { return f.second % 2 == 0; });
if (is_square) {
auto v = add_term(t);
bound_justifications bounds;
add_ineq("squares", bounds, v, lp::lconstraint_kind::GE, rational(0));
}
IF_VERBOSE(
3, display_constraint(verbose_stream() << "factored ", ci) << "\n";
for (auto const &[t, degree] : factors) {
display(verbose_stream() << " factor ", t)
<< " ^ " << degree << " = " << (value(t.first) + t.second) << "\n";
});
//
// 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 assumptions, and reference to original constraint
// p >= 0 <- q >= 0 and // p >= 0 <- q >= 0 and
@ -618,9 +671,60 @@ namespace nla {
// 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
// therefore, assume all but one polynomial satisfies the bound (ignoring multiplicities in this argument). // therefore, assume all but one polynomial satisfies the bound (ignoring multiplicities in this argument).
// Then derive the bound on the remaining polynomial from the others. // Then derive the bound on the remaining polynomial from the others.
//
// prod > 0 and k is <=, <: pick random entry to flip value and sign.
// prod = 0 and k is <=, >=, pick random 0 entry and assert it
// prod = 0 and k is >, <, revert 0s from values, recalculate prod, flip random entry if needed.
//
auto k = con.kind();
if (prod == 0 && (k == lp::lconstraint_kind::LE || k == lp::lconstraint_kind::GE || k == lp::lconstraint_kind::EQ)) {
unsigned n = 0, k = factors.size();
for (unsigned i = 0; i < factors.size(); ++i)
if (values[i] == 0 && rand() % (++n) == 0)
k = i;
SASSERT(k < factors.size());
auto v = add_term(factors[k].first);
bound_justifications bounds;
bound b(v, lp::lconstraint_kind::EQ, rational(0));
bounds.push_back(b);
add_ineq("factor = 0", bounds, v, lp::lconstraint_kind::EQ, rational(0));
return true;
}
if ((prod > 0 && k == lp::lconstraint_kind::GE) || (prod < 0 && k == lp::lconstraint_kind::LE)) {
// the current evaluation is consistent with inequalities.
// add factors that enforce the current evaluation.
for (auto & f : factors) {
if (f.second % 2 == 0)
continue;
bound_justifications bounds;
auto v = add_term(f.first);
auto k = m_values[v] > 0 ? lp::lconstraint_kind::GE : lp::lconstraint_kind::LE;
bounds.push_back(bound(v, k, rational(0)));
add_ineq("factor >= 0", bounds, v, k, rational(0));
}
return true;
}
if ((prod > 0 && k == lp::lconstraint_kind::LE) || (prod < 0 && k == lp::lconstraint_kind::GE)) {
// this is a conflict with the current evaluation. Produce a lemma that blocks
ex.push_back(ci);
for (auto &f : factors) {
auto [t, offset] = f.first;
auto val = value(t) + offset;
auto k = val > 0 ? lp::lconstraint_kind::LE : lp::lconstraint_kind::GE;
ineqs.push_back(ineq(t, k, -offset));
}
TRACE(arith, tout << "factor conflict\n";
for (auto const &f : factors) display(tout, f.first) << "; "; tout << "\n";
);
return false;
}
verbose_stream() << "still todo\n";
return true;
} }
// //
@ -689,6 +793,20 @@ namespace nla {
return v; return v;
} }
lpvar stellensatz::add_term(term_offset& t) {
if (t.second != 0) {
auto w = add_var(t.second.is_int()); // or reuse the constant 1 that is already there.
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::LE, t.second);
t.first.add_monomial(rational(1), w);
t.second = 0;
}
auto ti = m_solver.lra().add_term(t.first.coeffs_as_vector(), m_solver.lra().number_of_vars());
m_values.push_back(value(t.first));
return ti;
}
bool stellensatz::is_int(svector<lp::lpvar> const& vars) const { bool stellensatz::is_int(svector<lp::lpvar> const& vars) const {
return all_of(vars, [&](lpvar v) { return m_solver.lra().var_is_int(v); }); return all_of(vars, [&](lpvar v) { return m_solver.lra().var_is_int(v); });
} }
@ -786,6 +904,21 @@ namespace nla {
return out; return out;
} }
std::ostream& stellensatz::display(std::ostream& out, term_offset const& t) const {
bool first = true;
for (auto [v, coeff] : t.first) {
c().display_coeff(out, first, coeff);
first = false;
if (m_mon2vars.contains(v))
display_product(out, m_mon2vars[v]);
else
out << "j" << v;
}
if (t.second != 0)
out << " + " << t.second;
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.coeffs(), con.kind(), con.rhs()); return display_constraint(out, con.coeffs(), con.kind(), con.rhs());
@ -820,7 +953,7 @@ namespace nla {
int_solver = alloc(lp::int_solver, *lra_solver); int_solver = alloc(lp::int_solver, *lra_solver);
} }
lbool stellensatz::solver::solve(lp::explanation &ex) { lbool stellensatz::solver::solve(lp::explanation &ex, vector<ineq>& ineqs) {
while (true) { while (true) {
lbool r = solve_lra(ex); lbool r = solve_lra(ex);
if (r != l_true) if (r != l_true)
@ -833,7 +966,8 @@ namespace nla {
return l_true; return l_true;
TRACE(arith, s.display(tout)); TRACE(arith, s.display(tout));
// return l_undef; if (!s.saturate_factors(ex, ineqs))
return l_false;
// s.saturate_constraints(); ? // s.saturate_constraints(); ?
if (sz == lra_solver->number_of_vars()) if (sz == lra_solver->number_of_vars())
return l_undef; return l_undef;

View file

@ -27,7 +27,7 @@ namespace nla {
public: public:
solver(stellensatz &s) : s(s) {}; solver(stellensatz &s) : s(s) {};
void init(); void init();
lbool solve(lp::explanation &ex); lbool solve(lp::explanation &ex, vector<ineq>& ineqs);
lp::lar_solver &lra() { return *lra_solver; } lp::lar_solver &lra() { return *lra_solver; }
lp::lar_solver const &lra() const { return *lra_solver; } lp::lar_solver const &lra() const { return *lra_solver; }
}; };
@ -56,6 +56,7 @@ namespace nla {
map<unsigned_vector, unsigned, svector_hash<unsigned_hash>, eq> m_vars2mon; map<unsigned_vector, unsigned, svector_hash<unsigned_hash>, eq> m_vars2mon;
u_map<unsigned_vector> m_mon2vars; u_map<unsigned_vector> m_mon2vars;
// for factoring
small_object_allocator m_allocator; small_object_allocator m_allocator;
unsynch_mpq_manager m_qm; unsynch_mpq_manager m_qm;
polynomial::manager m_pm; polynomial::manager m_pm;
@ -86,7 +87,9 @@ namespace nla {
void insert_monomials_from_constraint(lp::constraint_index ci); void insert_monomials_from_constraint(lp::constraint_index ci);
// additional variables and monomials and constraints // additional variables and monomials and constraints
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);
lp::constraint_index add_ineq(char const* rule, bound_justifications const& bounds, lp::lar_term const &t, lp::lconstraint_kind k, rational const &rhs); lp::constraint_index add_ineq(char const* rule, bound_justifications const& bounds, lp::lar_term const &t, lp::lconstraint_kind k, rational const &rhs);
lp::constraint_index add_ineq(char const* rule, bound_justifications const &bounds, lpvar j, lp::lconstraint_kind k, lp::constraint_index add_ineq(char const* rule, bound_justifications const &bounds, lpvar j, lp::lconstraint_kind k,
rational const &rhs); rational const &rhs);
@ -109,16 +112,16 @@ namespace nla {
void saturate_squares(lpvar j, rational const &val_j, svector<lpvar> const &vars); void saturate_squares(lpvar j, rational const &val_j, svector<lpvar> const &vars);
// polynomial tricks // polynomial tricks
using term_offset = std::pair<lp::lar_term, rational>; // term and its offset
uint_set m_factored_constraints; uint_set m_factored_constraints;
bool get_factors(term_offset &t, vector<term_offset> &factors); bool get_factors(term_offset &t, vector<std::pair<term_offset, unsigned>> &factors);
polynomial::polynomial_ref to_poly(term_offset const &t); polynomial::polynomial_ref to_poly(term_offset const &t);
term_offset to_term(polynomial::polynomial const &p); term_offset to_term(polynomial::polynomial const &p);
void saturate_factors(lp::constraint_index ci); bool saturate_factors(lp::constraint_index ci, lp::explanation& ex, vector<ineq>& ineqs);
bool saturate_factors(lp::explanation& ex, vector<ineq>& ineqs);
// lemmas // lemmas
void add_lemma(lp::explanation const& ex); void add_lemma(lp::explanation const& ex, vector<ineq> const& ineqs);
indexed_uint_set m_processed_constraints; 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);
std::ostream& display(std::ostream& out) const; std::ostream& display(std::ostream& out) const;
@ -126,6 +129,7 @@ namespace nla {
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, vector<std::pair<rational, lpvar>> const& lhs, std::ostream& display_constraint(std::ostream& out, vector<std::pair<rational, lpvar>> 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, bound_justifications const &bounds) const; std::ostream& display(std::ostream &out, bound_justifications const &bounds) const;
public: public: