/*++ Copyright (c) 2006 Microsoft Corporation Module Name: macro_util.cpp Abstract: Macro finding goodies. They are used during preprocessing (MACRO_FINDER=true), and model building. Author: Leonardo de Moura (leonardo) 2010-12-15. Revision History: --*/ #include"macro_util.h" #include"occurs.h" #include"arith_simplifier_plugin.h" #include"basic_simplifier_plugin.h" #include"bv_simplifier_plugin.h" #include"var_subst.h" #include"ast_pp.h" #include"ast_ll_pp.h" #include"ast_util.h" #include"for_each_expr.h" #include"well_sorted.h" macro_util::macro_util(ast_manager & m, simplifier & s): m_manager(m), m_simplifier(s), m_arith_simp(0), m_bv_simp(0), m_basic_simp(0), m_forbidden_set(0), m_curr_clause(0) { } arith_simplifier_plugin * macro_util::get_arith_simp() const { if (m_arith_simp == 0) { const_cast(this)->m_arith_simp = static_cast(m_simplifier.get_plugin(m_manager.get_family_id("arith"))); } SASSERT(m_arith_simp != 0); return m_arith_simp; } bv_simplifier_plugin * macro_util::get_bv_simp() const { if (m_bv_simp == 0) { const_cast(this)->m_bv_simp = static_cast(m_simplifier.get_plugin(m_manager.get_family_id("bv"))); } SASSERT(m_bv_simp != 0); return m_bv_simp; } basic_simplifier_plugin * macro_util::get_basic_simp() const { if (m_basic_simp == 0) { const_cast(this)->m_basic_simp = static_cast(m_simplifier.get_plugin(m_manager.get_basic_family_id())); } SASSERT(m_basic_simp != 0); return m_basic_simp; } bool macro_util::is_bv(expr * n) const { return get_bv_simp()->is_bv(n); } bool macro_util::is_bv_sort(sort * s) const { return get_bv_simp()->is_bv_sort(s); } bool macro_util::is_add(expr * n) const { return get_arith_simp()->is_add(n) || get_bv_simp()->is_add(n); } bool macro_util::is_times_minus_one(expr * n, expr * & arg) const { return get_arith_simp()->is_times_minus_one(n, arg) || get_bv_simp()->is_times_minus_one(n, arg); } bool macro_util::is_le(expr * n) const { return get_arith_simp()->is_le(n) || get_bv_simp()->is_le(n); } bool macro_util::is_le_ge(expr * n) const { return get_arith_simp()->is_le_ge(n) || get_bv_simp()->is_le_ge(n); } poly_simplifier_plugin * macro_util::get_poly_simp_for(sort * s) const { if (is_bv_sort(s)) return get_bv_simp(); else return get_arith_simp(); } app * macro_util::mk_zero(sort * s) const { poly_simplifier_plugin * ps = get_poly_simp_for(s); ps->set_curr_sort(s); return ps->mk_zero(); } void macro_util::mk_sub(expr * t1, expr * t2, expr_ref & r) const { if (is_bv(t1)) { get_bv_simp()->mk_sub(t1, t2, r); } else { get_arith_simp()->mk_sub(t1, t2, r); } } void macro_util::mk_add(expr * t1, expr * t2, expr_ref & r) const { if (is_bv(t1)) { get_bv_simp()->mk_add(t1, t2, r); } else { get_arith_simp()->mk_add(t1, t2, r); } } void macro_util::mk_add(unsigned num_args, expr * const * args, sort * s, expr_ref & r) const { if (num_args == 0) { r = mk_zero(s); return; } poly_simplifier_plugin * ps = get_poly_simp_for(s); ps->set_curr_sort(s); ps->mk_add(num_args, args, r); } /** \brief Return true if \c n is an application of the form (f x_{k_1}, ..., x_{k_n}) where f is uninterpreted n == num_decls x_{k_i}'s are variables and {k_1, ..., k_n } is equals to the set {0, ..., num_decls-1} */ bool macro_util::is_macro_head(expr * n, unsigned num_decls) const { if (is_app(n) && !to_app(n)->get_decl()->is_associative() && to_app(n)->get_family_id() == null_family_id && to_app(n)->get_num_args() == num_decls) { sbuffer var2pos; var2pos.resize(num_decls, -1); for (unsigned i = 0; i < num_decls; i++) { expr * c = to_app(n)->get_arg(i); if (!is_var(c)) return false; unsigned idx = to_var(c)->get_idx(); if (idx >= num_decls || var2pos[idx] != -1) return false; var2pos[idx] = i; } return true; } return false; } /** \brief Return true if n is of the form (= (f x_{k_1}, ..., x_{k_n}) t) OR (iff (f x_{k_1}, ..., x_{k_n}) t) where is_macro_head((f x_{k_1}, ..., x_{k_n})) returns true AND t does not contain f AND f is not in forbidden_set In case of success head will contain (f x_{k_1}, ..., x_{k_n}) AND def will contain t */ bool macro_util::is_left_simple_macro(expr * n, unsigned num_decls, app * & head, expr * & def) const { if (m_manager.is_eq(n) || m_manager.is_iff(n)) { expr * lhs = to_app(n)->get_arg(0); expr * rhs = to_app(n)->get_arg(1); if (is_macro_head(lhs, num_decls) && !is_forbidden(to_app(lhs)->get_decl()) && !occurs(to_app(lhs)->get_decl(), rhs)) { head = to_app(lhs); def = rhs; return true; } } return false; } /** \brief Return true if n is of the form (= t (f x_{k_1}, ..., x_{k_n})) OR (iff t (f x_{k_1}, ..., x_{k_n})) where is_macro_head((f x_{k_1}, ..., x_{k_n})) returns true AND t does not contain f AND f is not in forbidden_set In case of success head will contain (f x_{k_1}, ..., x_{k_n}) AND def will contain t */ bool macro_util::is_right_simple_macro(expr * n, unsigned num_decls, app * & head, expr * & def) const { if (m_manager.is_eq(n) || m_manager.is_iff(n)) { expr * lhs = to_app(n)->get_arg(0); expr * rhs = to_app(n)->get_arg(1); if (is_macro_head(rhs, num_decls) && !is_forbidden(to_app(rhs)->get_decl()) && !occurs(to_app(rhs)->get_decl(), lhs)) { head = to_app(rhs); def = lhs; return true; } } return false; } /** \brief Return true if n contains f. The method ignores the sub-expression \c exception. \remark n is a "polynomial". */ bool macro_util::poly_contains_head(expr * n, func_decl * f, expr * exception) const { expr * curr = n; unsigned num_args; expr * const * args; if (is_add(n)) { num_args = to_app(n)->get_num_args(); args = to_app(n)->get_args(); } else { num_args = 1; args = &n; } for (unsigned i = 0; i < num_args; i++) { expr * arg = args[i]; if (arg != exception && occurs(f, arg)) return true; } return false; } bool macro_util::is_arith_macro(expr * n, unsigned num_decls, app_ref & head, expr_ref & def, bool & inv) const { // TODO: obsolete... we should move to collect_arith_macro_candidates arith_simplifier_plugin * as = get_arith_simp(); if (!m_manager.is_eq(n) && !as->is_le(n) && !as->is_ge(n)) return false; expr * lhs = to_app(n)->get_arg(0); expr * rhs = to_app(n)->get_arg(1); if (!as->is_numeral(rhs)) return false; inv = false; ptr_buffer args; expr * h = 0; unsigned lhs_num_args; expr * const * lhs_args; if (is_add(lhs)) { lhs_num_args = to_app(lhs)->get_num_args(); lhs_args = to_app(lhs)->get_args(); } else { lhs_num_args = 1; lhs_args = &lhs; } for (unsigned i = 0; i < lhs_num_args; i++) { expr * arg = lhs_args[i]; expr * neg_arg; if (h == 0 && is_macro_head(arg, num_decls) && !is_forbidden(to_app(arg)->get_decl()) && !poly_contains_head(lhs, to_app(arg)->get_decl(), arg)) { h = arg; } else if (h == 0 && as->is_times_minus_one(arg, neg_arg) && is_macro_head(neg_arg, num_decls) && !is_forbidden(to_app(neg_arg)->get_decl()) && !poly_contains_head(lhs, to_app(neg_arg)->get_decl(), arg)) { h = neg_arg; inv = true; } else { args.push_back(arg); } } if (h == 0) return false; head = to_app(h); expr_ref tmp(m_manager); as->mk_add(args.size(), args.c_ptr(), tmp); if (inv) as->mk_sub(tmp, rhs, def); else as->mk_sub(rhs, tmp, def); return true; } /** \brief Auxiliary function for is_pseudo_predicate_macro. It detects the pattern (= (f X) t) */ bool macro_util::is_pseudo_head(expr * n, unsigned num_decls, app * & head, app * & t) { if (!m_manager.is_eq(n)) return false; expr * lhs = to_app(n)->get_arg(0); expr * rhs = to_app(n)->get_arg(1); if (!is_ground(lhs) && !is_ground(rhs)) return false; sort * s = m_manager.get_sort(lhs); if (m_manager.is_uninterp(s)) return false; sort_size sz = s->get_num_elements(); if (sz.is_finite() && sz.size() == 1) return false; if (is_macro_head(lhs, num_decls)) { head = to_app(lhs); t = to_app(rhs); return true; } if (is_macro_head(rhs, num_decls)) { head = to_app(rhs); t = to_app(lhs); return true; } return false; } /** \brief Returns true if n if of the form (forall (X) (iff (= (f X) t) def[X])) where t is a ground term, (f X) is the head. */ bool macro_util::is_pseudo_predicate_macro(expr * n, app * & head, app * & t, expr * & def) { if (!is_quantifier(n) || !to_quantifier(n)->is_forall()) return false; TRACE("macro_util", tout << "processing: " << mk_pp(n, m_manager) << "\n";); expr * body = to_quantifier(n)->get_expr(); unsigned num_decls = to_quantifier(n)->get_num_decls(); if (!m_manager.is_iff(body)) return false; expr * lhs = to_app(body)->get_arg(0); expr * rhs = to_app(body)->get_arg(1); if (is_pseudo_head(lhs, num_decls, head, t) && !is_forbidden(head->get_decl()) && !occurs(head->get_decl(), rhs)) { def = rhs; return true; } if (is_pseudo_head(rhs, num_decls, head, t) && !is_forbidden(head->get_decl()) && !occurs(head->get_decl(), lhs)) { def = lhs; return true; } return false; } /** \brief A quasi-macro head is of the form f[X_1, ..., X_n], where n == num_decls, f[X_1, ..., X_n] is a term starting with symbol f, f is uninterpreted, contains all universally quantified variables as arguments. Note that, some arguments of f[X_1, ..., X_n] may not be variables. Examples of quasi-macros: f(x_1, x_1 + x_2, x_2) for num_decls == 2 g(x_1, x_1) for num_decls == 1 Return true if \c n is a quasi-macro. Store the macro head in \c head, and the conditions to apply the macro in \c cond. */ bool macro_util::is_quasi_macro_head(expr * n, unsigned num_decls) const { if (is_app(n) && to_app(n)->get_family_id() == null_family_id && to_app(n)->get_num_args() >= num_decls) { unsigned num_args = to_app(n)->get_num_args(); sbuffer found_vars; found_vars.resize(num_decls, false); unsigned num_found_vars = 0; for (unsigned i = 0; i < num_args; i++) { expr * arg = to_app(n)->get_arg(i); if (is_var(arg)) { unsigned idx = to_var(arg)->get_idx(); if (idx >= num_decls) return false; if (found_vars[idx] == false) { found_vars[idx] = true; num_found_vars++; } } else { if (occurs(to_app(n)->get_decl(), arg)) return false; } } return num_found_vars == num_decls; } return false; } /** \brief Convert a quasi-macro head into a macro head, and store the conditions under which it is valid in cond. */ void macro_util::quasi_macro_head_to_macro_head(app * qhead, unsigned num_decls, app_ref & head, expr_ref & cond) const { unsigned num_args = qhead->get_num_args(); sbuffer found_vars; found_vars.resize(num_decls, false); ptr_buffer new_args; ptr_buffer new_conds; unsigned next_var_idx = num_decls; for (unsigned i = 0; i < num_args; i++) { expr * arg = qhead->get_arg(i); if (is_var(arg)) { unsigned idx = to_var(arg)->get_idx(); SASSERT(idx < num_decls); if (found_vars[idx] == false) { found_vars[idx] = true; new_args.push_back(arg); continue; } } var * new_var = m_manager.mk_var(next_var_idx, m_manager.get_sort(arg)); next_var_idx++; expr * new_cond = m_manager.mk_eq(new_var, arg); new_args.push_back(new_var); new_conds.push_back(new_cond); } get_basic_simp()->mk_and(new_conds.size(), new_conds.c_ptr(), cond); head = m_manager.mk_app(qhead->get_decl(), new_args.size(), new_args.c_ptr()); } /** \brief Given a macro defined by head and def, stores an interpretation for head->get_decl() in interp. This method assumes is_macro_head(head, head->get_num_args()) returns true, and def does not contain head->get_decl(). See normalize_expr */ void macro_util::mk_macro_interpretation(app * head, expr * def, expr_ref & interp) const { SASSERT(is_macro_head(head, head->get_num_args())); SASSERT(!occurs(head->get_decl(), def)); normalize_expr(head, def, interp); } /** \brief The variables in head may be in the wrong order. Example: f(x_1, x_0) instead of f(x_0, x_1) This method is essentially renaming the variables in t. Suppose t is g(x_1, x_0 + x_1) This method will store g(x_0, x_1 + x_0) in norm_t. f(x_1, x_2) --> f(x_0, x_1) f(x_3, x_2) --> f(x_0, x_1) */ void macro_util::normalize_expr(app * head, expr * t, expr_ref & norm_t) const { expr_ref_buffer var_mapping(m_manager); bool changed = false; unsigned num_args = head->get_num_args(); unsigned max = num_args; for (unsigned i = 0; i < num_args; i++) { var * v = to_var(head->get_arg(i)); if (v->get_idx() >= max) max = v->get_idx() + 1; } TRACE("normalize_expr_bug", tout << "head: " << mk_pp(head, m_manager) << "\n"; tout << "applying substitution to:\n" << mk_bounded_pp(t, m_manager) << "\n";); for (unsigned i = 0; i < num_args; i++) { var * v = to_var(head->get_arg(i)); if (v->get_idx() != i) { changed = true; var * new_var = m_manager.mk_var(i, v->get_sort()); CTRACE("normalize_expr_bug", v->get_idx() >= num_args, tout << mk_pp(v, m_manager) << ", num_args: " << num_args << "\n";); SASSERT(v->get_idx() < max); var_mapping.setx(max - v->get_idx() - 1, new_var); } else { var_mapping.setx(max - i - 1, v); } } if (changed) { // REMARK: t may have nested quantifiers... So, I must use the std order for variable substitution. var_subst subst(m_manager); TRACE("macro_util_bug", tout << "head: " << mk_pp(head, m_manager) << "\n"; tout << "applying substitution to:\n" << mk_ll_pp(t, m_manager) << "\nsubstitituion:\n"; for (unsigned i = 0; i < var_mapping.size(); i++) { tout << "#" << i << " -> " << mk_pp(var_mapping[i], m_manager) << "\n"; }); subst(t, var_mapping.size(), var_mapping.c_ptr(), norm_t); SASSERT(is_well_sorted(m_manager, norm_t)); } else { norm_t = t; } } // ----------------------------- // // "Hint" support // See comment at is_hint_atom // for a definition of what a hint is. // // ----------------------------- bool is_hint_head(expr * n, ptr_buffer & vars) { if (!is_app(n)) return false; if (to_app(n)->get_decl()->is_associative() || to_app(n)->get_family_id() != null_family_id) return false; unsigned num_args = to_app(n)->get_num_args(); for (unsigned i = 0; i < num_args; i++) { expr * arg = to_app(n)->get_arg(i); if (is_var(arg)) vars.push_back(to_var(arg)); } return !vars.empty(); } /** \brief Returns true if the variables in n is a subset of \c vars. */ bool vars_of_is_subset(expr * n, ptr_buffer const & vars) { if (is_ground(n)) return true; obj_hashtable visited; ptr_buffer todo; todo.push_back(n); while (!todo.empty()) { expr * curr = todo.back(); todo.pop_back(); if (is_var(curr)) { if (std::find(vars.begin(), vars.end(), to_var(curr)) == vars.end()) return false; } else if (is_app(curr)) { unsigned num_args = to_app(curr)->get_num_args(); for (unsigned i = 0; i < num_args; i++) { expr * arg = to_app(curr)->get_arg(i); if (is_ground(arg)) continue; if (visited.contains(arg)) continue; visited.insert(arg); todo.push_back(arg); } } else { SASSERT(is_quantifier(curr)); return false; // do no support nested quantifier... being conservative. } } return true; } /** \brief (= lhs rhs) is a hint atom if lhs is of the form (f t_1 ... t_n) and all variables occurring in rhs are direct arguments of lhs. */ bool is_hint_atom(expr * lhs, expr * rhs) { ptr_buffer vars; if (!is_hint_head(lhs, vars)) return false; return !occurs(to_app(lhs)->get_decl(), rhs) && vars_of_is_subset(rhs, vars); } void hint_to_macro_head(ast_manager & m, app * head, unsigned num_decls, app_ref & new_head) { unsigned num_args = head->get_num_args(); ptr_buffer new_args; sbuffer found_vars; found_vars.resize(num_decls, false); unsigned next_var_idx = num_decls; for (unsigned i = 0; i < num_args; i++) { expr * arg = head->get_arg(i); if (is_var(arg)) { unsigned idx = to_var(arg)->get_idx(); SASSERT(idx < num_decls); if (found_vars[idx] == false) { found_vars[idx] = true; new_args.push_back(arg); continue; } } var * new_var = m.mk_var(next_var_idx, m.get_sort(arg)); next_var_idx++; new_args.push_back(new_var); } new_head = m.mk_app(head->get_decl(), new_args.size(), new_args.c_ptr()); } /** \brief Return true if n can be viewed as a polynomial "hint" based on head. That is, n (but the monomial exception) only uses the variables in head, and does not use head->get_decl(). is_hint_head(head, vars) must also return true */ bool macro_util::is_poly_hint(expr * n, app * head, expr * exception) { TRACE("macro_util_hint", tout << "is_poly_hint n:\n" << mk_pp(n, m_manager) << "\nhead:\n" << mk_pp(head, m_manager) << "\nexception:\n" << mk_pp(exception, m_manager) << "\n";); ptr_buffer vars; if (!is_hint_head(head, vars)) { TRACE("macro_util_hint", tout << "failed because head is not hint head\n";); return false; } func_decl * f = head->get_decl(); unsigned num_args; expr * const * args; if (is_add(n)) { num_args = to_app(n)->get_num_args(); args = to_app(n)->get_args(); } else { num_args = 1; args = &n; } for (unsigned i = 0; i < num_args; i++) { expr * arg = args[i]; if (arg != exception && (occurs(f, arg) || !vars_of_is_subset(arg, vars))) { TRACE("macro_util_hint", tout << "failed because of:\n" << mk_pp(arg, m_manager) << "\n";); return false; } } TRACE("macro_util_hint", tout << "succeeded\n";); return true; } // ----------------------------- // // Macro candidates // // ----------------------------- macro_util::macro_candidates::macro_candidates(ast_manager & m): m_defs(m), m_conds(m) { } void macro_util::macro_candidates::reset() { m_fs.reset(); m_defs.reset(); m_conds.reset(); m_ineq.reset(); m_satisfy.reset(); m_hint.reset(); } void macro_util::macro_candidates::insert(func_decl * f, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint) { m_fs.push_back(f); m_defs.push_back(def); m_conds.push_back(cond); m_ineq.push_back(ineq); m_satisfy.push_back(satisfy_atom); m_hint.push_back(hint); } // ----------------------------- // // Macro util // // ----------------------------- void macro_util::insert_macro(app * head, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint, macro_candidates & r) { expr_ref norm_def(m_manager); expr_ref norm_cond(m_manager); normalize_expr(head, def, norm_def); if (cond != 0) normalize_expr(head, cond, norm_cond); else if (!hint) norm_cond = m_manager.mk_true(); SASSERT(!hint || norm_cond.get() == 0); r.insert(head->get_decl(), norm_def.get(), norm_cond.get(), ineq, satisfy_atom, hint); } void macro_util::insert_quasi_macro(app * head, unsigned num_decls, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint, macro_candidates & r) { if (!is_macro_head(head, head->get_num_args())) { app_ref new_head(m_manager); expr_ref extra_cond(m_manager); expr_ref new_cond(m_manager); if (!hint) { quasi_macro_head_to_macro_head(head, num_decls, new_head, extra_cond); if (cond == 0) new_cond = extra_cond; else get_basic_simp()->mk_and(cond, extra_cond, new_cond); } else { hint_to_macro_head(m_manager, head, num_decls, new_head); } insert_macro(new_head, def, new_cond, ineq, satisfy_atom, hint, r); } else { insert_macro(head, def, cond, ineq, satisfy_atom, hint, r); } } bool macro_util::rest_contains_decl(func_decl * f, expr * except_lit) { if (m_curr_clause == 0) return false; SASSERT(is_clause(m_manager, m_curr_clause)); unsigned num_lits = get_clause_num_literals(m_manager, m_curr_clause); for (unsigned i = 0; i < num_lits; i++) { expr * l = get_clause_literal(m_manager, m_curr_clause, i); if (l != except_lit && occurs(f, l)) return true; } return false; } void macro_util::get_rest_clause_as_cond(expr * except_lit, expr_ref & extra_cond) { if (m_curr_clause == 0) return; SASSERT(is_clause(m_manager, m_curr_clause)); basic_simplifier_plugin * bs = get_basic_simp(); expr_ref_buffer neg_other_lits(m_manager); unsigned num_lits = get_clause_num_literals(m_manager, m_curr_clause); for (unsigned i = 0; i < num_lits; i++) { expr * l = get_clause_literal(m_manager, m_curr_clause, i); if (l != except_lit) { expr_ref neg_l(m_manager); bs->mk_not(l, neg_l); neg_other_lits.push_back(neg_l); } } if (neg_other_lits.empty()) return; get_basic_simp()->mk_and(neg_other_lits.size(), neg_other_lits.c_ptr(), extra_cond); } void macro_util::collect_poly_args(expr * n, expr * exception, ptr_buffer & args) { args.reset(); bool stop = false; unsigned num_args; expr * const * _args; if (is_add(n)) { num_args = to_app(n)->get_num_args(); _args = to_app(n)->get_args(); } else { num_args = 1; _args = &n; } for (unsigned i = 0; i < num_args; i++) { expr * arg = _args[i]; if (arg != exception) args.push_back(arg); } } void macro_util::add_arith_macro_candidate(app * head, unsigned num_decls, expr * def, expr * atom, bool ineq, bool hint, macro_candidates & r) { expr_ref cond(m_manager); if (!hint) get_rest_clause_as_cond(atom, cond); insert_quasi_macro(head, num_decls, def, cond, ineq, true, hint, r); } void macro_util::collect_arith_macro_candidates(expr * lhs, expr * rhs, expr * atom, unsigned num_decls, bool is_ineq, macro_candidates & r) { if (!is_add(lhs) && m_manager.is_eq(atom)) // this case is a simple macro. return; bool stop = false; ptr_buffer args; unsigned lhs_num_args; expr * const * lhs_args; if (is_add(lhs)) { lhs_num_args = to_app(lhs)->get_num_args(); lhs_args = to_app(lhs)->get_args(); } else { lhs_num_args = 1; lhs_args = &lhs; } for (unsigned i = 0; i < lhs_num_args; i++) { expr * arg = lhs_args[i]; expr * neg_arg; if (!is_app(arg)) continue; func_decl * f = to_app(arg)->get_decl(); bool _is_arith_macro = is_quasi_macro_head(arg, num_decls) && !is_forbidden(f) && !poly_contains_head(lhs, f, arg) && !occurs(f, rhs) && !rest_contains_decl(f, atom); bool _is_poly_hint = !_is_arith_macro && is_poly_hint(lhs, to_app(arg), arg); if (_is_arith_macro || _is_poly_hint) { collect_poly_args(lhs, arg, args); expr_ref rest(m_manager); mk_add(args.size(), args.c_ptr(), m_manager.get_sort(arg), rest); expr_ref def(m_manager); mk_sub(rhs, rest, def); add_arith_macro_candidate(to_app(arg), num_decls, def, atom, is_ineq, _is_poly_hint, r); } else if (is_times_minus_one(arg, neg_arg) && is_app(neg_arg)) { f = to_app(neg_arg)->get_decl(); bool _is_arith_macro = is_quasi_macro_head(neg_arg, num_decls) && !is_forbidden(f) && !poly_contains_head(lhs, f, arg) && !occurs(f, rhs) && !rest_contains_decl(f, atom); bool _is_poly_hint = !_is_arith_macro && is_poly_hint(lhs, to_app(neg_arg), arg); if (_is_arith_macro || _is_poly_hint) { collect_poly_args(lhs, arg, args); expr_ref rest(m_manager); mk_add(args.size(), args.c_ptr(), m_manager.get_sort(arg), rest); expr_ref def(m_manager); mk_sub(rest, rhs, def); add_arith_macro_candidate(to_app(neg_arg), num_decls, def, atom, is_ineq, _is_poly_hint, r); } } } } void macro_util::collect_arith_macro_candidates(expr * atom, unsigned num_decls, macro_candidates & r) { TRACE("macro_util_hint", tout << "collect_arith_macro_candidates:\n" << mk_pp(atom, m_manager) << "\n";); if (!m_manager.is_eq(atom) && !is_le_ge(atom)) return; expr * lhs = to_app(atom)->get_arg(0); expr * rhs = to_app(atom)->get_arg(1); bool is_ineq = !m_manager.is_eq(atom); collect_arith_macro_candidates(lhs, rhs, atom, num_decls, is_ineq, r); collect_arith_macro_candidates(rhs, lhs, atom, num_decls, is_ineq, r); } /** \brief Collect macro candidates for atom \c atom. The candidates are stored in \c r. The following post-condition holds: for each i in [0, r.size() - 1] we have a conditional macro of the form r.get_cond(i) IMPLIES f(x_1, ..., x_n) = r.get_def(i) where f == r.get_fs(i) .., x_n), f is uninterpreted and x_1, ..., x_n are variables. r.get_cond(i) and r.get_defs(i) do not contain f or variables not in {x_1, ..., x_n} The idea is to use r.get_defs(i) as the interpretation for f in a model M whenever r.get_cond(i) Given a model M and values { v_1, ..., v_n } Let M' be M{x_1 -> v_1, ..., v_n -> v_n} Note that M'(f(x_1, ..., x_n)) = M(f)(v_1, ..., v_n) Then, IF we have that M(f)(v_1, ..., v_n) = M'(r.get_def(i)) AND M'(r.get_cond(i)) = true THEN M'(atom) = true That is, if the conditional macro is used then the atom is satisfied when M'(r.get_cond(i)) = true IF r.is_ineq(i) = false, then M(f)(v_1, ..., v_n) ***MUST BE*** M'(r.get_def(i)) whenever M'(r.get_cond(i)) = true IF r.satisfy_atom(i) = true, then we have the stronger property: Then, IF we have that (M'(r.get_cond(i)) = true IMPLIES M(f)(v_1, ..., v_n) = M'(r.get_def(i))) THEN M'(atom) = true */ void macro_util::collect_macro_candidates_core(expr * atom, unsigned num_decls, macro_candidates & r) { if (m_manager.is_eq(atom) || m_manager.is_iff(atom)) { expr * lhs = to_app(atom)->get_arg(0); expr * rhs = to_app(atom)->get_arg(1); if (is_quasi_macro_head(lhs, num_decls) && !is_forbidden(to_app(lhs)->get_decl()) && !occurs(to_app(lhs)->get_decl(), rhs) && !rest_contains_decl(to_app(lhs)->get_decl(), atom)) { expr_ref cond(m_manager); get_rest_clause_as_cond(atom, cond); insert_quasi_macro(to_app(lhs), num_decls, rhs, cond, false, true, false, r); } else if (is_hint_atom(lhs, rhs)) { insert_quasi_macro(to_app(lhs), num_decls, rhs, 0, false, true, true, r); } if (is_quasi_macro_head(rhs, num_decls) && !is_forbidden(to_app(rhs)->get_decl()) && !occurs(to_app(rhs)->get_decl(), lhs) && !rest_contains_decl(to_app(rhs)->get_decl(), atom)) { expr_ref cond(m_manager); get_rest_clause_as_cond(atom, cond); insert_quasi_macro(to_app(rhs), num_decls, lhs, cond, false, true, false, r); } else if (is_hint_atom(rhs, lhs)) { insert_quasi_macro(to_app(rhs), num_decls, lhs, 0, false, true, true, r); } } collect_arith_macro_candidates(atom, num_decls, r); } void macro_util::collect_macro_candidates(expr * atom, unsigned num_decls, macro_candidates & r) { m_curr_clause = 0; r.reset(); collect_macro_candidates_core(atom, num_decls, r); } void macro_util::collect_macro_candidates(quantifier * q, macro_candidates & r) { r.reset(); expr * n = q->get_expr(); if (has_quantifiers(n)) return; unsigned num_decls = q->get_num_decls(); SASSERT(m_curr_clause == 0); if (is_clause(m_manager, n)) { m_curr_clause = n; unsigned num_lits = get_clause_num_literals(m_manager, n); for (unsigned i = 0; i < num_lits; i++) collect_macro_candidates_core(get_clause_literal(m_manager, n, i), num_decls, r); m_curr_clause = 0; } else { collect_macro_candidates_core(n, num_decls, r); } }