/*++ Copyright (c) 2011 Microsoft Corporation Module Name: degree_shift_tactic.cpp Abstract: Simple degree shift procedure. Basic idea: if goal G contains a real variable x, x occurs with degrees d_1, ..., d_k in G, and n = gcd(d_1, ..., d_k) > 1. Then, replace x^n with a new fresh variable y. Author: Leonardo de Moura (leonardo) 2011-12-30. Revision History: --*/ #include"tactical.h" #include"filter_model_converter.h" #include"extension_model_converter.h" #include"cooperate.h" #include"arith_decl_plugin.h" #include"simplify_tactic.h" #include"ast_smt2_pp.h" #include"rewriter_def.h" class degree_shift_tactic : public tactic { struct imp { ast_manager & m; arith_util m_autil; obj_map m_var2degree; obj_map m_var2var; obj_map m_var2pr; expr_ref_vector m_pinned; ptr_vector m_todo; rational m_one; bool m_produce_models; bool m_produce_proofs; volatile bool m_cancel; expr * mk_power(expr * t, rational const & k) { if (k.is_one()) return t; else return m_autil.mk_power(t, m_autil.mk_numeral(k, false)); } struct rw_cfg : public default_rewriter_cfg { imp & o; rw_cfg(imp & _o):o(_o) {} br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { arith_util & u = o.m_autil; if (!is_decl_of(f, u.get_family_id(), OP_POWER) || !is_app(args[0])) return BR_FAILED; ast_manager & m = o.m; rational g; app * t = to_app(args[0]); if (!o.m_var2degree.find(t, g)) return BR_FAILED; SASSERT(g > rational(1)); SASSERT(g.is_int()); rational k; VERIFY(u.is_numeral(args[1], k)); SASSERT(gcd(k, g) == g); rational new_k = div(k, g); expr * new_arg = o.m_var2var.find(t); result = o.mk_power(new_arg, new_k); if (o.m_produce_proofs) { proof * pr = o.m_var2pr.find(t); app * fact = m.mk_eq(m.mk_app(f, num, args), result); result_pr = m.mk_th_lemma(u.get_family_id(), fact, 1, &pr); } return BR_DONE; } }; class rw : public rewriter_tpl { rw_cfg m_cfg; public: rw(imp & o): rewriter_tpl(o.m, o.m_produce_proofs, m_cfg), m_cfg(o) { } }; scoped_ptr m_rw; imp(ast_manager & _m): m(_m), m_autil(_m), m_pinned(_m), m_one(1), m_rw(0) { m_cancel = false; } void set_cancel(bool f) { m_cancel = f; } void checkpoint() { if (m_cancel) throw tactic_exception(TACTIC_CANCELED_MSG); cooperate("degree_shift"); } void visit(expr * t, expr_fast_mark1 & visited) { if (!visited.is_marked(t)) { visited.mark(t); m_todo.push_back(t); } } void save_degree(expr * t, rational const & k) { SASSERT(k.is_int()); if (is_uninterp_const(t) && m_autil.is_real(t)) { rational old_k; if (m_var2degree.find(to_app(t), old_k)) { old_k = gcd(k, old_k); m_var2degree.insert(to_app(t), old_k); } else { m_var2degree.insert(to_app(t), k); } } } void visit_args(expr * t, expr_fast_mark1 & visited) { if (is_app(t)) { unsigned num_args = to_app(t)->get_num_args(); for (unsigned i = 0; i < num_args; i++) { expr * arg = to_app(t)->get_arg(i); save_degree(arg, m_one); visit(arg, visited); } } } void collect(expr * t, expr_fast_mark1 & visited) { rational k; visit(t, visited); while (!m_todo.empty()) { checkpoint(); expr * t = m_todo.back(); m_todo.pop_back(); if (is_var(t)) continue; if (is_quantifier(t)) { unsigned num_children = to_quantifier(t)->get_num_children(); for (unsigned i = 0; i < num_children; i ++) visit(to_quantifier(t)->get_child(i), visited); } else { SASSERT(is_app(t)); if (m_autil.is_power(t) && m_autil.is_numeral(to_app(t)->get_arg(1), k) && k.is_int() && k.is_pos()) { expr * arg = to_app(t)->get_arg(0); save_degree(arg, k); visit_args(arg, visited); } else { visit_args(t, visited); } } } } void display_candidates(std::ostream & out) { out << "candidates:\n"; obj_map::iterator it = m_var2degree.begin(); obj_map::iterator end = m_var2degree.end(); for (; it != end; ++it) { if (!it->m_value.is_one()) { out << "POWER: " << it->m_value << "\n" << mk_ismt2_pp(it->m_key, m) << "\n"; } } } void collect(goal const & g) { m_var2degree.reset(); expr_fast_mark1 visited; unsigned sz = g.size(); for (unsigned i = 0; i < sz; i++) { collect(g.form(i), visited); } TRACE("degree_shift", display_candidates(tout);); } void discard_non_candidates() { m_pinned.reset(); ptr_vector to_delete; obj_map::iterator it = m_var2degree.begin(); obj_map::iterator end = m_var2degree.end(); for (; it != end; ++it) { if (it->m_value.is_one()) to_delete.push_back(it->m_key); else m_pinned.push_back(it->m_key); // make sure it is not deleted during simplifications } ptr_vector::iterator it2 = to_delete.begin(); ptr_vector::iterator end2 = to_delete.end(); for (; it2 != end2; ++it2) m_var2degree.erase(*it2); } void prepare_substitution(model_converter_ref & mc) { SASSERT(!m_var2degree.empty()); filter_model_converter * fmc = 0; extension_model_converter * xmc = 0; if (m_produce_models) { fmc = alloc(filter_model_converter, m); xmc = alloc(extension_model_converter, m); mc = concat(fmc, xmc); } obj_map::iterator it = m_var2degree.begin(); obj_map::iterator end = m_var2degree.end(); for (; it != end; ++it) { SASSERT(it->m_value.is_int()); SASSERT(it->m_value >= rational(2)); app * fresh = m.mk_fresh_const(0, it->m_key->get_decl()->get_range()); m_pinned.push_back(fresh); m_var2var.insert(it->m_key, fresh); if (m_produce_models) { fmc->insert(fresh->get_decl()); xmc->insert(it->m_key->get_decl(), mk_power(fresh, rational(1)/it->m_value)); } if (m_produce_proofs) { expr * s = mk_power(it->m_key, it->m_value); expr * eq = m.mk_eq(fresh, s); proof * pr1 = m.mk_def_intro(eq); proof * result_pr = m.mk_apply_def(fresh, s, pr1); m_pinned.push_back(result_pr); m_var2pr.insert(it->m_key, result_pr); } } } void operator()(goal_ref const & g, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core) { SASSERT(g->is_well_sorted()); mc = 0; pc = 0; core = 0; m_produce_proofs = g->proofs_enabled(); m_produce_models = g->models_enabled(); tactic_report report("degree_shift", *g); collect(*g); discard_non_candidates(); if (!m_var2degree.empty()) { prepare_substitution(mc); m_rw = alloc(rw, *this); // substitute expr_ref new_curr(m); proof_ref new_pr(m); unsigned size = g->size(); for (unsigned idx = 0; idx < size; idx++) { checkpoint(); expr * curr = g->form(idx); (*m_rw)(curr, new_curr, new_pr); if (m_produce_proofs) { proof * pr = g->pr(idx); new_pr = m.mk_modus_ponens(pr, new_pr); } g->update(idx, new_curr, new_pr, g->dep(idx)); } // add >= 0 constraints for variables with even degree obj_map::iterator it = m_var2degree.begin(); obj_map::iterator end = m_var2degree.end(); for (; it != end; ++it) { SASSERT(it->m_value.is_int()); SASSERT(it->m_value >= rational(2)); if (it->m_value.is_even()) { app * new_var = m_var2var.find(it->m_key); app * new_c = m_autil.mk_ge(new_var, m_autil.mk_numeral(rational(0), false)); proof * new_pr = 0; if (m_produce_proofs) { proof * pr = m_var2pr.find(it->m_key); new_pr = m.mk_th_lemma(m_autil.get_family_id(), new_c, 1, &pr); } g->assert_expr(new_c, new_pr, 0); } } } g->inc_depth(); result.push_back(g.get()); TRACE("degree_shift", g->display(tout); if (mc) mc->display(tout);); SASSERT(g->is_well_sorted()); } }; imp * m_imp; public: degree_shift_tactic(ast_manager & m) { m_imp = alloc(imp, m); } virtual tactic * translate(ast_manager & m) { return alloc(degree_shift_tactic, m); } virtual ~degree_shift_tactic() { dealloc(m_imp); } virtual void operator()(goal_ref const & in, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core) { (*m_imp)(in, result, mc, pc, core); } virtual void cleanup() { ast_manager & m = m_imp->m; imp * d = m_imp; #pragma omp critical (tactic_cancel) { m_imp = 0; } dealloc(d); d = alloc(imp, m); #pragma omp critical (tactic_cancel) { m_imp = d; } } protected: virtual void set_cancel(bool f) { if (m_imp) m_imp->set_cancel(f); } }; tactic * mk_degree_shift_tactic(ast_manager & m, params_ref const & p) { params_ref mul2power_p; mul2power_p.set_bool(":mul-to-power", true); return and_then(using_params(mk_simplify_tactic(m), mul2power_p), clean(alloc(degree_shift_tactic, m))); }