3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-09-06 01:48:02 +00:00

re-organize muz_qe into separate units

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2013-08-28 21:20:24 -07:00
parent 4597872be8
commit 0d56499e2d
131 changed files with 994 additions and 20069 deletions

View file

@ -0,0 +1,280 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_array_blast.cpp
Abstract:
Remove array stores from rules.
Author:
Nikolaj Bjorner (nbjorner) 2012-11-23
Revision History:
--*/
#include "dl_mk_array_blast.h"
#include "qe_util.h"
namespace datalog {
mk_array_blast::mk_array_blast(context & ctx, unsigned priority) :
rule_transformer::plugin(priority, false),
m_ctx(ctx),
m(ctx.get_manager()),
a(m),
rm(ctx.get_rule_manager()),
m_rewriter(m, m_params),
m_simplifier(ctx),
m_sub(m),
m_next_var(0) {
m_params.set_bool("expand_select_store",true);
m_rewriter.updt_params(m_params);
}
mk_array_blast::~mk_array_blast() {
}
bool mk_array_blast::is_store_def(expr* e, expr*& x, expr*& y) {
if (m.is_iff(e, x, y) || m.is_eq(e, x, y)) {
if (!a.is_store(y)) {
std::swap(x,y);
}
if (is_var(x) && a.is_store(y)) {
return true;
}
}
return false;
}
expr* mk_array_blast::get_select(expr* e) const {
while (a.is_select(e)) {
e = to_app(e)->get_arg(0);
}
return e;
}
void mk_array_blast::get_select_args(expr* e, ptr_vector<expr>& args) const {
while (a.is_select(e)) {
app* ap = to_app(e);
for (unsigned i = 1; i < ap->get_num_args(); ++i) {
args.push_back(ap->get_arg(i));
}
e = ap->get_arg(0);
}
}
bool mk_array_blast::insert_def(rule const& r, app* e, var* v) {
//
// For the Ackermann reduction we would like the arrays
// to be variables, so that variables can be
// assumed to represent difference (alias)
// classes. Ehm., Soundness of this approach depends on
// if the arrays are finite domains...
//
if (!is_var(get_select(e))) {
return false;
}
if (v) {
m_sub.insert(e, v);
m_defs.insert(e, to_var(v));
}
else {
if (m_next_var == 0) {
ptr_vector<sort> vars;
r.get_vars(vars);
m_next_var = vars.size() + 1;
}
v = m.mk_var(m_next_var, m.get_sort(e));
m_sub.insert(e, v);
m_defs.insert(e, v);
++m_next_var;
}
return true;
}
bool mk_array_blast::ackermanize(rule const& r, expr_ref& body, expr_ref& head) {
expr_ref_vector conjs(m);
qe::flatten_and(body, conjs);
m_defs.reset();
m_sub.reset();
m_next_var = 0;
ptr_vector<expr> todo;
todo.push_back(head);
for (unsigned i = 0; i < conjs.size(); ++i) {
expr* e = conjs[i].get();
expr* x, *y;
if (m.is_eq(e, x, y) || m.is_iff(e, x, y)) {
if (a.is_select(y)) {
std::swap(x,y);
}
if (a.is_select(x) && is_var(y)) {
if (!insert_def(r, to_app(x), to_var(y))) {
return false;
}
}
}
if (a.is_select(e) && !insert_def(r, to_app(e), 0)) {
return false;
}
todo.push_back(e);
}
// now make sure to cover all occurrences.
ast_mark mark;
while (!todo.empty()) {
expr* e = todo.back();
todo.pop_back();
if (mark.is_marked(e)) {
continue;
}
mark.mark(e, true);
if (is_var(e)) {
continue;
}
if (!is_app(e)) {
return false;
}
app* ap = to_app(e);
if (a.is_select(ap) && !m_defs.contains(ap)) {
if (!insert_def(r, ap, 0)) {
return false;
}
}
if (a.is_select(e)) {
get_select_args(e, todo);
continue;
}
for (unsigned i = 0; i < ap->get_num_args(); ++i) {
todo.push_back(ap->get_arg(i));
}
}
m_sub(body);
m_sub(head);
conjs.reset();
// perform the Ackermann reduction by creating implications
// i1 = i2 => val1 = val2 for each equality pair:
// (= val1 (select a_i i1))
// (= val2 (select a_i i2))
defs_t::iterator it1 = m_defs.begin(), end = m_defs.end();
for (; it1 != end; ++it1) {
app* a1 = it1->m_key;
var* v1 = it1->m_value;
defs_t::iterator it2 = it1;
++it2;
for (; it2 != end; ++it2) {
app* a2 = it2->m_key;
var* v2 = it2->m_value;
if (get_select(a1) != get_select(a2)) {
continue;
}
expr_ref_vector eqs(m);
ptr_vector<expr> args1, args2;
get_select_args(a1, args1);
get_select_args(a2, args2);
for (unsigned j = 0; j < args1.size(); ++j) {
eqs.push_back(m.mk_eq(args1[j], args2[j]));
}
conjs.push_back(m.mk_implies(m.mk_and(eqs.size(), eqs.c_ptr()), m.mk_eq(v1, v2)));
}
}
if (!conjs.empty()) {
conjs.push_back(body);
body = m.mk_and(conjs.size(), conjs.c_ptr());
}
m_rewriter(body);
return true;
}
bool mk_array_blast::blast(rule& r, rule_set& rules) {
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
expr_ref_vector conjs(m), new_conjs(m);
expr_ref tmp(m);
expr_safe_replace sub(m);
bool change = false;
bool inserted = false;
for (unsigned i = 0; i < utsz; ++i) {
new_conjs.push_back(r.get_tail(i));
}
for (unsigned i = utsz; i < tsz; ++i) {
conjs.push_back(r.get_tail(i));
}
qe::flatten_and(conjs);
for (unsigned i = 0; i < conjs.size(); ++i) {
expr* x, *y, *e = conjs[i].get();
if (is_store_def(e, x, y)) {
// enforce topological order consistency:
uint_set lhs = rm.collect_vars(x);
uint_set rhs_vars = rm.collect_vars(y);
lhs &= rhs_vars;
if (!lhs.empty()) {
TRACE("dl", tout << "unusable equality " << mk_pp(e, m) << "\n";);
new_conjs.push_back(e);
}
else {
sub.insert(x, y);
inserted = true;
}
}
else {
m_rewriter(e, tmp);
change = change || (tmp != e);
new_conjs.push_back(tmp);
}
}
expr_ref fml2(m), body(m), head(m);
body = m.mk_and(new_conjs.size(), new_conjs.c_ptr());
head = r.get_head();
sub(body);
m_rewriter(body);
sub(head);
m_rewriter(head);
change = ackermanize(r, body, head) || change;
if (!inserted && !change) {
rules.add_rule(&r);
return false;
}
fml2 = m.mk_implies(body, head);
proof_ref p(m);
rule_set new_rules(m_ctx);
rm.mk_rule(fml2, p, new_rules, r.name());
rule_ref new_rule(rm);
if (m_simplifier.transform_rule(new_rules.last(), new_rule)) {
rules.add_rule(new_rule.get());
rm.mk_rule_rewrite_proof(r, *new_rule.get());
TRACE("dl", new_rule->display(m_ctx, tout << "new rule\n"););
}
return true;
}
rule_set * mk_array_blast::operator()(rule_set const & source) {
rule_set* rules = alloc(rule_set, m_ctx);
rules->inherit_predicates(source);
rule_set::iterator it = source.begin(), end = source.end();
bool change = false;
for (; !m_ctx.canceled() && it != end; ++it) {
change = blast(**it, *rules) || change;
}
if (!change) {
dealloc(rules);
rules = 0;
}
return rules;
}
};

View file

@ -0,0 +1,77 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_array_blast.h
Abstract:
Remove array variables from rules.
Author:
Nikolaj Bjorner (nbjorner) 2012-11-23
Revision History:
--*/
#ifndef _DL_MK_ARRAY_BLAST_H_
#define _DL_MK_ARRAY_BLAST_H_
#include"dl_context.h"
#include"dl_rule_set.h"
#include"dl_rule_transformer.h"
#include"dl_mk_interp_tail_simplifier.h"
#include "equiv_proof_converter.h"
#include "array_decl_plugin.h"
#include "expr_safe_replace.h"
namespace datalog {
/**
\brief Blast occurrences of arrays in rules
*/
class mk_array_blast : public rule_transformer::plugin {
typedef obj_map<app, var*> defs_t;
context& m_ctx;
ast_manager& m;
array_util a;
rule_manager& rm;
params_ref m_params;
th_rewriter m_rewriter;
mk_interp_tail_simplifier m_simplifier;
defs_t m_defs;
expr_safe_replace m_sub;
unsigned m_next_var;
bool blast(rule& r, rule_set& new_rules);
bool is_store_def(expr* e, expr*& x, expr*& y);
bool ackermanize(rule const& r, expr_ref& body, expr_ref& head);
expr* get_select(expr* e) const;
void get_select_args(expr* e, ptr_vector<expr>& args) const;
bool insert_def(rule const& r, app* e, var* v);
public:
/**
\brief Create rule transformer that removes array stores and selects by ackermannization.
*/
mk_array_blast(context & ctx, unsigned priority);
virtual ~mk_array_blast();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_ARRAY_BLAST_H_ */

View file

@ -0,0 +1,78 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_backwards.cpp
Abstract:
Create Horn clauses for backwards flow.
Author:
Nikolaj Bjorner (nbjorner) 2013-04-17
Revision History:
--*/
#include"dl_mk_backwards.h"
#include"dl_context.h"
namespace datalog {
mk_backwards::mk_backwards(context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx) {
}
mk_backwards::~mk_backwards() { }
rule_set * mk_backwards::operator()(rule_set const & source) {
context& ctx = source.get_context();
rule_manager& rm = source.get_rule_manager();
rule_set * result = alloc(rule_set, ctx);
unsigned sz = source.get_num_rules();
rule_ref new_rule(rm);
app_ref_vector tail(m);
app_ref head(m);
svector<bool> neg;
app_ref query(m);
query = m.mk_fresh_const("Q", m.mk_bool_sort());
result->set_output_predicate(query->get_decl());
m_ctx.register_predicate(query->get_decl(), false);
for (unsigned i = 0; i < sz; ++i) {
tail.reset();
neg.reset();
rule & r = *source.get_rule(i);
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
if (!source.is_output_predicate(r.get_decl())) {
tail.push_back(r.get_head());
neg.push_back(false);
}
for (unsigned j = utsz; j < tsz; ++j) {
tail.push_back(r.get_tail(j));
neg.push_back(false);
}
for (unsigned j = 0; j <= utsz; ++j) {
if (j == utsz && j > 0) {
break;
}
if (j == utsz) {
head = query;
}
else {
head = r.get_tail(j);
}
new_rule = rm.mk(head, tail.size(), tail.c_ptr(), neg.c_ptr(), r.name(), true);
result->add_rule(new_rule);
}
}
TRACE("dl", result->display(tout););
return result;
}
};

View file

@ -0,0 +1,38 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_backwards.h
Abstract:
Create Horn clauses for backwards flow.
Author:
Nikolaj Bjorner (nbjorner) 2013-04-17
Revision History:
--*/
#ifndef _DL_MK_BACKWARDS_H_
#define _DL_MK_BACKWARDS_H_
#include"dl_rule_transformer.h"
namespace datalog {
class mk_backwards : public rule_transformer::plugin {
ast_manager& m;
context& m_ctx;
public:
mk_backwards(context & ctx, unsigned priority = 33000);
~mk_backwards();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_BACKWARDS_H_ */

View file

@ -0,0 +1,318 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_bit_blast.cpp
Abstract:
<abstract>
Author:
Nikolaj Bjorner (nbjorner) 2012-08-30
Revision History:
--*/
#include "dl_mk_bit_blast.h"
#include "bit_blaster_rewriter.h"
#include "rewriter_def.h"
#include "ast_pp.h"
#include "expr_safe_replace.h"
#include "filter_model_converter.h"
#include "dl_mk_interp_tail_simplifier.h"
namespace datalog {
//
// P(v) :- Q(extract[1:1]v ++ 0), R(1 ++ extract[0:0]v).
// ->
// P(bv(x,y)) :- Q(bv(x,0)), R(bv(1,y)) .
//
// Introduce P_bv:
// P_bv(x,y) :- Q_bv(x,0), R_bv(1,y)
// P(bv(x,y)) :- P_bv(x,y)
// Query
// this model converter should be composed with a filter converter
// that gets rid of the new functions.
class bit_blast_model_converter : public model_converter {
ast_manager& m;
bv_util m_bv;
func_decl_ref_vector m_old_funcs;
func_decl_ref_vector m_new_funcs;
public:
bit_blast_model_converter(ast_manager& m):
m(m),
m_bv(m),
m_old_funcs(m),
m_new_funcs(m) {}
void insert(func_decl* old_f, func_decl* new_f) {
m_old_funcs.push_back(old_f);
m_new_funcs.push_back(new_f);
}
virtual model_converter * translate(ast_translation & translator) {
return alloc(bit_blast_model_converter, m);
}
virtual void operator()(model_ref & model) {
for (unsigned i = 0; i < m_new_funcs.size(); ++i) {
func_decl* p = m_new_funcs[i].get();
func_decl* q = m_old_funcs[i].get();
func_interp* f = model->get_func_interp(p);
expr_ref body(m);
unsigned arity_p = p->get_arity();
unsigned arity_q = q->get_arity();
SASSERT(0 < arity_p);
model->register_decl(p, f);
func_interp* g = alloc(func_interp, m, arity_q);
if (f) {
body = f->get_interp();
SASSERT(!f->is_partial());
SASSERT(body);
}
else {
body = m.mk_false();
}
unsigned idx = 0;
expr_ref arg(m), proj(m);
expr_safe_replace sub(m);
for (unsigned j = 0; j < arity_q; ++j) {
sort* s = q->get_domain(j);
arg = m.mk_var(j, s);
if (m_bv.is_bv_sort(s)) {
expr* args[1] = { arg };
unsigned sz = m_bv.get_bv_size(s);
for (unsigned k = 0; k < sz; ++k) {
proj = m.mk_app(m_bv.get_family_id(), OP_BIT2BOOL, 1, args);
sub.insert(m.mk_var(idx++, m.mk_bool_sort()), proj);
}
}
else {
sub.insert(m.mk_var(idx++, s), arg);
}
}
sub(body);
g->set_else(body);
model->register_decl(q, g);
}
}
};
class expand_mkbv_cfg : public default_rewriter_cfg {
context& m_context;
ast_manager& m;
bv_util m_util;
expr_ref_vector m_args, m_f_vars, m_g_vars;
func_decl_ref_vector m_old_funcs;
func_decl_ref_vector m_new_funcs;
rule_set const* m_src;
rule_set* m_dst;
obj_map<func_decl,func_decl*> m_pred2blast;
public:
expand_mkbv_cfg(context& ctx):
m_context(ctx),
m(ctx.get_manager()),
m_util(m),
m_args(m),
m_f_vars(m),
m_g_vars(m),
m_old_funcs(m),
m_new_funcs(m),
m_src(0),
m_dst(0)
{}
~expand_mkbv_cfg() {}
void set_src(rule_set const* src) { m_src = src; }
void set_dst(rule_set* dst) { m_dst = dst; }
func_decl_ref_vector const& old_funcs() const { return m_old_funcs; }
func_decl_ref_vector const& new_funcs() const { return m_new_funcs; }
br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) {
if (num == 0) {
if (m_src->is_output_predicate(f))
m_dst->set_output_predicate(f);
return BR_FAILED;
}
for (unsigned i = 0; i < num; ++i) {
if (!m_util.is_mkbv(args[i]))
return BR_FAILED;
}
//
// f(mk_bv(args),...)
//
m_args.reset();
m_g_vars.reset();
m_f_vars.reset();
expr_ref fml(m);
unsigned idx = 0;
for (unsigned j = 0; j < num; ++j) {
expr* arg = args[j];
if (m_util.is_mkbv(arg)) {
app* a = to_app(arg);
unsigned sz = a->get_num_args();
for (unsigned i = 0; i < sz; ++i) {
m_args.push_back(a->get_arg(i));
m_g_vars.push_back(m.mk_var(idx++,m.mk_bool_sort()));
}
m_f_vars.push_back(m_util.mk_bv(sz, m_g_vars.c_ptr()+m_g_vars.size()-sz));
}
else {
m_args.push_back(arg);
m_f_vars.push_back(m.mk_var(idx++, m.get_sort(arg)));
m_g_vars.push_back(m_f_vars.back());
}
}
func_decl* g = 0;
if (!m_pred2blast.find(f, g)) {
ptr_vector<sort> domain;
for (unsigned i = 0; i < m_args.size(); ++i) {
domain.push_back(m.get_sort(m_args[i].get()));
}
g = m_context.mk_fresh_head_predicate(f->get_name(), symbol("bv"), m_args.size(), domain.c_ptr(), f);
m_old_funcs.push_back(f);
m_new_funcs.push_back(g);
m_pred2blast.insert(f, g);
m_dst->inherit_predicate(*m_src, f, g);
}
result = m.mk_app(g, m_args.size(), m_args.c_ptr());
result_pr = 0;
return BR_DONE;
}
};
struct expand_mkbv : public rewriter_tpl<expand_mkbv_cfg> {
expand_mkbv_cfg m_cfg;
expand_mkbv(ast_manager& m, context& ctx):
rewriter_tpl<expand_mkbv_cfg>(m, m.proofs_enabled(), m_cfg),
m_cfg(ctx) {
}
};
class mk_bit_blast::impl {
context & m_context;
ast_manager & m;
params_ref m_params;
mk_interp_tail_simplifier m_simplifier;
bit_blaster_rewriter m_blaster;
expand_mkbv m_rewriter;
bool blast(rule *r, expr_ref& fml) {
proof_ref pr(m);
expr_ref fml1(m), fml2(m), fml3(m);
rule_ref r2(m_context.get_rule_manager());
// We need to simplify rule before bit-blasting.
if (!m_simplifier.transform_rule(r, r2)) {
r2 = r;
}
r2->to_formula(fml1);
m_blaster(fml1, fml2, pr);
m_rewriter(fml2, fml3);
TRACE("dl", tout << mk_pp(fml, m) << " -> " << mk_pp(fml2, m) << " -> " << mk_pp(fml3, m) << "\n";);
if (fml3 != fml) {
fml = fml3;
return true;
}
else {
return false;
}
}
public:
impl(context& ctx):
m_context(ctx),
m(ctx.get_manager()),
m_params(ctx.get_params().p),
m_simplifier(ctx),
m_blaster(ctx.get_manager(), m_params),
m_rewriter(ctx.get_manager(), ctx) {
m_params.set_bool("blast_full", true);
m_params.set_bool("blast_quant", true);
m_blaster.updt_params(m_params);
}
rule_set * operator()(rule_set const & source) {
// TODO pc
if (!m_context.get_params().bit_blast()) {
return 0;
}
rule_manager& rm = m_context.get_rule_manager();
unsigned sz = source.get_num_rules();
expr_ref fml(m);
rule_set * result = alloc(rule_set, m_context);
m_rewriter.m_cfg.set_src(&source);
m_rewriter.m_cfg.set_dst(result);
for (unsigned i = 0; !m_context.canceled() && i < sz; ++i) {
rule * r = source.get_rule(i);
r->to_formula(fml);
if (blast(r, fml)) {
proof_ref pr(m);
if (m_context.generate_proof_trace()) {
pr = m.mk_asserted(fml); // loses original proof of r.
}
// TODO add logic for pc:
// 1. replace fresh predicates by non-bit-blasted predicates
// 2. replace pr by the proof of r.
rm.mk_rule(fml, pr, *result, r->name());
}
else {
result->add_rule(r);
}
}
// copy output predicates without any rule (bit-blasting not really needed)
const func_decl_set& decls = source.get_output_predicates();
for (func_decl_set::iterator I = decls.begin(), E = decls.end(); I != E; ++I) {
if (!source.contains(*I))
result->set_output_predicate(*I);
}
if (m_context.get_model_converter()) {
filter_model_converter* fmc = alloc(filter_model_converter, m);
bit_blast_model_converter* bvmc = alloc(bit_blast_model_converter, m);
func_decl_ref_vector const& old_funcs = m_rewriter.m_cfg.old_funcs();
func_decl_ref_vector const& new_funcs = m_rewriter.m_cfg.new_funcs();
for (unsigned i = 0; i < old_funcs.size(); ++i) {
fmc->insert(new_funcs[i]);
bvmc->insert(old_funcs[i], new_funcs[i]);
}
m_context.add_model_converter(concat(bvmc, fmc));
}
return result;
}
};
mk_bit_blast::mk_bit_blast(context & ctx, unsigned priority) : plugin(priority) {
m_impl = alloc(impl, ctx);
}
mk_bit_blast::~mk_bit_blast() {
dealloc(m_impl);
}
rule_set * mk_bit_blast::operator()(rule_set const & source) {
return (*m_impl)(source);
}
};

View file

@ -0,0 +1,38 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_bit_blast.h
Abstract:
Functor for bit-blasting a rule set
Author:
Nikolaj Bjorner (nbjorner) 2012-08-30
Revision History:
--*/
#ifndef _DL_MK_BIT_BLAST_H_
#define _DL_MK_BIT_BLAST_H_
#include"dl_rule_transformer.h"
namespace datalog {
class mk_bit_blast : public rule_transformer::plugin {
class impl;
impl* m_impl;
public:
mk_bit_blast(context & ctx, unsigned priority = 35000);
~mk_bit_blast();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_BIT_BLAST_H_ */

View file

@ -0,0 +1,199 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_coalesce.cpp
Abstract:
Coalesce rules with shared bodies.
Author:
Nikolaj Bjorner (nbjorner) 2012-10-15
Revision History:
Notes:
Implements proof rule of the form:
a(x) & q(x) -> p(x), b(y) & q(y) -> p(y)
----------------------------------------------
(a(z) \/ b(z)) & q(z) -> p(z)
--*/
#include "dl_mk_coalesce.h"
#include "bool_rewriter.h"
namespace datalog {
mk_coalesce::mk_coalesce(context& ctx):
rule_transformer::plugin(50, false),
m_ctx(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_sub1(m),
m_sub2(m),
m_idx(0)
{}
void mk_coalesce::mk_pred(app_ref& pred, app* p1, app* p2) {
SASSERT(p1->get_decl() == p2->get_decl());
unsigned sz = p1->get_num_args();
expr_ref_vector args(m);
for (unsigned i = 0; i < sz; ++i) {
expr* a = p1->get_arg(i);
expr* b = p2->get_arg(i);
SASSERT(m.get_sort(a) == m.get_sort(b));
m_sub1.push_back(a);
m_sub2.push_back(b);
args.push_back(m.mk_var(m_idx++, m.get_sort(a)));
}
pred = m.mk_app(p1->get_decl(), args.size(), args.c_ptr());
}
void mk_coalesce::extract_conjs(expr_ref_vector const& sub, rule const& rl, expr_ref& result) {
obj_map<expr, unsigned> indices;
bool_rewriter bwr(m);
rule_ref r(const_cast<rule*>(&rl), rm);
ptr_vector<sort> sorts;
expr_ref_vector revsub(m), conjs(m);
rl.get_vars(sorts);
revsub.resize(sorts.size());
svector<bool> valid(sorts.size(), true);
for (unsigned i = 0; i < sub.size(); ++i) {
expr* e = sub[i];
sort* s = m.get_sort(e);
expr_ref w(m.mk_var(i, s), m);
if (is_var(e)) {
unsigned v = to_var(e)->get_idx();
SASSERT(v < valid.size());
if (sorts[v]) {
SASSERT(s == sorts[v]);
if (valid[v]) {
revsub[v] = w;
valid[v] = false;
}
else {
SASSERT(revsub[v].get());
SASSERT(m.get_sort(revsub[v].get()) == s);
conjs.push_back(m.mk_eq(revsub[v].get(), w));
}
}
}
else {
SASSERT(m.is_value(e));
SASSERT(m.get_sort(e) == m.get_sort(w));
conjs.push_back(m.mk_eq(e, w));
}
}
for (unsigned i = 0; i < sorts.size(); ++i) {
if (valid[i] && sorts[i] && !revsub[i].get()) {
revsub[i] = m.mk_var(m_idx++, sorts[i]);
}
}
var_subst vs(m, false);
for (unsigned i = r->get_uninterpreted_tail_size(); i < r->get_tail_size(); ++i) {
vs(r->get_tail(i), revsub.size(), revsub.c_ptr(), result);
conjs.push_back(result);
}
bwr.mk_and(conjs.size(), conjs.c_ptr(), result);
}
void mk_coalesce::merge_rules(rule_ref& tgt, rule const& src) {
SASSERT(same_body(*tgt.get(), src));
m_sub1.reset();
m_sub2.reset();
m_idx = 0;
app_ref pred(m), head(m);
expr_ref fml1(m), fml2(m), fml(m);
app_ref_vector tail(m);
ptr_vector<sort> sorts1, sorts2;
expr_ref_vector conjs1(m), conjs(m);
rule_ref res(rm);
bool_rewriter bwr(m);
svector<bool> is_neg;
tgt->get_vars(sorts1);
src.get_vars(sorts2);
mk_pred(head, src.get_head(), tgt->get_head());
for (unsigned i = 0; i < src.get_uninterpreted_tail_size(); ++i) {
mk_pred(pred, src.get_tail(i), tgt->get_tail(i));
tail.push_back(pred);
is_neg.push_back(src.is_neg_tail(i));
}
extract_conjs(m_sub1, src, fml1);
extract_conjs(m_sub2, *tgt.get(), fml2);
bwr.mk_or(fml1, fml2, fml);
SASSERT(is_app(fml));
tail.push_back(to_app(fml));
is_neg.push_back(false);
res = rm.mk(head, tail.size(), tail.c_ptr(), is_neg.c_ptr(), tgt->name());
if (m_ctx.generate_proof_trace()) {
src.to_formula(fml1);
tgt->to_formula(fml2);
res->to_formula(fml);
#if 0
sort* ps = m.mk_proof_sort();
sort* domain[3] = { ps, ps, m.mk_bool_sort() };
func_decl* merge = m.mk_func_decl(symbol("merge-clauses"), 3, domain, ps); // TBD: ad-hoc proof rule
expr* args[3] = { m.mk_asserted(fml1), m.mk_asserted(fml2), fml };
// ...m_pc->insert(m.mk_app(merge, 3, args));
#else
svector<std::pair<unsigned, unsigned> > pos;
vector<expr_ref_vector> substs;
proof* p = src.get_proof();
p = m.mk_hyper_resolve(1, &p, fml, pos, substs);
res->set_proof(m, p);
#endif
}
tgt = res;
}
bool mk_coalesce::same_body(rule const& r1, rule const& r2) const {
SASSERT(r1.get_decl() == r2.get_decl());
unsigned sz = r1.get_uninterpreted_tail_size();
if (sz != r2.get_uninterpreted_tail_size()) {
return false;
}
for (unsigned i = 0; i < sz; ++i) {
if (r1.get_decl(i) != r2.get_decl(i)) {
return false;
}
if (r1.is_neg_tail(i) != r2.is_neg_tail(i)) {
return false;
}
}
return true;
}
rule_set * mk_coalesce::operator()(rule_set const & source) {
rule_set* rules = alloc(rule_set, m_ctx);
rules->inherit_predicates(source);
rule_set::decl2rules::iterator it = source.begin_grouped_rules(), end = source.end_grouped_rules();
for (; it != end; ++it) {
rule_ref_vector d_rules(rm);
d_rules.append(it->m_value->size(), it->m_value->c_ptr());
for (unsigned i = 0; i < d_rules.size(); ++i) {
rule_ref r1(d_rules[i].get(), rm);
for (unsigned j = i + 1; j < d_rules.size(); ++j) {
if (same_body(*r1.get(), *d_rules[j].get())) {
merge_rules(r1, *d_rules[j].get());
d_rules[j] = d_rules.back();
d_rules.pop_back();
--j;
}
}
rules->add_rule(r1.get());
}
}
return rules;
}
};

View file

@ -0,0 +1,61 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_coalesce.h
Abstract:
Coalesce rules with shared bodies.
Author:
Nikolaj Bjorner (nbjorner) 2012-10-15
Revision History:
--*/
#ifndef _DL_MK_COALESCE_H_
#define _DL_MK_COALESCE_H_
#include"dl_context.h"
#include"dl_rule_set.h"
#include"uint_set.h"
#include"dl_rule_transformer.h"
#include"dl_mk_rule_inliner.h"
namespace datalog {
/**
\brief Implements an unfolding transformation.
*/
class mk_coalesce : public rule_transformer::plugin {
context& m_ctx;
ast_manager& m;
rule_manager& rm;
expr_ref_vector m_sub1, m_sub2;
unsigned m_idx;
void mk_pred(app_ref& pred, app* p1, app* p2);
void extract_conjs(expr_ref_vector const& sub, rule const& rl, expr_ref& result);
bool same_body(rule const& r1, rule const& r2) const;
void merge_rules(rule_ref& tgt, rule const& src);
public:
/**
\brief Create coalesced rules.
*/
mk_coalesce(context & ctx);
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_COALESCE_H_ */

View file

@ -0,0 +1,201 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_coi_filter.cpp
Abstract:
Rule transformer which removes relations which are out of the cone of
influence of output relations
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
Andrey Rybalchenko (rybal) 2013-8-8
Added bottom_up pruning.
--*/
#include <sstream>
#include"ast_pp.h"
#include"dl_mk_coi_filter.h"
#include"extension_model_converter.h"
namespace datalog {
// -----------------------------------
//
// mk_coi_filter
//
// -----------------------------------
rule_set * mk_coi_filter::operator()(rule_set const & source) {
if (source.empty()) {
return 0;
}
scoped_ptr<rule_set> result1 = top_down(source);
scoped_ptr<rule_set> result2 = bottom_up(result1?*result1:source);
if (!result2) {
result2 = result1.detach();
}
return result2.detach();
}
rule_set * mk_coi_filter::bottom_up(rule_set const & source) {
func_decl_set all, reached;
ptr_vector<func_decl> todo;
rule_set::decl2rules body2rules;
// initialization for reachability
for (rule_set::iterator it = source.begin(); it != source.end(); ++it) {
rule * r = *it;
all.insert(r->get_decl());
if (r->get_uninterpreted_tail_size() == 0) {
if (!reached.contains(r->get_decl())) {
reached.insert(r->get_decl());
todo.insert(r->get_decl());
}
}
else {
for (unsigned i = 0; i < r->get_uninterpreted_tail_size(); ++i) {
func_decl * d = r->get_tail(i)->get_decl();
all.insert(d);
rule_set::decl2rules::obj_map_entry * e = body2rules.insert_if_not_there2(d, 0);
if (!e->get_data().m_value) {
e->get_data().m_value = alloc(ptr_vector<rule>);
}
e->get_data().m_value->push_back(r);
}
}
}
// reachability computation
while (!todo.empty()) {
func_decl * d = todo.back();
todo.pop_back();
ptr_vector<rule> * rules;
if (!body2rules.find(d, rules)) continue;
for (ptr_vector<rule>::iterator it = rules->begin(); it != rules->end(); ++it) {
rule * r = *it;
if (reached.contains(r->get_decl())) continue;
bool contained = true;
for (unsigned i = 0; contained && i < r->get_uninterpreted_tail_size(); ++i) {
contained = reached.contains(r->get_tail(i)->get_decl());
}
if (!contained) continue;
reached.insert(r->get_decl());
todo.insert(r->get_decl());
}
}
// eliminate each rule when some body predicate is not reached
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
res->inherit_predicates(source);
for (rule_set::iterator it = source.begin(); it != source.end(); ++it) {
rule * r = *it;
bool contained = true;
for (unsigned i = 0; contained && i < r->get_uninterpreted_tail_size(); ++i) {
contained = reached.contains(r->get_tail(i)->get_decl());
}
if (contained) {
res->add_rule(r);
}
}
if (res->get_num_rules() == source.get_num_rules()) {
TRACE("dl", tout << "No transformation\n";);
res = 0;
}
else {
res->close();
}
// set to false each unreached predicate
if (m_context.get_model_converter()) {
extension_model_converter* mc0 = alloc(extension_model_converter, m);
for (func_decl_set::iterator it = all.begin(); it != all.end(); ++it) {
if (!reached.contains(*it)) {
mc0->insert(*it, m.mk_false());
}
}
m_context.add_model_converter(mc0);
}
// clean up body2rules range resources
for (rule_set::decl2rules::iterator it = body2rules.begin(); it != body2rules.end(); ++it) {
dealloc(it->m_value);
}
CTRACE("dl", 0 != res, res->display(tout););
return res.detach();
}
rule_set * mk_coi_filter::top_down(rule_set const & source) {
func_decl_set interesting_preds;
func_decl_set pruned_preds;
ptr_vector<func_decl> todo;
{
const func_decl_set& output_preds = source.get_output_predicates();
func_decl_set::iterator oend = output_preds.end();
for (func_decl_set::iterator it = output_preds.begin(); it!=oend; ++it) {
todo.push_back(*it);
interesting_preds.insert(*it);
}
}
const rule_dependencies& deps = source.get_dependencies();
while (!todo.empty()) {
func_decl * curr = todo.back();
todo.pop_back();
interesting_preds.insert(curr);
const rule_dependencies::item_set& cdeps = deps.get_deps(curr);
rule_dependencies::item_set::iterator dend = cdeps.end();
for (rule_dependencies::item_set::iterator it = cdeps.begin(); it != dend; ++it) {
func_decl * dep_pred = *it;
if (!interesting_preds.contains(dep_pred)) {
interesting_preds.insert(dep_pred);
todo.push_back(dep_pred);
}
}
}
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
res->inherit_predicates(source);
rule_set::iterator rend = source.end();
for (rule_set::iterator rit = source.begin(); rit != rend; ++rit) {
rule * r = *rit;
func_decl * pred = r->get_decl();
if (interesting_preds.contains(pred)) {
res->add_rule(r);
}
else if (m_context.get_model_converter()) {
pruned_preds.insert(pred);
}
}
if (res->get_num_rules() == source.get_num_rules()) {
TRACE("dl", tout << "No transformation\n";);
res = 0;
}
if (res && m_context.get_model_converter()) {
func_decl_set::iterator end = pruned_preds.end();
func_decl_set::iterator it = pruned_preds.begin();
extension_model_converter* mc0 = alloc(extension_model_converter, m);
for (; it != end; ++it) {
mc0->insert(*it, m.mk_true());
}
m_context.add_model_converter(mc0);
}
CTRACE("dl", 0 != res, res->display(tout););
return res.detach();
}
};

View file

@ -0,0 +1,51 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_coi_filter.h
Abstract:
Rule transformer which removes relations which are out of the cone of
influence of output relations
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
--*/
#ifndef _DL_MK_COI_FILTER_H_
#define _DL_MK_COI_FILTER_H_
#include "dl_context.h"
#include "dl_rule_transformer.h"
namespace datalog {
class mk_coi_filter : public rule_transformer::plugin {
typedef obj_map<func_decl, func_decl *> decl_map;
ast_manager & m;
context & m_context;
rule_set * bottom_up(rule_set const & source);
rule_set * top_down(rule_set const & source);
public:
mk_coi_filter(context & ctx, unsigned priority=45000)
: plugin(priority),
m(ctx.get_manager()),
m_context(ctx) {}
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_COI_FILTER_H_ */

View file

@ -0,0 +1,38 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_different_symbolic.h
Abstract:
Create Horn clauses for different symbolic transformation.
Author:
Nikolaj Bjorner (nbjorner) 2013-06-19
Revision History:
--*/
#ifndef _DL_MK_DIFFERENT_SYMBOLIC_H_
#define _DL_MK_DIFFERENT_SYMBOLIC_H_
#include"dl_rule_transformer.h"
namespace datalog {
class mk_different_symbolic : public rule_transformer::plugin {
ast_manager& m;
context& m_ctx;
public:
mk_different_symbolic(context & ctx, unsigned priority = 33037);
~mk_different_symbolic();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_DIFFERENT_SYMBOLIC_H_ */

View file

@ -0,0 +1,170 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_filter_rules.cpp
Abstract:
<abstract>
Author:
Leonardo de Moura (leonardo) 2010-05-18.
Revision History:
--*/
#include"dl_mk_filter_rules.h"
#include"dl_context.h"
#include"for_each_expr.h"
#include"ast_pp.h"
namespace datalog {
mk_filter_rules::mk_filter_rules(context & ctx):
plugin(2000),
m_context(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_result(0),
m_pinned(m) {
}
mk_filter_rules::~mk_filter_rules() {
ptr_vector<filter_key> to_dealloc;
filter_cache::iterator it = m_tail2filter.begin();
filter_cache::iterator end = m_tail2filter.end();
for(; it!=end; ++it) {
to_dealloc.push_back(it->m_key);
}
m_tail2filter.reset();
ptr_vector<filter_key>::iterator dit = to_dealloc.begin();
ptr_vector<filter_key>::iterator dend = to_dealloc.end();
for(; dit!=dend; ++dit) {
dealloc(*dit);
}
}
/**
\brief Return true if \c pred is a cadidate for a "filter" rule.
*/
bool mk_filter_rules::is_candidate(app * pred) {
if (!m_context.is_predicate(pred)) {
TRACE("mk_filter_rules", tout << mk_pp(pred, m) << "\nis not a candidate because it is interpreted.\n";);
return false;
}
var_idx_set used_vars;
unsigned n = pred->get_num_args();
for (unsigned i = 0; i < n; i++) {
expr * arg = pred->get_arg(i);
if (m.is_value(arg))
return true;
SASSERT(is_var(arg));
unsigned vidx = to_var(arg)->get_idx();
if (used_vars.contains(vidx))
return true;
used_vars.insert(vidx);
}
return false;
}
/**
\brief Create a "filter" (if it doesn't exist already) for the given predicate.
*/
func_decl * mk_filter_rules::mk_filter_decl(app * pred, var_idx_set const & non_local_vars) {
sort_ref_buffer filter_domain(m);
filter_key * key = alloc(filter_key, m);
mk_new_rule_tail(m, pred, non_local_vars, filter_domain, key->filter_args, key->new_pred);
func_decl * filter_decl = 0;
if (!m_tail2filter.find(key, filter_decl)) {
filter_decl = m_context.mk_fresh_head_predicate(pred->get_decl()->get_name(), symbol("filter"),
filter_domain.size(), filter_domain.c_ptr(), pred->get_decl());
m_pinned.push_back(filter_decl);
m_tail2filter.insert(key, filter_decl);
app_ref filter_head(m);
filter_head = m.mk_app(filter_decl, key->filter_args.size(), key->filter_args.c_ptr());
app * filter_tail = key->new_pred;
rule * filter_rule = m_context.get_rule_manager().mk(filter_head, 1, &filter_tail, (const bool *)0);
filter_rule->set_accounting_parent_object(m_context, m_current);
m_result->add_rule(filter_rule);
m_context.get_rule_manager().mk_rule_asserted_proof(*filter_rule);
}
else {
dealloc(key);
}
SASSERT(filter_decl != 0);
SASSERT(filter_decl->get_arity()==filter_domain.size());
return filter_decl;
}
void mk_filter_rules::process(rule * r) {
m_current = r;
app * new_head = r->get_head();
app_ref_vector new_tail(m);
svector<bool> new_is_negated;
unsigned sz = r->get_tail_size();
bool rule_modified = false;
for (unsigned i = 0; i < sz; i++) {
app * tail = r->get_tail(i);
if (is_candidate(tail)) {
TRACE("mk_filter_rules", tout << "is_candidate: " << mk_pp(tail, m) << "\n";);
var_idx_set non_local_vars = rm.collect_rule_vars_ex(r, tail);
func_decl * filter_decl = mk_filter_decl(tail, non_local_vars);
ptr_buffer<expr> new_args;
var_idx_set used_vars;
unsigned num_args = tail->get_num_args();
for (unsigned i = 0; i < num_args; i++) {
expr * arg = tail->get_arg(i);
if (is_var(arg)) {
unsigned vidx = to_var(arg)->get_idx();
if (non_local_vars.contains(vidx) && !used_vars.contains(vidx)) {
new_args.push_back(arg);
used_vars.insert(vidx);
}
}
}
SASSERT(new_args.size() == filter_decl->get_arity());
new_tail.push_back(m.mk_app(filter_decl, new_args.size(), new_args.c_ptr()));
rule_modified = true;
}
else {
new_tail.push_back(tail);
}
new_is_negated.push_back(r->is_neg_tail(i));
}
if (rule_modified) {
remove_duplicate_tails(new_tail, new_is_negated);
SASSERT(new_tail.size() == new_is_negated.size());
rule * new_rule = m_context.get_rule_manager().mk(new_head, new_tail.size(), new_tail.c_ptr(), new_is_negated.c_ptr());
new_rule->set_accounting_parent_object(m_context, m_current);
m_result->add_rule(new_rule);
m_context.get_rule_manager().mk_rule_rewrite_proof(*r, *new_rule);
m_modified = true;
}
else {
m_result->add_rule(r);
}
}
rule_set * mk_filter_rules::operator()(rule_set const & source) {
m_tail2filter.reset();
m_result = alloc(rule_set, m_context);
m_modified = false;
unsigned num_rules = source.get_num_rules();
for (unsigned i = 0; i < num_rules; i++) {
process(source.get_rule(i));
}
if(!m_modified) {
dealloc(m_result);
return static_cast<rule_set *>(0);
}
m_result->inherit_predicates(source);
return m_result;
}
};

View file

@ -0,0 +1,87 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_filter_rules.h
Abstract:
<abstract>
Author:
Leonardo de Moura (leonardo) 2010-05-18.
Revision History:
--*/
#ifndef _DL_MK_FILTER_RULES_H_
#define _DL_MK_FILTER_RULES_H_
#include"map.h"
#include"dl_rule_set.h"
#include"dl_rule_transformer.h"
namespace datalog {
/**
\brief Functor for applying a rule set transformation that creates "filters".
A "filter" is a rule of the form:
Head(X_1, ..., X_n) :- Tail(...)
where X_1,...,X_n are distinct, and Tail contain repeated variables and/or values.
After applying this functor only "filter" rules will contain atoms with repeated variables and/or values.
*/
class mk_filter_rules : public rule_transformer::plugin {
struct filter_key {
app_ref new_pred;
expr_ref_buffer filter_args;
filter_key(ast_manager & m) : new_pred(m), filter_args(m) {}
unsigned hash() const {
unsigned r = new_pred->hash();
for (unsigned i = 0; i < filter_args.size(); ++i) {
r ^= filter_args[i]->hash();
}
return r;
}
bool operator==(const filter_key & o) const {
return o.new_pred==new_pred && vectors_equal(o.filter_args, filter_args);
}
};
typedef obj_map<filter_key, func_decl*> filter_cache;
context & m_context;
ast_manager & m;
rule_manager & rm;
filter_cache m_tail2filter;
rule_set * m_result;
rule * m_current;
bool m_modified;
ast_ref_vector m_pinned;
bool is_candidate(app * pred);
func_decl * mk_filter_decl(app * pred, var_idx_set const & non_local_vars);
void process(rule * r);
public:
mk_filter_rules(context & ctx);
~mk_filter_rules();
/**
\brief Return a new rule set where only filter rules contain atoms with repeated variables and/or values.
*/
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_FILTER_RULES_H_ */

View file

@ -0,0 +1,618 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_interp_tail_simplifier.cpp
Abstract:
Rule transformer which simplifies interpreted tails
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
--*/
#include <sstream>
#include"ast_pp.h"
#include"bool_rewriter.h"
#include"rewriter.h"
#include"rewriter_def.h"
#include"dl_mk_rule_inliner.h"
#include"dl_mk_interp_tail_simplifier.h"
namespace datalog {
// -----------------------------------
//
// mk_interp_tail_simplifier::rule_substitution
//
// -----------------------------------
void mk_interp_tail_simplifier::rule_substitution::reset(rule * r) {
unsigned var_cnt = m_context.get_rule_manager().get_counter().get_max_rule_var(*r)+1;
m_subst.reset();
m_subst.reserve(1, var_cnt);
m_rule = r;
}
bool mk_interp_tail_simplifier::rule_substitution::unify(expr * e1, expr * e2) {
SASSERT(m_rule);
//we need to apply the current substitution in order to ensure the unifier
//works in an incremental way
expr_ref e1_s(m);
expr_ref e2_s(m);
m_subst.apply(e1,e1_s);
m_subst.apply(e2,e2_s);
//and we need to reset the cache as we're going to modify the substitution
m_subst.reset_cache();
return m_unif (e1_s, e2_s, m_subst, false);
}
void mk_interp_tail_simplifier::rule_substitution::apply(app * a, app_ref& res) {
SASSERT(m_rule);
expr_ref res_e(m);
m_subst.apply(a, res_e);
SASSERT(is_app(res_e.get()));
res = to_app(res_e.get());
}
void mk_interp_tail_simplifier::rule_substitution::get_result(rule_ref & res) {
SASSERT(m_rule);
apply(m_rule->get_head(), m_head);
m_tail.reset();
m_neg.reset();
unsigned tail_len = m_rule->get_tail_size();
for (unsigned i=0; i<tail_len; i++) {
app_ref new_tail_el(m);
apply(m_rule->get_tail(i), new_tail_el);
m_tail.push_back(new_tail_el);
m_neg.push_back(m_rule->is_neg_tail(i));
}
mk_rule_inliner::remove_duplicate_tails(m_tail, m_neg);
SASSERT(m_tail.size() == m_neg.size());
res = m_context.get_rule_manager().mk(m_head, m_tail.size(), m_tail.c_ptr(), m_neg.c_ptr());
res->set_accounting_parent_object(m_context, m_rule);
res->norm_vars(res.get_manager());
}
// -----------------------------------
//
// mk_interp_tail_simplifier
//
// -----------------------------------
class mk_interp_tail_simplifier::normalizer_cfg : public default_rewriter_cfg
{
struct expr_cmp
{
ast_manager& m;
expr_cmp(ast_manager& m) : m(m) {}
bool operator()(expr * ae, expr * be) {
return cmp_expr(ae, be, 4) == -1;
}
template<typename T>
static int cmp(T a, T b) { return (a>b) ? 1 : ((a == b) ? 0 : -1); }
int cmp_expr(expr * ae, expr * be, int depth) {
if (ae == be) { return 0; }
//remove negations
bool a_neg = m.is_not(ae, ae);
bool b_neg = m.is_not(be, be);
if (ae==be) { return cmp(a_neg, b_neg); }
if (!is_app(ae) && !is_app(be)) { return cmp(ae->get_id(), be->get_id()); }
if (!is_app(ae)) { return -1; }
if (!is_app(be)) { return 1; }
app * a = to_app(ae);
app * b = to_app(be);
if (a->get_decl()!=b->get_decl()) {
return cmp(a->get_decl()->get_id(), b->get_decl()->get_id());
}
if (a->get_num_args()!=b->get_num_args()) {
return cmp(a->get_num_args(), b->get_num_args());
}
if (depth==0) {
return cmp(a->get_id(),b->get_id());
}
unsigned arg_cnt = a->get_num_args();
unsigned neg_comparison = 0;
for (unsigned i=0; i<arg_cnt; i++) {
expr * arg_a = a->get_arg(i);
expr * arg_b = b->get_arg(i);
//we normalize away negations
bool a_is_neg = m.is_not(arg_a, arg_a);
bool b_is_neg = m.is_not(arg_b, arg_b);
if (neg_comparison==0 && a_is_neg!=b_is_neg) {
neg_comparison = a_is_neg ? -1 : 1;
}
int res = cmp_expr(arg_a, arg_b, depth-1);
if (res!=0) {
return res;
}
}
if (neg_comparison!=0) {
return neg_comparison;
}
//by normalizing away negation we may have put non-equal terms to be equal, so here we check
return cmp(a->get_id(),b->get_id());
}
};
ast_manager& m;
bool_rewriter m_brwr;
//instead of a local variable
expr_ref_vector m_app_args;
expr_cmp m_expr_cmp;
public:
normalizer_cfg(ast_manager& m)
: m(m), m_brwr(m), m_app_args(m), m_expr_cmp(m)
{
}
static void remove_duplicates(expr_ref_vector& v)
{
expr * a = v[0].get();
unsigned read_idx = 1;
unsigned write_idx = 1;
for (;;) {
while(read_idx<v.size() && a==v[read_idx].get()) {
read_idx++;
}
if (read_idx==v.size()) {
break;
}
a = v[read_idx].get();
if (write_idx!=read_idx) {
v[write_idx] = a;
}
write_idx++;
read_idx++;
}
v.shrink(write_idx);
}
typedef std::pair<expr *,expr *> arg_pair;
bool match_arg_pair(expr * e, arg_pair& pair, bool seek_conjunction)
{
if (seek_conjunction) {
return m.is_and(e, pair.first, pair.second);
}
else {
return m.is_or(e, pair.first, pair.second);
}
}
/**
If inside_disjunction is false, we're inside a conjunction (and arg pairs
represent disjunctions).
*/
app * detect_equivalence(const arg_pair& p1, const arg_pair& p2, bool inside_disjunction)
{
if (m.is_not(p1.first)==m.is_not(p2.first)) { return 0; }
if (m.is_not(p1.second)==m.is_not(p2.second)) { return 0; }
expr * first_bare = 0;
if (m.is_not(p1.first, first_bare) && p2.first!=first_bare) { return 0; }
if (m.is_not(p2.first, first_bare) && p1.first!=first_bare) { return 0; }
SASSERT(first_bare);
expr * second_bare = 0;
if (m.is_not(p1.second, second_bare) && p2.second!=second_bare) { return 0; }
if (m.is_not(p2.second, second_bare) && p1.second!=second_bare) { return 0; }
SASSERT(second_bare);
if (!m.is_bool(first_bare) || !m.is_bool(second_bare)) { return 0; }
//both negations are in the same pair
bool negs_together = m.is_not(p1.first)==m.is_not(p1.second);
if (negs_together==inside_disjunction) {
return m.mk_eq(first_bare, second_bare);
}
else {
return m.mk_eq(first_bare, m.mk_not(second_bare));
}
}
bool detect_equivalences(expr_ref_vector& v, bool inside_disjunction)
{
bool have_pair = false;
unsigned prev_pair_idx;
arg_pair ap;
unsigned read_idx = 0;
unsigned write_idx = 0;
while(read_idx<v.size()) {
expr * e = v[read_idx].get();
arg_pair new_ap;
if (match_arg_pair(e, new_ap, inside_disjunction)) {
app * neq = 0;
if (have_pair) {
neq = detect_equivalence(ap, new_ap, inside_disjunction);
}
if (neq) {
have_pair = false;
v[prev_pair_idx] = neq;
read_idx++;
continue;
}
else {
have_pair = true;
prev_pair_idx = write_idx;
ap = new_ap;
}
}
else {
have_pair = false;
}
if (write_idx!=read_idx) {
v[write_idx] = e;
}
read_idx++;
write_idx++;
}
v.shrink(write_idx);
return read_idx!=write_idx;
}
//bool detect_same_variable_conj_pairs
br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result,
proof_ref & result_pr)
{
if (m.is_not(f) && (m.is_and(args[0]) || m.is_or(args[0]))) {
SASSERT(num==1);
expr_ref tmp(m);
app* a = to_app(args[0]);
m_app_args.reset();
for (unsigned i = 0; i < a->get_num_args(); ++i) {
m_brwr.mk_not(a->get_arg(i), tmp);
m_app_args.push_back(tmp);
}
if (m.is_and(args[0])) {
result = m.mk_or(m_app_args.size(), m_app_args.c_ptr());
}
else {
result = m.mk_and(m_app_args.size(), m_app_args.c_ptr());
}
return BR_REWRITE2;
}
if (!m.is_and(f) && !m.is_or(f)) {
return BR_FAILED;
}
if (num == 0) {
if (m.is_and(f)) {
result = m.mk_true();
}
else {
result = m.mk_false();
}
return BR_DONE;
}
if (num == 1) {
result = args[0];
return BR_DONE;
}
m_app_args.reset();
m_app_args.append(num, args);
std::sort(m_app_args.c_ptr(), m_app_args.c_ptr()+m_app_args.size(), m_expr_cmp);
remove_duplicates(m_app_args);
bool have_rewritten_args = false;
have_rewritten_args = detect_equivalences(m_app_args, m.is_or(f));
if (m_app_args.size()==1) {
result = m_app_args[0].get();
}
else {
if (m.is_and(f)) {
result = m.mk_and(m_app_args.size(), m_app_args.c_ptr());
}
else {
SASSERT(m.is_or(f));
result = m.mk_or(m_app_args.size(), m_app_args.c_ptr());
}
}
if (have_rewritten_args) {
return BR_REWRITE1;
}
return BR_DONE;
}
};
class mk_interp_tail_simplifier::normalizer_rw : public rewriter_tpl<normalizer_cfg> {
public:
normalizer_rw(ast_manager& m, normalizer_cfg& cfg): rewriter_tpl<normalizer_cfg>(m, false, cfg) {}
};
mk_interp_tail_simplifier::mk_interp_tail_simplifier(context & ctx, unsigned priority)
: plugin(priority),
m(ctx.get_manager()),
m_context(ctx),
m_simp(ctx.get_rewriter()),
a(m),
m_rule_subst(ctx),
m_tail(m),
m_itail_members(m),
m_conj(m) {
m_cfg = alloc(normalizer_cfg, m);
m_rw = alloc(normalizer_rw, m, *m_cfg);
}
mk_interp_tail_simplifier::~mk_interp_tail_simplifier() {
dealloc(m_rw);
dealloc(m_cfg);
}
void mk_interp_tail_simplifier::simplify_expr(app * a, expr_ref& res)
{
expr_ref simp1_res(m);
m_simp(a, simp1_res);
(*m_rw)(simp1_res.get(), res);
/*if (simp1_res.get()!=res.get()) {
std::cout<<"pre norm:\n"<<mk_pp(simp1_res.get(),m)<<"post norm:\n"<<mk_pp(res.get(),m)<<"\n";
}*/
m_simp(res.get(), res);
}
bool mk_interp_tail_simplifier::propagate_variable_equivalences(rule * r, rule_ref& res) {
unsigned u_len = r->get_uninterpreted_tail_size();
unsigned len = r->get_tail_size();
if (u_len == len) {
return false;
}
m_todo.reset();
m_leqs.reset();
for (unsigned i = u_len; i < len; i++) {
m_todo.push_back(r->get_tail(i));
SASSERT(!r->is_neg_tail(i));
}
m_rule_subst.reset(r);
expr_ref_vector trail(m);
expr_ref tmp1(m), tmp2(m);
bool found_something = false;
#define TRY_UNIFY(_x,_y) if (m_rule_subst.unify(_x,_y)) { found_something = true; }
#define IS_FLEX(_x) (is_var(_x) || m.is_value(_x))
while (!m_todo.empty()) {
expr * arg1, *arg2;
expr * t0 = m_todo.back();
m_todo.pop_back();
expr* t = t0;
bool neg = m.is_not(t, t);
if (is_var(t)) {
TRY_UNIFY(t, neg ? m.mk_false() : m.mk_true());
}
else if (!neg && m.is_and(t)) {
app* a = to_app(t);
m_todo.append(a->get_num_args(), a->get_args());
}
else if (!neg && m.is_eq(t, arg1, arg2) && IS_FLEX(arg1) && IS_FLEX(arg2)) {
TRY_UNIFY(arg1, arg2);
}
else if (m.is_iff(t, arg1, arg2)) {
//determine the polarity of the equivalence and remove the negations
while (m.is_not(arg1, arg1)) neg = !neg;
while (m.is_not(arg2, arg2)) neg = !neg;
if (!is_var(arg1)) {
std::swap(arg1, arg2);
}
if (!IS_FLEX(arg1) || !IS_FLEX(arg2)) {
// no-op
}
else if (is_var(arg1) && !neg) {
TRY_UNIFY(arg1, arg2);
}
else if (is_var(arg1) && neg && m.is_true(arg2)) {
TRY_UNIFY(arg1, m.mk_false());
}
else if (is_var(arg1) && neg && m.is_false(arg2)) {
TRY_UNIFY(arg1, m.mk_true());
}
}
else if (!neg && (a.is_le(t, arg1, arg2) || a.is_ge(t, arg2, arg1))) {
tmp1 = a.mk_sub(arg1, arg2);
tmp2 = a.mk_sub(arg2, arg1);
if (false && m_leqs.contains(tmp2) && IS_FLEX(arg1) && IS_FLEX(arg2)) {
TRY_UNIFY(arg1, arg2);
}
else {
trail.push_back(tmp1);
m_leqs.insert(tmp1);
}
}
}
if (!found_something) {
return false;
}
TRACE("dl_interp_tail_simplifier_propagation_pre",
tout << "will propagate rule:\n";
r->display(m_context, tout);
);
m_rule_subst.get_result(res);
TRACE("dl_interp_tail_simplifier_propagation",
tout << "propagated equivalences of:\n";
r->display(m_context, tout);
tout << "into:\n";
res->display(m_context, tout);
);
return true;
}
bool mk_interp_tail_simplifier::transform_rule(rule * r0, rule_ref & res)
{
rule_ref r(r0, m_context.get_rule_manager());
if (r->has_quantifiers()) {
res = r;
return true;
}
start:
unsigned u_len = r->get_uninterpreted_tail_size();
unsigned len = r->get_tail_size();
if (u_len == len) {
res = r;
return true;
}
app_ref head(r->get_head(), m);
m_tail.reset();
m_tail_neg.reset();
for (unsigned i=0; i<u_len; i++) {
m_tail.push_back(r->get_tail(i));
m_tail_neg.push_back(r->is_neg_tail(i));
}
bool modified = false;
app_ref itail(m);
if (u_len+1==len) {
//we have only one interpreted tail
itail = r->get_tail(u_len);
SASSERT(!r->is_neg_tail(u_len));
}
else {
m_itail_members.reset();
for (unsigned i=u_len; i<len; i++) {
m_itail_members.push_back(r->get_tail(i));
SASSERT(!r->is_neg_tail(i));
}
itail = m.mk_and(m_itail_members.size(), m_itail_members.c_ptr());
modified = true;
}
expr_ref simp_res(m);
simplify_expr(itail.get(), simp_res);
modified |= itail.get() != simp_res.get();
if (m.is_false(simp_res)) {
TRACE("dl", r->display(m_context, tout << "rule is infeasible\n"););
return false;
}
SASSERT(m.is_bool(simp_res));
if (modified) {
m_conj.reset();
qe::flatten_and(simp_res, m_conj);
for (unsigned i = 0; i < m_conj.size(); ++i) {
expr* e = m_conj[i].get();
if (is_app(e)) {
m_tail.push_back(to_app(e));
}
else {
m_tail.push_back(m.mk_eq(e, m.mk_true()));
}
m_tail_neg.push_back(false);
}
SASSERT(m_tail.size() == m_tail_neg.size());
res = m_context.get_rule_manager().mk(head, m_tail.size(), m_tail.c_ptr(), m_tail_neg.c_ptr());
res->set_accounting_parent_object(m_context, r);
}
else {
res = r;
}
rule_ref pro_var_eq_result(m_context.get_rule_manager());
if (propagate_variable_equivalences(res, pro_var_eq_result)) {
SASSERT(rule_counter().get_max_rule_var(*r.get())==0 ||
rule_counter().get_max_rule_var(*r.get()) > rule_counter().get_max_rule_var(*pro_var_eq_result.get()));
r = pro_var_eq_result;
goto start;
}
CTRACE("dl", (res != r0), r0->display(m_context, tout << "old:\n"); res->display(m_context, tout << "new:\n"););
return true;
}
bool mk_interp_tail_simplifier::transform_rules(const rule_set & orig, rule_set & tgt) {
bool modified = false;
rule_manager& rm = m_context.get_rule_manager();
rule_set::iterator rit = orig.begin();
rule_set::iterator rend = orig.end();
for (; rit!=rend; ++rit) {
rule_ref new_rule(rm);
if (transform_rule(*rit, new_rule)) {
rm.mk_rule_rewrite_proof(**rit, *new_rule.get());
bool is_modified = *rit != new_rule;
modified |= is_modified;
tgt.add_rule(new_rule);
}
else {
modified = true;
}
}
return modified;
}
rule_set * mk_interp_tail_simplifier::operator()(rule_set const & source) {
if (source.get_num_rules() == 0) {
return 0;
}
rule_set * res = alloc(rule_set, m_context);
if (transform_rules(source, *res)) {
res->inherit_predicates(source);
} else {
dealloc(res);
res = 0;
}
return res;
}
};

View file

@ -0,0 +1,109 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_interp_tail_simplifier.h
Abstract:
Rule transformer which simplifies interpreted tails
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
--*/
#ifndef _DL_MK_INTERP_TAIL_SIMPLIFIER_H_
#define _DL_MK_INTERP_TAIL_SIMPLIFIER_H_
#include "dl_context.h"
#include "dl_rule_transformer.h"
#include "unifier.h"
#include "substitution.h"
#include "arith_decl_plugin.h"
namespace datalog {
class mk_interp_tail_simplifier : public rule_transformer::plugin {
class rule_substitution
{
ast_manager& m;
context& m_context;
substitution m_subst;
unifier m_unif;
app_ref m_head;
app_ref_vector m_tail;
svector<bool> m_neg;
rule * m_rule;
void apply(app * a, app_ref& res);
public:
rule_substitution(context & ctx)
: m(ctx.get_manager()), m_context(ctx), m_subst(m), m_unif(m), m_head(m), m_tail(m), m_rule(0) {}
/**
Reset substitution and get it ready for working with rule r.
As long as this object is used without a reset, the rule r must exist.
*/
void reset(rule * r);
/** Reset subtitution and unify tail tgt_idx of the target rule and the head of the src rule */
bool unify(expr * e1, expr * e2);
void get_result(rule_ref & res);
void display(std::ostream& stm) {
m_subst.display(stm);
}
};
class normalizer_cfg;
class normalizer_rw;
ast_manager & m;
context & m_context;
th_rewriter & m_simp;
arith_util a;
rule_substitution m_rule_subst;
ptr_vector<expr> m_todo;
obj_hashtable<expr> m_leqs;
app_ref_vector m_tail;
expr_ref_vector m_itail_members;
expr_ref_vector m_conj;
svector<bool> m_tail_neg;
normalizer_cfg* m_cfg;
normalizer_rw* m_rw;
void simplify_expr(app * a, expr_ref& res);
/** return true if some propagation was done */
bool propagate_variable_equivalences(rule * r, rule_ref& res);
/** Return true if something was modified */
bool transform_rules(const rule_set & orig, rule_set & tgt);
public:
mk_interp_tail_simplifier(context & ctx, unsigned priority=40000);
virtual ~mk_interp_tail_simplifier();
/**If rule should be retained, assign transformed version to res and return true;
if rule can be deleted, return false.
This method is kind of useful, so it's public to allow other rules to use it,
e.g. on their intermediate results.
*/
bool transform_rule(rule * r, rule_ref& res);
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_INTERP_TAIL_SIMPLIFIER_H_ */

View file

@ -0,0 +1,325 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_karr_invariants.cpp
Abstract:
Extract integer linear invariants.
The linear invariants are extracted according to Karr's method.
A short description is in
Nikolaj Bjorner, Anca Browne and Zohar Manna. Automatic Generation
of Invariants and Intermediate Assertions, in CP 95.
The algorithm is here adapted to Horn clauses.
The idea is to maintain two data-structures for each recursive relation.
We call them R and RD
- R - set of linear congruences that are true of R.
- RD - the dual basis of of solutions for R.
RD is updated by accumulating basis vectors for solutions
to R (the homogeneous dual of R)
R is updated from the inhomogeneous dual of RD.
Author:
Nikolaj Bjorner (nbjorner) 2013-03-09
Revision History:
--*/
#include"expr_safe_replace.h"
#include"bool_rewriter.h"
#include"for_each_expr.h"
#include"dl_mk_karr_invariants.h"
#include"dl_mk_backwards.h"
#include"dl_mk_loop_counter.h"
namespace datalog {
mk_karr_invariants::mk_karr_invariants(context & ctx, unsigned priority):
rule_transformer::plugin(priority, false),
m_ctx(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_inner_ctx(m, ctx.get_register_engine(), ctx.get_fparams()),
a(m),
m_pinned(m),
m_cancel(false) {
params_ref params;
params.set_sym("default_relation", symbol("karr_relation"));
params.set_sym("engine", symbol("datalog"));
params.set_bool("karr", false);
m_inner_ctx.updt_params(params);
}
mk_karr_invariants::~mk_karr_invariants() { }
matrix& matrix::operator=(matrix const& other) {
reset();
append(other);
return *this;
}
void matrix::display_row(
std::ostream& out, vector<rational> const& row, rational const& b, bool is_eq) {
for (unsigned j = 0; j < row.size(); ++j) {
out << row[j] << " ";
}
out << (is_eq?" = ":" >= ") << -b << "\n";
}
void matrix::display_ineq(
std::ostream& out, vector<rational> const& row, rational const& b, bool is_eq) {
bool first = true;
for (unsigned j = 0; j < row.size(); ++j) {
if (!row[j].is_zero()) {
if (!first && row[j].is_pos()) {
out << "+ ";
}
if (row[j].is_minus_one()) {
out << "- ";
}
if (row[j] > rational(1) || row[j] < rational(-1)) {
out << row[j] << "*";
}
out << "x" << j << " ";
first = false;
}
}
out << (is_eq?"= ":">= ") << -b << "\n";
}
void matrix::display(std::ostream& out) const {
for (unsigned i = 0; i < A.size(); ++i) {
display_row(out, A[i], b[i], eq[i]);
}
}
class mk_karr_invariants::add_invariant_model_converter : public model_converter {
ast_manager& m;
arith_util a;
func_decl_ref_vector m_funcs;
expr_ref_vector m_invs;
public:
add_invariant_model_converter(ast_manager& m): m(m), a(m), m_funcs(m), m_invs(m) {}
virtual ~add_invariant_model_converter() { }
void add(func_decl* p, expr* inv) {
if (!m.is_true(inv)) {
m_funcs.push_back(p);
m_invs.push_back(inv);
}
}
virtual void operator()(model_ref & mr) {
for (unsigned i = 0; i < m_funcs.size(); ++i) {
func_decl* p = m_funcs[i].get();
func_interp* f = mr->get_func_interp(p);
expr_ref body(m);
unsigned arity = p->get_arity();
SASSERT(0 < arity);
if (f) {
SASSERT(f->num_entries() == 0);
if (!f->is_partial()) {
bool_rewriter(m).mk_and(f->get_else(), m_invs[i].get(), body);
}
}
else {
f = alloc(func_interp, m, arity);
mr->register_decl(p, f);
body = m.mk_false(); // fragile: assume that relation was pruned by being infeasible.
}
f->set_else(body);
}
}
virtual model_converter * translate(ast_translation & translator) {
add_invariant_model_converter* mc = alloc(add_invariant_model_converter, m);
for (unsigned i = 0; i < m_funcs.size(); ++i) {
mc->add(translator(m_funcs[i].get()), m_invs[i].get());
}
return mc;
}
private:
void mk_body(matrix const& M, expr_ref& body) {
expr_ref_vector conj(m);
for (unsigned i = 0; i < M.size(); ++i) {
mk_body(M.A[i], M.b[i], M.eq[i], conj);
}
bool_rewriter(m).mk_and(conj.size(), conj.c_ptr(), body);
}
void mk_body(vector<rational> const& row, rational const& b, bool is_eq, expr_ref_vector& conj) {
expr_ref_vector sum(m);
expr_ref zero(m), lhs(m);
zero = a.mk_numeral(rational(0), true);
for (unsigned i = 0; i < row.size(); ++i) {
if (row[i].is_zero()) {
continue;
}
var* var = m.mk_var(i, a.mk_int());
if (row[i].is_one()) {
sum.push_back(var);
}
else {
sum.push_back(a.mk_mul(a.mk_numeral(row[i], true), var));
}
}
if (!b.is_zero()) {
sum.push_back(a.mk_numeral(b, true));
}
lhs = a.mk_add(sum.size(), sum.c_ptr());
if (is_eq) {
conj.push_back(m.mk_eq(lhs, zero));
}
else {
conj.push_back(a.mk_ge(lhs, zero));
}
}
};
void mk_karr_invariants::cancel() {
m_cancel = true;
m_inner_ctx.cancel();
}
rule_set * mk_karr_invariants::operator()(rule_set const & source) {
if (!m_ctx.get_params().karr()) {
return 0;
}
rule_set::iterator it = source.begin(), end = source.end();
for (; it != end; ++it) {
rule const& r = **it;
if (r.has_negation()) {
return 0;
}
}
mk_loop_counter lc(m_ctx);
mk_backwards bwd(m_ctx);
scoped_ptr<rule_set> src_loop = lc(source);
TRACE("dl", src_loop->display(tout << "source loop\n"););
get_invariants(*src_loop);
if (m_cancel) {
return 0;
}
// figure out whether to update same rules as used for saturation.
scoped_ptr<rule_set> rev_source = bwd(*src_loop);
get_invariants(*rev_source);
scoped_ptr<rule_set> src_annot = update_rules(*src_loop);
rule_set* rules = lc.revert(*src_annot);
rules->inherit_predicates(source);
TRACE("dl", rules->display(tout););
m_pinned.reset();
m_fun2inv.reset();
return rules;
}
void mk_karr_invariants::get_invariants(rule_set const& src) {
m_inner_ctx.reset();
rel_context_base& rctx = *m_inner_ctx.get_rel_context();
ptr_vector<func_decl> heads;
func_decl_set const& predicates = m_ctx.get_predicates();
for (func_decl_set::iterator fit = predicates.begin(); fit != predicates.end(); ++fit) {
m_inner_ctx.register_predicate(*fit, false);
}
m_inner_ctx.ensure_opened();
m_inner_ctx.replace_rules(src);
m_inner_ctx.close();
rule_set::decl2rules::iterator dit = src.begin_grouped_rules();
rule_set::decl2rules::iterator dend = src.end_grouped_rules();
for (; dit != dend; ++dit) {
heads.push_back(dit->m_key);
}
m_inner_ctx.rel_query(heads.size(), heads.c_ptr());
// retrieve invariants.
dit = src.begin_grouped_rules();
for (; dit != dend; ++dit) {
func_decl* p = dit->m_key;
expr_ref fml = rctx.try_get_formula(p);
if (fml && !m.is_true(fml)) {
expr* inv = 0;
if (m_fun2inv.find(p, inv)) {
fml = m.mk_and(inv, fml);
}
m_pinned.push_back(fml);
m_fun2inv.insert(p, fml);
}
}
}
rule_set* mk_karr_invariants::update_rules(rule_set const& src) {
scoped_ptr<rule_set> dst = alloc(rule_set, m_ctx);
rule_set::iterator it = src.begin(), end = src.end();
for (; it != end; ++it) {
update_body(*dst, **it);
}
if (m_ctx.get_model_converter()) {
add_invariant_model_converter* kmc = alloc(add_invariant_model_converter, m);
rule_set::decl2rules::iterator git = src.begin_grouped_rules();
rule_set::decl2rules::iterator gend = src.end_grouped_rules();
for (; git != gend; ++git) {
func_decl* p = git->m_key;
expr* fml = 0;
if (m_fun2inv.find(p, fml)) {
kmc->add(p, fml);
}
}
m_ctx.add_model_converter(kmc);
}
dst->inherit_predicates(src);
return dst.detach();
}
void mk_karr_invariants::update_body(rule_set& rules, rule& r) {
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
app_ref_vector tail(m);
expr_ref fml(m);
for (unsigned i = 0; i < tsz; ++i) {
tail.push_back(r.get_tail(i));
}
for (unsigned i = 0; i < utsz; ++i) {
func_decl* q = r.get_decl(i);
expr* fml = 0;
if (m_fun2inv.find(q, fml)) {
expr_safe_replace rep(m);
for (unsigned j = 0; j < q->get_arity(); ++j) {
rep.insert(m.mk_var(j, q->get_domain(j)),
r.get_tail(i)->get_arg(j));
}
expr_ref tmp(fml, m);
rep(tmp);
tail.push_back(to_app(tmp));
}
}
rule* new_rule = &r;
if (tail.size() != tsz) {
new_rule = rm.mk(r.get_head(), tail.size(), tail.c_ptr(), 0, r.name());
}
rules.add_rule(new_rule);
rm.mk_rule_rewrite_proof(r, *new_rule); // should be weakening rule.
}
};

View file

@ -0,0 +1,81 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_karr_invariants.h
Abstract:
Extract integer linear invariants.
Author:
Nikolaj Bjorner (nbjorner) 2013-03-08
Revision History:
--*/
#ifndef _DL_MK_KARR_INVARIANTS_H_
#define _DL_MK_KARR_INVARIANTS_H_
#include"dl_context.h"
#include"dl_rule_set.h"
#include"dl_rule_transformer.h"
#include"arith_decl_plugin.h"
#include"hilbert_basis.h"
namespace datalog {
/**
\brief Rule transformer that strengthens bodies with invariants.
*/
struct matrix {
vector<vector<rational> > A;
vector<rational> b;
svector<bool> eq;
unsigned size() const { return A.size(); }
void reset() { A.reset(); b.reset(); eq.reset(); }
matrix& operator=(matrix const& other);
void append(matrix const& other) { A.append(other.A); b.append(other.b); eq.append(other.eq); }
void display(std::ostream& out) const;
static void display_row(
std::ostream& out, vector<rational> const& row, rational const& b, bool is_eq);
static void display_ineq(
std::ostream& out, vector<rational> const& row, rational const& b, bool is_eq);
};
class mk_karr_invariants : public rule_transformer::plugin {
class add_invariant_model_converter;
context& m_ctx;
ast_manager& m;
rule_manager& rm;
context m_inner_ctx;
arith_util a;
obj_map<func_decl, expr*> m_fun2inv;
ast_ref_vector m_pinned;
volatile bool m_cancel;
void get_invariants(rule_set const& src);
void update_body(rule_set& result, rule& r);
rule_set* update_rules(rule_set const& src);
public:
mk_karr_invariants(context & ctx, unsigned priority);
virtual ~mk_karr_invariants();
virtual void cancel();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_KARR_INVARIANTS_H_ */

View file

@ -0,0 +1,158 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_loop_counter.cpp
Abstract:
Add loop counter argument to relations.
Author:
Nikolaj Bjorner (nbjorner) 2013-03-31
Revision History:
--*/
#include"dl_mk_loop_counter.h"
#include"dl_context.h"
namespace datalog {
mk_loop_counter::mk_loop_counter(context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx),
a(m),
m_refs(m) {
}
mk_loop_counter::~mk_loop_counter() { }
app_ref mk_loop_counter::add_arg(rule_set const& src, rule_set& dst, app* fn, unsigned idx) {
expr_ref_vector args(m);
func_decl* new_fn, *old_fn = fn->get_decl();
args.append(fn->get_num_args(), fn->get_args());
args.push_back(m.mk_var(idx, a.mk_int()));
if (!m_old2new.find(old_fn, new_fn)) {
ptr_vector<sort> domain;
domain.append(fn->get_num_args(), old_fn->get_domain());
domain.push_back(a.mk_int());
new_fn = m.mk_func_decl(old_fn->get_name(), domain.size(), domain.c_ptr(), old_fn->get_range());
m_old2new.insert(old_fn, new_fn);
m_new2old.insert(new_fn, old_fn);
m_refs.push_back(new_fn);
m_ctx.register_predicate(new_fn, false);
if (src.is_output_predicate(old_fn)) {
dst.set_output_predicate(new_fn);
}
}
return app_ref(m.mk_app(new_fn, args.size(), args.c_ptr()), m);
}
app_ref mk_loop_counter::del_arg(app* fn) {
expr_ref_vector args(m);
func_decl* old_fn, *new_fn = fn->get_decl();
SASSERT(fn->get_num_args() > 0);
args.append(fn->get_num_args()-1, fn->get_args());
VERIFY (m_new2old.find(new_fn, old_fn));
return app_ref(m.mk_app(old_fn, args.size(), args.c_ptr()), m);
}
rule_set * mk_loop_counter::operator()(rule_set const & source) {
m_refs.reset();
m_old2new.reset();
m_new2old.reset();
rule_manager& rm = source.get_rule_manager();
rule_set * result = alloc(rule_set, m_ctx);
unsigned sz = source.get_num_rules();
rule_ref new_rule(rm);
app_ref_vector tail(m);
app_ref head(m);
svector<bool> neg;
rule_counter& vc = rm.get_counter();
for (unsigned i = 0; i < sz; ++i) {
tail.reset();
neg.reset();
rule & r = *source.get_rule(i);
unsigned cnt = vc.get_max_rule_var(r)+1;
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
for (unsigned j = 0; j < utsz; ++j, ++cnt) {
tail.push_back(add_arg(source, *result, r.get_tail(j), cnt));
neg.push_back(r.is_neg_tail(j));
}
for (unsigned j = utsz; j < tsz; ++j) {
tail.push_back(r.get_tail(j));
neg.push_back(false);
}
head = add_arg(source, *result, r.get_head(), cnt);
// set the loop counter to be an increment of the previous
bool found = false;
unsigned last = head->get_num_args()-1;
for (unsigned j = 0; !found && j < utsz; ++j) {
if (head->get_decl() == tail[j]->get_decl()) {
tail.push_back(m.mk_eq(head->get_arg(last),
a.mk_add(tail[j]->get_arg(last),
a.mk_numeral(rational(1), true))));
neg.push_back(false);
found = true;
}
}
// initialize loop counter to 0 if none was found.
if (!found) {
expr_ref_vector args(m);
args.append(head->get_num_args(), head->get_args());
args[last] = a.mk_numeral(rational(0), true);
head = m.mk_app(head->get_decl(), args.size(), args.c_ptr());
}
new_rule = rm.mk(head, tail.size(), tail.c_ptr(), neg.c_ptr(), r.name(), true);
result->add_rule(new_rule);
}
// model converter: remove references to extra argument.
// proof converter: remove references to extra argument as well.
return result;
}
rule_set * mk_loop_counter::revert(rule_set const & source) {
context& ctx = source.get_context();
rule_manager& rm = source.get_rule_manager();
rule_set * result = alloc(rule_set, ctx);
unsigned sz = source.get_num_rules();
rule_ref new_rule(rm);
app_ref_vector tail(m);
app_ref head(m);
svector<bool> neg;
for (unsigned i = 0; i < sz; ++i) {
tail.reset();
neg.reset();
rule & r = *source.get_rule(i);
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
for (unsigned j = 0; j < utsz; ++j) {
tail.push_back(del_arg(r.get_tail(j)));
neg.push_back(r.is_neg_tail(j));
}
for (unsigned j = utsz; j < tsz; ++j) {
tail.push_back(r.get_tail(j));
neg.push_back(false);
}
head = del_arg(r.get_head());
new_rule = rm.mk(head, tail.size(), tail.c_ptr(), neg.c_ptr(), r.name(), true);
result->add_rule(new_rule);
}
// model converter: ...
// proof converter: ...
return result;
}
};

View file

@ -0,0 +1,51 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_loop_counter.h
Abstract:
Add loop counter argument to relations.
Author:
Nikolaj Bjorner (nbjorner) 2013-03-31
Revision History:
--*/
#ifndef _DL_MK_LOOP_COUNTER_H_
#define _DL_MK_LOOP_COUNTER_H_
#include"dl_rule_transformer.h"
#include"arith_decl_plugin.h"
namespace datalog {
class mk_loop_counter : public rule_transformer::plugin {
ast_manager& m;
context& m_ctx;
arith_util a;
func_decl_ref_vector m_refs;
obj_map<func_decl, func_decl*> m_new2old;
obj_map<func_decl, func_decl*> m_old2new;
app_ref add_arg(rule_set const& src, rule_set& dst, app* fn, unsigned idx);
app_ref del_arg(app* fn);
public:
mk_loop_counter(context & ctx, unsigned priority = 33000);
~mk_loop_counter();
rule_set * operator()(rule_set const & source);
func_decl* get_old(func_decl* f) const { return m_new2old.find(f); }
rule_set * revert(rule_set const& source);
};
};
#endif /* _DL_MK_LOOP_COUNTER_H_ */

View file

@ -0,0 +1,383 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_magic_sets.cpp
Abstract:
<abstract>
Author:
Krystof Hoder (t-khoder) 2010-10-04.
Revision History:
--*/
#include<utility>
#include<sstream>
#include"ast_pp.h"
#include"dl_mk_magic_sets.h"
namespace datalog {
mk_magic_sets::mk_magic_sets(context & ctx, func_decl* goal) :
plugin(10000, true),
m_context(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_pinned(m),
m_goal(goal, m) {
}
void mk_magic_sets::reset() {
m_extentional.reset();
m_todo.reset();
m_adorned_preds.reset();
m_adornments.reset();
m_magic_preds.reset();
m_pinned.reset();
}
void mk_magic_sets::adornment::populate(app * lit, const var_idx_set & bound_vars) {
SASSERT(empty());
unsigned arity = lit->get_num_args();
for (unsigned i = 0; i < arity; i++) {
const expr * arg = lit->get_arg(i);
bool bound = !is_var(arg) || bound_vars.contains(to_var(arg)->get_idx());
push_back(bound ? AD_BOUND : AD_FREE);
}
}
std::string mk_magic_sets::adornment::to_string() const {
std::string res;
const_iterator eit = begin();
const_iterator eend = end();
for (; eit != eend; ++eit) {
res += (*eit == AD_BOUND)?'b':'f';
}
return res;
}
unsigned get_bound_arg_count(app * lit, const var_idx_set & bound_vars) {
unsigned res = 0;
unsigned n = lit->get_num_args();
for (unsigned i = 0; i < n; i++) {
const expr * arg = lit->get_arg(i);
if (!is_var(arg) || bound_vars.contains(to_var(arg)->get_idx())) {
SASSERT(is_var(arg) || is_app(arg));
SASSERT(!is_app(arg) || to_app(arg)->get_num_args()==0);
res++;
}
}
return res;
}
float mk_magic_sets::get_unbound_cost(app * lit, const var_idx_set & bound_vars) {
func_decl * pred = lit->get_decl();
float res = 1;
unsigned n = lit->get_num_args();
for (unsigned i = 0; i < n; i++) {
const expr * arg = lit->get_arg(i);
if (is_var(arg) && !bound_vars.contains(to_var(arg)->get_idx())) {
res *= m_context.get_sort_size_estimate(pred->get_domain(i));
}
//res-=1;
}
return res;
}
/**
\brief From \c cont which is list of indexes of tail literals of rule \c r, select
the index pointing to a literal with at least one bound variable that will be the next
bound literal in the process of creating an adorned rule. If all literals are unbound,
return -1.
*/
int mk_magic_sets::pop_bound(unsigned_vector & cont, rule * r, const var_idx_set & bound_vars) {
float best_cost;
int candidate_index = -1;
unsigned n = cont.size();
for (unsigned i=0; i<n; i++) {
app * lit = r->get_tail(cont[i]);
unsigned bound_cnt = get_bound_arg_count(lit, bound_vars);
if (bound_cnt==0) {
continue;
}
float cost = get_unbound_cost(lit, bound_vars);
if (candidate_index==-1 || cost<best_cost) {
best_cost = cost;
candidate_index = i;
}
}
if (candidate_index==-1) {
return -1;
}
if (candidate_index != static_cast<int>(n-1)) {
std::swap(cont[candidate_index], cont[n-1]);
}
unsigned res = cont.back();
cont.pop_back();
return res;
}
app * mk_magic_sets::adorn_literal(app * lit, const var_idx_set & bound_vars) {
SASSERT(!m_extentional.contains(lit->get_decl()));
func_decl * old_pred = lit->get_decl();
SASSERT(m.is_bool(old_pred->get_range()));
adornment_desc adn(old_pred);
adn.m_adornment.populate(lit, bound_vars);
adornment_map::entry * e = m_adorned_preds.insert_if_not_there2(adn, 0);
func_decl * new_pred = e->get_data().m_value;
if (new_pred==0) {
std::string suffix = "ad_"+adn.m_adornment.to_string();
new_pred = m_context.mk_fresh_head_predicate(
old_pred->get_name(), symbol(suffix.c_str()),
old_pred->get_arity(), old_pred->get_domain(), old_pred);
m_pinned.push_back(new_pred);
e->get_data().m_value = new_pred;
m_todo.push_back(adn);
m_adornments.insert(new_pred, adn.m_adornment);
}
app * res = m.mk_app(new_pred, lit->get_args());
m_pinned.push_back(res);
return res;
}
app * mk_magic_sets::create_magic_literal(app * l) {
func_decl * l_pred = l->get_decl();
SASSERT(m.is_bool(l_pred->get_range()));
pred_adornment_map::obj_map_entry * ae = m_adornments.find_core(l_pred);
SASSERT(ae);
const adornment & adn = ae->get_data().m_value;
unsigned l_arity = l->get_num_args();
ptr_vector<expr> bound_args;
for (unsigned i=0; i<l_arity; i++) {
if (adn[i]==AD_BOUND) {
bound_args.push_back(l->get_arg(i));
}
}
pred2pred::obj_map_entry * e = m_magic_preds.insert_if_not_there2(l_pred, 0);
func_decl * mag_pred = e->get_data().m_value;
if (mag_pred==0) {
unsigned mag_arity = bound_args.size();
ptr_vector<sort> mag_domain;
for (unsigned i=0; i<l_arity; i++) {
if (adn[i]==AD_BOUND) {
mag_domain.push_back(l_pred->get_domain(i));
}
}
mag_pred = m_context.mk_fresh_head_predicate(l_pred->get_name(), symbol("ms"),
mag_arity, mag_domain.c_ptr(), l_pred);
m_pinned.push_back(mag_pred);
e->get_data().m_value = mag_pred;
}
app * res = m.mk_app(mag_pred, bound_args.c_ptr());
m_pinned.push_back(res);
return res;
}
void mk_magic_sets::create_magic_rules(app * head, unsigned tail_cnt, app * const * tail, bool const* negated, rule_set& result) {
//TODO: maybe include relevant interpreted predicates from the original rule
ptr_vector<app> new_tail;
svector<bool> negations;
new_tail.push_back(create_magic_literal(head));
new_tail.append(tail_cnt, tail);
negations.push_back(false);
negations.append(tail_cnt, negated);
for (unsigned i=0; i<tail_cnt; i++) {
if (m_extentional.contains(tail[i]->get_decl())) {
continue;
}
app * mag_head = create_magic_literal(tail[i]);
rule * r = m_context.get_rule_manager().mk(mag_head, i+1, new_tail.c_ptr(), negations.c_ptr());
TRACE("dl", r->display(m_context,tout); );
result.add_rule(r);
}
}
void mk_magic_sets::transform_rule(const adornment & head_adornment, rule * r, rule_set& result) {
app * head = r->get_head();
unsigned head_len = head->get_num_args();
SASSERT(head_len==head_adornment.size());
var_idx_set bound_vars;
for (unsigned i=0; i<head_len; i++) {
expr * arg = head->get_arg(i);
if (head_adornment[i]==AD_BOUND && is_var(arg)) {
bound_vars.insert(to_var(arg)->get_idx());
}
}
unsigned processed_tail_len = r->get_uninterpreted_tail_size();
unsigned_vector exten_tails;
unsigned_vector inten_tails;
for (unsigned i=0; i<processed_tail_len; i++) {
app * t = r->get_tail(i);
if (m_extentional.contains(t->get_decl())) {
exten_tails.push_back(i);
}
else {
inten_tails.push_back(i);
}
}
ptr_vector<app> new_tail;
svector<bool> negations;
while (new_tail.size()!=processed_tail_len) {
bool intentional = false;
int curr_index = pop_bound(exten_tails, r, bound_vars);
if (curr_index==-1) {
curr_index = pop_bound(inten_tails, r,bound_vars);
if (curr_index!=-1) {
intentional = true;
}
}
if (curr_index==-1) {
if (!exten_tails.empty()) {
curr_index = exten_tails.back();
exten_tails.pop_back();
}
else {
SASSERT(!inten_tails.empty());
curr_index = inten_tails.back();
inten_tails.pop_back();
intentional = true;
}
}
SASSERT(curr_index!=-1);
app * curr = r->get_tail(curr_index);
if (intentional) {
curr = adorn_literal(curr, bound_vars);
}
new_tail.push_back(curr);
negations.push_back(r->is_neg_tail(curr_index));
bound_vars |= rm.collect_vars(curr);
}
func_decl * new_head_pred;
VERIFY( m_adorned_preds.find(adornment_desc(head->get_decl(), head_adornment), new_head_pred) );
app * new_head = m.mk_app(new_head_pred, head->get_args());
SASSERT(new_tail.size()==r->get_uninterpreted_tail_size());
create_magic_rules(new_head, new_tail.size(), new_tail.c_ptr(), negations.c_ptr(), result);
unsigned tail_len = r->get_tail_size();
for (unsigned i=processed_tail_len; i<tail_len; i++) {
new_tail.push_back(r->get_tail(i));
negations.push_back(r->is_neg_tail(i));
}
new_tail.push_back(create_magic_literal(new_head));
negations.push_back(false);
rule * nr = m_context.get_rule_manager().mk(new_head, new_tail.size(), new_tail.c_ptr(), negations.c_ptr());
result.add_rule(nr);
nr->set_accounting_parent_object(m_context, r);
}
void mk_magic_sets::create_transfer_rule(const adornment_desc & d, rule_set& result) {
func_decl * adn_pred = m_adorned_preds.find(d);
unsigned arity = adn_pred->get_arity();
SASSERT(arity == d.m_pred->get_arity());
ptr_vector<expr> args;
for (unsigned i=0; i<arity; i++) {
args.push_back(m.mk_var(i, adn_pred->get_domain(i)));
}
app * lit = m.mk_app(d.m_pred, args.c_ptr());
app * adn_lit = m.mk_app(adn_pred, args.c_ptr());
app * mag_lit = create_magic_literal(adn_lit);
app * tail[] = {lit, mag_lit};
rule * r = m_context.get_rule_manager().mk(adn_lit, 2, tail, 0);
result.add_rule(r);
}
rule_set * mk_magic_sets::operator()(rule_set const & source) {
if (!m_context.magic_sets_for_queries()) {
return 0;
}
SASSERT(source.contains(m_goal));
SASSERT(source.get_predicate_rules(m_goal).size() == 1);
app * goal_head = source.get_predicate_rules(m_goal)[0]->get_head();
unsigned init_rule_cnt = source.get_num_rules();
{
func_decl_set intentional;
for (unsigned i=0; i<init_rule_cnt; i++) {
func_decl* pred = source.get_rule(i)->get_decl();
intentional.insert(pred);
}
//now we iterate through all predicates and collect the set of extentional ones
const rule_dependencies * deps;
rule_dependencies computed_deps(m_context);
if (source.is_closed()) {
deps = &source.get_dependencies();
}
else {
computed_deps.populate(source);
deps = &computed_deps;
}
rule_dependencies::iterator it = deps->begin();
rule_dependencies::iterator end = deps->end();
for (; it!=end; ++it) {
func_decl * pred = it->m_key;
if (intentional.contains(pred)) {
continue;
}
SASSERT(it->m_value->empty());//extentional predicates have no dependency
m_extentional.insert(pred);
}
}
//adornment goal_adn;
//goal_adn.populate(goal_head, );
var_idx_set empty_var_idx_set;
adorn_literal(goal_head, empty_var_idx_set);
rule_set * result = alloc(rule_set, m_context);
result->inherit_predicates(source);
while (!m_todo.empty()) {
adornment_desc task = m_todo.back();
m_todo.pop_back();
const rule_vector & pred_rules = source.get_predicate_rules(task.m_pred);
rule_vector::const_iterator it = pred_rules.begin();
rule_vector::const_iterator end = pred_rules.end();
for (; it != end; ++it) {
rule * r = *it;
transform_rule(task.m_adornment, r, *result);
}
if (!m_context.get_rel_context()->is_empty_relation(task.m_pred)) {
//we need a rule to copy facts that are already in a relation into the adorned
//relation (since out intentional predicates can have facts, not only rules)
create_transfer_rule(task, *result);
}
}
app * adn_goal_head = adorn_literal(goal_head, empty_var_idx_set);
app * mag_goal_head = create_magic_literal(adn_goal_head);
SASSERT(mag_goal_head->is_ground());
rule * mag_goal_rule = m_context.get_rule_manager().mk(mag_goal_head, 0, 0, 0);
result->add_rule(mag_goal_rule);
rule * back_to_goal_rule = m_context.get_rule_manager().mk(goal_head, 1, &adn_goal_head, 0);
result->add_rule(back_to_goal_rule);
return result;
}
};

View file

@ -0,0 +1,135 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_magic_sets.h
Abstract:
<abstract>
Author:
Krystof Hoder (t-khoder) 2010-10-4.
Revision History:
--*/
#ifndef _DL_MK_MAGIC_SETS_H_
#define _DL_MK_MAGIC_SETS_H_
#include<utility>
#include"map.h"
#include"obj_pair_hashtable.h"
#include"dl_context.h"
#include"dl_rule_set.h"
#include"dl_rule_transformer.h"
namespace datalog {
/**
\brief Implements magic sets rule transformation.
According to A. Voronkov. Foundations of Deductive Databases.
The stratified negation is not in the book addressed wrt. magic sets, but it seems
that, for the purpose of magic sets, the negated literals should be treated just as
if they were non-negated (we are interested only in values of arguments, not in the
actual content of relations, at that point).
*/
class mk_magic_sets : public rule_transformer::plugin {
enum a_flag {
AD_FREE,
AD_BOUND
};
struct a_flag_hash {
typedef a_flag data;
unsigned operator()(a_flag x) const { return x; }
};
struct adornment : public svector<a_flag> {
void populate(app * lit, const var_idx_set & bound_vars);
bool operator==(const adornment & o) const {
return vectors_equal(*this, o);
}
std::string to_string() const;
};
struct adornment_desc {
func_decl * m_pred;
adornment m_adornment;
adornment_desc() {}
adornment_desc(func_decl * pred) : m_pred(pred) {}
adornment_desc(func_decl * pred, const adornment & a)
: m_pred(pred), m_adornment(a) {}
bool operator==(const adornment_desc & o) const {
//m_tail_adornment value is implied by the rule and the head adornment
return m_pred==o.m_pred && m_adornment==o.m_adornment;
}
unsigned hash() const {
return m_pred->hash()^svector_hash<a_flag_hash>()(m_adornment);
}
};
struct adorned_rule {
app * m_head;
adornment m_head_adornment;
ptr_vector<app> m_tail;
};
typedef hashtable<adornment_desc, obj_hash<adornment_desc>,
default_eq<adornment_desc> > adornment_set;
typedef map<adornment_desc, func_decl *, obj_hash<adornment_desc>,
default_eq<adornment_desc> > adornment_map;
typedef obj_map<func_decl, adornment> pred_adornment_map;
typedef obj_map<func_decl, func_decl *> pred2pred;
context & m_context;
ast_manager & m;
rule_manager& rm;
ast_ref_vector m_pinned;
/**
\brief Predicates from the original set that appear in a head of a rule
*/
func_decl_set m_extentional;
//adornment_set m_processed;
vector<adornment_desc> m_todo;
adornment_map m_adorned_preds;
pred_adornment_map m_adornments;
pred2pred m_magic_preds;
func_decl_ref m_goal;
void reset();
float get_unbound_cost(app * lit, const var_idx_set & bound_vars);
int pop_bound(unsigned_vector & cont, rule * r, const var_idx_set & bound_vars);
app * create_magic_literal(app * l);
void create_magic_rules(app * head, unsigned tail_cnt, app * const * tail, bool const* negated, rule_set& result);
app * adorn_literal(app * lit, const var_idx_set & bound_vars);
void transform_rule(const adornment & head_adornment, rule * r, rule_set& result);
void create_transfer_rule(const adornment_desc & d, rule_set& result);
public:
/**
\brief Create magic sets rule transformer for \c goal_rule. When applying the transformer,
the \c goal_rule must be present in the \c rule_set that is being transformed.
*/
mk_magic_sets(context & ctx, func_decl* goal);
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_MAGIC_SETS_H_ */

View file

@ -0,0 +1,135 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_magic_symbolic.cpp
Abstract:
Create Horn clauses for magic symbolic flow.
Q(x) :- A(y), B(z), phi1(x,y,z).
Q(x) :- C(y), phi2(x,y).
A(x) :- C(y), phi3(x,y).
A(x) :- A(y), phi3(x,y).
B(x) :- C(y), A(z), phi4(x,y,z).
C(x) :- phi5(x).
Transformed clauses:
Q_ans(x) :- Q_query(x), A_ans(y), B_ans(z), phi1(x,y,z).
Q_ans(x) :- Q_query(x), C_ans(y), phi2(x,y).
Q_query(x) :- true.
A_ans(x) :- A_query(x), C_ans(y), phi2(x,y)
A_ans(x) :- A_query(x), A_ans(y), phi3(x,y).
A_query(y) :- Q_query(x), phi1(x,y,z).
A_query(y) :- A_query(x), phi3(x,y).
A_query(z) :- B_query(x), C_ans(y), phi4(x,y,z).
B_ans(x) :- B_query(x), C_ans(y), A_ans(z), phi4(x,y,z).
B_query(z) :- Q_query(x), A_ans(y), phi1(x,y,z).
C_ans(x) :- C_query(x), phi5(x).
C_query(y) :- Q_query(x), phi2(x,y).
C_query(y) :- Q_query(x), phi3(x,y).
C_query(y) :- B_query(x), phi4(x,y,z).
General scheme:
A(x) :- P1(x_1), ..., Pn(x_n), phi(x,x1,..,x_n).
P(x) :- Prefix(x,y,z), A(z) ...
A_ans(x) :- A_query(x), P_i_ans(x_i), phi(x,..).
A_query(z) :- P_query(x), Prefix_ans(x,y,z).
Author:
Nikolaj Bjorner (nbjorner) 2013-06-19
Revision History:
--*/
#include"dl_mk_magic_symbolic.h"
#include"dl_context.h"
namespace datalog {
mk_magic_symbolic::mk_magic_symbolic(context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx) {
}
mk_magic_symbolic::~mk_magic_symbolic() { }
rule_set * mk_magic_symbolic::operator()(rule_set const & source) {
if (!m_ctx.get_params().magic()) {
return 0;
}
context& ctx = source.get_context();
rule_manager& rm = source.get_rule_manager();
rule_set * result = alloc(rule_set, ctx);
unsigned sz = source.get_num_rules();
rule_ref new_rule(rm);
app_ref_vector tail(m);
app_ref head(m);
svector<bool> neg;
for (unsigned i = 0; i < sz; ++i) {
rule & r = *source.get_rule(i);
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
tail.reset();
neg.reset();
for (unsigned j = utsz; j < tsz; ++j) {
tail.push_back(r.get_tail(j));
neg.push_back(false);
}
tail.push_back(mk_query(r.get_head()));
neg.push_back(false);
for (unsigned j = 0; j < utsz; ++j) {
tail.push_back(mk_ans(r.get_tail(j)));
neg.push_back(false);
}
new_rule = rm.mk(mk_ans(r.get_head()), tail.size(), tail.c_ptr(), neg.c_ptr(), r.name(), true);
result->add_rule(new_rule);
if (source.is_output_predicate(r.get_decl())) {
result->set_output_predicate(new_rule->get_decl());
new_rule = rm.mk(mk_query(r.get_head()), 0, 0, 0, r.name(), true);
result->add_rule(new_rule);
}
for (unsigned j = 0; j < utsz; ++j) {
new_rule = rm.mk(mk_query(r.get_tail(j)), tail.size()-utsz+j, tail.c_ptr(), neg.c_ptr(), r.name(), true);
result->add_rule(new_rule);
}
}
TRACE("dl", result->display(tout););
return result;
}
app_ref mk_magic_symbolic::mk_query(app* q) {
string_buffer<64> name;
func_decl* f = q->get_decl();
name << f->get_name() << "!query";
func_decl_ref g(m);
g = m.mk_func_decl(symbol(name.c_str()), f->get_arity(), f->get_domain(), f->get_range());
m_ctx.register_predicate(g, false);
return app_ref(m.mk_app(g, q->get_num_args(), q->get_args()), m);
}
app_ref mk_magic_symbolic::mk_ans(app* q) {
string_buffer<64> name;
func_decl* f = q->get_decl();
func_decl_ref g(m);
name << f->get_name() << "!ans";
g = m.mk_func_decl(symbol(name.c_str()), f->get_arity(), f->get_domain(), f->get_range());
m_ctx.register_predicate(g, false);
return app_ref(m.mk_app(g, q->get_num_args(), q->get_args()), m);
}
};

View file

@ -0,0 +1,40 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_magic_symbolic.h
Abstract:
Create Horn clauses for magic symbolic transformation.
Author:
Nikolaj Bjorner (nbjorner) 2013-06-19
Revision History:
--*/
#ifndef _DL_MK_MAGIC_SYMBOLIC_H_
#define _DL_MK_MAGIC_SYMBOLIC_H_
#include"dl_rule_transformer.h"
namespace datalog {
class mk_magic_symbolic : public rule_transformer::plugin {
ast_manager& m;
context& m_ctx;
app_ref mk_ans(app* q);
app_ref mk_query(app* q);
public:
mk_magic_symbolic(context & ctx, unsigned priority = 33037);
~mk_magic_symbolic();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_MAGIC_SYMBOLIC_H_ */

View file

@ -0,0 +1,367 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_quantifier_abstraction.cpp
Abstract:
Create quantified Horn clauses from benchmarks with arrays.
Author:
Ken McMillan
Andrey Rybalchenko
Nikolaj Bjorner (nbjorner) 2013-04-02
Revision History:
--*/
#include "dl_mk_quantifier_abstraction.h"
#include "dl_context.h"
#include "expr_safe_replace.h"
#include "expr_abstract.h"
namespace datalog {
// model converter:
// Given model for P^(x, y, i, a[i])
// create model: P(x,y,a) == forall i . P^(x,y,i,a[i])
// requires substitution and list of bound variables.
class mk_quantifier_abstraction::qa_model_converter : public model_converter {
ast_manager& m;
func_decl_ref_vector m_old_funcs;
func_decl_ref_vector m_new_funcs;
vector<expr_ref_vector> m_subst;
vector<sort_ref_vector> m_sorts;
vector<svector<bool> > m_bound;
public:
qa_model_converter(ast_manager& m):
m(m), m_old_funcs(m), m_new_funcs(m) {}
virtual ~qa_model_converter() {}
virtual model_converter * translate(ast_translation & translator) {
return alloc(qa_model_converter, m);
}
void insert(func_decl* old_p, func_decl* new_p, expr_ref_vector& sub, sort_ref_vector& sorts, svector<bool> const& bound) {
m_old_funcs.push_back(old_p);
m_new_funcs.push_back(new_p);
m_subst.push_back(sub);
m_bound.push_back(bound);
m_sorts.push_back(sorts);
}
virtual void operator()(model_ref & old_model) {
model_ref new_model = alloc(model, m);
for (unsigned i = 0; i < m_new_funcs.size(); ++i) {
func_decl* p = m_new_funcs[i].get();
func_decl* q = m_old_funcs[i].get();
expr_ref_vector const& sub = m_subst[i];
sort_ref_vector const& sorts = m_sorts[i];
svector<bool> const& is_bound = m_bound[i];
func_interp* f = old_model->get_func_interp(p);
expr_ref body(m);
unsigned arity_p = p->get_arity();
unsigned arity_q = q->get_arity();
SASSERT(0 < arity_p);
func_interp* g = alloc(func_interp, m, arity_q);
if (f) {
body = f->get_interp();
SASSERT(!f->is_partial());
SASSERT(body);
}
else {
body = m.mk_false();
}
// Create quantifier wrapper around body.
TRACE("dl", tout << mk_pp(body, m) << "\n";);
// 1. replace variables by the compound terms from
// the original predicate.
expr_safe_replace rep(m);
for (unsigned i = 0; i < sub.size(); ++i) {
rep.insert(m.mk_var(i, m.get_sort(sub[i])), sub[i]);
}
rep(body);
rep.reset();
TRACE("dl", tout << mk_pp(body, m) << "\n";);
// 2. replace bound variables by constants.
expr_ref_vector consts(m), bound(m), free(m);
svector<symbol> names;
ptr_vector<sort> bound_sorts;
for (unsigned i = 0; i < sorts.size(); ++i) {
sort* s = sorts[i];
consts.push_back(m.mk_fresh_const("C", s));
rep.insert(m.mk_var(i, s), consts.back());
if (is_bound[i]) {
bound.push_back(consts.back());
names.push_back(symbol(i));
bound_sorts.push_back(s);
}
else {
free.push_back(consts.back());
}
}
rep(body);
rep.reset();
TRACE("dl", tout << mk_pp(body, m) << "\n";);
// 3. abstract and quantify those variables that should be bound.
expr_abstract(m, 0, bound.size(), bound.c_ptr(), body, body);
body = m.mk_forall(names.size(), bound_sorts.c_ptr(), names.c_ptr(), body);
TRACE("dl", tout << mk_pp(body, m) << "\n";);
// 4. replace remaining constants by variables.
for (unsigned i = 0; i < free.size(); ++i) {
rep.insert(free[i].get(), m.mk_var(i, m.get_sort(free[i].get())));
}
rep(body);
g->set_else(body);
TRACE("dl", tout << mk_pp(body, m) << "\n";);
new_model->register_decl(q, g);
}
old_model = new_model;
}
};
mk_quantifier_abstraction::mk_quantifier_abstraction(
context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx),
a(m),
m_refs(m) {
}
mk_quantifier_abstraction::~mk_quantifier_abstraction() {
}
func_decl* mk_quantifier_abstraction::declare_pred(rule_set const& rules, rule_set& dst, func_decl* old_p) {
if (rules.is_output_predicate(old_p)) {
dst.inherit_predicate(rules, old_p, old_p);
return 0;
}
unsigned sz = old_p->get_arity();
unsigned num_arrays = 0;
for (unsigned i = 0; i < sz; ++i) {
if (a.is_array(old_p->get_domain(i))) {
num_arrays++;
}
}
if (num_arrays == 0) {
return 0;
}
func_decl* new_p = 0;
if (!m_old2new.find(old_p, new_p)) {
expr_ref_vector sub(m), vars(m);
svector<bool> bound;
sort_ref_vector domain(m), sorts(m);
expr_ref arg(m);
for (unsigned i = 0; i < sz; ++i) {
sort* s0 = old_p->get_domain(i);
unsigned lookahead = 0;
sort* s = s0;
while (a.is_array(s)) {
lookahead += get_array_arity(s);
s = get_array_range(s);
}
arg = m.mk_var(bound.size() + lookahead, s0);
s = s0;
while (a.is_array(s)) {
unsigned arity = get_array_arity(s);
expr_ref_vector args(m);
for (unsigned j = 0; j < arity; ++j) {
sort* s1 = get_array_domain(s, j);
domain.push_back(s1);
args.push_back(m.mk_var(bound.size(), s1));
bound.push_back(true);
sorts.push_back(s1);
}
arg = mk_select(arg, args.size(), args.c_ptr());
s = get_array_range(s);
}
domain.push_back(s);
bound.push_back(false);
sub.push_back(arg);
sorts.push_back(s0);
}
SASSERT(old_p->get_range() == m.mk_bool_sort());
new_p = m.mk_func_decl(old_p->get_name(), domain.size(), domain.c_ptr(), old_p->get_range());
m_refs.push_back(new_p);
m_ctx.register_predicate(new_p, false);
if (m_mc) {
m_mc->insert(old_p, new_p, sub, sorts, bound);
}
m_old2new.insert(old_p, new_p);
}
return new_p;
}
app_ref mk_quantifier_abstraction::mk_head(rule_set const& rules, rule_set& dst, app* p, unsigned idx) {
func_decl* new_p = declare_pred(rules, dst, p->get_decl());
if (!new_p) {
return app_ref(p, m);
}
expr_ref_vector args(m);
expr_ref arg(m);
unsigned sz = p->get_num_args();
for (unsigned i = 0; i < sz; ++i) {
arg = p->get_arg(i);
sort* s = m.get_sort(arg);
while (a.is_array(s)) {
unsigned arity = get_array_arity(s);
for (unsigned j = 0; j < arity; ++j) {
args.push_back(m.mk_var(idx++, get_array_domain(s, j)));
}
arg = mk_select(arg, arity, args.c_ptr()+args.size()-arity);
s = get_array_range(s);
}
args.push_back(arg);
}
TRACE("dl",
tout << mk_pp(new_p, m) << "\n";
for (unsigned i = 0; i < args.size(); ++i) {
tout << mk_pp(args[i].get(), m) << "\n";
});
return app_ref(m.mk_app(new_p, args.size(), args.c_ptr()), m);
}
app_ref mk_quantifier_abstraction::mk_tail(rule_set const& rules, rule_set& dst, app* p) {
func_decl* old_p = p->get_decl();
func_decl* new_p = declare_pred(rules, dst, old_p);
if (!new_p) {
return app_ref(p, m);
}
SASSERT(new_p->get_arity() > old_p->get_arity());
unsigned num_extra_args = new_p->get_arity() - old_p->get_arity();
var_shifter shift(m);
expr_ref p_shifted(m);
shift(p, num_extra_args, p_shifted);
app* ps = to_app(p_shifted);
expr_ref_vector args(m);
app_ref_vector pats(m);
sort_ref_vector vars(m);
svector<symbol> names;
expr_ref arg(m);
unsigned idx = 0;
unsigned sz = p->get_num_args();
for (unsigned i = 0; i < sz; ++i) {
arg = ps->get_arg(i);
sort* s = m.get_sort(arg);
bool is_pattern = false;
while (a.is_array(s)) {
is_pattern = true;
unsigned arity = get_array_arity(s);
for (unsigned j = 0; j < arity; ++j) {
vars.push_back(get_array_domain(s, j));
names.push_back(symbol(idx));
args.push_back(m.mk_var(idx++, vars.back()));
}
arg = mk_select(arg, arity, args.c_ptr()+args.size()-arity);
s = get_array_range(s);
}
if (is_pattern) {
pats.push_back(to_app(arg));
}
args.push_back(arg);
}
expr* pat = 0;
expr_ref pattern(m);
pattern = m.mk_pattern(pats.size(), pats.c_ptr());
pat = pattern.get();
app_ref result(m);
symbol qid, skid;
result = m.mk_app(new_p, args.size(), args.c_ptr());
result = m.mk_eq(m.mk_forall(vars.size(), vars.c_ptr(), names.c_ptr(), result, 1, qid, skid, 1, &pat), m.mk_true());
return result;
}
expr * mk_quantifier_abstraction::mk_select(expr* arg, unsigned num_args, expr* const* args) {
ptr_vector<expr> args2;
args2.push_back(arg);
args2.append(num_args, args);
return a.mk_select(args2.size(), args2.c_ptr());
}
rule_set * mk_quantifier_abstraction::operator()(rule_set const & source) {
TRACE("dl", tout << "quantify " << source.get_num_rules() << " " << m_ctx.get_params().quantify_arrays() << "\n";);
if (!m_ctx.get_params().quantify_arrays()) {
return 0;
}
unsigned sz = source.get_num_rules();
for (unsigned i = 0; i < sz; ++i) {
rule& r = *source.get_rule(i);
if (r.has_negation()) {
return 0;
}
}
m_refs.reset();
m_old2new.reset();
m_new2old.reset();
rule_manager& rm = source.get_rule_manager();
rule_ref new_rule(rm);
expr_ref_vector tail(m);
app_ref head(m);
expr_ref fml(m);
rule_counter& vc = rm.get_counter();
if (m_ctx.get_model_converter()) {
m_mc = alloc(qa_model_converter, m);
}
rule_set * result = alloc(rule_set, m_ctx);
for (unsigned i = 0; i < sz; ++i) {
tail.reset();
rule & r = *source.get_rule(i);
TRACE("dl", r.display(m_ctx, tout); );
unsigned cnt = vc.get_max_rule_var(r)+1;
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
for (unsigned j = 0; j < utsz; ++j) {
tail.push_back(mk_tail(source, *result, r.get_tail(j)));
}
for (unsigned j = utsz; j < tsz; ++j) {
tail.push_back(r.get_tail(j));
}
head = mk_head(source, *result, r.get_head(), cnt);
fml = m.mk_implies(m.mk_and(tail.size(), tail.c_ptr()), head);
rule_ref_vector added_rules(rm);
proof_ref pr(m);
rm.mk_rule(fml, pr, *result);
TRACE("dl", result->last()->display(m_ctx, tout););
}
// proof converter: proofs are not necessarily preserved using this transformation.
if (m_old2new.empty()) {
dealloc(result);
dealloc(m_mc);
result = 0;
}
else {
m_ctx.add_model_converter(m_mc);
}
m_mc = 0;
return result;
}
};

View file

@ -0,0 +1,64 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_quantifier_abstraction.h
Abstract:
Convert clauses with array arguments to predicates
into Quantified Horn clauses.
Author:
Ken McMillan
Andrey Rybalchenko
Nikolaj Bjorner (nbjorner) 2013-04-02
Revision History:
Based on approach suggested in SAS 2013 paper
"On Solving Universally Quantified Horn Clauses"
--*/
#ifndef _DL_MK_QUANTIFIER_ABSTRACTION_H_
#define _DL_MK_QUANTIFIER_ABSTRACTION_H_
#include"dl_rule_transformer.h"
#include"array_decl_plugin.h"
namespace datalog {
class context;
class mk_quantifier_abstraction : public rule_transformer::plugin {
class qa_model_converter;
ast_manager& m;
context& m_ctx;
array_util a;
func_decl_ref_vector m_refs;
obj_map<func_decl, func_decl*> m_new2old;
obj_map<func_decl, func_decl*> m_old2new;
qa_model_converter* m_mc;
func_decl* declare_pred(rule_set const& rules, rule_set& dst, func_decl* old_p);
app_ref mk_head(rule_set const& rules, rule_set& dst, app* p, unsigned idx);
app_ref mk_tail(rule_set const& rules, rule_set& dst, app* p);
expr* mk_select(expr* a, unsigned num_args, expr* const* args);
public:
mk_quantifier_abstraction(context & ctx, unsigned priority);
virtual ~mk_quantifier_abstraction();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_QUANTIFIER_ABSTRACTION_H_ */

View file

@ -0,0 +1,302 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_quantifier_instantiation.cpp
Abstract:
Convert Quantified Horn clauses into non-quantified clauses using
instantiation.
Author:
Ken McMillan
Andrey Rybalchenko
Nikolaj Bjorner (nbjorner) 2013-04-02
Revision History:
Based on approach suggested in the SAS 2013 paper
"On Solving Universally Quantified Horn Clauses"
--*/
#include "dl_mk_quantifier_instantiation.h"
#include "dl_context.h"
#include "pattern_inference.h"
namespace datalog {
mk_quantifier_instantiation::mk_quantifier_instantiation(
context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx),
m_var2cnst(m),
m_cnst2var(m) {
}
mk_quantifier_instantiation::~mk_quantifier_instantiation() {
}
void mk_quantifier_instantiation::extract_quantifiers(rule& r, expr_ref_vector& conjs, quantifier_ref_vector& qs) {
conjs.reset();
qs.reset();
unsigned tsz = r.get_tail_size();
for (unsigned j = 0; j < tsz; ++j) {
conjs.push_back(r.get_tail(j));
}
qe::flatten_and(conjs);
for (unsigned j = 0; j < conjs.size(); ++j) {
expr* e = conjs[j].get();
quantifier* q;
if (rule_manager::is_forall(m, e, q)) {
qs.push_back(q);
conjs[j] = conjs.back();
conjs.pop_back();
--j;
}
}
}
void mk_quantifier_instantiation::instantiate_quantifier(quantifier* q, expr_ref_vector & conjs) {
expr_ref qe(m);
qe = q;
m_var2cnst(qe);
q = to_quantifier(qe);
if (q->get_num_patterns() == 0) {
proof_ref new_pr(m);
pattern_inference_params params;
pattern_inference infer(m, params);
infer(q, qe, new_pr);
q = to_quantifier(qe);
}
unsigned num_patterns = q->get_num_patterns();
for (unsigned i = 0; i < num_patterns; ++i) {
expr * pat = q->get_pattern(i);
SASSERT(m.is_pattern(pat));
instantiate_quantifier(q, to_app(pat), conjs);
}
}
void mk_quantifier_instantiation::instantiate_quantifier(quantifier* q, app* pat, expr_ref_vector & conjs) {
m_binding.reset();
m_binding.resize(q->get_num_decls());
term_pairs todo;
match(0, pat, 0, todo, q, conjs);
}
void mk_quantifier_instantiation::match(unsigned i, app* pat, unsigned j, term_pairs& todo, quantifier* q, expr_ref_vector& conjs) {
TRACE("dl", tout << "match" << mk_pp(pat, m) << "\n";);
while (j < todo.size()) {
expr* p = todo[j].first;
expr* t = todo[j].second;
if (is_var(p)) {
unsigned idx = to_var(p)->get_idx();
if (!m_binding[idx]) {
m_binding[idx] = t;
match(i, pat, j + 1, todo, q, conjs);
m_binding[idx] = 0;
return;
}
++j;
continue;
}
if (!is_app(p)) {
return;
}
app* a1 = to_app(p);
unsigned id = t->get_id();
unsigned next_id = id;
unsigned sz = todo.size();
do {
expr* t2 = m_terms[next_id];
if (is_app(t2)) {
app* a2 = to_app(t2);
if (a1->get_decl() == a2->get_decl() &&
a1->get_num_args() == a2->get_num_args()) {
for (unsigned k = 0; k < a1->get_num_args(); ++k) {
todo.push_back(std::make_pair(a1->get_arg(k), a2->get_arg(k)));
}
match(i, pat, j + 1, todo, q, conjs);
todo.resize(sz);
}
}
next_id = m_uf.next(next_id);
}
while (next_id != id);
return;
}
if (i == pat->get_num_args()) {
yield_binding(q, conjs);
return;
}
expr* arg = pat->get_arg(i);
ptr_vector<expr>* terms = 0;
if (m_funs.find(to_app(arg)->get_decl(), terms)) {
for (unsigned k = 0; k < terms->size(); ++k) {
todo.push_back(std::make_pair(arg, (*terms)[k]));
match(i + 1, pat, j, todo, q, conjs);
todo.pop_back();
}
}
}
void mk_quantifier_instantiation::yield_binding(quantifier* q, expr_ref_vector& conjs) {
DEBUG_CODE(
for (unsigned i = 0; i < m_binding.size(); ++i) {
SASSERT(m_binding[i]);
});
m_binding.reverse();
expr_ref res(m);
instantiate(m, q, m_binding.c_ptr(), res);
m_binding.reverse();
m_cnst2var(res);
conjs.push_back(res);
TRACE("dl", tout << mk_pp(q, m) << "\n==>\n" << mk_pp(res, m) << "\n";);
}
void mk_quantifier_instantiation::collect_egraph(expr* e) {
expr* e1, *e2;
m_todo.push_back(e);
expr_fast_mark1 visited;
while (!m_todo.empty()) {
e = m_todo.back();
m_todo.pop_back();
if (visited.is_marked(e)) {
continue;
}
unsigned n = e->get_id();
if (n >= m_terms.size()) {
m_terms.resize(n+1);
}
m_terms[n] = e;
visited.mark(e);
if (m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) {
m_uf.merge(e1->get_id(), e2->get_id());
}
if (is_app(e)) {
app* ap = to_app(e);
ptr_vector<expr>* terms = 0;
if (!m_funs.find(ap->get_decl(), terms)) {
terms = alloc(ptr_vector<expr>);
m_funs.insert(ap->get_decl(), terms);
}
terms->push_back(e);
m_todo.append(ap->get_num_args(), ap->get_args());
}
}
}
void mk_quantifier_instantiation::instantiate_rule(rule& r, expr_ref_vector& conjs, quantifier_ref_vector& qs, rule_set& rules) {
rule_manager& rm = m_ctx.get_rule_manager();
expr_ref fml(m), cnst(m);
var_ref var(m);
ptr_vector<sort> sorts;
r.get_vars(sorts);
m_uf.reset();
m_terms.reset();
m_var2cnst.reset();
m_cnst2var.reset();
fml = m.mk_and(conjs.size(), conjs.c_ptr());
for (unsigned i = 0; i < sorts.size(); ++i) {
if (!sorts[i]) {
sorts[i] = m.mk_bool_sort();
}
var = m.mk_var(i, sorts[i]);
cnst = m.mk_fresh_const("C", sorts[i]);
m_var2cnst.insert(var, cnst);
m_cnst2var.insert(cnst, var);
}
fml = m.mk_and(conjs.size(), conjs.c_ptr());
m_var2cnst(fml);
collect_egraph(fml);
for (unsigned i = 0; i < qs.size(); ++i) {
instantiate_quantifier(qs[i].get(), conjs);
}
obj_map<func_decl, ptr_vector<expr>*>::iterator it = m_funs.begin(), end = m_funs.end();
for (; it != end; ++it) {
dealloc(it->m_value);
}
m_funs.reset();
fml = m.mk_and(conjs.size(), conjs.c_ptr());
fml = m.mk_implies(fml, r.get_head());
TRACE("dl", r.display(m_ctx, tout); tout << mk_pp(fml, m) << "\n";);
rule_set added_rules(m_ctx);
proof_ref pr(m);
rm.mk_rule(fml, pr, added_rules);
if (r.get_proof()) {
// use def-axiom to encode that new rule is a weakening of the original.
proof* p1 = r.get_proof();
for (unsigned i = 0; i < added_rules.get_num_rules(); ++i) {
rule* r2 = added_rules.get_rule(i);
r2->to_formula(fml);
pr = m.mk_modus_ponens(m.mk_def_axiom(m.mk_implies(m.get_fact(p1), fml)), p1);
r2->set_proof(m, pr);
}
}
rules.add_rules(added_rules);
}
rule_set * mk_quantifier_instantiation::operator()(rule_set const & source) {
TRACE("dl", tout << m_ctx.get_params().instantiate_quantifiers() << "\n";);
if (!m_ctx.get_params().instantiate_quantifiers()) {
return 0;
}
bool has_quantifiers = false;
unsigned sz = source.get_num_rules();
for (unsigned i = 0; !has_quantifiers && i < sz; ++i) {
rule& r = *source.get_rule(i);
has_quantifiers = has_quantifiers || r.has_quantifiers();
if (r.has_negation()) {
return 0;
}
}
if (!has_quantifiers) {
return 0;
}
expr_ref_vector conjs(m);
quantifier_ref_vector qs(m);
rule_set * result = alloc(rule_set, m_ctx);
bool instantiated = false;
for (unsigned i = 0; i < sz; ++i) {
rule * r = source.get_rule(i);
extract_quantifiers(*r, conjs, qs);
if (qs.empty()) {
result->add_rule(r);
}
else {
instantiate_rule(*r, conjs, qs, *result);
instantiated = true;
}
}
// model convertion: identity function.
if (instantiated) {
result->inherit_predicates(source);
}
else {
dealloc(result);
result = 0;
}
return result;
}
};

View file

@ -0,0 +1,136 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_quantifier_instantiation.h
Abstract:
Convert Quantified Horn clauses into non-quantified clauses using
instantiation.
Author:
Ken McMillan
Andrey Rybalchenko
Nikolaj Bjorner (nbjorner) 2013-04-02
Revision History:
Based on approach suggested in the SAS 2013 paper
"On Solving Universally Quantified Horn Clauses"
--*/
#ifndef _DL_MK_QUANTIFIER_INSTANTIATION_H_
#define _DL_MK_QUANTIFIER_INSTANTIATION_H_
#include"dl_rule_transformer.h"
#include"expr_safe_replace.h"
namespace datalog {
class context;
class mk_quantifier_instantiation : public rule_transformer::plugin {
typedef svector<std::pair<expr*,expr*> > term_pairs;
class union_find {
unsigned_vector m_find;
unsigned_vector m_size;
unsigned_vector m_next;
void ensure_size(unsigned v) {
while (v >= get_num_vars()) {
mk_var();
}
}
public:
unsigned mk_var() {
unsigned r = m_find.size();
m_find.push_back(r);
m_size.push_back(1);
m_next.push_back(r);
return r;
}
unsigned get_num_vars() const { return m_find.size(); }
unsigned find(unsigned v) const {
if (v >= get_num_vars()) {
return v;
}
while (true) {
unsigned new_v = m_find[v];
if (new_v == v)
return v;
v = new_v;
}
}
unsigned next(unsigned v) const {
if (v >= get_num_vars()) {
return v;
}
return m_next[v];
}
bool is_root(unsigned v) const {
return v >= get_num_vars() || m_find[v] == v;
}
void merge(unsigned v1, unsigned v2) {
unsigned r1 = find(v1);
unsigned r2 = find(v2);
if (r1 == r2)
return;
ensure_size(v1);
ensure_size(v2);
if (m_size[r1] > m_size[r2])
std::swap(r1, r2);
m_find[r1] = r2;
m_size[r2] += m_size[r1];
std::swap(m_next[r1], m_next[r2]);
}
void reset() {
m_find.reset();
m_next.reset();
m_size.reset();
}
};
ast_manager& m;
context& m_ctx;
expr_safe_replace m_var2cnst;
expr_safe_replace m_cnst2var;
union_find m_uf;
ptr_vector<expr> m_todo;
ptr_vector<expr> m_terms;
ptr_vector<expr> m_binding;
obj_map<func_decl, ptr_vector<expr>*> m_funs;
void extract_quantifiers(rule& r, expr_ref_vector& conjs, quantifier_ref_vector& qs);
void collect_egraph(expr* e);
void instantiate_rule(rule& r, expr_ref_vector& conjs, quantifier_ref_vector& qs, rule_set& rules);
void instantiate_quantifier(quantifier* q, expr_ref_vector & conjs);
void instantiate_quantifier(quantifier* q, app* pat, expr_ref_vector & conjs);
void match(unsigned i, app* pat, unsigned j, term_pairs& todo, quantifier* q, expr_ref_vector& conjs);
void yield_binding(quantifier* q, expr_ref_vector& conjs);
public:
mk_quantifier_instantiation(context & ctx, unsigned priority);
virtual ~mk_quantifier_instantiation();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_QUANTIFIER_INSTANTIATION_H_ */

View file

@ -0,0 +1,896 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_rule_inliner.cpp
Abstract:
Rule transformer which simplifies interpreted tails
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
Added linear_inline 2012-9-10 (nbjorner)
Disable inliner for quantified rules 2012-10-31 (nbjorner)
Notes:
Resolution transformation (resolve):
P(x) :- Q(y), phi(x,y) Q(y) :- R(z), psi(y,z)
--------------------------------------------------
P(x) :- R(z), phi(x,y), psi(y,z)
Proof converter:
replace assumption (*) by rule and upper assumptions.
Subsumption transformation (remove rule):
P(x) :- Q(y), phi(x,y) Rules
---------------------------------
Rules
Model converter:
P(x) := P(x) or (exists y . Q(y) & phi(x,y))
--*/
#include <sstream>
#include "ast_pp.h"
#include "rewriter.h"
#include "rewriter_def.h"
#include "dl_mk_rule_inliner.h"
namespace datalog {
// -----------------------------------
//
// mk_rule_inliner::rule_unifier
//
// -----------------------------------
bool rule_unifier::unify_rules(const rule& tgt, unsigned tgt_idx, const rule& src) {
rule_counter& vc = m_rm.get_counter();
unsigned var_cnt = std::max(vc.get_max_rule_var(tgt), vc.get_max_rule_var(src))+1;
m_subst.reset();
m_subst.reserve(2, var_cnt);
m_ready = m_unif(tgt.get_tail(tgt_idx), src.get_head(), m_subst);
if (m_ready) {
m_deltas[0] = 0;
m_deltas[1] = var_cnt;
TRACE("dl",
output_predicate(m_context, src.get_head(), tout << "unify rules ");
output_predicate(m_context, tgt.get_head(), tout << "\n");
tout << "\n";);
}
return m_ready;
}
void rule_unifier::apply(app * a, bool is_tgt, app_ref& res) {
expr_ref res_e(m);
TRACE("dl", output_predicate(m_context, a, tout); tout << "\n";);
m_subst.apply(2, m_deltas, expr_offset(a, is_tgt ? 0 : 1), res_e);
SASSERT(is_app(res_e.get()));
res = to_app(res_e.get());
}
void rule_unifier::apply(
rule const& r, bool is_tgt, unsigned skipped_index,
app_ref_vector& res, svector<bool>& res_neg) {
unsigned rule_len = r.get_tail_size();
for (unsigned i = 0; i < rule_len; i++) {
if (i != skipped_index) { //i can never be UINT_MAX, so we'll never skip if we're not supposed to
app_ref new_tail_el(m);
apply(r.get_tail(i), is_tgt, new_tail_el);
res.push_back(new_tail_el);
res_neg.push_back(r.is_neg_tail(i));
}
}
}
bool rule_unifier::apply(rule const& tgt, unsigned tail_index, rule const& src, rule_ref& res) {
SASSERT(m_ready);
app_ref new_head(m);
app_ref_vector tail(m);
svector<bool> tail_neg;
rule_ref simpl_rule(m_rm);
apply(tgt.get_head(), true, new_head);
apply(tgt, true, tail_index, tail, tail_neg);
apply(src, false, UINT_MAX, tail, tail_neg);
mk_rule_inliner::remove_duplicate_tails(tail, tail_neg);
SASSERT(tail.size()==tail_neg.size());
res = m_rm.mk(new_head, tail.size(), tail.c_ptr(), tail_neg.c_ptr(), tgt.name(), m_normalize);
res->set_accounting_parent_object(m_context, const_cast<rule*>(&tgt));
TRACE("dl",
tgt.display(m_context, tout << "tgt (" << tail_index << "): \n");
src.display(m_context, tout << "src:\n");
res->display(m_context, tout << "res\n");
// m_unif.display(tout << "subst:\n");
);
if (m_normalize) {
m_rm.fix_unbound_vars(res, true);
if (m_interp_simplifier.transform_rule(res.get(), simpl_rule)) {
res = simpl_rule;
return true;
}
else {
return false;
}
}
else {
return true;
}
}
expr_ref_vector rule_unifier::get_rule_subst(const rule& r, bool is_tgt) {
SASSERT(m_ready);
expr_ref_vector result(m);
ptr_vector<sort> sorts;
expr_ref v(m), w(m);
r.get_vars(sorts);
for (unsigned i = 0; i < sorts.size(); ++i) {
if (!sorts[i]) {
sorts[i] = m.mk_bool_sort();
}
v = m.mk_var(i, sorts[i]);
m_subst.apply(2, m_deltas, expr_offset(v, is_tgt?0:1), w);
result.push_back(w);
}
return result;
}
// -----------------------------------
//
// mk_rule_inliner
//
// -----------------------------------
/**
Inline occurrences of rule src at tail_index in tgt and return the result in res.
*/
bool mk_rule_inliner::try_to_inline_rule(rule& tgt, rule& src, unsigned tail_index, rule_ref& res)
{
SASSERT(tail_index<tgt.get_positive_tail_size());
SASSERT(!tgt.is_neg_tail(tail_index));
tgt.norm_vars(m_context.get_rule_manager());
SASSERT(!has_quantifier(src));
if (!m_unifier.unify_rules(tgt, tail_index, src)) {
return false;
}
if (m_unifier.apply(tgt, tail_index, src, res)) {
if (m_context.generate_proof_trace()) {
expr_ref_vector s1 = m_unifier.get_rule_subst(tgt, true);
expr_ref_vector s2 = m_unifier.get_rule_subst(src, false);
datalog::resolve_rule(tgt, src, tail_index, s1, s2, *res.get());
}
return true;
}
else {
TRACE("dl", res->display(m_context, tout << "interpreted tail is unsat\n"););
//the interpreted part is unsatisfiable
return false;
}
}
// TBD: replace by r.has_quantifiers() and test
bool mk_rule_inliner::has_quantifier(rule const& r) const {
unsigned utsz = r.get_uninterpreted_tail_size();
for (unsigned i = utsz; i < r.get_tail_size(); ++i) {
if (r.get_tail(i)->has_quantifiers()) return true;
}
return false;
}
void mk_rule_inliner::count_pred_occurrences(rule_set const & orig)
{
rel_context_base* rel = m_context.get_rel_context();
if (rel) {
rel->collect_non_empty_predicates(m_preds_with_facts);
}
rule_set::iterator rend = orig.end();
for (rule_set::iterator rit = orig.begin(); rit!=rend; ++rit) {
rule * r = *rit;
func_decl * head_pred = r->get_decl();
m_head_pred_ctr.inc(head_pred);
if (r->get_tail_size()>0) {
m_head_pred_non_empty_tails_ctr.inc(head_pred);
}
unsigned ut_len = r->get_uninterpreted_tail_size();
for (unsigned i=0; i<ut_len; i++) {
func_decl * pred = r->get_decl(i);
m_tail_pred_ctr.inc(pred);
if (r->is_neg_tail(i)) {
m_preds_with_neg_occurrence.insert(pred);
}
}
}
}
bool mk_rule_inliner::inlining_allowed(rule_set const& source, func_decl * pred)
{
if (//these three conditions are important for soundness
source.is_output_predicate(pred) ||
m_preds_with_facts.contains(pred) ||
m_preds_with_neg_occurrence.contains(pred) ||
//this condition is used for breaking of cycles among inlined rules
m_forbidden_preds.contains(pred)) {
return false;
}
//
// these conditions are optional, they avoid possible exponential increase
// in the size of the problem
//
return
//m_head_pred_non_empty_tails_ctr.get(pred)<=1
m_head_pred_ctr.get(pred) <= 1
|| (m_tail_pred_ctr.get(pred) <= 1 && m_head_pred_ctr.get(pred) <= 4)
;
}
/** Caller has to dealloc the returned object */
rule_set * mk_rule_inliner::create_allowed_rule_set(rule_set const & orig)
{
rule_set * res = alloc(rule_set, m_context);
unsigned rcnt = orig.get_num_rules();
for (unsigned i=0; i<rcnt; i++) {
rule * r = orig.get_rule(i);
if (inlining_allowed(orig, r->get_decl())) {
res->add_rule(r);
}
}
//the rule set should be stratified, since orig (which is its superset) is as well
VERIFY(res->close());
return res;
}
/**
Try to make the set of inlined predicates acyclic by forbidding inlining of one
predicate from each strongly connected component. Return true if we did forbide some
predicate, and false if the set of rules is already acyclic.
*/
bool mk_rule_inliner::forbid_preds_from_cycles(rule_set const & r)
{
SASSERT(r.is_closed());
bool something_forbidden = false;
const rule_stratifier::comp_vector& comps = r.get_stratifier().get_strats();
rule_stratifier::comp_vector::const_iterator cend = comps.end();
for (rule_stratifier::comp_vector::const_iterator it = comps.begin(); it!=cend; ++it) {
rule_stratifier::item_set * stratum = *it;
if (stratum->size()==1) {
continue;
}
SASSERT(stratum->size()>1);
func_decl * first_stratum_pred = *stratum->begin();
//we're trying to break cycles by removing one predicate from each of them
m_forbidden_preds.insert(first_stratum_pred);
something_forbidden = true;
}
return something_forbidden;
}
bool mk_rule_inliner::forbid_multiple_multipliers(const rule_set & orig,
rule_set const & proposed_inlined_rules) {
bool something_forbidden = false;
const rule_stratifier::comp_vector& comps =
proposed_inlined_rules.get_stratifier().get_strats();
rule_stratifier::comp_vector::const_iterator cend = comps.end();
for (rule_stratifier::comp_vector::const_iterator it = comps.begin(); it!=cend; ++it) {
rule_stratifier::item_set * stratum = *it;
SASSERT(stratum->size()==1);
func_decl * head_pred = *stratum->begin();
bool is_multi_head_pred = m_head_pred_ctr.get(head_pred)>1;
bool is_multi_occurrence_pred = m_tail_pred_ctr.get(head_pred)>1;
const rule_vector& pred_rules = proposed_inlined_rules.get_predicate_rules(head_pred);
rule_vector::const_iterator iend = pred_rules.end();
for (rule_vector::const_iterator iit = pred_rules.begin(); iit!=iend; ++iit) {
rule * r = *iit;
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti = 0; ti<pt_len; ++ti) {
func_decl * tail_pred = r->get_decl(ti);
if (!inlining_allowed(orig, tail_pred)) {
continue;
}
unsigned tail_pred_head_cnt = m_head_pred_ctr.get(tail_pred);
if (tail_pred_head_cnt<=1) {
continue;
}
if (is_multi_head_pred) {
m_forbidden_preds.insert(head_pred);
something_forbidden = true;
goto process_next_pred;
}
if (is_multi_occurrence_pred) {
m_forbidden_preds.insert(tail_pred);
something_forbidden = true;
}
else {
is_multi_head_pred = true;
m_head_pred_ctr.get(head_pred) =
m_head_pred_ctr.get(head_pred)*tail_pred_head_cnt;
}
}
}
process_next_pred:;
}
unsigned rule_cnt = orig.get_num_rules();
for (unsigned ri=0; ri<rule_cnt; ri++) {
rule * r = orig.get_rule(ri);
func_decl * head_pred = r->get_decl();
if (inlining_allowed(orig, head_pred)) {
//we have already processed inlined rules
continue;
}
bool has_multi_head_pred = false;
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti = 0; ti<pt_len; ++ti) {
func_decl * pred = r->get_decl(ti);
if (!inlining_allowed(orig, pred)) {
continue;
}
if (m_head_pred_ctr.get(pred)<=1) {
continue;
}
if (has_multi_head_pred) {
m_forbidden_preds.insert(pred);
something_forbidden = true;
}
else {
has_multi_head_pred = true;
}
}
}
return something_forbidden;
}
void mk_rule_inliner::plan_inlining(rule_set const & orig)
{
count_pred_occurrences(orig);
scoped_ptr<rule_set> candidate_inlined_set = create_allowed_rule_set(orig);
while (forbid_preds_from_cycles(*candidate_inlined_set)) {
candidate_inlined_set = create_allowed_rule_set(orig);
}
if (forbid_multiple_multipliers(orig, *candidate_inlined_set)) {
candidate_inlined_set = create_allowed_rule_set(orig);
}
TRACE("dl", tout<<"rules to be inlined:\n" << (*candidate_inlined_set); );
// now we start filling in the set of the inlined rules in a topological order,
// so that we inline rules into other rules
SASSERT(m_inlined_rules.get_num_rules()==0);
const rule_stratifier::comp_vector& comps = candidate_inlined_set->get_stratifier().get_strats();
rule_stratifier::comp_vector::const_iterator cend = comps.end();
for (rule_stratifier::comp_vector::const_iterator it = comps.begin(); it!=cend; ++it) {
rule_stratifier::item_set * stratum = *it;
SASSERT(stratum->size()==1);
func_decl * pred = *stratum->begin();
const rule_vector& pred_rules = candidate_inlined_set->get_predicate_rules(pred);
rule_vector::const_iterator iend = pred_rules.end();
for (rule_vector::const_iterator iit = pred_rules.begin(); iit!=iend; ++iit) {
transform_rule(orig, *iit, m_inlined_rules);
}
}
TRACE("dl", tout << "inlined rules after mutual inlining:\n" << m_inlined_rules; );
}
bool mk_rule_inliner::transform_rule(rule_set const& orig, rule * r0, rule_set& tgt) {
bool modified = false;
rule_ref_vector todo(m_rm);
todo.push_back(r0);
while (!todo.empty()) {
rule_ref r(todo.back(), m_rm);
todo.pop_back();
unsigned pt_len = r->get_positive_tail_size();
unsigned i = 0;
for (; i < pt_len && !inlining_allowed(orig, r->get_decl(i)); ++i) {};
SASSERT(!has_quantifier(*r.get()));
if (i == pt_len) {
//there's nothing we can inline in this rule
tgt.add_rule(r);
continue;
}
modified = true;
func_decl * pred = r->get_decl(i);
const rule_vector& pred_rules = m_inlined_rules.get_predicate_rules(pred);
rule_vector::const_iterator iend = pred_rules.end();
for (rule_vector::const_iterator iit = pred_rules.begin(); iit!=iend; ++iit) {
rule * inl_rule = *iit;
rule_ref inl_result(m_rm);
if (try_to_inline_rule(*r.get(), *inl_rule, i, inl_result)) {
todo.push_back(inl_result);
}
}
}
if (modified) {
datalog::del_rule(m_mc, *r0);
}
return modified;
}
bool mk_rule_inliner::transform_rules(const rule_set & orig, rule_set & tgt) {
bool something_done = false;
rule_set::iterator rend = orig.end();
for (rule_set::iterator rit = orig.begin(); rit!=rend; ++rit) {
rule_ref r(*rit, m_rm);
func_decl * pred = r->get_decl();
// if inlining is allowed, then we are eliminating
// this relation through inlining,
// so we don't add its rules to the result
something_done |= !inlining_allowed(orig, pred) && transform_rule(orig, r, tgt);
}
if (something_done && m_mc) {
for (rule_set::iterator rit = orig.begin(); rit!=rend; ++rit) {
if (inlining_allowed(orig, (*rit)->get_decl())) {
datalog::del_rule(m_mc, **rit);
}
}
}
return something_done;
}
/**
Check whether rule r is oriented in a particular ordering.
This is to avoid infinite cycle of inlining in the eager inliner.
Out ordering is lexicographic, comparing atoms first on stratum they are in,
then on arity and then on ast ID of their func_decl.
*/
bool mk_rule_inliner::is_oriented_rewriter(rule * r, rule_stratifier const& strat) {
func_decl * head_pred = r->get_decl();
unsigned head_strat = strat.get_predicate_strat(head_pred);
unsigned head_arity = head_pred->get_arity();
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti=0; ti<pt_len; ++ti) {
func_decl * pred = r->get_decl(ti);
unsigned pred_strat = strat.get_predicate_strat(pred);
SASSERT(pred_strat<=head_strat);
if (pred_strat==head_strat) {
if (pred->get_arity()>head_arity
|| (pred->get_arity()==head_arity && pred->get_id()>=head_pred->get_id()) ) {
return false;
}
}
}
return true;
}
bool mk_rule_inliner::do_eager_inlining(rule * r, rule_set const& rules, rule_ref& res) {
SASSERT(rules.is_closed());
const rule_stratifier& strat = rules.get_stratifier();
func_decl * head_pred = r->get_decl();
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti = 0; ti < pt_len; ++ti) {
func_decl * pred = r->get_decl(ti);
if (pred == head_pred || m_preds_with_facts.contains(pred)) { continue; }
const rule_vector& pred_rules = rules.get_predicate_rules(pred);
rule * inlining_candidate = 0;
unsigned rule_cnt = pred_rules.size();
if (rule_cnt == 0) {
inlining_candidate = 0;
}
else if (rule_cnt == 1) {
inlining_candidate = pred_rules[0];
}
else {
inlining_candidate = 0;
for (unsigned ri = 0; ri < rule_cnt; ++ri) {
rule * pred_rule = pred_rules[ri];
if (!m_unifier.unify_rules(*r, ti, *pred_rule)) {
//we skip rules which don't unify with the tail atom
continue;
}
if (inlining_candidate != 0) {
// We have two rules that can be inlined into the current
// tail predicate. In this situation we don't do inlinning
// on this tail atom, as we don't want the overall number
// of rules to increase.
goto process_next_tail;
}
inlining_candidate = pred_rule;
}
}
if (inlining_candidate == 0) {
// nothing unifies with the tail atom, therefore the rule is unsatisfiable
// (we can say this because relation pred doesn't have any ground facts either)
res = 0;
datalog::del_rule(m_mc, *r);
return true;
}
if (!is_oriented_rewriter(inlining_candidate, strat)) {
// The rule which should be used for inlining isn't oriented
// in a simplifying direction. Inlining with such rule might lead to
// infinite loops, so we don't do it.
goto process_next_tail;
}
if (!try_to_inline_rule(*r, *inlining_candidate, ti, res)) {
datalog::del_rule(m_mc, *r);
res = 0;
}
return true;
process_next_tail:;
}
return false;
}
bool mk_rule_inliner::do_eager_inlining(scoped_ptr<rule_set> & rules) {
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
bool done_something = false;
rule_set::iterator rend = rules->end();
for (rule_set::iterator rit = rules->begin(); rit!=rend; ++rit) {
rule_ref r(*rit, m_rm);
rule_ref replacement(m_rm);
while (r && do_eager_inlining(r, *rules, replacement)) {
r = replacement;
done_something = true;
}
if (!r) {
continue;
}
res->add_rule(r);
}
if (done_something) {
rules = res.detach();
}
return done_something;
}
/**
Inline predicates that are known to not be join-points.
P(1,x) :- P(0,y), phi(x,y)
P(0,x) :- P(1,z), psi(x,z)
->
P(1,x) :- P(1,z), phi(x,y), psi(y,z)
whenever P(0,x) is not unifiable with the
body of the rule where it appears (P(1,z))
and P(0,x) is unifiable with at most one (?)
other rule (and it does not occur negatively).
*/
bool mk_rule_inliner::visitor::operator()(expr* e) {
m_unifiers.append(m_positions.find(e));
TRACE("dl",
tout << "unifier: " << (m_unifiers.empty()?0:m_unifiers.back());
tout << " num unifiers: " << m_unifiers.size();
tout << " num positions: " << m_positions.find(e).size() << "\n";
output_predicate(m_context, to_app(e), tout); tout << "\n";);
return true;
}
void mk_rule_inliner::visitor::reset(unsigned sz) {
m_unifiers.reset();
m_can_remove.reset();
m_can_remove.resize(sz, true);
m_can_expand.reset();
m_can_expand.resize(sz, true);
m_positions.reset();
}
unsigned_vector const& mk_rule_inliner::visitor::add_position(expr* e, unsigned j) {
obj_map<expr, unsigned_vector>::obj_map_entry * et = m_positions.insert_if_not_there2(e, unsigned_vector());
et->get_data().m_value.push_back(j);
return et->get_data().m_value;
}
unsigned_vector const& mk_rule_inliner::visitor::del_position(expr* e, unsigned j) {
obj_map<expr, unsigned_vector>::obj_map_entry * et = m_positions.find_core(e);
SASSERT(et && et->get_data().m_value.contains(j));
et->get_data().m_value.erase(j);
return et->get_data().m_value;
}
void mk_rule_inliner::add_rule(rule_set const& source, rule* r, unsigned i) {
svector<bool>& can_remove = m_head_visitor.can_remove();
svector<bool>& can_expand = m_head_visitor.can_expand();
app* head = r->get_head();
func_decl* headd = head->get_decl();
m_head_visitor.add_position(head, i);
m_head_index.insert(head);
m_pinned.push_back(r);
if (source.is_output_predicate(headd) ||
m_preds_with_facts.contains(headd)) {
can_remove.set(i, false);
TRACE("dl", output_predicate(m_context, head, tout << "cannot remove: " << i << " "); tout << "\n";);
}
unsigned tl_sz = r->get_uninterpreted_tail_size();
for (unsigned j = 0; j < tl_sz; ++j) {
app* tail = r->get_tail(j);
m_tail_visitor.add_position(tail, i);
m_tail_index.insert(tail);
}
bool can_exp =
tl_sz == 1
&& r->get_positive_tail_size() == 1
&& !m_preds_with_facts.contains(r->get_decl(0))
&& !source.is_output_predicate(r->get_decl(0));
can_expand.set(i, can_exp);
}
void mk_rule_inliner::del_rule(rule* r, unsigned i) {
app* head = r->get_head();
m_head_visitor.del_position(head, i);
unsigned tl_sz = r->get_uninterpreted_tail_size();
for (unsigned j = 0; j < tl_sz; ++j) {
app* tail = r->get_tail(j);
m_tail_visitor.del_position(tail, i);
}
}
#define PRT(_x_) ((_x_)?"T":"F")
bool mk_rule_inliner::inline_linear(scoped_ptr<rule_set>& rules) {
bool done_something = false;
unsigned sz = rules->get_num_rules();
m_head_visitor.reset(sz);
m_tail_visitor.reset(sz);
m_head_index.reset();
m_tail_index.reset();
TRACE("dl", rules->display(tout););
rule_ref_vector acc(m_rm);
for (unsigned i = 0; i < sz; ++i) {
acc.push_back(rules->get_rule(i));
}
// set up unification index.
svector<bool>& can_remove = m_head_visitor.can_remove();
svector<bool>& can_expand = m_head_visitor.can_expand();
for (unsigned i = 0; i < sz; ++i) {
add_rule(*rules, acc[i].get(), i);
}
// initialize substitution.
rule_counter& vc = m_rm.get_counter();
unsigned max_var = 0;
for (unsigned i = 0; i < sz; ++i) {
rule* r = acc[i].get();
max_var = std::max(max_var, vc.get_max_var(r->get_head()));
unsigned tl_sz = r->get_uninterpreted_tail_size();
for (unsigned j = 0; j < tl_sz; ++j) {
max_var = std::max(max_var, vc.get_max_var(r->get_tail(j)));
}
}
m_subst.reset();
m_subst.reserve_vars(max_var+1);
m_subst.reserve_offsets(std::max(m_tail_index.get_approx_num_regs(), 2+m_head_index.get_approx_num_regs()));
svector<bool> valid;
valid.reset();
valid.resize(sz, true);
bool allow_branching = m_context.get_params().inline_linear_branch();
for (unsigned i = 0; i < sz; ++i) {
while (true) {
rule_ref r(acc[i].get(), m_rm);
TRACE("dl", r->display(m_context, tout << "processing: " << i << "\n"););
if (!valid.get(i)) {
TRACE("dl", tout << "invalid: " << i << "\n";);
break;
}
if (!can_expand.get(i)) {
TRACE("dl", tout << "cannot expand: " << i << "\n";);
break;
}
m_head_visitor.reset();
m_head_index.unify(r->get_tail(0), m_head_visitor);
unsigned num_head_unifiers = m_head_visitor.get_unifiers().size();
if (num_head_unifiers != 1) {
TRACE("dl", tout << "no unique unifier " << num_head_unifiers << "\n";);
break;
}
unsigned j = m_head_visitor.get_unifiers()[0];
if (!can_remove.get(j) || !valid.get(j) || i == j) {
TRACE("dl", tout << PRT(can_remove.get(j)) << " " << PRT(valid.get(j)) << " " << PRT(i != j) << "\n";);
break;
}
rule* r2 = acc[j].get();
// check that the head of r2 only unifies with this single body position.
TRACE("dl", output_predicate(m_context, r2->get_head(), tout << "unify head: "); tout << "\n";);
m_tail_visitor.reset();
m_tail_index.unify(r2->get_head(), m_tail_visitor);
unsigned_vector const& tail_unifiers = m_tail_visitor.get_unifiers();
unsigned num_tail_unifiers = tail_unifiers.size();
SASSERT(!tail_unifiers.empty());
if (!allow_branching && num_tail_unifiers != 1) {
TRACE("dl", tout << "too many tails " << num_tail_unifiers << "\n";);
break;
}
rule_ref rl_res(m_rm);
if (!try_to_inline_rule(*r.get(), *r2, 0, rl_res)) {
TRACE("dl", r->display(m_context, tout << "inlining failed\n"); r2->display(m_context, tout); );
break;
}
done_something = true;
TRACE("dl", r->display(m_context, tout); r2->display(m_context, tout); rl_res->display(m_context, tout); );
del_rule(r, i);
add_rule(*rules, rl_res.get(), i);
r = rl_res;
acc[i] = r.get();
can_expand.set(i, can_expand.get(j));
if (num_tail_unifiers == 1) {
TRACE("dl", tout << "setting invalid: " << j << "\n";);
valid.set(j, false);
datalog::del_rule(m_mc, *r2);
del_rule(r2, j);
}
max_var = std::max(max_var, vc.get_max_rule_var(*r.get()));
m_subst.reserve_vars(max_var+1);
}
}
if (done_something) {
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
for (unsigned i = 0; i < sz; ++i) {
if (valid.get(i)) {
res->add_rule(acc[i].get());
}
}
res->inherit_predicates(*rules);
TRACE("dl", res->display(tout););
rules = res.detach();
}
return done_something;
}
rule_set * mk_rule_inliner::operator()(rule_set const & source) {
bool something_done = false;
ref<horn_subsume_model_converter> hsmc;
if (source.get_num_rules() == 0) {
return 0;
}
rule_set::iterator end = source.end();
for (rule_set::iterator it = source.begin(); it != end; ++ it) {
if (has_quantifier(**it)) {
return 0;
}
}
if (m_context.get_model_converter()) {
hsmc = alloc(horn_subsume_model_converter, m);
}
m_mc = hsmc.get();
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
if (m_context.get_params().inline_eager()) {
TRACE("dl", source.display(tout << "before eager inlining\n"););
plan_inlining(source);
something_done = transform_rules(source, *res);
VERIFY(res->close()); //this transformation doesn't break the negation stratification
// try eager inlining
if (do_eager_inlining(res)) {
something_done = true;
}
TRACE("dl", res->display(tout << "after eager inlining\n"););
}
if (something_done) {
res->inherit_predicates(source);
}
else {
res = alloc(rule_set, source);
}
if (m_context.get_params().inline_linear() && inline_linear(res)) {
something_done = true;
}
if (!something_done) {
res = 0;
}
else {
m_context.add_model_converter(hsmc.get());
}
return res.detach();
}
};

View file

@ -0,0 +1,205 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_interp_tail_simplifier.h
Abstract:
Rule transformer which inlines some of the rules
Author:
Krystof Hoder (t-khoder) 2011-10-02.
Revision History:
--*/
#ifndef _DL_MK_RULE_INLINER_H_
#define _DL_MK_RULE_INLINER_H_
#include "dl_context.h"
#include "dl_rule_transformer.h"
#include "dl_mk_interp_tail_simplifier.h"
#include "unifier.h"
#include "substitution.h"
#include "substitution_tree.h"
namespace datalog {
class rule_unifier {
ast_manager& m;
rule_manager& m_rm;
context& m_context;
/** We use this simplifier after inlining to get nicer intermediate rules */
mk_interp_tail_simplifier m_interp_simplifier;
substitution m_subst;
unifier m_unif;
bool m_ready;
bool m_normalize;
unsigned m_deltas[2];
public:
rule_unifier(context& ctx)
: m(ctx.get_manager()), m_rm(ctx.get_rule_manager()), m_context(ctx),
m_interp_simplifier(ctx), m_subst(m), m_unif(m), m_ready(false), m_normalize(true) {}
/** Reset subtitution and unify tail tgt_idx of the target rule and the head of the src rule */
bool unify_rules(rule const& tgt, unsigned tgt_idx, rule const& src);
/**
\brief Apply unifier to rules.
Return false if the resulting rule is a tautology (the interpreted tail is unsat).
*/
bool apply(rule const& tgt, unsigned tgt_idx, rule const& src, rule_ref& result);
void display(std::ostream& stm) { m_subst.display(stm, 2, m_deltas); }
/**
Retrieve substitutions for src/tgt. (second argument of unify_rules).
*/
expr_ref_vector get_rule_subst(rule const& r, bool is_tgt);
/**
Control if bound variables are normalized after unification.
The default is 'true': bound variables are re-mapped to an
initial segment of de-Bruijn indices.
*/
void set_normalize(bool n) { m_normalize = n; }
private:
void apply(app * a, bool is_tgt, app_ref& res);
/**
Apply substitution to a rule tail. Tail with skipped_index is skipped,
unless skipped_index is equal to UINT_MAX
*/
void apply(rule const& r, bool is_tgt, unsigned skipped_index, app_ref_vector& res,
svector<bool>& res_neg);
};
class mk_rule_inliner : public rule_transformer::plugin {
class visitor : public st_visitor {
context& m_context;
unsigned_vector m_unifiers;
svector<bool> m_can_remove, m_can_expand;
obj_map<expr, unsigned_vector> m_positions;
public:
visitor(context& c, substitution & s): st_visitor(s), m_context(c) {}
virtual bool operator()(expr* e);
void reset() { m_unifiers.reset(); }
void reset(unsigned sz);
svector<bool>& can_remove() { return m_can_remove; }
svector<bool>& can_expand() { return m_can_expand; }
unsigned_vector const& add_position(expr* e, unsigned j);
unsigned_vector const& del_position(expr* e, unsigned j);
unsigned_vector const& get_unifiers() { return m_unifiers; }
};
typedef obj_map<func_decl, func_decl *> decl_map;
ast_manager & m;
rule_manager & m_rm;
context & m_context;
th_rewriter& m_simp;
rule_ref_vector m_pinned;
func_decl_set m_forbidden_preds;
func_decl_set m_preds_with_facts;
func_decl_set m_preds_with_neg_occurrence;
ast_counter m_head_pred_ctr;
ast_counter m_head_pred_non_empty_tails_ctr;
ast_counter m_tail_pred_ctr;
rule_set m_inlined_rules;
horn_subsume_model_converter* m_mc;
//used in try_to_inline_rule and do_eager_inlining
rule_unifier m_unifier;
substitution_tree m_head_index; // for straight-line relation inlining.
substitution_tree m_tail_index;
substitution m_subst;
visitor m_head_visitor;
visitor m_tail_visitor;
bool tail_matches_head(app * tail, app* head);
bool try_to_inline_rule(rule& tgt, rule& src, unsigned tail_index, rule_ref& res);
bool inlining_allowed(rule_set const& orig, func_decl * pred);
void count_pred_occurrences(rule_set const & orig);
void plan_inlining(rule_set const & orig);
rule_set * create_allowed_rule_set(rule_set const & orig);
bool forbid_preds_from_cycles(rule_set const & r);
/** Ensure we don't inline two multi-head rules that would appear together in some tail */
bool forbid_multiple_multipliers(const rule_set & orig, rule_set const & proposed_inlined_rules);
/** Return true if the rule was modified */
bool transform_rule(rule_set const& orig, rule * r, rule_set& tgt);
/** Return true if some transformation was performed */
bool transform_rules(const rule_set & orig, rule_set & tgt);
bool is_oriented_rewriter(rule * r, rule_stratifier const& strat);
/**
Return false if nothing was done with the rule.
res may be set to zero if we managed to prove the rule unsatisfiable.
*/
bool do_eager_inlining(rule * r, rule_set const& rules, rule_ref& res);
/**
Inline rules even if it doesn't lead to elimination of the whole predicate.
The inlining is done as long as it doesn't increase the number of rules
(i.e. when only one rule defining a predicate can replace tail atom).
The original rule-set must be closed before passing t this function
*/
bool do_eager_inlining(scoped_ptr<rule_set> & rules);
bool has_quantifier(rule const& r) const;
/**
Inline predicates that are known to not be join-points.
*/
bool inline_linear(scoped_ptr<rule_set>& rules);
void add_rule(rule_set const& rule_set, rule* r, unsigned i);
void del_rule(rule* r, unsigned i);
public:
mk_rule_inliner(context & ctx, unsigned priority=35000)
: plugin(priority),
m(ctx.get_manager()),
m_rm(ctx.get_rule_manager()),
m_context(ctx),
m_simp(m_context.get_rewriter()),
m_pinned(m_rm),
m_inlined_rules(m_context),
m_mc(0),
m_unifier(ctx),
m_head_index(m),
m_tail_index(m),
m_subst(m),
m_head_visitor(ctx, m_subst),
m_tail_visitor(ctx, m_subst)
{}
virtual ~mk_rule_inliner() { }
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_INTERP_TAIL_SIMPLIFIER_H_ */

View file

@ -0,0 +1,241 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_scale.cpp
Abstract:
Author:
Nikolaj Bjorner (nbjorner) 2013-08-19
Revision History:
--*/
#include"dl_mk_scale.h"
#include"dl_context.h"
namespace datalog {
class mk_scale::scale_model_converter : public model_converter {
ast_manager& m;
func_decl_ref_vector m_trail;
arith_util a;
obj_map<func_decl, func_decl*> m_new2old;
public:
scale_model_converter(ast_manager& m): m(m), m_trail(m), a(m) {}
virtual ~scale_model_converter() {}
void add_new2old(func_decl* new_f, func_decl* old_f) {
m_trail.push_back(old_f);
m_trail.push_back(new_f);
m_new2old.insert(new_f, old_f);
}
virtual void operator()(model_ref& md) {
model_ref old_model = alloc(model, m);
obj_map<func_decl, func_decl*>::iterator it = m_new2old.begin();
obj_map<func_decl, func_decl*>::iterator end = m_new2old.end();
for (; it != end; ++it) {
func_decl* old_p = it->m_value;
func_decl* new_p = it->m_key;
func_interp* old_fi = alloc(func_interp, m, old_p->get_arity());
if (new_p->get_arity() == 0) {
old_fi->set_else(md->get_const_interp(new_p));
}
else {
func_interp* new_fi = md->get_func_interp(new_p);
expr_ref_vector subst(m);
var_subst vs(m, false);
expr_ref tmp(m);
if (!new_fi) {
TRACE("dl", tout << new_p->get_name() << " has no value in the current model\n";);
dealloc(old_fi);
continue;
}
for (unsigned i = 0; i < old_p->get_arity(); ++i) {
subst.push_back(m.mk_var(i, old_p->get_domain(i)));
}
subst.push_back(a.mk_numeral(rational(1), a.mk_real()));
// Hedge that we don't have to handle the general case for models produced
// by Horn clause solvers.
SASSERT(!new_fi->is_partial() && new_fi->num_entries() == 0);
vs(new_fi->get_else(), subst.size(), subst.c_ptr(), tmp);
old_fi->set_else(tmp);
old_model->register_decl(old_p, old_fi);
}
}
// register values that have not been scaled.
unsigned sz = md->get_num_constants();
for (unsigned i = 0; i < sz; ++i) {
func_decl* c = md->get_constant(i);
if (!m_new2old.contains(c)) {
old_model->register_decl(c, md->get_const_interp(c));
}
}
sz = md->get_num_functions();
for (unsigned i = 0; i < sz; ++i) {
func_decl* f = md->get_function(i);
if (!m_new2old.contains(f)) {
func_interp* fi = md->get_func_interp(f);
old_model->register_decl(f, fi->copy());
}
}
md = old_model;
//TRACE("dl", model_smt2_pp(tout, m, *md, 0); );
}
virtual model_converter * translate(ast_translation & translator) {
UNREACHABLE();
return 0;
}
};
mk_scale::mk_scale(context & ctx, unsigned priority):
plugin(priority),
m(ctx.get_manager()),
m_ctx(ctx),
a(m),
m_trail(m),
m_eqs(m) {
}
mk_scale::~mk_scale() {
}
rule_set * mk_scale::operator()(rule_set const & source) {
if (!m_ctx.get_params().scale()) {
return 0;
}
rule_manager& rm = source.get_rule_manager();
rule_set * result = alloc(rule_set, m_ctx);
unsigned sz = source.get_num_rules();
rule_ref new_rule(rm);
app_ref_vector tail(m);
app_ref head(m);
svector<bool> neg;
ptr_vector<sort> vars;
ref<scale_model_converter> smc;
if (m_ctx.get_model_converter()) {
smc = alloc(scale_model_converter, m);
}
m_mc = smc.get();
for (unsigned i = 0; i < sz; ++i) {
rule & r = *source.get_rule(i);
unsigned utsz = r.get_uninterpreted_tail_size();
unsigned tsz = r.get_tail_size();
tail.reset();
vars.reset();
m_cache.reset();
m_trail.reset();
m_eqs.reset();
r.get_vars(vars);
unsigned num_vars = vars.size();
for (unsigned j = 0; j < utsz; ++j) {
tail.push_back(mk_pred(num_vars, r.get_tail(j)));
}
for (unsigned j = utsz; j < tsz; ++j) {
tail.push_back(mk_constraint(num_vars, r.get_tail(j)));
}
app_ref new_pred = mk_pred(num_vars, r.get_head());
tail.append(m_eqs);
tail.push_back(a.mk_gt(m.mk_var(num_vars, a.mk_real()), a.mk_numeral(rational(0), false)));
neg.resize(tail.size(), false);
new_rule = rm.mk(new_pred, tail.size(), tail.c_ptr(), neg.c_ptr(), r.name(), true);
result->add_rule(new_rule);
if (source.is_output_predicate(r.get_decl())) {
result->set_output_predicate(new_rule->get_decl());
}
}
TRACE("dl", result->display(tout););
if (m_mc) {
m_ctx.add_model_converter(m_mc);
}
m_trail.reset();
m_cache.reset();
return result;
}
app_ref mk_scale::mk_pred(unsigned sigma_idx, app* q) {
func_decl* f = q->get_decl();
ptr_vector<sort> domain(f->get_arity(), f->get_domain());
domain.push_back(a.mk_real());
func_decl_ref g(m);
g = m.mk_func_decl(f->get_name(), f->get_arity() + 1, domain.c_ptr(), f->get_range());
expr_ref_vector args(m);
for (unsigned i = 0; i < q->get_num_args(); ++i) {
expr* arg = q->get_arg(i);
rational val;
if (a.is_numeral(arg, val)) {
if (val.is_zero()) {
// arg is unchanged.
}
else if (val.is_one()) {
arg = m.mk_var(sigma_idx, a.mk_real());
}
else {
// create a fresh variable 'v', add 'v == sigma*arg'
expr* v = m.mk_var(sigma_idx + 1 + m_eqs.size(), a.mk_real());
m_eqs.push_back(m.mk_eq(v, a.mk_mul(arg, m.mk_var(sigma_idx, a.mk_real()))));
arg = v;
}
}
args.push_back(arg);
}
args.push_back(m.mk_var(sigma_idx, a.mk_real()));
m_ctx.register_predicate(g, false);
if (m_mc) {
m_mc->add_new2old(g, f);
}
return app_ref(m.mk_app(g, q->get_num_args() + 1, args.c_ptr()), m);
}
app_ref mk_scale::mk_constraint(unsigned sigma_idx, app* q) {
expr* r = linearize(sigma_idx, q);
SASSERT(is_app(r));
return app_ref(to_app(r), m);
}
expr* mk_scale::linearize(unsigned sigma_idx, expr* e) {
expr* r;
if (m_cache.find(e, r)) {
return r;
}
if (!is_app(e)) {
return e;
}
expr_ref result(m);
app* ap = to_app(e);
if (ap->get_family_id() == m.get_basic_family_id() ||
a.is_add(e) || a.is_sub(e) ||
a.is_le(e) || a.is_ge(e) ||
a.is_lt(e) || a.is_gt(e)) {
expr_ref_vector args(m);
for (unsigned i = 0; i < ap->get_num_args(); ++i) {
args.push_back(linearize(sigma_idx, ap->get_arg(i)));
}
result = m.mk_app(ap->get_decl(), args.size(), args.c_ptr());
}
else if (a.is_numeral(e)) {
result = a.mk_mul(m.mk_var(sigma_idx, a.mk_real()), e);
}
else {
result = e;
}
m_trail.push_back(result);
m_cache.insert(e, result);
return result;
}
};

View file

@ -0,0 +1,53 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_mk_scale.h
Abstract:
Add scale factor to linear (Real) arithemetic Horn clauses.
The transformation replaces occurrences of isolated constants by
a scale multiplied to each constant.
Author:
Nikolaj Bjorner (nbjorner) 2013-08-19
Revision History:
--*/
#ifndef _DL_MK_SCALE_H_
#define _DL_MK_SCALE_H_
#include"dl_rule_transformer.h"
#include"arith_decl_plugin.h"
namespace datalog {
class mk_scale : public rule_transformer::plugin {
class scale_model_converter;
ast_manager& m;
context& m_ctx;
arith_util a;
expr_ref_vector m_trail;
app_ref_vector m_eqs;
obj_map<expr, expr*> m_cache;
scale_model_converter* m_mc;
expr* linearize(unsigned num_vars, expr* e);
app_ref mk_pred(unsigned num_vars, app* q);
app_ref mk_constraint(unsigned num_vars, app* q);
public:
mk_scale(context & ctx, unsigned priority = 33039);
virtual ~mk_scale();
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_SCALE_H_ */

View file

@ -0,0 +1,859 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_slice.cpp
Abstract:
<abstract>
Author:
Nikolaj Bjorner (nbjorner) 2012-9-12.
Revision History:
Consider a rule:
P(x,y) :- R(x,z), phi(x,y,z)
input: x, z
output: x, y
Let x_i, y_i, z_i be incides into the vectors x, y, z.
Suppose that positions in P and R are annotated with what is
slicable.
Sufficient conditions for sliceability:
x_i is sliceable if x_i does not appear in phi(x,y,z)
and the positions where x_i is used in P and R are sliceable
y_i is sliceable if y_i does not occur in phi(x,y,z), or
if it occurs in phi(x,y,z) it is only in one conjunct of the form
y_i = t[x_j,y_j,z_j]
and the positions where y_i is used in P and R are sliceable
z_i is sliceable if z_i does not occur in phi(x,y,z), or
if it occurs in phi(x,y,z) it is only in one conjunct of the form
y_i = t[x_j,y_j,z_i] where y_i is sliceable
and the positions where z_i is used in P and R are sliceable
A more refined approach may be using Gaussean elimination based
on x,z and eliminating variables from x,y (expressing them in terms
of a disjoint subeset of x,z).
--*/
#include "dl_mk_slice.h"
#include "ast_pp.h"
#include "expr_functors.h"
#include "dl_mk_rule_inliner.h"
#include "model_smt2_pp.h"
namespace datalog {
/**
Convert from sliced proofs to original proofs.
Given sliced rule
fml0: forall x y z u. p(x,y) & z = f(x,y) & phi(x,u) => p(u, z)
into
fml1: forall a b . q(a) & phi(a,b) => q(b)
It induces mappings:
theta: a |-> x, b |-> u
vars: x y z u.
predicates: -q(a) |-> p(x,y)
+q(b) |-> z = f(x,y) => p(u,z)
fml1 |-> fml0
The mapping theta is an injective function from variable indices
to variable indices. We can apply it as a substitution on expressions,
but we can also apply it as a transformation on substitutions. We
write theta[subst] when applying theta on substitution 'subst' such
that if [x |-> t] is in subst, then [theta(x) |-> theta(t)] is in
the result.
Given hyper-resolvent: fml1 subst1 fml2 subst2 |- fml3
where fml1 |-> fml1' with theta1
fml2 |-> fml2' with theta2
Perform the following steps:
1. [Convert fml1' fml2' to datalog rules because we have resolution routines]
2. Create subst1' := theta1[subst1]
subst2' := theta2[subst2]
3. Set fml1'' := subst1'(fml1')
fml2'' := subst2'(fml2')
4. Resolve fml1'' and fml2''
extract subst1'', subst2'' from resolvents.
extract goal fml3'
5. Create subst1''' := subst1'' o subst1'
subst2''' := subst2'' o subst2'
6. Return fml1'' subst1''' fml2'' subst2''' |- fml3'
7. Attach to fml3' the transformation ...?
*/
class mk_slice::slice_proof_converter : public proof_converter {
context& m_ctx;
ast_manager& m;
rule_manager& rm;
rule_ref_vector m_pinned_rules;
expr_ref_vector m_pinned_exprs;
obj_map<rule, rule*> m_rule2slice; // rule to sliced rule
obj_map<rule, unsigned_vector> m_renaming; // rule to renaming
obj_map<expr, rule*> m_sliceform2rule; // sliced formula to rule.
ptr_vector<proof> m_todo;
obj_map<proof, proof*> m_new_proof;
rule_unifier m_unifier;
slice_proof_converter(slice_proof_converter const& other);
void init_form2rule() {
if (!m_sliceform2rule.empty()) {
return;
}
obj_map<rule, rule*>::iterator it = m_rule2slice.begin();
obj_map<rule, rule*>::iterator end = m_rule2slice.end();
expr_ref fml(m);
for (; it != end; ++it) {
it->m_value->to_formula(fml);
m_pinned_exprs.push_back(fml);
TRACE("dl",
tout << "orig: " << mk_pp(fml, m) << "\n";
it->m_value->display(m_ctx, tout << "new:\n"););
m_sliceform2rule.insert(fml, it->m_key);
}
}
void translate_proof(proof_ref& pr) {
m_todo.reset();
m_new_proof.reset();
m_todo.push_back(pr);
while (!m_todo.empty()) {
proof* p = m_todo.back();
if (m_new_proof.contains(p)) {
m_todo.pop_back();
}
else if (translate_asserted(p)) {
// done
}
else if (translate_hyper_res(p)) {
// done
}
else {
m_new_proof.insert(p, p);
m_todo.pop_back();
TRACE("dl", tout << "unhandled proof term\n" << mk_pp(p, m) << "\n";);
}
}
pr = m_new_proof.find(pr);
}
bool translate_asserted(proof* p) {
expr* fact = 0;
rule* r = 0;
if (!m.is_asserted(p, fact)) {
return false;
}
if (!m_sliceform2rule.find(fact, r)) {
TRACE("dl", tout << "does not have fact\n" << mk_pp(fact, m) << "\n";);
return false;
}
proof_ref new_p(m);
new_p = r->get_proof();
m_pinned_exprs.push_back(new_p);
m_todo.pop_back();
m_new_proof.insert(p, new_p);
return true;
}
bool translate_hyper_res(proof* p) {
dl_decl_util util(m);
svector<std::pair<unsigned, unsigned> > positions;
expr_ref concl(m), slice_concl(m);
proof_ref_vector premises0(m);
vector<expr_ref_vector> substs, substs0;
if (!m.is_hyper_resolve(p, premises0, slice_concl, positions, substs0)) {
return false;
}
unsigned num_args = p->get_num_args();
SASSERT(num_args >= 2);
bool all_found = true;
for (unsigned i = 0; i < num_args-1; ++i) {
proof* arg = to_app(p->get_arg(i));
SASSERT(m.is_proof(arg));
if (!m_new_proof.contains(arg)) {
m_todo.push_back(arg);
all_found = false;
}
}
if (!all_found) {
return true;
}
ptr_vector<proof> premises;
proof* p0 = to_app(p->get_arg(0));
proof* p0_new = m_new_proof.find(p0);
expr* fact0 = m.get_fact(p0);
TRACE("dl", tout << "fact0: " << mk_pp(fact0, m) << "\n";);
rule* orig0;
if (!m_sliceform2rule.find(fact0, orig0)) {
return false;
}
premises.push_back(p0_new);
rule_ref r1(rm), r2(rm), r3(rm);
r1 = orig0;
substs.push_back(expr_ref_vector(m));
for (unsigned i = 1; i < num_args-1; ++i) {
proof* p1 = to_app(p->get_arg(i));
proof* p1_new = m_new_proof.find(p1);
expr* fact1 = m.get_fact(p1);
TRACE("dl", tout << "fact1: " << mk_pp(fact1, m) << "\n";);
rule* orig1 = 0;
if (!m_sliceform2rule.find(fact1, orig1)) {
return false;
}
premises.push_back(p1_new);
// TODO: work with substitutions.
r2 = orig1;
unsigned idx = 0; // brittle. TBD get index from positions.
VERIFY(m_unifier.unify_rules(*r1, idx, *r2));
m_unifier.apply(*r1.get(), idx, *r2.get(), r3);
expr_ref_vector const sub1 = m_unifier.get_rule_subst(*r1.get(), true);
for (unsigned j = 0; j < substs.size(); ++j) {
apply_subst(substs[j], sub1);
// size of substitutions may have grown...substs[j].resize(num_args[j]);
}
substs.push_back(m_unifier.get_rule_subst(*r2.get(), false));
TRACE("dl",
r1->display(m_ctx, tout << "rule1:");
r2->display(m_ctx, tout << "rule2:");
r3->display(m_ctx, tout << "res:"););
r1 = r3;
}
r1->to_formula(concl);
proof* new_p = m.mk_hyper_resolve(premises.size(), premises.c_ptr(), concl, positions, substs);
m_pinned_exprs.push_back(new_p);
m_pinned_rules.push_back(r1.get());
TRACE("dl",
tout << "orig: " << mk_pp(slice_concl, m) << "\n";
r1->display(m_ctx, tout << "new:"););
m_sliceform2rule.insert(slice_concl, r1.get());
m_rule2slice.insert(r1.get(), 0);
m_renaming.insert(r1.get(), unsigned_vector());
m_new_proof.insert(p, new_p);
m_todo.pop_back();
TRACE("dl", tout << "translated:\n" << mk_pp(p, m) << "\nto\n" << mk_pp(new_p, m) << "\n";);
return true;
}
public:
slice_proof_converter(context& ctx):
m_ctx(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_pinned_rules(rm),
m_pinned_exprs(m),
m_unifier(ctx) {}
void insert(rule* orig_rule, rule* slice_rule, unsigned sz, unsigned const* renaming) {
m_rule2slice.insert(orig_rule, slice_rule);
m_pinned_rules.push_back(orig_rule);
m_pinned_rules.push_back(slice_rule);
m_renaming.insert(orig_rule, unsigned_vector(sz, renaming));
}
virtual void operator()(ast_manager& m, unsigned num_source, proof * const * source, proof_ref & result) {
SASSERT(num_source == 1);
result = source[0];
init_form2rule();
translate_proof(result);
}
virtual proof_converter * translate(ast_translation & translator) {
UNREACHABLE();
// this would require implementing translation for the dl_context.
return 0;
}
};
class mk_slice::slice_model_converter : public model_converter {
ast_manager& m;
obj_map<func_decl, func_decl*> m_slice2old;
obj_map<func_decl, bit_vector> m_sliceable;
ast_ref_vector m_pinned;
public:
slice_model_converter(mk_slice& parent, ast_manager& m): m(m), m_pinned(m) {}
void add_predicate(func_decl* old_f, func_decl* slice_f) {
m_pinned.push_back(old_f);
m_pinned.push_back(slice_f);
m_slice2old.insert(slice_f, old_f);
}
void add_sliceable(func_decl* f, bit_vector const& bv) {
m_pinned.push_back(f);
m_sliceable.insert(f, bv);
}
virtual void operator()(model_ref & md) {
if (m_slice2old.empty()) {
return;
}
TRACE("dl", model_smt2_pp(tout, m, *md, 0); );
model_ref old_model = alloc(model, m);
obj_map<func_decl, func_decl*>::iterator it = m_slice2old.begin();
obj_map<func_decl, func_decl*>::iterator end = m_slice2old.end();
for (; it != end; ++it) {
func_decl* old_p = it->m_value;
func_decl* new_p = it->m_key;
bit_vector const& is_sliced = m_sliceable.find(old_p);
SASSERT(is_sliced.size() == old_p->get_arity());
SASSERT(is_sliced.size() > new_p->get_arity());
func_interp* old_fi = alloc(func_interp, m, is_sliced.size());
TRACE("dl", tout << mk_pp(old_p, m) << " " << mk_pp(new_p, m) << "\n";
for (unsigned j = 0; j < is_sliced.size(); ++j) {
tout << (is_sliced.get(j)?"1":"0");
}
tout << "\n";);
if (new_p->get_arity() == 0) {
old_fi->set_else(md->get_const_interp(new_p));
}
else {
expr_ref_vector subst(m);
expr_ref tmp(m);
var_subst vs(m, false);
for (unsigned i = 0; i < is_sliced.size(); ++i) {
if (!is_sliced.get(i)) {
subst.push_back(m.mk_var(i, old_p->get_domain(i)));
}
}
func_interp* new_fi = md->get_func_interp(new_p);
if (!new_fi) {
TRACE("dl", tout << new_p->get_name() << " has no value in the current model\n";);
dealloc(old_fi);
continue;
}
if (!new_fi->is_partial()) {
TRACE("dl", tout << mk_pp(new_fi->get_else(), m) << "\n";);
vs(new_fi->get_else(), subst.size(), subst.c_ptr(), tmp);
old_fi->set_else(tmp);
}
unsigned num_entries = new_fi->num_entries();
for (unsigned j = 0; j < num_entries; ++j) {
expr_ref res(m);
expr_ref_vector args(m);
func_entry const* e = new_fi->get_entry(j);
for (unsigned k = 0, l = 0; k < old_p->get_arity(); ++k) {
if (!is_sliced.get(k)) {
vs(e->get_arg(l++), subst.size(), subst.c_ptr(), tmp);
args.push_back(tmp);
}
else {
args.push_back(m.mk_var(k, old_p->get_domain(k)));
}
SASSERT(l <= new_p->get_arity());
}
vs(e->get_result(), subst.size(), subst.c_ptr(), res);
old_fi->insert_entry(args.c_ptr(), res.get());
}
old_model->register_decl(old_p, old_fi);
}
}
// register values that have not been sliced.
unsigned sz = md->get_num_constants();
for (unsigned i = 0; i < sz; ++i) {
func_decl* c = md->get_constant(i);
if (!m_slice2old.contains(c)) {
old_model->register_decl(c, md->get_const_interp(c));
}
}
sz = md->get_num_functions();
for (unsigned i = 0; i < sz; ++i) {
func_decl* f = md->get_function(i);
if (!m_slice2old.contains(f)) {
func_interp* fi = md->get_func_interp(f);
old_model->register_decl(f, fi->copy());
}
}
md = old_model;
TRACE("dl", model_smt2_pp(tout, m, *md, 0); );
}
virtual model_converter * translate(ast_translation & translator) {
UNREACHABLE();
return 0;
}
};
mk_slice::mk_slice(context & ctx):
plugin(1),
m_ctx(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_solved_vars(m),
m_pinned(m),
m_pc(0),
m_mc(0)
{}
bit_vector& mk_slice::get_predicate_slice(func_decl* h) {
if (!m_sliceable.contains(h)) {
bit_vector bv;
bv.resize(h->get_arity(), true);
m_sliceable.insert(h, bv);
}
return m_sliceable.find(h);
}
/**
\brief Saturate set of rules with respect to slicing criteria.
*/
void mk_slice::saturate(rule_set const& src) {
bool change = true;
while (change) {
change = false;
for (unsigned i = 0; i < src.get_num_rules(); ++i) {
change = prune_rule(*src.get_rule(i)) || change;
}
}
}
void mk_slice::filter_unique_vars(rule& r) {
//
// Variables that occur in multiple uinterpreted predicates are not sliceable.
//
uint_set used_vars;
for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) {
app* p = r.get_tail(j);
for (unsigned i = 0; i < p->get_num_args(); ++i) {
expr* v = p->get_arg(i);
if (is_var(v)) {
unsigned vi = to_var(v)->get_idx();
add_var(vi);
if (used_vars.contains(vi)) {
m_var_is_sliceable[vi] = false;
}
else {
used_vars.insert(vi);
}
}
}
}
}
void mk_slice::solve_vars(rule& r, uint_set& used_vars, uint_set& parameter_vars) {
expr_ref_vector conjs = get_tail_conjs(r);
for (unsigned j = 0; j < conjs.size(); ++j) {
expr* e = conjs[j].get();
expr_ref r(m);
unsigned v;
if (is_eq(e, v, r) && is_output(v) && m_var_is_sliceable[v]) {
TRACE("dl", tout << "is_eq: " << mk_pp(e, m) << " " << (m_solved_vars[v].get()?"solved":"new") << "\n";);
add_var(v);
if (!m_solved_vars[v].get()) {
add_free_vars(parameter_vars, r);
m_solved_vars[v] = r;
}
else {
// variables can only be solved once.
add_free_vars(used_vars, e);
add_free_vars(used_vars, m_solved_vars[v].get());
used_vars.insert(v);
}
}
else {
add_free_vars(used_vars, e);
}
}
}
bool mk_slice::prune_rule(rule& r) {
TRACE("dl", r.display(m_ctx, tout << "prune:\n"); );
bool change = false;
init_vars(r);
//
// if a predicate in the body takes a constant as argument,
// the corresponding position is not sliceable.
//
for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) {
app* p = r.get_tail(j);
bit_vector& bv = get_predicate_slice(p);
for (unsigned i = 0; i < p->get_num_args(); ++i) {
if (!is_var(p->get_arg(i)) && bv.get(i)) {
bv.unset(i);
change = true;
TRACE("dl", tout << "argument " << i << " is not a variable " << p->get_decl()->get_name() << "\n";);
}
}
}
filter_unique_vars(r);
//
// Collect the set of variables that are solved.
// Collect the occurrence count of the variables per conjunct.
//
uint_set used_vars, parameter_vars;
solve_vars(r, used_vars, parameter_vars);
uint_set::iterator it = used_vars.begin(), end = used_vars.end();
for (; it != end; ++it) {
if (*it < m_var_is_sliceable.size()) {
m_var_is_sliceable[*it] = false;
}
}
//
// Check if sliceable variables are either solved
// or are used to solve output sliceable variables, or
// don't occur in interpreted tail.
//
for (unsigned i = 0; i < num_vars(); ++i) {
if (!m_var_is_sliceable[i]) {
continue;
}
if (used_vars.contains(i)) {
m_var_is_sliceable[i] = false;
continue;
}
bool is_input = m_input[i];
bool is_output = m_output[i];
if (is_input && is_output) {
if (m_solved_vars[i].get()) {
m_var_is_sliceable[i] = false;
}
}
else if (is_output) {
if (parameter_vars.contains(i)) {
m_var_is_sliceable[i] = false;
}
}
else if (is_input) {
// I can be a parameter var, but not in used_vars.
}
else {
// variable does not correspond to
// any position in predicates.
}
}
//
// Update sliceable predicates based on slicing information of variables.
//
change = finalize_vars(r.get_head()) || change;
for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) {
change = finalize_vars(r.get_tail(j)) || change;
}
return change;
}
bool mk_slice::is_eq(expr* e, unsigned& v, expr_ref& r) {
expr* c, *th, *el, *e1, *e2;
unsigned v1, v2;
expr_ref r1(m), r2(m);
if (m.is_ite(e, c, th, el)) {
if (is_eq(th, v1, r1) && is_eq(el, v2, r2) && v1 == v2) {
v = v1;
r = m.mk_ite(c, r1, r2);
return true;
}
}
if (is_var(e)) {
v = to_var(e)->get_idx();
r = m.mk_true();
return true;
}
if (m.is_not(e,e) && is_var(e)) {
v = to_var(e)->get_idx();
r = m.mk_false();
return true;
}
if (m.is_eq(e, e1, e2) && is_var(e1)) {
v = to_var(e1)->get_idx();
r = e2;
return true;
}
if (m.is_eq(e, e1, e2) && is_var(e2)) {
v = to_var(e2)->get_idx();
r = e1;
return true;
}
return false;
}
bool mk_slice::is_output(unsigned idx) {
return idx < m_output.size() && m_output[idx] && !m_input[idx];
}
bool mk_slice::is_output(expr* e) {
if (is_var(e)) {
return is_output(to_var(e)->get_idx());
}
else {
return false;
}
}
void mk_slice::init_vars(rule& r) {
m_input.reset();
m_output.reset();
m_var_is_sliceable.reset();
m_solved_vars.reset();
init_vars(r.get_head(), true, false);
for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) {
init_vars(r.get_tail(j), false, r.is_neg_tail(j));
}
}
expr_ref_vector mk_slice::get_tail_conjs(rule const& r) {
expr_ref_vector conjs(m);
for (unsigned j = r.get_uninterpreted_tail_size(); j < r.get_tail_size(); ++j) {
conjs.push_back(r.get_tail(j));
}
qe::flatten_and(conjs);
return conjs;
}
void mk_slice::add_var(unsigned idx) {
if (idx >= m_input.size()) {
m_input.resize(idx+1, false);
m_output.resize(idx+1, false);
m_var_is_sliceable.resize(idx+1, true);
m_solved_vars.resize(idx+1);
}
}
void mk_slice::init_vars(app* p, bool is_output, bool is_neg_tail) {
bit_vector& bv = get_predicate_slice(p);
for (unsigned i = 0; i < p->get_num_args(); ++i) {
if (is_neg_tail) {
TRACE("dl", tout << "negated " << i << " in " << p->get_decl()->get_name() << "\n";);
bv.unset(i);
}
expr* arg = p->get_arg(i);
if (is_var(arg)) {
unsigned idx = to_var(arg)->get_idx();
add_var(idx);
if (is_output) {
m_output[idx] = true;
}
else {
m_input[idx] = true;
}
m_var_is_sliceable[idx] &= bv.get(i);
}
else {
SASSERT(m.is_value(arg));
if (!is_output) {
TRACE("dl", tout << "input " << i << " in " << p->get_decl()->get_name() << "\n";);
bv.unset(i);
}
}
}
}
bool mk_slice::finalize_vars(app* p) {
bool change = false;
bit_vector& bv = get_predicate_slice(p);
for (unsigned i = 0; i < p->get_num_args(); ++i) {
expr* arg = p->get_arg(i);
if (is_var(arg) && !m_var_is_sliceable[to_var(arg)->get_idx()] && bv.get(i)) {
bv.unset(i);
change = true;
TRACE("dl", tout << "variable is unslicable " << mk_pp(arg, m) << " for index " << i << " in " << p->get_decl()->get_name() << "\n";);
}
}
return change;
}
void mk_slice::add_free_vars(uint_set& result, expr* e) {
ptr_vector<sort> sorts;
get_free_vars(e, sorts);
for (unsigned i = 0; i < sorts.size(); ++i) {
if (sorts[i]) {
result.insert(i);
}
}
}
void mk_slice::display(std::ostream& out) {
obj_map<func_decl, bit_vector>::iterator it = m_sliceable.begin();
obj_map<func_decl, bit_vector>::iterator end = m_sliceable.end();
for (; it != end; ++it) {
out << it->m_key->get_name() << " ";
bit_vector const& bv = it->m_value;
for (unsigned i = 0; i < bv.size(); ++i) {
out << (bv.get(i)?"1":"0");
}
out << "\n";
}
}
void mk_slice::reset() {
m_input.reset();
m_output.reset();
m_var_is_sliceable.reset();
m_solved_vars.reset();
m_predicates.reset();
m_pinned.reset();
}
void mk_slice::declare_predicates(rule_set const& src, rule_set& dst) {
obj_map<func_decl, bit_vector>::iterator it = m_sliceable.begin(), end = m_sliceable.end();
ptr_vector<sort> domain;
bool has_output = false;
func_decl* f;
for (; it != end; ++it) {
domain.reset();
func_decl* p = it->m_key;
bit_vector const& bv = it->m_value;
for (unsigned i = 0; i < bv.size(); ++i) {
if (!bv.get(i)) {
domain.push_back(p->get_domain(i));
}
}
if (domain.size() < bv.size()) {
f = m_ctx.mk_fresh_head_predicate(p->get_name(), symbol("slice"), domain.size(), domain.c_ptr(), p);
m_pinned.push_back(f);
m_predicates.insert(p, f);
dst.inherit_predicate(src, p, f);
if (m_mc) {
m_mc->add_predicate(p, f);
}
}
else if (src.is_output_predicate(p)) {
dst.set_output_predicate(p);
has_output = true;
}
}
// disable slicing if the output predicates don't occur in rules.
if (!has_output) {
m_predicates.reset();
}
}
bool mk_slice::rule_updated(rule const& r) {
if (m_predicates.contains(r.get_decl())) return true;
for (unsigned i = 0; i < r.get_uninterpreted_tail_size(); ++i) {
if (m_predicates.contains(r.get_decl(i))) return true;
}
return false;
}
void mk_slice::update_predicate(app* p, app_ref& q) {
func_decl* qd;
if (m_predicates.find(p->get_decl(), qd)) {
bit_vector const& bv = get_predicate_slice(p->get_decl());
ptr_vector<expr> args;
for (unsigned i = 0; i < bv.size(); ++i) {
if (!bv.get(i)) {
args.push_back(p->get_arg(i));
}
}
q = m.mk_app(qd, args.size(), args.c_ptr());
}
else {
q = p;
}
}
void mk_slice::update_rule(rule& r, rule_set& dst) {
rule_ref new_rule(rm);
if (rule_updated(r)) {
init_vars(r);
app_ref_vector tail(m);
app_ref head(m);
ptr_vector<sort> sorts;
update_predicate(r.get_head(), head);
get_free_vars(head.get(), sorts);
for (unsigned i = 0; i < r.get_uninterpreted_tail_size(); ++i) {
app_ref t(m);
update_predicate(r.get_tail(i), t);
tail.push_back(t);
get_free_vars(t, sorts);
}
expr_ref_vector conjs = get_tail_conjs(r);
m_solved_vars.reset();
for (unsigned i = 0; i < conjs.size(); ++i) {
expr* e = conjs[i].get();
tail.push_back(to_app(e));
}
new_rule = rm.mk(head.get(), tail.size(), tail.c_ptr(), (const bool*) 0);
rm.fix_unbound_vars(new_rule, false);
TRACE("dl", r.display(m_ctx, tout << "replacing:\n"); new_rule->display(m_ctx, tout << "by:\n"););
if (m_ctx.generate_proof_trace()) {
rm.mk_rule_asserted_proof(*new_rule.get());
}
}
else {
new_rule = &r;
}
dst.add_rule(new_rule.get());
if (m_pc) {
m_pc->insert(&r, new_rule.get(), 0, 0);
}
}
void mk_slice::update_rules(rule_set const& src, rule_set& dst) {
for (unsigned i = 0; i < src.get_num_rules(); ++i) {
update_rule(*src.get_rule(i), dst);
}
}
rule_set * mk_slice::operator()(rule_set const & src) {
for (unsigned i = 0; i < src.get_num_rules(); ++i) {
if (src.get_rule(i)->has_quantifiers()) {
return 0;
}
}
ref<slice_proof_converter> spc;
ref<slice_model_converter> smc;
if (m_ctx.generate_proof_trace()) {
spc = alloc(slice_proof_converter, m_ctx);
}
if (m_ctx.get_model_converter()) {
smc = alloc(slice_model_converter, *this, m);
}
m_pc = spc.get();
m_mc = smc.get();
reset();
saturate(src);
rule_set* result = alloc(rule_set, m_ctx);
declare_predicates(src, *result);
if (m_predicates.empty()) {
// nothing could be sliced.
dealloc(result);
return 0;
}
TRACE("dl", display(tout););
update_rules(src, *result);
TRACE("dl", result->display(tout););
if (m_mc) {
obj_map<func_decl, bit_vector>::iterator it = m_sliceable.begin(), end = m_sliceable.end();
for (; it != end; ++it) {
m_mc->add_sliceable(it->m_key, it->m_value);
}
}
m_ctx.add_proof_converter(spc.get());
m_ctx.add_model_converter(smc.get());
return result;
}
};

View file

@ -0,0 +1,115 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_slice.h
Abstract:
<abstract>
Author:
Krystof Hoder (t-khoder) 2010-10-4.
Revision History:
--*/
#ifndef _DL_MK_SLICE_H_
#define _DL_MK_SLICE_H_
#include"dl_context.h"
#include"dl_rule_set.h"
#include"uint_set.h"
#include"dl_rule_transformer.h"
namespace datalog {
/**
\brief Implements a slicing rule transformation.
*/
class mk_slice : public rule_transformer::plugin {
class slice_proof_converter;
class slice_model_converter;
context& m_ctx;
ast_manager& m;
rule_manager& rm;
svector<bool> m_input;
svector<bool> m_output;
expr_ref_vector m_solved_vars;
svector<bool> m_var_is_sliceable;
obj_map<func_decl, func_decl*> m_predicates;
obj_map<func_decl, bit_vector> m_sliceable;
ast_ref_vector m_pinned;
slice_proof_converter* m_pc;
slice_model_converter* m_mc;
void reset();
void init(rule_set const& source);
void saturate(rule_set const& source);
void display(std::ostream& out);
bool prune_rule(rule& r);
void init_vars(rule& r);
void init_vars(app* p, bool is_output, bool is_neg_tail);
bool finalize_vars(app* p);
unsigned num_vars() const { return m_input.size(); }
bit_vector& get_predicate_slice(func_decl* p);
bit_vector& get_predicate_slice(app* p) { return get_predicate_slice(p->get_decl()); }
bool is_eq(expr* e, unsigned& v, expr_ref& r);
void add_free_vars(uint_set& s, expr* e);
void add_var(unsigned idx);
bool is_output(expr* e);
bool is_output(unsigned idx);
void update_rules(rule_set const& src, rule_set& dst);
void update_rule(rule& r, rule_set& dst);
expr_ref_vector get_tail_conjs(rule const& r);
void declare_predicates(rule_set const& src, rule_set& dst);
bool rule_updated(rule const& r);
void update_predicate(app* p, app_ref& q);
void filter_unique_vars(rule& r);
void solve_vars(rule& r, uint_set& used_vars, uint_set& parameter_vars);
public:
/**
\brief Create slice rule transformer for \c goal predicate. When applying the transformer,
the \c goal must be present in the \c rule_set that is being transformed.
*/
mk_slice(context & ctx);
virtual ~mk_slice() { }
rule_set * operator()(rule_set const & source);
func_decl* get_predicate(func_decl* p) { func_decl* q = p; m_predicates.find(p, q); return q; }
obj_map<func_decl, func_decl*> const& get_predicates() { return m_predicates; }
};
};
#endif /* _DL_MK_SLICE_H_ */

View file

@ -0,0 +1,364 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_subsumption_checker.cpp
Abstract:
Rule transformer which checks for subsumption
(currently just for subsumption with total relations)
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
--*/
#include <sstream>
#include"ast_pp.h"
#include "rewriter.h"
#include "rewriter_def.h"
#include"dl_mk_subsumption_checker.h"
namespace datalog {
// -----------------------------------
//
// mk_subsumption_checker
//
// -----------------------------------
bool mk_subsumption_checker::is_total_rule(const rule * r) {
if(r->get_tail_size()!=0) { return false; }
unsigned pt_len = r->get_positive_tail_size();
if(pt_len!=r->get_uninterpreted_tail_size()) {
//we dont' expect rules with negative tails to be total
return false;
}
for(unsigned i=0; i<pt_len; i++) {
func_decl * tail_pred = r->get_tail(i)->get_decl();
if(!m_total_relations.contains(tail_pred)) {
//this rule has a non-total predicate in the tail
return false;
}
}
unsigned t_len = r->get_positive_tail_size();
for(unsigned i=pt_len; i<t_len; i++) {
SASSERT(!r->is_neg_tail(i)); //we assume interpreted tail not to be negated
if(!m.is_true(r->get_tail(i))) {
//this rule has an interpreted tail which is not constant true
return false;
}
}
var_idx_set head_vars;
app * head = r->get_head();
unsigned arity = head->get_num_args();
for(unsigned i=0; i<arity; i++) {
expr * arg = head->get_arg(i);
if(!is_var(arg)) { return false; }
unsigned idx = to_var(arg)->get_idx();
if(head_vars.contains(idx)) { return false; }
head_vars.insert(idx);
}
SASSERT(head_vars.num_elems()==arity);
return true;
}
void mk_subsumption_checker::on_discovered_total_relation(func_decl * pred, rule * r) {
//this should be rule marking a new relation as total
SASSERT(!m_total_relations.contains(pred));
SASSERT(!r || pred==r->get_decl());
SASSERT(!r || is_total_rule(r));
m_total_relations.insert(pred);
m_total_relation_defining_rules.insert(pred, r);
m_have_new_total_rule = true;
if(r) {
m_ref_holder.push_back(r);
}
}
void mk_subsumption_checker::scan_for_total_rules(const rule_set & rules) {
bool new_discovered;
//we cycle through the rules until we keep discovering new total relations
//(discovering a total relation migh reveal other total relations)
do {
new_discovered = false;
rule_set::iterator rend = rules.end();
for(rule_set::iterator rit = rules.begin(); rit!=rend; ++rit) {
rule * r = *rit;
func_decl * head_pred = r->get_decl();
if(is_total_rule(r) && !m_total_relations.contains(head_pred)) {
on_discovered_total_relation(head_pred, r);
new_discovered = true;
}
}
} while(new_discovered);
}
bool mk_subsumption_checker::transform_rule(rule * r,
rule_subsumption_index& subs_index, rule_ref & res)
{
unsigned u_len = r->get_uninterpreted_tail_size();
unsigned len = r->get_tail_size();
if(u_len==0) {
res = r;
return true;
}
app_ref head(r->get_head(), m);
app_ref_vector tail(m);
svector<bool> tail_neg;
for(unsigned i=0; i<u_len; i++) {
app * tail_atom = r->get_tail(i);
bool neg = r->is_neg_tail(i);
if(m_total_relations.contains(tail_atom->get_decl())
|| subs_index.is_subsumed(tail_atom)) {
if(neg) {
//rule contains negated total relation, this means that it is unsatisfiable
//and can be removed
return false;
}
else {
//we remove total relations from the tail
continue;
}
}
if(!neg && head.get()==tail_atom) {
//rule contains its head positively in the tail, therefore
//it will never add any new facts to the relation, so it
//can be removed
return false;
}
tail.push_back(tail_atom);
tail_neg.push_back(neg);
}
if(tail.size()==u_len) {
res = r;
return true;
}
//we just copy the interpreted part of the tail
for(unsigned i=u_len; i<len; i++) {
tail.push_back(r->get_tail(i));
tail_neg.push_back(r->is_neg_tail(i));
}
SASSERT(tail.size()==tail_neg.size());
res = m_context.get_rule_manager().mk(head, tail.size(), tail.c_ptr(), tail_neg.c_ptr());
res->set_accounting_parent_object(m_context, r);
m_context.get_rule_manager().fix_unbound_vars(res, true);
m_context.get_rule_manager().mk_rule_rewrite_proof(*r, *res.get());
return true;
}
bool rule_size_comparator(rule * r1, rule * r2) {
return r1->get_tail_size() < r2->get_tail_size();
}
bool mk_subsumption_checker::transform_rules(const rule_set & orig, rule_set & tgt) {
bool modified = false;
func_decl_set total_relations_with_included_rules;
rule_subsumption_index subs_index(m_context);
rule_ref_vector orig_rules(m_context.get_rule_manager());
orig_rules.append(orig.get_num_rules(), orig.begin());
rule * * rbegin = orig_rules.c_ptr();
rule * * rend = rbegin + orig_rules.size();
//before traversing we sort rules so that the shortest are in the beginning.
//this will help make subsumption checks more efficient
std::sort(rbegin, rend, rule_size_comparator);
for(rule_set::iterator rit = rbegin; rit!=rend; ++rit) {
rule * r = *rit;
func_decl * head_pred = r->get_decl();
if(m_total_relations.contains(head_pred)) {
if(!orig.is_output_predicate(head_pred) ||
total_relations_with_included_rules.contains(head_pred)) {
//We just skip definitions of total non-output relations as
//we'll eliminate them from the problem.
//We also skip rules of total output relations for which we have
//already output the rule which implies their totality.
modified = true;
continue;
}
rule * defining_rule;
VERIFY(m_total_relation_defining_rules.find(head_pred, defining_rule));
if (defining_rule) {
rule_ref totality_rule(m_context.get_rule_manager());
VERIFY(transform_rule(defining_rule, subs_index, totality_rule));
if(defining_rule!=totality_rule) {
modified = true;
}
tgt.add_rule(totality_rule);
SASSERT(totality_rule->get_decl()==head_pred);
}
else {
modified = true;
}
total_relations_with_included_rules.insert(head_pred);
continue;
}
rule_ref new_rule(m_context.get_rule_manager());
if(!transform_rule(r, subs_index, new_rule)) {
modified = true;
continue;
}
if(m_new_total_relation_discovery_during_transformation && is_total_rule(new_rule)) {
on_discovered_total_relation(head_pred, new_rule.get());
}
if(subs_index.is_subsumed(new_rule)) {
modified = true;
continue;
}
if(new_rule.get()!=r) {
modified = true;
}
tgt.add_rule(new_rule);
subs_index.add(new_rule);
}
tgt.inherit_predicates(orig);
TRACE("dl",
tout << "original set size: "<<orig.get_num_rules()<<"\n"
<< "reduced set size: "<<tgt.get_num_rules()<<"\n"; );
return modified;
}
void mk_subsumption_checker::scan_for_relations_total_due_to_facts(rule_set const& source) {
rel_context_base* rel = m_context.get_rel_context();
if (!rel) {
return;
}
relation_manager& rm = rel->get_rmanager();
func_decl_set const& candidate_preds = m_context.get_predicates();
func_decl_set::iterator end = candidate_preds.end();
for(func_decl_set::iterator it = candidate_preds.begin(); it!=end; ++it) {
func_decl * pred = *it;
unsigned rel_sz;
if (m_total_relations.contains(pred)) { continue; } // already total
if (!rel->try_get_size(pred, rel_sz)) { continue; }
unsigned arity = pred->get_arity();
if (arity > 30) { continue; }
//for now we only check booleans domains
for(unsigned i=0; i<arity; i++) {
if(!m.is_bool(pred->get_domain(i))) {
goto next_pred;
}
}
{
unsigned total_size = 1<<arity;
//by calling rel.knows_exact_size() we got assured that the estimate is exact
obj_hashtable<app> * head_store;
if(m_ground_unconditional_rule_heads.find(pred, head_store)) {
//Some relations may receive facts by ground unconditioned rules.
//We scanned for those earlier, so now we check whether we cannot get a
//better estimate of relation size from these.
unsigned gnd_rule_cnt = head_store->size();
if(gnd_rule_cnt>rel_sz) {
rel_sz = gnd_rule_cnt;
}
}
SASSERT(total_size>=rel_sz);
if(total_size==rel_sz) {
on_discovered_total_relation(pred, 0);
}
}
next_pred:;
}
}
void mk_subsumption_checker::collect_ground_unconditional_rule_heads(const rule_set & rules)
{
rule_set::iterator rend = rules.end();
for(rule_set::iterator rit = rules.begin(); rit!=rend; ++rit) {
rule * r = *rit;
func_decl * pred = r->get_decl();
if(r->get_tail_size()!=0) { continue; }
app * head = r->get_head();
unsigned arity = pred->get_arity();
for(unsigned i=0; i<arity; i++) {
expr * arg = head->get_arg(i);
if(!is_app(arg)) {
goto next_rule;
}
}
if(!m_ground_unconditional_rule_heads.contains(pred)) {
m_ground_unconditional_rule_heads.insert(pred, alloc(obj_hashtable<app>));
}
m_ground_unconditional_rule_heads.find(pred)->insert(head);
next_rule:;
}
}
rule_set * mk_subsumption_checker::operator()(rule_set const & source) {
// TODO mc
m_have_new_total_rule = false;
collect_ground_unconditional_rule_heads(source);
scan_for_relations_total_due_to_facts(source);
scan_for_total_rules(source);
m_have_new_total_rule = false;
rule_set * res = alloc(rule_set, m_context);
bool modified = transform_rules(source, *res);
if (!m_have_new_total_rule && !modified) {
dealloc(res);
return 0;
}
//During the construction of the new set we may discover new total relations
//(by quantifier elimination on the uninterpreted tails).
SASSERT(m_new_total_relation_discovery_during_transformation || !m_have_new_total_rule);
while (m_have_new_total_rule) {
m_have_new_total_rule = false;
rule_set * old = res;
res = alloc(rule_set, m_context);
transform_rules(*old, *res);
dealloc(old);
}
return res;
}
};

View file

@ -0,0 +1,93 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
mk_subsumption_checker.h
Abstract:
Rule transformer which checks for subsumption
(currently just for subsumption with total relations)
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
--*/
#ifndef _DL_MK_SUBSUMPTION_CHECKER_H_
#define _DL_MK_SUBSUMPTION_CHECKER_H_
#include "dl_context.h"
#include "dl_rule_transformer.h"
#include "dl_rule_subsumption_index.h"
namespace datalog {
class mk_subsumption_checker : public rule_transformer::plugin {
ast_manager & m;
context & m_context;
rule_ref_vector m_ref_holder;
func_decl_set m_total_relations;
/** Map that for each relation contains the rule which implies its totality.
If the totality is due to the relation containing all facts, the rule stored
here is zero*/
obj_map<func_decl, rule *> m_total_relation_defining_rules;
/**
Contains heads of rules of shape
R(c1,c2,...cN).
grouped by their predicate.
This information helps to improve the results of the
scan_for_relations_total_due_to_facts() function.
*/
obj_map<func_decl, obj_hashtable<app> *> m_ground_unconditional_rule_heads;
bool m_have_new_total_rule;
bool m_new_total_relation_discovery_during_transformation;
bool is_total_rule(const rule * r);
/** Function to be called when a new total relation is discovered */
void on_discovered_total_relation(func_decl * pred, rule * r);
void scan_for_total_rules(rule_set const& rules);
void scan_for_relations_total_due_to_facts(rule_set const& rules);
void collect_ground_unconditional_rule_heads(const rule_set & rules);
/** Return false if rule is unsatisfiable */
bool transform_rule(rule * r, rule_subsumption_index& subs_index, rule_ref & res);
/** Return false if the rule set hasn't changed */
bool transform_rules(const rule_set & orig, rule_set & tgt);
public:
mk_subsumption_checker(context & ctx, unsigned priority=31000)
: plugin(priority),
m(ctx.get_manager()),
m_context(ctx),
m_ref_holder(ctx.get_rule_manager()),
m_new_total_relation_discovery_during_transformation(true) {}
~mk_subsumption_checker() {
reset_dealloc_values(m_ground_unconditional_rule_heads);
}
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_SUBSUMPTION_CHECKER_H_ */

View file

@ -0,0 +1,383 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_unbound_compressor.cpp
Abstract:
<abstract>
Author:
Krystof Hoder (t-khoder) 2010-10-04.
Revision History:
--*/
#include<utility>
#include<sstream>
#include"dl_mk_unbound_compressor.h"
namespace datalog {
mk_unbound_compressor::mk_unbound_compressor(context & ctx) :
plugin(500),
m_context(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_rules(rm),
m_pinned(m) {
}
void mk_unbound_compressor::reset() {
m_rules.reset();
m_todo.reset();
m_in_progress.reset();
m_map.reset();
m_pinned.reset();
}
bool mk_unbound_compressor::is_unbound_argument(rule * r, unsigned head_index) {
app * head = r->get_head();
expr * head_arg = head->get_arg(head_index);
if (!is_var(head_arg)) {
return false;
}
unsigned var_idx = to_var(head_arg)->get_idx();
return rm.collect_tail_vars(r).contains(var_idx);
}
void mk_unbound_compressor::add_task(func_decl * pred, unsigned arg_index) {
c_info ci = c_info(pred, arg_index);
if (m_map.contains(ci)) {
return; //this task was already added
}
unsigned parent_arity = pred->get_arity();
sort * const * parent_domain = pred->get_domain();
symbol const& parent_name = pred->get_name();
unsigned arity = parent_arity-1;
ptr_vector<sort> domain;
for (unsigned i = 0; i < parent_arity; i++) {
if (i != arg_index) {
domain.push_back(parent_domain[i]);
}
}
std::stringstream name_suffix;
name_suffix << "compr_arg_" << arg_index;
func_decl * cpred = m_context.mk_fresh_head_predicate(parent_name, symbol(name_suffix.str().c_str()),
arity, domain.c_ptr(), pred);
m_pinned.push_back(cpred);
m_todo.push_back(ci);
m_map.insert(ci, cpred);
}
void mk_unbound_compressor::detect_tasks(rule_set const& source, unsigned rule_index) {
rule * r = m_rules.get(rule_index);
var_idx_set& tail_vars = rm.collect_tail_vars(r);
app * head = r->get_head();
func_decl * head_pred = head->get_decl();
if (source.is_output_predicate(head_pred)) {
//we don't compress output predicates
return;
}
unsigned n = head_pred->get_arity();
rm.get_counter().reset();
rm.get_counter().count_vars(m, head, 1);
for (unsigned i=0; i<n; i++) {
expr * arg = head->get_arg(i);
if (!is_var(arg)) {
continue;
}
unsigned var_idx = to_var(arg)->get_idx();
if (!tail_vars.contains(var_idx)) {
//unbound
unsigned occurence_cnt = rm.get_counter().get(var_idx);
SASSERT(occurence_cnt>0);
if (occurence_cnt == 1) {
TRACE("dl", r->display(m_context, tout << "Compress: "););
add_task(head_pred, i);
return; //we compress out the unbound arguments one by one
}
}
}
}
void mk_unbound_compressor::try_compress(rule_set const& source, unsigned rule_index) {
start:
rule * r = m_rules.get(rule_index);
var_idx_set& tail_vars = rm.collect_tail_vars(r);
app * head = r->get_head();
func_decl * head_pred = head->get_decl();
unsigned head_arity = head_pred->get_arity();
rm.get_counter().reset();
rm.get_counter().count_vars(m, head);
unsigned arg_index;
for (arg_index = 0; arg_index < head_arity; arg_index++) {
expr * arg = head->get_arg(arg_index);
if (!is_var(arg)) {
continue;
}
unsigned var_idx = to_var(arg)->get_idx();
if (!tail_vars.contains(var_idx)) {
//unbound
unsigned occurence_cnt = rm.get_counter().get(var_idx);
SASSERT(occurence_cnt>0);
if ( occurence_cnt==1 && m_in_progress.contains(c_info(head_pred, arg_index)) ) {
//we have found what to compress
break;
}
}
}
if (arg_index == head_arity) {
//we didn't find anything to compress
return;
}
SASSERT(arg_index<head_arity);
c_info ci(head_pred, arg_index);
func_decl * cpred;
TRUSTME( m_map.find(ci, cpred) );
ptr_vector<expr> cargs;
for (unsigned i=0; i<head_arity; i++) {
if (i != arg_index) {
cargs.push_back(head->get_arg(i));
}
}
app_ref chead(m.mk_app(cpred, head_arity-1, cargs.c_ptr()), m);
if (r->get_tail_size()==0 && m_context.get_rule_manager().is_fact(chead)) {
m_non_empty_rels.insert(cpred);
m_context.add_fact(chead);
//remove the rule that became fact by placing the last rule on its place
m_head_occurrence_ctr.dec(m_rules.get(rule_index)->get_decl());
m_rules.set(rule_index, m_rules.get(m_rules.size()-1));
m_rules.shrink(m_rules.size()-1);
//since we moved the last rule to rule_index, we have to try to compress it as well
if (rule_index<m_rules.size()) {
goto start;
}
}
else {
rule_ref new_rule(m_context.get_rule_manager().mk(r, chead), m_context.get_rule_manager());
new_rule->set_accounting_parent_object(m_context, r);
m_head_occurrence_ctr.dec(m_rules.get(rule_index)->get_decl());
m_rules.set(rule_index, new_rule);
m_head_occurrence_ctr.inc(m_rules.get(rule_index)->get_decl());
detect_tasks(source, rule_index);
}
m_modified = true;
}
void mk_unbound_compressor::mk_decompression_rule(rule * r, unsigned tail_index, unsigned arg_index,
rule_ref& res)
{
app * orig_dtail = r->get_tail(tail_index); //dtail ~ decompressed tail
c_info ci(orig_dtail->get_decl(), arg_index);
func_decl * dtail_pred;
TRUSTME( m_map.find(ci, dtail_pred) );
ptr_vector<expr> dtail_args;
unsigned orig_dtail_arity = orig_dtail->get_num_args();
for (unsigned i=0;i<orig_dtail_arity;i++) {
if (i != arg_index) {
dtail_args.push_back(orig_dtail->get_arg(i));
}
}
SASSERT(dtail_args.size()==dtail_pred->get_arity());
app_ref dtail(m.mk_app(dtail_pred, dtail_args.size(), dtail_args.c_ptr()), m);
svector<bool> tails_negated;
app_ref_vector tails(m);
unsigned tail_len = r->get_tail_size();
for (unsigned i=0; i<tail_len; i++) {
tails_negated.push_back(r->is_neg_tail(i));
if (i==tail_index && !r->is_neg_tail(i)) {
tails.push_back(dtail);
}
else {
tails.push_back(r->get_tail(i));
}
}
// Accumulate negated filtered rule instead
// of replacing the original predicate.
if (r->is_neg_tail(tail_index)) {
tails_negated.push_back(true);
tails.push_back(dtail);
}
res = m_context.get_rule_manager().mk( r->get_head(), tails.size(), tails.c_ptr(), tails_negated.c_ptr());
res->set_accounting_parent_object(m_context, r);
m_context.get_rule_manager().fix_unbound_vars(res, true);
}
void mk_unbound_compressor::add_decompression_rule(rule_set const& source, rule * r, unsigned tail_index, unsigned arg_index) {
rule_ref new_rule(m_context.get_rule_manager());
mk_decompression_rule(r, tail_index, arg_index, new_rule);
unsigned new_rule_index = m_rules.size();
m_rules.push_back(new_rule);
m_context.get_rule_manager().mk_rule_rewrite_proof(*r, *new_rule.get());
m_head_occurrence_ctr.inc(new_rule->get_decl());
detect_tasks(source, new_rule_index);
m_modified = true;
//TODO: avoid rule duplicity
//If two predicates are compressed in a rule, applying decompression
//to the results can cause a rule being added multiple times:
//P:- R(x,y), S(x,y)
//is decompressed into rules
//P:- R1(x), S(x,y)
//P:- R(x,y), S1(x)
//and each of these rules is again decompressed giving the same rule
//P:- R1(x), S1(x)
//P:- R1(x), S1(x)
}
void mk_unbound_compressor::replace_by_decompression_rule(rule_set const& source, unsigned rule_index, unsigned tail_index, unsigned arg_index)
{
rule * r = m_rules.get(rule_index);
rule_ref new_rule(m_context.get_rule_manager());
mk_decompression_rule(r, tail_index, arg_index, new_rule);
m_rules.set(rule_index, new_rule);
//we don't update the m_head_occurrence_ctr because the head predicate doesn't change
detect_tasks(source, rule_index);
m_modified = true;
}
void mk_unbound_compressor::add_decompression_rules(rule_set const& source, unsigned rule_index) {
unsigned_vector compressed_tail_pred_arg_indexes;
//this value is updated inside the loop if replace_by_decompression_rule is called
rule_ref r(m_rules.get(rule_index), m_context.get_rule_manager());
unsigned utail_len = r->get_uninterpreted_tail_size();
unsigned tail_index=0;
while (tail_index<utail_len) {
app * t = r->get_tail(tail_index);
func_decl * t_pred = t->get_decl();
unsigned t_arity = t_pred->get_arity();
bool is_negated_predicate = r->is_neg_tail(tail_index);
compressed_tail_pred_arg_indexes.reset();
for (unsigned arg_index=0; arg_index<t_arity; arg_index++) {
c_info ci(t_pred, arg_index);
if (m_in_progress.contains(ci)) {
compressed_tail_pred_arg_indexes.push_back(arg_index);
}
}
bool orig_rule_replaced = false;
while (!compressed_tail_pred_arg_indexes.empty()) {
unsigned arg_index = compressed_tail_pred_arg_indexes.back();
compressed_tail_pred_arg_indexes.pop_back();
bool can_remove_orig_rule =
compressed_tail_pred_arg_indexes.empty() &&
!m_non_empty_rels.contains(t_pred) &&
m_head_occurrence_ctr.get(t_pred)==0;
if (can_remove_orig_rule || is_negated_predicate) {
replace_by_decompression_rule(source, rule_index, tail_index, arg_index);
orig_rule_replaced = true;
}
else {
add_decompression_rule(source, r, tail_index, arg_index);
}
}
if (orig_rule_replaced) {
//update r with the new rule
rule * new_rule = m_rules.get(rule_index);
SASSERT(new_rule->get_uninterpreted_tail_size() >= utail_len);
//here we check that the rule replacement didn't affect other uninterpreted literals
//in the tail (aside of variable renaming)
SASSERT(tail_index==0 ||
new_rule->get_tail(tail_index-1)->get_decl()==r->get_tail(tail_index-1)->get_decl());
r = new_rule;
//we have replaced the original rule, with one that has different
//content of the tail_index -th tail. we will therefore not do
//tail_index++, so that we examine the new tail literal as well
}
else {
tail_index++;
}
}
}
rule_set * mk_unbound_compressor::operator()(rule_set const & source) {
// TODO mc
m_modified = false;
rel_context_base* rel = m_context.get_rel_context();
if (rel) {
rel->collect_non_empty_predicates(m_non_empty_rels);
}
unsigned init_rule_cnt = source.get_num_rules();
SASSERT(m_rules.empty());
for (unsigned i=0; i<init_rule_cnt; i++) {
rule * r = source.get_rule(i);
m_rules.push_back(r);
m_head_occurrence_ctr.inc(r->get_decl());
}
for (unsigned i=0; i<init_rule_cnt; i++) {
detect_tasks(source, i);
}
while (!m_todo.empty()) {
m_in_progress.reset();
while (!m_todo.empty()) {
m_in_progress.insert(m_todo.back());
m_todo.pop_back();
}
unsigned rule_index = 0;
while (rule_index<m_rules.size()) {
try_compress(source, rule_index); //m_rules.size() can change here
if (rule_index<m_rules.size()) {
add_decompression_rules(source, rule_index); //m_rules.size() can change here
}
rule_index++;
}
}
rule_set * result = static_cast<rule_set *>(0);
if (m_modified) {
result = alloc(rule_set, m_context);
unsigned fin_rule_cnt = m_rules.size();
for (unsigned i=0; i<fin_rule_cnt; i++) {
result->add_rule(m_rules.get(i));
}
result->inherit_predicates(source);
}
reset();
return result;
}
};

View file

@ -0,0 +1,92 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_unbound_compressor.h
Abstract:
<abstract>
Author:
Krystof Hoder (t-khoder) 2010-10-4.
Revision History:
--*/
#ifndef _DL_MK_UNBOUND_COMPRESSOR_H_
#define _DL_MK_UNBOUND_COMPRESSOR_H_
#include<utility>
#include"map.h"
#include"obj_pair_hashtable.h"
#include"dl_context.h"
#include"dl_rule_set.h"
#include"dl_rule_transformer.h"
namespace datalog {
/**
\brief Functor for introducing auxiliary predicates to avoid unbound variables in
rule heads.
A rule
P(x,_) :- T(x).
is replaced by
P1(x) :- T(x).
and for each occurrence of P in a tail of a rule, a new rule is added with P1 in
its place.
*/
class mk_unbound_compressor : public rule_transformer::plugin {
/** predicate and index of compressed argument */
typedef std::pair<func_decl*,unsigned> c_info;
typedef pair_hash<ptr_hash<func_decl>,unsigned_hash> c_info_hash;
/** predicates that are results of compression */
typedef map<c_info, func_decl*, c_info_hash, default_eq<c_info> > c_map;
typedef hashtable<c_info, c_info_hash, default_eq<c_info> > in_progress_table;
typedef svector<c_info> todo_stack;
context & m_context;
ast_manager & m;
rule_manager & rm;
rule_ref_vector m_rules;
bool m_modified;
todo_stack m_todo;
in_progress_table m_in_progress;
c_map m_map;
/**
Relations that contain facts
*/
func_decl_set m_non_empty_rels;
ast_counter m_head_occurrence_ctr;
ast_ref_vector m_pinned;
bool is_unbound_argument(rule * r, unsigned head_index);
bool has_unbound_head_var(rule * r);
void detect_tasks(rule_set const& source, unsigned rule_index);
void add_task(func_decl * pred, unsigned arg_index);
void try_compress(rule_set const& source, unsigned rule_index);
void add_decompression_rules(rule_set const& source, unsigned rule_index);
void mk_decompression_rule(rule * r, unsigned tail_index, unsigned arg_index, rule_ref& res);
void add_decompression_rule(rule_set const& source, rule * r, unsigned tail_index, unsigned arg_index);
void replace_by_decompression_rule(rule_set const& source, unsigned rule_index, unsigned tail_index, unsigned arg_index);
void reset();
public:
mk_unbound_compressor(context & ctx);
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_UNBOUND_COMPRESSOR_H_ */

View file

@ -0,0 +1,64 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_unfold.cpp
Abstract:
Unfold rules once, return the unfolded set of rules.
Author:
Nikolaj Bjorner (nbjorner) 2012-10-15
Revision History:
--*/
#include "dl_mk_unfold.h"
namespace datalog {
mk_unfold::mk_unfold(context& ctx):
rule_transformer::plugin(100, false),
m_ctx(ctx),
m(ctx.get_manager()),
rm(ctx.get_rule_manager()),
m_unify(ctx)
{}
void mk_unfold::expand_tail(rule& r, unsigned tail_idx, rule_set const& src, rule_set& dst) {
SASSERT(tail_idx <= r.get_uninterpreted_tail_size());
if (tail_idx == r.get_uninterpreted_tail_size()) {
dst.add_rule(&r);
}
else {
func_decl* p = r.get_decl(tail_idx);
rule_vector const& p_rules = src.get_predicate_rules(p);
rule_ref new_rule(rm);
for (unsigned i = 0; i < p_rules.size(); ++i) {
rule const& r2 = *p_rules[i];
if (m_unify.unify_rules(r, tail_idx, r2) &&
m_unify.apply(r, tail_idx, r2, new_rule)) {
expr_ref_vector s1 = m_unify.get_rule_subst(r, true);
expr_ref_vector s2 = m_unify.get_rule_subst(r2, false);
resolve_rule(r, r2, tail_idx, s1, s2, *new_rule.get());
expand_tail(*new_rule.get(), tail_idx+r2.get_uninterpreted_tail_size(), src, dst);
}
}
}
}
rule_set * mk_unfold::operator()(rule_set const & source) {
rule_set* rules = alloc(rule_set, m_ctx);
rule_set::iterator it = source.begin(), end = source.end();
for (; it != end; ++it) {
expand_tail(**it, 0, source, *rules);
}
rules->inherit_predicates(source);
return rules;
}
};

View file

@ -0,0 +1,53 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
dl_mk_unfold.h
Abstract:
Unfold rules once, return the unfolded set of rules.
Author:
Nikolaj Bjorner (nbjorner) 2012-10-15
Revision History:
--*/
#ifndef _DL_MK_UNFOLD_H_
#define _DL_MK_UNFOLD_H_
#include"dl_context.h"
#include"dl_rule_set.h"
#include"uint_set.h"
#include"dl_rule_transformer.h"
#include"dl_mk_rule_inliner.h"
namespace datalog {
/**
\brief Implements an unfolding transformation.
*/
class mk_unfold : public rule_transformer::plugin {
context& m_ctx;
ast_manager& m;
rule_manager& rm;
rule_unifier m_unify;
void expand_tail(rule& r, unsigned tail_idx, rule_set const& src, rule_set& dst);
public:
/**
\brief Create unfold rule transformer.
*/
mk_unfold(context & ctx);
rule_set * operator()(rule_set const & source);
};
};
#endif /* _DL_MK_UNFOLD_H_ */

View file

@ -0,0 +1,81 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_transforms.cpp
Abstract:
Default transformations.
Author:
Nikolaj Bjorner (nbjorner) 2013-08-28.
Revision History:
Extracted from dl_context
--*/
#include"dl_transforms.h"
#include"dl_rule_transformer.h"
#include"dl_mk_coi_filter.h"
#include"dl_mk_filter_rules.h"
#include"dl_mk_interp_tail_simplifier.h"
#include"dl_mk_rule_inliner.h"
#include"dl_mk_bit_blast.h"
#include"dl_mk_array_blast.h"
#include"dl_mk_karr_invariants.h"
#include"dl_mk_magic_symbolic.h"
#include"dl_mk_quantifier_abstraction.h"
#include"dl_mk_quantifier_instantiation.h"
#include"dl_mk_subsumption_checker.h"
#include"dl_mk_scale.h"
namespace datalog {
void apply_default_transformation(context& ctx) {
rule_transformer transf(ctx);
ctx.ensure_closed();
transf.reset();
transf.register_plugin(alloc(datalog::mk_coi_filter, ctx));
transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, ctx));
transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 35005));
transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 35000));
transf.register_plugin(alloc(datalog::mk_coi_filter, ctx, 34990));
transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, ctx, 34980));
//and another round of inlining
transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34975));
transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34970));
transf.register_plugin(alloc(datalog::mk_coi_filter, ctx, 34960));
transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, ctx, 34950));
transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34940));
transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34930));
transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34920));
transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34910));
transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34900));
transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34890));
transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34880));
if (ctx.get_params().quantify_arrays()) {
transf.register_plugin(alloc(datalog::mk_quantifier_abstraction, ctx, 33000));
transf.register_plugin(alloc(datalog::mk_array_blast, ctx, 32500));
}
transf.register_plugin(alloc(datalog::mk_quantifier_instantiation, ctx, 32000));
transf.register_plugin(alloc(datalog::mk_bit_blast, ctx, 35000));
transf.register_plugin(alloc(datalog::mk_array_blast, ctx, 36000));
transf.register_plugin(alloc(datalog::mk_karr_invariants, ctx, 36010));
if (ctx.get_params().magic()) {
transf.register_plugin(alloc(datalog::mk_magic_symbolic, ctx, 36020));
}
transf.register_plugin(alloc(datalog::mk_scale, ctx, 36030));
ctx.transform_rules(transf);
}
}

View file

@ -0,0 +1,30 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
dl_transforms.h
Abstract:
Default transformations.
Author:
Nikolaj Bjorner (nbjorner) 2013-08-28.
Revision History:
Extracted from dl_context
--*/
#ifndef _DL_TRANSFORMS_H_
#define _DL_TRANSFORMS_H_
#include "dl_context.h"
namespace datalog {
void apply_default_transformation(context& ctx);
}
#endif