/*++ Copyright (c) 2006 Microsoft Corporation Module Name: user_smt_theory.cpp Abstract: Author: Leonardo de Moura (leonardo) 2010-05-22. Revision History: --*/ #include"smt_context.h" #include"user_smt_theory.h" #include"ast_pp.h" #include"smt_model_generator.h" #include"stats.h" #include"warning.h" namespace smt { // // value factory for user sorts. // // NB. This value factory for user theories // does not address theories where model // values are structured objects such as // arrays, records, or data-types. // class user_smt_theory_factory : public simple_factory { app* mk_value_core(unsigned const& val, sort* s) { return m_manager.mk_model_value(val, s); } public: user_smt_theory_factory(ast_manager& m, family_id fid): simple_factory(m, fid) {} }; user_theory::user_theory(ast_manager & m, front_end_params const& p, void * ext_context, void * ext_data, char const * name, family_id fid, user_decl_plugin * dp, user_simplifier_plugin * sp): theory(fid), m_params(p), m_ext_context(ext_context), m_ext_data(ext_data), m_name(name), m_simplify_axioms(false), m_decl_plugin(dp), m_simplifier_plugin(sp), m_find(*this), m_trail_stack(*this), m_asserted_axioms(m), m_persisted_axioms(m), m_persisted_axioms_qhead(0), m_delete_fptr(0), m_new_app_fptr(0), m_new_elem_fptr(0), m_init_search_fptr(0), m_push_fptr(0), m_pop_fptr(0), m_restart_fptr(0), m_reset_fptr(0), m_final_check_fptr(0), m_new_eq_fptr(0), m_new_diseq_fptr(0), m_new_assignment_fptr(0), m_new_relevant_fptr(0), m_mk_fresh_fptr(0), m_delete_invoking(false), m_new_app_invoking(false), m_new_elem_invoking(false), m_init_search_invoking(false), m_push_invoking(false), m_pop_invoking(false), m_restart_invoking(false), m_reset_invoking(false), m_final_check_invoking(false), m_new_eq_invoking(false), m_new_diseq_invoking(false), m_new_assignment_invoking(false), m_new_relevant_invoking(false) { } user_theory::~user_theory() { if (m_delete_fptr != 0) { flet i(m_delete_invoking, true); m_delete_fptr(this); } } theory * user_theory::mk_fresh(context * new_ctx) { if (m_mk_fresh_fptr == 0) { throw default_exception("The mk_fresh_ext_data callback was not set for user theory, you must use Z3_theory_set_mk_fresh_ext_data_callback"); return 0; } user_simplifier_plugin * new_sp = static_cast(new_ctx->get_simplifier().get_plugin(get_family_id())); SASSERT(new_sp != 0); user_theory * new_th = alloc(user_theory, get_manager(), new_ctx->get_fparams(), m_ext_context, m_mk_fresh_fptr(this), get_name(), get_family_id(), m_decl_plugin, new_sp); new_sp->set_owner(new_th); new_th->m_delete_fptr = m_delete_fptr; new_th->m_new_app_fptr = m_new_app_fptr; new_th->m_new_elem_fptr = m_new_elem_fptr; new_th->m_init_search_fptr = m_init_search_fptr; new_th->m_push_fptr = m_push_fptr; new_th->m_pop_fptr = m_pop_fptr; new_th->m_restart_fptr = m_restart_fptr; new_th->m_reset_fptr = m_reset_fptr; new_th->m_final_check_fptr = m_final_check_fptr; new_th->m_new_eq_fptr = m_new_eq_fptr; new_th->m_new_diseq_fptr = m_new_diseq_fptr; new_th->m_new_assignment_fptr = m_new_assignment_fptr; new_th->m_new_relevant_fptr = m_new_relevant_fptr; new_th->m_mk_fresh_fptr = m_mk_fresh_fptr; return new_th; } void user_theory::assert_axiom_core(app* a) { if (m_asserted_axiom_set.contains(a)) { return; } m_asserted_axiom_set.insert(a); m_asserted_axioms.push_back(a); if (m_params.m_user_theory_persist_axioms) { m_persisted_axioms.push_back(a); } } /** TODO: discuss semantics of assert_axiom. Should we simplify axiom of not? */ void user_theory::assert_axiom(ast * axiom) { ++m_stats.m_num_user_axioms; TRACE("user_smt_theory", tout << mk_pp(axiom, get_manager()) << "\n";); if (!is_expr(axiom)) { throw default_exception("invalid expression"); } if (!get_manager().is_bool(to_expr(axiom))) { throw default_exception("invalid theory axiom: axioms must have Boolean sort"); } if (!m_new_eq_invoking && !m_new_diseq_invoking && !m_new_assignment_invoking && !m_new_relevant_invoking && !m_final_check_invoking) { throw default_exception("theory axioms can only be invoked during callbacks " "for new (dis)equalities/assignments and final check"); } context & ctx = get_context(); ast_manager & m = get_manager(); if (!is_app(axiom) || !to_app(axiom)->is_ground() || ctx.get_fparams().m_user_theory_preprocess_axioms) { asserted_formulas asf(m, ctx.get_fparams()); asf.assert_expr(to_app(axiom)); asf.reduce(); unsigned sz = asf.get_num_formulas(); unsigned qhead = asf.get_qhead(); while (qhead < sz) { expr * f = asf.get_formula(qhead); assert_axiom_core(to_app(f)); ++qhead; } } else { if (!m_simplify_axioms) { m_simplifier_plugin->enable(false); } expr_ref s_axiom(m); proof_ref pr(m); simplifier & s = ctx.get_simplifier(); s(to_app(axiom), s_axiom, pr); if (!is_app(s_axiom)) { throw default_exception("invalid theory axiom: axioms must be applications"); } axiom = s_axiom; m_simplifier_plugin->enable(true); assert_axiom_core(to_app(axiom)); } } void user_theory::assume_eq(ast * _lhs, ast * _rhs) { if (!is_expr(_lhs) || !is_expr(_rhs)) { throw default_exception("assume_eq must take expressions as arguments"); } expr* lhs = to_expr(_lhs); expr* rhs = to_expr(_rhs); ast_manager& m = get_manager(); context& ctx = get_context(); if (m.is_true(rhs)) { std::swap(lhs, rhs); } if (m.is_true(lhs)) { theory_var v2 = mk_var(rhs); if (v2 == null_theory_var) { throw default_exception("invalid assume eq: lhs or rhs is not a theory term"); } bool_var bv = ctx.get_bool_var(rhs); ctx.set_true_first_flag(bv); ctx.mark_as_relevant(get_enode(v2)); return; } if (m.is_bool(lhs)) { throw default_exception("assume_eq on Booleans must take 'true' as one of the arguments"); } theory_var v1 = mk_var(lhs); theory_var v2 = mk_var(rhs); if (v1 == null_theory_var || v2 == null_theory_var) { throw default_exception("invalid assume eq: lhs or rhs is not a theory term"); } ctx.assume_eq(get_enode(v1), get_enode(v2)); } void user_theory::reset_propagation_queues() { m_new_eqs.reset(); m_new_diseqs.reset(); m_new_assignments.reset(); m_new_relevant_apps.reset(); } theory_var user_theory::get_var(ast * n) const { if (!is_app(n)) return null_theory_var; context & ctx = get_context(); if (ctx.e_internalized(to_app(n))) { enode * e = ctx.get_enode(to_app(n)); return e->get_th_var(get_id()); } return null_theory_var; } theory_var user_theory::mk_var(ast * n) { theory_var v = get_var(n); if (v != null_theory_var || !is_app(n)) { return v; } app* a = to_app(n); if (a->get_family_id() == get_id() && internalize_term(a)) { return mk_var(get_context().get_enode(a)); } return v; } ast * user_theory::get_root(ast * n) const { theory_var v = get_var(n); if (v != null_theory_var) { theory_var r = m_find.find(v); return get_ast(r); } return n; } ast * user_theory::get_next(ast * n) const { theory_var v = get_var(n); if (v != null_theory_var) { theory_var r = m_find.next(v); return get_ast(r); } return n; } void user_theory::shrink_use_list(unsigned sz) { SASSERT(m_use_list.size() >= sz); std::for_each(m_use_list.begin() + sz, m_use_list.end(), delete_proc >()); m_use_list.shrink(sz); } ptr_vector * user_theory::get_non_null_use_list(theory_var v) { SASSERT(v != null_theory_var); if (m_use_list[v] == 0) m_use_list[v] = alloc(ptr_vector); return m_use_list[v]; } unsigned user_theory::get_num_parents(ast * n) const { theory_var v = get_var(n); if (v != null_theory_var && m_use_list[v] != 0) return m_use_list[v]->size(); return 0; } ast * user_theory::get_parent(ast * n, unsigned i) const { theory_var v = get_var(n); if (v != null_theory_var && m_use_list[v] != 0) return m_use_list[v]->get(i, 0); return 0; } theory_var user_theory::mk_var(enode * n) { if (is_attached_to_var(n)) return n->get_th_var(get_id()); theory_var r = theory::mk_var(n); theory_var r2 = m_find.mk_var(); m_use_list.push_back(0); SASSERT(r == r2); get_context().attach_th_var(n, this, r); if (m_new_elem_fptr != 0) { flet invoking(m_new_elem_invoking, true); m_new_elem_fptr(this, n->get_owner()); } return r; } bool user_theory::internalize_atom(app * atom, bool gate_ctx) { return internalize_term(atom); } bool user_theory::internalize_term(app * term) { context & ctx = get_context(); unsigned num_args = term->get_num_args(); for (unsigned i = 0; i < num_args; i++) ctx.internalize(term->get_arg(i), false); // the internalization of the arguments may trigger the internalization of term. if (ctx.e_internalized(term)) return true; m_parents.push_back(term); enode * e = ctx.mk_enode(term, false, get_manager().is_bool(term), true); if (get_manager().is_bool(term)) { bool_var bv = ctx.mk_bool_var(term); ctx.set_var_theory(bv, get_id()); ctx.set_enode_flag(bv, true); } // make sure every argument is attached to a theory variable... for (unsigned i = 0; i < num_args; i++) { enode * arg = e->get_arg(i); theory_var v_arg = mk_var(arg); ptr_vector * arg_use_list = get_non_null_use_list(v_arg); arg_use_list->push_back(term); m_trail_stack.push(push_back_trail(*arg_use_list)); } if (m_new_app_fptr != 0) { flet invoking(m_new_app_invoking, true); m_new_app_fptr(this, term); } return true; } void user_theory::apply_sort_cnstr(enode * n, sort * s) { mk_var(n); } void user_theory::assign_eh(bool_var v, bool is_true) { m_new_assignments.push_back(v); } void user_theory::new_eq_eh(theory_var v1, theory_var v2) { m_new_eqs.push_back(var_pair(v1, v2)); } void user_theory::new_diseq_eh(theory_var v1, theory_var v2) { m_new_diseqs.push_back(var_pair(v1, v2)); } void user_theory::relevant_eh(app * n) { m_new_relevant_apps.push_back(n); } void user_theory::push_scope_eh() { SASSERT(m_new_assignments.empty()); SASSERT(m_new_eqs.empty()); SASSERT(m_new_diseqs.empty()); SASSERT(m_new_relevant_apps.empty()); theory::push_scope_eh(); m_trail_stack.push_scope(); m_scopes.push_back(scope()); scope & s = m_scopes.back(); s.m_asserted_axioms_old_sz = m_asserted_axioms.size(); s.m_parents_old_sz = m_parents.size(); if (m_push_fptr != 0) { flet invoke(m_push_invoking, true); m_push_fptr(this); } } void user_theory::pop_scope_eh(unsigned num_scopes) { reset_propagation_queues(); if (m_pop_fptr != 0) { for (unsigned i = 0; i < num_scopes; i++) { flet invoke(m_pop_invoking, true); m_pop_fptr(this); } } unsigned new_lvl = m_scopes.size() - num_scopes; scope & s = m_scopes[new_lvl]; m_parents.shrink(s.m_parents_old_sz); unsigned curr_sz = m_asserted_axioms.size(); unsigned old_sz = s.m_asserted_axioms_old_sz; for (unsigned i = old_sz; i < curr_sz; i++) { m_asserted_axiom_set.erase(m_asserted_axioms.get(i)); } m_asserted_axioms.shrink(old_sz); m_scopes.shrink(new_lvl); m_trail_stack.pop_scope(num_scopes); shrink_use_list(get_old_num_vars(num_scopes)); theory::pop_scope_eh(num_scopes); } void user_theory::restart_eh() { if (m_restart_fptr != 0) { flet invoke(m_restart_invoking, true); m_restart_fptr(this); } } void user_theory::init_search_eh() { if (m_init_search_fptr != 0) { flet invoke(m_init_search_invoking, true); m_init_search_fptr(this); } } final_check_status user_theory::final_check_eh() { if (m_final_check_fptr != 0) { unsigned old_sz = m_asserted_axioms.size(); flet invoke(m_final_check_invoking, true); Z3_bool r = m_final_check_fptr(this); if (old_sz != m_asserted_axioms.size()) { assert_axioms_into_context(old_sz); return r ? FC_CONTINUE : FC_GIVEUP; } return r ? FC_DONE : FC_GIVEUP; } return FC_DONE; } bool user_theory::can_propagate() { return (m_persisted_axioms.size() > m_persisted_axioms_qhead) || !m_new_eqs.empty() || !m_new_diseqs.empty() || !m_new_relevant_apps.empty() || !m_new_assignments.empty(); } literal user_theory::internalize_literal(expr * arg) { context & ctx = get_context(); ast_manager& m = get_manager(); if (is_app(arg) && m.is_not(arg)) { expr * arg_arg = to_app(arg)->get_arg(0); if (!ctx.b_internalized(arg_arg)) ctx.internalize(arg_arg, true); return literal(ctx.get_bool_var(arg_arg), true); } else if (m.is_false(arg)) { return false_literal; } else if (m.is_true(arg)) { return true_literal; } else { if (!ctx.b_internalized(arg)) ctx.internalize(arg, true); return literal(ctx.get_bool_var(arg)); } } void user_theory::assert_axioms_into_context(unsigned old_sz) { for (unsigned i = old_sz; i < m_asserted_axioms.size(); i++) { expr * axiom = m_asserted_axioms.get(i); assert_axiom_into_context(axiom); } } void user_theory::mark_as_relevant(literal l) { if (l == false_literal || l == true_literal) return; get_context().mark_as_relevant(l); } void user_theory::assert_axiom_into_context(expr * axiom) { TRACE("user_smt_theory", tout << mk_pp(axiom, get_manager()) << "\n";); ast_manager & m = get_manager(); context & ctx = get_context(); if (m.is_or(axiom)) { literal_buffer lits; unsigned num_args = to_app(axiom)->get_num_args(); for (unsigned i = 0; i < num_args; i++) { lits.push_back(internalize_literal(to_app(axiom)->get_arg(i))); mark_as_relevant(lits.back()); } ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); } else { literal l = internalize_literal(axiom); mark_as_relevant(l); ctx.mk_th_axiom(get_id(), 1, &l); } } void user_theory::propagate() { unsigned old_sz = m_asserted_axioms.size(); if (m_persisted_axioms_qhead < m_persisted_axioms.size()) { get_context().push_trail(value_trail(m_persisted_axioms_qhead)); for (; m_persisted_axioms_qhead < m_persisted_axioms.size(); ++m_persisted_axioms_qhead) { m_asserted_axioms.push_back(m_persisted_axioms[m_persisted_axioms_qhead].get()); } } do { for (unsigned i = 0; i < m_new_eqs.size(); i++) { var_pair & p = m_new_eqs[i]; if (m_new_eq_fptr != 0) { ++m_stats.m_num_eq; flet invoke(m_new_eq_invoking, true); m_new_eq_fptr(this, get_app(p.first), get_app(p.second)); } m_find.merge(p.first, p.second); } m_new_eqs.reset(); if (m_new_diseq_fptr != 0) { for (unsigned i = 0; i < m_new_diseqs.size(); i++) { ++m_stats.m_num_diseq; var_pair & p = m_new_diseqs[i]; flet invoke(m_new_diseq_invoking, true); m_new_diseq_fptr(this, get_app(p.first), get_app(p.second)); } } m_new_diseqs.reset(); if (m_new_assignment_fptr != 0) { context & ctx = get_context(); for (unsigned i = 0; i < m_new_assignments.size(); i++) { ++m_stats.m_num_assignment; bool_var bv = m_new_assignments[i]; lbool val = ctx.get_assignment(bv); SASSERT(val != l_undef); flet invoke(m_new_assignment_invoking, true); m_new_assignment_fptr(this, to_app(ctx.bool_var2expr(bv)), val == l_true); } } m_new_assignments.reset(); if (m_new_relevant_fptr != 0) { for (unsigned i = 0; i < m_new_relevant_apps.size(); i++) { flet invoke(m_new_relevant_invoking, true); m_new_relevant_fptr(this, m_new_relevant_apps[i]); } } m_new_relevant_apps.reset(); assert_axioms_into_context(old_sz); old_sz = m_asserted_axioms.size(); } while (!m_new_eqs.empty() || !m_new_diseqs.empty() || !m_new_relevant_apps.empty() || !m_new_assignments.empty()); } void user_theory::flush_eh() { reset(false); } void user_theory::reset_eh() { reset(true); } void user_theory::reset(bool full_reset) { if (m_reset_fptr != 0) { flet invoke(m_reset_invoking, true); m_reset_fptr(this); } m_trail_stack.reset(); reset_propagation_queues(); m_asserted_axioms.reset(); m_asserted_axiom_set.reset(); shrink_use_list(0); m_parents.reset(); m_scopes.reset(); m_persisted_axioms.reset(); m_persisted_axioms_qhead = 0; m_stats.reset(); if (full_reset) theory::reset_eh(); } void user_theory::display_statistics(std::ostream & out) const { print_stat(out, "num. user eqs: ", m_stats.m_num_eq); print_stat(out, "num. user diseq: ", m_stats.m_num_diseq); print_stat(out, "num. assignments: ", m_stats.m_num_assignment); print_stat(out, "num. user axioms: ", m_stats.m_num_user_axioms); } void user_theory::display_istatistics(std::ostream & out) const { out << "NUM_USER_EQS " << m_stats.m_num_eq << "\n"; out << "NUM_USER_DISEQ " << m_stats.m_num_diseq << "\n"; out << "NUM_ASSIGNMENTS " << m_stats.m_num_assignment << "\n"; out << "NUM_USER_AXIOMS " << m_stats.m_num_user_axioms << "\n"; } class user_smt_model_value_proc : public model_value_proc { func_decl_ref m_decl; public: user_smt_model_value_proc(ast_manager& m, func_decl* f) : m_decl(f, m) {} virtual app * mk_value(model_generator & mg, ptr_vector & values) { ast_manager& m = mg.get_manager(); return m.mk_app(m_decl, values.size(), values.c_ptr()); } }; bool user_theory::build_models() const { return true; } void user_theory::init_model(model_generator & m) { m.register_factory(alloc(user_smt_theory_factory, get_manager(), get_id())); } void user_theory::finalize_model(model_generator &) { // No-op } model_value_proc * user_theory::mk_value(enode * n, model_generator & mg) { ast_manager& m = get_manager(); func_decl* f = n->get_decl(); if (m_decl_plugin->is_value(f)) { return alloc(user_smt_model_value_proc, m, n->get_decl()); } else { return mg.mk_model_value(n); } } bool user_theory::get_value(enode * n, expr_ref & r) { return false; } char const * user_theory::get_name() const { return m_name.c_str(); } void user_theory::display(std::ostream & out) const { out << "Theory " << get_name() << ":\n"; } user_theory * mk_user_theory(solver & _s, void * ext_context, void * ext_data, char const * name) { context & ctx = _s.kernel(); // HACK symbol _name(name); ast_manager & m = ctx.get_manager(); family_id fid = m.get_family_id(_name); user_decl_plugin * dp = alloc(user_decl_plugin); m.register_plugin(fid, dp); simplifier & s = ctx.get_simplifier(); user_simplifier_plugin * sp = alloc(user_simplifier_plugin, _name, m); s.register_plugin(sp); user_theory * th = alloc(user_theory, m, ctx.get_fparams(), ext_context, ext_data, name, fid, dp, sp); ctx.register_plugin(th); sp->set_owner(th); return th; } };