/* Copyright (c) 2017 Microsoft Corporation Author: Nikolaj Bjorner (nbjorner) Lev Nachmanson (levnach) */ #pragma once #include #include "math/lp/lp_settings.h" #include "util/uint_set.h" #include "math/lp/implied_bound.h" #include "util/vector.h" namespace lp { template class lp_bound_propagator { uint_set m_visited_rows; // these maps map a column index to the corresponding index in ibounds u_map m_improved_lower_bounds; u_map m_improved_upper_bounds; T& m_imp; std_vector& m_ibounds; map, default_eq> m_val2fixed_row; // works for rows of the form x + y + sum of fixed = 0 map, default_eq> m_row2index_pos; // works for rows of the form x - y + sum of fixed = 0 map, default_eq> m_row2index_neg; const vector* m_column_types; // returns true iff there is only one non-fixed column in the row bool only_one_nfixed(unsigned r, unsigned& x) { x = UINT_MAX; for (const auto& c: lp().get_row(r)) { if (column_is_fixed(c.var())) continue; if (x != UINT_MAX) return false; x = c.var(); } return x != UINT_MAX; } public: const lar_solver& lp() const { return m_imp.lp(); } lar_solver& lp() { return m_imp.lp(); } bool upper_bound_is_available(unsigned j) const { switch (get_column_type(j)) { case column_type::fixed: case column_type::boxed: case column_type::upper_bound: return true; default: return false; } } bool lower_bound_is_available(unsigned j) const { switch (get_column_type(j)) { case column_type::fixed: case column_type::boxed: case column_type::lower_bound: return true; default: return false; } } private: void try_add_equation_with_internal_fixed_tables(unsigned r1) { unsigned v1, v2; if (!only_one_nfixed(r1, v1)) return; unsigned r2 = UINT_MAX; if (!m_val2fixed_row.find(val(v1), r2) || r2 >= lp().row_count()) { m_val2fixed_row.insert(val(v1), r1); return; } if (!only_one_nfixed(r2, v2) || val(v1) != val(v2) || is_int(v1) != is_int(v2)) { m_val2fixed_row.insert(val(v1), r1); return; } if (v1 == v2) return; #if Z3DEBUG lp_assert(val(v1) == val(v2)); unsigned debv1, debv2; lp_assert(only_one_nfixed(r1, debv1) && only_one_nfixed(r2, debv2)); lp_assert(debv1 == v1 && debv2 == v2); lp_assert(ival(v1).y == ival(v2).y); #endif explanation ex; explain_fixed_in_row(r1, ex); explain_fixed_in_row(r2, ex); TRACE("eq", print_row(tout, r1); print_row(tout, r2); tout << v1 << " == " << v2 << " = " << val(v1) << "\n"); add_eq_on_columns(ex, v1, v2, true); } static bool is_not_set(unsigned j) { return j == UINT_MAX; } static bool is_set(unsigned j) { return j != UINT_MAX; } void reset_cheap_eq_eh() { m_row2index_pos.reset(); m_row2index_neg.reset(); } struct reset_cheap_eq { lp_bound_propagator& p; reset_cheap_eq(lp_bound_propagator& p) : p(p) {} ~reset_cheap_eq() { p.reset_cheap_eq_eh(); } }; public: lp_bound_propagator(T& imp, std_vector & ibounds) : m_imp(imp), m_ibounds(ibounds) {} const std_vector& ibounds() const { return m_ibounds; } void init() { m_improved_upper_bounds.reset(); m_improved_lower_bounds.reset(); m_ibounds.clear(); m_column_types = &lp().get_column_types(); } column_type get_column_type(unsigned j) const { return (*m_column_types)[j]; } const impq& get_lower_bound(unsigned j) const { return lp().get_lower_bound(j); } const mpq& get_lower_bound_rational(unsigned j) const { return lp().get_lower_bound(j).x; } const impq& get_upper_bound(unsigned j) const { return lp().get_upper_bound(j); } const mpq& get_upper_bound_rational(unsigned j) const { return lp().get_upper_bound(j).x; } // require also the zero infinitesemal part bool column_is_fixed(lpvar j) const { return (*m_column_types)[j] == column_type::fixed && get_lower_bound(j).y.is_zero(); } void add_bound(mpq const& v, unsigned j, bool is_low, bool strict, std::function explain_bound) { lconstraint_kind kind = is_low ? GE : LE; if (strict) kind = static_cast(kind / 2); if (!m_imp.bound_is_interesting(j, kind, v)) return; if (is_low) { unsigned k; if (m_improved_lower_bounds.find(j, k)) { auto& found_bound = m_ibounds[k]; if (v > found_bound.m_bound || (v == found_bound.m_bound && !found_bound.m_strict && strict)) { found_bound.m_bound = v; found_bound.m_strict = strict; found_bound.set_explain(explain_bound); TRACE("add_bound", lp().print_implied_bound(found_bound, tout);); } } else { m_improved_lower_bounds.insert(j, static_cast(m_ibounds.size())); m_ibounds.push_back(implied_bound(v, j, is_low, strict, explain_bound)); TRACE("add_bound", lp().print_implied_bound(m_ibounds.back(), tout);); } } else { // the upper bound case unsigned k; if (m_improved_upper_bounds.find(j, k)) { auto& found_bound = m_ibounds[k]; if (v < found_bound.m_bound || (v == found_bound.m_bound && !found_bound.m_strict && strict)) { found_bound.m_bound = v; found_bound.m_strict = strict; found_bound.set_explain(explain_bound); TRACE("add_bound", lp().print_implied_bound(found_bound, tout);); } } else { m_improved_upper_bounds.insert(j, static_cast(m_ibounds.size())); m_ibounds.push_back(implied_bound(v, j, is_low, strict, explain_bound)); TRACE("add_bound", lp().print_implied_bound(m_ibounds.back(), tout);); } } } void consume(const mpq& a, constraint_index ci) { m_imp.consume(a, ci); } const mpq& val(unsigned j) const { return lp().get_column_value(j).x; // figure out why it is safe to return .x } const impq& ival(unsigned j) const { return lp().get_column_value(j); // figure out why it is safe to return .x } unsigned column(unsigned row, unsigned index) { return lp().get_row(row)[index].var(); } bool is_equal(lpvar j, lpvar k) const { return m_imp.is_equal(col_to_imp(j), col_to_imp(k)); } void clear_for_eq() { m_visited_rows.reset(); } std::ostream& print_expl(std::ostream& out, const explanation& exp) const { for (auto p : exp) lp().constraints().display( out, [this](lpvar j) { return lp().get_variable_name(j); }, p.ci()); return out; } bool add_eq_on_columns(const explanation& exp, lpvar je, lpvar ke, bool is_fixed) { lp_assert(je != ke && is_int(je) == is_int(ke)); lp_assert(ival(je) == ival(ke)); TRACE("eq", tout << "reported idx " << je << ", " << ke << "\n"; print_expl(tout, exp); tout << "theory_vars v" << lp().local_to_external(je) << " == v" << lp().local_to_external(ke) << "\n";); bool added = m_imp.add_eq(je, ke, exp, is_fixed); if (added) { if (is_fixed) lp().stats().m_fixed_eqs++; else lp().stats().m_offset_eqs++; } return added; } // column to theory_var unsigned col_to_imp(unsigned j) const { return lp().local_to_external(j); } // theory_var to column unsigned imp_to_col(unsigned j) const { return lp().external_to_local(j); } bool is_int(lpvar j) const { return lp().column_is_int(j); } void explain_fixed_in_row(unsigned row, explanation& ex) { TRACE("eq", tout << lp().get_row(row) << std::endl); for (const auto& c : lp().get_row(row)) if (lp().column_is_fixed(c.var())) explain_fixed_column(c.var(), ex); } unsigned explain_fixed_in_row_and_get_base(unsigned row, explanation& ex) { unsigned base = UINT_MAX; TRACE("eq", tout << lp().get_row(row) << std::endl); for (const auto& c : lp().get_row(row)) { if (lp().column_is_fixed(c.var())) { explain_fixed_column(c.var(), ex); } else if (lp().is_base(c.var())) { base = c.var(); } } return base; } void explain_fixed_column(unsigned j, explanation& ex) { SASSERT(column_is_fixed(j)); auto* deps = lp().get_bound_constraint_witnesses_for_column(j); for (auto ci : lp().flatten(deps)) ex.push_back(ci); } #ifdef Z3DEBUG bool all_fixed_in_row(unsigned row) const { for (const auto& c : lp().get_row(row)) if (!lp().column_is_fixed(c.var())) return false; return true; } // bounded by 2 unsigned num_of_non_fixed_in_row(unsigned row_index) const { unsigned n_of_nfixed = 0; for (const auto& c : lp().get_row(row_index)) { if (column_is_fixed(c.var())) continue; n_of_nfixed++; if (n_of_nfixed > 1) return n_of_nfixed; } return n_of_nfixed; } #endif // Let nf is the number of non-fixed columns in the row. // Then the function returns min(nf, 3). // if nf == 0, the row is of the form sum of fixed = 0 // if nf == 1, the row is of the form x + sum of fixed = 0, where x is not fixed base // if nf == 2, the row is of the form x + ay + sum of fixed = 0, x is a non fixed base and y is not fixed // y_sign is set to a, if abs(a)= 1, and 0 otherwise unsigned extract_non_fixed(unsigned row_index, unsigned& x, unsigned& y, int& y_sign) const { unsigned nf = 0; // number of non-fixed columns y = UINT_MAX; const auto& row = lp().get_row(row_index); x = lp().get_base_column_in_row(row_index); if (!column_is_fixed(x)) { nf++; } else { // we have a fixed base column, exiting return 0; } for (const auto& c : row) { unsigned j = c.var(); if (j == x) continue; if (column_is_fixed(j)) continue; if (++nf > 2) return nf; lp_assert(is_not_set(y)); y = j; if (c.coeff().is_one()) { y_sign = 1; } else if (c.coeff().is_minus_one()) { y_sign = -1; } else { // y has a coefficient other than 1 or -1 y_sign = 0; return nf; // maybe be too low but we don't care } } return nf; } void try_add_equation_with_lp_fixed_tables(unsigned row_index, unsigned v_j) { lp_assert(lp().get_base_column_in_row(row_index) == v_j); lp_assert(num_of_non_fixed_in_row(row_index) == 1 || column_is_fixed(v_j)); if (column_is_fixed(v_j)) { return; } unsigned j = null_lpvar; if (!lp().find_in_fixed_tables(val(v_j), is_int(v_j), j)) { try_add_equation_with_internal_fixed_tables(row_index); return; } TRACE("eq", tout << "v_j = "; lp().print_column_info(v_j, tout) << std::endl; tout << "found j " << j << std::endl; lp().print_column_info(j, tout) << std::endl; print_row(tout, row_index) << std::endl; ); explanation ex; explain_fixed_in_row(row_index, ex); explain_fixed_column(j, ex); add_eq_on_columns(ex, j, v_j, true); } void cheap_eq_on_nbase(unsigned row_index) { reset_cheap_eq _reset(*this); TRACE("eq", tout << "row_index = " << row_index << "\n"; print_row(tout, row_index) << "\n";); if (!check_insert(m_visited_rows, row_index)) return; unsigned x, y; int y_sign; unsigned nf = extract_non_fixed(row_index, x, y, y_sign); if (nf == 0 || nf > 2) return; if (nf == 1) { lp_assert(is_not_set(y)); try_add_equation_with_lp_fixed_tables(row_index, x); return; } if (y_sign == 0) { // the coefficient before y is not 1 or -1 return; } lp_assert(y_sign == -1 || y_sign == 1); lp_assert(lp().is_base(y) == false); auto& table = y_sign == 1 ? m_row2index_pos : m_row2index_neg; table.insert(val(x), row_index); TRACE("eq", tout << "y = " << y << "\n";); for (const column_cell& c : lp().get_column(y)) { unsigned i = c.var(); // the running index of the row if (i == row_index) continue; if (!check_insert(m_visited_rows, i)) continue; unsigned y_nb; nf = extract_non_fixed(i, x, y_nb, y_sign); if (nf != 2 || y_sign == 0) continue; lp_assert(y_nb == y); lp_assert(y_sign == 1 || y_sign == -1); auto& table = y_sign == 1 ? m_row2index_pos : m_row2index_neg; const auto& v = val(x); unsigned found_i;; if (!table.find(v, found_i)) { table.insert(v, i); } else { explanation ex; unsigned base_of_found = lp().get_base_column_in_row(found_i); if (is_int(x) != is_int(base_of_found) || ival(x).y != ival(base_of_found).y) continue; explain_fixed_in_row(found_i, ex); explain_fixed_in_row(i, ex); TRACE("eq", { print_row(tout, i); print_row(tout, found_i) << "\n"; lp().print_column_info(base_of_found, tout); lp().print_column_info(x, tout) << "\n"; }); add_eq_on_columns(ex, x, base_of_found, false); } } } std::ostream& print_row(std::ostream& out, unsigned row_index) const { bool first = true; for (const auto& c : lp().A_r().m_rows[row_index]) { if (lp().column_is_fixed(c.var())) continue; if (c.coeff().is_one()) { if (!first) out << "+"; } else if (c.coeff().is_minus_one()) out << "-"; out << lp().get_variable_name(c.var()) << " "; first = false; } out << "\n"; return out; } template bool check_insert(C& table, unsigned j) { if (table.contains(j)) return false; table.insert(j); return true; } }; } // namespace lp