diff --git a/src/qe/qe_term_graph.cpp b/src/qe/qe_term_graph.cpp new file mode 100644 index 000000000..d0bcc11c4 --- /dev/null +++ b/src/qe/qe_term_graph.cpp @@ -0,0 +1,525 @@ +/**++ +Copyright (c) Arie Gurfinkel + +Module Name: + + qe_term_graph.cpp + +Abstract: + + Equivalence graph of terms + +Author: + + Arie Gurfinkel + +Notes: + +--*/ + +#include "util/util.h" +#include "ast/ast_pp.h" +#include "ast/ast_util.h" +#include "ast/for_each_expr.h" +#include "qe/qe_term_graph.h" + +namespace qe { + +class term { + // -- an app represented by this term + app* m_app; + // -- root of the equivalence class + term* m_root; + // -- next element in the equivalence class (cyclic linked list) + term* m_next; + // -- eq class size + unsigned m_class_size; + + // -- general purpose mark + unsigned m_mark:1; + // -- general purpose second mark + unsigned m_mark2:1; + // -- is an interpreted constant + unsigned m_interpreted:1; + + // -- terms that contain this term as a child + //ptr_vector m_uses; + + // ptr_vector m_args; + +public: + term(app* a) : m_app(a), m_root(this), m_next(this), + m_class_size(1), m_mark(false), m_mark2(false), + m_interpreted(false) {} + + ~term() {} + + unsigned get_id() const {return m_app->get_id();} + + bool is_marked() const {return m_mark;} + void set_mark(bool v){m_mark = v;} + bool is_marked2() const {return m_mark2;} + void set_mark2(bool v){m_mark2 = v;} + + bool is_interpreted() const {return m_interpreted;} + void mark_as_interpreted() {m_interpreted=true;} + app* get_app() const {return m_app;} + + term &get_root() const {return *m_root;} + bool is_root() const {return m_root == this;} + void set_root(term &r) {m_root = &r;} + term &get_next() const {return *m_next;} + + unsigned get_class_size() const {return m_class_size;} + + void merge_eq_class(term &b) { + std::swap(this->m_next, b.m_next); + m_class_size += b.get_class_size(); + // -- reset (useful for debugging) + b.m_class_size = 0; + } + + // -- make this term the root of its equivalence class + void mk_root() { + if (is_root()) return; + + term *curr = this; + do { + if (curr->is_root()) { + // found previous root + SASSERT(curr != this); + m_class_size = curr->get_class_size(); + curr->m_class_size = 0; + } + curr->set_root(*this); + curr = &curr->get_next(); + } + while (curr != this); + } +}; + +class arith_term_graph_plugin : public term_graph_plugin { + term_graph &m_g; + ast_manager &m; + arith_util m_arith; + +public: + arith_term_graph_plugin(term_graph &g) : + term_graph_plugin (g.get_ast_manager().mk_family_id("arith")), + m_g(g), m(g.get_ast_manager()), m_arith(m) {(void)m_g;} + + virtual ~arith_term_graph_plugin() {} + + bool mk_eq_core (expr *_e1, expr *_e2, app_ref &res) { + expr *e1, *e2; + e1 = _e1; + e2 = _e2; + + if (m_arith.is_zero(e1)) { + std::swap(e1, e2); + } + // y + -1*x == 0 --> y = x + expr *a0 = 0, *a1 = 0, *x = 0; + if (m_arith.is_zero(e2) && m_arith.is_add(e1, a0, a1)) { + if (m_arith.is_times_minus_one(a1, x)) { + e1 = a0; + e2 = x; + } + else if (m_arith.is_times_minus_one(a0, x)) { + e1 = a1; + e2 = x; + } + } + res = m.mk_eq(e1, e2); + return true; + } + + app* mk_le_zero(expr *arg) { + expr *e1, *e2, *e3; + // XXX currently disabled + if (m_arith.is_add(arg, e1, e2)) { + // e1-e2<=0 --> e1<=e2 + if (m_arith.is_times_minus_one(e2, e3)) { + return m_arith.mk_le(e1, e3); + } + // -e1+e2<=0 --> e2<=e1 + else if (m_arith.is_times_minus_one(e1, e3)) { + return m_arith.mk_le(e2, e3); + } + } + return m_arith.mk_le(arg, mk_zero()); + } + + app* mk_ge_zero(expr *arg) { + expr *e1, *e2, *e3; + // XXX currently disabled + if (m_arith.is_add(arg, e1, e2)) { + // e1-e2>=0 --> e1>=e2 + if (m_arith.is_times_minus_one(e2, e3)) { + return m_arith.mk_ge(e1, e3); + } + // -e1+e2>=0 --> e2>=e1 + else if (m_arith.is_times_minus_one(e1, e3)) { + return m_arith.mk_ge(e2, e3); + } + } + return m_arith.mk_ge(arg, mk_zero()); + } + + bool mk_le_core (expr *arg1, expr * arg2, app_ref &result) { + // t <= -1 ==> t < 0 ==> ! (t >= 0) + rational n; + if (m_arith.is_int (arg1) && m_arith.is_minus_one (arg2)) { + result = m.mk_not (mk_ge_zero (arg1)); + return true; + } + else if (m_arith.is_zero(arg2)) { + result = mk_le_zero(arg1); + return true; + } + else if (m_arith.is_int(arg1) && m_arith.is_numeral(arg2, n) && n < 0) { + // t <= n ==> t < n + 1 ==> ! (t >= n + 1) + result = m.mk_not(m_arith.mk_ge(arg1, m_arith.mk_numeral(n+1, true))); + return true; + } + return false; + } + + expr * mk_zero () {return m_arith.mk_numeral (rational (0), true);} + bool is_one (expr const * n) const { + rational val; + return m_arith.is_numeral (n, val) && val.is_one (); + } + + bool mk_ge_core (expr * arg1, expr * arg2, app_ref &result) { + // t >= 1 ==> t > 0 ==> ! (t <= 0) + rational n; + if (m_arith.is_int (arg1) && is_one (arg2)) { + result = m.mk_not (mk_le_zero (arg1)); + return true; + } + else if (m_arith.is_zero(arg2)) { + result = mk_ge_zero(arg1); + return true; + } + else if (m_arith.is_int(arg1) && m_arith.is_numeral(arg2, n) && n > 0) { + // t >= n ==> t > n - 1 ==> ! (t <= n - 1) + result = m.mk_not(m_arith.mk_le(arg1, m_arith.mk_numeral(n-1, true))); + return true; + } + return false; + } + + virtual app_ref process_lit (app *_lit) { + app *lit = _lit; + expr *e1, *e2; + + // strip negation + bool is_neg = m.is_not(lit); + if (is_neg) { + lit = to_app(to_app(lit)->get_arg(0)); + } + + app_ref res(m); + res = lit; + if (m.is_eq (lit, e1, e2)) { + mk_eq_core(e1, e2, res); + } + else if (m_arith.is_le(lit, e1, e2)) { + mk_le_core(e1, e2, res); + } + else if (m_arith.is_ge(lit, e1, e2)) { + mk_ge_core(e1, e2, res); + } + + // restore negation + if (is_neg) { + res = m.mk_not(res); + } + + return res; + } +}; + +term_graph::term_graph(ast_manager &man) : m(man), m_lits(m), m_pinned(m) { + m_plugins.register_plugin (alloc(arith_term_graph_plugin, *this)); +} +term_graph::~term_graph() { + reset(); +} + +static family_id get_family_id(ast_manager &m, app *lit) { + family_id fid = null_family_id; + + expr *e1, *e2, *e3; + // strip negation + if (!m.is_not (lit, e1)) { e1 = lit; } + + // deal with equality using sort of range + if (m.is_eq (e1, e2, e3)) { + fid = get_sort (e2)->get_family_id(); + } + // extract family_id of top level app + else { + fid = to_app(e1)->get_decl()->get_family_id(); + } + + return fid; +} + +void term_graph::add_lit(app *l) { + app_ref lit(m); + + family_id fid = get_family_id (m, l); + term_graph_plugin *pin = m_plugins.get_plugin(fid); + if (pin) { + lit = pin->process_lit(l); + } else { + lit = l; + } + m_lits.push_back(lit); + internalize_lit(lit); +} + +bool term_graph::is_internalized(app *a) { + return m_app2term.contains(a->get_id()); +} + +term* term_graph::get_term(app *a) { + term *res; + return m_app2term.find (a->get_id(), res) ? res : nullptr; +} + +term *term_graph::mk_term(app *a) { + term * t = alloc(term, a); + if (a->get_num_args() == 0 && m.is_unique_value(a)){ + t->mark_as_interpreted(); + } + + m_terms.push_back(t); + m_app2term.insert(a->get_id(), t); + return t; +} + +term *term_graph::internalize_term(app *t) { + term *res = get_term(t); + + if (!res) { + for (expr * arg : *t) { + SASSERT(is_app(arg)); + internalize_term(::to_app(arg)); + } + res = mk_term(t); + } + return res; +} + +void term_graph::internalize_eq(app *a1, app* a2) { + internalize_lit(a1); + internalize_lit(a2); + merge(get_term(a1)->get_root(), get_term(a2)->get_root()); +} + +void term_graph::internalize_lit(app* lit) { + if (is_internalized(lit)) return; + + expr *e1, *e2; + if (m.is_eq (lit, e1, e2)) { + SASSERT(is_app(e1)); + SASSERT(is_app(e2)); + internalize_eq (::to_app(e1), ::to_app(e2)); + } + else { + // NSB: this is thrown away. + // Is caller responsible for maintaining other predicates than equalities? + internalize_term(lit); + } +} + +void term_graph::merge (term &t1, term &t2) { + SASSERT(t1.is_root()); + SASSERT(t2.is_root()); + + if (&t1 == &t2) return; + + term *a = &t1; + term *b = &t2; + if (a->get_class_size() > b->get_class_size()) { + std::swap(a, b); + } + + // make 'a' be the root of the equivalence class of 'b' + b->set_root(*a); + for (term *it = &b->get_next(); it != b; it = &it->get_next()) { + it->set_root(*a); + } + + // merge equivalence classes + a->merge_eq_class(*b); + + // -- merge might have invalidated term2map cache + m_term2app.reset(); + m_pinned.reset(); +} + +app* term_graph::mk_app_core (app *a) { + expr_ref_vector kids(m); + for (expr * arg : *a) { + kids.push_back (mk_app(::to_app(arg))); + } + + app* res = m.mk_app(a->get_decl(), a->get_num_args(), kids.c_ptr()); + m_pinned.push_back(res); + + return res; +} + +app_ref term_graph::mk_app(term const &r) { + SASSERT(r.is_root()); + + if (r.get_app()->get_num_args() == 0) { + return app_ref(r.get_app(), m); + } + + app* res; + if (m_term2app.find(r.get_id(), res)) { + return app_ref(res, m); + } + + res = mk_app_core (r.get_app()); + m_term2app.insert(r.get_id(), res); + return app_ref(res, m); + +} +app_ref term_graph::mk_app(app *a) { + term *t = get_term(a); + if (!t) + return app_ref(a, m); + else + return mk_app(t->get_root()); + +} + +void term_graph::mk_equalities(term const &t, app_ref_vector &out) { + SASSERT(t.is_root()); + app_ref rep(mk_app(t), m); + + for (term *it = &t.get_next(); it != &t; it = &it->get_next()) { + app* mem = mk_app_core(it->get_app()); + out.push_back (m.mk_eq (rep, mem)); + } +} + +void term_graph::mk_all_equalities(term const &t, app_ref_vector &out) { + mk_equalities(t, out); + + for (term *it = &t.get_next(); it != &t; it = &it->get_next ()) { + app* a1 = mk_app_core (it->get_app()); + for (term *it2 = &it->get_next(); it2 != &t; it2 = &it2->get_next()) { + app *a2; + a2 = mk_app_core(it2->get_app()); + out.push_back (m.mk_eq (a1, a2)); + } + } +} + +void term_graph::reset_marks() { + for (term * t : m_terms) { + t->set_mark(false); + } +} + +/// Order of preference for roots of equivalence classes +/// XXX This should be factored out to let clients control the preference +bool term_graph::term_le(term const &t1, term const &t2) { + + // prefer constants over applications + // prefer uninterpreted constants over values + // prefer smaller expressions over larger ones + app *a1, *a2; + a1 = t1.get_app(); + a2 = t2.get_app(); + if (a1->get_num_args() == 0 && a2->get_num_args() > 0) { + return true; + } + // NSB: how does this possibly define an order? + if (a1->get_num_args() == a2->get_num_args()) { + return m.is_value(a2); + } + + unsigned sz1 = get_num_exprs(a1); + unsigned sz2 = get_num_exprs(a2); + return sz1 < sz2; +} + +void term_graph::pick_root (term &t) { + term *r = &t; + for (term *it = &t.get_next(); it != &t; it = &it->get_next()) { + it->set_mark(true); + if (term_le(*it, *r)) { r = it; } + } + + // -- if found something better, make it the new root + if (r != &t) { + r->mk_root(); + } +} +/// Choose better roots for equivalence classes +void term_graph::pick_roots() { + for (term* t : m_terms) { + if (!t->is_marked() && t->is_root()) + pick_root(*t); + } + reset_marks(); +} + +void term_graph::display(std::ostream &out) { + for (term * t : m_terms) { + out << mk_pp(t->get_app(), m) << " is root " << t->is_root() + << " cls sz " << t->get_class_size() + << " term " << t + << "\n"; + } +} +void term_graph::to_lits (app_ref_vector &lits, bool all_equalities) { + pick_roots(); + + for (app * a : m_lits) { + if (is_internalized(a)) { + lits.push_back (mk_app(a)); + } + } + + for (term * t : m_terms) { + if (!t->is_root()) + continue; + else if (all_equalities) + mk_all_equalities (*t, lits); + else + mk_equalities(*t, lits); + } +} +void term_graph::to_lits (expr_ref_vector &lits, bool all_equalities) { + app_ref_vector out(m); + to_lits (out, all_equalities); + for (app* a : out) { + lits.push_back(a); + } +} + +app_ref term_graph::to_app() { + app_ref_vector lits(m); + to_lits(lits); + return mk_and(lits); +} + +void term_graph::reset() { + m_term2app.reset(); + m_pinned.reset(); + m_app2term.reset(); + std::for_each(m_terms.begin(), m_terms.end(), delete_proc()); + m_terms.reset(); + m_lits.reset(); +} + +} diff --git a/src/qe/qe_term_graph.h b/src/qe/qe_term_graph.h new file mode 100644 index 000000000..76d793e59 --- /dev/null +++ b/src/qe/qe_term_graph.h @@ -0,0 +1,94 @@ +/**++ +Copyright (c) Arie Gurfinkel + +Module Name: + + qe_term_graph.h + +Abstract: + + Equivalence graph of terms + +Author: + + Arie Gurfinkel + +Notes: + +--*/ +#ifndef QE_TERM_GRAPH_H__ +#define QE_TERM_GRAPH_H__ + +#include "ast/ast.h" +#include "util/plugin_manager.h" + +namespace qe { + + class term; + + class term_graph_plugin { + family_id m_id; + public: + term_graph_plugin(family_id fid) : m_id(fid) {} + virtual ~term_graph_plugin() {} + + family_id get_family_id() const {return m_id;} + + /// Process (and potentially augment) a literal + virtual app_ref process_lit (app *lit) = 0; + }; + + class term_graph { + ast_manager &m; + ptr_vector m_terms; + app_ref_vector m_lits; + u_map m_app2term; + + app_ref_vector m_pinned; + u_map m_term2app; + + plugin_manager m_plugins; + + void merge(term &t1, term &t2); + + term *mk_term(app *t); + term *get_term(app *t); + + term *internalize_term(app *t); + void internalize_eq(app *a1, app *a2); + void internalize_lit(app *lit); + + bool is_internalized(app *a); + + bool term_le(term const &t1, term const &t2); + void pick_root (term &t); + void pick_roots(); + + void reset_marks(); + + app *mk_app_core(app* a); + app_ref mk_app(term const &t); + app_ref mk_app(app *a); + void mk_equalities(term const &t, app_ref_vector &out); + void mk_all_equalities(term const &t, app_ref_vector &out); + void display(std::ostream &out); + public: + term_graph(ast_manager &man); + ~term_graph(); + + ast_manager &get_ast_manager() const { return m;} + + void add_lit(app *lit); + void add_lits(expr_ref_vector const &lits) { + for (expr* e : lits) add_lit(::to_app(e)); + } + + void reset(); + void to_lits(app_ref_vector &lits, bool all_equalities = false); + void to_lits(expr_ref_vector &lits, bool all_equalities = false); + app_ref to_app(); + + }; + +} +#endif