/*++ Copyright (c) 2017 Microsoft Corporation Module Name: nla_grobner.cpp Author: Lev Nachmanson (levnach) Nikolaj Bjorner (nbjorner) --*/ #include "util/uint_set.h" #include "params/smt_params_helper.hpp" #include "math/lp/nla_core.h" #include "math/lp/factorization_factory_imp.h" #include "math/grobner/pdd_solver.h" #include "math/dd/pdd_interval.h" #include "math/dd/pdd_eval.h" namespace nla { grobner::grobner(core* c): common(c), m_pdd_manager(m_core.lra.number_of_vars()), m_solver(m_core.m_reslim, m_core.lra.dep_manager(), m_pdd_manager), lra(m_core.lra), m_quota(m_core.params().arith_nl_gr_q()) {} void grobner::updt_params(params_ref const& p) { smt_params_helper ph(p); m_config.m_propagate_quotients = ph.arith_nl_grobner_propagate_quotients(); m_config.m_gcd_test = ph.arith_nl_grobner_gcd_test(); m_config.m_expand_terms = ph.arith_nl_grobner_expand_terms(); } lp::lp_settings& grobner::lp_settings() { return c().lp_settings(); } void grobner::operator()() { if (lra.column_count() > 5000) return; if (m_quota == 0) m_quota = c().params().arith_nl_gr_q(); bool const use_exp_delay = c().params().arith_nl_grobner_exp_delay(); if (m_quota == 1) { if (use_exp_delay) { constexpr unsigned delay_cap = 1000000; if (m_delay_base == 0) m_delay_base = 1; else if (m_delay_base < delay_cap) { m_delay_base *= 2; if (m_delay_base > delay_cap) m_delay_base = delay_cap; } m_delay = m_delay_base; } else m_delay = ++m_delay_base; m_quota = c().params().arith_nl_gr_q(); } if (m_delay > 0) { --m_delay; TRACE(grobner, tout << "delay " << m_delay << "\n"); return; } lp_settings().stats().m_grobner_calls++; find_nl_cluster(); if (!configure()) return; m_solver.saturate(); TRACE(grobner, m_solver.display(tout)); if (m_delay_base > 0) --m_delay_base; try { if (is_conflicting()) return; if (propagate_quotients()) return; if (propagate_gcd_test()) return; if (propagate_eqs()) return; if (propagate_factorization()) return; if (propagate_linear_equations()) return; } catch (...) { } // DEBUG_CODE(for (auto e : m_solver.equations()) check_missing_propagation(*e);); // for (auto e : m_solver.equations()) check_missing_propagation(*e); ++m_delay_base; if (m_quota > 0) --m_quota; IF_VERBOSE(5, verbose_stream() << "grobner miss, quota " << m_quota << "\n"); IF_VERBOSE(5, diagnose_pdd_miss(verbose_stream())); } bool grobner::is_conflicting() { for (auto eq : m_solver.equations()) { if (is_conflicting(*eq)) { lp_settings().stats().m_grobner_conflicts++; TRACE(grobner, m_solver.display(tout)); IF_VERBOSE(3, verbose_stream() << "grobner conflict\n"); return true; } } return false; } bool grobner::propagate_eqs() { unsigned changed = 0; for (auto eq : m_solver.equations()) if (propagate_fixed(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) return true; return changed > 0; } bool grobner::propagate_factorization() { unsigned changed = 0; for (auto eq : m_solver.equations()) if (propagate_factorization(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) return true; return changed > 0; } /** \brief detect equalities - k*x = 0, that is x = 0 - ax + b = 0 */ typedef lp::lar_term term; bool grobner::propagate_fixed(const dd::solver::equation& eq) { dd::pdd const& p = eq.poly(); if (p.is_unary()) { unsigned v = p.var(); if (c().var_is_fixed(v)) return false; ineq new_eq(v, llc::EQ, rational::zero()); if (c().ineq_holds(new_eq)) return false; lemma_builder lemma(c(), "grobner-eq"); add_dependencies(lemma, eq); lemma |= new_eq; TRACE(grobner, lemma.display(tout);); return true; } if (p.is_offset()) { unsigned v = p.var(); if (c().var_is_fixed(v)) return false; rational a = p.hi().val(); rational b = -p.lo().val(); rational d = lcm(denominator(a), denominator(b)); a *= d; b *= d; ineq new_eq(term(a, v), llc::EQ, b); if (c().ineq_holds(new_eq)) return false; lemma_builder lemma(c(), "grobner-eq"); add_dependencies(lemma, eq); lemma |= new_eq; TRACE(grobner, lemma.display(tout);); return true; } return false; } /** \brief detect simple factors x*q = 0 => x = 0 or q = 0 */ bool grobner::propagate_factorization(const dd::solver::equation& eq) { dd::pdd const& p = eq.poly(); auto [vars, q] = p.var_factors(); if (vars.empty() || !q.is_linear()) return false; // IF_VERBOSE(0, verbose_stream() << "factored " << q << " : " << vars << "\n"); auto [t, offset] = linear_to_term(q); vector ineqs; for (auto v : vars) ineqs.push_back(ineq(v, llc::EQ, rational::zero())); ineqs.push_back(ineq(t, llc::EQ, -offset)); for (auto const& i : ineqs) if (c().ineq_holds(i)) return false; lemma_builder lemma(c(), "grobner-factored"); add_dependencies(lemma, eq); for (auto const& i : ineqs) lemma |= i; //lemma.display(verbose_stream()); return true; } std::pair grobner::linear_to_term(dd::pdd q) { SASSERT(q.is_linear()); rational lc(1); auto ql = q; lp::lar_term t; while (!ql.is_val()) { lc = lcm(lc, denominator(ql.hi().val())); ql = ql.lo(); } lc = lcm(denominator(ql.val()), lc); while (!q.is_val()) { t.add_monomial(lc * q.hi().val(), q.var()); q = q.lo(); } rational offset = lc * q.val(); return {t, offset}; } bool grobner::propagate_gcd_test() { if (!m_config.m_gcd_test) return false; unsigned changed = 0; for (auto eq : m_solver.equations()) if (propagate_gcd_test(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) return true; return changed > 0; } // x^2 - y = 0 => y mod 4 = { 0, 1 } // x^k - y = 0 => y mod k^2 = {0, 1, .., (k-1)^k } bool grobner::propagate_gcd_test(dd::solver::equation const& eq) { dd::pdd p = eq.poly(); if (p.is_linear()) return false; if (p.is_val()) return false; auto v = p.var(); if (!c().var_is_int(v)) return false; for (auto v : p.free_vars()) if (!c().var_is_int(v)) return false; dd::pdd_eval eval; eval.var2val() = [&](unsigned j) { return val(j); }; if (eval(p) == 0) return false; p.get_powers(m_powers); if (m_powers.empty()) return false; rational d(1); for (auto const& value : p.coefficients()) d = lcm(d, denominator(value.coeff)); if (d != 1) p *= d; auto& m = p.manager(); auto fails_gcd_test = [&](dd::pdd const& q) { rational g(0); rational k(0); for (auto const& value : q.coefficients()) { if (value.is_constant) { k += value.coeff; continue; } g = gcd(g, value.coeff); if (g == 1) return false; } return k != 0 && !divides(g, k); }; for (auto [x, k] : m_powers) { SASSERT(k > 1); if (k > 4) // cut off at larger exponents. continue; bool gcd_fail = true; dd::pdd kx = m.mk_var(x) * m.mk_val(k); for (unsigned r = 0; gcd_fail && r < k; r++) { dd::pdd kx_plus_r = kx + m.mk_val(r); auto q = p.subst_pdd(x, kx_plus_r); if (!fails_gcd_test(q)) gcd_fail = false; } if (gcd_fail) { lemma_builder lemma(c(), "grobner-gcd-test"); add_dependencies(lemma, eq); return true; } } return false; } bool grobner::propagate_quotients() { if (!m_config.m_propagate_quotients) return false; unsigned changed = 0; for (auto eq : m_solver.equations()) if (propagate_quotients(*eq) && ++changed >= m_solver.number_of_conflicts_to_report()) return true; return changed > 0; } // factor each nl var at a time. // x*y + z = 0 // x = 0 => z = 0 // y = 0 => z = 0 // z = 0 => x = 0 or y = 0 // z > 0 & x > 0 => x <= z // z < 0 & x > 0 => x <= -z // z > 0 & x < 0 => -x <= z // z < 0 & x < 0 => -x <= -z bool grobner::propagate_quotients(dd::solver::equation const& eq) { dd::pdd const& p = eq.poly(); if (p.is_linear()) return false; if (p.is_val()) return false; auto v = p.var(); if (!c().var_is_int(v)) return false; for (auto v : p.free_vars()) if (!c().var_is_int(v)) return false; dd::pdd_eval eval; eval.var2val() = [&](unsigned j) { return val(j); }; if (eval(p) == 0) return false; TRACE(grobner, tout << "propagate_quotients " << p << "\n"); tracked_uint_set nl_vars; rational d(1); for (auto const& m : p) { d = lcm(d, denominator(m.coeff)); if (m.vars.size() == 1) continue; for (auto j : m.vars) nl_vars.insert(j); } bool found_lemma = false; for (auto v : nl_vars) { auto& m = p.manager(); dd::pdd lc(m), r(m); p.factor(v, 1, lc, r); if (!r.is_linear()) continue; if (d != 1) { lc *= d; r *= d; } auto v_value = val(v); auto r_value = eval(r); auto lc_value = eval(lc); TRACE(grobner, tout << "nl-var: " << v << " " << v_value << "\n" << "lc: " << lc << " " << lc_value << "\n" << "r: " << r << " " << r_value << "\n"; ); SASSERT(v_value.is_int()); SASSERT(r_value.is_int()); SASSERT(lc_value.is_int()); if (r_value == 0) { if (v_value == 0) continue; if (lc_value == 0) continue; if (!lc.is_linear()) continue; auto [t, offset] = linear_to_term(lc); auto [t2, offset2] = linear_to_term(r); lemma_builder lemma(c(), "grobner-quotient"); add_dependencies(lemma, eq); // v = 0 or lc = 0 or r != 0 lemma |= ineq(v, llc::EQ, rational::zero()); lemma |= ineq(t, llc::EQ, -offset); lemma |= ineq(t2, llc::NE, -offset2); TRACE(grobner, lemma.display(tout << "quotient1\n")); found_lemma = true; continue; } // r_value != 0 if (v_value == 0) { // v = 0 => r = 0 lemma_builder lemma(c(), "grobner-quotient"); add_dependencies(lemma, eq); auto [t, offset] = linear_to_term(r); lemma |= ineq(v, llc::NE, rational::zero()); lemma |= ineq(t, llc::EQ, -offset); TRACE(grobner, lemma.display(tout << "quotient2\n")); found_lemma = true; continue; } if (lc_value == 0) { if (!lc.is_linear()) continue; // lc = 0 => r = 0 lemma_builder lemma(c(), "grobner-quotient"); add_dependencies(lemma, eq); auto [t, offset] = linear_to_term(lc); auto [t2, offset2] = linear_to_term(r); lemma |= ineq(t, llc::NE, -offset); lemma |= ineq(t2, llc::EQ, -offset2); TRACE(grobner, lemma.display(tout << "quotient3\n")); found_lemma = true; continue; } if (divides(v_value, r_value)) continue; if (abs(v_value) > abs(r_value)) { // v*c + r = 0 & v > 0 => r >= v or -r >= v or r = 0 lemma_builder lemma(c(), "grobner-quotient"); auto [t, offset] = linear_to_term(r); add_dependencies(lemma, eq); if (v_value > 0 && r_value > 0) { // v*c + t = 0 => v <= 0 or v <= t or t <= 0 lemma |= ineq(v, llc::LE, rational::zero()); // v <= 0 lemma |= ineq(t, llc::LE, -offset); // t <= 0 t.add_monomial(rational(-1), v); lemma |= ineq(t, llc::GE, -offset); // t - v >= 0 } else if (v_value > 0 && r_value < 0) { // v*c + t = 0 => v <= 0 or v <= -t or t >= 0 lemma |= ineq(v, llc::LE, rational::zero()); // v <= 0 lemma |= ineq(t, llc::GE, -offset); // t >= 0 t.add_monomial(rational(1), v); lemma |= ineq(t, llc::LE, -offset); // t + v <= 0 } else if (v_value < 0 && r_value > 0) { // v*c + t = 0 => v >= 0 or -v <= t or t <= 0 lemma |= ineq(v, llc::GE, rational::zero()); // v >= 0 lemma |= ineq(t, llc::LE, -offset); // t <= 0 t.add_monomial(rational(1), v); lemma |= ineq(t, llc::GE, -offset); // t + v >= 0 } else if (v_value < 0 && r_value < 0) { // v*c + t = 0 => v >= 0 or v >= t or t >= 0 lemma |= ineq(v, llc::GE, rational::zero()); // v >= 0 lemma |= ineq(t, llc::GE, -offset); // t >= 0 t.add_monomial(rational(-1), v); lemma |= ineq(t, llc::LE, -offset); // t - v <= 0 } TRACE(grobner, lemma.display(tout << "quotient4\n")); found_lemma = true; continue; } if (abs(lc_value) > 1 && abs(v_value) > 1 && abs(v_value) >= abs(r_value)) { // v*c + t = 0 & |c| > 1, |v| > 1 => |v| < |t| lemma_builder lemma(c(), "grobner-bounds"); auto [tr, offset_t] = linear_to_term(r); auto [tc, offset_c] = linear_to_term(lc); add_dependencies(lemma, eq); if (lc_value > 1) // c <= 1 lemma |= ineq(tc, llc::LE, rational(1) - offset_c); // lc <= 1 else // c >= -1 lemma |= ineq(tc, llc::GE, rational(-1) - offset_c); // lc >= -1 if (v_value > 1) lemma |= ineq(v, llc::LE, rational(1)); // v <= 1 else lemma |= ineq(v, llc::GE, rational(-1)); // v >= -1 if (v_value > 0 && r_value > 0) { // t >= v + 1 tr.add_monomial(rational(-1), v); lemma |= ineq(tr, llc::GE, rational(1) - offset_t); } else if (v_value > 0 && r_value < 0) { // t + v <= -1 tr.add_monomial(rational(1), v); lemma |= ineq(tr, llc::LE, rational(-1) - offset_t); } else if (v_value < 0 && r_value > 0) { // t + v >= 1 tr.add_monomial(rational(1), v); lemma |= ineq(tr, llc::GE, rational(1) - offset_t); } else if (v_value < 0 && r_value < 0) { // t - v <= -1 tr.add_monomial(rational(-1), v); lemma |= ineq(tr, llc::LE, rational(-1) - offset_t); } found_lemma = true; TRACE(grobner, lemma.display(tout << "quotient5\n")); continue; } // other division lemmas are possible. // also extend to non-linear r, non-linear lc } CTRACE(grobner, !found_lemma, tout << "no lemmas found for " << p << "\n"); return found_lemma; } void grobner::explain(dd::solver::equation const& eq, lp::explanation& exp) { u_dependency_manager dm; vector lv; dm.linearize(eq.dep(), lv); for (unsigned ci : lv) exp.push_back(ci); } void grobner::add_dependencies(lemma_builder& lemma, const dd::solver::equation& eq) { lp::explanation exp; explain(eq, exp); lemma &= exp; } bool grobner::configure() { m_solver.reset(); try { set_level2var(); TRACE(grobner, tout << "base vars: "; for (lpvar j : c().active_var_set()) if (lra.is_base(j)) tout << "j" << j << " "; tout << "\n"); for (lpvar j : c().active_var_set()) { if (lra.is_base(j)) add_row(lra.basic2row(j)); if (c().is_monic_var(j) && c().var_is_fixed(j)) add_fixed_monic(j); } } catch (dd::pdd_manager::mem_out) { IF_VERBOSE(2, verbose_stream() << "pdd throw\n"); return false; } TRACE(grobner, m_solver.display(tout)); #if 0 IF_VERBOSE(2, m_pdd_grobner.display(verbose_stream())); dd::pdd_eval eval(m_pdd_manager); eval.var2val() = [&](unsigned j){ return val(j); }; for (auto* e : m_pdd_grobner.equations()) { dd::pdd p = e->poly(); rational v = eval(p); if (p.is_linear() && !eval(p).is_zero()) { IF_VERBOSE(0, verbose_stream() << "violated linear constraint " << p << "\n"); } } #endif struct dd::solver::config cfg; cfg.m_max_steps = m_solver.equations().size(); cfg.m_max_simplified = c().params().arith_nl_grobner_max_simplified(); cfg.m_eqs_growth = c().params().arith_nl_grobner_eqs_growth(); cfg.m_expr_size_growth = c().params().arith_nl_grobner_expr_size_growth(); cfg.m_expr_degree_growth = c().params().arith_nl_grobner_expr_degree_growth(); cfg.m_number_of_conflicts_to_report = c().params().arith_nl_grobner_cnfl_to_report(); m_solver.set(cfg); m_solver.adjust_cfg(); m_pdd_manager.set_max_num_nodes(10000); // or something proportional to the number of initial nodes. return true; } std::ostream& grobner::diagnose_pdd_miss(std::ostream& out) { // m_pdd_grobner.display(out); dd::pdd_eval eval; eval.var2val() = [&](unsigned j){ return val(j); }; for (auto* e : m_solver.equations()) { dd::pdd p = e->poly(); rational v = eval(p); if (!v.is_zero()) { out << p << " := " << v << "\n"; } } for (unsigned j = 0; j < lra.number_of_vars(); ++j) { if (lra.column_has_lower_bound(j) || lra.column_has_upper_bound(j)) { out << j << ": ["; if (lra.column_has_lower_bound(j)) out << lra.get_lower_bound(j); out << ".."; if (lra.column_has_upper_bound(j)) out << lra.get_upper_bound(j); out << "]\n"; } } return out; } bool grobner::equation_is_true(dd::solver::equation const& eq) { if (any_of(eq.poly().free_vars(), [&](unsigned j) { return lra.column_is_free(j); })) return true; dd::pdd_eval eval; eval.var2val() = [&](unsigned j){ return val(j); }; return eval(eq.poly()) == 0; } bool grobner::is_conflicting(const dd::solver::equation& e) { if (equation_is_true(e)) return false; auto& di = c().m_intervals.get_dep_intervals(); dd::pdd_interval evali(di); evali.var2interval() = [this](lpvar j, bool deps, scoped_dep_interval& a) { if (deps) c().m_intervals.set_var_interval(j, a); else c().m_intervals.set_var_interval(j, a); }; scoped_dep_interval i(di), i_wd(di); evali.get_interval(e.poly(), i); if (!di.separated_from_zero(i)) { if (add_horner_conflict(e)) { TRACE(grobner, m_solver.display(tout << "horner conflict ", e) << "\n"); return true; } return false; } evali.get_interval(e.poly(), i_wd); std::function f = [this](const lp::explanation& e) { lemma_builder lemma(m_core, "pdd"); lemma &= e; }; if (di.check_interval_for_conflict_on_zero(i_wd, e.dep(), f)) { TRACE(grobner, m_solver.display(tout << "conflict ", e) << "\n"); return true; } else { TRACE(grobner, m_solver.display(tout << "no conflict ", e) << "\n"); return false; } } bool grobner::propagate_linear_equations() { unsigned changed = 0; m_mon2var.clear(); for (auto const& m : c().emons()) m_mon2var[m.vars()] = m.var(); for (auto eq : m_solver.equations()) if (propagate_linear_equations(*eq)) ++changed; return changed > 0; } bool grobner::propagate_linear_equations(dd::solver::equation const& e) { if (equation_is_true(e)) return false; rational value(0); for (auto const& [coeff, vars] : e.poly()) { if (vars.empty()) value += coeff; else if (vars.size() == 1) value += coeff*val(vars[0]); else if (m_mon2var.find(vars) == m_mon2var.end()) return false; else value += coeff*val(m_mon2var.find(vars)->second); } if (value == 0) return false; rational lc(1); for (auto const& [coeff, vars] : e.poly()) lc = lcm(denominator(coeff), lc); vector> coeffs; rational offset(0); for (auto const& [coeff, vars] : e.poly()) { if (vars.size() == 0) offset -= lc*coeff; else if (vars.size() == 1) coeffs.push_back({lc*coeff, vars[0]}); else coeffs.push_back({lc*coeff, m_mon2var.find(vars)->second}); } lp::lpvar j = c().lra.add_term(coeffs, UINT_MAX); c().lra.update_column_type_and_bound(j, lp::lconstraint_kind::EQ, offset, e.dep()); c().m_check_feasible = true; return true; } void grobner::add_var_and_its_factors_to_q_and_collect_new_rows(lpvar j, svector & q) { if (c().active_var_set_contains(j)) return; c().insert_to_active_var_set(j); if (c().is_monic_var(j)) { const monic& m = c().emons()[j]; for (auto fcn : factorization_factory_imp(m, m_core)) for (const factor& fc: fcn) q.push_back(var(fc)); } else if (c().lra.column_has_term(j)) { const lp::lar_term& t = c().lra.get_term(j); for (auto k : t) q.push_back(k.j()); } else if (c().var_is_fixed(j)) return; const auto& matrix = lra.A_r(); for (auto & s : matrix.m_columns[j]) { unsigned row = s.var(); if (m_rows.contains(row)) continue; m_rows.insert(row); unsigned k = lra.get_base_column_in_row(row); // a free column over the reals can be assigned if (lra.column_is_free(k) && k != j) { if (!lra.var_is_int(k)) continue; // free integer columns are ignored unless m_add_all_eqs is set or we are doing gcd test. if (!m_add_all_eqs && !m_config.m_gcd_test && !m_config.m_propagate_quotients) continue; // a free integer column with integer coefficients can be assigned. if (!m_add_all_eqs && all_of(c().lra.get_row(row), [&](auto& ri) { return ri.coeff().is_int();})) continue; } CTRACE(grobner, matrix.m_rows[row].size() > c().params().arith_nl_grobner_row_length_limit(), tout << "ignore the row " << row << " with the size " << matrix.m_rows[row].size() << "\n";); // limits overhead of grobner equations, unless this is for extracting a complete COI of the non-satisfied subset. if (!m_add_all_eqs && matrix.m_rows[row].size() > c().params().arith_nl_horner_row_length_limit()) continue; for (auto& rc : matrix.m_rows[row]) add_var_and_its_factors_to_q_and_collect_new_rows(rc.var(), q); } } const rational& grobner::val_of_fixed_var_with_deps(lpvar j, u_dependency*& dep) { auto* d = lra.get_bound_constraint_witnesses_for_column(j); if (d) dep = c().m_intervals.mk_join(dep, d); return lra.column_lower_bound(j).x; } dd::pdd grobner::pdd_expr(lp::lar_term const& t, u_dependency*& dep) { dd::pdd r = m_pdd_manager.mk_val(0); for (auto const& ti : t) r += pdd_expr(ti.coeff(), ti.j(), dep); return r; } dd::pdd grobner::pdd_expr(const rational& coeff, lpvar j, u_dependency*& dep) { dd::pdd r = m_pdd_manager.mk_val(coeff); sbuffer vars; vars.push_back(j); u_dependency* zero_dep = dep; while (!vars.empty()) { j = vars.back(); vars.pop_back(); if (c().params().arith_nl_grobner_subs_fixed() > 0 && c().var_is_fixed_to_zero(j)) { r = m_pdd_manager.mk_val(val_of_fixed_var_with_deps(j, zero_dep)); dep = zero_dep; return r; } if (c().params().arith_nl_grobner_subs_fixed() == 1 && c().var_is_fixed(j)) r *= val_of_fixed_var_with_deps(j, dep); else if (m_config.m_expand_terms && c().lra.column_has_term(j)) r *= pdd_expr(c().lra.get_term(j), dep); else if (!c().is_monic_var(j)) r *= m_pdd_manager.mk_var(j); else for (lpvar k : c().emons()[j].vars()) vars.push_back(k); } return r; } /** \brief convert p == 0 into a solved form v == r, such that v has bounds [lo, oo) iff r has bounds [lo', oo) v has bounds (oo,hi] iff r has bounds (oo,hi'] The solved form allows the Grobner solver identify more bounds conflicts. A bad leading term can miss bounds conflicts. For example for x + y + z == 0 where x, y : [0, oo) and z : (oo,0] we prefer to solve z == -x - y instead of x == -z - y because the solution -z - y has neither an upper, nor a lower bound. */ bool grobner::is_solved(dd::pdd const& p, unsigned& v, dd::pdd& r) { if (!p.is_linear()) return false; r = p; unsigned num_lo = 0, num_hi = 0; unsigned lo = 0, hi = 0; rational lc, hc, c; while (!r.is_val()) { SASSERT(r.hi().is_val()); v = r.var(); rational val = r.hi().val(); switch (lra.get_column_type(v)) { case lp::column_type::lower_bound: if (val > 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; break; case lp::column_type::upper_bound: if (val < 0) num_lo++, lo = v, lc = val; else num_hi++, hi = v, hc = val; break; case lp::column_type::fixed: case lp::column_type::boxed: break; default: return false; } if (num_lo > 1 && num_hi > 1) return false; r = r.lo(); } if (num_lo == 1 && num_hi > 1) { v = lo; c = lc; } else if (num_hi == 1 && num_lo > 1) { v = hi; c = hc; } else return false; r = c*m_pdd_manager.mk_var(v) - p; if (c != 1) r = r * (1/c); return true; } /** \brief add an equality to grobner solver, convert it to solved form if available. */ void grobner::add_eq(dd::pdd& p, u_dependency* dep) { unsigned v; dd::pdd q(m_pdd_manager); m_solver.simplify(p, dep); if (is_solved(p, v, q)) m_solver.add_subst(v, q, dep); else m_solver.add(p, dep); } void grobner::add_fixed_monic(unsigned j) { u_dependency* dep = nullptr; dd::pdd r = m_pdd_manager.mk_val(rational(1)); for (lpvar k : c().emons()[j].vars()) r *= pdd_expr(rational::one(), k, dep); r -= val_of_fixed_var_with_deps(j, dep); add_eq(r, dep); } void grobner::add_row(const std_vector> & row) { u_dependency *dep = nullptr; rational val; dd::pdd sum = m_pdd_manager.mk_val(rational(0)); for (const auto &p : row) sum += pdd_expr(p.coeff(), p.var(), dep); TRACE(grobner, c().print_row(row, tout) << " " << sum << "\n"); add_eq(sum, dep); } void grobner::find_nl_cluster() { prepare_rows_and_active_vars(); svector q; TRACE(grobner, for (lpvar j : c().m_to_refine) print_monic(c().emons()[j], tout) << "\n";); for (lpvar j : c().m_to_refine) q.push_back(j); while (!q.empty()) { lpvar j = q.back(); q.pop_back(); add_var_and_its_factors_to_q_and_collect_new_rows(j, q); } TRACE(grobner, tout << "vars in cluster: "; for (lpvar j : c().active_var_set()) tout << "j" << j << " "; tout << "\n"; display_matrix_of_m_rows(tout); ); } void grobner::prepare_rows_and_active_vars() { m_rows.reset(); c().clear_active_var_set(); } void grobner::display_matrix_of_m_rows(std::ostream & out) const { const auto& matrix = lra.A_r(); out << m_rows.size() << " rows" << "\n"; out << "the matrix\n"; for (const auto & r : matrix.m_rows) c().print_row(r, out) << std::endl; } void grobner::set_level2var() { unsigned n = lra.column_count(); unsigned_vector sorted_vars(n), weighted_vars(n); for (unsigned j = 0; j < n; j++) { sorted_vars[j] = j; weighted_vars[j] = c().get_var_weight(j); } #if 1 // potential update to weights for (unsigned j = 0; j < n; j++) { if (c().is_monic_var(j) && c().m_to_refine.contains(j)) { for (lpvar k : c().m_emons[j].vars()) { weighted_vars[k] += 6; } } } #endif std::sort(sorted_vars.begin(), sorted_vars.end(), [&](unsigned a, unsigned b) { unsigned wa = weighted_vars[a]; unsigned wb = weighted_vars[b]; return wa < wb || (wa == wb && a < b); }); unsigned_vector l2v(n); for (unsigned j = 0; j < n; j++) l2v[j] = sorted_vars[j]; m_pdd_manager.reset(l2v); TRACE(grobner, for (auto v : sorted_vars) tout << "j" << v << " w:" << weighted_vars[v] << " "; tout << "\n"); } bool grobner::is_nla_conflict(const dd::solver::equation& eq) { vector eqs; eqs.push_back(eq.poly()); return l_false == c().m_nra.check(eqs); } bool grobner::add_horner_conflict(const dd::solver::equation& eq) { nex_creator& nc = m_nex_creator; nc.pop(0); nex_creator::sum_factory sum(nc); u_map var2nex; for (auto v : eq.poly().free_vars()) var2nex.insert(v, nc.mk_var(v)); unsigned mx = 0; for (auto v : eq.poly().free_vars()) mx = std::max(v, mx); nc.set_number_of_vars(mx + 1); for (auto const& [coeff, vars] : eq.poly()) { switch (vars.size()) { case 0: sum += nc.mk_scalar(coeff); break; case 1: sum += nc.mk_mul(coeff, var2nex[vars[0]]); break; default: nc.m_mk_mul.reset(); nc.m_mk_mul *= coeff; for (auto v : vars) nc.m_mk_mul *= var2nex[v]; sum += nc.m_mk_mul.mk(); break; } } nex* e = nc.simplify(sum.mk()); if (e->get_degree() < 2 || !e->is_sum()) return false; auto dep = eq.dep(); cross_nested cn( [this, dep](const nex* n) { return c().m_intervals.check_nex(n, dep); }, [this](unsigned j) { return c().var_is_fixed(j); }, c().reslim(), c().random(), nc); cn.run(to_sum(e)); bool ret = cn.done(); return ret; } bool grobner::add_nla_conflict(const dd::solver::equation& eq) { if (is_nla_conflict(eq)) { lemma_builder lemma(m_core,"nla-conflict"); lp::explanation exp; explain(eq, exp); lemma &= exp; return true; } return false; } void grobner::check_missing_propagation(const dd::solver::equation& e) { bool is_confl = is_nla_conflict(e); CTRACE(grobner, is_confl, m_solver.display(tout << "missed conflict ", e);); if (is_confl) { IF_VERBOSE(2, verbose_stream() << "missed conflict\n"); return; } //lbool r = c().m_nra.check_tight(e.poly()); //CTRACE(grobner, r == l_false, m_solver.display(tout << "tight equality ", e);); } }