Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
z3-z3-4.12.6.src.qe.lite.qe_lite_tactic.cpp Maven / Gradle / Ivy
/*++
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 "util/uint_set.h"
#include "ast/expr_abstract.h"
#include "ast/used_vars.h"
#include "ast/rewriter/rewriter_def.h"
#include "ast/ast_pp.h"
#include "ast/ast_ll_pp.h"
#include "ast/ast_smt2_pp.h"
#include "ast/is_variable_test.h"
#include "ast/rewriter/bool_rewriter.h"
#include "ast/rewriter/var_subst.h"
#include "ast/ast_util.h"
#include "ast/rewriter/th_rewriter.h"
#include "ast/for_each_expr.h"
#include "ast/rewriter/expr_safe_replace.h"
#include "ast/datatype_decl_plugin.h"
#include "tactic/tactical.h"
#include "qe/mbp/mbp_solve_plugin.h"
#include "qe/lite/qe_lite_tactic.h"
#include "tactic/dependent_expr_state_tactic.h"
namespace qel {
bool occurs_var(unsigned idx, expr* e) {
if (is_ground(e)) return false;
ptr_buffer todo;
todo.push_back(e);
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_ground(e)) continue;
if (is_var(e)) {
if (to_var(e)->get_idx() == idx) return true;
}
else if (is_app(e)) {
todo.append(to_app(e)->get_num_args(), to_app(e)->get_args());
}
else if (is_quantifier(e)) {
quantifier* q = to_quantifier(e);
if (occurs_var(idx + q->get_num_decls(), q->get_expr())) return true;
}
}
return false;
}
class eq_der {
ast_manager & m;
arith_util a;
datatype_util dt;
bv_util bv;
is_variable_proc* m_is_variable;
beta_reducer m_subst;
expr_ref_vector m_subst_map;
expr_ref_vector m_new_exprs;
plugin_manager m_solvers;
ptr_vector m_map;
int_vector m_pos2var;
int_vector m_var2pos;
ptr_vector m_inx2var;
unsigned_vector m_order;
expr_ref_buffer m_new_args;
th_rewriter m_rewriter;
params_ref m_params;
bool is_sub_extract(unsigned idx, expr* t) {
bool has_ground = false;
if (bv.is_concat(t)) {
unsigned lo, hi;
ptr_buffer args;
args.append(to_app(t)->get_num_args(), to_app(t)->get_args());
for (unsigned i = 0; i < args.size(); ++i) {
expr* arg = args[i];
if (is_ground(arg)) {
has_ground = true;
continue;
}
if (bv.is_extract(arg, lo, hi, arg)) {
if (is_var(arg) && to_var(arg)->get_idx() == idx)
continue;
}
if (bv.is_concat(arg)) {
args.append(to_app(arg)->get_num_args(), to_app(arg)->get_args());
continue;
}
return false;
}
return has_ground;
}
return false;
}
bool strict_occurs_var(unsigned idx, expr* t) {
return occurs_var(idx, t) && !is_sub_extract(idx, t);
}
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 == nullptr || has_quantifiers(t) || strict_occurs_var(v->get_idx(), t))
definitions[i] = nullptr;
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] == nullptr)
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 (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 occurring in the quantified formula.
if (definitions.get(vidx, nullptr) != nullptr) {
if (visiting.is_marked(t)) {
// cycle detected: remove t
visiting.reset_mark(t);
definitions[vidx] = nullptr;
}
else if (is_sub_extract(vidx, definitions[vidx])) {
order.push_back(vidx);
done.mark(definitions[vidx]);
}
else {
visiting.mark(t);
fr.second = 1;
todo.push_back(frame(definitions[vidx], 0));
goto start;
}
}
}
else {
SASSERT(fr.second == 1);
visiting.reset_mark(t);
if (!done.is_marked(t) && definitions.get(vidx, nullptr) != nullptr) {
order.push_back(vidx);
}
}
done.mark(t);
todo.pop_back();
break;
case AST_QUANTIFIER:
UNREACHABLE();
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 (done.is_marked(arg)) continue;
todo.push_back(frame(arg, 0));
goto start;
}
done.mark(t);
todo.pop_back();
break;
default:
UNREACHABLE();
break;
}
}
}
}
bool is_variable(expr * e) const {
return (*m_is_variable)(e);
}
bool is_neg_var(ast_manager & m, expr * e, var*& v) {
expr* e1;
if (m.is_not(e, e1) && is_variable(e1)) {
v = to_var(e1);
return true;
}
else {
return false;
}
}
/**
\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, ptr_vector& vs, expr_ref_vector& ts) {
expr* e1;
if (m.is_not(e, e1)) {
return is_var_eq(e, vs, ts);
}
else if (is_var_eq(e, vs, ts) && vs.size() == 1 && m.is_bool(vs[0])) {
expr_ref tmp(m);
bool_rewriter(m).mk_not(ts[0].get(), tmp);
ts[0] = tmp;
return true;
}
else {
return false;
}
}
bool trivial_solve(expr* lhs, expr* rhs, expr* eq, ptr_vector& vs, expr_ref_vector& ts) {
if (!is_variable(lhs)) {
std::swap(lhs, rhs);
}
if (!is_variable(lhs)) {
return false;
}
vs.push_back(to_var(lhs));
ts.push_back(rhs);
TRACE("qe_lite", tout << mk_pp(eq, m) << "\n";);
return true;
}
bool same_vars(ptr_vector const& vs1, ptr_vector const& vs2) const {
if (vs1.size() != vs2.size()) {
return false;
}
for (unsigned i = 0; i < vs1.size(); ++i) {
if (vs1[i] != vs2[i]) {
return false;
}
}
return true;
}
/**
\brief Return true if e can be viewed as a variable equality.
*/
bool is_var_eq(expr * e, ptr_vector& vs, expr_ref_vector & ts) {
expr* lhs = nullptr, *rhs = nullptr;
TRACE("qe_lite", tout << mk_pp(e, m) << "\n";);
// (= VAR t), (iff VAR t), (iff (not VAR) t), (iff t (not VAR)) cases
if (m.is_eq(e, lhs, rhs) && trivial_solve(lhs, rhs, e, vs, ts)) {
return true;
}
family_id fid = e->get_sort()->get_family_id();
if (m.is_eq(e, lhs, rhs)) {
fid = lhs->get_sort()->get_family_id();
}
auto* p = m_solvers.get_plugin(fid);
if (p) {
expr_ref res = (*p)(e);
if (res != e && m.is_eq(res, lhs, rhs) && is_variable(lhs)) {
vs.push_back(to_var(lhs));
ts.push_back(rhs);
TRACE("qe_lite", tout << res << "\n";);
return true;
}
}
return false;
}
bool is_var_def(bool check_eq, expr* e, ptr_vector& vs, expr_ref_vector& ts) {
if (check_eq) {
return is_var_eq(e, vs, ts);
}
else {
return is_var_diseq(e, vs, ts);
}
}
void get_elimination_order() {
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("qe_lite",
tout << "Elimination m_order:" << std::endl;
tout << m_order << std::endl;
);
}
void create_substitution(unsigned sz) {
m_subst_map.reset();
m_subst_map.resize(sz, nullptr);
m_subst.reset();
m_subst.set_inv_bindings(sz, m_subst_map.data());
for (unsigned idx : m_order) {
// do all the previous substitutions before inserting
expr* cur = m_map[idx];
expr_ref r(m);
if (is_ground(cur)) r = cur; else m_subst(cur, r);
unsigned inx = sz - idx - 1;
TRACE("qe_lite", tout << idx << " |-> " << r << "\n";);
CTRACE("top_sort", m_subst_map.get(inx) != nullptr,
tout << "inx is " << inx << "\n"
<< "idx is " << idx << "\n"
<< "sz is " << sz << "\n"
<< "subst_map[inx]: " << mk_pp(m_subst_map.get(inx), m) << "\n";);
SASSERT(m_subst_map.get(inx) == nullptr);
m_subst.update_inv_binding_at(inx, r);
m_subst_map[inx] = std::move(r);
}
}
void flatten_args(quantifier* q, unsigned& num_args, expr*const*& args) {
expr * e = q->get_expr();
if ((is_forall(q) && m.is_or(e)) ||
(is_exists(q) && 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]);
}
}
if (m_new_args.size() == num_args) {
r = q;
return;
}
expr_ref t(m);
switch (q->get_kind()) {
case forall_k:
rw.mk_or(m_new_args.size(), m_new_args.data(), t);
break;
case exists_k:
rw.mk_and(m_new_args.size(), m_new_args.data(), t);
break;
default:
t = e;
break;
}
expr_ref new_e = m_subst(t, m_subst_map.size(), m_subst_map.data());
TRACE("qe_lite", tout << new_e << "\n";);
// 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_subst(q->get_pattern(j), m_subst_map.size(), m_subst_map.data());
new_patterns.push_back(new_pat);
}
for (unsigned j = 0; j < q->get_num_no_patterns(); j++) {
expr_ref new_nopat = m_subst(q->get_no_pattern(j), m_subst_map.size(), m_subst_map.data());
new_no_patterns.push_back(new_nopat);
}
r = m.update_quantifier(q, new_patterns.size(), new_patterns.data(),
new_no_patterns.size(), new_no_patterns.data(), 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;
if (is_lambda(q)) {
r = q;
pr = nullptr;
return;
}
flatten_args(q, num_args, args);
unsigned def_count = 0;
unsigned largest_vinx = 0;
find_definitions(num_args, args, is_exists(q), 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 ? nullptr : m.mk_der(q, r);
}
}
void elim_unused_vars(expr_ref& r, proof_ref &pr) {
if (is_quantifier(r)) {
quantifier * q = to_quantifier(r);
r = ::elim_unused_vars(m, q, m_params);
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) {
def_count = 0;
largest_vinx = 0;
m_map.reset();
m_pos2var.reset();
m_var2pos.reset();
m_inx2var.reset();
m_pos2var.reserve(num_args, -1);
// Find all definitions
for (unsigned i = 0; i < num_args; i++) {
checkpoint();
ptr_vector vs;
expr_ref_vector ts(m);
expr_ref t(m);
if (is_var_def(is_exists, args[i], vs, ts)) { // vs is the variable, ts is the definition
for (unsigned j = 0; j < vs.size(); ++j) {
var* v = vs[j];
t = ts.get(j);
m_rewriter(t);
if (t != ts.get(j)) m_new_exprs.push_back(t);
unsigned idx = v->get_idx();
if (m_map.get(idx, nullptr) == nullptr) {
m_map.reserve(idx + 1, 0);
m_inx2var.reserve(idx + 1, 0);
m_map[idx] = t;
m_inx2var[idx] = v;
m_pos2var[i] = idx;
m_var2pos.reserve(idx + 1, -1);
m_var2pos[idx] = i;
def_count++;
largest_vinx = std::max(idx, largest_vinx);
m_new_exprs.push_back(std::move(t));
}
else if (!m.is_value(m_map[idx])) {
// check if the new definition is simpler
expr *old_def = m_map[idx];
// -- prefer values
if (m.is_value(t)) {
m_pos2var[m_var2pos[idx]] = -1;
m_pos2var[i] = idx;
m_var2pos[idx] = i;
m_map[idx] = t;
m_new_exprs.push_back(std::move(t));
}
// -- prefer ground
else if (is_app(t) && to_app(t)->is_ground() &&
(!is_app(old_def) ||
!to_app(old_def)->is_ground())) {
m_pos2var[m_var2pos[idx]] = -1;
m_pos2var[i] = idx;
m_var2pos[idx] = i;
m_map[idx] = t;
m_new_exprs.push_back(std::move(t));
}
// -- prefer constants
else if (is_uninterp_const(t)
/* && !is_uninterp_const(old_def) */){
m_pos2var[m_var2pos[idx]] = -1;
m_pos2var[i] = idx;
m_var2pos[idx] = i;
m_map[idx] = t;
m_new_exprs.push_back(std::move(t));
}
TRACE ("qe_def",
tout << "Replacing definition of VAR " << idx << " from "
<< mk_pp(old_def, m) << " to " << mk_pp(t, m)
<< " inferred from: " << mk_pp(args[i], m) << "\n";);
}
}
}
}
}
void flatten_definitions(expr_ref_vector& conjs) {
TRACE("qe_lite",
expr_ref tmp(m);
tmp = m.mk_and(conjs.size(), conjs.data());
tout << mk_pp(tmp, m) << "\n";);
for (unsigned i = 0; i < conjs.size(); ++i) {
expr* c = conjs[i].get();
expr *l = nullptr, *r = nullptr;
if (m.is_false(c)) {
conjs[0] = c;
conjs.resize(1);
break;
}
if (is_ground(c)) {
continue;
}
if (!m.is_eq(c, l, r)) {
continue;
}
if (!is_app(l) || !is_app(r)) {
continue;
}
if (dt.is_constructor(to_app(l)->get_decl())) {
flatten_constructor(to_app(l), to_app(r), conjs);
conjs[i] = conjs.back();
conjs.pop_back();
--i;
continue;
}
if (dt.is_constructor(to_app(r)->get_decl())) {
flatten_constructor(to_app(r), to_app(l), conjs);
conjs[i] = conjs.back();
conjs.pop_back();
--i;
continue;
}
}
TRACE("qe_lite",
expr_ref tmp(m);
tmp = m.mk_and(conjs.size(), conjs.data());
tout << "after flatten\n" << mk_pp(tmp, m) << "\n";);
}
void flatten_constructor(app* c, app* r, expr_ref_vector& conjs) {
SASSERT(dt.is_constructor(c));
func_decl* d = c->get_decl();
if (dt.is_constructor(r->get_decl())) {
app* b = to_app(r);
if (d == b->get_decl()) {
for (unsigned j = 0; j < c->get_num_args(); ++j) {
conjs.push_back(m.mk_eq(c->get_arg(j), b->get_arg(j)));
}
}
else {
conjs.push_back(m.mk_false());
}
}
else {
func_decl* rec = dt.get_constructor_is(d);
conjs.push_back(m.mk_app(rec, r));
ptr_vector const& acc = *dt.get_constructor_accessors(d);
for (unsigned i = 0; i < acc.size(); ++i) {
conjs.push_back(m.mk_eq(c->get_arg(i), m.mk_app(acc[i], r)));
}
}
}
bool is_unconstrained(var* x, expr* t, unsigned i, expr_ref_vector const& conjs) {
sort* s = x->get_sort();
if (!m.is_fully_interp(s) || !s->get_num_elements().is_infinite()) return false;
bool occ = occurs_var(x->get_idx(), t);
for (unsigned j = 0; !occ && j < conjs.size(); ++j) {
occ = (i != j) && occurs_var(x->get_idx(), conjs[j]);
}
return !occ;
}
bool remove_unconstrained(expr_ref_vector& conjs) {
bool reduced = false, change = true;
expr *r = nullptr, *l = nullptr, *ne = nullptr;
while (change) {
change = false;
for (unsigned i = 0; i < conjs.size(); ++i) {
if (m.is_not(conjs[i].get(), ne) && m.is_eq(ne, l, r)) {
TRACE("qe_lite", tout << mk_pp(conjs[i].get(), m) << " " << is_variable(l) << " " << is_variable(r) << "\n";);
if (is_variable(l) && ::is_var(l) && is_unconstrained(::to_var(l), r, i, conjs)) {
conjs[i] = m.mk_true();
reduced = true;
change = true;
}
else if (is_variable(r) && ::is_var(r) && is_unconstrained(::to_var(r), l, i, conjs)) {
conjs[i] = m.mk_true();
reduced = true;
change = true;
}
}
}
}
return reduced;
}
bool reduce_var_set(expr_ref_vector& conjs) {
unsigned def_count = 0;
unsigned largest_vinx = 0;
bool reduced = false;
flatten_definitions(conjs);
find_definitions(conjs.size(), conjs.data(), 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.data());
create_substitution(largest_vinx + 1);
new_r = m_subst(r, m_subst_map.size(), m_subst_map.data());
m_rewriter(new_r);
conjs.reset();
flatten_and(new_r, conjs);
reduced = true;
}
}
if (remove_unconstrained(conjs)) {
reduced = true;
}
return reduced;
}
void checkpoint() {
tactic::checkpoint(m);
}
public:
eq_der(ast_manager & m, params_ref const & p):
m(m),
a(m),
dt(m),
bv(m),
m_is_variable(nullptr),
m_subst(m),
m_subst_map(m),
m_new_exprs(m),
m_new_args(m),
m_rewriter(m),
m_params(p) {
}
void set_is_variable_proc(is_variable_proc& proc) {
m_is_variable = &proc;
m_solvers.reset();
m_solvers.register_plugin(mbp::mk_arith_solve_plugin(m, proc));
m_solvers.register_plugin(mbp::mk_basic_solve_plugin(m, proc));
m_solvers.register_plugin(mbp::mk_bv_solve_plugin(m, proc));
}
void operator()(quantifier * q, expr_ref & r, proof_ref & pr) {
TRACE("qe_lite", tout << mk_pp(q, m) << "\n";);
pr = nullptr;
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 {
checkpoint();
proof_ref curr_pr(m);
q = to_quantifier(r);
reduce_quantifier1(q, r, curr_pr);
if (m.proofs_enabled() && r != q) {
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; }
};
// ------------------------------------------------------------
// basic destructive equality (and disequality) resolution for arrays.
class ar_der {
ast_manager& m;
array_util a;
is_variable_proc* m_is_variable;
ptr_vector m_todo;
expr_mark m_visited;
bool is_variable(expr * e) const {
return (*m_is_variable)(e);
}
void mark_all(expr* e) {
for_each_expr(*this, m_visited, e);
}
void mark_all(expr_ref_vector const& fmls, unsigned j) {
for (unsigned i = 0; i < fmls.size(); ++i) {
if (i != j) {
mark_all(fmls[i]);
}
}
}
/**
Ex A. A[x] = t & Phi[A] where x \not\in A, t. A \not\in t, x
=>
Ex A. Phi[store(A,x,t)]
(Not implemented)
Perhaps also:
Ex A. store(A,y,z)[x] = t & Phi[A] where x \not\in A, t, y, z, A \not\in y z, t
=>
Ex A, v . (x = y => z = t) & Phi[store(store(A,x,t),y,v)]
*/
bool solve_select(expr_ref_vector& conjs, unsigned i, expr* e1, expr* e2) {
if (a.is_select(e1)) {
app* a1 = to_app(e1);
expr* A = a1->get_arg(0);
if (!is_variable(A)) {
return false;
}
m_visited.reset();
for (unsigned j = 1; j < a1->get_num_args(); ++j) {
mark_all(a1->get_arg(j));
}
mark_all(e2);
if (m_visited.is_marked(A)) {
return false;
}
ptr_vector args;
args.push_back(A);
args.append(a1->get_num_args()-1, a1->get_args()+1);
args.push_back(e2);
expr* B = a.mk_store(args.size(), args.data());
expr_safe_replace rep(m);
rep.insert(A, B);
expr_ref tmp(m);
TRACE("qe_lite",
tout << mk_pp(e1, m) << " = " << mk_pp(e2, m) << "\n";);
for (unsigned j = 0; j < conjs.size(); ++j) {
if (i == j) {
conjs[j] = m.mk_true();
}
else {
rep(conjs[j].get(), tmp);
conjs[j] = tmp;
}
}
return true;
}
return false;
}
bool solve_select(expr_ref_vector& conjs, unsigned i, expr* e) {
expr* e1, *e2;
return
m.is_eq(e, e1, e2) &&
(solve_select(conjs, i, e1, e2) ||
solve_select(conjs, i, e2, e1));
}
/**
Ex x. A[x] != B[x] & Phi where x \not\in A, B, Phi
=>
A != B & Phi
*/
bool solve_neq_select(expr_ref_vector& conjs, unsigned i, expr* e) {
expr* e1, *a1, *a2;
if (m.is_not(e, e1) && m.is_eq(e1, a1, a2)) {
if (a.is_select(a1) &&
a.is_select(a2) &&
to_app(a1)->get_num_args() == to_app(a2)->get_num_args()) {
expr* e1 = to_app(a1)->get_arg(0);
expr* e2 = to_app(a2)->get_arg(0);
m_visited.reset();
mark_all(conjs, i);
mark_all(e1);
mark_all(e2);
for (unsigned j = 1; j < to_app(a1)->get_num_args(); ++j) {
expr* x = to_app(a1)->get_arg(j);
expr* y = to_app(a2)->get_arg(j);
if (!is_variable(x)) {
return false;
}
if (x != y) {
return false;
}
if (m_visited.is_marked(x)) {
return false;
}
}
conjs[i] = m.mk_not(m.mk_eq(e1, e2));
return true;
}
}
return false;
}
void checkpoint() {
tactic::checkpoint(m);
}
public:
ar_der(ast_manager& m): m(m), a(m), m_is_variable(nullptr) {}
void operator()(expr_ref_vector& fmls) {
for (unsigned i = 0; i < fmls.size(); ++i) {
checkpoint();
solve_select(fmls, i, fmls[i].get());
solve_neq_select(fmls, i, fmls[i].get());
}
}
void operator()(expr* e) {}
void set_is_variable_proc(is_variable_proc& proc) { m_is_variable = &proc;}
};
// ------------------------------------------------------------
// fm_tactic adapted to eliminate designated de-Bruijn 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;
signed_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;
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 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 {
bool result = false;
m.is_not(t, t);
expr * lhs, * rhs;
if (m_util.is_le(t, lhs, rhs) || m_util.is_ge(t, lhs, rhs)) {
result = m_util.is_numeral(rhs) && is_linear_pol(lhs);
}
TRACE("qe_lite", tout << mk_pp(t, m) << " " << (result?"true":"false") << "\n";);
return result;
}
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.data());
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("qe_lite", 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("qe_lite", 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):
m(_m),
m_is_variable(nullptr),
m_allocator("fm-elim"),
m_util(m),
m_bvar2expr(m),
m_var2expr(m),
m_new_fmls(m),
m_inconsistent_core(m) {
updt_params();
m_counter = 0;
m_inconsistent = false;
}
~fm() {
reset_constraints();
}
void updt_params() {
m_fm_real_only = false;
m_fm_limit = 5000000;
m_fm_cutoff1 = 8;
m_fm_cutoff2 = 256;
m_fm_extra = 0;
m_fm_occ = true;
}
private:
struct forbidden_proc {
fm & m_owner;
forbidden_proc(fm & o):m_owner(o) {}
void operator()(::var * n) {
if (m_owner.is_var(n) && n->get_sort()->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)) {
TRACE("qe_lite", tout << "OCC: " << mk_ismt2_pp(f, m) << "\n";);
continue;
}
TRACE("qe_lite", 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(nullptr); // 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 = nullptr;
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 = nullptr;
}
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.data());
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.data());
}
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());
TRACE("qe_lite", tout << mk_pp(t,m) << " |-> " << x << " forbidden: " << forbidden << "\n";);
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("qe_lite", 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) {
TRACE("qe_lite", tout << mk_pp(f, m) << "\n";);
SASSERT(!m.is_or(f) || m_fm_occ);
sbuffer lits;
sbuffer xs;
buffer as;
rational c;
bool strict = false;
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);
VERIFY (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 = nullptr;
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.data(), c);
if (all_int && strict) {
strict = false;
c--;
}
}
}
TRACE("qe_lite", 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.data(),
xs.size(),
xs.data(),
as.data(),
c,
strict,
dep);
TRACE("qe_lite", 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("qe_lite", 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("qe_lite", 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, nullptr);
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("qe_lite",
for (auto const& kv : x_cost_vector) {
tout << "(" << mk_ismt2_pp(m_var2expr.get(kv.first), m) << " " << kv.second << ") ";
}
tout << "\n";);
for (auto const& kv : x_cost_vector) {
xs.push_back(kv.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;
for (constraint const* c : cs) {
bool curr_unit_coeff;
analyze(*c, 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 constraints 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) {
for (constraint const* cns : s) {
app * c = to_expr(*cns);
t.push_back(c);
}
}
clauses tmp_clauses;
void save_constraints(var x) { }
void mark_constraints_dead(constraints const & cs) {
for (constraint* c : cs)
c->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("qe_lite", tout << "resolution " << x << " consequent literal is always true: \n";
display(tout, l);
tout << "\n";
display(tout, u); tout << "\n";);
return nullptr; // 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("qe_lite", tout << "resolution " << x << " tautology: \n";
display(tout, l);
tout << "\n";
display(tout, u); tout << "\n";);
return nullptr;
}
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("qe_lite", tout << "resolution " << x << " inconsistent: \n";
display(tout, l);
tout << "\n";
display(tout, u); tout << "\n";);
m_inconsistent = true;
m_inconsistent_core = new_dep;
return nullptr;
}
constraint * new_cnstr = mk_constraint(new_lits.size(),
new_lits.data(),
new_xs.size(),
new_xs.data(),
new_as.data(),
new_c,
new_strict,
new_dep);
TRACE("qe_lite", 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("qe_lite", 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("qe_lite", 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("qe_lite", tout << "too many new constraints: " << num_new_cnstrs << "\n";);
del_constraints(new_constraints.size(), new_constraints.data());
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 != nullptr) {
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("qe_lite", tout << "variables was eliminated old: " << num_old_cnstrs << " new_constraints: " << sz << "\n";);
return true;
}
void copy_remaining(vector & v2cs) {
for (constraints& cs : v2cs) {
for (constraint* c : cs) {
if (!c->m_dead) {
c->m_dead = true;
expr * new_f = to_expr(*c);
TRACE("qe_lite", 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() {
tactic::checkpoint(m);
}
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);
for (auto & f : fmls) {
if (has_quantifiers(f)) return;
}
if (m_inconsistent) {
m_new_fmls.reset();
m_new_fmls.push_back(m.mk_false());
}
else {
TRACE("qe_lite", display(tout););
subsume();
var_vector candidates;
sort_candidates(candidates);
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])) {
}
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 {
for (constraint const* c : cs) {
display(out << " ", *c);
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
} // anonymous namespace
class qe_lite::impl {
struct elim_cfg : public default_rewriter_cfg {
impl& m_imp;
ast_manager& m;
public:
elim_cfg(impl& i): m_imp(i), m(i.m) {}
bool reduce_quantifier(quantifier * q,
expr * new_body,
expr * const * new_patterns,
expr * const * new_no_patterns,
expr_ref & result,
proof_ref & result_pr) {
result = new_body;
if (is_forall(q)) {
result = m.mk_not(result);
}
uint_set indices;
for (unsigned i = 0; i < q->get_num_decls(); ++i) {
indices.insert(i);
}
if (q->get_kind() != lambda_k) {
m_imp(indices, true, result);
}
if (is_forall(q)) {
result = push_not(result);
}
expr_ref tmp(m);
tmp = m.update_quantifier(
q,
q->get_num_patterns(), new_patterns,
q->get_num_no_patterns(), new_no_patterns, result);
m_imp.m_rewriter(tmp, result, result_pr);
if (m.proofs_enabled()) {
result_pr = m.mk_transitivity(m.mk_rewrite(q, tmp), result_pr);
}
return true;
}
};
class elim_star : public rewriter_tpl {
elim_cfg m_cfg;
public:
elim_star(impl& i):
rewriter_tpl(i.m, i.m.proofs_enabled(), m_cfg),
m_cfg(i)
{}
};
private:
ast_manager& m;
qel::eq_der m_der;
qel::fm::fm m_fm;
qel::ar_der m_array_der;
elim_star m_elim_star;
th_rewriter m_rewriter;
bool m_use_array_der;
bool has_unique_non_ground(expr_ref_vector const& fmls, unsigned& index) {
index = fmls.size();
if (index <= 1) {
return false;
}
for (unsigned i = 0; i < fmls.size(); ++i) {
if (!is_ground(fmls[i])) {
if (index != fmls.size()) {
return false;
}
index = i;
}
}
return index < fmls.size();
}
public:
impl(ast_manager & m, params_ref const & p, bool use_array_der):
m(m),
m_der(m, p),
m_fm(m),
m_array_der(m),
m_elim_star(*this),
m_rewriter(m),
m_use_array_der(use_array_der) {}
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.data(), fml, tmp);
ptr_vector sorts;
svector names;
for (unsigned i = 0; i < vars.size(); ++i) {
sorts.push_back(vars[i]->get_sort());
names.push_back(vars[i]->get_decl()->get_name());
}
q = m.mk_exists(vars.size(), sorts.data(), names.data(), 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(tmp);
var_subst vs(m, true);
fml = vs(tmp, vars.size(), (expr*const*)vars.data());
// 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 = std::move(tmp);
}
}
void operator()(expr_ref& fml, proof_ref& pr) {
expr_ref tmp(m);
m_elim_star(fml, tmp, pr);
if (m.proofs_enabled()) {
pr = m.mk_rewrite(fml, tmp);
}
fml = std::move(tmp);
}
void operator()(uint_set const& index_set, bool index_of_bound, expr_ref& fml) {
expr_ref_vector disjs(m), conjs(m);
flatten_or(fml, disjs);
for (unsigned i = 0, e = disjs.size(); i != e; ++i) {
conjs.reset();
conjs.push_back(disjs[i].get());
(*this)(index_set, index_of_bound, conjs);
bool_rewriter(m).mk_and(conjs.size(), conjs.data(), fml);
disjs[i] = std::move(fml);
}
bool_rewriter(m).mk_or(disjs.size(), disjs.data(), fml);
}
void operator()(uint_set const& index_set, bool index_of_bound, expr_ref_vector& fmls) {
flatten_and(fmls);
unsigned index;
if (has_unique_non_ground(fmls, index)) {
expr_ref fml(m);
fml = fmls[index].get();
(*this)(index_set, index_of_bound, fml);
fmls[index] = fml;
return;
}
TRACE("qe_lite", tout << fmls << "\n";);
is_variable_test is_var(index_set, index_of_bound);
m_der.set_is_variable_proc(is_var);
m_fm.set_is_variable_proc(is_var);
m_array_der.set_is_variable_proc(is_var);
m_der(fmls);
m_fm(fmls);
// AG: disable m_array_der() since it interferes with other array handling
if (m_use_array_der) m_array_der(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, params_ref const & p, bool use_array_der) {
m_impl = alloc(impl, m, p, use_array_der);
}
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);
}
namespace {
class qe_lite_simplifier : public dependent_expr_simplifier {
params_ref m_params;
qe_lite m_qe;
public:
qe_lite_simplifier(ast_manager& m, params_ref const& p, dependent_expr_state& st) :
dependent_expr_simplifier(m, st),
m_qe(m, p, true) {
updt_params(p);
}
char const* name() const override { return "qe-lite"; }
void updt_params(params_ref const& p) override {
m_params.append(p);
}
void reduce() override {
if (!m_fmls.has_quantifiers())
return;
proof_ref new_pr(m);
expr_ref new_f(m);
for (unsigned i : indices()) {
auto [f, p, d] = m_fmls[i]();
if (!has_quantifiers(f))
continue;
new_f = f;
m_qe(new_f, new_pr);
if (f != new_f)
m_fmls.update(i, dependent_expr(m, new_f, mp(p, new_pr), d));
}
}
};
}
tactic * mk_qe_lite_tactic(ast_manager & m, params_ref const & p) {
return alloc(dependent_expr_state_tactic, m, p, mk_qe_lite_simplifier);
}
dependent_expr_simplifier* mk_qe_lite_simplifier(ast_manager& m, params_ref const& p, dependent_expr_state& st) {
return alloc(qe_lite_simplifier, m, p, st);
}