diff --git a/src/math/polysat/fixplex.h b/src/math/polysat/fixplex.h index 7b65f112f..b8b5c3557 100644 --- a/src/math/polysat/fixplex.h +++ b/src/math/polysat/fixplex.h @@ -53,10 +53,24 @@ namespace polysat { virtual void add_le(var_t v, var_t w, unsigned dep) = 0; virtual void add_lt(var_t v, var_t w, unsigned dep) = 0; virtual void restore_ineq() = 0; + virtual bool inconsistent() const = 0; virtual unsigned_vector const& get_unsat_core() const = 0; }; + struct ineq { + var_t v = UINT_MAX; + var_t w = UINT_MAX; + bool strict = false; + bool is_active = true; + unsigned dep = UINT_MAX; + ineq(var_t v, var_t w, unsigned dep, bool s) : + v(v), w(w), strict(s), dep(dep) {} + + std::ostream& display(std::ostream& out) const { + return out << "v" << v << (strict ? " < v" : " <= v") << w; + } + }; template class fixplex : public fixplex_base { @@ -143,19 +157,10 @@ namespace polysat { fix_entry():x(null_var), r(0) {} }; - struct ineq { - var_t v = null_var; - var_t w = null_var; - bool strict = false; - bool is_active = true; - unsigned dep = UINT_MAX; - ineq(var_t v, var_t w, unsigned dep, bool s): - v(v), w(w), strict(s), dep(dep) {} - }; - enum trail_i { inc_level_i, set_bound_i, + set_inconsistent_i, add_ineq_i, add_row_i }; @@ -175,6 +180,7 @@ namespace polysat { random_gen m_random; uint_set m_left_basis; unsigned_vector m_unsat_core; + bool m_inconsistent = false; unsigned_vector m_base_vars; stats m_stats; vector m_stashed_bounds; @@ -200,6 +206,7 @@ namespace polysat { void push() override; void pop(unsigned n) override; + bool inconsistent() const override { return m_inconsistent; } lbool make_feasible() override; void add_row(var_t base, unsigned num_vars, var_t const* vars, rational const* coeffs) override; @@ -226,6 +233,10 @@ namespace polysat { unsigned get_num_vars() const { return m_vars.size(); } void reset(); lbool propagate_bounds(); + + svector> stack; + uint_set on_stack; + lbool propagate_ineqs(ineq const& i0); void propagate_eqs(); vector const& var_eqs() const { return m_var_eqs; } void reset_eqs() { m_var_eqs.reset(); } @@ -251,8 +262,8 @@ namespace polysat { void get_offset_eqs(row const& r); void fixed_var_eh(row const& r, var_t x); void eq_eh(var_t x, var_t y, row const& r1, row const& r2); - lbool propagate_bounds(row const& r); - bool propagate_bounds(ineq const& i); + bool propagate_row(row const& r); + bool propagate_ineq(ineq const& i); bool propagate_strict_bounds(ineq const& i); bool propagate_non_strict_bounds(ineq const& i); bool new_bound(row const& r, var_t x, mod_interval const& range); @@ -404,6 +415,7 @@ namespace polysat { } }; typedef _scoped_numeral scoped_numeral; + }; typedef generic_uint_ext uint64_ext; @@ -415,5 +427,11 @@ namespace polysat { } + inline std::ostream& operator<<(std::ostream& out, ineq const& i) { + return i.display(out); + } + + + }; diff --git a/src/math/polysat/fixplex_def.h b/src/math/polysat/fixplex_def.h index aa223926a..8b7955fa8 100644 --- a/src/math/polysat/fixplex_def.h +++ b/src/math/polysat/fixplex_def.h @@ -96,6 +96,11 @@ namespace polysat { case trail_i::add_ineq_i: restore_ineq(); break; + case trail_i::set_inconsistent_i: + SASSERT(m_inconsistent); + m_inconsistent = false; + m_unsat_core.reset(); + break; default: UNREACHABLE(); } @@ -126,6 +131,8 @@ namespace polysat { template lbool fixplex::make_feasible() { + if (m_inconsistent) + return l_false; ++m_stats.m_num_checks; m_left_basis.reset(); unsigned num_iterations = 0; @@ -537,11 +544,13 @@ namespace polysat { auto hi0 = m_vars[v].hi; m_stashed_bounds.push_back(stashed_bound(v, m_vars[v])); m_trail.push_back(trail_i::set_bound_i); + // std::cout << "new bound " << x << " " << m_vars[x] << " " << mod_interval(l, h) << " -> "; m_vars[v] &= mod_interval(l, h); if (lo0 != m_vars[v].lo) m_vars[v].m_lo_dep = dep; if (hi0 != m_vars[v].hi) m_vars[v].m_hi_dep = dep; + // std::cout << m_vars[x] << "\n"; } template @@ -614,9 +623,9 @@ namespace polysat { if (idx >= m_ineqs.size()) continue; auto& ineq = m_ineqs[idx]; - m_var_is_touched.setx(ineq.v, false, false); - m_var_is_touched.setx(ineq.w, false, false); - ineq.is_active = false; + m_var_is_touched.setx(ineq.v, false, false); + m_var_is_touched.setx(ineq.w, false, false); + ineq.is_active = false; } m_ineqs_to_check.reset(); } @@ -646,11 +655,12 @@ namespace polysat { */ template bool fixplex::ineqs_are_violated() { + lbool r; for (unsigned i = 0; i < m_ineqs_to_check.size(); ++i) { unsigned idx = m_ineqs_to_check[i]; if (idx >= m_ineqs.size()) continue; - if (!propagate_bounds(m_ineqs[idx])) + if (r = propagate_ineqs(m_ineqs[idx]), r == l_false) return true; } return false; @@ -860,7 +870,6 @@ namespace polysat { */ template void fixplex::set_infeasible_base(var_t v) { - m_unsat_core.reset(); SASSERT(is_base(v)); auto row = base2row(v); ptr_vector todo; @@ -869,7 +878,12 @@ namespace polysat { todo.push_back(m_vars[v].m_lo_dep); todo.push_back(m_vars[v].m_hi_dep); } + SASSERT(!m_inconsistent); + SASSERT(m_unsat_core.empty()); + m_inconsistent = true; + m_trail.push_back(trail_i::set_inconsistent_i); m_deps.linearize(todo, m_unsat_core); + } /** @@ -1115,15 +1129,87 @@ namespace polysat { template lbool fixplex::propagate_bounds() { lbool r = l_true; - for (unsigned i = 0; r == l_true && i < m_rows.size(); ++i) - r = propagate_bounds(row(i)); - if (r != l_true) - return r; - for (auto ineq : m_ineqs) { - if (!propagate_bounds(ineq)) + for (unsigned i = 0; i < m_rows.size(); ++i) + if (!propagate_row(row(i))) return l_false; + for (auto ineq : m_ineqs) + if (r = propagate_ineqs(ineq), r != l_true) + return r; + return l_true; + } + + // + // DFS search propagating inequalities + // TBDs: + // - manage on_stack: which variables? (not source of root inequality) + // - propagate other direction? + // - re-propagate if lower bound for w gets increased in stack? + // - combine with row propagation? + // - search for shorter cycles? conflicts with literals asserted at lower (glue) levels? + // - statistics + // use diff-logic propagation instead? + // - use heap based on potential + // - update gamma without overflow issues + // + + template + lbool fixplex::propagate_ineqs(ineq const& i0) { + numeral old_lo = m_vars[i0.w].lo; + if (!propagate_ineq(i0)) + return l_false; + if (old_lo == m_vars[i0.w].lo) + return l_true; + on_stack.reset(); + stack.reset(); + stack.push_back(std::make_pair(0, i0)); + on_stack.insert(i0.v); + while (!stack.empty()) { + if (!m_limit.inc()) + return l_undef; + auto [ineq_out, i] = stack.back(); + auto const& ineqs = m_var2ineqs[i.w]; + for (; ineq_out < ineqs.size(); ++ineq_out) { + auto& i_out = m_ineqs[ineqs[ineq_out]]; + if (i.w != i_out.v) + continue; + old_lo = m_vars[i_out.w].lo; + if (!propagate_ineq(i_out)) + return l_false; + bool is_onstack = on_stack.contains(i_out.w); + if (old_lo != m_vars[i_out.w].lo && !is_onstack) { + on_stack.insert(i_out.w); + stack.back().first = ineq_out + 1; + stack.push_back(std::make_pair(0, i_out)); + break; + } + + if (!is_onstack) { + on_stack.insert(i_out.w); + continue; + } + + bool strict = i_out.strict, found = false; + unsigned j = stack.size(); + for (; !found && j-- > 0; ) { + ineq i2 = stack[j].second; + strict |= i2.strict; + if (i2.v == i_out.w) + found = true; + } + if (strict && found) { + auto* d = mk_leaf(i_out.dep); + for (; j < stack.size(); ++j) + d = m_deps.mk_join(d, mk_leaf(stack[j].second.dep)); + conflict(d); + return l_false; + } + } + if (ineq_out == ineqs.size()) { + on_stack.remove(i.w); + stack.pop_back(); + } } - return r; + return l_true; } /** @@ -1136,7 +1222,7 @@ namespace polysat { */ template - lbool fixplex::propagate_bounds(row const& r) { + bool fixplex::propagate_row(row const& r) { mod_interval range(0, 1); numeral free_c = 0; var_t free_v = null_var; @@ -1152,12 +1238,12 @@ namespace polysat { } range += m_vars[v] * c; if (range.is_free()) - return l_true; + return true; } if (free_v != null_var) { range = (-range) * free_c; - lbool res = new_bound(r, free_v, range) ? l_true : l_false; + bool res = new_bound(r, free_v, range); SASSERT(in_bounds(free_v)); return res; } @@ -1165,7 +1251,7 @@ namespace polysat { var_t v = e.var(); SASSERT(!is_free(v)); auto range1 = range - m_vars[v] * e.coeff(); - lbool res = new_bound(r, v, range1) ? l_true : l_false; + bool res = new_bound(r, v, range1); if (res != l_true) return res; // SASSERT(in_bounds(v)); @@ -1180,6 +1266,8 @@ namespace polysat { auto* vlo = m_vars[v].m_lo_dep, *vhi = m_vars[v].m_hi_dep; auto* wlo = m_vars[w].m_lo_dep, *whi = m_vars[w].m_hi_dep; + if (v == w) + return conflict(i, nullptr), false; if (lo(w) == 0 && !new_bound(i, w, lo(w) + 1, lo(w), wlo)) return false; if (hi(w) == 1 && !new_bound(i, w, lo(w), hi(w) - 1, whi)) @@ -1214,9 +1302,9 @@ namespace polysat { // manual patch if (is_fixed(w) && lo(w) == 0) - return conflict(wlo, whi), false; + return conflict(i, wlo, whi), false; if (is_fixed(v) && hi(v) == 0) - return conflict(vlo, vhi), false; + return conflict(i, vlo, vhi), false; if (!is_free(w) && (lo(w) <= hi(w) || hi(w) == 0) && (lo(v) < hi(v) || hi(v) == 0) && !new_bound(i, v, lo(v), hi(w) - 1, vlo, wlo, whi)) return false; if (!is_free(v) && (lo(w) <= hi(w) || hi(w) == 0) && (lo(v) < hi(v) || hi(v) == 0) && !new_bound(i, w, lo(v) + 1, hi(w), vlo, vhi, whi)) @@ -1234,7 +1322,7 @@ namespace polysat { if (hi(w) <= lo(v) && (lo(v) < hi(v) || hi(v) == 0) && !new_bound(i, w, lo(w), 0, vlo, vhi, wlo, whi)) return false; if (lo(w) < hi(w) && hi(w) <= lo(v) && (lo(v) < hi(v) || hi(v) == 0)) - return conflict(vlo, vhi, wlo, whi), false; + return conflict(i, vlo, vhi, wlo, whi), false; // if (!is_free(w) && hi(v) < lo(v) && lo(w) != 0 && (lo(w) <= hi(w) || hi(w) == 0) && !new_bound(i, v, lo(w) - 1, hi(v), vlo, vhi, wlo, whi)) // return false; @@ -1245,7 +1333,7 @@ namespace polysat { if (lo(w) == 0 && !new_bound(i, w, lo(w) + 1, lo(w), wlo)) return false; if (is_fixed(v) && hi(w) <= hi(v) && lo(w) <= hi(w) && !(is_free(w))) - return conflict(wlo, whi, vhi, vlo), false; + return conflict(i, wlo, whi, vhi, vlo), false; if (lo(w) <= lo(v) && lo(v) <= hi(v) && !new_bound(i, w, lo(v) + 1, lo(v), wlo, vhi, vlo)) return false; if (hi(w) <= hi(v) && lo(w) <= hi(w) && !(is_free(w)) && !new_bound(i, v, lo(v), hi(v) - 1, wlo, whi, vhi)) @@ -1263,7 +1351,7 @@ namespace polysat { if (hi(w) <= lo(v) && lo(w) <= hi(w) && !(is_free(w)) && !new_bound(i, v, lo(v) + 1, hi(w) - 1, wlo, whi, vlo)) return false; if (is_fixed(w) && hi(v) == 0 && lo(w) <= lo(v)) - return conflict(wlo, whi, vhi, vlo), false; + return conflict(i, wlo, whi, vhi, vlo), false; if (hi(v) == 0 && lo(w) <= lo(v) && !new_bound(i, w, lo(v) + 1, hi(v), wlo, vhi, vlo)) return false; if (hi(v) == 0 && !(is_free(v)) && !new_bound(i, v, lo(v), hi(v) - 1, vhi)) @@ -1290,7 +1378,7 @@ namespace polysat { if (hi(w) < lo(w) && hi(w) <= lo(v) && lo(v) < hi(v) && !new_bound(i, w, lo(w), 0, vlo, vhi, wlo, whi)) return false; if (lo(w) < hi(w) && hi(w) <= lo(v) && (lo(v) < hi(v) || hi(v) == 0)) - return conflict(vlo, vhi, wlo, whi), false; + return conflict(i, vlo, vhi, wlo, whi), false; // automatically generated code. // see scripts/fixplex.py for script @@ -1366,7 +1454,7 @@ namespace polysat { } template - bool fixplex::propagate_bounds(ineq const& i) { + bool fixplex::propagate_ineq(ineq const& i) { if (i.strict) return propagate_strict_bounds(i); else @@ -1382,6 +1470,9 @@ namespace polysat { void fixplex::conflict(u_dependency* a) { m_unsat_core.reset(); m_deps.linearize(a, m_unsat_core); + SASSERT(!m_inconsistent); + m_inconsistent = true; + m_trail.push_back(trail_i::set_inconsistent_i); TRACE("polysat", tout << "core: " << m_unsat_core << "\n";); } @@ -1399,10 +1490,8 @@ namespace polysat { template bool fixplex::new_bound(ineq const& i, var_t x, numeral const& l, numeral const& h, u_dependency* a, u_dependency* b, u_dependency* c, u_dependency* d) { bool was_fixed = lo(x) + 1 == hi(x); - u_dependency* dep = m_deps.mk_join(mk_leaf(i.dep), m_deps.mk_join(a, m_deps.mk_join(b, m_deps.mk_join(c, d)))); - // std::cout << "new bound " << x << " " << m_vars[x] << " " << mod_interval(l, h) << " -> "; + u_dependency* dep = m_deps.mk_join(mk_leaf(i.dep), m_deps.mk_join(a, m_deps.mk_join(b, m_deps.mk_join(c, d)))); update_bounds(x, l, h, dep); - // std::cout << m_vars[x] << "\n"; if (m_vars[x].is_empty()) return conflict(m_vars[x].m_lo_dep, m_vars[x].m_hi_dep), false; else if (!was_fixed && lo(x) + 1 == hi(x)) { @@ -1435,12 +1524,8 @@ namespace polysat { if (vi.m_is_base) out << "b:" << vi.m_base2row << " " << pp(m_rows[vi.m_base2row].m_value) << " "; out << "\n"; } - for (auto const& i : m_ineqs) { - if (i.strict) - out << "v" << i.v << " < v" << i.w << "\n"; - else - out << "v" << i.v << " <= v" << i.w << "\n"; - } + for (ineq const& i : m_ineqs) + out << i << "\n"; return out; } diff --git a/src/test/fixplex.cpp b/src/test/fixplex.cpp index adad74428..3ef9774b4 100644 --- a/src/test/fixplex.cpp +++ b/src/test/fixplex.cpp @@ -131,13 +131,40 @@ namespace polysat { std::cout << 29 << " " << e.mul_inverse(29) << " " << 29*e.mul_inverse(29) << "\n"; } - static void test_ineq1() { - std::cout << "ineq1\n"; + static void test_ineq_propagation1() { + std::cout << "ineq propagation 1\n"; scoped_fp fp; var_t x = 0, y = 1; fp.add_lt(x, y, 1); fp.add_le(y, x, 2); fp.run(); + std::cout << "core:" << fp.get_unsat_core() << "\n"; + SASSERT(fp.get_unsat_core().contains(1)); + SASSERT(fp.get_unsat_core().contains(2)); + } + + static void test_ineq_propagation2() { + std::cout << "ineq propagation 2\n"; + scoped_fp fp; + var_t x = 0, y = 1, z = 2, u = 3, v = 4; + fp.add_le(x, y, 1); + fp.add_le(y, z, 2); + fp.add_le(z, u, 3); + fp.add_le(u, v, 4); + fp.add_le(v, x, 5); + fp.run(); + for (unsigned i = 0; i < 4; ++i) { + SASSERT(!fp.inconsistent()); + fp.push(); + fp.add_lt(i, i + 1, 6); + fp.run(); + SASSERT(fp.inconsistent()); + std::cout << "core:" << fp.get_unsat_core() << "\n"; + for (unsigned j = 1; j < 5; ++j) + SASSERT((j != i + 1) == fp.get_unsat_core().contains(j)); + fp.get_unsat_core().contains(6); + fp.pop(1); + } } static void test_ineqs() { @@ -344,7 +371,8 @@ namespace polysat { void tst_fixplex() { - polysat::test_ineq1(); + polysat::test_ineq_propagation1(); + polysat::test_ineq_propagation2(); polysat::test_ineqs(); polysat::test1();