#include "math/lp/dioph_eq.h" #include #include #include #include #include #include "math/lp/int_solver.h" #include "math/lp/lar_solver.h" #include "math/lp/lp_utils.h" #include "math/lp/var_register.h" /* Following paper: "A Practical Approach to Satisfiability Modulo Linear Integer Arithmetic" by Alberto Griggio(griggio@fbk.eu). Data structures are: -- term_o: inherits lar_term and differs from it by having a constant, while lar_term is just a sum of monomials -- lra: pointer to lar_solver. -- lia: point to int_solver. -- m_sum_of_fixed: it keeps the contribution of the fixed variables to the row -- m_e_matrix: i-th row of this matrix keeps the term corresponding to i-ith entry -- m_l_matrix: the m_l_matrix[i] produces m_e_matrix[i] by using the terms definitions of lar_solver -- m_k2s: when the variable k is substituted in the row s of m_e_matrix, the pair (k,s) is added to m_k2s. m_k2s is a one to one mapping. -- m_fresh_k2xt_terms: when a fresh definitions is created for a variable k in row s then the triple (k,xt,(t,s)) is added to m_fresh_k2xt_terms, where xt is the fresh variable, and t it the term defining the substitution: something like k - xt + 5z + 6y = 0. The set of pairs (k, xt) is a one to one mapping m_row2fresh_defs[i]: is the list of all xt that were defined for row m_e_matrix[i]. Invariant: Every xt in m_row2fresh[i] must have a corresponding entry in m_fresh_k2xt_terms The mapping between the columns of lar_solver and m_e_matrix is controlled by m_var_register. local_to_lar_solver(lar_solver_to_local(j)) == j. If local_to_lar_solver(j) == -1 then j is a fresh variable, that is such that got introduced when normalizing a term like 3x-6y + 5z +11 = 0, when no variable has a coefficient +-1. -- get_term_from_entry(unsigned i) returns a term corresponding i-th entry. If t = get_term_from_entry(i) then we have equality t = 0. Initially get_term_from_entry(i) is set to initt(j) = lra.get_term(j) - j, for some column j,where all fixed variables are replaced by their values. To track the explanations of equality t = 0 we initially set m_l_matrix[i] = {(1,j)}. The explanation is obtained by replacing term of the m_l_matrix[i] = sum(aj*j) by the linear combination sum (aj*initt(j)) and joining the explanations of all fixed variables in the latter sum. entry_invariant(i) guarantees the validity of the i-th entry. */ namespace lp { template bool contains(const T& t, K j) { return t.find(j) != t.end(); } struct iv { mpq m_coeff; unsigned m_j; lpvar var() const { return m_j; } const mpq& coeff() const { return m_coeff; } mpq& coeff() { return m_coeff; } iv() {} iv(const mpq& v, unsigned j) : m_coeff(v), m_j(j) {} }; class bijection { std::unordered_map m_map; std::unordered_map m_rev_map; auto map_begin() const { return m_map.begin(); } auto map_end() const { return m_map.end(); } public: // Iterator helper struct map_it { const bijection& m_bij; auto begin() const { return m_bij.map_begin(); } auto end() const { return m_bij.map_end(); } }; // Add a method to get iterators over the key->val map (complementing key_val) map_it key_val_pairs() const { return {*this}; } void add(unsigned a, unsigned b) { SASSERT(!contains(m_map, a) && !contains(m_rev_map, b)); m_map[a] = b; m_rev_map[b] = a; } unsigned get_key(unsigned b) const { SASSERT(has_val(b)); return m_rev_map.find(b)->second; } unsigned size() const { return static_cast(m_map.size()); } void erase_val(unsigned b) { VERIFY(contains(m_rev_map, b) && contains(m_map, m_rev_map[b])); auto it = m_rev_map.find(b); if (it == m_rev_map.end()) return; unsigned key = it->second; m_rev_map.erase(it); VERIFY(has_key(key)); m_map.erase(key); } bool has_val(unsigned b) const { return m_rev_map.find(b) != m_rev_map.end(); } bool has_key(unsigned a) const { return m_map.find(a) != m_map.end(); } void transpose_val(unsigned b0, unsigned b1) { bool has_b0 = has_val(b0); bool has_b1 = has_val(b1); // Both b0 and b1 exist if (has_b0 && has_b1) { unsigned a0 = m_rev_map.at(b0); unsigned a1 = m_rev_map.at(b1); erase_val(b0); erase_val(b1); add(a1, b0); add(a0, b1); } // Only b0 exists else if (has_b0) { unsigned a0 = m_rev_map.at(b0); erase_val(b0); add(a0, b1); } // Only b1 exists else if (has_b1) { unsigned a1 = m_rev_map.at(b1); erase_val(b1); add(a1, b0); } // Neither b0 nor b1 exists; do nothing } unsigned operator[](unsigned i) const { auto it = m_map.find(i); VERIFY(it != m_map.end()); return it->second; } }; template struct bij_map { // We store T indexed by 'b' in an std::unordered_map, and use bijection to map from 'a' to 'b'. bijection m_bij; std::unordered_map m_data; // Adds a->b in m_bij, associates val with b. void add(unsigned a, unsigned b, const T& val) { // You might have some method in bijection such as 'insert(a, b)' // or possibly you'd do: // m_bij.add_key_val(a, b); // For example: m_bij.add(a, b); m_data[b] = val; } unsigned get_second_of_key(unsigned a) const { return m_bij[a]; } void erase_by_second_key(unsigned b) { SASSERT(m_bij.has_val(b)); m_bij.erase_val(b); auto it = m_data.find(b); VERIFY(it != m_data.end()); m_data.erase(it); } bool has_key(unsigned j) const { return m_bij.has_key(j); } bool has_second_key(unsigned j) const { return m_bij.has_val(j); } // Get the data by 'a', look up b in m_bij, then read from m_data const T& get_by_key(unsigned a) const { unsigned b = m_bij[a]; // relies on operator[](unsigned) from bijection return get_by_val(b); } // Get the data by 'b' directly const T& get_by_val(unsigned b) const { auto it = m_data.find(b); VERIFY(it != m_data.end()); return it->second; } }; class dioph_eq::imp { // This class represents a term with an added constant number c, in form sum // {x_i*a_i} + c. class term_o : public lar_term { mpq m_c; public: term_o clone() const { term_o ret; for (const auto& p : *this) { ret.add_monomial(p.coeff(), p.j()); } ret.c() = c(); ret.set_j(j()); return ret; } term_o(const lar_term& t) : lar_term(t), m_c(0) { SASSERT(m_c.is_zero()); } const mpq& c() const { return m_c; } mpq& c() { return m_c; } term_o() : m_c(0) {} friend term_o operator*(const mpq& k, const term_o& term) { term_o r; for (const auto& p : term) r.add_monomial(p.coeff() * k, p.j()); r.c() = k * term.c(); return r; } friend term_o operator+(const term_o& a, const term_o& b) { term_o r = a.clone(); for (const auto& p : b) { r.add_monomial(p.coeff(), p.j()); } r.c() += b.c(); return r; } friend term_o operator-(const term_o& a, const term_o& b) { term_o r = a.clone(); for (const auto& p : b) r.sub_monomial(p.coeff(), p.j()); r.c() -= b.c(); return r; } #if Z3DEBUG friend bool operator==(const term_o& a, const term_o& b) { term_o t = a.clone(); t += mpq(-1) * b; return t.c() == mpq(0) && t.size() == 0; } friend bool operator!=(const term_o& a, const term_o& b) { return ! (a == b); } #endif term_o& operator+=(const term_o& t) { for (const auto& p : t) { add_monomial(p.coeff(), p.j()); } m_c += t.c(); return *this; } }; std::ostream& print_S(std::ostream& out) { out << "S:\n"; for (unsigned ei = 0 ; ei < m_e_matrix.row_count(); ei++) { print_entry(ei, out, false, false, true); } return out; } std::ostream& print_bounds(std::ostream& out) { out << "bounds:\n"; for (unsigned v = 0; v < lra.column_count(); ++v) { lra.print_column_info(v, out); } return out; } std::ostream& print_lar_term_L(const lar_term& t, std::ostream& out) const { return print_linear_combination_customized( t.coeffs_as_vector(), [](int j) -> std::string { return "x" + std::to_string(j); }, out); } // used for debug only std::ostream& print_lar_term_L(const std_vector& t, std::ostream& out) const { vector> tp; for (const auto& p : t) { tp.push_back(std::make_pair(p.coeff(), p.var())); } return print_linear_combination_customized( tp, [](int j) -> std::string { return "x" + std::to_string(j); }, out); } std::ostream& print_term_o(term_o const& term, std::ostream& out) const { if (term.size() == 0 && term.c().is_zero()) { out << "0"; return out; } bool first = true; // Copy term to a std_vector and sort by p.j() std_vector> sorted_term; sorted_term.reserve(term.size()); for (const auto& p : term) { sorted_term.emplace_back(p.coeff(), p.j()); } std::sort( sorted_term.begin(), sorted_term.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); // Print the sorted term for (auto& [val, j] : sorted_term) { if (first) { first = false; } else if (is_pos(val)) { out << " + "; } else { out << " - "; val = -val; } if (val == -numeric_traits::one()) out << " - "; else if (val != numeric_traits::one()) out << T_to_string(val); out << "x"; out << j; } // Handle the constant term separately if (!term.c().is_zero()) { if (!first) { if (term.c().is_pos()) out << " + "; else out << " - "; } out << abs(term.c()); } return out; } // the maximal size of the term to try to tighten the bounds: // if the size of the term is large than the chances are that the GCD of the coefficients is one unsigned m_tighten_size_max = 10; bool m_some_terms_are_ignored = false; std_vector m_sum_of_fixed; // we have to use m_var_register because of the fresh variables: otherwise they clash with the existing lar_solver column indices var_register m_var_register; // the terms are stored in m_A and m_c static_matrix m_e_matrix; // the rows of the matrix are the terms, static_matrix m_l_matrix; // the rows of the matrix are the l_terms providing the certificate to the entries modulo the constant part: look an entry_invariant that assures that the each two rows are in sync. int_solver& lia; lar_solver& lra; explanation m_infeas_explanation; bool m_report_branch = false; // set F // iterate over all rows from 0 to m_e_matrix.row_count() - 1 and return those i such !m_k2s.has_val(i) // set S - iterate over bijection m_k2s mpq m_c; // the constant of the equation struct term_with_index { // The invariant is // 1) m_index[m_data[k].var()] = k, for each 0 <= k < m_data.size(), and // 2) m_index[j] = -1, or m_data[m_index[j]].var() = j, for every 0 <= j < m_index.size(). // For example m_data = [(coeff, 5), (coeff, 3)] // then m_index = [-1,-1, -1, 1, -1, 0, -1, ....]. std_vector m_data; std_vector m_index; // used for debug only lar_term to_term() const { lar_term r; for (const auto& p : m_data) { r.add_monomial(p.coeff(), p.var()); } return r; } auto size() const { return m_data.size(); } bool has(unsigned k) const { return k < m_index.size() && m_index[k] >= 0; } const mpq& operator[](unsigned j) const { SASSERT(j >= 0 && j < m_index.size()); SASSERT(m_index[j] >= 0 && m_index[j] < (int)m_data.size()); return m_data[m_index[j]].coeff(); } void erase(unsigned k) { if (k >= m_index.size() || m_index[k] == -1) return; unsigned idx = m_index[k]; // If not last element, move last element to idx position if (idx != m_data.size() - 1) { m_data[idx] = m_data.back(); m_index[m_data[idx].var()] = idx; } m_data.pop_back(); m_index[k] = -1; SASSERT(invariant()); } void add(const mpq& a, unsigned j) { SASSERT(!a.is_zero()); // Expand m_index if needed if (j >= m_index.size()) { m_index.resize(j + 1, -1); } int idx = m_index[j]; if (idx == -1) { // Insert a new monomial { a, j } into m_data m_data.push_back({a, j}); m_index[j] = static_cast(m_data.size() - 1); } else { // Accumulate the coefficient m_data[idx].coeff() += a; // If the coefficient becomes zero, remove the entry if (m_data[idx].coeff().is_zero()) { int last = static_cast(m_data.size() - 1); // Swap with the last element for efficient removal if (idx != last) { auto tmp = m_data[last]; m_data[idx] = tmp; m_index[tmp.var()] = idx; } m_data.pop_back(); m_index[j] = -1; } } SASSERT(invariant()); } bool invariant() const { // 1. For each j in [0..m_index.size()), if m_index[j] = -1, ensure no m_data[k].var() == j // otherwise verify m_data[m_index[j]].var() == j for (unsigned j = 0; j < m_index.size(); j++) { int idx = m_index[j]; if (idx == -1) { // Check that j is not in m_data for (unsigned k = 0; k < m_data.size(); ++k) { if (m_data[k].var() == j) { return false; } } } else { // Check that var() in m_data[idx] matches j if (idx < 0 || static_cast(idx) >= m_data.size()) { return false; } if (m_data[idx].var() != j || m_data[idx].coeff().is_zero()) { return false; } } } // 2. For each item in m_data, check that m_index[m_data[k].var()] == k // and that the coeff() is non-zero for (unsigned k = 0; k < m_data.size(); ++k) { unsigned var = m_data[k].var(); if (var >= m_index.size()) { return false; } if (m_index[var] != static_cast(k)) { return false; } if (m_data[k].coeff().is_zero()) { return false; } } return true; } void clear() { for (const auto& p : m_data) { m_index[p.var()] = -1; } m_data.clear(); SASSERT(invariant()); } auto begin() const { return m_data.begin();} auto end() const { return m_data.end(); } }; // m_lspace is for operations on l_terms - m_e_matrix rows term_with_index m_lspace; // m_espace is for operations on m_e_matrix rows term_with_index m_espace; bijection m_k2s; bij_map> m_fresh_k2xt_terms; // m_row2fresh_defs[i] is the set of all fresh variables xt // such that pairs (xt, m_fresh_k2xt_terms[xt]) is a fresh definition introduced for row i // When row i is changed all entries depending on m_fresh_k2xt_terms[xt] should be recalculated, // and the corresponding fresh definitions removed. std::unordered_map> m_row2fresh_defs; indexed_uint_set m_changed_rows; // m_changed_columns are the columns that just became fixed, or those that just stopped being fixed. // If such a column appears in an entry it has to be recalculated. indexed_uint_set m_changed_columns; indexed_uint_set m_changed_terms; // represented by term columns indexed_uint_set m_terms_to_tighten; // represented by term columns // m_column_to_terms[j] is the set of all k such lra.get_term(k) depends on j std::unordered_map> m_columns_to_terms; unsigned m_normalize_conflict_index = UINT_MAX; // the row index of the conflict mpq m_normalize_conflict_gcd; // the gcd of the coefficients the m_e_matrix[m_normalize_conflict_gcd]. void reset_conflict() { m_normalize_conflict_index = UINT_MAX; } bool has_conflict_index() const { return m_normalize_conflict_index != UINT_MAX; } void set_rewrite_conflict(unsigned idx, const mpq& gcd) { SASSERT(idx != UINT_MAX); m_normalize_conflict_index = idx; m_normalize_conflict_gcd = gcd; lra.stats().m_dio_rewrite_conflicts++; } unsigned m_max_of_branching_iterations = 0; unsigned m_number_of_branching_calls; struct branch { unsigned m_j = UINT_MAX; mpq m_rs; // if m_left is true, then the branch is interpreted // as x[j] <= m_rs // otherwise x[j] >= m_rs bool m_left; bool m_fully_explored = false; void flip() { SASSERT(m_fully_explored == false); m_left = !m_left; m_fully_explored = true; } }; struct variable_branch_stats { std::vector m_ii_after_left; // g_right[i] - the rumber of int infeasible after taking the i-ith // right branch std::vector m_ii_after_right; double score() const { double avm_lefts = m_ii_after_left.size() ? static_cast(std::accumulate( m_ii_after_left.begin(), m_ii_after_left.end(), 0)) / m_ii_after_left.size() : std::numeric_limits::infinity(); double avm_rights = m_ii_after_right.size() ? static_cast(std::accumulate( m_ii_after_right.begin(), m_ii_after_right.end(), 0)) / m_ii_after_right.size() : std::numeric_limits::infinity(); return std::min(avm_lefts, avm_rights); } }; void undo_add_term_method(const lar_term* t) { TRACE("d_undo", tout << "t:" << t << ", t->j():" << t->j() << std::endl;); TRACE("dioph_eq", lra.print_term(*t, tout); tout << ", t->j() =" << t->j() << std::endl;); if (!contains(m_active_terms, t)) { for (auto i = m_added_terms.size(); i-- > 0; ) { if (m_added_terms[i] != t) continue; m_added_terms[i] = m_added_terms.back(); m_added_terms.pop_back(); break; // all is done since the term has not made it to m_active_terms } return; } // deregister the term that has been activated for (const auto& p : t->ext_coeffs()) { TRACE("dio_reg", tout << "derigister p.var():" << p.var() << "->" << t->j() << std::endl;); auto it = m_columns_to_terms.find(p.var()); SASSERT(it != m_columns_to_terms.end()); it->second.erase(t->j()); if (it->second.size() == 0) { m_columns_to_terms.erase(it); } } SASSERT(std::find(m_added_terms.begin(), m_added_terms.end(), t) == m_added_terms.end()); SASSERT(contains(m_active_terms, t)); m_active_terms.erase(t); TRACE("dioph_eq", tout << "the deleted term column in m_l_matrix" << std::endl; for (auto p : m_l_matrix.column(t->j())) { tout << "p.coeff():" << p.coeff() << ", row " << p.var() << std::endl; } tout << "m_l_matrix has " << m_l_matrix.column_count() << " columns" << std::endl; tout << "and " << m_l_matrix.row_count() << " rows" << std::endl; print_lar_term_L(*t, tout); tout << "; t->j()=" << t->j() << std::endl;); shrink_matrices(); } struct undo_add_term : public trail { imp& m_s; const lar_term* m_t; undo_add_term(imp& s, const lar_term* t) : m_s(s), m_t(t) {} void undo() { m_s.undo_add_term_method(m_t); } }; struct protected_queue { std::list m_q; std::unordered_map::iterator> m_positions; bool empty() const { return m_q.empty(); } unsigned size() const { return static_cast(m_q.size()); } void push(unsigned j) { if (m_positions.find(j) != m_positions.end()) return; m_q.push_back(j); m_positions[j] = std::prev(m_q.end()); } unsigned pop_front() { unsigned j = m_q.front(); m_q.pop_front(); m_positions.erase(j); return j; } void remove(unsigned j) { auto it = m_positions.find(j); if (it != m_positions.end()) { m_q.erase(it->second); m_positions.erase(it); } SASSERT(invariant()); } bool contains(unsigned j) const { return m_positions.find(j) != m_positions.end(); } void reset() { m_q.clear(); m_positions.clear(); } // Invariant method to ensure m_q and m_positions are aligned bool invariant() const { if (m_q.size() != m_positions.size()) return false; for (auto it = m_q.begin(); it != m_q.end(); ++it) { auto pos_it = m_positions.find(*it); if (pos_it == m_positions.end()) return false; if (pos_it->second != it) return false; } return true; } }; protected_queue m_q; struct undo_fixed_column : public trail { imp& m_imp; unsigned m_j; // the column that has been added const mpq m_fixed_val; undo_fixed_column(imp& s, unsigned j) : m_imp(s), m_j(j), m_fixed_val(s.lra.get_lower_bound(j).x) { SASSERT(s.lra.column_is_fixed(j)); } void undo() override { m_imp.add_changed_column(m_j); } }; void remove_last_entry() { unsigned ei = static_cast(m_e_matrix.row_count() - 1); if (m_k2s.has_val(ei)) { remove_from_S(ei); } m_sum_of_fixed.pop_back(); } void eliminate_last_term_column() { // change only the rows in m_l_matrix, and update m_e_matrix lazily unsigned j = m_l_matrix.column_count() - 1; make_sure_j_is_in_the_last_row_of_l_matrix(); const auto& last_e_row = m_l_matrix.m_rows.back(); mpq alpha; for (const auto& p : last_e_row) { if (p.var() == j) { alpha = p.coeff(); break; } } unsigned last_row_index = m_l_matrix.row_count() - 1; m_l_matrix.divide_row(last_row_index, alpha); // divide the last row by alpha auto& column = m_l_matrix.m_columns[j]; int pivot_col_cell_index = -1; for (unsigned k = 0; k < column.size(); k++) { if (column[k].var() == last_row_index) { pivot_col_cell_index = k; break; } } SASSERT(pivot_col_cell_index >= 0); if (pivot_col_cell_index != 0) { SASSERT(column.size() > 1); // swap the pivot column cell with the head cell auto c = column[0]; column[0] = column[pivot_col_cell_index]; column[pivot_col_cell_index] = c; m_l_matrix.m_rows[last_row_index][column[0].offset()].offset() = 0; m_l_matrix.m_rows[c.var()][c.offset()].offset() = pivot_col_cell_index; } while (column.size() > 1) { auto& c = column.back(); SASSERT(c.var() != last_row_index); m_l_matrix.pivot_row_to_row_given_cell(last_row_index, c, j); m_changed_rows.insert(c.var()); } } void make_sure_j_is_in_the_last_row_of_l_matrix() { unsigned j = m_l_matrix.column_count() - 1; const auto& last_e_row = m_l_matrix.m_rows.back(); if (any_of(last_e_row, [j](auto const& p) { return p.var() == j; })) return; SASSERT(m_l_matrix.m_columns.back().size() > 0); unsigned i = m_l_matrix.m_columns[j][0].var(); m_l_matrix.add_rows(mpq(1), i, m_l_matrix.row_count() - 1); // what is the post-condition? Is 'j' used in the post-condition or is it 'i'? // SASSERT(any_of(m_l_matrix.m_rows.back(), [i](auto const& p) { return p.var() == i; })); } void shrink_matrices() { unsigned i = m_l_matrix.row_count() - 1; eliminate_last_term_column(); remove_last_row_in_matrix(m_l_matrix); remove_last_row_in_matrix(m_e_matrix); while (m_l_matrix.column_count() && m_l_matrix.m_columns.back().size() == 0) { m_l_matrix.m_columns.pop_back(); } while (m_e_matrix.column_count() && m_e_matrix.m_columns.back().size() == 0) { m_e_matrix.m_columns.pop_back(); } m_var_register.shrink(m_e_matrix.column_count()); remove_irrelevant_fresh_defs_for_row(i); if (m_k2s.has_val(i)) remove_from_S(i); m_sum_of_fixed.pop_back(); } void remove_last_row_in_matrix(static_matrix& m) { auto& last_row = m.m_rows.back(); for (unsigned k = static_cast(last_row.size()); k-- > 0;) { m.remove_element(last_row, last_row[k]); } m.m_rows.pop_back(); } void remove_entry_index(std::list& l, unsigned ei) { auto it = std::find(l.begin(), l.end(), ei); if (it != l.end()) l.erase(it); } void add_changed_column(unsigned j) { TRACE("dio", lra.print_column_info(j, tout);); m_changed_columns.insert(j); } std_vector m_added_terms; std::unordered_set m_active_terms; std_vector m_branch_stats; std_vector m_branch_stack; std_vector m_explanation_of_branches; bool term_has_big_number(const lar_term& t) const { for (const auto& p : t) { if (p.coeff().is_big()) return true; if (is_fixed(p.var()) && lra.get_lower_bound(p.var()).x.is_big()) return true; } return false; } bool ignore_big_nums() const { return lra.settings().dio_ignore_big_nums(); } void add_term_callback(const lar_term* t) { unsigned j = t->j(); TRACE("dio", tout << "term column t->j():" << j << std::endl; lra.print_term(*t, tout) << std::endl;); if (!lra.column_is_int(j)) { TRACE("dio", tout << "ignored a non-integral column" << std::endl;); m_some_terms_are_ignored = true; return; } CTRACE("dio", !lra.column_has_term(j), tout << "added term that is not associated with a column yet" << std::endl;); if (ignore_big_nums() && term_has_big_number(*t)) { TRACE("dio", tout << "term_has_big_number\n";); m_some_terms_are_ignored = true; return; } m_added_terms.push_back(t); mark_term_change(t->j()); auto undo = undo_add_term(*this, t); lra.trail().push(undo); } void mark_term_change(unsigned j) { TRACE("dio", tout << "marked term change j:" << j << std::endl;); m_changed_terms.insert(j); } void update_column_bound_callback(unsigned j) { if (!lra.column_is_int(j)) return; if (lra.column_has_term(j) && ignore_big_nums() && !term_has_big_number(lra.get_term(j))) m_terms_to_tighten.insert(j); // the boundary of the term has changed: we can be successful to tighten this term if (!lra.column_is_fixed(j)) return; if (ignore_big_nums() && lra.get_lower_bound(j).x.is_big()) return; TRACE("dio", tout << "j:" << j << "\n"; lra.print_column_info(j, tout);); m_changed_columns.insert(j); lra.trail().push(undo_fixed_column(*this, j)); } public: imp(int_solver& lia, lar_solver& lra) : lia(lia), lra(lra) { lra.m_add_term_callback = [this](const lar_term* t) { add_term_callback(t); }; lra.m_update_column_bound_callback = [this](unsigned j) { update_column_bound_callback(j); }; } term_o get_term_from_entry(unsigned i) const { term_o t; for (const auto& p : m_e_matrix.m_rows[i]) { t.add_monomial(p.coeff(), p.var()); } t.c() = m_sum_of_fixed[i]; return t; } // adds variable j of lar_solver to the local diophantine handler unsigned add_var(unsigned j) { return this->m_var_register.add_var(j, true); } unsigned local_to_lar_solver(unsigned j) const { return m_var_register.local_to_external(j); } void register_columns_to_term(const lar_term& t) { TRACE("dio_reg", tout << "register term:"; lra.print_term(t, tout); tout << ", t.j()=" << t.j() << std::endl;); for (const auto& p : t.ext_coeffs()) { auto it = m_columns_to_terms.find(p.var()); TRACE("dio_reg", tout << "register p.var():" << p.var() << "->" << t.j() << std::endl;); if (it != m_columns_to_terms.end()) { it->second.insert(t.j()); } else { std::unordered_set s; s.insert(t.j()); m_columns_to_terms[p.var()] = s; } } } // the term has form sum(a_i*x_i) - t.j() = 0, void fill_entry(const lar_term& t) { unsigned entry_index = (unsigned)m_e_matrix.row_count(); m_sum_of_fixed.push_back(mpq(0)); mpq& fixed_sum = m_sum_of_fixed.back(); // fill m_l_matrix row m_l_matrix.add_row(); // todo: consider to compress variables t.j() by using a devoted var_register for term columns m_l_matrix.add_columns_up_to(t.j()); m_l_matrix.add_new_element(entry_index, t.j(), mpq(1)); // fill E-entry m_e_matrix.add_row(); SASSERT(m_sum_of_fixed.size() == m_l_matrix.row_count() && m_e_matrix.row_count() == m_e_matrix.row_count()); for (const auto& p : t.ext_coeffs()) { SASSERT(p.coeff().is_int()); if (is_fixed(p.var())) fixed_sum += p.coeff() * lia.lower_bound(p.var()).x; else { unsigned lj = add_var(p.var()); m_e_matrix.add_columns_up_to(lj); m_e_matrix.add_new_element(entry_index, lj, p.coeff()); } } subs_entry(entry_index); SASSERT(entry_invariant(entry_index)); TRACE("dio_entry", print_entry(entry_index, tout) << std::endl;); } void subs_entry(unsigned ei) { if (ei >= m_e_matrix.row_count()) return; // m_q is the queue of variables that can be substituted in ei m_q.reset(); for (const auto& p : m_e_matrix.m_rows[ei]) { if (can_substitute(p.var())) m_q.push(p.var()); } if (m_q.size() == 0) { TRACE("dio", tout << "nothing to subst on ei:" << ei << "\n";); return; } substitute_on_q(ei); SASSERT(entry_invariant(ei)); } void substitute_on_q_with_entry_in_S(unsigned ei, unsigned j, const mpq& alpha) { unsigned ei_to_sub = m_k2s[j]; int sign_j = get_sign_in_e_row(ei_to_sub, j); // we need to eliminate alpha*j in ei's row add_two_entries(-mpq(sign_j) * alpha, ei_to_sub, ei); for (const auto& p : m_e_matrix.m_rows[ei]) { unsigned jj = p.var(); if (can_substitute(jj)) m_q.push(jj); } } void substitute_with_fresh_def(unsigned ei, unsigned j, const mpq& alpha) { const lar_term& sub_term = m_fresh_k2xt_terms.get_by_key(j).first; TRACE("dioph_eq", print_lar_term_L(sub_term, tout) << std::endl;); SASSERT(sub_term.get_coeff(j).is_one()); // we need to eliminate alpha*j in ei's row add_term_to_entry(-alpha, sub_term, ei); for (const auto& p : m_e_matrix.m_rows[ei]) { unsigned jj = p.var(); if (can_substitute(jj)) m_q.push(jj); } } // q is the queue of variables that can be substituted in ei void substitute_on_q(unsigned ei) { while (!m_q.empty()) { unsigned j = m_q.pop_front(); mpq alpha = get_coeff_in_e_row(ei, j); if (alpha.is_zero()) continue; if (m_k2s.has_key(j)) { substitute_on_q_with_entry_in_S(ei, j, alpha); } else { substitute_with_fresh_def(ei, j, alpha); } } } bool term_is_in_range(const lar_term& t) const { for (const auto& p : t) { if (p.var() >= m_e_matrix.column_count()) return false; } return true; } // adds the term multiplied by coeff to m_e_matrix row i void add_term_to_entry(const mpq& coeff, const lar_term& t, unsigned i) { SASSERT(term_is_in_range(t)); m_e_matrix.add_term_to_row(coeff, t, i); } // adds entry i0 multiplied by coeff to entry i1 void add_two_entries(const mpq& coeff, unsigned i0, unsigned i1) { m_e_matrix.add_rows(coeff, i0, i1); m_l_matrix.add_rows(coeff, i0, i1); m_sum_of_fixed[i1] += coeff * m_sum_of_fixed[i0]; } void clear_e_row(unsigned ei) { auto& row = m_e_matrix.m_rows[ei]; while (row.size() > 0) { auto& c = row.back(); m_e_matrix.remove_element(row, c); } } void recalculate_entry(unsigned ei) { TRACE("dio", print_entry(ei, tout) << std::endl;); mpq& fixed_sum = m_sum_of_fixed[ei]; fixed_sum = mpq(0); open_l_term_to_espace(ei, fixed_sum); clear_e_row(ei); mpq denom(1); for (const auto& p : m_espace.m_data) { unsigned lj = add_var(p.var()); m_e_matrix.add_columns_up_to(lj); m_e_matrix.add_new_element(ei, lj, p.coeff()); if (!denominator(p.coeff()).is_one()) { denom = lcm(denom, denominator(p.coeff())); } } if (!denom.is_one()) { fixed_sum *= denom; m_l_matrix.multiply_row(ei, denom); m_e_matrix.multiply_row(ei, denom); } if (belongs_to_s(ei)) { remove_from_S(ei); } SASSERT(entry_invariant(ei)); } void find_changed_terms_and_more_changed_rows() { for (unsigned j : m_changed_columns) { const auto it = m_columns_to_terms.find(j); if (it != m_columns_to_terms.end()) for (unsigned k : it->second) { mark_term_change(k); } if (!m_var_register.external_is_used(j)) continue; for (const auto& p : m_e_matrix.column(this->lar_solver_to_local(j))) { m_changed_rows.insert(p.var()); } } } void remove_irrelevant_fresh_defs_for_row(unsigned ei) { auto it = m_row2fresh_defs.find(ei); if (it == m_row2fresh_defs.end()) return; for (unsigned xt : it->second) { if (m_fresh_k2xt_terms.has_second_key(xt)) m_fresh_k2xt_terms.erase_by_second_key(xt); } m_row2fresh_defs.erase(it); } void remove_irrelevant_fresh_defs() { std_vector xt_to_remove; std_vector rows_to_remove_the_defs_from; for (const auto& p : m_fresh_k2xt_terms.m_bij.key_val_pairs()) { unsigned xt = p.second; if (xt >= m_e_matrix.column_count()) { xt_to_remove.push_back(xt); rows_to_remove_the_defs_from.push_back(m_fresh_k2xt_terms.get_by_val(xt).second); } } for (unsigned xt : xt_to_remove) { m_fresh_k2xt_terms.erase_by_second_key(xt); } for (unsigned ei : m_changed_rows) { remove_irrelevant_fresh_defs_for_row(ei); } for (unsigned ei : rows_to_remove_the_defs_from) { remove_irrelevant_fresh_defs_for_row(ei); } } void process_changed_columns(std_vector &f_vector) { find_changed_terms_and_more_changed_rows(); for (unsigned j: m_changed_terms) { if (j >= lra.column_count()) continue; SASSERT(!ignore_big_nums() || !term_has_big_number(lra.get_term(j))); m_terms_to_tighten.insert(j); if (j < m_l_matrix.column_count()) { for (const auto& cs : m_l_matrix.column(j)) { m_changed_rows.insert(cs.var()); } } } // find more entries to recalculate std_vector more_changed_rows; for (unsigned ei : m_changed_rows) { auto it = m_row2fresh_defs.find(ei); if (it == m_row2fresh_defs.end()) continue; for (unsigned xt : it->second) { SASSERT(var_is_fresh(xt)); for (const auto& p : m_e_matrix.m_columns[xt]) { more_changed_rows.push_back(p.var()); } } } for (unsigned ei : more_changed_rows) { m_changed_rows.insert(ei); } for (unsigned ei : m_changed_rows) { if (ei >= m_e_matrix.row_count()) continue; if (belongs_to_s(ei)) f_vector.push_back(ei); recalculate_entry(ei); if (m_e_matrix.m_columns.back().size() == 0) { m_e_matrix.m_columns.pop_back(); m_var_register.shrink(m_e_matrix.column_count()); } if (m_l_matrix.m_columns.back().size() == 0) { m_l_matrix.m_columns.pop_back(); } } remove_irrelevant_fresh_defs(); eliminate_substituted_in_changed_rows(); m_changed_columns.reset(); m_changed_rows.reset(); m_changed_terms.reset(); SASSERT(entries_are_ok()); } int get_sign_in_e_row(unsigned ei, unsigned j) const { const auto& row = m_e_matrix.m_rows[ei]; auto it = std::find_if(row.begin(), row.end(), [j](const auto& p) { return p.var() == j; }); SASSERT(it != row.end() && abs(it->coeff()) == mpq(1)); return it->coeff().is_pos() ? 1 : -1; } mpq get_coeff_in_e_row(unsigned ei, unsigned j) { const auto& row = m_e_matrix.m_rows[ei]; auto it = std::find_if(row.begin(), row.end(), [j](const auto& p) { return p.var() == j; }); if (it == row.end()) return mpq(0); return it->coeff(); } void eliminate_substituted_in_changed_rows() { for (unsigned ei : m_changed_rows) subs_entry(ei); } // returns true if a variable j is substituted bool can_substitute(unsigned j) const { return m_k2s.has_key(j) || m_fresh_k2xt_terms.has_key(j); } bool entries_are_ok() { if (lra.settings().get_cancel_flag()) return true; for (unsigned ei = 0; ei < m_e_matrix.row_count(); ei++) { if (entry_invariant(ei) == false) { TRACE("dio", tout << "bad entry:"; print_entry(ei, tout);); return false; } if (belongs_to_f(ei)) { // see that all vars are substituted const auto& row = m_e_matrix.m_rows[ei]; for (const auto& p : row) { if (m_k2s.has_key(p.var())) { TRACE("dio", tout << "entry:" << ei << " belongs to f but depends on column " << p.var() << std::endl; tout << "m_var_register.local_to_external(p.var()):" << m_var_register.local_to_external(p.var()) << std::endl; print_entry(ei, tout); tout << "column " << p.var() << " of m_e_matrix:"; for (const auto & c : m_e_matrix.m_columns[p.var()]) { tout << "row:" << c.var() << ", "; } tout << std::endl; tout << "column " << p.var() << " is subst by entry:"; print_entry(m_k2s[p.var()],tout) << std::endl;); return false; } } } } return true; } void init(std_vector & f_vector) { m_report_branch = false; m_infeas_explanation.clear(); lia.get_term().clear(); m_number_of_branching_calls = 0; m_branch_stack.clear(); m_lra_level = 0; reset_conflict(); process_changed_columns(f_vector); for (const lar_term* t : m_added_terms) { m_active_terms.insert(t); f_vector.push_back(m_e_matrix.row_count()); // going to add a row in fill_entry fill_entry(*t); register_columns_to_term(*t); } SASSERT(is_in_sync()); m_added_terms.clear(); SASSERT(entries_are_ok()); } template mpq gcd_of_coeffs(const K& k, bool check_for_one) { if (check_for_one) for (const auto& p : k) if (p.coeff().is_one() || p.coeff().is_minus_one()) return mpq(1); mpq g(0); for (const auto& p : k) { SASSERT(p.coeff().is_int()); if (g.is_zero()) g = abs(p.coeff()); else g = gcd(g, p.coeff()); if (g.is_one()) break; } return g; } std::ostream& print_deps(std::ostream& out, u_dependency* dep) const { explanation ex(lra.flatten(dep)); return lra.print_expl(out, ex); } bool var_is_fresh(unsigned j) const { bool ret = m_fresh_k2xt_terms.has_second_key(j); SASSERT(!ret || m_var_register.local_to_external(j) == UINT_MAX); return ret; } template bool has_fresh_var(const T& t) const { return any_of(t, [&](auto const& p) { return var_is_fresh(p.var()); }); } bool has_fresh_var(unsigned row_index) const { return has_fresh_var(m_e_matrix[row_index]); } // A conflict is reported when the gcd of the monomial coefficients does not divide the free coefficent. // If there is no conflict the entry is divided, normalized, by gcd. // The function returns true if and only if there is no conflict. In the case of a conflict a branch // can be returned as well. bool normalize_e_by_gcd(unsigned ei, mpq& g) { mpq& e = m_sum_of_fixed[ei]; TRACE("dioph_eq", print_entry(ei, tout) << std::endl;); g = gcd_of_coeffs(m_e_matrix.m_rows[ei], false); if (g.is_zero() || g.is_one()) { SASSERT(g.is_one() || e.is_zero()); return true; } TRACE("dioph_eq", tout << "g:" << g << std::endl;); mpq c_g = e / g; if (c_g.is_int()) { for (auto& p : m_e_matrix.m_rows[ei]) { p.coeff() /= g; } m_sum_of_fixed[ei] = c_g; // e.m_l /= g for (auto& p : m_l_matrix.m_rows[ei]) { p.coeff() /= g; } TRACE("dioph_eq", tout << "ep_m_e:"; print_entry(ei, tout) << std::endl;); SASSERT(entry_invariant(ei)); return true; } // c_g is not integral return false; } lia_move subs_qfront_by_fresh(unsigned k, protected_queue& q, unsigned j) { const lar_term& e = m_fresh_k2xt_terms.get_by_key(k).first; TRACE("dioph_eq", tout << "k:" << k << ", in "; print_term_o(create_term_from_espace(), tout) << std::endl; tout << "subs with e:"; print_lar_term_L(e, tout) << std::endl;); mpq coeff = -m_espace[k]; // need to copy since it will be zeroed m_espace.erase(k); // m_espace[k] = 0; SASSERT(e.get_coeff(k).is_one()); for (const auto& p : e) { unsigned j = p.var(); if (j == k) continue; m_espace.add(p.coeff() * coeff, j); // do we need to add j to the queue? if (m_espace.has(j) && can_substitute(j)) q.push(j); } // there is no change in m_l_matrix TRACE("dioph_eq", tout << "after subs k:" << k << "\n"; print_term_o(create_term_from_espace(), tout) << std::endl; tout << "m_lspace:{"; print_lar_term_L(m_lspace.m_data, tout); tout << "}, opened:"; print_ml(m_lspace.to_term(), tout) << std::endl;); return tighten_on_espace(j); } void add_l_row_to_term_with_index(const mpq& coeff, unsigned ei) { for (const auto& p : m_l_matrix.m_rows[ei]) { m_lspace.add(coeff * p.coeff(), p.var()); } } lia_move subs_qfront_by_S(unsigned k, protected_queue& q, unsigned j) { const mpq& e = m_sum_of_fixed[m_k2s[k]]; TRACE("dio", tout << "k:" << k << ", in "; print_term_o(create_term_from_espace(), tout) << std::endl; tout << "subs with e:"; print_entry(m_k2s[k], tout) << std::endl;); mpq coeff = m_espace[k]; // need to copy since it will be zeroed m_espace.erase(k); // m_espace[k] = 0; const auto& e_row = m_e_matrix.m_rows[m_k2s[k]]; auto it = std::find_if(e_row.begin(), e_row.end(), [k](const auto& c) { return c.var() == k; }); SASSERT(it != e_row.end()); const mpq& k_coeff_in_e = it->coeff(); bool is_one = k_coeff_in_e.is_one(); SASSERT(is_one || k_coeff_in_e.is_minus_one()); if (is_one) coeff = -coeff; for (const auto& p : e_row) { unsigned j = p.var(); if (j == k) continue; m_espace.add(p.coeff() * coeff, j); // do we need to add j to the queue? if (m_espace.has(j) && can_substitute(j)) q.push(j); } m_c += coeff * e; add_l_row_to_term_with_index(coeff, sub_index(k)); TRACE("dio", tout << "after subs k:" << k << "\n"; print_term_o(create_term_from_espace(), tout) << std::endl; tout << "m_lspace:{"; print_lar_term_L(m_lspace.to_term(), tout); tout << "}, opened:"; print_ml(m_lspace.to_term(), tout) << std::endl;); return tighten_on_espace(j); } bool is_substituted_by_fresh(unsigned k) const { return m_fresh_k2xt_terms.has_key(k); } // find a variable in q, not neccessarily at the beginning of the queue, that when substituted would create the minimal // number of non-zeroes unsigned find_var_to_substitute_on_espace(protected_queue& q) { // go over all q elements j // say j is substituted by entry ei = m_k2s[j] // count the number of variables i in m_e_matrix[ei] that m_espace does not contain i, // and choose ei where this number is minimal unsigned best_var = UINT_MAX; size_t min_new_vars = std::numeric_limits::max(); unsigned num_candidates = 0; std::vector to_remove; for (unsigned j : q.m_q) { size_t new_vars = 0; if (!m_espace.has(j)) { to_remove.push_back(j); continue; } if (m_k2s.has_key(j)) { unsigned ei = m_k2s[j]; // entry index for substitution for (const auto& p : m_e_matrix.m_rows[ei]) if (p.var() != j && !m_espace.has(p.var())) ++new_vars; } else if (m_fresh_k2xt_terms.has_key(j)) { const lar_term& fresh_term = m_fresh_k2xt_terms.get_by_key(j).first; for (const auto& p : fresh_term) if (p.var() != j && !m_espace.has(p.var())) ++new_vars; } if (new_vars < min_new_vars) { min_new_vars = new_vars; best_var = j; num_candidates = 1; } else if (new_vars == min_new_vars) { ++num_candidates; if ((lra.settings().random_next() % num_candidates) == 0) best_var = j; } } if (best_var != UINT_MAX) q.remove(best_var); for (unsigned j: to_remove) q.remove(j); return best_var; } // The term giving the substitution is in form (+-)x_k + sum {a_i*x_i} + c = 0. // We substitute x_k in t by (+-)coeff*(sum {a_i*x_i} + c), where coeff is // the coefficient of x_k in t. lia_move subs_front_with_S_and_fresh(protected_queue& q, unsigned j) { process_fixed_in_espace(); auto r = tighten_on_espace(j); if (r == lia_move::conflict) return lia_move::conflict; unsigned k = find_var_to_substitute_on_espace(q); if (k == UINT_MAX) return lia_move::undef; SASSERT(m_espace.has(k)); // we might substitute with a term from S or a fresh term SASSERT(can_substitute(k)); lia_move ret; if (is_substituted_by_fresh(k)) ret = subs_qfront_by_fresh(k, q, j); else ret = subs_qfront_by_S(k, q, j); return join(ret, r); } lar_term l_term_from_row(unsigned k) const { lar_term ret; for (const auto& p : m_l_matrix.m_rows[k]) ret.add_monomial(p.coeff(), p.var()); return ret; } bool is_fixed(unsigned j) const { return lra.column_is_fixed(j); } term_o fix_vars(const lar_term& t) const { term_o ret; for (const auto& p : t) { if (is_fixed(p.var())) { ret.c() += p.coeff() * this->lra.get_lower_bound(p.var()).x; } else { ret.add_monomial(p.coeff(), p.var()); } } return ret; } term_o fix_vars(const term_o& t) const { term_o ret; ret.c() = t.c(); for (const auto& p : t) { if (is_fixed(p.var())) { ret.c() += p.coeff() * this->lra.get_lower_bound(p.var()).x; } else { ret.add_monomial(p.coeff(), p.var()); } } return ret; } const unsigned sub_index(unsigned k) const { return m_k2s[k]; } template T pop_front_of_queue(std::queue& q) const { T value = q.front(); q.pop(); return value; } bool subs_invariant(unsigned j) const { term_o ls = fix_vars(term_to_lar_solver(remove_fresh_vars(create_term_from_espace()))); term_o t0 = m_lspace.to_term(); term_o t1 = open_ml(t0); t1.add_monomial(mpq(1), j); term_o rs = fix_vars(t1); if (ls != rs) { std::cout << "enabling trace dio\n"; enable_trace("dio"); TRACE("dio", tout << "ls:"; print_term_o(ls, tout) << "\n"; tout << "rs:"; print_term_o(rs, tout) << "\n";); return false; } return true; } lia_move subs_with_S_and_fresh(protected_queue& q, unsigned j) { lia_move r = lia_move::undef; while (!q.empty() && r != lia_move::conflict && m_espace.size() <= m_tighten_size_max) { lia_move ret = subs_front_with_S_and_fresh(q, j); r = join(ret, r); } return r; } unsigned term_weight(const lar_term& t) const { unsigned weight = 0; for (const auto& p : t) { // Get index j from coefficient unsigned j = p.var(); // Look up j in columns_to_terms map auto it = m_columns_to_terms.find(j); if (it != m_columns_to_terms.end()) weight = std::max(weight, static_cast(it->second.size())); } return weight; } lia_move tighten_terms_with_S() { // Copy changed terms to another vector for sorting std_vector sorted_changed_terms; std_vector processed_terms; for (unsigned j: m_terms_to_tighten) { if (j >= lra.column_count() || !lra.column_has_term(j) || lra.column_is_free(j) || !lia.column_is_int(j)) { processed_terms.push_back(j); continue; } sorted_changed_terms.push_back(j); } // Sort by term_weight descending std::sort(sorted_changed_terms.begin(), sorted_changed_terms.end(), [this](unsigned j1, unsigned j2) { return term_weight(lra.get_term(j1)) > term_weight(lra.get_term(j2) ); }); lia_move r = lia_move::undef; // Process sorted terms TRACE("dio", tout << "changed terms:"; for (auto j : sorted_changed_terms) tout << j << " "; tout << std::endl; print_S(tout); // lra.display(tout); // print_bounds(tout); ); for (unsigned j : sorted_changed_terms) { m_terms_to_tighten.remove(j); if (ignore_big_nums() && term_has_big_number(lra.get_term(j))) continue; auto ret = tighten_bounds_for_term_column(j); r = join(ret, r); if (r == lia_move::conflict) break; } for (unsigned j : processed_terms) m_terms_to_tighten.remove(j); TRACE("dio", tout << r << "\n"); return r; } term_o create_term_from_espace() const { term_o t; for (const auto& p : m_espace.m_data) { t.add_monomial(p.coeff(), p.var()); } t.c() = m_c; return t; } // We will have lar_t, and let j is lar_t.j(), the term column. // In the m_espace we have lar_t. The result of open_ml((1*j)) is lar_t - (1, j). // So we have "equality" m_espace = open(m_lspace) + (1*lar_t.j()) // return false iff seen a big number and dio_ignore_big_nums() is true bool init_substitutions(const lar_term& lar_t, protected_queue& q) { m_espace.clear(); m_c = mpq(0); m_lspace.clear(); m_lspace.add(mpq(1), lar_t.j()); SASSERT(get_extended_term_value(lar_t).is_zero()); for (const auto& p : lar_t) { if (is_fixed(p.j())) { const mpq& b = lia.lower_bound(p.j()).x; if (ignore_big_nums() && b.is_big()) return false; m_c += p.coeff() * b; } else { unsigned lj = lar_solver_to_local(p.j()); SASSERT(!p.coeff().is_big()); m_espace.add(p.coeff(), lj);; if (can_substitute(lj)) q.push(lj); } } SASSERT(subs_invariant(lar_t.j())); return true; } unsigned lar_solver_to_local(unsigned j) const { return m_var_register.external_to_local(j); } void process_fixed_in_espace() { std_vector fixed_vars; for (const auto & p: m_espace) { if (!var_is_fresh(p.var()) && is_fixed(local_to_lar_solver(p.var()))) fixed_vars.push_back(p.var()); } for (unsigned j : fixed_vars) { m_c += m_espace[j] * lra.get_lower_bound(local_to_lar_solver(j)).x; m_espace.erase(j); } } lia_move tighten_on_espace(unsigned j) { mpq g = gcd_of_coeffs(m_espace.m_data, true); if (g.is_one()) return lia_move::undef; if (g.is_zero()) { handle_constant_term(j); if (!m_infeas_explanation.empty()) return lia_move::conflict; return lia_move::undef; } if (create_branch_report(j, g)) { lra.settings().stats().m_dio_branch_from_proofs++; return lia_move::branch; } // g is not trivial, trying to tighten the bounds auto r = tighten_bounds_for_non_trivial_gcd(g, j, true); if (r == lia_move::undef) r = tighten_bounds_for_non_trivial_gcd(g, j, false); if (r == lia_move::undef && m_changed_columns.contains(j)) r = lia_move::continue_with_check; return r; } /* j is the index of the column representing a term Return lia_move::conflict if a conflict was found, lia_move::continue_with_check if j became a fixed variable, and undef otherwise When we have a constraint x + y <= 8 then after the substitutions with S and fresh variables we might have x + y = 7t - 1 <= 8, where t is a term. Then 7t <= 9, or t <= 9/7, and we can enforce t <= floor(9/7) = 1. Then x + y = 7*1 - 1 <= 6: the bound is strenthgened. The constraint in this example comes from the upper bound on j, where j is the slack variable in x + y - j = 0. */ lia_move tighten_bounds_for_term_column(unsigned j) { // q is the queue of variables that can be substituted in term_to_tighten protected_queue q; TRACE("dio", tout << "j:" << j << " , intitial term t: "; print_lar_term_L(lra.get_term(j), tout) << std::endl; for( const auto& p : lra.get_term(j).ext_coeffs()) { lra.print_column_info(p.var(), tout); } ); if (!init_substitutions(lra.get_term(j), q)) return lia_move::undef; TRACE("dio", tout << "t:"; tout << "m_espace:"; print_term_o(create_term_from_espace(), tout) << std::endl; tout << "in lar_solver indices:\n"; print_term_o(term_to_lar_solver(create_term_from_espace()), tout) << "\n"; tout << "m_lspace:"; print_lar_term_L(m_lspace.to_term(), tout) << std::endl;); if (subs_with_S_and_fresh(q, j) == lia_move::conflict) return lia_move::conflict; process_fixed_in_espace(); SASSERT(subs_invariant(j)); return tighten_on_espace(j); } bool should_report_branch() const { return (lra.settings().stats().m_dio_calls% lra.settings().dio_report_branch_with_term_tigthening_period()) == 0; } void remove_fresh_from_espace() { protected_queue q; for (const auto& p : m_espace.m_data) { if (var_is_fresh(p.var())) q.push(p.var()); } while (!q.empty()) { unsigned xt = q.pop_front(); // add the fresh term to m_espace const lar_term& e = m_fresh_k2xt_terms.get_by_val(xt).first; mpq coeff = m_espace[xt]; // need to copy since it will be zeroed m_espace.erase(xt); // m_espace[k] = 0; for (const auto& p : e) { unsigned j = p.var(); if (j == xt) continue; m_espace.add(p.coeff() * coeff, j); // do we need to add j to the queue? if (m_espace.has(j) && var_is_fresh(j)) q.push(j); } } } impq get_extended_term_value(const lar_term& t) const { impq ret; for (const auto& p : t.ext_coeffs()) { ret += p.coeff() * lra.get_column_value(p.j()); } return ret; } mpq get_term_value(const term_o& t) const { mpq ret = t.c(); for (const auto& p : t) { ret += p.coeff() * lra.get_column_value(p.var()).x; } return ret; } mpq get_value_of_espace() const { mpq r; for (const auto & p : m_espace.m_data) { r += p.coeff()*lra.get_column_value(local_to_lar_solver(p.var())).x; SASSERT(lra.get_column_value(local_to_lar_solver(p.var())).y.is_zero()); } return r; } bool create_branch_report(unsigned j, const mpq& g) { if (!should_report_branch()) return false; if (!lia.at_bound(j)) return false; mpq rs = (lra.get_column_value(j).x - m_c) / g; if (rs.is_int()) return false; m_report_branch = true; remove_fresh_from_espace(); SASSERT(get_value_of_espace() + m_c == lra.get_column_value(j).x && lra.get_column_value(j).x.is_int()); lar_term& t = lia.get_term(); t.clear(); for (const auto& p : m_espace.m_data) { t.add_monomial(p.coeff() / g, local_to_lar_solver(p.var())); } lia.offset() = floor(rs); lia.is_upper() = true; m_report_branch = true; TRACE("dioph_eq", tout << "prepare branch, t:"; print_lar_term_L(t, tout) << " <= " << lia.offset() << std::endl; tout << "current value of t:" << get_term_value(t) << std::endl; ); SASSERT(get_value_of_espace() / g > lia.offset() ); return true; } void get_expl_from_meta_term(const lar_term& t, explanation& ex, const mpq & gcd) { u_dependency* dep = explain_fixed_in_meta_term(t, gcd); for (constraint_index ci : lra.flatten(dep)) ex.push_back(ci); } void handle_constant_term(unsigned j) { if (m_c.is_zero()) { return; } mpq rs; bool is_strict; u_dependency* b_dep = nullptr; if (lra.has_upper_bound(j, b_dep, rs, is_strict)) { if (m_c > rs || (is_strict && m_c == rs)) { u_dependency* dep = lra.join_deps(explain_fixed(lra.get_term(j), mpq(0)), explain_fixed_in_meta_term(m_lspace.m_data, mpq(0))); dep = lra.join_deps( dep, lra.get_bound_constraint_witnesses_for_column(j)); for (constraint_index ci : lra.flatten(dep)) { m_infeas_explanation.push_back(ci); } return; } } if (lra.has_lower_bound(j, b_dep, rs, is_strict)) { if (m_c < rs || (is_strict && m_c == rs)) { u_dependency* dep = lra.join_deps(explain_fixed(lra.get_term(j), mpq(0)), explain_fixed_in_meta_term(m_lspace.m_data, mpq(0))); dep = lra.join_deps( dep, lra.get_bound_constraint_witnesses_for_column(j)); for (constraint_index ci : lra.flatten(dep)) { m_infeas_explanation.push_back(ci); } } } } // m_espace contains the coefficients of the term // m_c contains the fixed part of the term // m_tmp_l is the linear combination of the equations that removes the // substituted variables. // returns true iff the conflict is found lia_move tighten_bounds_for_non_trivial_gcd(const mpq& g, unsigned j, bool is_upper) { mpq rs; bool is_strict = false; u_dependency* b_dep = nullptr; SASSERT(!g.is_zero()); if (lra.has_bound_of_type(j, b_dep, rs, is_strict, is_upper)) { TRACE("dio", tout << "current " << (is_upper? "upper":"lower") << " bound for x" << j << ":" << rs << std::endl;); mpq rs_g = (rs - m_c) % g; if (rs_g.is_neg()) { rs_g += g; } if (! (!rs_g.is_neg() && rs_g.is_int())) { std::cout << "rs:" << rs << "\n"; std::cout << "m_c:" << m_c << "\n"; std::cout << "g:" << g << "\n"; std::cout << "rs_g:" << rs_g << "\n"; } SASSERT(rs_g.is_int()); TRACE("dio", tout << "(rs - m_c) % g:" << rs_g << std::endl;); if (!rs_g.is_zero()) { if (tighten_bound_kind(g, j, rs, rs_g, is_upper)) return lia_move::conflict; } else { TRACE("dio", tout << "no improvement in the bound\n";); } } return lia_move::undef; } // returns true only on a conflict bool tighten_bound_kind(const mpq& g, unsigned j, const mpq& rs, const mpq& rs_g, bool upper) { /* Variable j corresponds to term t = lra.get_term(j). At this point we substituted some variables of t with the equivalent terms in S and the equivalent expressions containing fresh variables: t = sum{a_i * x_i: i in K} + sum{b_i * x_i: i in P }, where P and K are disjoint sets, a_i % g = 0 for each i in K, and x_i is a fixed variable for each i in P. In the notations of the program: m_espace corresponds to sum{a_i * x_i: i in K}, m_c is the value of sum{b_i * x_i: i in P}, open_ml(m_lspace) gives sum{a_i*x_i: i in K} + sum{b_i * x_i: i in P}. We can rewrite t = g*t_ + m_c, where t_ = sum{(a_i/g)*x_i: i in K}. Let us suppose that upper is true and rs is an upper bound of variable j, or t = g*t_ + m_c <= rs. Parameter rs_g is defined as (rs - m_c) % g. Notice that rs_g does not change when m_c changes by a multiple of g. We also know that rs_g > 0. For some integer k we have rs - m_c = k*g + rs_g. Starting with g*t_ + m_c <= rs, we proceed to g*t_ <= rs - m_c = k*g + rs_g. We can discard rs_g on the right: g*t_ <= k*g = rs - m_c - rs_g. Adding m_c to both sides gives us g*t_ + m_c <= rs - rs_g, or t <= rs - rs_g. In case of a lower bound we have t = g*t_+ m_c >= rs Then g*t_ >= rs - m_c = k*g + rs_g => g*t_ >= k*g + g. Adding m_c to both sides gets us g*t_ + m_c >= k*g + g + m_c = rs - m_c - rs_g + g + m_c = rs + (g - rs_g). Each fixed variable i in P such that b_i is divisible by g can be moved from P to K. Then we apply all arguments above, and get the same result, since m_c changes by a multiple of g. */ mpq bound = upper ? rs - rs_g : rs + g - rs_g; TRACE("dio", tout << "is upper:" << upper << std::endl; tout << "new " << (upper ? "upper" : "lower") << " bound:" << bound << std::endl;); SASSERT((upper && bound < lra.get_upper_bound(j).x) || (!upper && bound > lra.get_lower_bound(j).x)); lconstraint_kind kind = upper ? lconstraint_kind::LE : lconstraint_kind::GE; u_dependency* dep = upper ? lra.get_column_upper_bound_witness(j) : lra.get_column_lower_bound_witness(j); auto fixed_part_of_the_term = open_fixed_from_ml(m_lspace); TRACE("dio", tout << "fixed_part_of_the_term:"; print_term_o(fixed_part_of_the_term.to_term(), tout); ); // the right side is off by 1*j from m_espace if (is_fixed(j)) fixed_part_of_the_term.add(mpq(1), j); for (const auto& p: fixed_part_of_the_term) { SASSERT(is_fixed(p.var())); if (p.coeff().is_int() && (p.coeff() % g).is_zero()) { // we can skip this dependency // because the monomial p.coeff()*p.var() is 0 modulo g, and it does not matter that p.var() is fixed. // We could have added p.coeff()*p.var() to g*t_, substructed the value of p.coeff()*p.var() from m_c and // still get the same result. TRACE("dio", tout << "skipped dep:\n"; print_deps(tout, lra.get_bound_constraint_witnesses_for_column(p.var()));); continue; } dep = lra.join_deps(dep, lra.get_bound_constraint_witnesses_for_column(p.var())); } TRACE("dio", tout << "jterm:"; print_lar_term_L(lra.get_term(j), tout) << "\ndep:"; print_deps(tout, dep) << std::endl;); if (lra.settings().get_cancel_flag()) return false; lra.update_column_type_and_bound(j, kind, bound, dep); lp_status st = lra.find_feasible_solution(); if (is_sat(st) || st == lp::lp_status::CANCELLED) return false; lra.get_infeasibility_explanation(m_infeas_explanation); return true; } // if gcd is not zero we ignore all monomials with the coefficients divisible by gcd template u_dependency* explain_fixed_in_meta_term(const T& t, const mpq& gcd) const { return explain_fixed(open_fixed_from_ml(t), gcd); } // if gcd is not zero we ignore all monomials with the coefficients divisible by gcd template u_dependency* explain_fixed(const T& t, const mpq& gcd) const { u_dependency* dep = nullptr; if (gcd.is_zero()) { for (const auto& p : t) { if (is_fixed(p.var())) { u_dependency* bound_dep = lra.get_bound_constraint_witnesses_for_column(p.var()); dep = lra.join_deps(dep, bound_dep); } } } else { for (const auto& p : t) { if (is_fixed(p.var())) { if ((p.coeff()/gcd).is_int()) continue; u_dependency* bound_dep = lra.get_bound_constraint_witnesses_for_column(p.var()); dep = lra.join_deps(dep, bound_dep); } } } return dep; } void fill_f_vector(std_vector & f_vector) { for (unsigned ei = 0; ei < m_e_matrix.row_count(); ei++) { if (belongs_to_s(ei)) continue; if (m_e_matrix.m_rows[ei].size() == 0) { if (m_sum_of_fixed[ei].is_zero()) { continue; } else { set_rewrite_conflict(ei, mpq(0)); return; } } f_vector.push_back(ei); } } lia_move process_f(std_vector& f_vector) { if (has_conflict_index()) return lia_move::conflict; lia_move r; do { r = rewrite_eqs(f_vector); if (lra.settings().get_cancel_flag()) return lia_move::undef; if (r == lia_move::conflict || r == lia_move::undef) break; SASSERT(m_changed_columns.size() == 0); } while (f_vector.size()); if (r == lia_move::conflict) { return lia_move::conflict; } TRACE("dio_s", print_S(tout)); return lia_move::undef; } lia_move process_f_and_tighten_terms(std_vector& f_vector) { lia_move ret = process_f(f_vector); if (ret != lia_move::undef) return ret; ret = tighten_terms_with_S(); if (ret == lia_move::conflict) lra.stats().m_dio_tighten_conflicts++; TRACE("dio", print_S(tout);); return ret; } void collect_evidence() { lra.get_infeasibility_explanation(m_infeas_explanation); for (const auto& p : m_infeas_explanation) { m_explanation_of_branches.push_back(p.ci()); } } // returns true if the left and the right branches were explored void undo_explored_branches() { TRACE("dio_br", tout << "m_branch_stack.size():" << m_branch_stack.size() << std::endl;); while (m_branch_stack.size() && m_branch_stack.back().m_fully_explored) { m_branch_stack.pop_back(); lra_pop(); } TRACE("dio_br", tout << "after pop:m_branch_stack.size():" << m_branch_stack.size() << std::endl;); } lia_move check_fixing(unsigned j) const { // do not change entry here unsigned ei = m_k2s[j]; // entry index mpq g = mpq(0); // gcd mpq c = m_sum_of_fixed[ei]; for (const auto& p : m_e_matrix.m_rows[m_k2s[j]]) { if (p.var() == j) { const mpq& j_coeff = p.coeff(); SASSERT(j_coeff.is_one() || j_coeff.is_minus_one()); c += j_coeff * lra.get_lower_bound(local_to_lar_solver(j)).x; TRACE("dio_br", tout << "the value of the vixed var is:" << lra.get_lower_bound(local_to_lar_solver(j)).x << ", m_sum_of_fixed[" << ei << "]:" << m_sum_of_fixed[ei] << ", new free coeff c:" << c << std::endl;); continue; } if (g.is_zero()) { g = abs(p.coeff()); } else { g = gcd(g, p.coeff()); } if (g.is_one()) return lia_move::undef; } if (!(c / g).is_int()) { return lia_move::conflict; } return lia_move::undef; } // here j is a local variable lia_move fix_var(unsigned j) { SASSERT(is_fixed(local_to_lar_solver(j))); /* We only can get a conflict when j is substituted, and the entry m_k2s[j], the entry defining the substitution becomes infeaseable, that is the gcd of the monomial coeffitients does not divide the free coefficient. In other cases the gcd of the monomials will remain to be 1. */ if (m_k2s.has_key(j)) { // j is substituted but using an entry TRACE("dio_br", tout << "fixed j:" << j << ", was substited by "; print_entry(m_k2s[j], tout);); if (check_fixing(j) == lia_move::conflict) { for (auto ci : lra.flatten(explain_fixed_in_meta_term(m_l_matrix.m_rows[m_k2s[j]], mpq(0)))) { m_explanation_of_branches.push_back(ci); } return lia_move::conflict; } } return lia_move::undef; } void undo_branching() { while (m_lra_level--) { lra.pop(); } lra.find_feasible_solution(); SASSERT(lra.get_status() == lp_status::CANCELLED || lra.is_feasible()); } // Returns true if a branch is created, and false if not. // The latter case can happen if we have a sat. bool push_branch() { branch br = create_branch(); if (br.m_j == UINT_MAX) return false; m_branch_stack.push_back(br); lra.stats().m_dio_branching_depth = std::max(lra.stats().m_dio_branching_depth, (unsigned)m_branch_stack.size()); return true; } lia_move add_var_bound_for_branch(const branch& b) { if (b.m_left) lra.add_var_bound(b.m_j, lconstraint_kind::LE, b.m_rs); else lra.add_var_bound(b.m_j, lconstraint_kind::GE, b.m_rs + mpq(1)); TRACE("dio_br", lra.print_column_info(b.m_j, tout) << "add bound" << std::endl;); if (lra.column_is_fixed(b.m_j)) { unsigned local_bj; if (!m_var_register.external_is_used(b.m_j, local_bj)) return lia_move::undef; if (fix_var(local_bj) == lia_move::conflict) { TRACE("dio_br", tout << "conflict in fix_var" << std::endl;); return lia_move::conflict; } } return lia_move::undef; } unsigned m_lra_level = 0; void lra_push() { m_lra_level++; lra.push(); SASSERT(m_lra_level == m_branch_stack.size()); } void lra_pop() { m_lra_level--; SASSERT(m_lra_level != UINT_MAX); lra.pop(); lra.find_feasible_solution(); SASSERT(lra.get_status() == lp_status::CANCELLED || lra.is_feasible()); } void transfer_explanations_from_closed_branches() { m_infeas_explanation.clear(); for (auto ci : m_explanation_of_branches) { if (this->lra.constraints().valid_index(ci)) m_infeas_explanation.push_back(ci); } } lia_move branching_on_undef() { m_explanation_of_branches.clear(); bool need_create_branch = true; m_number_of_branching_calls = 0; while (++m_number_of_branching_calls < m_max_of_branching_iterations) { lra.stats().m_dio_branch_iterations++; if (need_create_branch) { if (!push_branch()) { undo_branching(); lra.stats().m_dio_branching_sats++; return lia_move::sat; } need_create_branch = false; } lra_push(); // exploring a new branch if (add_var_bound_for_branch(m_branch_stack.back()) == lia_move::conflict) { undo_explored_branches(); if (m_branch_stack.size() == 0) { lra.stats().m_dio_branching_infeasibles++; transfer_explanations_from_closed_branches(); lra.stats().m_dio_branching_conflicts++; return lia_move::conflict; } need_create_branch = false; m_branch_stack.back().flip(); lra_pop(); continue; } auto st = lra.find_feasible_solution(); TRACE("dio_br", tout << "st:" << lp_status_to_string(st) << std::endl;); if (st == lp_status::CANCELLED) return lia_move::undef; else if (lp::is_sat(st)) { // have a feasible solution unsigned n_of_ii = get_number_of_int_inf(); TRACE("dio_br", tout << "n_of_ii:" << n_of_ii << "\n";); if (n_of_ii == 0) { undo_branching(); lra.stats().m_dio_branching_sats++; return lia_move::sat; } // got to create a new branch update_branch_stats(m_branch_stack.back(), n_of_ii); need_create_branch = true; } else { collect_evidence(); undo_explored_branches(); if (m_branch_stack.size() == 0) { lra.stats().m_dio_branching_infeasibles++; transfer_explanations_from_closed_branches(); lra.stats().m_dio_branching_conflicts++; return lia_move::conflict; } TRACE("dio_br", tout << lp_status_to_string(lra.get_status()) << std::endl; tout << "explanation:\n"; lra.print_expl(tout, m_infeas_explanation);); need_create_branch = false; lra_pop(); m_branch_stack.back().flip(); } } undo_branching(); return lia_move::undef; } unsigned get_number_of_int_inf() const { return (unsigned)std::count_if( lra.r_basis().begin(), lra.r_basis().end(), [this](unsigned j) { return lia.column_is_int_inf(j); }); } double get_branch_score(unsigned j) { if (j >= m_branch_stats.size()) m_branch_stats.resize(j + 1); return m_branch_stats[j].score(); } void update_branch_stats(const branch& b, unsigned n_of_ii) { // Ensure the branch stats vector is large enough if (b.m_j >= m_branch_stats.size()) m_branch_stats.resize(b.m_j + 1); if (b.m_left) m_branch_stats[b.m_j].m_ii_after_left.push_back(n_of_ii); else m_branch_stats[b.m_j].m_ii_after_right.push_back(n_of_ii); } branch create_branch() { unsigned bj = UINT_MAX; double score = std::numeric_limits::infinity(); // looking for the minimal score unsigned n = 0; for (unsigned j : lra.r_basis()) { if (!lia.column_is_int_inf(j)) continue; double sc = get_branch_score(j); if (sc < score || (sc == score && lra.settings().random_next() % (++n) == 0)) { score = sc; bj = j; } } branch br; if (bj == UINT_MAX) { // it the case when we cannot create a branch SASSERT( lra.settings().get_cancel_flag() || (lra.is_feasible() && [&]() { for (unsigned j = 0; j < lra.column_count(); ++j) { if (lia.column_is_int_inf(j)) { return false; } } return true; }())); return br; // to signal that we have no ii variables } br.m_j = bj; br.m_left = (lra.settings().random_next() % 2 == 0); br.m_rs = floor(lra.get_column_value(bj).x); TRACE("dio_br", tout << "score:" << score << "; br.m_j:" << br.m_j << "," << (br.m_left ? "left" : "right") << ", br.m_rs:" << br.m_rs << std::endl;); return br; } bool columns_to_terms_is_correct() const { std::unordered_map> c2t; for (unsigned k = 0; k < lra.terms().size(); k++) { const lar_term* t = lra.terms()[k]; if (!lia.column_is_int(t->j())) continue; if (ignore_big_nums() && term_has_big_number(*t)) continue; SASSERT(t->j() != UINT_MAX); for (const auto& p : (*t).ext_coeffs()) { unsigned j = p.var(); auto it = c2t.find(j); if (it == c2t.end()) { std::unordered_set s; s.insert(t->j()); c2t[j] = s; } else { it->second.insert(t->j()); } } } for (const auto& p : c2t) { unsigned j = p.first; const auto it = m_columns_to_terms.find(j); if (it == m_columns_to_terms.end()) { TRACE("dio", tout << "column j" << j << " is not registered" << std::endl; tout << "the column belongs to the the following terms:"; for (unsigned tj : p.second) { tout << " " << tj; } tout << std::endl;); return false; } if (it->second != p.second) { TRACE("dioph_eq_deb", tout << "m_columns_to_terms[" << j << "] has to be "; tout << "{"; for (unsigned lll : p.second) { tout << lll << ", "; } tout << "}, \nbut it is {"; for (unsigned lll : it->second) { tout << lll << ", "; }; tout << "}" << std::endl; ); return false; } } // reverse inclusion for (const auto& p : m_columns_to_terms) { unsigned j = p.first; const auto it = c2t.find(j); if (it == c2t.end()) { TRACE("dio", tout << "should not be registered j " << j << std::endl; lra.print_terms(tout);); return false; } if (it->second != p.second) { return false; } } return true; } bool is_in_sync() const { for (unsigned j = 0; j < m_e_matrix.column_count(); j++) { unsigned external_j = m_var_register.local_to_external(j); if (external_j == UINT_MAX) continue; if (external_j >= lra.column_count() && m_e_matrix.m_columns[j].size()) { // It is OK to have an empty column in m_e_matrix. return false; } } for (unsigned ei = 0; ei < m_e_matrix.row_count(); ei++) { auto it = m_row2fresh_defs.find(ei); if (it != m_row2fresh_defs.end()) { for (unsigned xt : it->second) { if (!m_fresh_k2xt_terms.has_second_key(xt)) return false; } } } return columns_to_terms_is_correct(); } public: lia_move check() { lra.stats().m_dio_calls++; TRACE("dio", tout << lra.stats().m_dio_calls << std::endl;); std_vector f_vector; lia_move ret; do { init(f_vector); ret = process_f_and_tighten_terms(f_vector); } while (ret == lia_move::continue_with_check); if (ret != lia_move::undef) return ret; if (lra.stats().m_dio_calls % lra.settings().dio_branching_period() == 0) ret = branching_on_undef(); m_max_of_branching_iterations = (unsigned)m_max_of_branching_iterations / 2; return ret; } private: unsigned get_markovich_number(unsigned k, unsigned h) { size_t col_size = m_e_matrix.m_columns[k].size(); size_t row_size = m_e_matrix.m_rows[h].size(); // Subtract 1 from sizes once and multiply return static_cast((col_size - 1) * (row_size - 1)); } std::tuple find_minimal_abs_coeff(unsigned ei) { bool first = true; mpq ahk; unsigned k = -1; int k_sign = 0; mpq t; for (const auto& p : m_e_matrix.m_rows[ei]) { t = abs(p.coeff()); if (first || t < ahk) { ahk = t; k_sign = p.coeff().is_pos() ? 1 : -1; k = p.var(); first = false; if (ahk.is_one()) break; } } return std::make_tuple(ahk, k, k_sign, get_markovich_number(k, ei)); } bool j_sign_is_correct(unsigned ei, unsigned j, int j_sign) { const auto& row = m_e_matrix.m_rows[ei]; auto it = std::find_if(row.begin(), row.end(), [j](const auto& p) { return p.var() == j; }); if (it == row.end()) return false; return (it->coeff() == mpq(1) && j_sign == 1) || (it->coeff() == mpq(-1) && j_sign == -1); } // j is the variable to eliminate, or substitude, it appears in row ei of m_e_matrix with // a coefficient equal to j_sign which is +-1 void eliminate_var_in_f(unsigned ei, unsigned j, int j_sign) { SASSERT(belongs_to_s(ei)); const auto& e = m_sum_of_fixed[ei]; SASSERT(j_sign_is_correct(ei, j, j_sign)); TRACE("dio", tout << "eliminate var:" << j << " by using:"; print_entry(ei, tout) << std::endl;); auto& column = m_e_matrix.m_columns[j]; auto it = std::find_if(column.begin(), column.end(), [ei](const auto& cell) { return cell.var() == ei; }); unsigned pivot_col_cell_index = (unsigned)std::distance(column.begin(), it); if (pivot_col_cell_index != 0) { // swap the pivot column cell with the head cell auto c = column[0]; column[0] = column[pivot_col_cell_index]; column[pivot_col_cell_index] = c; m_e_matrix.m_rows[ei][column[0].offset()].offset() = 0; m_e_matrix.m_rows[c.var()][c.offset()].offset() = pivot_col_cell_index; } for (auto cell_to_process = column.size(); cell_to_process-- > 0; ) { auto& c = column[cell_to_process]; if (belongs_to_s(c.var())) continue; SASSERT(c.var() != ei && entry_invariant(c.var())); mpq coeff = m_e_matrix.get_val(c); unsigned i = c.var(); TRACE("dio", tout << "before pivot entry:"; print_entry(i, tout) << std::endl;); m_sum_of_fixed[i] -= j_sign * coeff * e; m_e_matrix.pivot_row_to_row_given_cell_with_sign(ei, c, j, j_sign); // m_sum_of_fixed[i].m_l -= j_sign * coeff * e.m_l; m_l_matrix.add_rows(-j_sign * coeff, ei, i); TRACE("dio", tout << "after pivoting c_row:"; print_entry(i, tout);); CTRACE( "dio", !entry_invariant(i), tout << "invariant delta:"; { print_term_o(get_term_from_entry(ei) - fix_vars(open_ml(m_l_matrix.m_rows[ei])), tout) << std::endl; }); SASSERT(entry_invariant(i)); } SASSERT(is_eliminated_from_f(j)); } // j is the variable to eliminate, or substitude, it appears in term t with // a coefficient equal to j_sign which is +-1 , // matrix m_l_matrix is not changed since it is a substitution of a fresh variable void eliminate_var_in_f_with_term(const lar_term& t, unsigned j, int j_sign) { SASSERT(abs(t.get_coeff(j)).is_one()); TRACE("dio", tout << "eliminate var:" << j << " by using:"; print_lar_term_L(t, tout) << std::endl;); auto& column = m_e_matrix.m_columns[j]; for (auto cell_to_process = column.size(); cell_to_process-- > 0; ) { auto& c = column[cell_to_process]; if (belongs_to_s(c.var())) continue; mpq coeff = m_e_matrix.get_val(c); TRACE("dio", tout << "before pivot entry :"; print_entry(c.var(), tout) << std::endl;); m_e_matrix.pivot_term_to_row_given_cell(t, c, j, j_sign); TRACE("dio", tout << "after pivoting c_row:"; print_entry(c.var(), tout);); SASSERT(entry_invariant(c.var())); } SASSERT(is_eliminated_from_f(j)); } bool is_eliminated_from_f(unsigned j) const { for (unsigned ei = 0; ei < m_e_matrix.row_count(); ei++) { if (!belongs_to_f(ei)) continue; const auto& row = m_e_matrix.m_rows[ei]; bool eliminated_in_row = all_of(row, [j](auto & p) { return p.var() != j; }); if (!eliminated_in_row) return false; } return true; } term_o term_to_lar_solver(const term_o& eterm) const { term_o ret; for (const auto& p : eterm) { ret.add_monomial(p.coeff(), local_to_lar_solver(p.var())); } ret.c() = eterm.c(); return ret; } bool belongs_to_s(unsigned ei) const { return m_k2s.has_val(ei); } bool entry_invariant(unsigned ei) const { if (lra.settings().get_cancel_flag()) return true; for (const auto& p : m_e_matrix.m_rows[ei]) { if (!p.coeff().is_int()) { return false; } if (var_is_fresh(p.var())) continue; unsigned j = local_to_lar_solver(p.var()); if (is_fixed(j)) { enable_trace("dio"); TRACE("dio", tout << "x" << j << "(local: " << "x" << p.var() << ") should not be fixed\nbad entry:"; print_entry(ei, tout) << "\n";); return false; } } term_o ls = term_to_lar_solver(remove_fresh_vars(get_term_from_entry(ei))); mpq ls_val = get_term_value(ls); if (!ls_val.is_zero()) { std::cout << "ls_val is not zero\n"; enable_trace("dio"); TRACE("dio", { tout << "get_term_from_entry(" << ei << "):"; print_term_o(get_term_from_entry(ei), tout) << std::endl; tout << "ls:"; print_term_o(ls, tout) << std::endl; tout << "e.m_l:"; print_lar_term_L(l_term_from_row(ei), tout) << std::endl; tout << "open_ml(e.m_l):"; print_lar_term_L(open_ml(l_term_from_row(ei)), tout) << std::endl; tout << "rs:"; print_term_o(fix_vars(open_ml(m_l_matrix.m_rows[ei])), tout) << std::endl; }); return false; } bool ret = ls == fix_vars(open_ml(m_l_matrix.m_rows[ei])); if (!ret) { enable_trace("dio"); CTRACE("dio", !ret, { tout << "get_term_from_entry(" << ei << "):"; print_term_o(get_term_from_entry(ei), tout) << std::endl; tout << "ls:"; print_term_o(ls, tout) << std::endl; tout << "e.m_l:"; print_lar_term_L(l_term_from_row(ei), tout) << std::endl; tout << "open_ml(e.m_l):"; print_lar_term_L(open_ml(l_term_from_row(ei)), tout) << std::endl; tout << "rs:"; print_term_o(fix_vars(open_ml(m_l_matrix.m_rows[ei])), tout) << std::endl; }); } return ret; } term_o remove_fresh_vars(const term_o& tt) const { term_o t = tt; protected_queue q; for (const auto& p : t) { if (var_is_fresh(p.j())) { q.push(p.j()); } } while (!q.empty()) { unsigned xt = q.pop_front(); // xt is a fresh var const lar_term& fresh_t = m_fresh_k2xt_terms.get_by_val(xt).first; TRACE("dio_remove_fresh", print_lar_term_L(fresh_t, tout);); SASSERT(fresh_t.get_coeff(xt).is_minus_one()); if (!t.contains(xt)) continue; mpq xt_coeff = t.get_coeff(xt); t += xt_coeff * fresh_t; SASSERT(t.contains(xt) == false); for (const auto& p : t) { if (var_is_fresh(p.j())) { q.push(p.j()); } } } return t; } std::ostream& print_ml(const lar_term& ml, std::ostream& out) const { term_o opened_ml = open_ml(ml); return print_lar_term_L(opened_ml, out); } // collect only fixed variables in a term template term_with_index open_fixed_from_ml(const T& ml) const { term_with_index r; for (const auto& v : ml) { for (const auto & p : lra.get_term(v.var()).ext_coeffs()) { if (lra.column_is_fixed(p.var())) r.add(v.coeff() * p.coeff(), p.var()); } } return r; } template term_o open_ml(const T& ml) const { term_o r; for (const auto& p : ml) { r += p.coeff() * (lra.get_term(p.var()) - lar_term(p.var())); } return r; } void open_l_term_to_espace(unsigned ei, mpq& c) { m_espace.clear(); for (const auto& p : m_l_matrix.m_rows[ei]) { const lar_term& t = lra.get_term(p.var()); for (const auto& q : t.ext_coeffs()) { if (is_fixed(q.var())) { c += p.coeff() * q.coeff() * lia.lower_bound(q.var()).x; } else { m_espace.add(p.coeff() * q.coeff(), q.var()); } } } } void copy_row_to_espace(unsigned ei) { m_espace.clear(); auto& row = m_e_matrix.m_rows[ei]; for (const auto& cell : row) m_espace.add(cell.coeff(), cell.var()); } // The idea is to be able remove this fresh definition when the row h changes. // The row can change if it depends on the term that is deleted, or on a variable that becomes fixed/unfixed // fr_j is a fresh variable void register_var_in_fresh_defs(unsigned h, unsigned fr_j) { auto it = m_row2fresh_defs.find(h); if (it == m_row2fresh_defs.end()) { m_row2fresh_defs[h].push_back(fr_j); } else { it->second.push_back(fr_j); } } // k is the variable to substitute void fresh_var_step(unsigned h, unsigned k, const mpq& ahk) { // step 7 from the paper // xt is the fresh variable unsigned xt = add_var(UINT_MAX); while (xt >= m_e_matrix.column_count()) m_e_matrix.add_column(); // Create a term the fresh variable definition: it seems needed in Debug only /* Let eh = sum(ai*xi) + c. For each i != k, let ai = qi*ahk + ri, and let c = c_q * ahk + c_r. eh = ahk*(x_k + sum{qi*xi|i != k} + c_q) + sum {ri*xi|i!= k} + c_r. Then -xt + x_k + sum {qi*x_i)| i != k} + c_q will be the fresh row eh = ahk*xt + sum {ri*x_i | i != k} + c_r is the row m_e_matrix[e.m_row_index] */ mpq q, r; lar_term fresh_t; // fresh term fresh_t.add_monomial(-mpq(1), xt); fresh_t.add_monomial(mpq(1), k); for (const auto& i : m_e_matrix.m_rows[h]) { const mpq& ai = i.coeff(); if (i.var() == k) continue; q = machine_div_rem(ai, ahk, r); if (!q.is_zero()) fresh_t.add_monomial(q, i.var()); } m_fresh_k2xt_terms.add(k, xt, std::make_pair(fresh_t, h)); SASSERT(var_is_fresh(xt)); register_var_in_fresh_defs(h, xt); eliminate_var_in_f_with_term(fresh_t, k, 1); } std::ostream& print_entry(unsigned i, std::ostream& out, bool print_dep = false, bool print_in_S = true, bool print_column_info = false) const { unsigned j = m_k2s.has_val(i) ? m_k2s.get_key(i) : UINT_MAX; out << "entry " << i << ": "; if (j != UINT_MAX) out << "x" << j << " "; out << "{\n"; print_term_o(get_term_from_entry(i), out << "\t") << " = 0\n"; if (print_dep) { auto l_term = l_term_from_row(i); out << "\tm_l:{"; print_lar_term_L(l_term, out) << "}, "; print_ml(l_term, out) << std::endl; out << "expl of fixed in m_l:{\n"; print_deps(out, explain_fixed_in_meta_term(l_term, mpq(0))); out << "}\n"; } if (belongs_to_f(i)) out << "in F\n"; else if (print_in_S) out << "in S\n"; if (print_column_info) { bool has_fresh = false; for (const auto& p : m_e_matrix[i] ) { if (var_is_fresh(p.var())) { has_fresh = true; break; } } if (!has_fresh) { for (const auto& p : get_term_from_entry(i)) { auto j = local_to_lar_solver(p.var()); out << "\tx" << p.var() << " := " << lra.get_column_value(j) << " " << lra.get_bounds_string(j) << "\n"; } } else { out << "\thas fresh vars\n"; } } out << "}\n"; return out; } bool belongs_to_f(unsigned ei) const { return !m_k2s.has_val(ei); } void remove_from_S(unsigned ei) { m_k2s.erase_val(ei); } // k is the variable that is being substituted // h is the index of the entry void move_entry_from_f_to_s(unsigned k, unsigned h) { m_k2s.add(k, h); } // this is the step 6 or 7 of the algorithm // returns lia_move::conflict if a conflict was found, continue_with_check if some progress has been achieved, // otherwise returns lia_move::undef lia_move rewrite_eqs(std_vector& f_vector) { if (f_vector.size() == 0) return lia_move::undef; unsigned h = -1, kh = 0; // the initial value of kh does not matter, assign to remove the warning unsigned n = 0; // number of choices for a fresh variable mpq min_ahk; int kh_sign = 0; // the initial values of kh_sign and h_markovich_number do not matter, assign to remove the warning unsigned h_markovich_number = 0; unsigned ih = -1; // f_vector[ih] = h for (unsigned i = 0; i < f_vector.size(); i++) { unsigned ei = f_vector[i]; SASSERT (belongs_to_f(ei)); if (m_e_matrix.m_rows[ei].size() == 0) { if (m_sum_of_fixed[ei].is_zero()) { continue; } else { set_rewrite_conflict(ei, mpq(0)); return lia_move::conflict; } } auto [ahk, k, k_sign, markovich_number] = find_minimal_abs_coeff(ei); mpq gcd; if (!normalize_e_by_gcd(ei, gcd)) { set_rewrite_conflict(ei, gcd); return lia_move::conflict; } if (!gcd.is_one()) ahk /= gcd; if (n == 0 || min_ahk > ahk) { ih = i; n = 1; min_ahk = ahk; h = ei; kh = k; kh_sign = k_sign; h_markovich_number = markovich_number; continue; } if (min_ahk == ahk && h_markovich_number > markovich_number) { ih = i; h = ei; kh = k; kh_sign = k_sign; h_markovich_number = markovich_number; } if (min_ahk.is_one() && h_markovich_number == 0) break; } if (h == UINT_MAX) { TRACE("dio", tout << "done - cannot find an entry to rewrite\n"); return lia_move::undef; } SASSERT(h == f_vector[ih]); if (min_ahk.is_one()) { TRACE("dioph_eq", tout << "push to S:\n"; print_entry(h, tout);); move_entry_from_f_to_s(kh, h); eliminate_var_in_f(h, kh, kh_sign); f_vector[ih] = f_vector.back(); f_vector.pop_back(); } else { fresh_var_step(h, kh, min_ahk * mpq(kh_sign)); } return lia_move::continue_with_check; } public: void explain(explanation& ex) { SASSERT(ex.empty()); if (has_conflict_index()) { TRACE("dio", print_entry(m_normalize_conflict_index, tout << "conflict:", true) << std::endl;); for (auto ci : lra.flatten(explain_fixed_in_meta_term(m_l_matrix.m_rows[m_normalize_conflict_index], m_normalize_conflict_gcd))) ex.push_back(ci); } else { for (auto ci : m_infeas_explanation) ex.push_back(ci.ci()); } TRACE("dio", lra.print_expl(tout, ex);); } // needed for the template bound_analyzer_on_row.h const lar_solver& lp() const { return lra; } lar_solver& lp() {return lra;} bool some_terms_are_ignored() const { return m_some_terms_are_ignored; } }; // Constructor definition dioph_eq::dioph_eq(int_solver& lia) { m_imp = alloc(imp, lia, lia.lra); } dioph_eq::~dioph_eq() { dealloc(m_imp); } lia_move dioph_eq::check() { return m_imp->check(); } void dioph_eq::explain(lp::explanation& ex) { m_imp->explain(ex); } bool dioph_eq::some_terms_are_ignored() const { return m_imp->some_terms_are_ignored(); } } // namespace lp