/*++ Copyright (c) 2012 Microsoft Corporation Module Name: qe_lite.cpp Abstract: Light weight partial quantifier-elimination procedure Author: Nikolaj Bjorner (nbjorner) 2012-10-17 Revision History: --*/ #include "qe_lite.h" #include "expr_abstract.h" #include "used_vars.h" #include "occurs.h" #include "for_each_expr.h" #include "rewriter_def.h" #include "ast_pp.h" #include "ast_ll_pp.h" #include "ast_smt2_pp.h" #include "tactical.h" #include "bool_rewriter.h" #include "var_subst.h" #include "uint_set.h" #include "dl_util.h" #include "th_rewriter.h" #include "dl_util.h" class is_variable_proc { public: virtual bool operator()(expr* e) const = 0; }; class is_variable_test : public is_variable_proc { enum is_var_kind { BY_VAR_SET, BY_VAR_SET_COMPLEMENT, BY_NUM_DECLS }; uint_set m_var_set; unsigned m_num_decls; is_var_kind m_var_kind; public: is_variable_test(uint_set const& vars, bool index_of_bound) : m_var_set(vars), m_num_decls(0), m_var_kind(index_of_bound?BY_VAR_SET:BY_VAR_SET_COMPLEMENT) {} is_variable_test(unsigned num_decls) : m_num_decls(num_decls), m_var_kind(BY_NUM_DECLS) {} virtual bool operator()(expr* e) const { if (!is_var(e)) { return false; } unsigned idx = to_var(e)->get_idx(); switch(m_var_kind) { case BY_VAR_SET: return m_var_set.contains(idx); case BY_VAR_SET_COMPLEMENT: return !m_var_set.contains(idx); case BY_NUM_DECLS: return idx < m_num_decls; } UNREACHABLE(); return false; } }; class der2 { ast_manager & m; is_variable_proc* m_is_variable; var_subst m_subst; expr_ref_buffer m_new_exprs; ptr_vector m_map; int_vector m_pos2var; ptr_vector m_inx2var; unsigned_vector m_order; expr_ref_vector m_subst_map; expr_ref_buffer m_new_args; th_rewriter m_rewriter; void der_sort_vars(ptr_vector & vars, ptr_vector & definitions, unsigned_vector & order) { order.reset(); // eliminate self loops, and definitions containing quantifiers. bool found = false; for (unsigned i = 0; i < definitions.size(); i++) { var * v = vars[i]; expr * t = definitions[i]; if (t == 0 || has_quantifiers(t) || occurs(v, t)) definitions[i] = 0; else found = true; // found at least one candidate } if (!found) return; typedef std::pair frame; svector todo; expr_fast_mark1 visiting; expr_fast_mark2 done; unsigned vidx, num; for (unsigned i = 0; i < definitions.size(); i++) { if (definitions[i] == 0) continue; var * v = vars[i]; SASSERT(v->get_idx() == i); SASSERT(todo.empty()); todo.push_back(frame(v, 0)); while (!todo.empty()) { start: frame & fr = todo.back(); expr * t = fr.first; if (t->get_ref_count() > 1 && done.is_marked(t)) { todo.pop_back(); continue; } switch (t->get_kind()) { case AST_VAR: vidx = to_var(t)->get_idx(); if (fr.second == 0) { CTRACE("der_bug", vidx >= definitions.size(), tout << "vidx: " << vidx << "\n";); // Remark: The size of definitions may be smaller than the number of variables occuring in the quantified formula. if (definitions.get(vidx, 0) != 0) { if (visiting.is_marked(t)) { // cycle detected: remove t visiting.reset_mark(t); definitions[vidx] = 0; } else { visiting.mark(t); fr.second = 1; todo.push_back(frame(definitions[vidx], 0)); goto start; } } } else { SASSERT(fr.second == 1); if (definitions.get(vidx, 0) != 0) { visiting.reset_mark(t); order.push_back(vidx); } else { // var was removed from the list of candidate vars to elim cycle // do nothing } } if (t->get_ref_count() > 1) done.mark(t); todo.pop_back(); break; case AST_QUANTIFIER: UNREACHABLE(); todo.pop_back(); break; case AST_APP: num = to_app(t)->get_num_args(); while (fr.second < num) { expr * arg = to_app(t)->get_arg(fr.second); fr.second++; if (arg->get_ref_count() > 1 && done.is_marked(arg)) continue; todo.push_back(frame(arg, 0)); goto start; } if (t->get_ref_count() > 1) done.mark(t); todo.pop_back(); break; default: UNREACHABLE(); todo.pop_back(); break; } } } } bool is_variable(expr * e) const { return (*m_is_variable)(e); } bool is_neg_var(ast_manager & m, expr * e) { expr* e1; return m.is_not(e, e1) && is_variable(e1); } /** \brief Return true if e can be viewed as a variable disequality. Store the variable id in v and the definition in t. For example: if e is (not (= (VAR 1) T)), then v assigned to 1, and t to T. if e is (iff (VAR 2) T), then v is assigned to 2, and t to (not T). (not T) is used because this formula is equivalent to (not (iff (VAR 2) (not T))), and can be viewed as a disequality. */ bool is_var_diseq(expr * e, var * & v, expr_ref & t) { expr* e1; if (m.is_not(e, e1)) { return is_var_eq(e, v, t); } else if (is_var_eq(e, v, t) && m.is_bool(v)) { bool_rewriter(m).mk_not(t, t); m_new_exprs.push_back(t); return true; } else { return false; } } /** \brief Return true if e can be viewed as a variable equality. */ bool is_var_eq(expr * e, var * & v, expr_ref & t) { expr* lhs, *rhs; // (= VAR t), (iff VAR t), (iff (not VAR) t), (iff t (not VAR)) cases if (m.is_eq(e, lhs, rhs) || m.is_iff(e, lhs, rhs)) { // (iff (not VAR) t) (iff t (not VAR)) cases if (!is_variable(lhs) && !is_variable(rhs) && m.is_bool(lhs)) { if (!is_neg_var(m, lhs)) { std::swap(lhs, rhs); } if (!is_neg_var(m, lhs)) { return false; } v = to_var(lhs); t = m.mk_not(rhs); m_new_exprs.push_back(t); TRACE("der", tout << mk_pp(e, m) << "\n";); return true; } if (!is_variable(lhs)) std::swap(lhs, rhs); if (!is_variable(lhs)) return false; v = to_var(lhs); t = rhs; TRACE("der", tout << mk_pp(e, m) << "\n";); return true; } // (ite cond (= VAR t) (= VAR t2)) case expr* cond, *e2, *e3; if (m.is_ite(e, cond, e2, e3)) { if (is_var_eq(e2, v, t)) { expr_ref t2(m); var* v2; if (is_var_eq(e3, v2, t2) && v2 == v) { t = m.mk_ite(cond, t, t2); m_new_exprs.push_back(t); return true; } } return false; } // VAR = true case if (is_variable(e)) { t = m.mk_true(); v = to_var(e); TRACE("der", tout << mk_pp(e, m) << "\n";); return true; } // VAR = false case if (is_neg_var(m, e)) { t = m.mk_false(); v = to_var(to_app(e)->get_arg(0)); TRACE("der", tout << mk_pp(e, m) << "\n";); return true; } return false; } bool is_var_def(bool check_eq, expr* e, var*& v, expr_ref& t) { if (check_eq) { return is_var_eq(e, v, t); } else { return is_var_diseq(e, v, t); } } void get_elimination_order() { m_order.reset(); TRACE("top_sort", tout << "DEFINITIONS: " << std::endl; for(unsigned i = 0; i < m_map.size(); i++) if(m_map[i]) tout << "VAR " << i << " = " << mk_pp(m_map[i], m) << std::endl; ); der_sort_vars(m_inx2var, m_map, m_order); TRACE("der", tout << "Elimination m_order:" << std::endl; for(unsigned i=0; iget_expr(); if ((q->is_forall() && m.is_or(e)) || (q->is_exists() && m.is_and(e))) { num_args = to_app(e)->get_num_args(); args = to_app(e)->get_args(); } } void apply_substitution(quantifier * q, expr_ref & r) { expr * e = q->get_expr(); unsigned num_args = 1; expr* const* args = &e; flatten_args(q, num_args, args); bool_rewriter rw(m); // get a new expression m_new_args.reset(); for(unsigned i = 0; i < num_args; i++) { int x = m_pos2var[i]; if (x == -1 || m_map[x] == 0) { m_new_args.push_back(args[i]); } } expr_ref t(m); if (q->is_forall()) { rw.mk_or(m_new_args.size(), m_new_args.c_ptr(), t); } else { rw.mk_and(m_new_args.size(), m_new_args.c_ptr(), t); } expr_ref new_e(m); m_subst(t, m_subst_map.size(), m_subst_map.c_ptr(), new_e); // don't forget to update the quantifier patterns expr_ref_buffer new_patterns(m); expr_ref_buffer new_no_patterns(m); for (unsigned j = 0; j < q->get_num_patterns(); j++) { expr_ref new_pat(m); m_subst(q->get_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_pat); new_patterns.push_back(new_pat); } for (unsigned j = 0; j < q->get_num_no_patterns(); j++) { expr_ref new_nopat(m); m_subst(q->get_no_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_nopat); new_no_patterns.push_back(new_nopat); } r = m.update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), new_no_patterns.size(), new_no_patterns.c_ptr(), new_e); } void reduce_quantifier1(quantifier * q, expr_ref & r, proof_ref & pr) { expr * e = q->get_expr(); is_variable_test is_v(q->get_num_decls()); set_is_variable_proc(is_v); unsigned num_args = 1; expr* const* args = &e; flatten_args(q, num_args, args); unsigned def_count = 0; unsigned largest_vinx = 0; find_definitions(num_args, args, q->is_exists(), def_count, largest_vinx); if (def_count > 0) { get_elimination_order(); SASSERT(m_order.size() <= def_count); // some might be missing because of cycles if (!m_order.empty()) { create_substitution(largest_vinx + 1); apply_substitution(q, r); } else { r = q; } } else { TRACE("der_bug", tout << "Did not find any diseq\n" << mk_pp(q, m) << "\n";); r = q; } if (m.proofs_enabled()) { pr = r == q ? 0 : m.mk_der(q, r); } } void elim_unused_vars(expr_ref& r, proof_ref &pr) { if (is_quantifier(r)) { quantifier * q = to_quantifier(r); ::elim_unused_vars(m, q, r); if (m.proofs_enabled()) { proof * p1 = m.mk_elim_unused_vars(q, r); pr = m.mk_transitivity(pr, p1); } } } void find_definitions(unsigned num_args, expr* const* args, bool is_exists, unsigned& def_count, unsigned& largest_vinx) { var * v = 0; expr_ref t(m); def_count = 0; largest_vinx = 0; m_map.reset(); m_pos2var.reset(); m_inx2var.reset(); m_pos2var.reserve(num_args, -1); // Find all definitions for (unsigned i = 0; i < num_args; i++) { if (is_var_def(is_exists, args[i], v, t)) { unsigned idx = v->get_idx(); if(m_map.get(idx, 0) == 0) { m_map.reserve(idx + 1, 0); m_inx2var.reserve(idx + 1, 0); m_map[idx] = t; m_inx2var[idx] = v; m_pos2var[i] = idx; def_count++; largest_vinx = std::max(idx, largest_vinx); } } } } bool reduce_var_set(expr_ref_vector& conjs) { unsigned def_count = 0; unsigned largest_vinx = 0; find_definitions(conjs.size(), conjs.c_ptr(), true, def_count, largest_vinx); if (def_count > 0) { get_elimination_order(); SASSERT(m_order.size() <= def_count); // some might be missing because of cycles if (!m_order.empty()) { expr_ref r(m), new_r(m); r = m.mk_and(conjs.size(), conjs.c_ptr()); create_substitution(largest_vinx + 1); m_subst(r, m_subst_map.size(), m_subst_map.c_ptr(), new_r); m_rewriter(new_r); conjs.reset(); datalog::flatten_and(new_r, conjs); return true; } } return false; } public: der2(ast_manager & m): m(m), m_is_variable(0), m_subst(m), m_new_exprs(m), m_subst_map(m), m_new_args(m), m_rewriter(m) {} void set_is_variable_proc(is_variable_proc& proc) { m_is_variable = &proc;} void operator()(quantifier * q, expr_ref & r, proof_ref & pr) { TRACE("der", tout << mk_pp(q, m) << "\n";); pr = 0; r = q; reduce_quantifier(q, r, pr); if (r != q) { elim_unused_vars(r, pr); } } void reduce_quantifier(quantifier * q, expr_ref & r, proof_ref & pr) { r = q; // Keep applying reduce_quantifier1 until r doesn't change anymore do { proof_ref curr_pr(m); q = to_quantifier(r); reduce_quantifier1(q, r, curr_pr); if (m.proofs_enabled()) { pr = m.mk_transitivity(pr, curr_pr); } } while (q != r && is_quantifier(r)); m_new_exprs.reset(); } void operator()(expr_ref_vector& r) { while (reduce_var_set(r)) ; m_new_exprs.reset(); } ast_manager& get_manager() const { return m; } }; // ------------------------------------------------------------ // fm_tactic adapted to eliminate designated de-Brujin indices. namespace fm { typedef ptr_vector clauses; typedef unsigned var; typedef int bvar; typedef int literal; typedef svector var_vector; // Encode the constraint // lits \/ ( as[0]*xs[0] + ... + as[num_vars-1]*xs[num_vars-1] <= c // if strict is true, then <= is <. struct constraint { static unsigned get_obj_size(unsigned num_lits, unsigned num_vars) { return sizeof(constraint) + num_lits*sizeof(literal) + num_vars*(sizeof(var) + sizeof(rational)); } unsigned m_id; unsigned m_num_lits:29; unsigned m_strict:1; unsigned m_dead:1; unsigned m_mark:1; unsigned m_num_vars; literal * m_lits; var * m_xs; rational * m_as; rational m_c; expr_dependency * m_dep; ~constraint() { rational * it = m_as; rational * end = it + m_num_vars; for (; it != end; ++it) it->~rational(); } unsigned hash() const { return hash_u(m_id); } }; typedef ptr_vector constraints; class constraint_set { unsigned_vector m_id2pos; constraints m_set; public: typedef constraints::const_iterator iterator; bool contains(constraint const & c) const { if (c.m_id >= m_id2pos.size()) return false; return m_id2pos[c.m_id] != UINT_MAX; } bool empty() const { return m_set.empty(); } unsigned size() const { return m_set.size(); } void insert(constraint & c) { unsigned id = c.m_id; m_id2pos.reserve(id+1, UINT_MAX); if (m_id2pos[id] != UINT_MAX) return; // already in the set unsigned pos = m_set.size(); m_id2pos[id] = pos; m_set.push_back(&c); } void erase(constraint & c) { unsigned id = c.m_id; if (id >= m_id2pos.size()) return; unsigned pos = m_id2pos[id]; if (pos == UINT_MAX) return; m_id2pos[id] = UINT_MAX; unsigned last_pos = m_set.size() - 1; if (pos != last_pos) { constraint * last_c = m_set[last_pos]; m_set[pos] = last_c; m_id2pos[last_c->m_id] = pos; } m_set.pop_back(); } constraint & erase() { SASSERT(!empty()); constraint & c = *m_set.back(); m_id2pos[c.m_id] = UINT_MAX; m_set.pop_back(); return c; } void reset() { m_id2pos.reset(); m_set.reset(); } void finalize() { m_id2pos.finalize(); m_set.finalize(); } iterator begin() const { return m_set.begin(); } iterator end() const { return m_set.end(); } }; class fm { ast_manager & m; is_variable_proc* m_is_variable; small_object_allocator m_allocator; arith_util m_util; constraints m_constraints; expr_ref_vector m_bvar2expr; char_vector m_bvar2sign; obj_map m_expr2bvar; char_vector m_is_int; char_vector m_forbidden; expr_ref_vector m_var2expr; obj_map m_expr2var; unsigned_vector m_var2pos; vector m_lowers; vector m_uppers; uint_set m_forbidden_set; // variables that cannot be eliminated because occur in non OCC ineq part expr_ref_vector m_new_fmls; volatile bool m_cancel; id_gen m_id_gen; bool m_fm_real_only; unsigned m_fm_limit; unsigned m_fm_cutoff1; unsigned m_fm_cutoff2; unsigned m_fm_extra; bool m_fm_occ; unsigned long long m_max_memory; unsigned m_counter; bool m_inconsistent; expr_dependency_ref m_inconsistent_core; constraint_set m_sub_todo; // --------------------------- // // OCC clause recognizer // // --------------------------- bool is_literal(expr * t) const { expr * atom; return is_uninterp_const(t) || (m.is_not(t, atom) && is_uninterp_const(atom)); } bool is_constraint(expr * t) const { return !is_literal(t); } bool is_var(expr * t, expr * & x) const { if ((*m_is_variable)(t)) { x = t; return true; } else if (m_util.is_to_real(t) && (*m_is_variable)(to_app(t)->get_arg(0))) { x = to_app(t)->get_arg(0); return true; } return false; } bool is_var(expr * t) const { expr * x; return is_var(t, x); } bool is_linear_mon_core(expr * t, expr * & x) const { expr * c; if (m_util.is_mul(t, c, x) && m_util.is_numeral(c) && is_var(x, x)) return true; return is_var(t, x); } bool is_linear_mon(expr * t) const { expr * x; return is_linear_mon_core(t, x); } bool is_linear_pol(expr * t) const { unsigned num_mons; expr * const * mons; if (m_util.is_add(t)) { num_mons = to_app(t)->get_num_args(); mons = to_app(t)->get_args(); } else { num_mons = 1; mons = &t; } expr_fast_mark2 visited; bool all_forbidden = true; for (unsigned i = 0; i < num_mons; i++) { expr * x; if (!is_linear_mon_core(mons[i], x)) return false; if (visited.is_marked(x)) return false; // duplicates are not supported... must simplify first visited.mark(x); SASSERT(::is_var(x)); if (!m_forbidden_set.contains(::to_var(x)->get_idx()) && (!m_fm_real_only || !m_util.is_int(x))) all_forbidden = false; } return !all_forbidden; } bool is_linear_ineq(expr * t) const { m.is_not(t, t); expr * lhs, * rhs; TRACE("is_occ_bug", tout << mk_pp(t, m) << "\n";); if (m_util.is_le(t, lhs, rhs) || m_util.is_ge(t, lhs, rhs)) { if (!m_util.is_numeral(rhs)) return false; return is_linear_pol(lhs); } return false; } bool is_occ(expr * t) { if (m_fm_occ && m.is_or(t)) { unsigned num = to_app(t)->get_num_args(); bool found = false; for (unsigned i = 0; i < num; i++) { expr * l = to_app(t)->get_arg(i); if (is_literal(l)) { continue; } else if (is_linear_ineq(l)) { if (found) return false; found = true; } else { return false; } } return found; } return is_linear_ineq(t); } // --------------------------- // // Memory mng // // --------------------------- void del_constraint(constraint * c) { m.dec_ref(c->m_dep); m_sub_todo.erase(*c); m_id_gen.recycle(c->m_id); c->~constraint(); unsigned sz = constraint::get_obj_size(c->m_num_lits, c->m_num_vars); m_allocator.deallocate(sz, c); } void del_constraints(unsigned sz, constraint * const * cs) { for (unsigned i = 0; i < sz; i++) del_constraint(cs[i]); } void reset_constraints() { del_constraints(m_constraints.size(), m_constraints.c_ptr()); m_constraints.reset(); } constraint * mk_constraint(unsigned num_lits, literal * lits, unsigned num_vars, var * xs, rational * as, rational & c, bool strict, expr_dependency * dep) { unsigned sz = constraint::get_obj_size(num_lits, num_vars); char * mem = static_cast(m_allocator.allocate(sz)); char * mem_as = mem + sizeof(constraint); char * mem_lits = mem_as + sizeof(rational)*num_vars; char * mem_xs = mem_lits + sizeof(literal)*num_lits; constraint * cnstr = new (mem) constraint(); cnstr->m_id = m_id_gen.mk(); cnstr->m_num_lits = num_lits; cnstr->m_dead = false; cnstr->m_mark = false; cnstr->m_strict = strict; cnstr->m_num_vars = num_vars; cnstr->m_lits = reinterpret_cast(mem_lits); for (unsigned i = 0; i < num_lits; i++) cnstr->m_lits[i] = lits[i]; cnstr->m_xs = reinterpret_cast(mem_xs); cnstr->m_as = reinterpret_cast(mem_as); for (unsigned i = 0; i < num_vars; i++) { TRACE("mk_constraint_bug", tout << "xs[" << i << "]: " << xs[i] << "\n";); cnstr->m_xs[i] = xs[i]; new (cnstr->m_as + i) rational(as[i]); } cnstr->m_c = c; DEBUG_CODE({ for (unsigned i = 0; i < num_vars; i++) { SASSERT(cnstr->m_xs[i] == xs[i]); SASSERT(cnstr->m_as[i] == as[i]); } }); cnstr->m_dep = dep; m.inc_ref(dep); return cnstr; } // --------------------------- // // Util // // --------------------------- unsigned num_vars() const { return m_is_int.size(); } // multiply as and c, by the lcm of their denominators void mk_int(unsigned num, rational * as, rational & c) { rational l = denominator(c); for (unsigned i = 0; i < num; i++) l = lcm(l, denominator(as[i])); if (l.is_one()) return; c *= l; SASSERT(c.is_int()); for (unsigned i = 0; i < num; i++) { as[i] *= l; SASSERT(as[i].is_int()); } } void normalize_coeffs(constraint & c) { if (c.m_num_vars == 0) return; // compute gcd of all coefficients rational g = c.m_c; if (g.is_neg()) g.neg(); for (unsigned i = 0; i < c.m_num_vars; i++) { if (g.is_one()) break; if (c.m_as[i].is_pos()) g = gcd(c.m_as[i], g); else g = gcd(-c.m_as[i], g); } if (g.is_one()) return; c.m_c /= g; for (unsigned i = 0; i < c.m_num_vars; i++) c.m_as[i] /= g; } void display(std::ostream & out, constraint const & c) const { for (unsigned i = 0; i < c.m_num_lits; i++) { literal l = c.m_lits[i]; if (sign(l)) out << "~"; bvar p = lit2bvar(l); out << mk_ismt2_pp(m_bvar2expr[p], m); out << " "; } out << "("; if (c.m_num_vars == 0) out << "0"; for (unsigned i = 0; i < c.m_num_vars; i++) { if (i > 0) out << " + "; if (!c.m_as[i].is_one()) out << c.m_as[i] << "*"; out << mk_ismt2_pp(m_var2expr.get(c.m_xs[i]), m); } if (c.m_strict) out << " < "; else out << " <= "; out << c.m_c; out << ")"; } /** \brief Return true if c1 subsumes c2 c1 subsumes c2 If 1) All literals of c1 are literals of c2 2) polynomial of c1 == polynomial of c2 3) c1.m_c <= c2.m_c */ bool subsumes(constraint const & c1, constraint const & c2) { if (&c1 == &c2) return false; // quick checks first if (c1.m_num_lits > c2.m_num_lits) return false; if (c1.m_num_vars != c2.m_num_vars) return false; if (c1.m_c > c2.m_c) return false; if (!c1.m_strict && c2.m_strict && c1.m_c == c2.m_c) return false; m_counter += c1.m_num_lits + c2.m_num_lits; for (unsigned i = 0; i < c1.m_num_vars; i++) { m_var2pos[c1.m_xs[i]] = i; } bool failed = false; for (unsigned i = 0; i < c2.m_num_vars; i++) { unsigned pos1 = m_var2pos[c2.m_xs[i]]; if (pos1 == UINT_MAX || c1.m_as[pos1] != c2.m_as[i]) { failed = true; break; } } for (unsigned i = 0; i < c1.m_num_vars; i++) { m_var2pos[c1.m_xs[i]] = UINT_MAX; } if (failed) return false; for (unsigned i = 0; i < c2.m_num_lits; i++) { literal l = c2.m_lits[i]; bvar b = lit2bvar(l); SASSERT(m_bvar2sign[b] == 0); m_bvar2sign[b] = sign(l) ? -1 : 1; } for (unsigned i = 0; i < c1.m_num_lits; i++) { literal l = c1.m_lits[i]; bvar b = lit2bvar(l); char s = sign(l) ? -1 : 1; if (m_bvar2sign[b] != s) { failed = true; break; } } for (unsigned i = 0; i < c2.m_num_lits; i++) { literal l = c2.m_lits[i]; bvar b = lit2bvar(l); m_bvar2sign[b] = 0; } if (failed) return false; return true; } void backward_subsumption(constraint const & c) { if (c.m_num_vars == 0) return; var best = UINT_MAX; unsigned best_sz = UINT_MAX; bool best_lower = false; for (unsigned i = 0; i < c.m_num_vars; i++) { var xi = c.m_xs[i]; if (is_forbidden(xi)) continue; // variable is not in the index bool neg_a = c.m_as[i].is_neg(); constraints & cs = neg_a ? m_lowers[xi] : m_uppers[xi]; if (cs.size() < best_sz) { best = xi; best_sz = cs.size(); best_lower = neg_a; } } if (best_sz == 0) return; if (best == UINT_MAX) return; // none of the c variables are in the index. constraints & cs = best_lower ? m_lowers[best] : m_uppers[best]; m_counter += cs.size(); constraints::iterator it = cs.begin(); constraints::iterator it2 = it; constraints::iterator end = cs.end(); for (; it != end; ++it) { constraint * c2 = *it; if (c2->m_dead) continue; if (subsumes(c, *c2)) { TRACE("fm_subsumption", display(tout, c); tout << "\nsubsumed:\n"; display(tout, *c2); tout << "\n";); c2->m_dead = true; continue; } *it2 = *it; ++it2; } cs.set_end(it2); } void subsume() { while (!m_sub_todo.empty()) { constraint & c = m_sub_todo.erase(); if (c.m_dead) continue; backward_subsumption(c); } } public: // --------------------------- // // Initialization // // --------------------------- fm(ast_manager & _m, params_ref const & p): m(_m), m_allocator("fm-elim"), m_util(m), m_bvar2expr(m), m_var2expr(m), m_new_fmls(m), m_inconsistent_core(m) { updt_params(p); m_cancel = false; } ~fm() { reset_constraints(); } void updt_params(params_ref const & p) { m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); m_fm_real_only = p.get_bool("fm_real_only", true); m_fm_limit = p.get_uint("fm_limit", 5000000); m_fm_cutoff1 = p.get_uint("fm_cutoff1", 8); m_fm_cutoff2 = p.get_uint("fm_cutoff2", 256); m_fm_extra = p.get_uint("fm_extra", 0); m_fm_occ = p.get_bool("fm_occ", false); } void set_cancel(bool f) { m_cancel = f; } private: struct forbidden_proc { fm & m_owner; forbidden_proc(fm & o):m_owner(o) {} void operator()(::var * n) { if (m_owner.is_var(n) && m_owner.m.get_sort(n)->get_family_id() == m_owner.m_util.get_family_id()) { m_owner.m_forbidden_set.insert(n->get_idx()); } } void operator()(app * n) { } void operator()(quantifier * n) {} }; void init_forbidden_set(expr_ref_vector const & g) { m_forbidden_set.reset(); expr_fast_mark1 visited; forbidden_proc proc(*this); unsigned sz = g.size(); for (unsigned i = 0; i < sz; i++) { expr * f = g[i]; if (is_occ(f)) continue; TRACE("is_occ_bug", tout << "not OCC:\n" << mk_ismt2_pp(f, m) << "\n";); quick_for_each_expr(proc, visited, f); } } void init(expr_ref_vector const & g) { m_sub_todo.reset(); m_id_gen.reset(); reset_constraints(); m_bvar2expr.reset(); m_bvar2sign.reset(); m_bvar2expr.push_back(0); // bvar 0 is not used m_bvar2sign.push_back(0); m_expr2var.reset(); m_is_int.reset(); m_var2pos.reset(); m_forbidden.reset(); m_var2expr.reset(); m_expr2var.reset(); m_lowers.reset(); m_uppers.reset(); m_new_fmls.reset(); m_counter = 0; m_inconsistent = false; m_inconsistent_core = 0; init_forbidden_set(g); } // --------------------------- // // Internal data-structures // // --------------------------- static bool sign(literal l) { return l < 0; } static bvar lit2bvar(literal l) { return l < 0 ? -l : l; } bool is_int(var x) const { return m_is_int[x] != 0; } bool is_forbidden(var x) const { return m_forbidden[x] != 0; } bool all_int(constraint const & c) const { for (unsigned i = 0; i < c.m_num_vars; i++) { if (!is_int(c.m_xs[i])) return false; } return true; } app * to_expr(constraint const & c) { expr * ineq; if (c.m_num_vars == 0) { // 0 < k (for k > 0) --> true // 0 <= 0 -- > true if (c.m_c.is_pos() || (!c.m_strict && c.m_c.is_zero())) return m.mk_true(); ineq = 0; } else { bool int_cnstr = all_int(c); ptr_buffer ms; for (unsigned i = 0; i < c.m_num_vars; i++) { expr * x = m_var2expr.get(c.m_xs[i]); if (!int_cnstr && is_int(c.m_xs[i])) x = m_util.mk_to_real(x); if (c.m_as[i].is_one()) ms.push_back(x); else ms.push_back(m_util.mk_mul(m_util.mk_numeral(c.m_as[i], int_cnstr), x)); } expr * lhs; if (c.m_num_vars == 1) lhs = ms[0]; else lhs = m_util.mk_add(ms.size(), ms.c_ptr()); expr * rhs = m_util.mk_numeral(c.m_c, int_cnstr); if (c.m_strict) { ineq = m.mk_not(m_util.mk_ge(lhs, rhs)); } else { ineq = m_util.mk_le(lhs, rhs); } } if (c.m_num_lits == 0) { if (ineq) return to_app(ineq); else return m.mk_false(); } ptr_buffer lits; for (unsigned i = 0; i < c.m_num_lits; i++) { literal l = c.m_lits[i]; if (sign(l)) lits.push_back(m.mk_not(m_bvar2expr.get(lit2bvar(l)))); else lits.push_back(m_bvar2expr.get(lit2bvar(l))); } if (ineq) lits.push_back(ineq); if (lits.size() == 1) return to_app(lits[0]); else return m.mk_or(lits.size(), lits.c_ptr()); } var mk_var(expr * t) { SASSERT(::is_var(t)); SASSERT(m_util.is_int(t) || m_util.is_real(t)); var x = m_var2expr.size(); m_var2expr.push_back(t); bool is_int = m_util.is_int(t); m_is_int.push_back(is_int); m_var2pos.push_back(UINT_MAX); m_expr2var.insert(t, x); m_lowers.push_back(constraints()); m_uppers.push_back(constraints()); bool forbidden = m_forbidden_set.contains(::to_var(t)->get_idx()) || (m_fm_real_only && is_int); m_forbidden.push_back(forbidden); SASSERT(m_var2expr.size() == m_is_int.size()); SASSERT(m_lowers.size() == m_is_int.size()); SASSERT(m_uppers.size() == m_is_int.size()); SASSERT(m_forbidden.size() == m_is_int.size()); SASSERT(m_var2pos.size() == m_is_int.size()); return x; } bvar mk_bvar(expr * t) { SASSERT(is_uninterp_const(t)); SASSERT(m.is_bool(t)); bvar p = m_bvar2expr.size(); m_bvar2expr.push_back(t); m_bvar2sign.push_back(0); SASSERT(m_bvar2expr.size() == m_bvar2sign.size()); m_expr2bvar.insert(t, p); SASSERT(p > 0); return p; } var to_var(expr * t) { var x; if (!m_expr2var.find(t, x)) x = mk_var(t); SASSERT(m_expr2var.contains(t)); SASSERT(m_var2expr.get(x) == t); TRACE("to_var_bug", tout << mk_ismt2_pp(t, m) << " --> " << x << "\n";); return x; } bvar to_bvar(expr * t) { bvar p; if (m_expr2bvar.find(t, p)) return p; return mk_bvar(t); } literal to_literal(expr * t) { if (m.is_not(t, t)) return -to_bvar(t); else return to_bvar(t); } void add_constraint(expr * f, expr_dependency * dep) { SASSERT(!m.is_or(f) || m_fm_occ); sbuffer lits; sbuffer xs; buffer as; rational c; bool strict; unsigned num; expr * const * args; if (m.is_or(f)) { num = to_app(f)->get_num_args(); args = to_app(f)->get_args(); } else { num = 1; args = &f; } #if Z3DEBUG bool found_ineq = false; #endif for (unsigned i = 0; i < num; i++) { expr * l = args[i]; if (is_literal(l)) { lits.push_back(to_literal(l)); } else { // found inequality SASSERT(!found_ineq); DEBUG_CODE(found_ineq = true;); bool neg = m.is_not(l, l); SASSERT(m_util.is_le(l) || m_util.is_ge(l)); strict = neg; if (m_util.is_ge(l)) neg = !neg; expr * lhs = to_app(l)->get_arg(0); expr * rhs = to_app(l)->get_arg(1); m_util.is_numeral(rhs, c); if (neg) c.neg(); unsigned num_mons; expr * const * mons; if (m_util.is_add(lhs)) { num_mons = to_app(lhs)->get_num_args(); mons = to_app(lhs)->get_args(); } else { num_mons = 1; mons = &lhs; } bool all_int = true; for (unsigned j = 0; j < num_mons; j++) { expr * monomial = mons[j]; expr * a; rational a_val; expr * x; if (m_util.is_mul(monomial, a, x)) { VERIFY(m_util.is_numeral(a, a_val)); } else { x = monomial; a_val = rational(1); } if (neg) a_val.neg(); VERIFY(is_var(x, x)); xs.push_back(to_var(x)); as.push_back(a_val); if (!is_int(xs.back())) all_int = false; } mk_int(as.size(), as.c_ptr(), c); if (all_int && strict) { strict = false; c--; } } } TRACE("to_var_bug", tout << "before mk_constraint: "; for (unsigned i = 0; i < xs.size(); i++) tout << " " << xs[i]; tout << "\n";); constraint * new_c = mk_constraint(lits.size(), lits.c_ptr(), xs.size(), xs.c_ptr(), as.c_ptr(), c, strict, dep); TRACE("to_var_bug", tout << "add_constraint: "; display(tout, *new_c); tout << "\n";); VERIFY(register_constraint(new_c)); } bool is_false(constraint const & c) const { return c.m_num_lits == 0 && c.m_num_vars == 0 && (c.m_c.is_neg() || (c.m_strict && c.m_c.is_zero())); } bool register_constraint(constraint * c) { normalize_coeffs(*c); if (is_false(*c)) { del_constraint(c); m_inconsistent = true; TRACE("add_constraint_bug", tout << "is false "; display(tout, *c); tout << "\n";); return false; } bool r = false; for (unsigned i = 0; i < c->m_num_vars; i++) { var x = c->m_xs[i]; if (!is_forbidden(x)) { r = true; if (c->m_as[i].is_neg()) m_lowers[x].push_back(c); else m_uppers[x].push_back(c); } } if (r) { m_sub_todo.insert(*c); m_constraints.push_back(c); return true; } else { TRACE("add_constraint_bug", tout << "all variables are forbidden "; display(tout, *c); tout << "\n";); m_new_fmls.push_back(to_expr(*c)); del_constraint(c); return false; } } void init_use_list(expr_ref_vector const & g) { unsigned sz = g.size(); for (unsigned i = 0; !m_inconsistent && i < sz; i++) { expr * f = g[i]; if (is_occ(f)) add_constraint(f, 0); else m_new_fmls.push_back(f); } } unsigned get_cost(var x) const { unsigned long long r = static_cast(m_lowers[x].size()) * static_cast(m_uppers[x].size()); if (r > UINT_MAX) return UINT_MAX; return static_cast(r); } typedef std::pair x_cost; struct x_cost_lt { char_vector const m_is_int; x_cost_lt(char_vector & is_int):m_is_int(is_int) {} bool operator()(x_cost const & p1, x_cost const & p2) const { // Integer variables with cost 0 can be eliminated even if they depend on real variables. // Cost 0 == no lower or no upper bound. if (p1.second == 0) { if (p2.second > 0) return true; return p1.first < p2.first; } if (p2.second == 0) return false; bool int1 = m_is_int[p1.first] != 0; bool int2 = m_is_int[p2.first] != 0; return (!int1 && int2) || (int1 == int2 && p1.second < p2.second); } }; void sort_candidates(var_vector & xs) { svector x_cost_vector; unsigned num = num_vars(); for (var x = 0; x < num; x++) { if (!is_forbidden(x)) { x_cost_vector.push_back(x_cost(x, get_cost(x))); } } // x_cost_lt is not a total order on variables std::stable_sort(x_cost_vector.begin(), x_cost_vector.end(), x_cost_lt(m_is_int)); TRACE("fm", svector::iterator it2 = x_cost_vector.begin(); svector::iterator end2 = x_cost_vector.end(); for (; it2 != end2; ++it2) { tout << "(" << mk_ismt2_pp(m_var2expr.get(it2->first), m) << " " << it2->second << ") "; } tout << "\n";); svector::iterator it2 = x_cost_vector.begin(); svector::iterator end2 = x_cost_vector.end(); for (; it2 != end2; ++it2) { xs.push_back(it2->first); } } void cleanup_constraints(constraints & cs) { unsigned j = 0; unsigned sz = cs.size(); for (unsigned i = 0; i < sz; i++) { constraint * c = cs[i]; if (c->m_dead) continue; cs[j] = c; j++; } cs.shrink(j); } // Set all_int = true if all variables in c are int. // Set unit_coeff = true if the coefficient of x in c is 1 or -1. // If all_int = false, then unit_coeff may not be set. void analyze(constraint const & c, var x, bool & all_int, bool & unit_coeff) const { all_int = true; unit_coeff = true; for (unsigned i = 0; i < c.m_num_vars; i++) { if (!is_int(c.m_xs[i])) { all_int = false; return; } if (c.m_xs[i] == x) { unit_coeff = (c.m_as[i].is_one() || c.m_as[i].is_minus_one()); } } } void analyze(constraints const & cs, var x, bool & all_int, bool & unit_coeff) const { all_int = true; unit_coeff = true; constraints::const_iterator it = cs.begin(); constraints::const_iterator end = cs.end(); for (; it != end; ++it) { bool curr_unit_coeff; analyze(*(*it), x, all_int, curr_unit_coeff); if (!all_int) return; if (!curr_unit_coeff) unit_coeff = false; } } // An integer variable x may be eliminated, if // 1- All variables in the contraints it occur are integer. // 2- The coefficient of x in all lower bounds (or all upper bounds) is unit. bool can_eliminate(var x) const { if (!is_int(x)) return true; bool all_int; bool l_unit, u_unit; analyze(m_lowers[x], x, all_int, l_unit); if (!all_int) return false; analyze(m_uppers[x], x, all_int, u_unit); return all_int && (l_unit || u_unit); } void copy_constraints(constraints const & s, clauses & t) { constraints::const_iterator it = s.begin(); constraints::const_iterator end = s.end(); for (; it != end; ++it) { app * c = to_expr(*(*it)); t.push_back(c); } } clauses tmp_clauses; void save_constraints(var x) { } void mark_constraints_dead(constraints const & cs) { constraints::const_iterator it = cs.begin(); constraints::const_iterator end = cs.end(); for (; it != end; ++it) (*it)->m_dead = true; } void mark_constraints_dead(var x) { save_constraints(x); mark_constraints_dead(m_lowers[x]); mark_constraints_dead(m_uppers[x]); } void get_coeff(constraint const & c, var x, rational & a) { for (unsigned i = 0; i < c.m_num_vars; i++) { if (c.m_xs[i] == x) { a = c.m_as[i]; return; } } UNREACHABLE(); } var_vector new_xs; vector new_as; svector new_lits; constraint * resolve(constraint const & l, constraint const & u, var x) { m_counter += l.m_num_vars + u.m_num_vars + l.m_num_lits + u.m_num_lits; rational a, b; get_coeff(l, x, a); get_coeff(u, x, b); SASSERT(a.is_neg()); SASSERT(b.is_pos()); a.neg(); SASSERT(!is_int(x) || a.is_one() || b.is_one()); new_xs.reset(); new_as.reset(); rational new_c = l.m_c*b + u.m_c*a; bool new_strict = l.m_strict || u.m_strict; for (unsigned i = 0; i < l.m_num_vars; i++) { var xi = l.m_xs[i]; if (xi == x) continue; unsigned pos = new_xs.size(); new_xs.push_back(xi); SASSERT(m_var2pos[xi] == UINT_MAX); m_var2pos[xi] = pos; new_as.push_back(l.m_as[i] * b); SASSERT(new_xs[m_var2pos[xi]] == xi); SASSERT(new_xs.size() == new_as.size()); } for (unsigned i = 0; i < u.m_num_vars; i++) { var xi = u.m_xs[i]; if (xi == x) continue; unsigned pos = m_var2pos[xi]; if (pos == UINT_MAX) { new_xs.push_back(xi); new_as.push_back(u.m_as[i] * a); } else { new_as[pos] += u.m_as[i] * a; } } // remove zeros and check whether all variables are int bool all_int = true; unsigned sz = new_xs.size(); unsigned j = 0; for (unsigned i = 0; i < sz; i++) { if (new_as[i].is_zero()) continue; if (!is_int(new_xs[i])) all_int = false; if (i != j) { new_xs[j] = new_xs[i]; new_as[j] = new_as[i]; } j++; } new_xs.shrink(j); new_as.shrink(j); if (all_int && new_strict) { new_strict = false; new_c --; } // reset m_var2pos for (unsigned i = 0; i < l.m_num_vars; i++) { m_var2pos[l.m_xs[i]] = UINT_MAX; } if (new_xs.empty() && (new_c.is_pos() || (!new_strict && new_c.is_zero()))) { // literal is true TRACE("fm", tout << "resolution " << x << " consequent literal is always true: \n"; display(tout, l); tout << "\n"; display(tout, u); tout << "\n";); return 0; // no constraint needs to be created. } new_lits.reset(); for (unsigned i = 0; i < l.m_num_lits; i++) { literal lit = l.m_lits[i]; bvar p = lit2bvar(lit); m_bvar2sign[p] = sign(lit) ? -1 : 1; new_lits.push_back(lit); } bool tautology = false; for (unsigned i = 0; i < u.m_num_lits && !tautology; i++) { literal lit = u.m_lits[i]; bvar p = lit2bvar(lit); switch (m_bvar2sign[p]) { case 0: new_lits.push_back(lit); break; case -1: if (!sign(lit)) tautology = true; break; case 1: if (sign(lit)) tautology = true; break; default: UNREACHABLE(); } } // reset m_bvar2sign for (unsigned i = 0; i < l.m_num_lits; i++) { literal lit = l.m_lits[i]; bvar p = lit2bvar(lit); m_bvar2sign[p] = 0; } if (tautology) { TRACE("fm", tout << "resolution " << x << " tautology: \n"; display(tout, l); tout << "\n"; display(tout, u); tout << "\n";); return 0; } expr_dependency * new_dep = m.mk_join(l.m_dep, u.m_dep); if (new_lits.empty() && new_xs.empty() && (new_c.is_neg() || (new_strict && new_c.is_zero()))) { TRACE("fm", tout << "resolution " << x << " inconsistent: \n"; display(tout, l); tout << "\n"; display(tout, u); tout << "\n";); m_inconsistent = true; m_inconsistent_core = new_dep; return 0; } constraint * new_cnstr = mk_constraint(new_lits.size(), new_lits.c_ptr(), new_xs.size(), new_xs.c_ptr(), new_as.c_ptr(), new_c, new_strict, new_dep); TRACE("fm", tout << "resolution " << x << "\n"; display(tout, l); tout << "\n"; display(tout, u); tout << "\n---->\n"; display(tout, *new_cnstr); tout << "\n"; tout << "new_dep: " << new_dep << "\n";); return new_cnstr; } ptr_vector new_constraints; bool try_eliminate(var x) { constraints & l = m_lowers[x]; constraints & u = m_uppers[x]; cleanup_constraints(l); cleanup_constraints(u); if (l.empty() || u.empty()) { // easy case mark_constraints_dead(x); TRACE("fm", tout << "variable was eliminated (trivial case)\n";); return true; } unsigned num_lowers = l.size(); unsigned num_uppers = u.size(); if (num_lowers > m_fm_cutoff1 && num_uppers > m_fm_cutoff1) return false; if (num_lowers * num_uppers > m_fm_cutoff2) return false; if (!can_eliminate(x)) return false; m_counter += num_lowers * num_uppers; TRACE("fm_bug", tout << "eliminating " << mk_ismt2_pp(m_var2expr.get(x), m) << "\nlowers:\n"; display_constraints(tout, l); tout << "uppers:\n"; display_constraints(tout, u);); unsigned num_old_cnstrs = num_uppers + num_lowers; unsigned limit = num_old_cnstrs + m_fm_extra; unsigned num_new_cnstrs = 0; new_constraints.reset(); for (unsigned i = 0; i < num_lowers; i++) { for (unsigned j = 0; j < num_uppers; j++) { if (m_inconsistent || num_new_cnstrs > limit) { TRACE("fm", tout << "too many new constraints: " << num_new_cnstrs << "\n";); del_constraints(new_constraints.size(), new_constraints.c_ptr()); return false; } constraint const & l_c = *(l[i]); constraint const & u_c = *(u[j]); constraint * new_c = resolve(l_c, u_c, x); if (new_c != 0) { num_new_cnstrs++; new_constraints.push_back(new_c); } } } mark_constraints_dead(x); unsigned sz = new_constraints.size(); m_counter += sz; for (unsigned i = 0; i < sz; i++) { constraint * c = new_constraints[i]; backward_subsumption(*c); register_constraint(c); } TRACE("fm", tout << "variables was eliminated old: " << num_old_cnstrs << " new_constraints: " << sz << "\n";); return true; } void copy_remaining(vector & v2cs) { vector::iterator it = v2cs.begin(); vector::iterator end = v2cs.end(); for (; it != end; ++it) { constraints & cs = *it; constraints::iterator it2 = cs.begin(); constraints::iterator end2 = cs.end(); for (; it2 != end2; ++it2) { constraint * c = *it2; if (!c->m_dead) { c->m_dead = true; expr * new_f = to_expr(*c); TRACE("fm_bug", tout << "asserting...\n" << mk_ismt2_pp(new_f, m) << "\nnew_dep: " << c->m_dep << "\n";); m_new_fmls.push_back(new_f); } } } v2cs.finalize(); } // Copy remaining clauses to m_new_fmls void copy_remaining() { copy_remaining(m_uppers); copy_remaining(m_lowers); } void checkpoint() { // cooperate("fm"); if (m_cancel) throw tactic_exception(TACTIC_CANCELED_MSG); if (memory::get_allocation_size() > m_max_memory) throw tactic_exception(TACTIC_MAX_MEMORY_MSG); } public: void set_is_variable_proc(is_variable_proc& proc) { m_is_variable = &proc;} void operator()(expr_ref_vector& fmls) { init(fmls); init_use_list(fmls); if (m_inconsistent) { m_new_fmls.reset(); m_new_fmls.push_back(m.mk_false()); } else { TRACE("fm", display(tout);); subsume(); var_vector candidates; sort_candidates(candidates); unsigned eliminated = 0; unsigned num = candidates.size(); for (unsigned i = 0; i < num; i++) { checkpoint(); if (m_counter > m_fm_limit) break; m_counter++; if (try_eliminate(candidates[i])) eliminated++; if (m_inconsistent) { m_new_fmls.reset(); m_new_fmls.push_back(m.mk_false()); break; } } if (!m_inconsistent) { copy_remaining(); } } reset_constraints(); fmls.reset(); fmls.append(m_new_fmls); } void display_constraints(std::ostream & out, constraints const & cs) const { constraints::const_iterator it = cs.begin(); constraints::const_iterator end = cs.end(); for (; it != end; ++it) { out << " "; display(out, *(*it)); out << "\n"; } } void display(std::ostream & out) const { unsigned num = num_vars(); for (var x = 0; x < num; x++) { if (is_forbidden(x)) continue; out << mk_ismt2_pp(m_var2expr.get(x), m) << "\n"; display_constraints(out, m_lowers[x]); display_constraints(out, m_uppers[x]); } } }; } // namespace fm class qe_lite::impl { ast_manager& m; der2 m_der; params_ref m_params; fm::fm m_fm; public: impl(ast_manager& m): m(m), m_der(m), m_fm(m, m_params) {} void operator()(app_ref_vector& vars, expr_ref& fml) { if (vars.empty()) { return; } expr_ref tmp(fml); quantifier_ref q(m); proof_ref pr(m); symbol qe_lite("QE"); expr_abstract(m, 0, vars.size(), (expr*const*)vars.c_ptr(), fml, tmp); ptr_vector sorts; svector names; for (unsigned i = 0; i < vars.size(); ++i) { sorts.push_back(m.get_sort(vars[i].get())); names.push_back(vars[i]->get_decl()->get_name()); } q = m.mk_exists(vars.size(), sorts.c_ptr(), names.c_ptr(), tmp, 1, qe_lite); m_der.reduce_quantifier(q, tmp, pr); // assumes m_der just updates the quantifier and does not change things more. if (is_exists(tmp) && to_quantifier(tmp)->get_qid() == qe_lite) { used_vars used; tmp = to_quantifier(tmp)->get_expr(); used.process(tmp); var_subst vs(m, true); vs(tmp, vars.size(), (expr*const*)vars.c_ptr(), fml); // collect set of variables that were used. unsigned j = 0; for (unsigned i = 0; i < vars.size(); ++i) { if (used.contains(vars.size()-i-1)) { vars.set(j, vars.get(i)); ++j; } } vars.resize(j); } else { fml = tmp; } } void operator()(expr_ref& fml, proof_ref& pr) { // TODO apply der everywhere as a rewriting rule. // TODO add cancel method. if (is_quantifier(fml)) { m_der(to_quantifier(fml), fml, pr); } } void operator()(uint_set const& index_set, bool index_of_bound, expr_ref& fml) { expr_ref_vector conjs(m); conjs.push_back(fml); (*this)(index_set, index_of_bound, conjs); bool_rewriter(m).mk_and(conjs.size(), conjs.c_ptr(), fml); } void operator()(uint_set const& index_set, bool index_of_bound, expr_ref_vector& fmls) { datalog::flatten_and(fmls); is_variable_test is_var(index_set, index_of_bound); TRACE("qe_lite", for (unsigned i = 0; i < fmls.size(); ++i) tout << mk_pp(fmls[i].get(), m) << "\n";); IF_VERBOSE(3, for (unsigned i = 0; i < fmls.size(); ++i) verbose_stream() << mk_pp(fmls[i].get(), m) << "\n";); m_der.set_is_variable_proc(is_var); m_der(fmls); m_fm.set_is_variable_proc(is_var); m_fm(fmls); TRACE("qe_lite", for (unsigned i = 0; i < fmls.size(); ++i) tout << mk_pp(fmls[i].get(), m) << "\n";); } }; qe_lite::qe_lite(ast_manager& m) { m_impl = alloc(impl, m); } qe_lite::~qe_lite() { dealloc(m_impl); } void qe_lite::operator()(app_ref_vector& vars, expr_ref& fml) { (*m_impl)(vars, fml); } void qe_lite::operator()(expr_ref& fml, proof_ref& pr) { (*m_impl)(fml, pr); } void qe_lite::operator()(uint_set const& index_set, bool index_of_bound, expr_ref& fml) { (*m_impl)(index_set, index_of_bound, fml); } void qe_lite::operator()(uint_set const& index_set, bool index_of_bound, expr_ref_vector& fmls) { (*m_impl)(index_set, index_of_bound, fmls); }