/*++ Copyright (c) 2012 Microsoft Corporation Module Name: reduce_args_simplifier.cpp Abstract: Reduce the number of arguments in function applications. Author: Leonardo (leonardo) 2012-02-19 Notes: --*/ #include "util/map.h" #include "ast/ast_smt2_pp.h" #include "ast/ast_util.h" #include "ast/has_free_vars.h" #include "ast/rewriter/rewriter_def.h" #include "ast/simplifiers/dependent_expr_state.h" /** \brief Reduce the number of arguments in function applications. Example, suppose we have a function f with 2 arguments. There are 1000 applications of this function, but the first argument is always "a", "b" or "c". Thus, we replace the f(t1, t2) with f_a(t2) if t1 = a f_b(t2) if t2 = b f_c(t2) if t2 = c Since f_a, f_b, f_c are new symbols, satisfiability is preserved. This transformation is very similar in spirit to the Ackermman's reduction. This transformation should work in the following way: 1- Create a mapping decl2arg_map from declarations to tuples of booleans, an entry [f -> (true, false, true)] means that f is a declaration with 3 arguments where the first and third arguments are always values. 2- Traverse the formula and populate the mapping. For each function application f(t1, ..., tn) do a) Create a boolean tuple (is_value(t1), ..., is_value(tn)) and do the logical-and with the tuple that is already in the mapping. If there is no such tuple in the mapping, we just add a new entry. If all entries are false-tuples, then there is nothing to be done. The transformation is not applicable. Now, we create a mapping decl2new_decl from (decl, val_1, ..., val_n) to decls. Note that, n may be different for each entry, but it is the same for the same declaration. For example, suppose we have [f -> (true, false, true)] in decl2arg_map, and applications f(1, a, 2), f(1, b, 2), f(1, b, 3), f(2, b, 3), f(2, c, 3) in the formula. Then, decl2arg_map would contain (f, 1, 2) -> f_1_2 (f, 1, 3) -> f_1_3 (f, 2, 3) -> f_2_3 where f_1_2, f_1_3 and f_2_3 are new function symbols. Using the new map, we can replace the occurrences of f. */ class reduce_args_simplifier : public dependent_expr_simplifier { bv_util m_bv; static bool is_var_plus_offset(ast_manager& m, bv_util& bv, expr* e, expr*& base) { expr *lhs, *rhs; if (bv.is_bv_add(e, lhs, rhs) && bv.is_numeral(lhs)) base = rhs; else base = e; return !has_free_vars(base); } static bool may_be_unique(ast_manager& m, bv_util& bv, expr* e, expr*& base) { base = nullptr; return m.is_unique_value(e) || is_var_plus_offset(m, bv, e, base); } static bool may_be_unique(ast_manager& m, bv_util& bv, expr* e) { expr* base; return may_be_unique(m, bv, e, base); } struct find_non_candidates_proc { ast_manager & m; bv_util & m_bv; obj_hashtable & m_non_candidates; find_non_candidates_proc(ast_manager & m, bv_util & bv, obj_hashtable & non_candidates): m(m), m_bv(bv), m_non_candidates(non_candidates) { } void operator()(var * n) {} void operator()(quantifier *n) {} void operator()(app * n) { if (!is_uninterp(n)) return; func_decl * d; if (n->get_num_args() == 0) return; // ignore constants d = n->get_decl(); if (m_non_candidates.contains(d)) return; // it is already in the set. for (expr* arg : *n) if (may_be_unique(m, m_bv, arg)) return; m_non_candidates.insert(d); } }; /** \brief Populate the table non_candidates with function declarations \c f such that there is a function application (f t1 ... tn) where t1 ... tn are not values. */ void find_non_candidates(obj_hashtable & non_candidates) { non_candidates.reset(); find_non_candidates_proc proc(m, m_bv, non_candidates); expr_fast_mark1 visited; for (auto i : indices()) quick_for_each_expr(proc, visited, m_fmls[i].fml()); TRACE("reduce_args", tout << "non_candidates:\n"; for (func_decl* d : non_candidates) tout << d->get_name() << "\n";); } struct populate_decl2args_proc { reduce_args_simplifier& m_owner; ast_manager & m; bv_util & m_bv; obj_hashtable & m_non_candidates; obj_map & m_decl2args; obj_map > m_decl2base; // for args = base + offset populate_decl2args_proc(reduce_args_simplifier& o, ast_manager & m, bv_util & bv, obj_hashtable & nc, obj_map & d): m_owner(o), m(m), m_bv(bv), m_non_candidates(nc), m_decl2args(d) {} void operator()(var * n) {} void operator()(quantifier * n) {} void operator()(app * n) { if (n->get_num_args() == 0) return; // ignore constants func_decl * d = n->get_decl(); if (d->get_family_id() != null_family_id) return; // ignore interpreted symbols if (m_non_candidates.contains(d)) return; // declaration is not a candidate if (m_owner.m_fmls.frozen(d)) return; unsigned j = n->get_num_args(); obj_map::iterator it = m_decl2args.find_iterator(d); expr* base; if (it == m_decl2args.end()) { m_decl2args.insert(d, bit_vector()); svector& bases = m_decl2base.insert_if_not_there(d, svector()); bases.resize(j); it = m_decl2args.find_iterator(d); SASSERT(it != m_decl2args.end()); it->m_value.reserve(j); while (j > 0) { --j; it->m_value.set(j, may_be_unique(m, m_bv, n->get_arg(j), base)); bases[j] = base; } } else { svector& bases = m_decl2base[d]; SASSERT(j == it->m_value.size()); while (j > 0) { --j; it->m_value.set(j, it->m_value.get(j) && may_be_unique(m, m_bv, n->get_arg(j), base) && bases[j] == base); } } } }; void populate_decl2args(obj_hashtable & non_candidates, obj_map & decl2args) { expr_fast_mark1 visited; decl2args.reset(); populate_decl2args_proc proc(*this, m, m_bv, non_candidates, decl2args); for (auto i : indices()) quick_for_each_expr(proc, visited, m_fmls[i].fml()); // Remove all cases where the simplification is not applicable. ptr_buffer bad_decls; for (auto const& [k, v] : decl2args) if (all_of(v, [&](auto b) { return !b;})) bad_decls.push_back(k); for (func_decl* a : bad_decls) decl2args.erase(a); TRACE("reduce_args", tout << "decl2args:" << std::endl; for (auto const& [k, v] : decl2args) { tout << k->get_name() << ": "; for (unsigned i = 0; i < v.size(); ++i) tout << (v.get(i) ? "1" : "0"); tout << std::endl; }); } struct arg2func_hash_proc { bit_vector const & m_bv; arg2func_hash_proc(bit_vector const & bv):m_bv(bv) {} unsigned operator()(app const * n) const { // compute the hash-code using only the arguments where m_bv is true. unsigned a = 0x9e3779b9; unsigned num_args = n->get_num_args(); for (unsigned i = 0; i < num_args; i++) { if (!m_bv.get(i)) continue; // ignore argument a = hash_u_u(a, n->get_arg(i)->get_id()); } return a; } }; struct arg2func_eq_proc { bit_vector const & m_bv; arg2func_eq_proc(bit_vector const & bv):m_bv(bv) {} bool operator()(app const * n1, app const * n2) const { // compare only the arguments where m_bv is true SASSERT(n1->get_num_args() == n2->get_num_args()); unsigned num_args = n1->get_num_args(); for (unsigned i = 0; i < num_args; i++) { if (!m_bv.get(i)) continue; // ignore argument if (n1->get_arg(i) != n2->get_arg(i)) return false; } return true; } }; typedef map arg2func; typedef obj_map decl2arg2func_map; struct reduce_args_ctx { ast_manager & m; decl2arg2func_map m_decl2arg2funcs; reduce_args_ctx(ast_manager & m): m(m) { } ~reduce_args_ctx() { for (auto const& [_, map] : m_decl2arg2funcs) { for (auto const& [k, v] : *map) { m.dec_ref(k); m.dec_ref(v); } dealloc(map); } } }; struct reduce_args_rw_cfg : public default_rewriter_cfg { ast_manager & m; reduce_args_simplifier& m_owner; obj_map & m_decl2args; decl2arg2func_map & m_decl2arg2funcs; reduce_args_rw_cfg(reduce_args_simplifier& owner, obj_map & decl2args, decl2arg2func_map & decl2arg2funcs): m(owner.m), m_owner(owner), m_decl2args(decl2args), m_decl2arg2funcs(decl2arg2funcs) { } br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { result_pr = nullptr; if (f->get_arity() == 0) return BR_FAILED; // ignore constants if (f->get_family_id() != null_family_id) return BR_FAILED; // ignore interpreted symbols obj_map::iterator it = m_decl2args.find_iterator(f); if (it == m_decl2args.end()) return BR_FAILED; bit_vector & bv = it->m_value; arg2func *& map = m_decl2arg2funcs.insert_if_not_there(f, 0); if (!map) { map = alloc(arg2func, arg2func_hash_proc(bv), arg2func_eq_proc(bv)); } app_ref tmp(m.mk_app(f, num, args), m); func_decl *& new_f = map->insert_if_not_there(tmp, nullptr); if (!new_f) { // create fresh symbol ptr_buffer domain; unsigned arity = f->get_arity(); for (unsigned i = 0; i < arity; ++i) { if (!bv.get(i)) domain.push_back(f->get_domain(i)); } new_f = m.mk_fresh_func_decl(f->get_name(), symbol::null, domain.size(), domain.data(), f->get_range()); m.inc_ref(tmp); m.inc_ref(new_f); } ptr_buffer new_args; for (unsigned i = 0; i < num; i++) { if (!bv.get(i)) new_args.push_back(args[i]); } result = m.mk_app(new_f, new_args.size(), new_args.data()); return BR_DONE; } }; struct reduce_args_rw : rewriter_tpl { reduce_args_rw_cfg m_cfg; public: reduce_args_rw(reduce_args_simplifier & owner, obj_map & decl2args, decl2arg2func_map & decl2arg2funcs): rewriter_tpl(owner.m, false, m_cfg), m_cfg(owner, decl2args, decl2arg2funcs) { } }; void mk_mc(obj_map & decl2args, decl2arg2func_map & decl2arg2funcs, vector const& removed) { ptr_buffer new_args; var_ref_vector new_vars(m); ptr_buffer new_eqs; for (auto const& [f, map] : decl2arg2funcs) for (auto const& [t, new_def] : *map) m_fmls.model_trail().hide(new_def); vector> defs; for (auto const& [f, map] : decl2arg2funcs) { expr * def = nullptr; SASSERT(decl2args.contains(f)); bit_vector & bv = decl2args.find(f); new_vars.reset(); new_args.reset(); for (unsigned i = 0; i < f->get_arity(); i++) { new_vars.push_back(m.mk_var(i, f->get_domain(i))); if (!bv.get(i)) new_args.push_back(new_vars.back()); } for (auto const& [t, new_def] : *map) { SASSERT(new_def->get_arity() == new_args.size()); app * new_t = m.mk_app(new_def, new_args); if (def == nullptr) { def = new_t; } else { new_eqs.reset(); for (unsigned i = 0; i < f->get_arity(); i++) if (bv.get(i)) new_eqs.push_back(m.mk_eq(new_vars.get(i), t->get_arg(i))); SASSERT(new_eqs.size() > 0); expr * cond = mk_and(m, new_eqs); def = m.mk_ite(cond, new_t, def); } } SASSERT(def); expr_dependency* dep = nullptr; defs.push_back({ func_decl_ref(f,m), expr_ref(def, m), expr_dependency_ref(dep, m) }); } m_fmls.model_trail().push(defs, removed); } unsigned m_num_decls = 0; public: reduce_args_simplifier(ast_manager& m, dependent_expr_state& st, params_ref const& p) : dependent_expr_simplifier(m, st), m_bv(m) {} ~reduce_args_simplifier() override {} char const* name() const override { return "reduce-args"; } void collect_statistics(statistics& st) const override { st.update("reduced-funcs", m_num_decls); } void reset_statistics() override { m_num_decls = 0; } void reduce() override { m_fmls.freeze_suffix(); obj_hashtable non_candidates; obj_map decl2args; find_non_candidates(non_candidates); populate_decl2args(non_candidates, decl2args); if (decl2args.empty()) return; m_num_decls += decl2args.size(); reduce_args_ctx ctx(m); reduce_args_rw rw(*this, decl2args, ctx.m_decl2arg2funcs); vector removed; // if not global scope then what? // cannot just use in incremental mode. for (auto i : indices()) { auto [f, p, d] = m_fmls[i](); if (p) continue; expr_ref new_f(m); rw(f, new_f); if (f != new_f) { removed.push_back(m_fmls[i]); m_fmls.update(i, dependent_expr(m, new_f, p, d)); } } mk_mc(decl2args, ctx.m_decl2arg2funcs, removed); } }; dependent_expr_simplifier* mk_reduce_args_simplifier(ast_manager & m, dependent_expr_state& st, params_ref const & p) { return alloc(reduce_args_simplifier, m, st, p); }