mirror of
https://github.com/Z3Prover/z3
synced 2025-04-10 19:27:06 +00:00
1052 lines
36 KiB
C++
1052 lines
36 KiB
C++
/*++
|
|
Copyright (c) 2012 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
pb2bv_tactic.cpp
|
|
|
|
Abstract:
|
|
|
|
Tactic for converting Pseudo-Boolean constraints to BV
|
|
|
|
Author:
|
|
|
|
Christoph (cwinter) 2012-02-15
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
#include"tactical.h"
|
|
#include"cooperate.h"
|
|
#include"bound_manager.h"
|
|
#include"bool_rewriter.h"
|
|
#include"rewriter_def.h"
|
|
#include"ref_util.h"
|
|
#include"arith_decl_plugin.h"
|
|
#include"trace.h"
|
|
#include"ast_smt2_pp.h"
|
|
#include"expr_substitution.h"
|
|
#include"filter_model_converter.h"
|
|
#include"pb2bv_model_converter.h"
|
|
#include"pb2bv_tactic.h"
|
|
|
|
class pb2bv_tactic : public tactic {
|
|
public:
|
|
struct non_pb {};
|
|
|
|
struct only_01_visitor {
|
|
typedef rational numeral;
|
|
ast_manager & m;
|
|
arith_util & m_util;
|
|
bound_manager & m_bm;
|
|
|
|
only_01_visitor(arith_util & u, bound_manager & bm):
|
|
m(u.get_manager()),
|
|
m_util(u),
|
|
m_bm(bm) {
|
|
}
|
|
|
|
void throw_non_pb(expr * n) {
|
|
TRACE("pb2bv", tout << "Not pseudo-Boolean: " << mk_ismt2_pp(n, m) << "\n";);
|
|
throw non_pb();
|
|
}
|
|
|
|
void operator()(var * n) {
|
|
throw_non_pb(n);
|
|
}
|
|
|
|
void operator()(app * n) {
|
|
family_id fid = n->get_family_id();
|
|
if (fid == m.get_basic_family_id()) {
|
|
// all basic family ops (but term-ite and distinct) are OK
|
|
if (m.is_term_ite(n) || m.is_distinct(n))
|
|
throw_non_pb(n);
|
|
return;
|
|
}
|
|
|
|
if (fid == m_util.get_family_id()) {
|
|
// check if linear
|
|
switch (n->get_decl_kind()) {
|
|
case OP_LE: case OP_GE: case OP_LT: case OP_GT:
|
|
case OP_ADD: case OP_NUM:
|
|
return;
|
|
case OP_MUL:
|
|
if (n->get_num_args() != 2)
|
|
throw_non_pb(n);
|
|
if (!m_util.is_numeral(n->get_arg(0)))
|
|
throw_non_pb(n);
|
|
return;
|
|
default:
|
|
throw_non_pb(n);
|
|
}
|
|
}
|
|
|
|
if (is_uninterp_const(n)) {
|
|
if (m.is_bool(n))
|
|
return; // boolean variables are ok
|
|
if (m_util.is_int(n)) {
|
|
numeral l, u; bool s;
|
|
if (m_bm.has_lower(n, l, s) &&
|
|
m_bm.has_upper(n, u, s) &&
|
|
(l.is_zero() || l.is_one()) &&
|
|
(u.is_zero() || u.is_one()))
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw_non_pb(n);
|
|
}
|
|
|
|
void operator()(quantifier * n) {
|
|
throw_non_pb(n);
|
|
}
|
|
};
|
|
private:
|
|
|
|
struct imp {
|
|
typedef rational numeral;
|
|
|
|
ast_manager & m;
|
|
bound_manager m_bm;
|
|
bool_rewriter m_b_rw;
|
|
arith_util m_arith_util;
|
|
bv_util m_bv_util;
|
|
expr_dependency_ref_vector m_new_deps;
|
|
|
|
bool m_produce_models;
|
|
bool m_produce_unsat_cores;
|
|
|
|
unsigned m_all_clauses_limit;
|
|
unsigned m_cardinality_limit;
|
|
unsigned long long m_max_memory;
|
|
// m_const2bit should be a map, since we want constant time access to it, and avoid quadratic behavior.
|
|
// It is ok to use a vector at the model converter because we don't need to search that vector.
|
|
obj_map<func_decl, expr*> m_const2bit;
|
|
obj_map<func_decl, expr*> m_not_const2bit;
|
|
expr_ref_vector m_temporary_ints;
|
|
|
|
expr_dependency_ref m_used_dependencies;
|
|
|
|
struct lit {
|
|
expr * m_v;
|
|
public:
|
|
lit(expr * v, bool sign = false):m_v(TAG(expr*, v, sign)) {}
|
|
bool sign() const { return GET_TAG(m_v) == 1; }
|
|
expr * var() const { return UNTAG(expr*, m_v); }
|
|
void neg() {
|
|
#ifdef Z3DEBUG
|
|
bool s = sign();
|
|
#endif
|
|
m_v = TAG(expr*, UNTAG(expr*, m_v), !sign());
|
|
SASSERT(s == !sign());
|
|
}
|
|
};
|
|
|
|
struct monomial {
|
|
numeral m_a;
|
|
lit m_lit;
|
|
monomial(lit l):m_a(1), m_lit(l) {}
|
|
monomial(numeral const & a, lit l):m_a(a), m_lit(l) {}
|
|
};
|
|
|
|
typedef vector<monomial> polynomial;
|
|
|
|
struct monomial_lt {
|
|
bool operator()(monomial const & m1, monomial const & m2) const { return m1.m_a > m2.m_a; }
|
|
};
|
|
|
|
enum constraint_kind { EQ, GE, LE };
|
|
|
|
struct failed {};
|
|
|
|
struct visitor {
|
|
imp & m_owner;
|
|
|
|
visitor(imp & o):m_owner(o) {}
|
|
|
|
void throw_failed(expr * n) {
|
|
throw failed();
|
|
}
|
|
|
|
void operator()(var * n) {
|
|
throw_failed(n);
|
|
}
|
|
|
|
void operator()(app * n) {
|
|
}
|
|
|
|
void operator()(quantifier * n) {
|
|
throw_failed(n);
|
|
}
|
|
};
|
|
|
|
void checkpoint() {
|
|
cooperate("pb2bv");
|
|
if (memory::get_allocation_size() > m_max_memory)
|
|
throw tactic_exception(TACTIC_MAX_MEMORY_MSG);
|
|
}
|
|
|
|
void quick_pb_check(goal_ref const & g) {
|
|
expr_fast_mark1 visited;
|
|
only_01_visitor proc(m_arith_util, m_bm);
|
|
unsigned sz = g->size();
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
expr * f = g->form(i);
|
|
for_each_expr_core<only_01_visitor, expr_fast_mark1, true, true>(proc, visited, f);
|
|
}
|
|
}
|
|
|
|
struct rw_cfg : public default_rewriter_cfg {
|
|
ast_manager & m;
|
|
imp & owner;
|
|
expr_ref m_saved_res;
|
|
|
|
rw_cfg(imp & o):
|
|
m(o.m),
|
|
owner(o),
|
|
m_saved_res(m) {
|
|
}
|
|
|
|
bool max_steps_exceeded(unsigned num_steps) const {
|
|
cooperate("pb2bv");
|
|
if (memory::get_allocation_size() > owner.m_max_memory)
|
|
throw tactic_exception(TACTIC_MAX_MEMORY_MSG);
|
|
return false;
|
|
}
|
|
|
|
bool get_subst(expr * s, expr * & t, proof * & t_pr) {
|
|
t_pr = 0;
|
|
if (owner.is_constraint_core(s)) {
|
|
owner.convert(to_app(s), m_saved_res, true, false);
|
|
t = m_saved_res;
|
|
TRACE("pb2bv_convert", tout << mk_ismt2_pp(s, m) << "\n-->\n" << mk_ismt2_pp(t, m) << "\n";);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct rw : public rewriter_tpl<rw_cfg> {
|
|
rw_cfg m_cfg;
|
|
|
|
rw(imp & o):
|
|
rewriter_tpl<rw_cfg>(o.m, false, m_cfg),
|
|
m_cfg(o) {
|
|
}
|
|
};
|
|
|
|
rw m_rw;
|
|
|
|
struct pb2bv_all_clauses {
|
|
imp & m_owner;
|
|
ast_manager & m;
|
|
unsigned m_size;
|
|
vector<numeral> m_sums;
|
|
expr_ref_vector m_lits;
|
|
ptr_vector<expr> m_cls;
|
|
polynomial const * m_pol;
|
|
expr_ref_vector m_result;
|
|
|
|
pb2bv_all_clauses(imp & owner):
|
|
m_owner(owner),
|
|
m(m_owner.m),
|
|
m_lits(m),
|
|
m_result(m) {
|
|
}
|
|
|
|
void init_lits(polynomial const & p) {
|
|
polynomial::const_iterator it = p.begin();
|
|
polynomial::const_iterator end = p.end();
|
|
for (; it != end; ++it)
|
|
m_lits.push_back(m_owner.mon_lit2lit(it->m_lit));
|
|
}
|
|
|
|
void init_sums(polynomial const & p) {
|
|
SASSERT(m_sums.empty());
|
|
m_size = p.size();
|
|
m_sums.resize(m_size);
|
|
unsigned i = m_size;
|
|
while (i > 0) {
|
|
--i;
|
|
if (i == m_size - 1)
|
|
m_sums[i] = p[i].m_a;
|
|
else
|
|
m_sums[i] = p[i].m_a + m_sums[i+1];
|
|
}
|
|
}
|
|
|
|
void process(unsigned idx, numeral c) {
|
|
if (c.is_nonpos())
|
|
return;
|
|
|
|
if (idx == m_size || m_sums[idx] < c) {
|
|
SASSERT(c.is_pos());
|
|
// conflict 0 >= c > 0
|
|
switch (m_cls.size()) {
|
|
case 0: m_result.push_back(m.mk_false()); break;
|
|
case 1: m_result.push_back(m_cls[0]); break;
|
|
default: m_result.push_back(m.mk_or(m_cls.size(), m_cls.c_ptr()));
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_owner.checkpoint();
|
|
|
|
m_cls.push_back(m_lits.get(idx));
|
|
process(idx+1, c);
|
|
m_cls.pop_back();
|
|
process(idx+1, c - (*m_pol)[idx].m_a);
|
|
}
|
|
|
|
void operator()(polynomial const & m_p, numeral const & m_c, expr_ref & r) {
|
|
m_pol = &(m_p);
|
|
init_sums(m_p);
|
|
init_lits(m_p);
|
|
process(0, m_c);
|
|
m_owner.m_b_rw.mk_and(m_result.size(), m_result.c_ptr(), r);
|
|
}
|
|
};
|
|
|
|
void display(std::ostream & out, polynomial const & m_p, numeral const & m_c) const {
|
|
polynomial::const_iterator it = m_p.begin();
|
|
polynomial::const_iterator end = m_p.end();
|
|
for (bool first = true; it != end; ++it) {
|
|
if (!first)
|
|
out << " + ";
|
|
first = false;
|
|
if (!it->m_a.is_one())
|
|
out << it->m_a << "*";
|
|
if (it->m_lit.sign())
|
|
out << "~";
|
|
out << mk_ismt2_pp(it->m_lit.var(), m);
|
|
}
|
|
out << " >= " << m_c << "\n";
|
|
}
|
|
|
|
expr * int2lit(app * x, bool sign = false) {
|
|
func_decl * fd = x->get_decl();
|
|
obj_map<func_decl, expr*> & const2lit = sign ? m_not_const2bit : m_const2bit;
|
|
|
|
expr * r = 0;
|
|
const2lit.find(fd, r);
|
|
if (r != 0)
|
|
return r;
|
|
|
|
r = m.mk_fresh_const(0, m.mk_bool_sort());
|
|
expr * not_r = m.mk_not(r);
|
|
m_const2bit.insert(fd, r);
|
|
m_not_const2bit.insert(fd, not_r);
|
|
m.inc_ref(fd);
|
|
m.inc_ref(r);
|
|
m.inc_ref(not_r);
|
|
|
|
return sign ? not_r : r;
|
|
}
|
|
|
|
expr * mon_lit2lit(monomial const & mo) {
|
|
return int2lit(to_app(mo.m_lit.var()), mo.m_lit.sign());
|
|
}
|
|
|
|
expr * mk_unit(expr * t, bool sign) {
|
|
return mon_lit2lit(lit(t, sign));
|
|
}
|
|
|
|
static bool is_cardinality(polynomial const & m_p, numeral const & m_c) {
|
|
for (unsigned i = 0; i < m_p.size(); i++) {
|
|
if (!m_p[i].m_a.is_one())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void bitblast_pbc(polynomial & m_p, numeral const & m_c, expr_ref & r) {
|
|
bool is_card = is_cardinality(m_p, m_c);
|
|
|
|
if (is_card && numeral(m_p.size()) < m_c) {
|
|
r = m.mk_false();
|
|
return;
|
|
}
|
|
|
|
if (is_card && m_c.is_one()) {
|
|
ptr_buffer<expr> args;
|
|
for (unsigned i = 0; i < m_p.size(); i++) {
|
|
args.push_back(mon_lit2lit(m_p[i]));
|
|
}
|
|
r = m.mk_or(args.size(), args.c_ptr());
|
|
return;
|
|
}
|
|
|
|
if (is_card && m_c == numeral(m_p.size())) {
|
|
ptr_buffer<expr> args;
|
|
for (unsigned i = 0; i < m_p.size(); i++) {
|
|
args.push_back(mon_lit2lit(m_p[i]));
|
|
}
|
|
m_b_rw.mk_and(args.size(), args.c_ptr(), r);
|
|
return;
|
|
}
|
|
|
|
if (m_p.size() <= m_all_clauses_limit) {
|
|
pb2bv_all_clauses proc(*this);
|
|
proc(m_p, m_c, r);
|
|
return;
|
|
}
|
|
|
|
if (is_card) {
|
|
SASSERT(m_c < numeral(m_p.size())); // After normalization, this should be true.
|
|
SASSERT(m_c.is_unsigned()); // Otherwise this is not going to fit into memory...
|
|
|
|
unsigned n = m_p.size();
|
|
unsigned k = m_c.get_unsigned();
|
|
unsigned rowsz = n - k + 1;
|
|
|
|
unsigned long long cost = k * rowsz;
|
|
if (cost <= static_cast<unsigned long long>(m_cardinality_limit)) {
|
|
SASSERT(rowsz > 0);
|
|
|
|
expr_ref_vector tmp(m);
|
|
tmp.resize(rowsz, m.mk_true());
|
|
|
|
for (unsigned i = 0; i < k; i++) {
|
|
for (unsigned j = 0; j < rowsz; j++) {
|
|
expr_ref new_ite(m);
|
|
m_b_rw.mk_ite(mon_lit2lit(m_p[i + j]),
|
|
tmp.get(j),
|
|
j == 0 ? m.mk_false() : tmp.get(j-1),
|
|
new_ite);
|
|
tmp.set(j, new_ite.get());
|
|
}
|
|
}
|
|
|
|
TRACE("pb2bv_bv", tout << "BV Cardinality: " << mk_ismt2_pp(tmp.back(), m) << std::endl;);
|
|
r = tmp.back();
|
|
return;
|
|
}
|
|
}
|
|
|
|
TRACE("pb2bv_bv_detail", tout << "encoding:\n"; display(tout, m_p, m_c););
|
|
// [Leo] improving number of bits needed.
|
|
// using (sum-of-coeffs).get_num_bits()
|
|
numeral sum;
|
|
for (unsigned i = 0; i < m_p.size(); i++) {
|
|
monomial const & mo = m_p[i];
|
|
SASSERT(mo.m_a.is_pos());
|
|
sum += mo.m_a;
|
|
}
|
|
|
|
if (sum < m_c) {
|
|
// trivially false.
|
|
r = m.mk_false();
|
|
return;
|
|
}
|
|
|
|
unsigned bits = sum.get_num_bits();
|
|
|
|
TRACE("num_bits_bug", tout << "bits: " << bits << " sum: " << sum << " size: " << m_p.size() << "\n";);
|
|
|
|
// [Leo]: The following assertion should hold, right?
|
|
// I mean, the constraints are normalized, then mo.m_a <= m_c for every monomial in cnstr.
|
|
// [Christoph]: I agree and never saw it violated so far!
|
|
SASSERT(m_c.get_num_bits() <= bits);
|
|
|
|
ptr_buffer<expr> lhs_args;
|
|
|
|
for (unsigned i = 0; i < m_p.size(); i++) {
|
|
monomial const & mo = m_p[i];
|
|
// encode using if-then-else
|
|
expr * bv_monom =
|
|
m.mk_ite(mon_lit2lit(mo.m_lit),
|
|
m_bv_util.mk_numeral(mo.m_a, bits),
|
|
m_bv_util.mk_numeral(numeral(0), bits));
|
|
lhs_args.push_back(bv_monom);
|
|
}
|
|
|
|
expr * lhs = m.mk_app(m_bv_util.get_family_id(), OP_BADD, lhs_args.size(), lhs_args.c_ptr());
|
|
expr * rhs = m_bv_util.mk_numeral(m_c, bits);
|
|
|
|
r = m_bv_util.mk_ule(rhs, lhs);
|
|
}
|
|
|
|
void split(polynomial & m_p, numeral & m_c, polynomial & m_clause) {
|
|
if (m_p.size() <= 2 || m_c.is_one())
|
|
return;
|
|
|
|
if (m_p[0].m_a != m_c || m_p[1].m_a != m_c)
|
|
return; // nothing to do.
|
|
|
|
unsigned sz = m_p.size();
|
|
unsigned i;
|
|
for (i = 2; i < sz; i++) {
|
|
if (m_p[i].m_a != m_c)
|
|
break;
|
|
}
|
|
|
|
SASSERT (i < sz);
|
|
|
|
// copy lits [0, i) to m_clause
|
|
for (unsigned j = 0; j < i; j++)
|
|
m_clause.push_back(monomial(numeral(1), m_p[j].m_lit));
|
|
|
|
app * new_var = m.mk_fresh_const(0, m_arith_util.mk_int());
|
|
m_temporary_ints.push_back(new_var);
|
|
|
|
m_clause.push_back(monomial(numeral(1), lit(new_var, true)));
|
|
|
|
// remove monomials [0, i) from m_p and add new_var in the beginning
|
|
for (unsigned j = i; j < sz; j++) {
|
|
m_p[j - i + 1] = m_p[j];
|
|
}
|
|
m_p.shrink(sz - i + 1);
|
|
m_p[0] = monomial(m_c, lit(new_var, false));
|
|
}
|
|
|
|
void mk_pbc(polynomial & m_p, numeral & m_c, expr_ref & r, bool enable_split) {
|
|
if (m_c.is_nonpos()) {
|
|
// constraint is equivalent to true.
|
|
r = m.mk_true();
|
|
return;
|
|
}
|
|
polynomial::iterator it = m_p.begin();
|
|
polynomial::iterator end = m_p.end();
|
|
numeral a_gcd = it->m_a;
|
|
for (; it != end; ++it) {
|
|
if (it->m_a > m_c)
|
|
it->m_a = m_c; // trimming coefficients
|
|
a_gcd = gcd(a_gcd, it->m_a);
|
|
}
|
|
SASSERT(a_gcd.is_pos());
|
|
if (!a_gcd.is_one()) {
|
|
it = m_p.begin();
|
|
for (; it != end; ++it)
|
|
it->m_a /= a_gcd;
|
|
m_c = ceil(m_c/a_gcd);
|
|
}
|
|
it = m_p.begin();
|
|
numeral a_sum;
|
|
for (; it != end; ++it) {
|
|
a_sum += m_c;
|
|
if (a_sum >= m_c)
|
|
break;
|
|
}
|
|
if (a_sum < m_c) {
|
|
// constraint is equivalent to false.
|
|
r = m.mk_false();
|
|
return;
|
|
}
|
|
polynomial clause;
|
|
TRACE("split_bug", display(tout, m_p, m_c););
|
|
if (enable_split)
|
|
split(m_p, m_c, clause);
|
|
TRACE("split_bug", display(tout, m_p, m_c); display(tout, clause, rational(1)););
|
|
if (clause.empty()) {
|
|
bitblast_pbc(m_p, m_c, r);
|
|
}
|
|
else {
|
|
expr_ref r1(m);
|
|
expr_ref r2(m);
|
|
bitblast_pbc(m_p, m_c, r1);
|
|
bitblast_pbc(clause, numeral(1), r2);
|
|
TRACE("split_bug", tout << mk_ismt2_pp(r1, m) << "\nAND\n" << mk_ismt2_pp(r2, m) << "\n";);
|
|
m_b_rw.mk_and(r1, r2, r);
|
|
}
|
|
}
|
|
|
|
void adjust(bool & pos, constraint_kind & k, numeral & c) {
|
|
if (!pos) {
|
|
if (k == LE) {
|
|
// not (lhs <= c) --> lhs > c --> lhs >= c+1
|
|
pos = true;
|
|
k = GE;
|
|
c++;
|
|
}
|
|
else if (k == GE) {
|
|
// not (lhs >= c) --> lhs < c --> lhs <= c-1
|
|
pos = true;
|
|
k = LE;
|
|
c--;
|
|
}
|
|
}
|
|
SASSERT(pos || k == EQ);
|
|
}
|
|
|
|
void throw_non_pb(expr * n) {
|
|
TRACE("pb2bv", tout << "Not pseudo-Boolean: " << mk_ismt2_pp(n, m) << "\n";);
|
|
throw non_pb();
|
|
}
|
|
|
|
// check if polynomial is encoding
|
|
// a_0*x_0 + a_0*~y_0 + ... + a_{n-1}*x_{n - 1} + a_{n - 1}*~y_{n - 1} = c
|
|
// x_0 = y_0, ..., x_{n - 1} = y_{n - 1}
|
|
bool is_eq_vector(polynomial const & p, numeral const & c) {
|
|
TRACE("is_eq_vector", display(tout, p, c););
|
|
unsigned sz = p.size();
|
|
if (sz % 2 == 1)
|
|
return false; // size must be even
|
|
// I implemented only the easy (and very common) case, where a_i = 2^{n-i-1} and c = 2^n - 1
|
|
unsigned n = sz/2;
|
|
if (c != m_bv_util.power_of_two(n) - numeral(1))
|
|
return false;
|
|
for (unsigned i = 0; i < n; i++) {
|
|
monomial const & m1 = p[i*2];
|
|
monomial const & m2 = p[i*2+1];
|
|
if (m1.m_lit.sign() == m2.m_lit.sign())
|
|
return false;
|
|
if (m1.m_a != m2.m_a)
|
|
return false;
|
|
if (m1.m_a != m_bv_util.power_of_two(n - i - 1))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void add_bounds_dependencies(expr * a) {
|
|
m_used_dependencies = m.mk_join(m_used_dependencies, m_bm.lower_dep(a));
|
|
m_used_dependencies = m.mk_join(m_used_dependencies, m_bm.upper_dep(a));
|
|
}
|
|
|
|
void convert(app * t, expr_ref & r, bool pos, bool root) {
|
|
constraint_kind k;
|
|
expr * lhs, * rhs;
|
|
if (m.is_eq(t, lhs, rhs)) {
|
|
if (is_uninterp_const(lhs) && is_uninterp_const(rhs)) {
|
|
add_bounds_dependencies(lhs);
|
|
add_bounds_dependencies(rhs);
|
|
r = m.mk_iff(mon_lit2lit(lit(lhs, false)),
|
|
mon_lit2lit(lit(rhs, !pos)));
|
|
return;
|
|
}
|
|
k = EQ;
|
|
}
|
|
else if (m_arith_util.is_le(t, lhs, rhs)) {
|
|
k = LE;
|
|
}
|
|
else if (m_arith_util.is_ge(t, lhs, rhs)) {
|
|
k = GE;
|
|
}
|
|
else {
|
|
throw_non_pb(t);
|
|
}
|
|
|
|
numeral c; bool is_int;
|
|
if (m_arith_util.is_numeral(lhs, c)) {
|
|
adjust(pos, k, c);
|
|
if (is_uninterp_const(rhs)) {
|
|
add_bounds_dependencies(rhs);
|
|
|
|
if (k == EQ) {
|
|
bool sign = c.is_zero();
|
|
if (!pos) sign = !sign;
|
|
r = mk_unit(rhs, sign);
|
|
}
|
|
else if ((c.is_zero() && k == LE) ||
|
|
(c.is_one() && k == GE)) {
|
|
// redundant 0 <= x, 1 >= x
|
|
TRACE("pb2bv", tout << "discarding:\n" << mk_ismt2_pp(t, m) << "\n";);
|
|
SASSERT(pos);
|
|
r = m.mk_true();
|
|
}
|
|
else {
|
|
SASSERT((c.is_zero() && k == GE) ||
|
|
(c.is_one() && k == LE));
|
|
// unit 0 >= x, 1 <= x
|
|
SASSERT(pos);
|
|
r = mk_unit(rhs, k == GE);
|
|
}
|
|
return;
|
|
}
|
|
throw_non_pb(t);
|
|
}
|
|
if (!m_arith_util.is_numeral(rhs, c, is_int) || !is_int)
|
|
throw_non_pb(t);
|
|
|
|
adjust(pos, k, c);
|
|
|
|
if (is_uninterp_const(lhs)) {
|
|
add_bounds_dependencies(lhs);
|
|
|
|
if (k == EQ) {
|
|
TRACE("pb2bv_bug", tout << "c: " << c << "\n";);
|
|
if (!c.is_zero() && !c.is_one()) {
|
|
// x = k --> true where k is not 0 or 1
|
|
r = pos ? m.mk_false() : m.mk_true();
|
|
}
|
|
else {
|
|
bool sign = c.is_zero();
|
|
if (!pos) sign = !sign;
|
|
r = mk_unit(lhs, sign);
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
// Our atom is of the form: (<= lhs c) or (>= lhs c)
|
|
// c may be different from 0,1.
|
|
if (k == LE) {
|
|
// x <= c >= 1
|
|
if (c >= numeral(1)) {
|
|
TRACE("pb2bv", tout << "discarding:\n" << mk_ismt2_pp(t, m) << "\n";);
|
|
r = m.mk_true();
|
|
return;
|
|
}
|
|
else if (c.is_neg()) {
|
|
// x <= c < 0
|
|
r = m.mk_false();
|
|
return;
|
|
}
|
|
SASSERT(c.is_zero());
|
|
}
|
|
else if (k == GE) {
|
|
if (c.is_nonpos()) {
|
|
TRACE("pb2bv", tout << "discarding:\n" << mk_ismt2_pp(t, m) << "\n";);
|
|
// x >= a <= 0
|
|
r = m.mk_true();
|
|
return;
|
|
}
|
|
else if (c > numeral(1)) {
|
|
// x >= a > 1
|
|
r = m.mk_false();
|
|
return;
|
|
}
|
|
SASSERT(c.is_one());
|
|
}
|
|
CTRACE("pb2bv", !(c.is_zero() || c.is_one()),
|
|
tout << "BUG: " << mk_ismt2_pp(t, m) << "\nk: " << k << " " << c << "\n";);
|
|
SASSERT(c.is_zero() || c.is_one());
|
|
SASSERT(!((c.is_zero() && k == GE) ||
|
|
(c.is_one() && k == LE)));
|
|
|
|
CTRACE("pb2bv_bug", !((c.is_zero() && k == LE) || (c.is_one() && k == GE)),
|
|
tout << "c: " << c << ", k: " << k << "\n";
|
|
tout << "t: " << mk_ismt2_pp(t, m) << "\n";);
|
|
SASSERT((c.is_zero() && k == LE) ||
|
|
(c.is_one() && k == GE));
|
|
// x <= 0, x >= 1
|
|
SASSERT(pos);
|
|
r = mk_unit(lhs, k == LE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!m_arith_util.is_add(lhs))
|
|
throw_non_pb(t);
|
|
|
|
unsigned sz = to_app(lhs)->get_num_args();
|
|
expr * const * ms = to_app(lhs)->get_args();
|
|
expr * a, * x;
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
expr * m = ms[i];
|
|
if (is_uninterp_const(m))
|
|
continue;
|
|
if (m_arith_util.is_mul(m, a, x) && m_arith_util.is_numeral(a) && is_uninterp_const(x))
|
|
continue;
|
|
throw_non_pb(t);
|
|
}
|
|
|
|
// is pb constraint.
|
|
numeral a_val;
|
|
polynomial m_p;
|
|
numeral m_c;
|
|
m_c = c;
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
expr * m = ms[i];
|
|
if (is_uninterp_const(m)) {
|
|
add_bounds_dependencies(m);
|
|
m_p.push_back(monomial(lit(m)));
|
|
}
|
|
else if (m_arith_util.is_mul(m, a, x) && m_arith_util.is_numeral(a, a_val)) {
|
|
add_bounds_dependencies(x);
|
|
if (a_val.is_neg()) {
|
|
a_val.neg();
|
|
// -a x --> -a(1-!x) ==> -a + a!x,
|
|
m_c += a_val;
|
|
m_p.push_back(monomial(a_val, lit(x, true)));
|
|
}
|
|
else {
|
|
m_p.push_back(monomial(a_val, lit(x)));
|
|
}
|
|
}
|
|
else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
std::stable_sort(m_p.begin(), m_p.end(), monomial_lt());
|
|
|
|
if (k == GE) {
|
|
mk_pbc(m_p, m_c, r, root);
|
|
}
|
|
else if (k == LE) {
|
|
m_c.neg();
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
monomial & m = m_p[i];
|
|
SASSERT(m.m_a.is_nonneg());
|
|
m_c += m.m_a;
|
|
m.m_lit.neg();
|
|
}
|
|
mk_pbc(m_p, m_c, r, root);
|
|
}
|
|
else {
|
|
SASSERT(k == EQ);
|
|
|
|
if (is_eq_vector(m_p, m_c)) {
|
|
TRACE("is_eq_vector", tout << "found eq vector\n";);
|
|
unsigned sz = m_p.size();
|
|
expr_ref_vector eqs(m);
|
|
for (unsigned i = 0; i < sz; i += 2) {
|
|
app * x_i = to_app(m_p[i].m_lit.var());
|
|
app * y_i = to_app(m_p[i+1].m_lit.var());
|
|
eqs.push_back(m.mk_eq(int2lit(x_i), int2lit(y_i)));
|
|
}
|
|
m_b_rw.mk_and(eqs.size(), eqs.c_ptr(), r);
|
|
if (!pos)
|
|
m_b_rw.mk_not(r, r);
|
|
return;
|
|
}
|
|
|
|
polynomial m_p2;
|
|
numeral m_c2 = m_c;
|
|
m_c2.neg();
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
monomial m = m_p[i];
|
|
SASSERT(m.m_a.is_nonneg());
|
|
m_c2 += m.m_a;
|
|
m.m_lit.neg();
|
|
m_p2.push_back(m);
|
|
}
|
|
expr_ref r1(m);
|
|
expr_ref r2(m);
|
|
mk_pbc(m_p, m_c, r1, false);
|
|
mk_pbc(m_p2, m_c2, r2, false);
|
|
TRACE("pb2bv_convert", tout << mk_ismt2_pp(t, m) << "\n";
|
|
display(tout, m_p, m_c);
|
|
display(tout, m_p2, m_c2);
|
|
tout << "--->\n" << mk_ismt2_pp(r1, m) << "\nAND\n" << mk_ismt2_pp(r2, m) << "\n";);
|
|
m_b_rw.mk_and(r1, r2, r);
|
|
if (!pos)
|
|
m_b_rw.mk_not(r, r);
|
|
}
|
|
}
|
|
|
|
bool is_constraint_core(expr * n) {
|
|
return (m.is_eq(n) && m_arith_util.is_int(to_app(n)->get_arg(0))) || m_arith_util.is_le(n) || m_arith_util.is_ge(n);
|
|
}
|
|
|
|
bool is_constraint(expr * n, expr * & atom, bool & pos) {
|
|
pos = true;
|
|
while (m.is_not(n)) {
|
|
n = to_app(n)->get_arg(0);
|
|
pos = !pos;
|
|
}
|
|
atom = n;
|
|
return is_constraint_core(n);
|
|
}
|
|
|
|
imp(ast_manager & _m, params_ref const & p):
|
|
m(_m),
|
|
m_bm(m),
|
|
m_b_rw(m, p),
|
|
m_arith_util(m),
|
|
m_bv_util(m),
|
|
m_new_deps(m),
|
|
m_temporary_ints(m),
|
|
m_used_dependencies(m),
|
|
m_rw(*this) {
|
|
updt_params(p);
|
|
m_b_rw.set_flat(false); // no flattening otherwise will blowup the memory
|
|
m_b_rw.set_elim_and(true);
|
|
}
|
|
|
|
~imp() {
|
|
dec_ref_map_key_values(m, m_const2bit);
|
|
dec_ref_map_values(m, m_not_const2bit);
|
|
m_rw.reset();
|
|
m_bm.reset();
|
|
m_temporary_ints.reset();
|
|
}
|
|
|
|
void updt_params(params_ref const & p) {
|
|
m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX));
|
|
m_all_clauses_limit = p.get_uint(":pb2bv-all-clauses-limit", 8);
|
|
m_cardinality_limit = p.get_uint(":pb2bv-cardinality-limit", UINT_MAX);
|
|
}
|
|
|
|
void collect_param_descrs(param_descrs & r) {
|
|
insert_max_memory(r);
|
|
r.insert(":pb2bv-all-clauses-limit", CPK_UINT, "(default: 8) maximum number of literals for using equivalent CNF encoding of PB constraint.");
|
|
r.insert(":pb2bv-cardinality-limit", CPK_UINT, "(default: inf) limit for using arc-consistent cardinality constraint encoding.");
|
|
}
|
|
|
|
void set_cancel(bool f) {
|
|
m_rw.set_cancel(f);
|
|
}
|
|
|
|
virtual void operator()(goal_ref const & g,
|
|
goal_ref_buffer & result,
|
|
model_converter_ref & mc,
|
|
proof_converter_ref & pc,
|
|
expr_dependency_ref & core) {
|
|
TRACE("pb2bv", g->display(tout););
|
|
SASSERT(g->is_well_sorted());
|
|
fail_if_proof_generation("pb2bv", g);
|
|
m_produce_models = g->models_enabled();
|
|
m_produce_unsat_cores = g->unsat_core_enabled();
|
|
mc = 0; pc = 0; core = 0; result.reset();
|
|
tactic_report report("pb2bv", *g);
|
|
m_bm.reset(); m_rw.reset(); m_new_deps.reset();
|
|
|
|
if (g->inconsistent()) {
|
|
result.push_back(g.get());
|
|
return;
|
|
}
|
|
|
|
m_bm(*g);
|
|
|
|
TRACE("pb2bv", m_bm.display(tout););
|
|
|
|
try {
|
|
quick_pb_check(g);
|
|
}
|
|
catch (non_pb) {
|
|
throw tactic_exception("goal is in a fragment unsupported by pb2bv");
|
|
}
|
|
|
|
unsigned size = g->size();
|
|
expr_ref_vector new_exprs(m);
|
|
expr_dependency_ref_vector new_deps(m);
|
|
|
|
try {
|
|
expr_ref new_curr(m);
|
|
proof_ref new_pr(m);
|
|
expr_ref new_f(m);
|
|
for (unsigned idx = 0; idx < size; idx++) {
|
|
expr * curr = g->form(idx);
|
|
expr * atom;
|
|
bool pos;
|
|
if (is_constraint(curr, atom, pos)) {
|
|
convert(to_app(atom), new_f, pos, true);
|
|
TRACE("pb2bv_convert", tout << "pos: " << pos << "\n" << mk_ismt2_pp(atom, m) << "\n--->\n" << mk_ismt2_pp(new_f, m) << "\n";);
|
|
}
|
|
else {
|
|
m_rw(curr, new_f);
|
|
}
|
|
if (m_produce_unsat_cores) {
|
|
new_deps.push_back(m.mk_join(m_used_dependencies, g->dep(idx)));
|
|
m_used_dependencies.reset();
|
|
}
|
|
new_exprs.push_back(new_f);
|
|
}
|
|
}
|
|
catch (non_pb) {
|
|
throw tactic_exception("goal is in a fragment unsupported by pb2bv");
|
|
}
|
|
|
|
for (unsigned idx = 0; idx < size; idx++)
|
|
g->update(idx, new_exprs[idx].get(), 0, (m_produce_unsat_cores) ? new_deps[idx].get() : g->dep(idx));
|
|
|
|
if (m_produce_models) {
|
|
filter_model_converter * mc1 = alloc(filter_model_converter, m);
|
|
obj_map<func_decl, expr*>::iterator it = m_const2bit.begin();
|
|
obj_map<func_decl, expr*>::iterator end = m_const2bit.end();
|
|
for (; it != end; ++it)
|
|
mc1->insert(to_app(it->m_value)->get_decl());
|
|
// store temp int constants in the filter
|
|
unsigned num_temps = m_temporary_ints.size();
|
|
for (unsigned i = 0; i < num_temps; i++)
|
|
mc1->insert(to_app(m_temporary_ints.get(i))->get_decl());
|
|
pb2bv_model_converter * mc2 = alloc(pb2bv_model_converter, m, m_const2bit, m_bm);
|
|
mc = concat(mc1, mc2);
|
|
}
|
|
|
|
g->inc_depth();
|
|
result.push_back(g.get());
|
|
TRACE("pb2bv", g->display(tout););
|
|
SASSERT(g->is_well_sorted());
|
|
}
|
|
};
|
|
|
|
imp * m_imp;
|
|
params_ref m_params;
|
|
public:
|
|
pb2bv_tactic(ast_manager & m, params_ref const & p):
|
|
m_params(p) {
|
|
m_imp = alloc(imp, m, p);
|
|
}
|
|
|
|
virtual tactic * translate(ast_manager & m) {
|
|
return alloc(pb2bv_tactic, m, m_params);
|
|
}
|
|
|
|
virtual ~pb2bv_tactic() {
|
|
dealloc(m_imp);
|
|
}
|
|
|
|
virtual void updt_params(params_ref const & p) {
|
|
m_params = p;
|
|
m_imp->updt_params(p);
|
|
}
|
|
|
|
virtual void collect_param_descrs(param_descrs & r) {
|
|
m_imp->collect_param_descrs(r);
|
|
}
|
|
|
|
virtual void operator()(goal_ref const & in,
|
|
goal_ref_buffer & result,
|
|
model_converter_ref & mc,
|
|
proof_converter_ref & pc,
|
|
expr_dependency_ref & core) {
|
|
(*m_imp)(in, result, mc, pc, core);
|
|
}
|
|
|
|
virtual void cleanup() {
|
|
ast_manager & m = m_imp->m;
|
|
imp * d = m_imp;
|
|
#pragma omp critical (tactic_cancel)
|
|
{
|
|
d = m_imp;
|
|
}
|
|
dealloc(d);
|
|
d = alloc(imp, m, m_params);
|
|
#pragma omp critical (tactic_cancel)
|
|
{
|
|
m_imp = d;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
virtual void set_cancel(bool f) {
|
|
if (m_imp)
|
|
m_imp->set_cancel(f);
|
|
}
|
|
};
|
|
|
|
tactic * mk_pb2bv_tactic(ast_manager & m, params_ref const & p) {
|
|
return clean(alloc(pb2bv_tactic, m, p));
|
|
}
|
|
|
|
struct is_pb_probe : public probe {
|
|
virtual result operator()(goal const & g) {
|
|
try {
|
|
ast_manager & m = g.m();
|
|
bound_manager bm(m);
|
|
bm(g);
|
|
arith_util a_util(m);
|
|
expr_fast_mark1 visited;
|
|
pb2bv_tactic::only_01_visitor proc(a_util, bm);
|
|
|
|
unsigned sz = g.size();
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
expr * f = g.form(i);
|
|
for_each_expr_core<pb2bv_tactic::only_01_visitor, expr_fast_mark1, true, true>(proc, visited, f);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (pb2bv_tactic::non_pb) {
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
probe * mk_is_pb_probe() {
|
|
return alloc(is_pb_probe);
|
|
}
|