mirror of
				https://github.com/Z3Prover/z3
				synced 2025-10-31 03:32:28 +00:00 
			
		
		
		
	add factorization
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
		
							parent
							
								
									184fae6fcc
								
							
						
					
					
						commit
						81cffee736
					
				
					 2 changed files with 190 additions and 52 deletions
				
			
		|  | @ -27,29 +27,34 @@ | |||
|               necessarily derivable with the old 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 | ||||
| (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 | ||||
| Currently missed lemma: | ||||
| 
 | ||||
|  Note that:  | ||||
| j10 > 0 => 2 - j12 <= 0 | ||||
| j10 < 0 => 2 - j12 >= 0 | ||||
| Then add assumption j10 > 0 or j10 < 0 | ||||
| (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 | ||||
| 
 | ||||
| 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() { | ||||
|         lp::explanation ex; | ||||
|         vector<ineq> ineqs; | ||||
|         init_solver(); | ||||
|         saturate_constraints(); | ||||
|         saturate_basic_linearize(); | ||||
|         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) | ||||
|             add_lemma(ex); | ||||
|             add_lemma(ex, ineqs); | ||||
|         return r; | ||||
|     } | ||||
| 
 | ||||
|  | @ -150,14 +156,16 @@ namespace nla { | |||
|     // 1. constraints that are obtained by multiplication are explained from the original constraint
 | ||||
|     // 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(); | ||||
|         lp::explanation ex2; | ||||
|         lemma_builder new_lemma(c(), "stellensatz"); | ||||
|         m_processed_constraints.reset(); | ||||
|         m_constraints_in_conflict.reset(); | ||||
|         for (auto p : ex1)  | ||||
|             explain_constraint(new_lemma, p.ci(), ex2);         | ||||
|         new_lemma &= ex2; | ||||
|         for (auto const &ineq : ineqs) | ||||
|             new_lemma |= ineq; | ||||
|         IF_VERBOSE(5, verbose_stream() << "unsat \n" << new_lemma << "\n"); | ||||
|         TRACE(arith, tout << "unsat\n" << new_lemma << "\n"); | ||||
|         c().lra_solver().settings().stats().m_nla_stellensatz++; | ||||
|  | @ -168,9 +176,9 @@ namespace nla { | |||
|     // and by an original constraint.
 | ||||
|     // 
 | ||||
|     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; | ||||
|         m_processed_constraints.insert(ci); | ||||
|         m_constraints_in_conflict.insert(ci); | ||||
|         auto dep = m_ci2dep[ci]; | ||||
|         m_solver.lra().push_explanation(dep, ex);         | ||||
|         lp::constraint_index old_ci; | ||||
|  | @ -542,6 +550,7 @@ namespace nla { | |||
|         insert_monomials_from_constraint(new_ci); | ||||
|         m_ci2dep.setx(new_ci, nullptr, nullptr); | ||||
|         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
 | ||||
|  | @ -550,67 +559,111 @@ namespace nla { | |||
|     // term -> polynomial translation
 | ||||
|     // polynomial -> term translation
 | ||||
|     // 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); | ||||
|         polynomial::factors fs(m_pm); | ||||
|         m_pm.factor(p, fs); | ||||
|         if (fs.distinct_factors() <= 1) | ||||
|         if (fs.total_factors() <= 1) | ||||
|             return false; | ||||
|         unsigned df = fs.distinct_factors(); | ||||
|         for (unsigned i = 0; i < df; ++i) { | ||||
|             auto f = fs[i]; | ||||
|             auto degree = fs.get_degree(i); | ||||
|             term_offset tf = to_term(*f); | ||||
|             for (unsigned j = 0; j < degree; ++j) | ||||
|                 factors.push_back(tf); | ||||
|             factors.push_back({to_term(*f), degree}); | ||||
|         }         | ||||
|         SASSERT(!factors.empty()); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     polynomial::polynomial_ref stellensatz::to_poly(term_offset const& t) { | ||||
| 
 | ||||
|         ptr_vector<polynomial::monomial> ms; | ||||
|         vector<rational> coeffs; | ||||
|         polynomial::scoped_numeral_vector coeffs(m_pm.m()); | ||||
|         rational den(1); | ||||
|         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())); | ||||
|         } | ||||
|          | ||||
|         for (auto kv : t.first)  | ||||
|             coeffs.push_back(den * kv.coeff()); | ||||
|         coeffs.push_back(den * t.second); | ||||
|                  | ||||
|             coeffs.push_back((den * kv.coeff()).to_mpq().numerator()); | ||||
|         coeffs.push_back((den * t.second).to_mpq().numerator()); | ||||
|         ms.push_back(m_pm.mk_monomial(0, nullptr));                 | ||||
|         polynomial::polynomial_ref p(m_pm); | ||||
|         p = m_pm.mk_polynomial(ms.size(), coeffs.data(), ms.data());  | ||||
|         return 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)) | ||||
|             return; | ||||
|             return true; | ||||
|         m_factored_constraints.insert(ci); | ||||
|         auto const &con = m_solver.lra().constraints()[ci]; | ||||
|         if (all_of(con.coeffs(), [&](auto const& cv) { return !m_mon2vars.contains(cv.second); })) | ||||
|             return; | ||||
|             return true; | ||||
|         term_offset t; | ||||
|         // ignore nested terms and nested polynomials..
 | ||||
|         for (auto const& [coeff, v] : con.coeffs())  | ||||
|             t.first.add_monomial(coeff, v); | ||||
|         t.second = -con.rhs(); | ||||
|          | ||||
|         vector<term_offset> factors; | ||||
|         if (get_factors(t, factors)) | ||||
|             return; | ||||
|         vector<std::pair<term_offset, unsigned>> factors; | ||||
|         if (!get_factors(t, factors)) | ||||
|             return true; | ||||
| 
 | ||||
|         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); | ||||
|             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)
 | ||||
|         // assert both factors with bound assumptions, and reference to original constraint
 | ||||
|         //    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
 | ||||
|         // 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.
 | ||||
|         //
 | ||||
|         // 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; | ||||
|     } | ||||
| 
 | ||||
|     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 { | ||||
|         return all_of(vars, [&](lpvar v) { return m_solver.lra().var_is_int(v); }); | ||||
|     } | ||||
|  | @ -786,6 +904,21 @@ namespace nla { | |||
|         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 { | ||||
|         auto const& con = m_solver.lra().constraints()[ci]; | ||||
|         return display_constraint(out, con.coeffs(), con.kind(), con.rhs()); | ||||
|  | @ -820,7 +953,7 @@ namespace nla { | |||
|         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) { | ||||
|             lbool r = solve_lra(ex); | ||||
|             if (r != l_true) | ||||
|  | @ -833,7 +966,8 @@ namespace nla { | |||
|                 return l_true; | ||||
| 
 | ||||
|             TRACE(arith, s.display(tout)); | ||||
|             // return l_undef;
 | ||||
|             if (!s.saturate_factors(ex, ineqs)) | ||||
|                 return l_false; | ||||
|             // s.saturate_constraints(); ?
 | ||||
|             if (sz == lra_solver->number_of_vars()) | ||||
|                 return l_undef; | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ namespace nla { | |||
|         public:   | ||||
|             solver(stellensatz &s) : s(s) {};                 | ||||
|             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 const &lra() const { return *lra_solver; } | ||||
|         }; | ||||
|  | @ -56,6 +56,7 @@ namespace nla { | |||
|         map<unsigned_vector, unsigned, svector_hash<unsigned_hash>, eq> m_vars2mon; | ||||
|         u_map<unsigned_vector> m_mon2vars; | ||||
| 
 | ||||
|         // for factoring
 | ||||
|         small_object_allocator m_allocator; | ||||
|         unsynch_mpq_manager m_qm; | ||||
|         polynomial::manager m_pm; | ||||
|  | @ -86,7 +87,9 @@ namespace nla { | |||
|         void insert_monomials_from_constraint(lp::constraint_index ci); | ||||
| 
 | ||||
|         // 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_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, lpvar j, lp::lconstraint_kind k, | ||||
|                                       rational const &rhs); | ||||
|  | @ -109,16 +112,16 @@ namespace nla { | |||
|         void saturate_squares(lpvar j, rational const &val_j, svector<lpvar> const &vars); | ||||
| 
 | ||||
|         // polynomial tricks
 | ||||
|         using term_offset = std::pair<lp::lar_term, rational>;  // term and its offset
 | ||||
|         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); | ||||
|         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
 | ||||
|         void add_lemma(lp::explanation const& ex); | ||||
|         indexed_uint_set m_processed_constraints; | ||||
|         void add_lemma(lp::explanation const& ex, vector<ineq> const& ineqs); | ||||
|         indexed_uint_set m_constraints_in_conflict; | ||||
|         void explain_constraint(lemma_builder& new_lemma, lp::constraint_index ci, lp::explanation &ex); | ||||
| 
 | ||||
|         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, vector<std::pair<rational, lpvar>> const& lhs, | ||||
|                                          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; | ||||
| 
 | ||||
|     public: | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue