/*++ Copyright (c) 2006 Microsoft Corporation Module Name: dl_mk_explanations.cpp Abstract: Author: Krystof Hoder (t-khoder) 2010-11-08. Revision History: --*/ #include #include"ast_pp.h" #include "ast_smt_pp.h" #include"dl_finite_product_relation.h" #include"dl_product_relation.h" #include"dl_sieve_relation.h" #include"dl_mk_explanations.h" namespace datalog { // ----------------------------------- // // explanation_relation_plugin declaration // // ----------------------------------- class explanation_relation; class explanation_relation_plugin : public relation_plugin { friend class explanation_relation; class join_fn; class project_fn; class rename_fn; class union_fn; class foreign_union_fn; class assignment_filter_fn; class negation_filter_fn; class intersection_filter_fn; bool m_relation_level_explanations; func_decl_ref m_union_decl; vector > m_pool; app * mk_union(app * a1, app * a2) { return get_ast_manager().mk_app(m_union_decl, a1, a2); } public: static symbol get_name(bool relation_level) { return symbol(relation_level ? "relation_explanation" : "fact_explanation"); } explanation_relation_plugin(bool relation_level, relation_manager & manager) : relation_plugin(get_name(relation_level), manager), m_relation_level_explanations(relation_level), m_union_decl(mk_explanations::get_union_decl(get_context()), get_ast_manager()) {} ~explanation_relation_plugin() { for (unsigned i = 0; i < m_pool.size(); ++i) { for (unsigned j = 0; j < m_pool[i].size(); ++j) { dealloc(m_pool[i][j]); } } } virtual bool can_handle_signature(const relation_signature & s) { unsigned n=s.size(); for(unsigned i=0; i(relation_base::get_plugin()); } virtual void to_formula(expr_ref& fml) const { ast_manager& m = fml.get_manager(); fml = m.mk_eq(m.mk_var(0, m.get_sort(m_data[0])), m_data[0]); } bool is_undefined(unsigned col_idx) const { return m_data[col_idx]==0; } bool no_undefined() const { if(empty()) { return true; } unsigned n = get_signature().size(); for(unsigned i=0; i(get_plugin().mk_empty(get_signature())); res->m_empty = m_empty; SASSERT(res->m_data.empty()); res->m_data.append(m_data); return res; } virtual relation_base * complement(func_decl* pred) const { explanation_relation * res = static_cast(get_plugin().mk_empty(get_signature())); if(empty()) { res->set_undefined(); } return res; } void display_explanation(app * expl, std::ostream & out) const { if(expl) { //TODO: some nice explanation output ast_smt_pp pp(get_plugin().get_ast_manager()); pp.display_expr_smt2(out, expl); } else { out << ""; } } virtual void display(std::ostream & out) const { if(empty()) { out << "\n"; return; } unsigned sz = get_signature().size(); for(unsigned i=0; i s.size() && !m_pool[s.size()].empty()) { explanation_relation* r = m_pool[s.size()].back(); m_pool[s.size()].pop_back(); r->m_empty = true; r->m_data.reset(); return r; } return alloc(explanation_relation, *this, s); } void explanation_relation_plugin::recycle(explanation_relation* r) { relation_signature const& sig = r->get_signature(); if (m_pool.size() <= sig.size()) { m_pool.resize(sig.size()+1); } m_pool[sig.size()].push_back(r); } class explanation_relation_plugin::join_fn : public convenient_relation_join_fn { public: join_fn(const relation_signature & sig1, const relation_signature & sig2) : convenient_relation_join_fn(sig1, sig2, 0, 0, 0) {} virtual relation_base * operator()(const relation_base & r1_0, const relation_base & r2_0) { const explanation_relation & r1 = static_cast(r1_0); const explanation_relation & r2 = static_cast(r2_0); explanation_relation_plugin & plugin = r1.get_plugin(); explanation_relation * res = static_cast(plugin.mk_empty(get_result_signature())); if(!r1.empty() && !r2.empty()) { res->m_empty = false; SASSERT(res->m_data.empty()); res->m_data.append(r1.m_data); res->m_data.append(r2.m_data); } return res; } }; relation_join_fn * explanation_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { if(&r1.get_plugin()!=this || &r2.get_plugin()!=this) { return 0; } if(col_cnt!=0) { return 0; } return alloc(join_fn, r1.get_signature(), r2.get_signature()); } class explanation_relation_plugin::project_fn : public convenient_relation_project_fn { public: project_fn(const relation_signature & sig, unsigned col_cnt, const unsigned * removed_cols) : convenient_relation_project_fn(sig, col_cnt, removed_cols) {} virtual relation_base * operator()(const relation_base & r0) { const explanation_relation & r = static_cast(r0); explanation_relation_plugin & plugin = r.get_plugin(); explanation_relation * res = static_cast(plugin.mk_empty(get_result_signature())); if(!r.empty()) { relation_fact proj_data = r.m_data; project_out_vector_columns(proj_data, m_removed_cols); res->assign_data(proj_data); } return res; } }; relation_transformer_fn * explanation_relation_plugin::mk_project_fn(const relation_base & r, unsigned col_cnt, const unsigned * removed_cols) { if(&r.get_plugin()!=this) { return 0; } return alloc(project_fn, r.get_signature(), col_cnt, removed_cols); } class explanation_relation_plugin::rename_fn : public convenient_relation_rename_fn { public: rename_fn(const relation_signature & sig, unsigned permutation_cycle_len, const unsigned * permutation_cycle) : convenient_relation_rename_fn(sig, permutation_cycle_len, permutation_cycle) {} virtual relation_base * operator()(const relation_base & r0) { const explanation_relation & r = static_cast(r0); explanation_relation_plugin & plugin = r.get_plugin(); explanation_relation * res = static_cast(plugin.mk_empty(get_result_signature())); if(!r.empty()) { relation_fact permutated_data = r.m_data; permutate_by_cycle(permutated_data, m_cycle); res->assign_data(permutated_data); } return res; } }; relation_transformer_fn * explanation_relation_plugin::mk_rename_fn(const relation_base & r, unsigned permutation_cycle_len, const unsigned * permutation_cycle) { return alloc(rename_fn, r.get_signature(), permutation_cycle_len, permutation_cycle); } class explanation_relation_plugin::union_fn : public relation_union_fn { scoped_ptr m_delta_union_fun; public: virtual void operator()(relation_base & tgt0, const relation_base & src0, relation_base * delta0) { explanation_relation & tgt = static_cast(tgt0); const explanation_relation & src = static_cast(src0); explanation_relation * delta = delta0 ? static_cast(delta0) : 0; explanation_relation_plugin & plugin = tgt.get_plugin(); if(!src.no_undefined() || !tgt.no_undefined() || (delta && !delta->no_undefined())) { UNREACHABLE(); } if(src.empty()) { return; } if(plugin.m_relation_level_explanations) { tgt.unite_with_data(src.m_data); if(delta) { if(!m_delta_union_fun) { m_delta_union_fun = plugin.get_manager().mk_union_fn(*delta, src); SASSERT(m_delta_union_fun); } (*m_delta_union_fun)(*delta, src); } } else { if(tgt.empty()) { tgt.assign_data(src.m_data); if(delta && delta->empty()) { delta->assign_data(src.m_data); } } } } }; class explanation_relation_plugin::foreign_union_fn : public relation_union_fn { scoped_ptr m_delta_union_fun; public: virtual void operator()(relation_base & tgt0, const relation_base & src, relation_base * delta0) { explanation_relation & tgt = static_cast(tgt0); explanation_relation * delta = delta0 ? static_cast(delta0) : 0; if(src.empty()) { return; } tgt.set_undefined(); if(delta) { delta->set_undefined(); } } }; relation_union_fn * explanation_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, const relation_base * delta) { if(!check_kind(tgt) || (delta && !check_kind(*delta))) { return 0; } if(!check_kind(src)) { //this is to handle the product relation return alloc(foreign_union_fn); } return alloc(union_fn); } class explanation_relation_plugin::assignment_filter_fn : public relation_mutator_fn { ast_manager & m_manager; var_subst & m_subst; unsigned m_col_idx; app_ref m_new_rule; public: assignment_filter_fn(context & ctx, unsigned col_idx, app_ref new_rule) : m_manager(ctx.get_manager()), m_subst(ctx.get_var_subst()), m_col_idx(col_idx), m_new_rule(new_rule) {} virtual void operator()(relation_base & r0) { explanation_relation & r = static_cast(r0); if(!r.is_undefined(m_col_idx)) { UNREACHABLE(); } unsigned sz = r.get_signature().size(); ptr_vector subst_arg; subst_arg.resize(sz, 0); unsigned ofs = sz-1; for(unsigned i=0; iget_arg(0); expr * arg2 = cond->get_arg(1); if(is_var(arg2)) { std::swap(arg1, arg2); } if(!is_var(arg1) || !is_app(arg2)) { return 0; } var * col_var = to_var(arg1); app * new_rule = to_app(arg2); if(!get_context().get_decl_util().is_rule_sort(col_var->get_sort())) { return 0; } unsigned col_idx = col_var->get_idx(); return alloc(assignment_filter_fn, get_context(), col_idx, app_ref(new_rule, get_ast_manager())); } class explanation_relation_plugin::negation_filter_fn : public relation_intersection_filter_fn { public: virtual void operator()(relation_base & r, const relation_base & neg) { if(!neg.empty()) { r.reset(); } } }; relation_intersection_filter_fn * explanation_relation_plugin::mk_filter_by_negation_fn(const relation_base & r, const relation_base & neg, unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols) { if(&r.get_plugin()!=this || &neg.get_plugin()!=this) { return 0; } return alloc(negation_filter_fn); } class explanation_relation_plugin::intersection_filter_fn : public relation_intersection_filter_fn { explanation_relation_plugin & m_plugin; func_decl_ref m_union_decl; public: intersection_filter_fn(explanation_relation_plugin & plugin) : m_plugin(plugin), m_union_decl(plugin.m_union_decl) {} virtual void operator()(relation_base & tgt0, const relation_base & src0) { explanation_relation & tgt = static_cast(tgt0); const explanation_relation & src = static_cast(src0); if(src.empty()) { tgt.reset(); return; } if(tgt.empty()) { return; } unsigned sz = tgt.get_signature().size(); for(unsigned i=0; iget_decl()==m_union_decl.get()) { if(curr_tgt->get_arg(0)==curr_src || curr_tgt->get_arg(1)==curr_src) { tgt.m_data.set(i, curr_src); continue; } } //the intersection is imprecise because we do nothing here, but it is good enough for //the purpose of explanations } } }; relation_intersection_filter_fn * explanation_relation_plugin::mk_filter_by_intersection_fn( const relation_base & tgt, const relation_base & src, unsigned joined_col_cnt, const unsigned * tgt_cols, const unsigned * src_cols) { if(&tgt.get_plugin()!=this || &src.get_plugin()!=this) { return 0; } //this checks the join is one to one on all columns if(tgt.get_signature()!=src.get_signature() || joined_col_cnt!=tgt.get_signature().size() || !containers_equal(tgt_cols, tgt_cols+joined_col_cnt, src_cols, src_cols+joined_col_cnt)) { return 0; } counter ctr; ctr.count(joined_col_cnt, tgt_cols); if(ctr.get_max_counter_value()>1 || (joined_col_cnt && ctr.get_max_positive()!=joined_col_cnt-1)) { return 0; } return alloc(intersection_filter_fn, *this); } // ----------------------------------- // // mk_explanations // // ----------------------------------- mk_explanations::mk_explanations(context & ctx, bool relation_level) : plugin(50000), m_manager(ctx.get_manager()), m_context(ctx), m_decl_util(ctx.get_decl_util()), m_relation_level(relation_level), m_pinned(m_manager) { m_e_sort = m_decl_util.mk_rule_sort(); m_pinned.push_back(m_e_sort); relation_manager & rmgr = ctx.get_rmanager(); symbol er_symbol = explanation_relation_plugin::get_name(relation_level); m_er_plugin = static_cast(rmgr.get_relation_plugin(er_symbol)); if(!m_er_plugin) { m_er_plugin = alloc(explanation_relation_plugin, relation_level, rmgr); rmgr.register_plugin(m_er_plugin); if(!m_relation_level) { DEBUG_CODE( finite_product_relation_plugin * dummy; SASSERT(!rmgr.try_get_finite_product_relation_plugin(*m_er_plugin, dummy)); ); rmgr.register_plugin(alloc(finite_product_relation_plugin, *m_er_plugin, rmgr)); } } DEBUG_CODE( if(!m_relation_level) { finite_product_relation_plugin * dummy; SASSERT(rmgr.try_get_finite_product_relation_plugin(*m_er_plugin, dummy)); } ); } func_decl * mk_explanations::get_union_decl(context & ctx) { ast_manager & m = ctx.get_manager(); sort_ref s(ctx.get_decl_util().mk_rule_sort(), m); //can it happen that the function name would collide with some other symbol? //if functions can be overloaded by their ranges, it should be fine. return m.mk_func_decl(symbol("e_union"), s, s, s); } void mk_explanations::assign_rel_level_kind(func_decl * e_decl, func_decl * orig) { SASSERT(m_relation_level); relation_manager & rmgr = m_context.get_rmanager(); unsigned sz = e_decl->get_arity(); relation_signature sig; rmgr.from_predicate(e_decl, sig); svector inner_sieve(sz-1, true); inner_sieve.push_back(false); svector expl_sieve(sz-1, false); expl_sieve.push_back(true); sieve_relation_plugin & sieve_plugin = sieve_relation_plugin::get_plugin(rmgr); family_id inner_kind = rmgr.get_requested_predicate_kind(orig); //may be null_family_id family_id inner_sieve_kind = sieve_plugin.get_relation_kind(sig, inner_sieve, inner_kind); family_id expl_kind = m_er_plugin->get_kind(); family_id expl_sieve_kind = sieve_plugin.get_relation_kind(sig, expl_sieve, expl_kind); product_relation_plugin::rel_spec product_spec; product_spec.push_back(inner_sieve_kind); product_spec.push_back(expl_sieve_kind); family_id pred_kind = product_relation_plugin::get_plugin(rmgr).get_relation_kind(sig, product_spec); rmgr.set_predicate_kind(e_decl, pred_kind); } func_decl * mk_explanations::get_e_decl(func_decl * orig_decl) { decl_map::obj_map_entry * e = m_e_decl_map.insert_if_not_there2(orig_decl, 0); if(e->get_data().m_value==0) { relation_signature e_domain; e_domain.append(orig_decl->get_arity(), orig_decl->get_domain()); e_domain.push_back(m_e_sort); func_decl * new_decl = m_context.mk_fresh_head_predicate(orig_decl->get_name(), symbol("expl"), e_domain.size(), e_domain.c_ptr(), orig_decl); m_pinned.push_back(new_decl); e->get_data().m_value = new_decl; if(m_relation_level) { assign_rel_level_kind(new_decl, orig_decl); } } return e->get_data().m_value; } app * mk_explanations::get_e_lit(app * lit, unsigned e_var_idx) { expr_ref_vector args(m_manager); func_decl * e_decl = get_e_decl(lit->get_decl()); args.append(lit->get_num_args(), lit->get_args()); args.push_back(m_manager.mk_var(e_var_idx, m_e_sort)); return m_manager.mk_app(e_decl, args.c_ptr()); } symbol mk_explanations::get_rule_symbol(rule * r) { if (r->name() == symbol::null) { std::stringstream sstm; r->display(m_context, sstm); std::string res = sstm.str(); res = res.substr(0, res.find_last_not_of('\n')+1); return symbol(res.c_str()); } else { return r->name(); } } rule * mk_explanations::get_e_rule(rule * r) { var_counter ctr; ctr.count_vars(m_manager, r); unsigned max_var; unsigned next_var = ctr.get_max_positive(max_var) ? (max_var+1) : 0; unsigned head_var = next_var++; app_ref e_head(get_e_lit(r->get_head(), head_var), m_manager); app_ref_vector e_tail(m_manager); svector neg_flags; unsigned pos_tail_sz = r->get_positive_tail_size(); for(unsigned i=0; iget_tail(i), e_var)); neg_flags.push_back(false); } unsigned tail_sz = r->get_tail_size(); for(unsigned i=pos_tail_sz; iget_tail(i)); neg_flags.push_back(r->is_neg_tail(i)); } symbol rule_repr = get_rule_symbol(r); expr_ref_vector rule_expr_args(m_manager); for(unsigned tail_idx=0; tail_idxget_arg(tail->get_num_args()-1)); } else { //this adds argument values and the explanation term //(values will be substituted for variables at runtime by the finite_product_relation) rule_expr_args.append(tail->get_num_args(), tail->get_args()); } } //rule_expr contains rule function with string representation of the rule as symbol and //for each positive uninterpreted tail it contains its argument values and its explanation term expr * rule_expr = m_decl_util.mk_rule(rule_repr, rule_expr_args.size(), rule_expr_args.c_ptr()); app_ref e_record(m_manager.mk_eq(m_manager.mk_var(head_var, m_e_sort), rule_expr), m_manager); e_tail.push_back(e_record); neg_flags.push_back(false); SASSERT(e_tail.size()==neg_flags.size()); return m_context.get_rule_manager().mk(e_head, e_tail.size(), e_tail.c_ptr(), neg_flags.c_ptr()); } void mk_explanations::transform_rules(const rule_set & orig, rule_set & tgt) { rule_set::iterator rit = orig.begin(); rule_set::iterator rend = orig.end(); for(; rit!=rend; ++rit) { rule * e_rule = get_e_rule(*rit); tgt.add_rule(e_rule); } //add rules that will (for output predicates) copy facts from explained relations back to //the original ones expr_ref_vector lit_args(m_manager); decl_set::iterator pit = m_original_preds.begin(); decl_set::iterator pend = m_original_preds.end(); for(; pit!=pend; ++pit) { func_decl * orig_decl = *pit; if(!m_context.is_output_predicate(orig_decl)) { continue; } lit_args.reset(); unsigned arity = orig_decl->get_arity(); for(unsigned i=0; iget_domain(i))); } app_ref orig_lit(m_manager.mk_app(orig_decl, lit_args.c_ptr()), m_manager); app_ref e_lit(get_e_lit(orig_lit, arity), m_manager); app * tail[] = { e_lit.get() }; tgt.add_rule(m_context.get_rule_manager().mk(orig_lit, 1, tail, 0)); } } void mk_explanations::translate_rel_level_relation(relation_manager & rmgr, relation_base & orig, relation_base & e_rel) { SASSERT(m_e_fact_relation); SASSERT(e_rel.get_plugin().is_product_relation()); product_relation & prod_rel = static_cast(e_rel); SASSERT(prod_rel.size()==2); SASSERT(prod_rel[0].get_plugin().is_sieve_relation()); SASSERT(prod_rel[1].get_plugin().is_sieve_relation()); sieve_relation * srels[] = { static_cast(&prod_rel[0]), static_cast(&prod_rel[1]) }; if(&srels[0]->get_inner().get_plugin()==m_er_plugin) { std::swap(srels[0], srels[1]); } SASSERT(&srels[0]->get_inner().get_plugin()==&orig.get_plugin()); SASSERT(&srels[1]->get_inner().get_plugin()==m_er_plugin); relation_base & new_orig = srels[0]->get_inner(); explanation_relation & expl_rel = static_cast(srels[1]->get_inner()); { scoped_ptr orig_union_fun = rmgr.mk_union_fn(new_orig, orig); SASSERT(orig_union_fun); (*orig_union_fun)(new_orig, orig); } { scoped_ptr expl_union_fun = rmgr.mk_union_fn(expl_rel, *m_e_fact_relation); SASSERT(expl_union_fun); (*expl_union_fun)(expl_rel, *m_e_fact_relation); } } void mk_explanations::transform_facts(relation_manager & rmgr) { if(!m_e_fact_relation) { relation_signature expl_singleton_sig; expl_singleton_sig.push_back(m_e_sort); relation_base * expl_singleton = rmgr.mk_empty_relation(expl_singleton_sig, m_er_plugin->get_kind()); relation_fact es_fact(m_manager); es_fact.push_back(m_decl_util.mk_fact(symbol("fact"))); expl_singleton->add_fact(es_fact); SASSERT(&expl_singleton->get_plugin()==m_er_plugin); m_e_fact_relation = static_cast(expl_singleton); } decl_set::iterator it = m_original_preds.begin(); decl_set::iterator end = m_original_preds.end(); for(; it!=end; ++it) { func_decl * orig_decl = *it; func_decl * e_decl = get_e_decl(orig_decl); if(m_context.is_output_predicate(orig_decl)) { m_context.set_output_predicate(e_decl); } if(!rmgr.try_get_relation(orig_decl)) { //there are no facts for this predicate continue; } relation_base & orig_rel = rmgr.get_relation(orig_decl); relation_base & e_rel = rmgr.get_relation(e_decl); SASSERT(e_rel.empty()); //the e_rel should be a new relation if(m_relation_level) { translate_rel_level_relation(rmgr, orig_rel, e_rel); } else { scoped_ptr product_fun = rmgr.mk_join_fn(orig_rel, *m_e_fact_relation, 0, 0, 0); SASSERT(product_fun); scoped_rel aux_extended_rel = (*product_fun)(orig_rel, *m_e_fact_relation); scoped_ptr union_fun = rmgr.mk_union_fn(e_rel, *aux_extended_rel); SASSERT(union_fun); (*union_fun)(e_rel, *aux_extended_rel); } } } rule_set * mk_explanations::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { SASSERT(!mc && !pc); if(source.get_num_rules()==0) { return 0; } m_context.collect_predicates(m_original_preds); rule_set * res = alloc(rule_set, m_context); transform_facts(m_context.get_rmanager()); transform_rules(source, *res); return res; } };