diff --git a/src/model/model_core.h b/src/model/model_core.h index d705e432d..7f5051386 100644 --- a/src/model/model_core.h +++ b/src/model/model_core.h @@ -48,6 +48,14 @@ public: func_interp * get_func_interp(func_decl * d) const { func_interp * fi; return m_finterp.find(d, fi) ? fi : nullptr; } bool eval(func_decl * f, expr_ref & r) const; + bool is_true_decl(func_decl *f) const { + expr_ref r(m); + return eval(f, r) && m.is_true(r); + } + bool is_false_decl(func_decl *f) const { + expr_ref r(m); + return eval(f, r) && m.is_false(r); + } unsigned get_num_constants() const { return m_const_decls.size(); } unsigned get_num_functions() const { return m_func_decls.size(); } diff --git a/src/muz/base/fp_params.pyg b/src/muz/base/fp_params.pyg index c96a12ca2..31ab3a358 100644 --- a/src/muz/base/fp_params.pyg +++ b/src/muz/base/fp_params.pyg @@ -160,7 +160,6 @@ def_module_params('fp', '2 = use Gaussian elimination optimization (broken), 3 = use additive IUC plugin'), ('spacer.iuc.old_hyp_reducer', BOOL, False, 'use old hyp reducer instead of new implementation, for debugging only'), ('spacer.validate_lemmas', BOOL, False, 'Validate each lemma after generalization'), - ('spacer.reuse_pobs', BOOL, True, 'Reuse pobs'), ('spacer.ground_pobs', BOOL, True, 'Ground pobs by using values from a model'), ('spacer.iuc.print_farkas_stats', BOOL, False, 'prints for each proof how many Farkas lemmas it contains and how many of these participate in the cut (for debugging)'), ('spacer.iuc.debug_proof', BOOL, False, 'prints proof used by unsat-core-learner for debugging purposes (debugging)'), diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 40569295b..b034a8fd5 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -55,6 +55,8 @@ Notes: #include "muz/spacer/spacer_sat_answer.h" +#define WEAKNESS_MAX 65535 + namespace spacer { /// pob -- proof obligation @@ -66,8 +68,8 @@ pob::pob (pob* parent, pred_transformer& pt, m_binding(m_pt.get_ast_manager()), m_new_post (m_pt.get_ast_manager ()), m_level (level), m_depth (depth), - m_open (true), m_use_farkas (true), m_weakness(0), - m_blocked_lvl(0) { + m_open (true), m_use_farkas (true), m_in_queue(false), + m_weakness(0), m_blocked_lvl(0) { if (add_to_parent && m_parent) { m_parent->add_child(*this); } @@ -79,6 +81,7 @@ void pob::set_post(expr* post) { } void pob::set_post(expr* post, app_ref_vector const &binding) { + SASSERT(!is_in_queue()); normalize(post, m_post, m_pt.get_context().simplify_pob(), m_pt.get_context().use_euf_gen()); @@ -88,6 +91,7 @@ void pob::set_post(expr* post, app_ref_vector const &binding) { } void pob::inherit(pob const &p) { + SASSERT(!is_in_queue()); SASSERT(m_parent == p.m_parent); SASSERT(&m_pt == &p.m_pt); SASSERT(m_post == p.m_post); @@ -105,17 +109,10 @@ void pob::inherit(pob const &p) { m_derivation = nullptr; } -void pob::clean () { - if (m_new_post) { - m_post = m_new_post; - m_new_post.reset(); - } -} - void pob::close () { if (!m_open) { return; } - reset (); + m_derivation = nullptr; m_open = false; for (unsigned i = 0, sz = m_kids.size (); i < sz; ++i) { m_kids [i]->close(); } @@ -137,17 +134,24 @@ void pob::get_skolems(app_ref_vector &v) { pob* pob_queue::top () { /// nothing in the queue - if (m_obligations.empty()) { return nullptr; } + if (m_data.empty()) { return nullptr; } /// top queue element is above max level - if (m_obligations.top()->level() > m_max_level) { return nullptr; } + if (m_data.top()->level() > m_max_level) { return nullptr; } /// top queue element is at the max level, but at a higher than base depth - if (m_obligations.top ()->level () == m_max_level && - m_obligations.top()->depth() > m_min_depth) { return nullptr; } + if (m_data.top ()->level () == m_max_level && + m_data.top()->depth() > m_min_depth) { return nullptr; } /// there is something good in the queue - return m_obligations.top ().get (); + return m_data.top (); } +void pob_queue::pop() { + pob *p = m_data.top(); + p->set_in_queue(false); + m_data.pop(); +} + + void pob_queue::set_root(pob& root) { m_root = &root; @@ -156,19 +160,28 @@ void pob_queue::set_root(pob& root) reset(); } -pob_queue::~pob_queue() {} - void pob_queue::reset() { - while (!m_obligations.empty()) { m_obligations.pop(); } - if (m_root) { m_obligations.push(m_root); } + while (!m_data.empty()) { + pob *p = m_data.top(); + m_data.pop(); + p->set_in_queue(false); + } + if (m_root) { + SASSERT(!m_root->is_in_queue()); + m_root->set_in_queue(true); + m_data.push(m_root.get()); + } } void pob_queue::push(pob &n) { - TRACE("pob_queue", - tout << "pob_queue::push(" << n.post()->get_id() << ")\n";); - m_obligations.push (&n); - n.get_context().new_pob_eh(&n); + if (!n.is_in_queue()) { + TRACE("pob_queue", + tout << "pob_queue::push(" << n.post()->get_id() << ")\n";); + n.set_in_queue(true); + m_data.push (&n); + n.get_context().new_pob_eh(&n); + } } // ---------------- @@ -266,6 +279,8 @@ pob *derivation::create_next_child(model &mdl) m_evars.reset(); pt().mbp(vars, m_trans, mdl, true, pt().get_context().use_ground_pob()); + CTRACE("spacer", !vars.empty(), + tout << "Failed to eliminate: " << vars << "\n";); m_evars.append (vars); vars.reset(); } @@ -295,6 +310,8 @@ pob *derivation::create_next_child(model &mdl) vars.append(m_evars); pt().mbp(vars, post, mdl, true, pt().get_context().use_ground_pob()); + CTRACE("spacer", !vars.empty(), + tout << "Failed to eliminate: " << vars << "\n";); //qe::reduce_array_selects (*mev.get_model (), post); } else { @@ -398,6 +415,8 @@ pob *derivation::create_next_child () this->pt().mbp(vars, m_trans, *mdl, true, this->pt().get_context().use_ground_pob()); // keep track of implicitly quantified variables + CTRACE("spacer", !vars.empty(), + tout << "Failed to eliminate: " << vars << "\n";); m_evars.append (vars); vars.reset(); } @@ -462,8 +481,11 @@ void derivation::premise::set_summary (expr * summary, bool must, lemma::lemma (ast_manager &manager, expr * body, unsigned lvl) : m_ref_count(0), m(manager), m_body(body, m), m_cube(m), - m_zks(m), m_bindings(m), m_lvl(lvl), m_init_lvl(m_lvl), - m_pob(nullptr), m_ctp(nullptr), m_external(false), m_bumped(0) { + m_zks(m), m_bindings(m), + m_pob(nullptr), m_ctp(nullptr), + m_lvl(lvl), m_init_lvl(m_lvl), + m_bumped(0), m_weakness(WEAKNESS_MAX), + m_external(false), m_blocked(false) { SASSERT(m_body); normalize(m_body, m_body); } @@ -471,8 +493,11 @@ lemma::lemma (ast_manager &manager, expr * body, unsigned lvl) : lemma::lemma(pob_ref const &p) : m_ref_count(0), m(p->get_ast_manager()), m_body(m), m_cube(m), - m_zks(m), m_bindings(m), m_lvl(p->level()), m_init_lvl(m_lvl), - m_pob(p), m_ctp(nullptr), m_external(false), m_bumped(0) { + m_zks(m), m_bindings(m), + m_pob(p), m_ctp(nullptr), + m_lvl(p->level()), m_init_lvl(m_lvl), + m_bumped(0), m_weakness(p->weakness()), + m_external(false), m_blocked(false) { SASSERT(m_pob); m_pob->get_skolems(m_zks); add_binding(m_pob->get_binding()); @@ -482,8 +507,11 @@ lemma::lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl) : m_ref_count(0), m(p->get_ast_manager()), m_body(m), m_cube(m), - m_zks(m), m_bindings(m), m_lvl(p->level()), m_init_lvl(m_lvl), - m_pob(p), m_ctp(nullptr), m_external(false), m_bumped(0) + m_zks(m), m_bindings(m), + m_pob(p), m_ctp(nullptr), + m_lvl(p->level()), m_init_lvl(m_lvl), + m_bumped(0), m_weakness(p->weakness()), + m_external(false), m_blocked(false) { if (m_pob) { m_pob->get_skolems(m_zks); @@ -820,7 +848,7 @@ const datalog::rule *pred_transformer::find_rule(model &model) { for (auto &kv : m_pt_rules) { app *tag = kv.m_value->tag(); - if (model.eval(tag->get_decl(), val) && m.is_true(val)) { + if (model.is_true_decl(tag->get_decl())) { return &kv.m_value->rule(); } } @@ -1162,7 +1190,16 @@ expr_ref pred_transformer::get_origin_summary (model &mdl, } // bail out of if the model is insufficient - if (!mdl.is_true(summary)) return expr_ref(m); + // (skip quantified lemmas cause we can't validate them in the model) + // TBD: for quantified lemmas use current instances + flatten_and(summary); + for (auto *s : summary) { + if (!is_quantifier(s) && !mdl.is_true(s)) { + TRACE("spacer", tout << "Summary not true in the model: " + << mk_pp(s, m) << "\n";); + return expr_ref(m); + } + } // -- pick an implicant expr_ref_vector lits(m); @@ -1347,7 +1384,7 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, if (is_sat == l_true || is_sat == l_undef) { if (core) { core->reset(); } - if (model) { + if (model && model->get()) { r = find_rule(**model, is_concrete, reach_pred_used, num_reuse_reach); TRACE ("spacer", tout << "reachable " << "is_concrete " << is_concrete << " rused: "; @@ -1387,7 +1424,12 @@ bool pred_transformer::is_ctp_blocked(lemma *lem) { // -- find rule of the ctp const datalog::rule *r; r = find_rule(*ctp); - if (r == nullptr) {return false;} + if (r == nullptr) { + // no rules means lemma is blocked forever because + // it does not satisfy some derived facts + lem->set_blocked(true); + return true; + } // -- find predicates along the rule find_predecessors(*r, m_predicates); @@ -1410,6 +1452,8 @@ bool pred_transformer::is_invariant(unsigned level, lemma* lem, unsigned& solver_level, expr_ref_vector* core) { + if (lem->is_blocked()) return false; + m_stats.m_num_is_invariant++; if (is_ctp_blocked(lem)) { m_stats.m_num_ctp_blocked++; @@ -1451,7 +1495,7 @@ bool pred_transformer::is_invariant(unsigned level, lemma* lem, SASSERT (level <= solver_level); } else if (r == l_true) { - // optionally remove unused symbols from the model + // TBD: optionally remove unused symbols from the model if (mdl_ref_ptr) {lem->set_ctp(*mdl_ref_ptr);} } else {lem->reset_ctp();} @@ -2120,16 +2164,10 @@ void pred_transformer::frames::simplify_formulas () /// pred_transformer::pobs -pob* pred_transformer::pobs::mk_pob(pob *parent, - unsigned level, unsigned depth, - expr *post, app_ref_vector const &b) { - - if (!m_pt.ctx.reuse_pobs()) { - pob* n = alloc(pob, parent, m_pt, level, depth); - n->set_post(post, b); - return n; - } - +pob* pred_transformer::pob_manager::mk_pob(pob *parent, + unsigned level, unsigned depth, + expr *post, + app_ref_vector const &b) { // create a new pob and set its post to normalize it pob p(parent, m_pt, level, depth, false); p.set_post(post, b); @@ -2138,7 +2176,7 @@ pob* pred_transformer::pobs::mk_pob(pob *parent, auto &buf = m_pobs[p.post()]; for (unsigned i = 0, sz = buf.size(); i < sz; ++i) { pob *f = buf.get(i); - if (f->parent() == parent) { + if (f->parent() == parent && !f->is_in_queue()) { f->inherit(p); return f; } @@ -2160,7 +2198,21 @@ pob* pred_transformer::pobs::mk_pob(pob *parent, return n; } - +pob* pred_transformer::pob_manager::find_pob(pob* parent, expr *post) { + pob p(parent, m_pt, 0, 0, false); + p.set_post(post); + pob *res = nullptr; + if (m_pobs.contains(p.post())) { + for (auto *f : m_pobs[p.post()]) { + if (f->parent() == parent) { + // try to find pob not already in queue + if (!f->is_in_queue()) return f; + res = f; + } + } + } + return res; +} // ---------------- @@ -2207,7 +2259,6 @@ void context::updt_params() { m_use_ctp = m_params.spacer_ctp(); m_use_inc_clause = m_params.spacer_use_inc_clause(); m_blast_term_ite = m_params.spacer_blast_term_ite(); - m_reuse_pobs = m_params.spacer_reuse_pobs(); m_use_ind_gen = m_params.spacer_use_inductive_generalizer(); m_use_array_eq_gen = m_params.spacer_use_array_eq_generalizer(); m_validate_lemmas = m_params.spacer_validate_lemmas(); @@ -3055,27 +3106,19 @@ bool context::check_reachability () } SASSERT (m_pob_queue.top ()); - // -- remove all closed nodes and updated all dirty nodes + // -- remove all closed nodes // -- this is necessary because there is no easy way to // -- remove nodes from the priority queue. - while (m_pob_queue.top ()->is_closed () || - m_pob_queue.top()->is_dirty()) { - pob_ref n = m_pob_queue.top (); - m_pob_queue.pop (); - if (n->is_closed()) { - IF_VERBOSE (1, - verbose_stream () << "Deleting closed node: " - << n->pt ().head ()->get_name () - << "(" << n->level () << ", " << n->depth () << ")" - << " " << n->post ()->get_id () << "\n";); - if (m_pob_queue.is_root(*n)) { return true; } - SASSERT (m_pob_queue.top ()); - } else if (n->is_dirty()) { - n->clean (); - // -- the node n might not be at the top after it is cleaned - m_pob_queue.push (*n); - } else - { UNREACHABLE(); } + while (m_pob_queue.top ()->is_closed ()) { + pob_ref n = m_pob_queue.top(); + m_pob_queue.pop(); + IF_VERBOSE (1, + verbose_stream () << "Deleting closed node: " + << n->pt ().head ()->get_name () + << "(" << n->level () << ", " << n->depth () << ")" + << " " << n->post ()->get_id () << "\n";); + if (m_pob_queue.is_root(*n)) {return true;} + SASSERT (m_pob_queue.top ()); } SASSERT (m_pob_queue.top ()); @@ -3169,6 +3212,7 @@ bool context::is_reachable(pob &n) unsigned num_reuse_reach = 0; unsigned saved = n.level (); + // TBD: don't expose private field n.m_level = infty_level (); lbool res = n.pt().is_reachable(n, nullptr, &mdl, uses_level, is_concrete, r, @@ -3413,10 +3457,21 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) // Optionally update the node to be the negation of the lemma if (v && m_use_lemma_as_pob) { - n.new_post (mk_and(lemma->get_cube())); - n.set_farkas_generalizer (false); - // XXX hack while refactoring is in progress - n.clean(); + expr_ref c(m); + c = mk_and(lemma->get_cube()); + // check that the post condition is different + if (c != n.post()) { + pob *f = n.pt().find_pob(n.parent(), c); + // skip if a similar pob is already in the queue + if (f != &n && (!f || !f->is_in_queue())) { + f = n.pt().mk_pob(n.parent(), n.level(), + n.depth(), c, n.get_binding()); + SASSERT(!f->is_in_queue()); + f->inc_level(); + //f->set_farkas_generalizer(false); + out.push_back(f); + } + } } // schedule the node to be placed back in the queue @@ -3434,7 +3489,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) } case l_undef: // something went wrong - if (n.weakness() < 100 /* MAX_WEAKENSS */) { + if (n.weakness() < 10 /* MAX_WEAKENSS */) { bool has_new_child = false; SASSERT(m_weak_abs); m_stats.m_expand_pob_undef++; @@ -3932,7 +3987,7 @@ bool context::is_inductive() { } /// pob_lt operator -inline bool pob_lt::operator() (const pob *pn1, const pob *pn2) const +inline bool pob_lt_proc::operator() (const pob *pn1, const pob *pn2) const { SASSERT (pn1); SASSERT (pn2); diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 37b035b98..93c2337e6 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -122,12 +122,14 @@ class lemma { expr_ref_vector m_cube; app_ref_vector m_zks; app_ref_vector m_bindings; + pob_ref m_pob; + model_ref m_ctp; // counter-example to pushing unsigned m_lvl; // current level of the lemma unsigned m_init_lvl; // level at which lemma was created - pob_ref m_pob; - model_ref m_ctp; // counter-example to pushing - bool m_external; - unsigned m_bumped; + unsigned m_bumped:16; + unsigned m_weakness:16; + unsigned m_external:1; + unsigned m_blocked:1; void mk_expr_core(); void mk_cube_core(); @@ -154,12 +156,15 @@ public: bool has_pob() {return m_pob;} pob_ref &get_pob() {return m_pob;} - inline unsigned weakness(); + unsigned weakness() {return m_weakness;} void add_skolem(app *zk, app* b); - inline void set_external(bool ext){m_external = ext;} - inline bool external() { return m_external;} + void set_external(bool ext){m_external = ext;} + bool external() { return m_external;} + + bool is_blocked() {return m_blocked;} + void set_blocked(bool v) {m_blocked=v;} bool is_inductive() const {return is_infty_level(m_lvl);} unsigned level () const {return m_lvl;} @@ -268,18 +273,31 @@ class pred_transformer { }; /** - manager of proof-obligations (pobs) + manager of proof-obligations (pob_manager) + + Pobs are determined uniquely by their post-condition and a parent pob. + They are managed by pob_manager and remain live through the + life of the manager */ - class pobs { + class pob_manager { + // a buffer that contains space for one pob and allocates more + // space if needed typedef ptr_buffer pob_buffer; + // Type for the map from post-conditions to pobs. The common + // case is that each post-condition corresponds to a single + // pob. Other cases are handled by expanding the buffer typedef obj_map expr2pob_buffer; + // parent predicate transformer pred_transformer &m_pt; + // map from post-conditions to pobs expr2pob_buffer m_pobs; + + // a store pob_ref_vector m_pinned; public: - pobs(pred_transformer &pt) : m_pt(pt) {} + pob_manager(pred_transformer &pt) : m_pt(pt) {} pob* mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, app_ref_vector const &b); @@ -290,6 +308,7 @@ class pred_transformer { } unsigned size() const {return m_pinned.size();} + pob* find_pob(pob* parent, expr *post); }; class pt_rule { @@ -361,7 +380,7 @@ class pred_transformer { ptr_vector m_rules; // rules used to derive transformer scoped_ptr m_solver; // solver context ref m_reach_solver; // context for reachability facts - pobs m_pobs; // proof obligations created so far + pob_manager m_pobs; // proof obligations created so far frames m_frames; // frames with lemmas reach_fact_ref_vector m_reach_facts; // reach facts unsigned m_rf_init_sz; // number of reach fact from INIT @@ -481,6 +500,9 @@ public: expr *post, app_ref_vector const &b){ return m_pobs.mk_pob(parent, level, depth, post, b); } + pob* find_pob(pob *parent, expr *post) { + return m_pobs.find_pob(parent, post); + } pob* mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) { @@ -548,6 +570,7 @@ public: * A proof obligation. */ class pob { + // TBD: remove this friend class context; unsigned m_ref_count; /// parent node @@ -562,14 +585,15 @@ class pob { /// new post to be swapped in for m_post expr_ref m_new_post; /// level at which to decide the post - unsigned m_level; - - unsigned m_depth; + unsigned m_level:16; + unsigned m_depth:16; /// whether a concrete answer to the post is found - bool m_open; + unsigned m_open:1; /// whether to use farkas generalizer to construct a lemma blocking this node - bool m_use_farkas; + unsigned m_use_farkas:1; + /// true if this pob is in pob_queue + unsigned m_in_queue:1; unsigned m_weakness; /// derivation representing the position of this node in the parent's rule @@ -584,17 +608,25 @@ class pob { // depth -> watch std::map m_expand_watches; unsigned m_blocked_lvl; + public: pob (pob* parent, pred_transformer& pt, unsigned level, unsigned depth=0, bool add_to_parent=true); ~pob() {if (m_parent) { m_parent->erase_child(*this); }} + // TBD: move into constructor and make private + void set_post(expr *post, app_ref_vector const &binding); + void set_post(expr *post); + unsigned weakness() {return m_weakness;} void bump_weakness() {m_weakness++;} void reset_weakness() {m_weakness=0;} - void inc_level () {m_level++; m_depth++;reset_weakness();} + void inc_level () { + SASSERT(!is_in_queue()); + m_level++; m_depth++;reset_weakness(); + } void inherit(pob const &p); void set_derivation (derivation *d) {m_derivation = d;} @@ -614,32 +646,25 @@ public: unsigned level () const { return m_level; } unsigned depth () const {return m_depth;} unsigned width () const {return m_kids.size();} - unsigned blocked_at(unsigned lvl=0){return (m_blocked_lvl = std::max(lvl, m_blocked_lvl)); } + unsigned blocked_at(unsigned lvl=0){ + return (m_blocked_lvl = std::max(lvl, m_blocked_lvl)); + } + bool is_in_queue() const {return m_in_queue;} + void set_in_queue(bool v) {m_in_queue = v;} bool use_farkas_generalizer () const {return m_use_farkas;} void set_farkas_generalizer (bool v) {m_use_farkas = v;} expr* post() const { return m_post.get (); } - void set_post(expr *post); - void set_post(expr *post, app_ref_vector const &binding); - - /// indicate that a new post should be set for the node - void new_post(expr *post) {if (post != m_post) {m_new_post = post;}} - /// true if the node needs to be updated outside of the priority queue - bool is_dirty () {return m_new_post;} - /// clean a dirty node - void clean(); - - void reset () {clean (); m_derivation = nullptr; m_open = true;} bool is_closed () const { return !m_open; } void close(); - const ptr_vector &children() {return m_kids;} + const ptr_vector &children() const {return m_kids;} void add_child (pob &v) {m_kids.push_back (&v);} void erase_child (pob &v) {m_kids.erase (&v);} - const ptr_vector &lemmas() {return m_lemmas;} + const ptr_vector &lemmas() const {return m_lemmas;} void add_lemma(lemma* new_lemma) {m_lemmas.push_back(new_lemma);} bool is_ground () const { return m_binding.empty (); } @@ -652,8 +677,14 @@ public: */ void get_skolems(app_ref_vector& v); - void on_expand() { m_expand_watches[m_depth].start(); if (m_parent.get()){m_parent.get()->on_expand();} } - void off_expand() { m_expand_watches[m_depth].stop(); if (m_parent.get()){m_parent.get()->off_expand();} }; + void on_expand() { + m_expand_watches[m_depth].start(); + if (m_parent.get()){m_parent.get()->on_expand();} + } + void off_expand() { + m_expand_watches[m_depth].stop(); + if (m_parent.get()){m_parent.get()->off_expand();} + } double get_expand_time(unsigned depth) { return m_expand_watches[depth].get_seconds();} void inc_ref () {++m_ref_count;} @@ -672,25 +703,16 @@ public: }; -struct pob_lt : - public std::binary_function -{bool operator() (const pob *pn1, const pob *pn2) const;}; - -struct pob_gt : - public std::binary_function { - pob_lt lt; - bool operator() (const pob *n1, const pob *n2) const - {return lt(n2, n1);} +struct pob_lt_proc : public std::binary_function { + bool operator() (const pob *pn1, const pob *pn2) const; }; -struct pob_ref_gt : - public std::binary_function { - pob_gt gt; - bool operator() (const pob_ref &n1, const pob_ref &n2) const - {return gt (n1.get (), n2.get ());} +struct pob_gt_proc : public std::binary_function { + bool operator() (const pob *n1, const pob *n2) const { + return pob_lt_proc()(n2, n1); + } }; -inline unsigned lemma::weakness() {return m_pob ? m_pob->weakness() : UINT_MAX;} /** */ class derivation { @@ -767,36 +789,41 @@ public: class pob_queue { + + typedef std::priority_queue, pob_gt_proc> pob_queue_ty; pob_ref m_root; unsigned m_max_level; unsigned m_min_depth; - std::priority_queue, - pob_ref_gt> m_obligations; + pob_queue_ty m_data; public: pob_queue(): m_root(nullptr), m_max_level(0), m_min_depth(0) {} - ~pob_queue(); + ~pob_queue() {} void reset(); - pob * top (); - void pop () {m_obligations.pop ();} + pob* top(); + void pop(); void push (pob &n); void inc_level () { - SASSERT (!m_obligations.empty () || m_root); + SASSERT (!m_data.empty () || m_root); m_max_level++; m_min_depth++; - if (m_root && m_obligations.empty()) { m_obligations.push(m_root); } + if (m_root && m_data.empty()) { + SASSERT(!m_root->is_in_queue()); + m_root->set_in_queue(true); + m_data.push(m_root.get()); + } } pob& get_root() const {return *m_root.get ();} void set_root(pob& n); - bool is_root (pob& n) const {return m_root.get () == &n;} + bool is_root(pob& n) const {return m_root.get () == &n;} unsigned max_level() const {return m_max_level;} unsigned min_depth() const {return m_min_depth;} - size_t size() const {return m_obligations.size();} + size_t size() const {return m_data.size();} }; @@ -910,7 +937,6 @@ class context { bool m_use_ctp; bool m_use_inc_clause; bool m_blast_term_ite; - bool m_reuse_pobs; bool m_use_ind_gen; bool m_use_array_eq_gen; bool m_validate_lemmas; @@ -1007,7 +1033,6 @@ public: bool use_ctp() {return m_use_ctp;} bool use_inc_clause() {return m_use_inc_clause;} bool blast_term_ite() {return m_blast_term_ite;} - bool reuse_pobs() {return m_reuse_pobs;} bool elim_aux() {return m_elim_aux;} bool reach_dnf() {return m_reach_dnf;} diff --git a/src/muz/spacer/spacer_iuc_solver.cpp b/src/muz/spacer/spacer_iuc_solver.cpp index a68db4c0a..27b8ee357 100644 --- a/src/muz/spacer/spacer_iuc_solver.cpp +++ b/src/muz/spacer/spacer_iuc_solver.cpp @@ -322,6 +322,24 @@ void iuc_solver::get_iuc(expr_ref_vector &core) // -- new hypothesis reducer else { +#if 0 + static unsigned bcnt = 0; + { + bcnt++; + TRACE("spacer", tout << "Dumping pf bcnt: " << bcnt << "\n";); + if (bcnt == 123) { + std::ofstream ofs; + ofs.open("/tmp/bpf_" + std::to_string(bcnt) + ".dot"); + iuc_proof iuc_pf_before(m, res.get(), core_lits); + iuc_pf_before.display_dot(ofs); + ofs.close(); + + proof_checker pc(m); + expr_ref_vector side(m); + ENSURE(pc.check(res, side)); + } + } +#endif scoped_watch _t_ (m_hyp_reduce2_sw); // pre-process proof for better iuc extraction @@ -356,6 +374,22 @@ void iuc_solver::get_iuc(expr_ref_vector &core) iuc_proof iuc_pf(m, res, core_lits); +#if 0 + static unsigned cnt = 0; + { + cnt++; + TRACE("spacer", tout << "Dumping pf cnt: " << cnt << "\n";); + if (cnt == 123) { + std::ofstream ofs; + ofs.open("/tmp/pf_" + std::to_string(cnt) + ".dot"); + iuc_pf.display_dot(ofs); + ofs.close(); + proof_checker pc(m); + expr_ref_vector side(m); + ENSURE(pc.check(res, side)); + } + } +#endif unsat_core_learner learner(m, iuc_pf); unsat_core_plugin* plugin; diff --git a/src/muz/spacer/spacer_quant_generalizer.cpp b/src/muz/spacer/spacer_quant_generalizer.cpp index f66fe7b29..95e884d42 100644 --- a/src/muz/spacer/spacer_quant_generalizer.cpp +++ b/src/muz/spacer/spacer_quant_generalizer.cpp @@ -114,7 +114,7 @@ void lemma_quantifier_generalizer::find_candidates(expr *e, if (!contains_selects(e, m)) return; app_ref_vector indices(m); - get_select_indices(e, indices, m); + get_select_indices(e, indices); app_ref_vector extra(m); expr_sparse_mark marked_args; @@ -155,7 +155,7 @@ bool lemma_quantifier_generalizer::match_sk_idx(expr *e, app_ref_vector const &z if (!contains_selects(e, m)) return false; app_ref_vector indicies(m); - get_select_indices(e, indicies, m); + get_select_indices(e, indicies); if (indicies.size() > 2) return false; unsigned i=0; @@ -255,15 +255,17 @@ void lemma_quantifier_generalizer::cleanup(expr_ref_vector &cube, app_ref_vector lb and ub are null if no bound was found */ -void lemma_quantifier_generalizer::mk_abs_cube(lemma_ref &lemma, app *term, var *var, +void lemma_quantifier_generalizer::mk_abs_cube(lemma_ref &lemma, app *term, + var *var, expr_ref_vector &gnd_cube, expr_ref_vector &abs_cube, - expr *&lb, expr *&ub, unsigned &stride) { + expr *&lb, expr *&ub, + unsigned &stride) { // create an abstraction function that maps candidate term to variables expr_safe_replace sub(m); // term -> var - sub.insert(term , var); + sub.insert(term, var); rational val; if (m_arith.is_numeral(term, val)) { bool is_int = val.is_int(); @@ -285,19 +287,17 @@ void lemma_quantifier_generalizer::mk_abs_cube(lemma_ref &lemma, app *term, var for (expr *lit : m_cube) { expr_ref abs_lit(m); - sub (lit, abs_lit); - if (lit == abs_lit) { - gnd_cube.push_back(lit); - } + sub(lit, abs_lit); + if (lit == abs_lit) {gnd_cube.push_back(lit);} else { expr *e1, *e2; // generalize v=num into v>=num if (m.is_eq(abs_lit, e1, e2) && (e1 == var || e2 == var)) { - if (m_arith.is_numeral(e1)) { - abs_lit = m_arith.mk_ge (var, e1); + if (m_arith.is_numeral(e1)) { + abs_lit = m_arith.mk_ge(var, e1); } else if (m_arith.is_numeral(e2)) { abs_lit = m_arith.mk_ge(var, e2); - } + } } abs_cube.push_back(abs_lit); if (contains_selects(abs_lit, m)) { @@ -464,7 +464,7 @@ bool lemma_quantifier_generalizer::generalize (lemma_ref &lemma, app *term) { if (ub) tout << mk_pp(ub, m); else tout << "none"; tout << "\n";); - if (!lb && !ub) + if (!lb && !ub) return false; // -- guess lower or upper bound if missing @@ -542,46 +542,50 @@ bool lemma_quantifier_generalizer::generalize (lemma_ref &lemma, app *term) { return false; } -bool lemma_quantifier_generalizer::find_stride(expr_ref_vector &c, expr_ref &pattern, unsigned &stride) { +bool lemma_quantifier_generalizer::find_stride(expr_ref_vector &cube, + expr_ref &pattern, + unsigned &stride) { expr_ref tmp(m); - tmp = mk_and(c); + tmp = mk_and(cube); normalize(tmp, tmp, false, true); - c.reset(); - flatten_and(tmp, c); + cube.reset(); + flatten_and(tmp, cube); app_ref_vector indices(m); - get_select_indices(pattern, indices, m); + get_select_indices(pattern, indices); - // TODO - if (indices.size() > 1) - return false; + CTRACE("spacer_qgen", indices.empty(), + tout << "Found no select indices in: " << pattern << "\n";); + + // TBD: handle multi-dimensional arrays and literals with multiple + // select terms + if (indices.size() != 1) return false; app *p_index = indices.get(0); - if (is_var(p_index)) return false; - std::vector instances; - for (expr* lit : c) { + unsigned_vector instances; + for (expr* lit : cube) { if (!contains_selects(lit, m)) continue; indices.reset(); - get_select_indices(lit, indices, m); + get_select_indices(lit, indices); - // TODO: - if (indices.size() > 1) + // TBD: handle multi-dimensional arrays + if (indices.size() != 1) continue; app *candidate = indices.get(0); unsigned size = p_index->get_num_args(); unsigned matched = 0; - for (unsigned p=0; p < size; p++) { + for (unsigned p = 0; p < size; p++) { expr *arg = p_index->get_arg(p); if (is_var(arg)) { rational val; - if (p < candidate->get_num_args() && - m_arith.is_numeral(candidate->get_arg(p), val) && + if (p < candidate->get_num_args() && + m_arith.is_numeral(candidate->get_arg(p), val) && val.is_unsigned()) { instances.push_back(val.get_unsigned()); } diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index 42b40c665..3ea0917b7 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -900,17 +900,22 @@ namespace { struct collect_indices { app_ref_vector& m_indices; array_util a; - collect_indices(app_ref_vector& indices): m_indices(indices), a(indices.get_manager()) {} + collect_indices(app_ref_vector& indices): m_indices(indices), + a(indices.get_manager()) {} void operator()(expr* n) {} void operator()(app* n) { - if (a.is_select(n)) - for (unsigned i = 1; i < n->get_num_args(); ++i) - if (is_app(n->get_arg(i))) - m_indices.push_back(to_app(n->get_arg(i))); + if (a.is_select(n)) { + // for all but first argument + for (unsigned i = 1; i < n->get_num_args(); ++i) { + expr *arg = n->get_arg(i); + if (is_app(arg)) + m_indices.push_back(to_app(arg)); + } + } } }; - void get_select_indices(expr* fml, app_ref_vector &indices, ast_manager& m) { + void get_select_indices(expr* fml, app_ref_vector &indices) { collect_indices ci(indices); for_each_expr(ci, fml); } diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h index 9912769c5..8da574d0e 100644 --- a/src/muz/spacer/spacer_util.h +++ b/src/muz/spacer/spacer_util.h @@ -121,7 +121,7 @@ namespace spacer { void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml); bool contains_selects (expr* fml, ast_manager& m); - void get_select_indices (expr* fml, app_ref_vector& indices, ast_manager& m); + void get_select_indices (expr* fml, app_ref_vector& indices); void find_decls (expr* fml, app_ref_vector& decls, std::string& prefix); diff --git a/src/qe/qe_term_graph.cpp b/src/qe/qe_term_graph.cpp index 5cf1206a3..e9ec3d4b7 100644 --- a/src/qe/qe_term_graph.cpp +++ b/src/qe/qe_term_graph.cpp @@ -200,7 +200,8 @@ namespace qe { } std::ostream& display(std::ostream& out) const { - out << get_id() << ": " << m_expr << " - "; + out << get_id() << ": " << m_expr + << (is_root() ? " R" : "") << " - "; term const* r = &this->get_next(); while (r != this) { out << r->get_id() << " "; @@ -371,20 +372,20 @@ namespace qe { } void term_graph::merge(term &t1, term &t2) { - // -- merge might invalidate term2app cache - m_term2app.reset(); - m_pinned.reset(); - term *a = &t1.get_root(); term *b = &t2.get_root(); if (a == b) return; + // -- merge might invalidate term2app cache + m_term2app.reset(); + m_pinned.reset(); + if (a->get_class_size() > b->get_class_size()) { std::swap(a, b); } - // Remove parents of it from the cg table. + // Remove parents of b from the cg table. for (term* p : term::parents(b)) { if (!p->is_marked()) { p->set_mark(true); @@ -401,7 +402,7 @@ namespace qe { a->merge_eq_class(*b); // Insert parents of b's old equilvalence class into the cg table - for (term* p : term::parents(a)) { + for (term* p : term::parents(b)) { if (p->is_marked()) { term* p_old = m_cg_table.insert_if_not_there(p); p->set_mark(false); @@ -412,6 +413,7 @@ namespace qe { } } } + SASSERT(marks_are_clear()); } expr* term_graph::mk_app_core (expr *e) { @@ -484,10 +486,16 @@ namespace qe { } } + bool term_graph::marks_are_clear() { + for (term * t : m_terms) { + if (t->is_marked()) return false; + } + return true; + } + /// Order of preference for roots of equivalence classes /// XXX This should be factored out to let clients control the preference bool term_graph::term_lt(term const &t1, term const &t2) { - // prefer constants over applications // prefer uninterpreted constants over values // prefer smaller expressions over larger ones @@ -521,6 +529,7 @@ namespace qe { /// Choose better roots for equivalence classes void term_graph::pick_roots() { + SASSERT(marks_are_clear()); for (term* t : m_terms) { if (!t->is_marked() && t->is_root()) pick_root(*t); @@ -589,7 +598,7 @@ namespace qe { // prefer a node that resembles current child, // otherwise, pick a root representative, if present. if (m_term2app.find(ch->get_id(), e)) - kids.push_back(e); + kids.push_back(e); else if (m_root2rep.find(ch->get_root().get_id(), e)) kids.push_back(e); else diff --git a/src/qe/qe_term_graph.h b/src/qe/qe_term_graph.h index 2ab234a96..0c66f4193 100644 --- a/src/qe/qe_term_graph.h +++ b/src/qe/qe_term_graph.h @@ -75,6 +75,7 @@ namespace qe { void pick_roots(); void reset_marks(); + bool marks_are_clear(); expr* mk_app_core(expr* a); expr_ref mk_app(term const &t); diff --git a/src/solver/solver_pool.cpp b/src/solver/solver_pool.cpp index 9dacfb5ce..598bb6c02 100644 --- a/src/solver/solver_pool.cpp +++ b/src/solver/solver_pool.cpp @@ -101,9 +101,11 @@ public: proof * get_proof() override { scoped_watch _t_(m_pool.m_proof_watch); if (!m_proof.get()) { - elim_aux_assertions pc(m_pred); m_proof = m_base->get_proof(); - pc(m, m_proof, m_proof); + if (m_proof) { + elim_aux_assertions pc(m_pred); + pc(m, m_proof, m_proof); + } } return m_proof; }