/*++ Copyright (c) 2006 Microsoft Corporation Module Name: theory_arith_aux.h Abstract: Author: Leonardo de Moura (leonardo) 2008-04-29. Revision History: --*/ #ifndef _THEORY_ARITH_AUX_H_ #define _THEORY_ARITH_AUX_H_ #include"theory_arith.h" namespace smt { // ----------------------------------- // // Rows // // ----------------------------------- template void theory_arith::row::reset() { m_entries.reset(); m_size = 0; m_base_var = -1; m_first_free_idx = -1; } /** \brief Add a new row_entry. The result is a reference to the new row_entry. The position of the new row_entry in the row is stored in pos_idx. */ template typename theory_arith::row_entry & theory_arith::row::add_row_entry(int & pos_idx) { m_size++; if (m_first_free_idx == -1) { pos_idx = m_entries.size(); m_entries.push_back(row_entry()); return m_entries.back(); } else { pos_idx = m_first_free_idx; row_entry & result = m_entries[pos_idx]; SASSERT(result.is_dead()); m_first_free_idx = result.m_next_free_row_entry_idx; return result; } } /** \brief Delete row_entry at position idx. */ template void theory_arith::row::del_row_entry(unsigned idx) { row_entry & t = m_entries[idx]; SASSERT(!t.is_dead()); t.m_next_free_row_entry_idx = m_first_free_idx; t.m_var = null_theory_var; m_size--; SASSERT(t.is_dead()); } /** \brief Remove holes (i.e., dead entries) from the row. */ template void theory_arith::row::compress(vector & cols) { unsigned i = 0; unsigned j = 0; unsigned sz = m_entries.size(); for (; i < sz; i++) { row_entry & t1 = m_entries[i]; if (!t1.is_dead()) { if (i != j) { row_entry & t2 = m_entries[j]; t2.m_coeff.swap(t1.m_coeff); t2.m_var = t1.m_var; t2.m_col_idx = t1.m_col_idx; SASSERT(!t2.is_dead()); column & col = cols[t2.m_var]; col[t2.m_col_idx].m_row_idx = j; } j++; } } SASSERT(j == m_size); m_entries.shrink(m_size); m_first_free_idx = -1; } /** \brief Invoke compress if the row contains too many holes (i.e., dead entries). */ template inline void theory_arith::row::compress_if_needed(vector & cols) { if (size() * 2 < num_entries()) { compress(cols); } } /** \brief Fill the map var -> pos/idx */ template inline void theory_arith::row::save_var_pos(svector & result_map) const { typename vector::const_iterator it = m_entries.begin(); typename vector::const_iterator end = m_entries.end(); unsigned idx = 0; for (; it != end; ++it, ++idx) { if (!it->is_dead()) { result_map[it->m_var] = idx; } } } /** \brief Reset the map var -> pos/idx. That is for all variables v in the row, set result[v] = -1 This method can be viewed as the "inverse" of save_var_pos. */ template inline void theory_arith::row::reset_var_pos(svector & result_map) const { typename vector::const_iterator it = m_entries.begin(); typename vector::const_iterator end = m_entries.end(); for (; it != end; ++it) { if (!it->is_dead()) { result_map[it->m_var] = -1; } } }; #ifdef Z3DEBUG /** \brief Return true if the coefficient of v in the row is equals to 'expected'. */ template bool theory_arith::row::is_coeff_of(theory_var v, numeral const & expected) const { typename vector::const_iterator it = m_entries.begin(); typename vector::const_iterator end = m_entries.end(); for (; it != end; ++it) { if (!it->is_dead() && it->m_var == v) { return it->m_coeff == expected; } } return false; } #endif template void theory_arith::row::display(std::ostream & out) const { out << "v" << m_base_var << ", "; typename vector::const_iterator it = m_entries.begin(); typename vector::const_iterator end = m_entries.end(); for (; it != end; ++it) { if (!it->is_dead()) { out << it->m_coeff << "*v" << it->m_var << " "; } } out << "\n"; } template typename theory_arith::numeral theory_arith::row::get_denominators_lcm() const { numeral r(1); TRACE("lcm_bug", tout << "starting get_denominators_lcm...\n";); typename vector::const_iterator it = m_entries.begin(); typename vector::const_iterator end = m_entries.end(); for (; it != end; ++it) { if (!it->is_dead()) { r = lcm(r, denominator(it->m_coeff)); TRACE("lcm_bug", tout << "it->m_coeff: " << it->m_coeff << ", denominator(it->m_coeff): " << denominator(it->m_coeff) << ", r: " << r << "\n";); } } return r; } template int theory_arith::row::get_idx_of(theory_var v) const { typename vector::const_iterator it = m_entries.begin(); typename vector::const_iterator end = m_entries.end(); for (unsigned idx = 0; it != end; ++it, ++idx) { if (!it->is_dead() && it->m_var == v) return idx; } return -1; } // ----------------------------------- // // Columns // // ----------------------------------- template void theory_arith::column::reset() { m_entries.reset(); m_size = 0; m_first_free_idx = -1; } /** \brief Remove holes (i.e., dead entries) from the column. */ template void theory_arith::column::compress(vector & rows) { unsigned i = 0; unsigned j = 0; unsigned sz = m_entries.size(); for (; i < sz; i++) { col_entry & e1 = m_entries[i]; if (!e1.is_dead()) { if (i != j) { m_entries[j] = e1; row & r = rows[e1.m_row_id]; r[e1.m_row_idx].m_col_idx = j; } j++; } } SASSERT(j == m_size); m_entries.shrink(m_size); m_first_free_idx = -1; } /** \brief Invoke compress if the column contains too many holes (i.e., dead entries). */ template inline void theory_arith::column::compress_if_needed(vector & rows) { if (size() * 2 < num_entries()) { compress(rows); } } /** \brief Special version of compress, that is used when the column contain only one entry located at position singleton_pos. */ template void theory_arith::column::compress_singleton(vector & rows, unsigned singleton_pos) { SASSERT(m_size == 1); if (singleton_pos != 0) { col_entry & s = m_entries[singleton_pos]; m_entries[0] = s; row & r = rows[s.m_row_id]; r[s.m_row_idx].m_col_idx = 0; } m_first_free_idx = -1; m_entries.shrink(1); } template const typename theory_arith::col_entry * theory_arith::column::get_first_col_entry() const { typename svector::const_iterator it = m_entries.begin(); typename svector::const_iterator end = m_entries.end(); for (; it != end; ++it) { if (!it->is_dead()) { return it; } } return 0; } template typename theory_arith::col_entry & theory_arith::column::add_col_entry(int & pos_idx) { m_size++; if (m_first_free_idx == -1) { pos_idx = m_entries.size(); m_entries.push_back(col_entry()); return m_entries.back(); } else { pos_idx = m_first_free_idx; col_entry & result = m_entries[pos_idx]; SASSERT(result.is_dead()); m_first_free_idx = result.m_next_free_row_entry_idx; return result; } } template void theory_arith::column::del_col_entry(unsigned idx) { col_entry & c = m_entries[idx]; SASSERT(!c.is_dead()); c.m_row_id = dead_row_id; c.m_next_free_row_entry_idx = m_first_free_idx; m_first_free_idx = idx; m_size--; } // ----------------------------------- // // Atoms // // ----------------------------------- template theory_arith::atom::atom(bool_var bv, theory_var v, numeral const & k, atom_kind kind): bound(v, inf_numeral::zero(), B_LOWER, true), m_bvar(bv), m_k(k), m_atom_kind(kind), m_is_true(false) { } template void theory_arith::atom::assign_eh(bool is_true, inf_numeral const & epsilon) { m_is_true = is_true; if (is_true) { this->m_value = m_k; this->m_bound_kind = static_cast(m_atom_kind); SASSERT((this->m_bound_kind == B_LOWER) == (m_atom_kind == A_LOWER)); } else { if (get_atom_kind() == A_LOWER) { this->m_value = m_k; this->m_value -= epsilon; this->m_bound_kind = B_UPPER; } else { SASSERT(get_atom_kind() == A_UPPER); this->m_value = m_k; this->m_value += epsilon; this->m_bound_kind = B_LOWER; } } } // ----------------------------------- // // Auxiliary methods // // ----------------------------------- /** \brief Return the col_entry that points to a base row that contains the given variable. Return 0 if no row contains v. */ template typename theory_arith::col_entry const * theory_arith::get_a_base_row_that_contains(theory_var v) { while (true) { column const & c = m_columns[v]; if (c.size() == 0) return 0; int quasi_base_rid = -1; typename svector::const_iterator it = c.begin_entries(); typename svector::const_iterator end = c.end_entries(); for (; it != end; ++it) { if (!it->is_dead()) { unsigned rid = it->m_row_id; row & r = m_rows[rid]; if (is_base(r.get_base_var())) return it; else if (quasi_base_rid == -1) quasi_base_rid = rid; } } SASSERT(quasi_base_rid != -1); // since c.size() != 0 quasi_base_row2base_row(quasi_base_rid); // There is no guarantee that v is still a variable of row quasi_base_rid. // However, this loop will always terminate since I'm creating // a base row that contains v, or decreasing c.size(). } } /** \brief Return true if all coefficients of the given row are int. */ template bool theory_arith::all_coeff_int(row const & r) const { typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead() && !it->m_coeff.is_int()) TRACE("gomory_cut", display_row(tout, r, true);); return false; } return true; } /** \brief Return the col_entry that points to row that contains the given variable. This row should not be owned by an unconstrained quasi-base variable. Return 0 if failed. This method is used by move_unconstrained_to_base */ template typename theory_arith::col_entry const * theory_arith::get_row_for_eliminating(theory_var v) const { column const & c = m_columns[v]; if (c.size() == 0) return 0; typename svector::const_iterator it = c.begin_entries(); typename svector::const_iterator end = c.end_entries(); for (; it != end; ++it) { if (!it->is_dead()) { row const & r = m_rows[it->m_row_id]; theory_var s = r.get_base_var(); if (is_quasi_base(s) && m_var_occs[s].size() == 0) continue; if (is_int(v)) { numeral const & c = r[it->m_row_idx].m_coeff; // If c == 1 or c == -1, and all other coefficients of r are integer, // then if we pivot v with the base var of r, we will produce a row // that will guarantee an integer assignment for v, when the // non-base vars have integer assignment. if (!c.is_one() && !c.is_minus_one()) continue; if (!all_coeff_int(r)) continue; } return it; } } return 0; } template void theory_arith::move_unconstrained_to_base() { if (lazy_pivoting_lvl() == 0) return; TRACE("move_unconstrained_to_base", tout << "before...\n"; display(tout);); int num = get_num_vars(); for (theory_var v = 0; v < num; v++) { if (m_var_occs[v].size() == 0 && is_free(v)) { switch (get_var_kind(v)) { case QUASI_BASE: break; case BASE: if (is_int(v) && !all_coeff_int(m_rows[get_var_row(v)])) // If the row contains non integer coefficients, then v may be assigned // to a non-integer value even if all non-base variables are integer. // So, v should not be "eliminated" break; eliminate(v, m_eager_gcd); break; case NON_BASE: { col_entry const * entry = get_row_for_eliminating(v); if (entry) { TRACE("move_unconstrained_to_base", tout << "moving v" << v << " to the base\n";); row & r = m_rows[entry->m_row_id]; SASSERT(r[entry->m_row_idx].m_var == v); pivot(r.get_base_var(), v, r[entry->m_row_idx].m_coeff, m_eager_gcd); SASSERT(is_base(v)); set_var_kind(v, QUASI_BASE); SASSERT(is_quasi_base(v)); } break; } } } } TRACE("move_unconstrained_to_base", tout << "after...\n"; display(tout);); CASSERT("arith", wf_rows()); CASSERT("arith", wf_columns()); CASSERT("arith", valid_row_assignment()); } /** \brief Force all quasi_base rows to become base rows. */ template void theory_arith::elim_quasi_base_rows() { int num = get_num_vars(); for (theory_var v = 0; v < num; v++) { if (is_quasi_base(v)) { quasi_base_row2base_row(get_var_row(v)); } } } /** \brief Remove fixed vars from the base. */ template void theory_arith::remove_fixed_vars_from_base() { int num = get_num_vars(); for (theory_var v = 0; v < num; v++) { if (is_base(v) && is_fixed(v)) { row const & r = m_rows[get_var_row(v)]; typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead() && it->m_var != v && !is_fixed(it->m_var)) { break; } } if (it != end) { pivot(v, it->m_var, it->m_coeff, false); } } } } /** \brief Try to minimize the number of rational coefficients. The idea is to pivot x_i and x_j whenever there is a row x_i + 1/n * x_j + ... = 0 where - x_i is a base variable - x_j is a non-base variables - x_j is not a fixed variable - The denominator of any other coefficient a_ik divides n (I only consider the coefficient of non-fixed variables) remark if there are more than one variable with such properties, we give preference to free variables, then to variables where upper - lower is maximal. */ template void theory_arith::try_to_minimize_rational_coeffs() { int num = get_num_vars(); for (theory_var v = 0; v < num; v++) { if (!is_base(v) || !is_int(v)) continue; numeral max_den; row const & r = m_rows[get_var_row(v)]; typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (it->is_dead()) continue; if (it->m_var == v) continue; if (is_fixed(it->m_var)) continue; numeral num = numerator(it->m_coeff); if (!num.is_one() && !num.is_minus_one()) continue; numeral den = denominator(it->m_coeff); if (den > max_den) max_den = den; } if (max_den <= numeral(1)) continue; // check whether all a_ik denominators divide max_den it = r.begin_entries(); for (; it != end; ++it) { if (it->is_dead()) continue; if (is_fixed(it->m_var)) continue; numeral den = denominator(it->m_coeff); if (!(max_den / den).is_int()) break; } if (it != end) continue; // pick best candidate theory_var x_j = null_theory_var; numeral a_ij; it = r.begin_entries(); for (; it != end; ++it) { if (it->is_dead()) continue; if (it->m_var == v) continue; if (is_fixed(it->m_var)) continue; numeral num = numerator(it->m_coeff); if (!num.is_one() && !num.is_minus_one()) continue; numeral den = denominator(it->m_coeff); if (den != max_den) continue; if (x_j == null_theory_var || // TODO: add extra cases... is_free(it->m_var) || (is_bounded(x_j) && !is_bounded(it->m_var)) || (is_bounded(x_j) && is_bounded(it->m_var) && (upper_bound(x_j) - lower_bound(x_j) > upper_bound(it->m_var) - lower_bound(it->m_var)))) { x_j = it->m_var; a_ij = it->m_coeff; if (is_free(x_j)) break; } } if (x_j != null_theory_var) pivot(v, x_j, a_ij, false); } } // ----------------------------------- // // Derived bounds // // ----------------------------------- template void theory_arith::derived_bound::push_justification(antecedents& a, numeral const& coeff) { if (proofs_enabled) { for (unsigned i = 0; i < m_lits.size(); ++i) { a.push_lit(m_lits[i], coeff); } for (unsigned i = 0; i < m_eqs.size(); ++i) { a.push_eq(m_eqs[i], coeff); } } else { a.lits().append(m_lits.size(), m_lits.c_ptr()); a.eqs().append(m_eqs.size(), m_eqs.c_ptr()); } } template void theory_arith::justified_derived_bound::push_justification(antecedents& a, numeral const& coeff) { for (unsigned i = 0; i < this->m_lits.size(); ++i) { a.push_lit(this->m_lits[i], coeff*m_lit_coeffs[i]); } for (unsigned i = 0; i < this->m_eqs.size(); ++i) { a.push_eq(this->m_eqs[i], coeff*m_eq_coeffs[i]); } } template void theory_arith::justified_derived_bound::push_lit(literal l, numeral const& coeff) { for (unsigned i = 0; i < this->m_lits.size(); ++i) { if (this->m_lits[i] == l) { m_lit_coeffs[i] += coeff; return; } } this->m_lits.push_back(l); m_lit_coeffs.push_back(coeff); } template void theory_arith::justified_derived_bound::push_eq(enode_pair const& p, numeral const& coeff) { for (unsigned i = 0; i < this->m_eqs.size(); ++i) { if (this->m_eqs[i] == p) { m_eq_coeffs[i] += coeff; return; } } this->m_eqs.push_back(p); m_eq_coeffs.push_back(coeff); } /** \brief Copy the justification of b to new_bound. Only literals and equalities not in lits and eqs are copied. The justification of b is also copied to lits and eqs. */ template void theory_arith::accumulate_justification(bound & b, derived_bound& new_bound, numeral const& coeff, literal_idx_set & lits, eq_set & eqs) { antecedents& ante = m_tmp_antecedents; ante.reset(); b.push_justification(ante, coeff); unsigned num_lits = ante.lits().size(); for (unsigned i = 0; i < num_lits; ++i) { literal l = ante.lits()[i]; if (lits.contains(l.index())) continue; if (proofs_enabled) { new_bound.push_lit(l, ante.lit_coeffs()[i]); } else { new_bound.push_lit(l, numeral::zero()); lits.insert(l.index()); } } unsigned num_eqs = ante.eqs().size(); for (unsigned i = 0; i < num_eqs; ++i) { enode_pair const & p = ante.eqs()[i]; if (eqs.contains(p)) continue; if (proofs_enabled) { new_bound.push_eq(p, ante.eq_coeffs()[i]); } else { new_bound.push_eq(p, numeral::zero()); eqs.insert(p); } } } template typename theory_arith::inf_numeral theory_arith::normalize_bound(theory_var v, inf_numeral const & k, bound_kind kind) { if (is_real(v)) return k; if (kind == B_LOWER) return inf_numeral(ceil(k)); SASSERT(kind == B_UPPER); return inf_numeral(floor(k)); } /** \brief Create a derived bound for v using the given row as an explanation. */ template void theory_arith::mk_bound_from_row(theory_var v, inf_numeral const & k, bound_kind kind, row const & r) { inf_numeral k_norm = normalize_bound(v, k, kind); derived_bound * new_bound = proofs_enabled?alloc(justified_derived_bound, v, k_norm, kind):alloc(derived_bound, v, k_norm, kind); m_bounds_to_delete.push_back(new_bound); m_asserted_bounds.push_back(new_bound); m_tmp_lit_set.reset(); m_tmp_eq_set.reset(); #ifdef Z3DEBUG inf_numeral val; #endif typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead()) { theory_var v = it->m_var; bool use_upper = (kind == B_UPPER); if (!it->m_coeff.is_pos()) use_upper = !use_upper; TRACE("derived_bound", tout << "using " << (use_upper ? "upper" : "lower") << " bound of v" << v << "\n";); bound * b = get_bound(v, use_upper); SASSERT(b); DEBUG_CODE({ inf_numeral tmp = b->get_value(); tmp *= it->m_coeff; val += tmp; }); accumulate_justification(*b, *new_bound, it->m_coeff, m_tmp_lit_set, m_tmp_eq_set); } } TRACE("derived_bound", tout << "explanation:\n"; literal_vector::const_iterator it1 = new_bound->m_lits.begin(); literal_vector::const_iterator end1 = new_bound->m_lits.end(); for (; it1 != end1; ++it1) tout << *it1 << " "; tout << " "; eq_vector::const_iterator it2 = new_bound->m_eqs.begin(); eq_vector::const_iterator end2 = new_bound->m_eqs.end(); for (; it2 != end2; ++it2) tout << "#" << it2->first->get_owner_id() << "=#" << it2->second->get_owner_id() << " "; tout << "\n";); DEBUG_CODE(CTRACE("derived_bound", k != val, tout << "k: " << k << ", k_norm: " << k_norm << ", val: " << val << "\n";);); SASSERT(k == val); } // ----------------------------------- // // Maximization/Minimization // // ----------------------------------- /** \brief Set: row1 <- row1 + coeff * row2, where row1 is a temporary row. \remark Columns do not need to be updated when updating a temporary row. */ template void theory_arith::add_tmp_row(row & r1, numeral const & coeff, row const & r2) { r1.save_var_pos(m_var_pos); // // loop over variables in row2, // add terms in row2 to row1. // #define ADD_TMP_ROW(_SET_COEFF_, _ADD_COEFF_) \ typename vector::const_iterator it = r2.begin_entries(); \ typename vector::const_iterator end = r2.end_entries(); \ for (; it != end; ++it) { \ if (!it->is_dead()) { \ theory_var v = it->m_var; \ int pos = m_var_pos[v]; \ if (pos == -1) { \ /* variable v is not in row1 */ \ int row_idx; \ row_entry & r_entry = r1.add_row_entry(row_idx); \ r_entry.m_var = v; \ _SET_COEFF_; \ } \ else { \ /* variable v is in row1 */ \ row_entry & r_entry = r1[pos]; \ SASSERT(r_entry.m_var == v); \ _ADD_COEFF_; \ if (r_entry.m_coeff.is_zero()) { \ r1.del_row_entry(pos); \ } \ m_var_pos[v] = -1; \ } \ } \ } ((void) 0) if (coeff.is_one()) { ADD_TMP_ROW(r_entry.m_coeff = it->m_coeff, r_entry.m_coeff += it->m_coeff); } else if (coeff.is_minus_one()) { ADD_TMP_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff.neg(), r_entry.m_coeff -= it->m_coeff); } else { ADD_TMP_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff *= coeff, r_entry.m_coeff += it->m_coeff * coeff); } r1.reset_var_pos(m_var_pos); } /** \brief Select tightest variable x_i to pivot with x_j. The goal is to select a x_i such that the value of x_j is increased (decreased) if inc = true (inc = false), and the tableau remains feasible. Store the gain in x_j of the pivoting operation in 'gain'. Note the gain can be too much. That is, it may make x_i infeasible. In this case, instead of pivoting we move x_j to its upper bound (lower bound) when inc = true (inc = false). If no x_i imposes a restriction on x_j, then return null_theory_var. That is, x_j is free to move to its upper bound (lower bound). */ template theory_var theory_arith::pick_var_to_leave(theory_var x_j, bool inc, numeral & a_ij, inf_numeral & gain) { TRACE("maximize", tout << "selecting variable to replace v" << x_j << ", inc: " << inc << "\n";); theory_var x_i = null_theory_var; inf_numeral curr_gain; column & c = m_columns[x_j]; typename svector::iterator it = c.begin_entries(); typename svector::iterator end = c.end_entries(); for (; it != end; ++it) { if (!it->is_dead()) { row & r = m_rows[it->m_row_id]; theory_var s = r.get_base_var(); if (s != null_theory_var && !is_quasi_base(s)) { numeral const & coeff = r[it->m_row_idx].m_coeff; bool inc_s = coeff.is_neg() ? inc : !inc; bound * b = get_bound(s, inc_s); if (b) { curr_gain = get_value(s); curr_gain -= b->get_value(); curr_gain /= coeff; if (curr_gain.is_neg()) curr_gain.neg(); if (x_i == null_theory_var || (curr_gain < gain) || (gain.is_zero() && curr_gain.is_zero() && s < x_i)) { x_i = s; a_ij = coeff; gain = curr_gain; } } } TRACE("maximize", tout << "x_j: v" << x_i << ", gain: " << gain << "\n";); } } TRACE("maximize", tout << "x_i v" << x_i << "\n";); return x_i; } /** \brief Maximize (Minimize) the given temporary row. Return true if succeeded. */ template bool theory_arith::max_min(row & r, bool max) { TRACE("max_min", tout << "max_min...\n";); m_stats.m_max_min++; SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); theory_var x_i = null_theory_var; theory_var x_j = null_theory_var; bool inc = false; numeral a_ij, curr_a_ij, coeff, curr_coeff; inf_numeral curr_gain, gain; #ifdef _TRACE unsigned i = 0; #endif while (true) { x_j = null_theory_var; x_i = null_theory_var; gain.reset(); TRACE("maximize", tout << "i: " << i << ", max: " << max << "\n"; display_row(tout, r, true); tout << "state:\n"; display(tout); i++;); typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead()) { theory_var curr_x_j = it->m_var; SASSERT(is_non_base(curr_x_j)); curr_coeff = it->m_coeff; bool curr_inc = curr_coeff.is_pos() ? max : !max; if ((curr_inc && at_upper(curr_x_j)) || (!curr_inc && at_lower(curr_x_j))) continue; // variable cannot be used for max/min. theory_var curr_x_i = pick_var_to_leave(curr_x_j, curr_inc, curr_a_ij, curr_gain); if (curr_x_i == null_theory_var) { // we can increase/decrease curr_x_j as much as we want. x_i = null_theory_var; // unbounded x_j = curr_x_j; inc = curr_inc; break; } else if (curr_gain > gain) { x_i = curr_x_i; x_j = curr_x_j; a_ij = curr_a_ij; coeff = curr_coeff; gain = curr_gain; inc = curr_inc; } else if (curr_gain.is_zero() && (x_i == null_theory_var || curr_x_i < x_i)) { x_i = curr_x_i; x_j = curr_x_j; a_ij = curr_a_ij; coeff = curr_coeff; gain = curr_gain; inc = curr_inc; // continue } } } TRACE("maximize", tout << "after traversing row:\nx_i: v" << x_i << ", x_j: v" << x_j << ", gain: " << gain << "\n";); if (x_j == null_theory_var) { TRACE("maximize", tout << "row is " << (max ? "maximized" : "minimized") << "\n";); SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); return true; } if (x_i == null_theory_var) { // can increase/decrease x_j as much as we want. if (inc && upper(x_j)) { update_value(x_j, upper_bound(x_j) - get_value(x_j)); TRACE("maximize", tout << "moved v" << x_j << " to upper bound\n";); SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); continue; } if (!inc && lower(x_j)) { update_value(x_j, lower_bound(x_j) - get_value(x_j)); TRACE("maximize", tout << "moved v" << x_j << " to lower bound\n";); SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); continue; } return false; // unbounded. } if (!is_fixed(x_j) && is_bounded(x_j) && (upper_bound(x_j) - lower_bound(x_j) <= gain)) { // can increase/decrease x_j up to upper/lower bound. if (inc) { update_value(x_j, upper_bound(x_j) - get_value(x_j)); TRACE("maximize", tout << "moved v" << x_j << " to upper bound\n";); } else { update_value(x_j, lower_bound(x_j) - get_value(x_j)); TRACE("maximize", tout << "moved v" << x_j << " to lower bound\n";); } SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); continue; } TRACE("maximize", tout << "max: " << max << ", x_i: v" << x_i << ", x_j: v" << x_j << ", a_ij: " << a_ij << ", coeff: " << coeff << "\n";); bool move_xi_to_lower; if (inc) move_xi_to_lower = a_ij.is_pos(); else move_xi_to_lower = a_ij.is_neg(); pivot(x_i, x_j, a_ij, false); SASSERT(is_non_base(x_i)); SASSERT(is_base(x_j)); if (move_xi_to_lower) update_value(x_i, lower_bound(x_i) - get_value(x_i)); else update_value(x_i, upper_bound(x_i) - get_value(x_i)); row & r2 = m_rows[get_var_row(x_j)]; coeff.neg(); add_tmp_row(r, coeff, r2); SASSERT(r.get_idx_of(x_j) == -1); SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); } } /** \brief Add an entry to a temporary row. \remark Columns do not need to be updated when updating a temporary row. */ template template void theory_arith::add_tmp_row_entry(row & r, numeral const & coeff, theory_var v) { int r_idx; row_entry & r_entry = r.add_row_entry(r_idx); r_entry.m_var = v; r_entry.m_coeff = coeff; if (invert) r_entry.m_coeff .neg(); } /** \brief Maximize/Minimize the given variable. The bounds of v are update if procedure succeeds. */ template bool theory_arith::max_min(theory_var v, bool max) { TRACE("maximize", tout << (max ? "maximizing" : "minimizing") << " v" << v << "...\n";); SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); SASSERT(!is_quasi_base(v)); if ((max && at_upper(v)) || (!max && at_lower(v))) return false; // nothing to be done... m_tmp_row.reset(); if (is_non_base(v)) { add_tmp_row_entry(m_tmp_row, numeral(1), v); } else { row & r = m_rows[get_var_row(v)]; typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead() && it->m_var != v) add_tmp_row_entry(m_tmp_row, it->m_coeff, it->m_var); } } if (max_min(m_tmp_row, max)) { TRACE("maximize", tout << "v" << v << " " << (max ? "max" : "min") << " value is: " << get_value(v) << "\n"; display_row(tout, m_tmp_row, true); display_row_info(tout, m_tmp_row);); mk_bound_from_row(v, get_value(v), max ? B_UPPER : B_LOWER, m_tmp_row); return true; } return false; } /** \brief Maximize & Minimize variables in vars. Return false if an inconsistency was detected. */ template bool theory_arith::max_min(svector const & vars) { bool succ = false; svector::const_iterator it = vars.begin(); svector::const_iterator end = vars.end(); for (; it != end; ++it) { if (max_min(*it, true)) succ = true; if (max_min(*it, false)) succ = true; } if (succ) { // process new bounds bool r = propagate_core(); TRACE("maximize", tout << "after max/min round:\n"; display(tout);); return r; } return true; } // ----------------------------------- // // Freedom intervals // // ----------------------------------- /** \brief See Model-based theory combination paper. Return false if failed to build the freedom interval. \remark If x_j is an integer variable, then m will contain the lcm of the denominators of a_ij. We only consider the a_ij coefficients for x_i */ template bool theory_arith::get_freedom_interval(theory_var x_j, bool & inf_l, inf_numeral & l, bool & inf_u, inf_numeral & u, numeral & m) { if (is_base(x_j)) return false; inf_numeral const & x_j_val = get_value(x_j); column & c = m_columns[x_j]; typename svector::iterator it = c.begin_entries(); typename svector::iterator end = c.end_entries(); inf_l = true; inf_u = true; l.reset(); u.reset(); m = numeral(1); #define IS_FIXED() { if (!inf_l && !inf_u && l == u) goto fi_succeeded; } #define SET_LOWER(VAL) { inf_numeral const & _VAL = VAL; if (inf_l || _VAL > l) { l = _VAL; inf_l = false; } IS_FIXED(); } #define SET_UPPER(VAL) { inf_numeral const & _VAL = VAL; if (inf_u || _VAL < u) { u = _VAL; inf_u = false; } IS_FIXED(); } if (lower(x_j)) { SET_LOWER(lower_bound(x_j)); } if (upper(x_j)) { SET_UPPER(upper_bound(x_j)); } for (; it != end; ++it) { if (!it->is_dead()) { row & r = m_rows[it->m_row_id]; theory_var x_i = r.get_base_var(); if (x_i != null_theory_var && !is_quasi_base(x_i)) { numeral const & a_ij = r[it->m_row_idx].m_coeff; inf_numeral const & x_i_val = get_value(x_i); if (is_int(x_i) && is_int(x_j) && !a_ij.is_int()) m = lcm(m, denominator(a_ij)); bound * x_i_lower = lower(x_i); bound * x_i_upper = upper(x_i); if (a_ij.is_neg()) { if (x_i_lower) { inf_numeral new_l = x_j_val + ((x_i_val - x_i_lower->get_value()) / a_ij); SET_LOWER(new_l); } if (x_i_upper) { inf_numeral new_u = x_j_val + ((x_i_val - x_i_upper->get_value()) / a_ij); SET_UPPER(new_u); } } else { if (x_i_upper) { inf_numeral new_l = x_j_val + ((x_i_val - x_i_upper->get_value()) / a_ij); SET_LOWER(new_l); } if (x_i_lower) { inf_numeral new_u = x_j_val + ((x_i_val - x_i_lower->get_value()) / a_ij); SET_UPPER(new_u); } } } } } fi_succeeded: TRACE("freedom_interval", tout << "freedom variable for:\n"; display_var(tout, x_j); tout << "["; if (inf_l) tout << "-oo"; else tout << l; tout << "; "; if (inf_u) tout << "oo"; else tout << u; tout << "]\n";); return true; } // ----------------------------------- // // Implied eqs // // ----------------------------------- /** \brief Try to check whether v1 == v2 is implied by the current state. If it is return true. */ template bool theory_arith::try_to_imply_eq(theory_var v1, theory_var v2) { SASSERT(v1 != v2); SASSERT(get_value(v1) == get_value(v2)); SASSERT(valid_row_assignment()); SASSERT(satisfy_bounds()); if (is_quasi_base(v1) || is_quasi_base(v2)) return false; m_tmp_row.reset(); if (is_non_base(v1)) { add_tmp_row_entry(m_tmp_row, numeral(1), v1); } else { row & r = m_rows[get_var_row(v1)]; typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead() && it->m_var != v1) add_tmp_row_entry(m_tmp_row, it->m_coeff, it->m_var); } } m_tmp_row.save_var_pos(m_var_pos); #define ADD_ENTRY(COEFF, VAR) { \ int pos = m_var_pos[VAR]; \ if (pos == -1) { \ add_tmp_row_entry(m_tmp_row, COEFF, VAR); \ } \ else { \ row_entry & r_entry = m_tmp_row[pos]; \ SASSERT(r_entry.m_var == VAR); \ r_entry.m_coeff += COEFF; \ if (r_entry.m_coeff.is_zero()) \ m_tmp_row.del_row_entry(pos); \ m_var_pos[VAR] = -1; \ } \ } if (is_non_base(v2)) { ADD_ENTRY(numeral(-1), v2); } else { row & r = m_rows[get_var_row(v2)]; typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead() && it->m_var != v2) { numeral c = it->m_coeff; c.neg(); ADD_ENTRY(c, it->m_var); } } } m_tmp_row.reset_var_pos(m_var_pos); SASSERT(m_tmp_row.size() > 0); #if 0 TRACE("imply_eq", display_row_info(tout, m_tmp_row);); m_tmp_acc_lits.reset(); m_tmp_acc_eqs.reset(); m_tmp_lit_set.reset(); m_tmp_eq_set.reset(); if (max_min(m_tmp_row, true) && is_zero_row(m_tmp_row, true, m_tmp_acc_lits, m_tmp_acc_eqs, m_tmp_lit_set, m_tmp_eq_set) && max_min(m_tmp_row, false) && is_zero_row(m_tmp_row, false, m_tmp_acc_lits, m_tmp_acc_eqs, m_tmp_lit_set, m_tmp_eq_set)) { // v1 == v2 TRACE("imply_eq", tout << "found new implied equality:\n"; display_var(tout, v1); display_var(tout, v2);); // TODO: assert implied equality // return true; } #endif return false; } // ----------------------------------- // // Assume eqs // // The revamped assume eqs try to perturbate the // current assignment using pivoting operations. // // ----------------------------------- #define RANGE 10000 /** \brief Performs a random update on v using its freedom interval. Return true if it was possible to change. */ template bool theory_arith::random_update(theory_var v) { if (is_fixed(v) || !is_non_base(v)) return false; bool inf_l, inf_u; inf_numeral l, u; numeral m; get_freedom_interval(v, inf_l, l, inf_u, u, m); if (inf_l && inf_u) { inf_numeral new_val = inf_numeral(m_random() % (RANGE + 1)); set_value(v, new_val); return true; } if (is_int(v)) { if (!inf_l) { l = ceil(l); if (!m.is_one()) l = m*ceil(l/m); } if (!inf_u) { u = floor(u); if (!m.is_one()) u = m*floor(u/m); } } if (!inf_l && !inf_u && l >= u) return false; if (inf_u) { SASSERT(!inf_l); inf_numeral delta = inf_numeral(m_random() % (RANGE + 1)); inf_numeral new_val = l + m*delta; set_value(v, new_val); return true; } if (inf_l) { SASSERT(!inf_u); inf_numeral delta = inf_numeral(m_random() % (RANGE + 1)); inf_numeral new_val = u - m*delta; set_value(v, new_val); return true; } if (!is_int(v)) { SASSERT(!inf_l && !inf_u); numeral delta = numeral(m_random() % (RANGE + 1)); inf_numeral new_val = l + ((delta * (u - l)) / numeral(RANGE)); set_value(v, new_val); return true; } else { unsigned range = RANGE; numeral r = (u.get_rational() - l.get_rational()) / m; if (r < numeral(RANGE)) range = static_cast(r.get_uint64()); inf_numeral new_val = l + m * (inf_numeral(m_random() % (range + 1))); set_value(v, new_val); return true; } } template void theory_arith::mutate_assignment() { remove_fixed_vars_from_base(); int num_vars = get_num_vars(); m_var_value_table.reset(); m_tmp_var_set.reset(); sbuffer candidates; for (theory_var v = 0; v < num_vars; v++) { enode * n1 = get_enode(v); if (!is_relevant_and_shared(n1)) continue; theory_var other = m_var_value_table.insert_if_not_there(v); if (other == v) continue; // first node with the given value... enode * n2 = get_enode(other); if (n1->get_root() == n2->get_root()) continue; if (!is_fixed(v)) { candidates.push_back(v); } else if (!is_fixed(other) && !m_tmp_var_set.contains(other)) { m_tmp_var_set.insert(other); candidates.push_back(other); } } if (candidates.empty()) return; typename sbuffer::iterator it = candidates.begin(); typename sbuffer::iterator end = candidates.end(); m_tmp_var_set.reset(); m_tmp_var_set2.reset(); for (; it != end; ++it) { theory_var v = *it; SASSERT(!is_fixed(v)); if (is_base(v)) { row & r = m_rows[get_var_row(v)]; typename vector::const_iterator it2 = r.begin_entries(); typename vector::const_iterator end2 = r.end_entries(); for (; it2 != end2; ++it2) { if (!it2->is_dead() && it2->m_var != v && !is_fixed(it2->m_var) && random_update(it2->m_var)) break; } } else { random_update(v); } } SASSERT(m_to_patch.empty()); } /** \brief We must redefine this method, because theory of arithmetic contains underspecified operators such as division by 0. (/ a b) is essentially an uninterpreted function when b = 0. Thus, 'a' must be considered a shared var if it is the child of an underspecified operator. */ template bool theory_arith::is_shared(theory_var v) const { enode * n = get_enode(v); enode * r = n->get_root(); enode_vector::const_iterator it = r->begin_parents(); enode_vector::const_iterator end = r->end_parents(); for (; it != end; ++it) { enode * parent = *it; app * o = parent->get_owner(); if (o->get_family_id() == get_id()) { switch (o->get_decl_kind()) { case OP_DIV: case OP_IDIV: case OP_REM: case OP_MOD: return true; default: break; } } } return false; } template bool theory_arith::assume_eqs_core() { // See comment in m_liberal_final_check declaration if (m_liberal_final_check) mutate_assignment(); TRACE("assume_eq_int", display(tout);); unsigned old_sz = m_assume_eq_candidates.size(); TRACE("func_interp_bug", display(tout);); m_var_value_table.reset(); bool result = false; int num = get_num_vars(); for (theory_var v = 0; v < num; v++) { enode * n = get_enode(v); TRACE("func_interp_bug", tout << "#" << n->get_owner_id() << " -> " << m_value[v] << "\n";); if (!is_relevant_and_shared(n)) continue; theory_var other = null_theory_var; other = m_var_value_table.insert_if_not_there(v); if (other == v) continue; enode * n2 = get_enode(other); if (n->get_root() == n2->get_root()) continue; TRACE("func_interp_bug", tout << "adding to assume_eq queue #" << n->get_owner_id() << " #" << n2->get_owner_id() << "\n";); m_assume_eq_candidates.push_back(std::make_pair(other, v)); result = true; } if (result) get_context().push_trail(restore_size_trail, false>(m_assume_eq_candidates, old_sz)); return delayed_assume_eqs(); // return this->assume_eqs(m_var_value_table); } template bool theory_arith::delayed_assume_eqs() { if (m_assume_eq_head == m_assume_eq_candidates.size()) return false; get_context().push_trail(value_trail(m_assume_eq_head)); while (m_assume_eq_head < m_assume_eq_candidates.size()) { std::pair const & p = m_assume_eq_candidates[m_assume_eq_head]; theory_var v1 = p.first; theory_var v2 = p.second; m_assume_eq_head++; CTRACE("func_interp_bug", get_value(v1) == get_value(v2) && get_enode(v1)->get_root() != get_enode(v2)->get_root(), tout << "assuming eq: #" << get_enode(v1)->get_owner_id() << " = #" << get_enode(v2)->get_owner_id() << "\n";); if (get_value(v1) == get_value(v2) && get_enode(v1)->get_root() != get_enode(v2)->get_root() && assume_eq(get_enode(v1), get_enode(v2))) { return true; } } return false; } #if 0 /** \brief Check if the given row is implying a zero upper/lower bound. Accumulate the justification in the given vectors. Return true if it is. */ template bool theory_arith::is_zero_row(row const & r, bool upper, literal_vector & lit_vect, eq_vector & eq_vect, literal_idx_set & lits, eq_set & eqs) { inf_numeral val; typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (; it != end; ++it) { if (!it->is_dead()) { theory_var v = it->m_var; bool use_upper = upper; if (!it->m_coeff.is_pos()) use_upper = !use_upper; bound * b = get_bound(v, use_upper); inf_numeral tmp = b->get_value(); tmp *= it->m_coeff; val += tmp; SASSERT(b); acc_umulate_justification(*b, lit_vect, eq_vect, lits, eqs); } } return val.is_zero(); } #endif }; #endif /* _THEORY_ARITH_AUX_H_ */