/*++ Copyright (c) 2010 Microsoft Corporation and Arie Gurfinkel Module Name: spacer_qe_project.cpp Abstract: Simple projection function for real arithmetic based on Loos-W. Projection functions for arrays based on MBP Author: Nikolaj Bjorner (nbjorner) 2013-09-12 Anvesh Komuravelli Arie Gurfinkel Revision History: --*/ #include "ast/arith_decl_plugin.h" #include "ast/ast_pp.h" #include "ast/expr_functors.h" #include "ast/expr_substitution.h" #include "ast/ast_util.h" #include "ast/is_variable_test.h" #include "ast/rewriter/expr_replacer.h" #include "ast/rewriter/expr_safe_replace.h" #include "ast/rewriter/th_rewriter.h" #include "model/model_evaluator.h" #include "model/model_pp.h" #include "qe/qe.h" #include "qe/qe_lite.h" #include "muz/spacer/spacer_mev_array.h" #include "muz/spacer/spacer_qe_project.h" namespace spacer_qe { bool is_partial_eq (app* a); /** * \brief utility class for partial equalities * * A partial equality (a ==I b), for two arrays a,b and a finite set of indices I holds * iff (Forall i. i \not\in I => a[i] == b[i]); in other words, it is a * restricted form of the extensionality axiom * * using this class, we denote (a =I b) as f(a,b,i0,i1,...) * where f is an uninterpreted predicate with name PARTIAL_EQ and * I = {i0,i1,...} */ class peq { ast_manager& m; expr_ref m_lhs; expr_ref m_rhs; unsigned m_num_indices; expr_ref_vector m_diff_indices; func_decl_ref m_decl; // the partial equality declaration app_ref m_peq; // partial equality application app_ref m_eq; // equivalent std equality using def. of partial eq array_util m_arr_u; public: static const char* PARTIAL_EQ; peq (app* p, ast_manager& m); peq (expr* lhs, expr* rhs, unsigned num_indices, expr * const * diff_indices, ast_manager& m); void lhs (expr_ref& result); void rhs (expr_ref& result); void get_diff_indices (expr_ref_vector& result); void mk_peq (app_ref& result); void mk_eq (app_ref_vector& aux_consts, app_ref& result, bool stores_on_rhs = true); }; const char* peq::PARTIAL_EQ = "partial_eq"; peq::peq (app* p, ast_manager& m): m (m), m_lhs (p->get_arg (0), m), m_rhs (p->get_arg (1), m), m_num_indices (p->get_num_args ()-2), m_diff_indices (m), m_decl (p->get_decl (), m), m_peq (p, m), m_eq (m), m_arr_u (m) { VERIFY (is_partial_eq (p)); SASSERT (m_arr_u.is_array (m_lhs) && m_arr_u.is_array (m_rhs) && ast_eq_proc() (m_lhs->get_sort (), m_rhs->get_sort ())); for (unsigned i = 2; i < p->get_num_args (); i++) { m_diff_indices.push_back (p->get_arg (i)); } } peq::peq (expr* lhs, expr* rhs, unsigned num_indices, expr * const * diff_indices, ast_manager& m): m (m), m_lhs (lhs, m), m_rhs (rhs, m), m_num_indices (num_indices), m_diff_indices (m), m_decl (m), m_peq (m), m_eq (m), m_arr_u (m) { SASSERT (m_arr_u.is_array (lhs) && m_arr_u.is_array (rhs) && ast_eq_proc() (lhs->get_sort (), rhs->get_sort ())); ptr_vector sorts; sorts.push_back (m_lhs->get_sort ()); sorts.push_back (m_rhs->get_sort ()); for (unsigned i = 0; i < num_indices; i++) { sorts.push_back (diff_indices[i]->get_sort ()); m_diff_indices.push_back (diff_indices [i]); } m_decl = m.mk_func_decl (symbol (PARTIAL_EQ), sorts.size (), sorts.data (), m.mk_bool_sort ()); } void peq::lhs (expr_ref& result) { result = m_lhs; } void peq::rhs (expr_ref& result) { result = m_rhs; } void peq::get_diff_indices (expr_ref_vector& result) { for (unsigned i = 0; i < m_diff_indices.size (); i++) { result.push_back (m_diff_indices.get (i)); } } void peq::mk_peq (app_ref& result) { if (!m_peq) { ptr_vector args; args.push_back (m_lhs); args.push_back (m_rhs); for (unsigned i = 0; i < m_num_indices; i++) { args.push_back (m_diff_indices.get (i)); } m_peq = m.mk_app (m_decl, args.size (), args.data ()); } result = m_peq; } void peq::mk_eq (app_ref_vector& aux_consts, app_ref& result, bool stores_on_rhs) { if (!m_eq) { expr_ref lhs (m_lhs, m), rhs (m_rhs, m); if (!stores_on_rhs) { std::swap (lhs, rhs); } // lhs = (...(store (store rhs i0 v0) i1 v1)...) sort* val_sort = get_array_range (lhs->get_sort ()); expr_ref_vector::iterator end = m_diff_indices.end (); for (expr_ref_vector::iterator it = m_diff_indices.begin (); it != end; it++) { app* val = m.mk_fresh_const ("diff", val_sort); ptr_vector store_args; store_args.push_back (rhs); store_args.push_back (*it); store_args.push_back (val); rhs = m_arr_u.mk_store (store_args); aux_consts.push_back (val); } m_eq = m.mk_eq (lhs, rhs); } result = m_eq; } bool is_partial_eq (app* a) { return a->get_decl ()->get_name () == peq::PARTIAL_EQ; } } namespace spacer_qe { class is_relevant_default : public i_expr_pred { public: bool operator()(expr* e) override { return true; } }; class mk_atom_default : public qe::i_nnf_atom { public: void operator()(expr* e, bool pol, expr_ref& result) override { if (pol) result = e; else result = result.get_manager().mk_not(e); } }; class arith_project_util { ast_manager& m; arith_util a; th_rewriter m_rw; expr_ref_vector m_lits; expr_ref_vector m_terms; vector m_coeffs; vector m_divs; bool_vector m_strict; bool_vector m_eq; scoped_ptr m_var; bool is_linear(rational const& mul, expr* t, rational& c, expr_ref_vector& ts) { expr* t1, *t2; rational mul1; bool res = true; if (t == m_var->x()) { c += mul; } else if (a.is_mul(t, t1, t2) && a.is_numeral(t1, mul1)) { res = is_linear(mul* mul1, t2, c, ts); } else if (a.is_mul(t, t1, t2) && a.is_numeral(t2, mul1)) { res = is_linear(mul* mul1, t1, c, ts); } else if (a.is_add(t)) { app* ap = to_app(t); for (unsigned i = 0; res && i < ap->get_num_args(); ++i) { res = is_linear(mul, ap->get_arg(i), c, ts); } } else if (a.is_sub(t, t1, t2)) { res = is_linear(mul, t1, c, ts) && is_linear(-mul, t2, c, ts); } else if (a.is_uminus(t, t1)) { res = is_linear(-mul, t1, c, ts); } else if (a.is_numeral(t, mul1)) { ts.push_back(a.mk_numeral(mul*mul1, t->get_sort())); } else if ((*m_var)(t)) { IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(t, m) << "\n";); TRACE ("qe", tout << "Failed to project: " << mk_pp (t, m) << "\n";); res = false; } else if (mul.is_one()) { ts.push_back(t); } else { ts.push_back(a.mk_mul(a.mk_numeral(mul, t->get_sort()), t)); } return res; } // either an equality (cx + t = 0) or an inequality (cx + t <= 0) or a divisibility literal (d | cx + t) bool is_linear(expr* lit, rational& c, expr_ref& t, rational& d, bool& is_strict, bool& is_eq, bool& is_diseq) { SASSERT ((*m_var)(lit)); expr* e1, *e2; c.reset(); sort* s; expr_ref_vector ts(m); bool is_not = m.is_not(lit, lit); rational mul(1); if (is_not) { mul.neg(); } SASSERT(!m.is_not(lit)); if (a.is_le(lit, e1, e2) || a.is_ge(lit, e2, e1)) { if (!is_linear( mul, e1, c, ts) || !is_linear(-mul, e2, c, ts)) return false; s = e1->get_sort(); is_strict = is_not; } else if (a.is_lt(lit, e1, e2) || a.is_gt(lit, e2, e1)) { if (!is_linear( mul, e1, c, ts) || !is_linear(-mul, e2, c, ts)) return false; s = e1->get_sort(); is_strict = !is_not; } else if (m.is_eq(lit, e1, e2) && a.is_int_real (e1)) { expr *t, *num; rational num_val, d_val, z; bool is_int; if (a.is_mod (e1, t, num) && a.is_numeral (num, num_val, is_int) && is_int && a.is_numeral (e2, z) && z.is_zero ()) { // divsibility constraint: t % num == 0 <=> num | t if (num_val.is_zero ()) { IF_VERBOSE(1, verbose_stream() << "div by zero" << mk_pp(lit, m) << "\n";); return false; } d = num_val; if (!is_linear (mul, t, c, ts)) return false; } else if (a.is_mod (e2, t, num) && a.is_numeral (num, num_val, is_int) && is_int && a.is_numeral (e1, z) && z.is_zero ()) { // divsibility constraint: 0 == t % num <=> num | t if (num_val.is_zero ()) { IF_VERBOSE(1, verbose_stream() << "div by zero" << mk_pp(lit, m) << "\n";); return false; } d = num_val; if (!is_linear (mul, t, c, ts)) return false; } else { // equality or disequality if (!is_linear( mul, e1, c, ts) || !is_linear(-mul, e2, c, ts)) return false; if (is_not) is_diseq = true; else is_eq = true; } s = e1->get_sort(); } else { IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(lit, m) << "\n";); TRACE ("qe", tout << "Failed to project: " << mk_pp (lit, m) << "\n";); return false; } if (ts.empty()) { t = a.mk_numeral(rational(0), s); } else if (ts.size () == 1) { t = ts.get (0); } else { t = a.mk_add(ts.size(), ts.data()); } return true; } bool project(model& mdl, expr_ref_vector& lits) { unsigned num_pos = 0; unsigned num_neg = 0; bool use_eq = false; expr_ref_vector new_lits(m); expr_ref eq_term (m); m_lits.reset (); m_terms.reset(); m_coeffs.reset(); m_strict.reset(); m_eq.reset (); for (unsigned i = 0; i < lits.size(); ++i) { rational c(0), d(0); expr_ref t(m); bool is_strict = false; bool is_eq = false; bool is_diseq = false; if (!(*m_var)(lits.get (i))) { new_lits.push_back(lits.get (i)); continue; } if (is_linear(lits.get (i), c, t, d, is_strict, is_eq, is_diseq)) { if (c.is_zero()) { m_rw(lits.get (i), t); new_lits.push_back(t); } else if (is_eq) { if (!use_eq) { // c*x + t = 0 <=> x = -t/c eq_term = mk_mul (-(rational::one ()/c), t); use_eq = true; } m_lits.push_back (lits.get (i)); m_coeffs.push_back(c); m_terms.push_back(t); m_strict.push_back(false); m_eq.push_back (true); } else { if (is_diseq) { // c*x + t != 0 // find out whether c*x + t < 0, or c*x + t > 0 expr_ref cx (m), cxt (m), val (m); rational r; cx = mk_mul (c, m_var->x()); cxt = mk_add (cx, t); val = mdl(cxt); VERIFY(a.is_numeral(val, r)); SASSERT (r > rational::zero () || r < rational::zero ()); if (r > rational::zero ()) { c = -c; t = mk_mul (-(rational::one()), t); } is_strict = true; } m_lits.push_back (lits.get (i)); m_coeffs.push_back(c); m_terms.push_back(t); m_strict.push_back(is_strict); m_eq.push_back (false); if (c.is_pos()) { ++num_pos; } else { ++num_neg; } } } else return false; } if (use_eq) { TRACE ("qe", tout << "Using equality term: " << mk_pp (eq_term, m) << "\n"; ); // substitute eq_term for x everywhere for (unsigned i = 0; i < m_lits.size(); ++i) { expr_ref cx (m), cxt (m), z (m), result (m); cx = mk_mul (m_coeffs[i], eq_term); cxt = mk_add (cx, m_terms.get(i)); z = a.mk_numeral(rational(0), eq_term->get_sort()); if (m_eq[i]) { // c*x + t = 0 result = a.mk_eq (cxt, z); } else if (m_strict[i]) { // c*x + t < 0 result = a.mk_lt (cxt, z); } else { // c*x + t <= 0 result = a.mk_le (cxt, z); } m_rw (result); new_lits.push_back (result); } } lits.reset(); lits.append(new_lits); if (use_eq || num_pos == 0 || num_neg == 0) { return true; } bool use_pos = num_pos < num_neg; unsigned max_t = find_max(mdl, use_pos); expr_ref new_lit (m); for (unsigned i = 0; i < m_lits.size(); ++i) { if (i != max_t) { if (m_coeffs[i].is_pos() == use_pos) { new_lit = mk_le(i, max_t); } else { new_lit = mk_lt(i, max_t); } lits.push_back(new_lit); TRACE ("qe", tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; tout << "New literal: " << mk_pp (new_lit, m) << "\n"; ); } } return true; } bool project(model& mdl, app_ref_vector const& lits, expr_map& map, app_ref& div_lit) { unsigned num_pos = 0; // number of positive literals true in the model unsigned num_neg = 0; // number of negative literals true in the model m_lits.reset (); m_terms.reset(); m_coeffs.reset(); m_divs.reset (); m_strict.reset(); m_eq.reset (); expr_ref var_val = mdl(m_var->x()); unsigned eq_idx = lits.size (); for (unsigned i = 0; i < lits.size(); ++i) { rational c(0), d(0); expr_ref t(m); bool is_strict = false; bool is_eq = false; bool is_diseq = false; if (!(*m_var)(lits.get (i))) continue; if (is_linear(lits.get (i), c, t, d, is_strict, is_eq, is_diseq)) { TRACE ("qe", tout << "Literal: " << mk_pp (lits.get (i), m) << "\n"; ); if (c.is_zero()) { TRACE ("qe", tout << "independent of variable\n"; ); continue; } // evaluate c*x + t in the model expr_ref cx (m), cxt (m), val (m); rational r; cx = mk_mul (c, m_var->x()); cxt = mk_add (cx, t); val = mdl(cxt); VERIFY(a.is_numeral(val, r)); if (is_eq) { TRACE ("qe", tout << "equality term\n"; ); // check if the equality is true in the mdl if (eq_idx == lits.size () && r == rational::zero ()) { eq_idx = m_lits.size (); } m_lits.push_back (lits.get (i)); m_coeffs.push_back(c); m_terms.push_back(t); m_strict.push_back(false); m_eq.push_back (true); m_divs.push_back (d); } else { TRACE ("qe", tout << "not an equality term\n"; ); if (is_diseq) { // c*x + t != 0 // find out whether c*x + t < 0, or c*x + t > 0 if (r > rational::zero ()) { c = -c; t = mk_mul (-(rational::one()), t); r = -r; } // note: if the disequality is false in the model, // r==0 and we end up choosing c*x + t < 0 is_strict = true; } m_lits.push_back (lits.get (i)); m_coeffs.push_back(c); m_terms.push_back(t); m_strict.push_back(is_strict); m_eq.push_back (false); m_divs.push_back (d); if (d.is_zero ()) { // not a div term if ((is_strict && r < rational::zero ()) || (!is_strict && r <= rational::zero ())) { // literal true in the model if (c.is_pos()) { ++num_pos; } else { ++num_neg; } } } } TRACE ("qe", tout << "c: " << c << "\n"; tout << "t: " << mk_pp (t, m) << "\n"; tout << "d: " << d << "\n"; ); } else return false; } rational lcm_coeffs (1), lcm_divs (1); if (a.is_int (m_var->x())) { // lcm of (absolute values of) coeffs for (unsigned i = 0; i < m_lits.size (); i++) { lcm_coeffs = lcm (lcm_coeffs, abs (m_coeffs[i])); } // normalize coeffs of x to +/-lcm_coeffs and scale terms and divs appropriately; // find lcm of scaled-up divs for (unsigned i = 0; i < m_lits.size (); i++) { rational factor (lcm_coeffs / abs(m_coeffs[i])); if (!factor.is_one () && !a.is_zero (m_terms.get (i))) m_terms[i] = a.mk_mul (a.mk_numeral (factor, a.mk_int ()), m_terms.get (i)); m_coeffs[i] = (m_coeffs[i].is_pos () ? lcm_coeffs : -lcm_coeffs); if (!m_divs[i].is_zero ()) { m_divs[i] *= factor; lcm_divs = lcm (lcm_divs, m_divs[i]); } TRACE ("qe", tout << "normalized coeff: " << m_coeffs[i] << "\n"; tout << "normalized term: " << mk_pp (m_terms.get (i), m) << "\n"; tout << "normalized div: " << m_divs[i] << "\n"; ); } // consider new divisibility literal (lcm_coeffs | (lcm_coeffs * x)) lcm_divs = lcm (lcm_divs, lcm_coeffs); TRACE ("qe", tout << "lcm of coeffs: " << lcm_coeffs << "\n"; tout << "lcm of divs: " << lcm_divs << "\n"; ); } expr_ref z (a.mk_numeral (rational::zero (), a.mk_int ()), m); expr_ref x_term_val (m); // use equality term if (eq_idx < lits.size ()) { if (a.is_real (m_var->x ())) { // c*x + t = 0 <=> x = -t/c expr_ref eq_term (mk_mul (-(rational::one ()/m_coeffs[eq_idx]), m_terms.get (eq_idx)), m); m_rw (eq_term); map.insert (m_var->x (), eq_term, nullptr); TRACE ("qe", tout << "Using equality term: " << mk_pp (eq_term, m) << "\n"; ); } else { // find substitution term for (lcm_coeffs * x) if (m_coeffs[eq_idx].is_pos ()) { x_term_val = a.mk_uminus (m_terms.get (eq_idx)); } else { x_term_val = m_terms.get (eq_idx); } m_rw (x_term_val); TRACE ("qe", tout << "Using equality literal: " << mk_pp (m_lits.get (eq_idx), m) << "\n"; tout << "substitution for (lcm_coeffs * x): " << mk_pp (x_term_val, m) << "\n"; ); // can't simply substitute for x; need to explicitly substitute the lits mk_lit_substitutes (x_term_val, map, eq_idx); if (!lcm_coeffs.is_one ()) { // new div constraint: lcm_coeffs | x_term_val div_lit = m.mk_eq (a.mk_mod (x_term_val, a.mk_numeral (lcm_coeffs, a.mk_int ())), z); } } return true; } expr_ref new_lit (m); if (num_pos == 0 || num_neg == 0) { TRACE ("qe", if (num_pos == 0) { tout << "virtual substitution with +infinity\n"; } else { tout << "virtual substitution with -infinity\n"; } ); /** * make all equalities false; * if num_pos = 0 (num_neg = 0), make all positive (negative) inequalities false; * make the rest inequalities true; * substitute value of x under given model for the rest (div terms) */ if (a.is_int (m_var->x())) { // to substitute for (lcm_coeffs * x), it suffices to pick // some element in the congruence class of (lcm_coeffs * x) mod lcm_divs; // simply substituting var_val for x in the literals does this job; // but to keep constants small, we use (lcm_coeffs * var_val) % lcm_divs instead rational var_val_num; VERIFY (a.is_numeral (var_val, var_val_num)); x_term_val = a.mk_numeral (mod (lcm_coeffs * var_val_num, lcm_divs), a.mk_int ()); TRACE ("qe", tout << "Substitution for (lcm_coeffs * x): " << mk_pp (x_term_val, m) << "\n"; ); } for (unsigned i = 0; i < m_lits.size (); i++) { if (!m_divs[i].is_zero ()) { // m_divs[i] | (x_term_val + m_terms[i]) // -- x_term_val is the absolute value, negate it if needed if (m_coeffs.get (i).is_pos ()) new_lit = a.mk_add (m_terms.get (i), x_term_val); else new_lit = a.mk_add (m_terms.get (i), a.mk_uminus (x_term_val)); // XXX Our handling of divisibility constraints is very fragile. // XXX Rewrite before applying divisibility to preserve syntactic structure m_rw(new_lit); new_lit = m.mk_eq (a.mk_mod (new_lit, a.mk_numeral (m_divs[i], a.mk_int ())), z); } else if (m_eq[i] || (num_pos == 0 && m_coeffs[i].is_pos ()) || (num_neg == 0 && m_coeffs[i].is_neg ())) { new_lit = m.mk_false (); } else { new_lit = m.mk_true (); } map.insert (m_lits.get (i), new_lit, nullptr); TRACE ("qe", tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; tout << "New literal: " << mk_pp (new_lit, m) << "\n"; ); } return true; } bool use_pos = num_pos < num_neg; // pick a side; both are sound unsigned max_t = find_max(mdl, use_pos); TRACE ("qe", if (use_pos) { tout << "virtual substitution with upper bound:\n"; } else { tout << "virtual substitution with lower bound:\n"; } tout << "test point: " << mk_pp (m_lits.get (max_t), m) << "\n"; tout << "coeff: " << m_coeffs[max_t] << "\n"; tout << "term: " << mk_pp (m_terms.get (max_t), m) << "\n"; tout << "is_strict: " << m_strict[max_t] << "\n"; ); if (a.is_real (m_var->x ())) { for (unsigned i = 0; i < m_lits.size(); ++i) { if (i != max_t) { if (m_eq[i]) { if (!m_strict[max_t]) { new_lit = mk_eq (i, max_t); } else { new_lit = m.mk_false (); } } else if (m_coeffs[i].is_pos() == use_pos) { new_lit = mk_le (i, max_t); } else { new_lit = mk_lt (i, max_t); } } else { new_lit = m.mk_true (); } map.insert (m_lits.get (i), new_lit, nullptr); TRACE ("qe", tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; tout << "New literal: " << mk_pp (new_lit, m) << "\n"; ); } } else { SASSERT (a.is_int (m_var->x ())); // mk substitution term for (lcm_coeffs * x) // evaluate c*x + t for the literal at max_t expr_ref cx (m), cxt (m), val (m); rational r; cx = mk_mul (m_coeffs[max_t], m_var->x()); cxt = mk_add (cx, m_terms.get (max_t)); val = mdl(cxt); VERIFY(a.is_numeral(val, r)); // get the offset from the smallest/largest possible value for x // literal smallest/largest val of x // ------- -------------------------- // l < x l+1 // l <= x l // x < u u-1 // x <= u u rational offset; if (m_strict[max_t]) { offset = abs(r) - rational::one (); } else { offset = abs(r); } // obtain the offset modulo lcm_divs offset %= lcm_divs; // for strict negative literal (i.e. strict lower bound), // substitution term is (t+1+offset); for non-strict, it's (t+offset) // // for positive term, subtract from 0 x_term_val = mk_add (m_terms.get (max_t), a.mk_numeral (offset, a.mk_int ())); if (m_strict[max_t]) { x_term_val = a.mk_add (x_term_val, a.mk_numeral (rational::one(), a.mk_int ())); } if (m_coeffs[max_t].is_pos ()) { x_term_val = a.mk_uminus (x_term_val); } m_rw (x_term_val); TRACE ("qe", tout << "substitution for (lcm_coeffs * x): " << mk_pp (x_term_val, m) << "\n"; ); // obtain substitutions for all literals in map mk_lit_substitutes (x_term_val, map, max_t); if (!lcm_coeffs.is_one ()) { // new div constraint: lcm_coeffs | x_term_val div_lit = m.mk_eq (a.mk_mod (x_term_val, a.mk_numeral (lcm_coeffs, a.mk_int ())), z); } } return true; } unsigned find_max(model& mdl, bool do_pos) { unsigned result = UINT_MAX; bool found = false; bool found_strict = false; rational found_val (0), r, r_plus_x, found_c; expr_ref val(m); // evaluate x in mdl rational r_x; val = mdl(m_var->x ()); VERIFY(a.is_numeral (val, r_x)); for (unsigned i = 0; i < m_terms.size(); ++i) { rational const& ac = m_coeffs[i]; if (!m_eq[i] && ac.is_pos() == do_pos) { val = mdl(m_terms.get (i)); VERIFY(a.is_numeral(val, r)); r /= abs(ac); // skip the literal if false in the model if (do_pos) { r_plus_x = r + r_x; } else { r_plus_x = r - r_x; } if (!((m_strict[i] && r_plus_x < rational::zero ()) || (!m_strict[i] && r_plus_x <= rational::zero ()))) { continue; } IF_VERBOSE(2, verbose_stream() << "max: " << mk_pp(m_terms.get (i), m) << " " << r << " " << (!found || r > found_val || (r == found_val && !found_strict && m_strict[i])) << "\n";); if (!found || r > found_val || (r == found_val && !found_strict && m_strict[i])) { result = i; found_val = r; found_c = ac; found = true; found_strict = m_strict[i]; } } } SASSERT(found); return result; } // ax + t <= 0 // bx + s <= 0 // a and b have different signs. // Infer: a|b|x + |b|t + |a|bx + |a|s <= 0 // e.g. |b|t + |a|s <= 0 expr_ref mk_lt(unsigned i, unsigned j) { rational const& ac = m_coeffs[i]; rational const& bc = m_coeffs[j]; SASSERT(ac.is_pos() != bc.is_pos()); SASSERT(ac.is_neg() != bc.is_neg()); expr_ref bt (m), as (m), ts (m), z (m); expr* t = m_terms.get (i); expr* s = m_terms.get (j); bt = mk_mul(abs(bc), t); as = mk_mul(abs(ac), s); ts = mk_add(bt, as); z = a.mk_numeral(rational(0), t->get_sort()); expr_ref result1(m), result2(m); if (m_strict[i] || m_strict[j]) { result1 = a.mk_lt(ts, z); } else { result1 = a.mk_le(ts, z); } m_rw(result1, result2); return result2; } // ax + t <= 0 // bx + s <= 0 // a and b have same signs. // encode:// t/|a| <= s/|b| // e.g. |b|t <= |a|s expr_ref mk_le(unsigned i, unsigned j) { rational const& ac = m_coeffs[i]; rational const& bc = m_coeffs[j]; SASSERT(ac.is_pos() == bc.is_pos()); SASSERT(ac.is_neg() == bc.is_neg()); expr_ref bt (m), as (m); expr* t = m_terms.get (i); expr* s = m_terms.get (j); bt = mk_mul(abs(bc), t); as = mk_mul(abs(ac), s); expr_ref result1(m), result2(m); if (!m_strict[j] && m_strict[i]) { result1 = a.mk_lt(bt, as); } else { result1 = a.mk_le(bt, as); } m_rw(result1, result2); return result2; } // ax + t = 0 // bx + s <= 0 // replace equality by (-t/a == -s/b), or, as = bt expr_ref mk_eq (unsigned i, unsigned j) { expr_ref as (m), bt (m); as = mk_mul (m_coeffs[i], m_terms.get (j)); bt = mk_mul (m_coeffs[j], m_terms.get (i)); expr_ref result (m); result = m.mk_eq (as, bt); m_rw (result); return result; } expr* mk_add(expr* t1, expr* t2) { return a.mk_add(t1, t2); } expr* mk_mul(rational const& r, expr* t2) { expr* t1 = a.mk_numeral(r, t2->get_sort()); return a.mk_mul(t1, t2); } /** * walk the ast of fml and introduce a fresh variable for every mod term * (updating the mdl accordingly) */ void factor_mod_terms (expr_ref& fml, app_ref_vector& vars, model& mdl) { expr_ref_vector todo (m), eqs (m); expr_map factored_terms (m); ast_mark done; todo.push_back (fml); while (!todo.empty ()) { expr* e = todo.back (); if (!is_app (e) || done.is_marked (e)) { todo.pop_back (); continue; } app* ap = to_app (e); unsigned num_args = ap->get_num_args (); bool all_done = true, changed = false; expr_ref_vector args (m); for (unsigned i = 0; i < num_args; i++) { expr* old_arg = ap->get_arg (i); if (!done.is_marked (old_arg)) { todo.push_back (old_arg); all_done = false; } if (!all_done) continue; // all args so far have been processed // get the correct arg to use proof* pr = nullptr; expr* new_arg = nullptr; factored_terms.get (old_arg, new_arg, pr); if (new_arg) { // changed args.push_back (new_arg); changed = true; } else { // not changed args.push_back (old_arg); } } if (all_done) { // all args processed; make new term func_decl* d = ap->get_decl (); expr_ref new_term (m); new_term = m.mk_app (d, args.size (), args.data ()); // check for mod and introduce new var if (a.is_mod (ap)) { app_ref new_var (m); new_var = m.mk_fresh_const ("mod_var", d->get_range ()); eqs.push_back (m.mk_eq (new_var, new_term)); // obtain value of new_term in mdl expr_ref val = mdl(new_term); // use the variable from now on new_term = new_var; changed = true; // update vars and mdl vars.push_back (new_var); mdl.register_decl (new_var->get_decl (), val); } if (changed) { factored_terms.insert (e, new_term, nullptr); } done.mark (e, true); todo.pop_back (); } } // mk new fml proof* pr = nullptr; expr* new_fml = nullptr; factored_terms.get (fml, new_fml, pr); if (new_fml) { fml = new_fml; // add in eqs fml = m.mk_and (fml, m.mk_and (eqs.size (), eqs.data ())); } else { // unchanged SASSERT (eqs.empty ()); } } /** * factor out mod terms by using divisibility terms; * * for now, only handle mod equalities of the form (t1 % num == t2), * replacing it by the equivalent (num | (t1-t2)) /\ (0 <= t2 < abs(num)); * the divisibility atom is a special mod term ((t1-t2) % num == 0) */ void mod2div (expr_ref& fml, expr_map& map) { expr* new_fml = nullptr; proof *pr = nullptr; map.get (fml, new_fml, pr); if (new_fml) { fml = new_fml; return; } expr_ref z (a.mk_numeral (rational::zero (), a.mk_int ()), m); bool is_mod_eq = false; expr *e1, *e2, *num; expr_ref t1 (m), t2 (m); rational num_val; bool is_int; // check if fml is a mod equality (t1 % num) == t2 if (m.is_eq (fml, e1, e2)) { expr* t; if (a.is_mod (e1, t, num) && a.is_numeral (num, num_val, is_int) && is_int) { t1 = t; t2 = e2; is_mod_eq = true; } else if (a.is_mod (e2, t, num) && a.is_numeral (num, num_val, is_int) && is_int) { t1 = t; t2 = e1; is_mod_eq = true; } } if (is_mod_eq) { // recursively mod2div for t1 and t2 mod2div (t1, map); mod2div (t2, map); rational t2_num; if (a.is_numeral (t2, t2_num) && t2_num.is_zero ()) { // already in the desired form; // new_fml is (num_val | t1) new_fml = m.mk_eq (a.mk_mod (t1, a.mk_numeral (num_val, a.mk_int ())), z); } else { expr_ref_vector lits (m); // num_val | (t1 - t2) lits.push_back (m.mk_eq (a.mk_mod (a.mk_sub (t1, t2), a.mk_numeral (num_val, a.mk_int ())), z)); // 0 <= t2 lits.push_back (a.mk_le (z, t2)); // t2 < abs (num_val) lits.push_back (a.mk_lt (t2, a.mk_numeral (abs (num_val), a.mk_int ()))); new_fml = m.mk_and (lits.size (), lits.data ()); } } else if (!is_app (fml)) { new_fml = fml; } else { app* a = to_app (fml); expr_ref_vector children (m); expr_ref ch (m); for (unsigned i = 0; i < a->get_num_args (); i++) { ch = a->get_arg (i); mod2div (ch, map); children.push_back (ch); } new_fml = m.mk_app (a->get_decl (), children.size (), children.data ()); } map.insert (fml, new_fml, nullptr); fml = new_fml; } void collect_lits (expr* fml, app_ref_vector& lits) { expr_ref_vector todo (m); ast_mark visited; todo.push_back(fml); while (!todo.empty()) { expr* e = todo.back(); todo.pop_back(); if (visited.is_marked(e)) { continue; } visited.mark(e, true); if (!is_app(e)) { continue; } app* a = to_app(e); if (m.is_and(a) || m.is_or(a)) { for (unsigned i = 0; i < a->get_num_args(); ++i) { todo.push_back(a->get_arg(i)); } } else { lits.push_back (a); } } SASSERT(todo.empty()); visited.reset(); } /** * assume that all coeffs of x are the same, say c * substitute x_term_val for (c*x) in all lits and update map * make the literal at idx true */ void mk_lit_substitutes (expr_ref const& x_term_val, expr_map& map, unsigned idx) { expr_ref z (a.mk_numeral (rational::zero (), a.mk_int ()), m); expr_ref cxt (m), new_lit (m); for (unsigned i = 0; i < m_lits.size(); ++i) { if (i == idx) { new_lit = m.mk_true (); } else { // cxt if (m_coeffs[i].is_neg ()) { cxt = a.mk_sub (m_terms.get (i), x_term_val); } else { cxt = a.mk_add (m_terms.get (i), x_term_val); } if (m_divs[i].is_zero ()) { if (m_eq[i]) { new_lit = m.mk_eq (cxt, z); } else if (m_strict[i]) { new_lit = a.mk_lt (cxt, z); } else { new_lit = a.mk_le (cxt, z); } m_rw(new_lit); } else { // div term // XXX rewrite before applying mod to ensure mod is the top-level operator m_rw(cxt); new_lit = m.mk_eq (a.mk_mod (cxt, a.mk_numeral (m_divs[i], a.mk_int ())), z); } } map.insert (m_lits.get (i), new_lit, nullptr); TRACE ("qe", tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; tout << "New literal: " << mk_pp (new_lit, m) << "\n"; ); } } void substitute (expr_ref& fml, app_ref_vector& lits, expr_map& map) { expr_substitution sub (m); // literals for (unsigned i = 0; i < lits.size (); i++) { expr* new_lit = nullptr; proof* pr = nullptr; app* old_lit = lits.get (i); map.get (old_lit, new_lit, pr); if (new_lit) { sub.insert (old_lit, new_lit); TRACE ("qe", tout << "old lit " << mk_pp (old_lit, m) << "\n"; tout << "new lit " << mk_pp (new_lit, m) << "\n"; ); } } // substitute for x, if any expr* x_term = nullptr; proof* pr = nullptr; map.get (m_var->x (), x_term, pr); if (x_term) { sub.insert (m_var->x (), x_term); TRACE ("qe", tout << "substituting " << mk_pp (m_var->x (), m) << " by " << mk_pp (x_term, m) << "\n"; ); } scoped_ptr rep = mk_default_expr_replacer (m, false); rep->set_substitution (&sub); (*rep)(fml); } public: arith_project_util(ast_manager& m): m(m), a(m), m_rw(m), m_lits (m), m_terms (m) {} // OLD AND UNUSED INTERFACE expr_ref operator()(model& mdl, app_ref_vector& vars, expr_ref_vector const& lits) { app_ref_vector new_vars(m); expr_ref_vector result(lits); for (unsigned i = 0; i < vars.size(); ++i) { app* v = vars.get (i); m_var = alloc(contains_app, m, v); bool fail = a.is_int (v) || !project (mdl, result); if (fail) new_vars.push_back (v); IF_VERBOSE(2, if (fail) { verbose_stream() << "can't project:" << mk_pp(v, m) << "\n"; } ); TRACE("qe", if (!fail) { tout << "projected: " << mk_pp(v, m) << "\n"; for (unsigned i = 0; i < result.size(); ++i) { tout << mk_pp(result.get (i), m) << "\n"; } } else { tout << "Failed to project: " << mk_pp (v, m) << "\n"; } ); } vars.reset(); vars.append(new_vars); return mk_and(result); } void operator()(model& mdl, app_ref_vector& vars, expr_ref& fml) { expr_map map (m); operator()(mdl, vars, fml, map); } void operator()(model& mdl, app_ref_vector& vars, expr_ref& fml, expr_map& map) { app_ref_vector new_vars(m); // factor out mod terms by introducing new variables TRACE ("qe", tout << "before factoring out mod terms:" << "\n"; tout << mk_pp (fml, m) << "\n"; tout << "mdl:\n"; model_pp (tout, mdl); tout << "\n"; ); factor_mod_terms (fml, vars, mdl); TRACE ("qe", tout << "after factoring out mod terms:" << "\n"; tout << mk_pp (fml, m) << "\n"; tout << "updated mdl:\n"; model_pp (tout, mdl); tout << "\n"; ); app_ref_vector lits (m); // expr_map map (m); for (unsigned i = 0; i < vars.size(); ++i) { app* v = vars.get (i); TRACE ("qe", tout << "projecting variable: " << mk_pp (v, m) << "\n"; ); m_var = alloc(contains_app, m, v); map.reset (); lits.reset (); if (a.is_int (v)) { // factor out mod terms using div terms expr_map mod_map (m); mod2div (fml, mod_map); TRACE ("qe", tout << "after mod2div:" << "\n"; tout << mk_pp (fml, m) << "\n"; ); } collect_lits (fml, lits); app_ref div_lit (m); if (project (mdl, lits, map, div_lit)) { substitute (fml, lits, map); if (div_lit) { fml = m.mk_and (fml, div_lit); } TRACE("qe", tout << "projected: " << mk_pp(v, m) << " " << mk_pp(fml, m) << "\n"; ); } else { IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(v, m) << "\n";); TRACE ("qe", tout << "Failed to project: " << mk_pp (v, m) << "\n";); new_vars.push_back(v); } } vars.reset(); vars.append(new_vars); m_rw (fml); } }; class array_project_eqs_util { ast_manager& m; array_util m_arr_u; model_ref M; app_ref m_v; // array var to eliminate ast_mark m_has_stores_v; // has stores for m_v expr_ref m_subst_term_v; // subst term for m_v expr_safe_replace m_true_sub_v; // subst for true equalities expr_safe_replace m_false_sub_v; // subst for false equalities expr_ref_vector m_aux_lits_v; expr_ref_vector m_idx_lits_v; app_ref_vector m_aux_vars; model_evaluator_array_util m_mev; void reset_v () { m_v = nullptr; m_has_stores_v.reset (); m_subst_term_v = nullptr; m_true_sub_v.reset (); m_false_sub_v.reset (); m_aux_lits_v.reset (); m_idx_lits_v.reset (); } void reset () { M = nullptr; reset_v (); m_aux_vars.reset (); } /** * find all array equalities on m_v or containing stores on/of m_v * * also mark terms containing stores on/of m_v */ void find_arr_eqs (expr_ref const& fml, expr_ref_vector& eqs) { if (!is_app (fml)) return; ast_mark done; ptr_vector todo; todo.push_back (to_app (fml)); while (!todo.empty ()) { app* a = todo.back (); if (done.is_marked (a)) { todo.pop_back (); continue; } bool all_done = true; bool args_have_stores = false; unsigned num_args = a->get_num_args (); for (unsigned i = 0; i < num_args; i++) { expr* arg = a->get_arg (i); if (!is_app (arg)) continue; if (!done.is_marked (arg)) { all_done = false; todo.push_back (to_app (arg)); } else if (!args_have_stores && m_has_stores_v.is_marked (arg)) { args_have_stores = true; } } if (!all_done) continue; todo.pop_back (); // mark if a has stores if ((!m_arr_u.is_select (a) && args_have_stores) || (m_arr_u.is_store (a) && (a->get_arg (0) == m_v))) { m_has_stores_v.mark (a, true); TRACE ("qe", tout << "has stores:\n"; tout << mk_pp (a, m) << "\n"; ); } // check if a is a relevant array equality if (m.is_eq (a)) { expr* a0 = to_app (a)->get_arg (0); expr* a1 = to_app (a)->get_arg (1); if (a0 == m_v || a1 == m_v || (m_arr_u.is_array (a0) && m_has_stores_v.is_marked (a))) { eqs.push_back (a); } } // else, we can check for disequalities and handle them using extensionality, // but it's not necessary done.mark (a, true); } } /** * factor out select terms on m_v using fresh consts */ void factor_selects (app_ref& fml) { expr_map sel_cache (m); ast_mark done; ptr_vector todo; expr_ref_vector pinned (m); // to ensure a reference todo.push_back (fml); while (!todo.empty ()) { app* a = todo.back (); if (done.is_marked (a)) { todo.pop_back (); continue; } expr_ref_vector args (m); bool all_done = true; for (unsigned i = 0; i < a->get_num_args (); i++) { expr* arg = a->get_arg (i); if (!is_app (arg)) continue; if (!done.is_marked (arg)) { all_done = false; todo.push_back (to_app (arg)); } else if (all_done) { // all done so far.. expr* arg_new = nullptr; proof* pr; sel_cache.get (arg, arg_new, pr); if (!arg_new) { arg_new = arg; } args.push_back (arg_new); } } if (!all_done) continue; todo.pop_back (); expr_ref a_new (m.mk_app (a->get_decl (), args.size (), args.data ()), m); // if a_new is select on m_v, introduce new constant if (m_arr_u.is_select (a) && (args.get (0) == m_v || m_has_stores_v.is_marked (args.get (0)))) { sort* val_sort = get_array_range (m_v->get_sort()); app_ref val_const (m.mk_fresh_const ("sel", val_sort), m); m_aux_vars.push_back (val_const); // extend M to include val_const expr_ref val(m); m_mev.eval (*M, a_new, val); M->register_decl (val_const->get_decl (), val); // add equality m_aux_lits_v.push_back (m.mk_eq (val_const, a_new)); // replace select by const a_new = val_const; } if (a != a_new) { sel_cache.insert (a, a_new, nullptr); pinned.push_back (a_new); } done.mark (a, true); } expr* res = nullptr; proof* pr; sel_cache.get (fml, res, pr); if (res) { fml = to_app (res); } } /** * convert partial equality expression p_exp to an equality by * recursively adding stores on diff indices * * add stores on lhs or rhs depending on whether stores_on_rhs is false/true */ void convert_peq_to_eq (expr* p_exp, app_ref& eq, bool stores_on_rhs = true) { peq p (to_app (p_exp), m); app_ref_vector diff_val_consts (m); p.mk_eq (diff_val_consts, eq, stores_on_rhs); m_aux_vars.append (diff_val_consts); // extend M to include diff_val_consts expr_ref arr (m); expr_ref_vector I (m); p.lhs (arr); p.get_diff_indices (I); expr_ref val (m); unsigned num_diff = diff_val_consts.size (); SASSERT (num_diff == I.size ()); for (unsigned i = 0; i < num_diff; i++) { // mk val term ptr_vector sel_args; sel_args.push_back (arr); sel_args.push_back (I.get (i)); expr_ref val_term (m_arr_u.mk_select (sel_args.size (), sel_args.data ()), m); // evaluate and assign to ith diff_val_const m_mev.eval (*M, val_term, val); M->register_decl (diff_val_consts.get (i)->get_decl (), val); } } /** * mk (e0 ==indices e1) * * result has stores if either e0 or e1 or an index term has stores */ void mk_peq (expr* e0, expr* e1, unsigned num_indices, expr* const* indices, app_ref& result) { peq p (e0, e1, num_indices, indices, m); p.mk_peq (result); } void find_subst_term (app* eq) { app_ref p_exp (m); mk_peq (eq->get_arg (0), eq->get_arg (1), 0, nullptr, p_exp); bool subst_eq_found = false; while (true) { TRACE ("qe", tout << "processing peq:\n"; tout << mk_pp (p_exp, m) << "\n"; ); peq p (p_exp, m); expr_ref lhs (m), rhs (m); p.lhs (lhs); p.rhs (rhs); if (!m_has_stores_v.is_marked (lhs)) { std::swap (lhs, rhs); } if (m_has_stores_v.is_marked (lhs)) { /** project using the equivalence: * * (store(arr0,idx,x) ==I arr1) <-> * * (idx \in I => (arr0 ==I arr1)) /\ * (idx \not\in I => (arr0 ==I+idx arr1) /\ (arr1[idx] == x))) */ expr_ref_vector I (m); p.get_diff_indices (I); app* a_lhs = to_app (lhs); expr* arr0 = a_lhs->get_arg (0); expr* idx = a_lhs->get_arg (1); expr* x = a_lhs->get_arg (2); expr* arr1 = rhs; // check if (idx \in I) in M bool idx_in_I = false; expr_ref_vector idx_diseq (m); if (!I.empty ()) { expr_ref val (m); m_mev.eval (*M, idx, val); for (unsigned i = 0; i < I.size () && !idx_in_I; i++) { if (idx == I.get (i)) { idx_in_I = true; } else { expr_ref val1 (m); expr* idx1 = I.get (i); expr_ref idx_eq (m.mk_eq (idx, idx1), m); m_mev.eval (*M, idx1, val1); if (val == val1) { idx_in_I = true; m_idx_lits_v.push_back (idx_eq); } else { idx_diseq.push_back (m.mk_not (idx_eq)); } } } } if (idx_in_I) { TRACE ("qe", tout << "store index in diff indices:\n"; tout << mk_pp (m_idx_lits_v.back (), m) << "\n"; ); // arr0 ==I arr1 mk_peq (arr0, arr1, I.size (), I.data (), p_exp); TRACE ("qe", tout << "new peq:\n"; tout << mk_pp (p_exp, m) << "\n"; ); } else { m_idx_lits_v.append (idx_diseq); // arr0 ==I+idx arr1 I.push_back (idx); mk_peq (arr0, arr1, I.size (), I.data (), p_exp); TRACE ("qe", tout << "new peq:\n"; tout << mk_pp (p_exp, m) << "\n"; ); // arr1[idx] == x ptr_vector sel_args; sel_args.push_back (arr1); sel_args.push_back (idx); expr_ref arr1_idx (m_arr_u.mk_select (sel_args.size (), sel_args.data ()), m); expr_ref eq (m.mk_eq (arr1_idx, x), m); m_aux_lits_v.push_back (eq); TRACE ("qe", tout << "new eq:\n"; tout << mk_pp (eq, m) << "\n"; ); } } else if (lhs == rhs) { // trivial peq (a ==I a) break; } else if (lhs == m_v || rhs == m_v) { subst_eq_found = true; TRACE ("qe", tout << "subst eq found!\n"; ); break; } else { UNREACHABLE (); } } // factor out select terms on m_v from p_exp using fresh constants if (subst_eq_found) { factor_selects (p_exp); TRACE ("qe", tout << "after factoring selects:\n"; tout << mk_pp (p_exp, m) << "\n"; for (unsigned i = m_aux_lits_v.size () - m_aux_vars.size (); i < m_aux_lits_v.size (); i++) { tout << mk_pp (m_aux_lits_v.get (i), m) << "\n"; } ); // find subst_term bool stores_on_rhs = true; app* a = to_app (p_exp); if (a->get_arg (1) == m_v) { stores_on_rhs = false; } app_ref eq (m); convert_peq_to_eq (p_exp, eq, stores_on_rhs); m_subst_term_v = eq->get_arg (1); TRACE ("qe", tout << "subst term found:\n"; tout << mk_pp (m_subst_term_v, m) << "\n"; ); } } /** * try to substitute for m_v, using array equalities * * compute substitution term and aux lits */ bool project (expr_ref const& fml) { expr_ref_vector eqs (m); ptr_vector true_eqs; // subset of eqs; eqs ensures references find_arr_eqs (fml, eqs); TRACE ("qe", tout << "array equalities:\n"; for (unsigned i = 0; i < eqs.size (); i++) { tout << mk_pp (eqs.get (i), m) << "\n"; } ); // evaluate eqs in M for (unsigned i = 0; i < eqs.size (); i++) { TRACE ("qe", tout << "array equality:\n"; tout << mk_pp (eqs.get (i), m) << "\n"; ); expr* eq = eqs.get (i); // evaluate eq in M app* a = to_app (eq); expr_ref val (m); m_mev.eval_array_eq (*M, a, a->get_arg (0), a->get_arg (1), val); if (!val) { // XXX HACK: unable to evaluate. set to true? val = m.mk_true (); } SASSERT (m.is_true (val) || m.is_false (val)); if (m.is_false (val)) { m_false_sub_v.insert (eq, m.mk_false ()); } else { true_eqs.push_back (to_app (eq)); } } // compute nesting depths of stores on m_v in true_eqs, as follows: // 0 if m_v appears on both sides of equality // 1 if equality is (m_v=t) // 2 if equality is (store(m_v,i,v)=t) // ... unsigned num_true_eqs = true_eqs.size (); vector nds (num_true_eqs); for (unsigned i = 0; i < num_true_eqs; i++) { app* eq = true_eqs.get (i); expr* lhs = eq->get_arg (0); expr* rhs = eq->get_arg (1); bool lhs_has_v = (lhs == m_v || m_has_stores_v.is_marked (lhs)); bool rhs_has_v = (rhs == m_v || m_has_stores_v.is_marked (rhs)); app* store = nullptr; SASSERT (lhs_has_v || rhs_has_v); if (!lhs_has_v) { store = to_app (rhs); } else if (!rhs_has_v) { store = to_app (lhs); } // else v appears on both sides -- trivial equality // put it in the beginning to simplify it away unsigned nd = 0; // nesting depth if (store) { for (nd = 1; m_arr_u.is_store (store); nd++, store = to_app (store->get_arg (0))) /* empty */ ; SASSERT (store == m_v); } nds[i] = nd; } SASSERT (true_eqs.size () == nds.size ()); // sort true_eqs according to nesting depth // use insertion sort for (unsigned i = 1; i < num_true_eqs; i++) { app_ref eq(m); eq = true_eqs.get (i); unsigned nd = nds.get (i); unsigned j = i; for (; j >= 1 && nds.get (j-1) > nd; j--) { true_eqs.set (j, true_eqs.get (j-1)); nds.set (j, nds.get (j-1)); } if (j < i) { true_eqs.set (j, eq); nds.set (j, nd); TRACE ("qe", tout << "changing eq order!\n"; ); } } // search for subst term for (unsigned i = 0; !m_subst_term_v && i < num_true_eqs; i++) { app* eq = true_eqs.get (i); m_true_sub_v.insert (eq, m.mk_true ()); // try to find subst term find_subst_term (eq); } return true; } void mk_result (expr_ref& fml) { th_rewriter rw(m); rw (fml); // add in aux_lits and idx_lits expr_ref_vector lits (m); // TODO: eliminate possible duplicates, especially in idx_lits // theory rewriting is a possibility, but not sure if it // introduces unwanted terms such as ite's lits.append (m_idx_lits_v); lits.append (m_aux_lits_v); lits.push_back (fml); fml = m.mk_and (lits.size (), lits.data ()); if (m_subst_term_v) { m_true_sub_v.insert (m_v, m_subst_term_v); m_true_sub_v (fml); } else { m_true_sub_v (fml); m_false_sub_v (fml); } rw(fml); SASSERT (!m.is_false (fml)); } public: array_project_eqs_util (ast_manager& m): m (m), m_arr_u (m), m_v (m), m_subst_term_v (m), m_true_sub_v (m), m_false_sub_v (m), m_aux_lits_v (m), m_idx_lits_v (m), m_aux_vars (m), m_mev (m) {} void operator () (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { reset (); app_ref_vector rem_arr_vars (m); // remaining arr vars M = &mdl; for (unsigned i = 0; i < arr_vars.size (); i++) { reset_v (); m_v = arr_vars.get (i); if (!m_arr_u.is_array (m_v)) { TRACE ("qe", tout << "not an array variable: " << mk_pp (m_v, m) << "\n"; ); aux_vars.push_back (m_v); continue; } TRACE ("qe", tout << "projecting equalities on variable: " << mk_pp (m_v, m) << "\n"; ); if (project (fml)) { mk_result (fml); contains_app contains_v (m, m_v); if (!m_subst_term_v || contains_v (m_subst_term_v)) { rem_arr_vars.push_back (m_v); } TRACE ("qe", tout << "after projection: \n"; tout << mk_pp (fml, m) << "\n"; ); } else { IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(m_v, m) << "\n";); TRACE ("qe", tout << "Failed to project: " << mk_pp (m_v, m) << "\n";); rem_arr_vars.push_back(m_v); } } arr_vars.reset (); arr_vars.append (rem_arr_vars); aux_vars.append (m_aux_vars); } }; class array_select_reducer { ast_manager& m; array_util m_arr_u; obj_map m_cache; expr_ref_vector m_pinned; // to ensure a reference expr_ref_vector m_idx_lits; model_ref M; model_evaluator_array_util m_mev; th_rewriter m_rw; ast_mark m_arr_test; ast_mark m_has_stores; bool m_reduce_all_selects; void reset () { m_cache.reset (); m_pinned.reset (); m_idx_lits.reset (); M = nullptr; m_arr_test.reset (); m_has_stores.reset (); m_reduce_all_selects = false; } bool is_equals (expr *e1, expr *e2) { if (e1 == e2) return true; expr_ref val1 (m), val2 (m); m_mev.eval (*M, e1, val1); m_mev.eval (*M, e2, val2); return (val1 == val2); } void add_idx_cond (expr_ref& cond) { m_rw (cond); if (!m.is_true (cond)) m_idx_lits.push_back (cond); } bool has_stores (expr* e) { if (m_reduce_all_selects) return true; return m_has_stores.is_marked (e); } void mark_stores (app* a, bool args_have_stores) { if (m_reduce_all_selects) return; if (args_have_stores || (m_arr_u.is_store (a) && m_arr_test.is_marked (a->get_arg (0)))) { m_has_stores.mark (a, true); } } bool reduce (expr_ref& e) { if (!is_app (e)) return true; expr *r = nullptr; if (m_cache.find (e, r)) { e = r; return true; } ptr_vector todo; todo.push_back (to_app (e)); while (!todo.empty ()) { app *a = todo.back (); unsigned sz = todo.size (); expr_ref_vector args (m); bool dirty = false; bool args_have_stores = false; for (unsigned i = 0; i < a->get_num_args (); ++i) { expr *arg = a->get_arg (i); expr *narg = nullptr; if (!is_app (arg)) args.push_back (arg); else if (m_cache.find (arg, narg)) { args.push_back (narg); dirty |= (arg != narg); if (!args_have_stores && has_stores (narg)) { args_have_stores = true; } } else { todo.push_back (to_app (arg)); } } if (todo.size () > sz) continue; todo.pop_back (); if (dirty) { r = m.mk_app (a->get_decl (), args.size (), args.data ()); m_pinned.push_back (r); } else r = a; if (m_arr_u.is_select (r) && has_stores (to_app (r)->get_arg (0))) { r = reduce_core (to_app(r)); } else { mark_stores (to_app (r), args_have_stores); } m_cache.insert (a, r); } SASSERT (r); e = r; return true; } expr* reduce_core (app *a) { if (!m_arr_u.is_store (a->get_arg (0))) return a; SASSERT (a->get_num_args () == 2 && "Multi-dimensional arrays are not supported"); expr* array = a->get_arg (0); expr* j = a->get_arg (1); while (m_arr_u.is_store (array)) { a = to_app (array); expr* idx = a->get_arg (1); expr_ref cond (m); if (is_equals (idx, j)) { cond = m.mk_eq (idx, j); add_idx_cond (cond); return a->get_arg (2); } else { cond = m.mk_not (m.mk_eq (idx, j)); add_idx_cond (cond); array = a->get_arg (0); } } expr* args[2] = {array, j}; expr* r = m_arr_u.mk_select (2, args); m_pinned.push_back (r); return r; } void mk_result (expr_ref& fml) { // conjoin idx lits expr_ref_vector lits (m); lits.append (m_idx_lits); lits.push_back (fml); fml = m.mk_and (lits.size (), lits.data ()); // simplify all trivial expressions introduced m_rw (fml); TRACE ("qe", tout << "after reducing selects:\n"; tout << mk_pp (fml, m) << "\n"; ); } public: array_select_reducer (ast_manager& m): m (m), m_arr_u (m), m_pinned (m), m_idx_lits (m), m_mev (m), m_rw (m), m_reduce_all_selects (false) {} void operator () (model& mdl, app_ref_vector const& arr_vars, expr_ref& fml, bool reduce_all_selects = false) { if (!reduce_all_selects && arr_vars.empty ()) return; reset (); M = &mdl; m_reduce_all_selects = reduce_all_selects; // mark vars to eliminate for (unsigned i = 0; i < arr_vars.size (); i++) { m_arr_test.mark (arr_vars.get (i), true); } // assume all arr_vars are of array sort // and assume no store equalities on arr_vars if (reduce (fml)) { mk_result (fml); } else { IF_VERBOSE(2, verbose_stream() << "can't project arrays:" << "\n";); TRACE ("qe", tout << "Failed to project arrays\n";); } } }; class array_project_selects_util { typedef obj_map*> sel_map; ast_manager& m; array_util m_arr_u; arith_util m_ari_u; sel_map m_sel_terms; // representative indices for eliminating selects expr_ref_vector m_idx_reprs; expr_ref_vector m_idx_vals; app_ref_vector m_sel_consts; expr_ref_vector m_idx_lits; model_ref M; model_evaluator_array_util m_mev; expr_safe_replace m_sub; ast_mark m_arr_test; void reset () { m_sel_terms.reset (); m_idx_reprs.reset (); m_idx_vals.reset (); m_sel_consts.reset (); m_idx_lits.reset (); M = nullptr; m_sub.reset (); m_arr_test.reset (); } /** * collect sel terms on array vars as given by m_arr_test */ void collect_selects (expr* fml) { if (!is_app (fml)) return; ast_mark done; ptr_vector todo; todo.push_back (to_app (fml)); while (!todo.empty ()) { app* a = todo.back (); if (done.is_marked (a)) { todo.pop_back (); continue; } unsigned num_args = a->get_num_args (); bool all_done = true; for (unsigned i = 0; i < num_args; i++) { expr* arg = a->get_arg (i); if (!done.is_marked (arg) && is_app (arg)) { todo.push_back (to_app (arg)); all_done = false; } } if (!all_done) continue; todo.pop_back (); if (m_arr_u.is_select (a)) { expr* arr = a->get_arg (0); if (m_arr_test.is_marked (arr)) { ptr_vector* lst = m_sel_terms.find (to_app (arr));; lst->push_back (a); } } done.mark (a, true); } } /** * model based ackermannization for sel terms of some array * * update sub with val consts for sel terms */ void ackermann (ptr_vector const& sel_terms) { if (sel_terms.empty ()) return; expr* v = sel_terms.get (0)->get_arg (0); // array variable sort* v_sort = v->get_sort (); sort* val_sort = get_array_range (v_sort); sort* idx_sort = get_array_domain (v_sort, 0); (void) idx_sort; unsigned start = m_idx_reprs.size (); // append at the end for (unsigned i = 0; i < sel_terms.size (); i++) { app* a = sel_terms.get (i); expr* idx = a->get_arg (1); expr_ref val (m); m_mev.eval (*M, idx, val); bool is_new = true; for (unsigned j = start; j < m_idx_vals.size (); j++) { if (m_idx_vals.get (j) == val) { // idx belongs to the jth equivalence class; // substitute sel term with ith sel const expr* c = m_sel_consts.get (j); m_sub.insert (a, c); // add equality (idx == repr) expr* repr = m_idx_reprs.get (j); m_idx_lits.push_back (m.mk_eq (idx, repr)); is_new = false; break; } } if (is_new) { // new repr, val, and sel const m_idx_reprs.push_back (idx); m_idx_vals.push_back (val); app_ref c (m.mk_fresh_const ("sel", val_sort), m); m_sel_consts.push_back (c); // substitute sel term with new const m_sub.insert (a, c); // extend M to include c m_mev.eval (*M, a, val); M->register_decl (c->get_decl (), val); } } // sort reprs by their value and add a chain of strict inequalities unsigned num_reprs = m_idx_reprs.size () - start; if (num_reprs == 0) return; SASSERT ((m_ari_u.is_real (idx_sort) || m_ari_u.is_int (idx_sort)) && "Unsupported index sort: neither real nor int"); // using insertion sort unsigned end = start + num_reprs; for (unsigned i = start+1; i < end; i++) { expr_ref repr(m), val(m); repr = m_idx_reprs.get (i); val = m_idx_vals.get (i); unsigned j = i; for (; j > start; j--) { rational j_val, jm1_val; VERIFY (m_ari_u.is_numeral (val, j_val)); VERIFY (m_ari_u.is_numeral (m_idx_vals.get (j-1), jm1_val)); if (j_val >= jm1_val) break; m_idx_reprs[j] = m_idx_reprs.get (j-1); m_idx_vals[j] = m_idx_vals.get (j-1); } m_idx_reprs[j] = repr; m_idx_vals[j] = val; } for (unsigned i = start; i < end-1; i++) { m_idx_lits.push_back (m_ari_u.mk_lt (m_idx_reprs.get (i), m_idx_reprs.get (i+1))); } } void mk_result (expr_ref& fml) { // conjoin idx lits expr_ref_vector lits (m); lits.append (m_idx_lits); lits.push_back (fml); fml = m.mk_and (lits.size (), lits.data ()); // substitute for sel terms m_sub (fml); TRACE ("qe", tout << "after projection of selects:\n"; tout << mk_pp (fml, m) << "\n"; ); } /** * project selects * populates idx lits and obtains substitution for sel terms */ bool project (expr_ref& fml) { // collect sel terms -- populate the map m_sel_terms collect_selects (fml); // model based ackermannization sel_map::iterator begin = m_sel_terms.begin (), end = m_sel_terms.end (); for (sel_map::iterator it = begin; it != end; it++) { TRACE ("qe", tout << "ackermann for var: " << mk_pp (it->m_key, m) << "\n"; ); ackermann (*(it->m_value)); } TRACE ("qe", tout << "idx lits:\n"; for (unsigned i = 0; i < m_idx_lits.size (); i++) { tout << mk_pp (m_idx_lits.get (i), m) << "\n"; } ); return true; } public: array_project_selects_util (ast_manager& m): m (m), m_arr_u (m), m_ari_u (m), m_idx_reprs (m), m_idx_vals (m), m_sel_consts (m), m_idx_lits (m), m_mev (m), m_sub (m) {} void operator () (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { reset (); M = &mdl; // mark vars to eliminate for (unsigned i = 0; i < arr_vars.size (); i++) { m_arr_test.mark (arr_vars.get (i), true); } // alloc empty map from array var to sel terms over it for (unsigned i = 0; i < arr_vars.size (); i++) { ptr_vector* lst = alloc (ptr_vector); m_sel_terms.insert (arr_vars.get (i), lst); } // assume all arr_vars are of array sort // and they only appear in select terms if (project (fml)) { mk_result (fml); aux_vars.append (m_sel_consts); arr_vars.reset (); } else { IF_VERBOSE(2, verbose_stream() << "can't project arrays:" << "\n";); TRACE ("qe", tout << "Failed to project arrays\n";); } // dealloc sel_map::iterator begin = m_sel_terms.begin (), end = m_sel_terms.end (); for (sel_map::iterator it = begin; it != end; it++) { dealloc (it->m_value); } m_sel_terms.reset (); } }; expr_ref arith_project(model& mdl, app_ref_vector& vars, expr_ref_vector const& lits) { ast_manager& m = vars.get_manager(); arith_project_util ap(m); return ap(mdl, vars, lits); } void arith_project(model& mdl, app_ref_vector& vars, expr_ref& fml) { ast_manager& m = vars.get_manager(); arith_project_util ap(m); qe::atom_set pos_lits, neg_lits; is_relevant_default is_relevant; mk_atom_default mk_atom; get_nnf (fml, is_relevant, mk_atom, pos_lits, neg_lits); ap(mdl, vars, fml); } void arith_project(model& mdl, app_ref_vector& vars, expr_ref& fml, expr_map& map) { ast_manager& m = vars.get_manager(); arith_project_util ap(m); qe::atom_set pos_lits, neg_lits; is_relevant_default is_relevant; mk_atom_default mk_atom; get_nnf (fml, is_relevant, mk_atom, pos_lits, neg_lits); ap(mdl, vars, fml, map); } void array_project_eqs (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { ast_manager& m = arr_vars.get_manager (); array_project_eqs_util ap (m); ap (mdl, arr_vars, fml, aux_vars); } void reduce_array_selects (model& mdl, app_ref_vector const& arr_vars, expr_ref& fml, bool reduce_all_selects) { ast_manager& m = arr_vars.get_manager (); array_select_reducer ap (m); ap (mdl, arr_vars, fml, reduce_all_selects); } void reduce_array_selects (model& mdl, expr_ref& fml) { ast_manager& m = fml.get_manager (); app_ref_vector _tmp (m); reduce_array_selects (mdl, _tmp, fml, true); } void array_project_selects (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { ast_manager& m = arr_vars.get_manager (); array_project_selects_util ap (m); ap (mdl, arr_vars, fml, aux_vars); } void array_project (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars, bool reduce_all_selects) { // 1. project array equalities array_project_eqs (mdl, arr_vars, fml, aux_vars); TRACE ("qe", ast_manager& m = fml.get_manager (); tout << "Projected array eqs:\n" << mk_pp (fml, m) << "\n"; tout << "Remaining array vars:\n"; for (unsigned i = 0; i < arr_vars.size (); i++) { tout << mk_pp (arr_vars.get (i), m) << "\n"; } tout << "Aux vars:\n"; for (unsigned i = 0; i < aux_vars.size (); i++) { tout << mk_pp (aux_vars.get (i), m) << "\n"; } ); // 2. reduce selects if (reduce_all_selects) { reduce_array_selects (mdl, fml); } else { reduce_array_selects (mdl, arr_vars, fml); } TRACE ("qe", ast_manager& m = fml.get_manager (); tout << "Reduced selects:\n" << mk_pp (fml, m) << "\n"; ); // 3. project selects using model based ackermannization array_project_selects (mdl, arr_vars, fml, aux_vars); TRACE ("qe", ast_manager& m = fml.get_manager (); tout << "Projected array selects:\n" << mk_pp (fml, m) << "\n"; tout << "All aux vars:\n"; for (unsigned i = 0; i < aux_vars.size (); i++) { tout << mk_pp (aux_vars.get (i), m) << "\n"; } ); } }