3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-23 17:15:31 +00:00

Additional BDD operations; BDD vectors and finite domain abstraction

This commit is contained in:
Jakob Rath 2022-08-01 12:30:34 +02:00 committed by Nikolaj Bjorner
parent 9275d1e57a
commit 42233ab5c8
6 changed files with 1432 additions and 32 deletions

View file

@ -1,6 +1,7 @@
z3_add_component(dd
SOURCES
dd_bdd.cpp
dd_fdd.cpp
dd_pdd.cpp
COMPONENT_DEPENDENCIES
util

View file

@ -142,6 +142,8 @@ namespace dd {
if (a == b) return false_bdd;
if (is_false(a)) return b;
if (is_false(b)) return a;
if (is_true(a)) return mk_not_rec(b);
if (is_true(b)) return mk_not_rec(a);
break;
default:
UNREACHABLE();
@ -571,7 +573,63 @@ namespace dd {
e1->m_result = r;
return r;
}
/**
* co-factor a using b.
* b must be a variable bdd (it can be generalized to a cube)
*/
bdd bdd_manager::mk_cofactor(bdd const& a, bdd const& b) {
bool first = true;
scoped_push _sp(*this);
SASSERT(!b.is_const() && b.lo().is_const() && b.hi().is_const());
while (true) {
try {
return bdd(mk_cofactor_rec(a.root, b.root), this);
}
catch (const mem_out &) {
if (!first) throw;
try_reorder();
first = false;
}
}
}
bdd_manager::BDD bdd_manager::mk_cofactor_rec(BDD a, BDD b) {
if (is_const(a)) return a;
if (is_const(b)) return a;
unsigned la = level(a), lb = level(b);
// cases where b is a single literal
if (la == lb && is_const(lo(b)) && is_const(hi(b)))
return is_true(hi(b)) ? hi(a) : lo(a);
if (la < lb && is_const(lo(b)) && is_const(hi(b)))
return a;
// cases where b is a proper cube (with more than one literal
if (la == lb) {
if (is_false(lo(b)))
a = hi(a), b = hi(b);
else
a = lo(a), b = lo(b);
return mk_cofactor_rec(a, b);
}
if (la < lb)
return mk_cofactor_rec(a, is_false(lo(b)) ? hi(b) : lo(b));
op_entry* e1 = pop_entry(a, b, bdd_cofactor_op);
op_entry const* e2 = m_op_cache.insert_if_not_there(e1);
if (check_result(e1, e2, a, b, bdd_cofactor_op))
return e2->m_result;
SASSERT(la > lb);
push(mk_cofactor_rec(lo(a), b));
push(mk_cofactor_rec(hi(a), b));
BDD r = make_node(la, read(2), read(1));
pop(2);
e1->m_result = r;
return r;
}
bdd bdd_manager::mk_ite(bdd const& c, bdd const& t, bdd const& e) {
bool first = true;
scoped_push _sp(*this);
@ -587,14 +645,15 @@ namespace dd {
}
}
bdd_manager::BDD bdd_manager::mk_ite_rec(BDD a, BDD b, BDD c) {
if (is_true(a)) return b;
if (is_false(a)) return c;
if (b == c) return b;
if (is_true(b)) return apply(a, c, bdd_or_op);
if (is_false(c)) return apply(a, b, bdd_and_op);
if (is_false(b)) return apply(mk_not_rec(a), c, bdd_and_op);
if (is_true(c)) return apply(mk_not_rec(a), b, bdd_or_op);
if (is_true(b)) return apply_rec(a, c, bdd_or_op);
if (is_false(c)) return apply_rec(a, b, bdd_and_op);
if (is_false(b)) return apply_rec(mk_not_rec(a), c, bdd_and_op);
if (is_true(c)) return apply_rec(mk_not_rec(a), b, bdd_or_op);
SASSERT(!is_const(a) && !is_const(b) && !is_const(c));
op_entry * e1 = pop_entry(a, b, c);
op_entry const* e2 = m_op_cache.insert_if_not_there(e1);
@ -644,6 +703,7 @@ namespace dd {
bdd_manager::BDD bdd_manager::mk_quant(unsigned n, unsigned const* vars, BDD b, bdd_op op) {
BDD result = b;
// TODO: should this method catch mem_out like the other non-rec mk_ methods?
for (unsigned i = 0; i < n; ++i) {
result = mk_quant_rec(m_var2level[vars[i]], result, op);
}
@ -895,4 +955,299 @@ namespace dd {
bdd& bdd::operator=(bdd const& other) { unsigned r1 = root; root = other.root; m->inc_ref(root); m->dec_ref(r1); return *this; }
std::ostream& operator<<(std::ostream& out, bdd const& b) { return b.display(out); }
bdd bdd_manager::mk_eq(bddv const& a, bddv const& b) {
SASSERT(a.size() == b.size());
bdd eq = mk_true();
for (unsigned i = 0; i < a.size(); ++i)
eq &= !(a[i] ^ b[i]);
return eq;
}
bdd bdd_manager::mk_eq(bddv const& a, rational const& n) {
SASSERT(n.is_int() && n >= 0 && n < rational(2).expt(a.size()));
bdd b = mk_true();
for (unsigned i = 0; i < a.size(); ++i)
b &= n.get_bit(i) ? a[i] : !a[i];
return b;
}
bdd bdd_manager::mk_eq(unsigned_vector const& vars, rational const& n) {
SASSERT(n.is_int() && n >= 0 && n < rational(2).expt(vars.size()));
bdd b = mk_true();
for (unsigned i = 0; i < vars.size(); ++i)
b &= n.get_bit(i) ? mk_var(vars[i]) : mk_nvar(vars[i]);
return b;
}
bdd bdd_manager::mk_ule(bddv const& a, bddv const& b) {
SASSERT(a.size() == b.size());
bdd lt = mk_false();
bdd eq = mk_true();
for (unsigned i = a.size(); i-- > 0 && !eq.is_false(); ) {
lt |= eq && (!a[i] && b[i]);
eq &= !(a[i] ^ b[i]);
}
return lt || eq;
}
bdd bdd_manager::mk_uge(bddv const& a, bddv const& b) { return mk_ule(b, a); }
bdd bdd_manager::mk_ult(bddv const& a, bddv const& b) { return mk_ule(a, b) && !mk_eq(a, b); }
bdd bdd_manager::mk_ugt(bddv const& a, bddv const& b) { return mk_ult(b, a); }
bdd bdd_manager::mk_sle(bddv const& a, bddv const& b) {
SASSERT(a.size() == b.size());
// Note: sle can be reduced to ule by flipping the sign bits of both arguments
bdd lt = mk_false();
bdd eq = mk_true();
unsigned const sz = a.size();
if (sz > 0) {
lt = a[sz - 1] && !b[sz - 1];
eq = !(a[sz - 1] ^ b[sz - 1]);
for (unsigned i = sz - 1; i-- > 0; ) {
lt |= eq && (!a[i] && b[i]);
eq &= !(a[i] ^ b[i]);
}
}
return lt || eq;
}
bdd bdd_manager::mk_sge(bddv const& a, bddv const& b) { return mk_sle(b, a); }
bdd bdd_manager::mk_slt(bddv const& a, bddv const& b) { return mk_sle(a, b) && !mk_eq(a, b); }
bdd bdd_manager::mk_sgt(bddv const& a, bddv const& b) { return mk_slt(b, a); }
bddv bdd_manager::mk_add(bddv const& a, bddv const& b) {
SASSERT(a.size() == b.size());
bdd carry = mk_false();
bddv result(this);
#if 0
for (unsigned i = 0; i < a.size(); ++i) {
result.push_back(carry ^ a[i] ^ b[i]);
carry = (carry && a[i]) || (carry && b[i]) || (a[i] && b[i]);
}
#else
if (a.size() > 0)
result.push_back(a[0] ^ b[0]);
for (unsigned i = 1; i < a.size(); ++i) {
carry = (carry && a[i-1]) || (carry && b[i-1]) || (a[i-1] && b[i-1]);
result.push_back(carry ^ a[i] ^ b[i]);
}
#endif
return result;
}
bddv bdd_manager::mk_add(bddv const& a, std::function<bdd(unsigned)>& b) {
bdd carry = mk_false();
bddv result(this);
if (a.size() > 0)
result.push_back(a[0] ^ b(0));
for (unsigned i = 1; i < a.size(); ++i) {
auto bi1 = b(i-1);
carry = (carry && a[i-1]) || (carry && bi1) || (a[i-1] && bi1);
result.push_back(carry ^ a[i] ^ b(i));
}
return result;
}
bddv bdd_manager::mk_sub(bddv const& a, bddv const& b) {
SASSERT(a.size() == b.size());
bdd carry = mk_false();
bddv result(this);
if (a.size() > 0)
result.push_back(a[0] ^ b[0]);
for (unsigned i = 1; i < a.size(); ++i) {
// carry = (a[i-1] && b[i-1] && carry) || (!a[i-1] && (b[i-1] || carry));
carry = mk_ite(a[i-1], b[i-1] && carry, b[i-1] || carry);
result.push_back(carry ^ a[i] ^ b[i]);
}
return result;
}
bddv bdd_manager::mk_usub(bddv const& a) {
bddv result(this);
bdd carry = mk_false();
result.push_back(a[0]);
for (unsigned i = 1; i < a.size(); ++i) {
carry = a[i-1] || carry;
result.push_back(carry ^ a[i]);
}
return result;
}
bool_vector bdd_manager::mk_usub(bool_vector const& b) {
bool_vector result;
if (b.empty())
return result;
bool carry = false;
result.push_back(b[0]);
for (unsigned i = 1; i < b.size(); ++i) {
carry = carry || b[i-1];
result.push_back(carry ^ b[i]);
}
return result;
}
bddv bdd_manager::mk_mul(bddv const& a, bddv const& b) {
SASSERT(a.size() == b.size());
bddv result = mk_zero(a.size());
for (unsigned i = 0; i < b.size(); ++i) {
std::function<bdd(unsigned)> get_a = [&](unsigned k) {
if (k < i)
return mk_false();
else
return a[k - i] && b[i];
};
result = mk_add(result, get_a);
}
return result;
}
bddv bdd_manager::mk_mul(bddv const& a, rational const& val) {
SASSERT(val.is_int() && val >= 0 && val < rational::power_of_two(a.size()));
bool_vector b;
for (unsigned i = 0; i < a.size(); ++i)
b.push_back(val.get_bit(i));
return mk_mul(a, b);
}
bddv bdd_manager::mk_mul(bddv const& a, bool_vector const& b) {
SASSERT(a.size() == b.size());
bddv result = mk_zero(a.size());
// use identity (bvmul a b) == (bvneg (bvmul (bvneg a) b))
unsigned cnt = 0;
for (auto v : b) if (v) cnt++;
if (cnt*2 > b.size()+1)
return mk_usub(mk_mul(a, mk_usub(b)));
for (unsigned i = 0; i < a.size(); ++i) {
std::function<bdd(unsigned)> get_a = [&](unsigned k) {
if (k < i)
return mk_false();
else
return a[k - i];
};
if (b[i])
result = mk_add(result, get_a);
}
return result;
}
bddv bdd_manager::mk_concat(bddv const& a, bddv const& b) {
bddv result = a;
result.m_bits.append(b.m_bits);
return result;
}
/**
* Quotient remainder
*
* rem, div have size 2*|a| = worksize.
* Initialization:
* rem := a ++ false
* div := false ++ b
*/
void bdd_manager::mk_quot_rem(bddv const& a, bddv const& b, bddv& quot, bddv& rem) {
SASSERT(a.size() == b.size());
quot = mk_zero(a.size());
unsigned worksize = a.size() + b.size();
rem = a.append(mk_zero(b.size()));
bddv div = mk_zero(a.size()).append(b);
//
// Keep shifting divisor to the right and subtract whenever it is
// smaller than the remaining value
//
for (unsigned i = 0; i <= b.size(); ++i) {
bdd divLteRem = div <= rem;
bddv remSubDiv = rem - div;
for (unsigned j = 0; j < worksize; ++j)
rem[j] = mk_ite(divLteRem, remSubDiv[j], rem[j]);
if (i > 0)
quot[b.size() - i] = divLteRem;
div.shr();
}
rem.m_bits.shrink(b.size());
}
bddv bdd_manager::mk_num(rational const& n, unsigned num_bits) {
SASSERT(n.is_int() && n >= 0 && n < rational::power_of_two(num_bits));
bddv result(this);
for (unsigned i = 0; i < num_bits; ++i)
result.push_back(n.get_bit(i) ? mk_true() : mk_false());
return result;
}
bddv bdd_manager::mk_ones(unsigned num_bits) {
bddv result(this);
for (unsigned i = 0; i < num_bits; ++i)
result.push_back(mk_true());
return result;
}
bddv bdd_manager::mk_zero(unsigned num_bits) {
bddv result(this);
for (unsigned i = 0; i < num_bits; ++i)
result.push_back(mk_false());
return result;
}
bddv bdd_manager::mk_var(unsigned num_bits, unsigned const* vars) {
bddv result(this);
for (unsigned i = 0; i < num_bits; ++i)
result.push_back(mk_var(vars[i]));
return result;
}
bddv bdd_manager::mk_var(unsigned_vector const& vars) {
return mk_var(vars.size(), vars.data());
}
bool bdd_manager::is_constv(bddv const& a) {
for (bdd const& bit : a.bits())
if (!is_const(bit.root))
return false;
return true;
}
rational bdd_manager::to_val(bddv const& a) {
rational result = rational::zero();
for (unsigned i = 0; i < a.size(); ++i) {
bdd const &bit = a[i];
SASSERT(is_const(bit.root));
if (bit.is_true())
result += rational::power_of_two(i);
}
return result;
}
void bddv::shl() {
for (unsigned j = size(); j-- > 1;)
m_bits[j] = m_bits[j - 1];
m_bits[0] = m->mk_false();
}
void bddv::shr() {
for (unsigned j = 1; j < size(); ++j)
m_bits[j - 1] = m_bits[j];
m_bits[size() - 1] = m->mk_false();
}
bdd bddv::all0() const {
bdd r = m->mk_true();
for (unsigned i = 0; i < size() && !r.is_false(); ++i)
r &= !m_bits[i];
return r;
}
bdd bddv::all1() const {
bdd r = m->mk_true();
for (unsigned i = 0; i < size() && !r.is_false(); ++i)
r &= m_bits[i];
return r;
}
}

View file

@ -21,13 +21,18 @@ Revision History:
#include "util/vector.h"
#include "util/map.h"
#include "util/small_object_allocator.h"
#include "util/rational.h"
namespace dd {
class bdd;
class bddv;
class test_bdd;
class bdd_manager {
friend bdd;
friend bddv;
friend test_bdd;
typedef unsigned BDD;
@ -40,7 +45,8 @@ namespace dd {
bdd_not_op = 5,
bdd_and_proj_op = 6,
bdd_or_proj_op = 7,
bdd_no_op = 8,
bdd_cofactor_op = 8,
bdd_no_op = 9,
};
struct bdd_node {
@ -141,6 +147,7 @@ namespace dd {
BDD mk_not_rec(BDD b);
BDD mk_ite_rec(BDD a, BDD b, BDD c);
BDD mk_quant_rec(unsigned lvl, BDD b, bdd_op op);
BDD mk_cofactor_rec(BDD a, BDD b);
void push(BDD b);
void pop(unsigned num_scopes);
@ -188,6 +195,7 @@ namespace dd {
bdd mk_and(bdd const& a, bdd const& b);
bdd mk_or(bdd const& a, bdd const& b);
bdd mk_xor(bdd const& a, bdd const& b);
bdd mk_cofactor(bdd const& a, bdd const& b);
void reserve_var(unsigned v);
bool well_formed();
@ -199,10 +207,13 @@ namespace dd {
~scoped_push() { m.m_bdd_stack.shrink(m_size); }
};
bool_vector mk_usub(bool_vector const& b);
public:
struct mem_out {};
bdd_manager(unsigned nodes);
bdd_manager(unsigned num_vars);
~bdd_manager();
void set_max_num_nodes(unsigned n) { m_max_num_bdd_nodes = n; }
@ -219,6 +230,39 @@ namespace dd {
bdd mk_forall(unsigned v, bdd const& b);
bdd mk_ite(bdd const& c, bdd const& t, bdd const& e);
/* BDD vector operations
* - Fixed-width arithmetic, i.e., modulo 2^size
* - The lowest index is the LSB
*/
bdd mk_ule(bddv const& a, bddv const& b);
bdd mk_uge(bddv const& a, bddv const& b); // { return mk_ule(b, a); }
bdd mk_ult(bddv const& a, bddv const& b); // { return mk_ule(a, b) && !mk_eq(a, b); }
bdd mk_ugt(bddv const& a, bddv const& b); // { return mk_ult(b, a); }
bdd mk_sle(bddv const& a, bddv const& b);
bdd mk_sge(bddv const& a, bddv const& b); // { return mk_sle(b, a); }
bdd mk_slt(bddv const& a, bddv const& b); // { return mk_sle(a, b) && !mk_eq(a, b); }
bdd mk_sgt(bddv const& a, bddv const& b); // { return mk_slt(b, a); }
bdd mk_eq(bddv const& a, bddv const& b);
bdd mk_eq(bddv const& a, rational const& v);
bdd mk_eq(unsigned_vector const& vars, rational const& v);
bddv mk_num(rational const& n, unsigned num_bits);
bddv mk_ones(unsigned num_bits);
bddv mk_zero(unsigned num_bits);
bddv mk_var(unsigned num_bits, unsigned const* vars);
bddv mk_var(unsigned_vector const& vars);
bddv mk_add(bddv const& a, bddv const& b);
bddv mk_add(bddv const& a, std::function<bdd(unsigned)>& get_bit);
bddv mk_sub(bddv const& a, bddv const& b);
bddv mk_usub(bddv const& a);
bddv mk_mul(bddv const& a, bddv const& b);
bddv mk_mul(bddv const& a, bool_vector const& b);
bddv mk_mul(bddv const& a, rational const& val);
bddv mk_concat(bddv const& a, bddv const& b);
void mk_quot_rem(bddv const& a, bddv const& b, bddv& quot, bddv& rem);
bool is_constv(bddv const& a);
rational to_val(bddv const& a);
std::ostream& display(std::ostream& out);
std::ostream& display(std::ostream& out, bdd const& b);
@ -242,14 +286,16 @@ namespace dd {
unsigned var() const { return m->var(root); }
bool is_true() const { return root == bdd_manager::true_bdd; }
bool is_false() const { return root == bdd_manager::false_bdd; }
bool is_false() const { return root == bdd_manager::false_bdd; }
bool is_const() const { return is_false() || is_true(); }
bdd operator!() { return m->mk_not(*this); }
bdd operator&&(bdd const& other) { return m->mk_and(*this, other); }
bdd operator||(bdd const& other) { return m->mk_or(*this, other); }
bdd operator^(bdd const& other) { return m->mk_xor(*this, other); }
bdd operator!() const { return m->mk_not(*this); }
bdd operator&&(bdd const& other) const { return m->mk_and(*this, other); }
bdd operator||(bdd const& other) const { return m->mk_or(*this, other); }
bdd operator^(bdd const& other) const { return m->mk_xor(*this, other); }
bdd operator|=(bdd const& other) { return *this = *this || other; }
bdd operator&=(bdd const& other) { return *this = *this && other; }
bdd cofactor(bdd const& cube) { return m->mk_cofactor(*this, cube); }
std::ostream& display(std::ostream& out) const { return m->display(out, *this); }
bool operator==(bdd const& other) const { return root == other.root; }
bool operator!=(bdd const& other) const { return root != other.root; }
@ -260,6 +306,79 @@ namespace dd {
std::ostream& operator<<(std::ostream& out, bdd const& b);
class bddv {
friend bdd_manager;
vector<bdd> m_bits;
bdd_manager* m;
bddv(vector<bdd> const& bits, bdd_manager* m): m_bits(bits), m(m) { SASSERT(m); }
bddv(vector<bdd>&& bits, bdd_manager* m): m_bits(std::move(bits)), m(m) { SASSERT(m); }
bddv(bdd_manager* m): m_bits(), m(m) { SASSERT(m); }
bdd const& operator[](unsigned i) const { return m_bits[i]; }
bdd& operator[](unsigned i) { return m_bits[i]; }
void push_back(bdd const& a) { m_bits.push_back(a); }
void push_back(bdd&& a) { m_bits.push_back(std::move(a)); }
void shl();
void shr();
public:
unsigned size() const { return m_bits.size(); }
vector<bdd> const& bits() const { return m_bits; }
/* unsigned comparison operators */
bdd operator<=(bddv const& other) const { return m->mk_ule(*this, other); } ///< unsigned comparison
bdd operator>=(bddv const& other) const { return m->mk_uge(*this, other); } ///< unsigned comparison
bdd operator< (bddv const& other) const { return m->mk_ult(*this, other); } ///< unsigned comparison
bdd operator> (bddv const& other) const { return m->mk_ugt(*this, other); } ///< unsigned comparison
bdd operator<=(rational const& other) const { return m->mk_ule(*this, m->mk_num(other, size())); } ///< unsigned comparison
bdd operator>=(rational const& other) const { return m->mk_uge(*this, m->mk_num(other, size())); } ///< unsigned comparison
bdd operator< (rational const& other) const { return m->mk_ult(*this, m->mk_num(other, size())); } ///< unsigned comparison
bdd operator> (rational const& other) const { return m->mk_ugt(*this, m->mk_num(other, size())); } ///< unsigned comparison
/* signed comparison operators */
bdd sle(bddv const& other) const { return m->mk_sle(*this, other); }
bdd sge(bddv const& other) const { return m->mk_sge(*this, other); }
bdd slt(bddv const& other) const { return m->mk_slt(*this, other); }
bdd sgt(bddv const& other) const { return m->mk_sgt(*this, other); }
bdd all0() const;
bdd all1() const;
bdd operator==(bddv const& other) const { return m->mk_eq(*this, other); }
bdd operator==(rational const& other) const { return m->mk_eq(*this, other); }
bdd operator!=(bddv const& other) const { return !m->mk_eq(*this, other); }
bdd operator!=(rational const& other) const { return !m->mk_eq(*this, other); }
bddv operator+(bddv const& other) const { return m->mk_add(*this, other); }
bddv operator+(rational const& other) const { return m->mk_add(*this, m->mk_num(other, size())); }
bddv operator-(bddv const& other) const { return m->mk_sub(*this, other); }
bddv operator-(rational const& other) const { return m->mk_sub(*this, m->mk_num(other, size())); }
bddv rev_sub(rational const& other) const { return m->mk_sub(m->mk_num(other, size()), *this); }
bddv operator*(bddv const& other) const { return m->mk_mul(*this, other); }
bddv operator*(rational const& other) const { return m->mk_mul(*this, other); }
bddv operator*(bool_vector const& other) const { return m->mk_mul(*this, other); }
bddv append(bddv const& other) const { return m->mk_concat(*this, other); }
void quot_rem(bddv const& divisor, bddv& quot, bddv& rem) const { m->mk_quot_rem(*this, divisor, quot, rem); }
bool is_const() const { return m->is_constv(*this); }
rational to_val() const { return m->to_val(*this); }
};
inline bdd operator<=(rational const& r, bddv const& a) { return a >= r; } ///< unsigned comparison
inline bdd operator>=(rational const& r, bddv const& a) { return a <= r; } ///< unsigned comparison
inline bdd operator< (rational const& r, bddv const& a) { return a > r; } ///< unsigned comparison
inline bdd operator> (rational const& r, bddv const& a) { return a < r; } ///< unsigned comparison
inline bdd operator==(rational const& r, bddv const& a) { return a == r; }
inline bdd operator!=(rational const& r, bddv const& a) { return a != r; }
inline bddv operator*(rational const& r, bddv const& a) { return a * r; }
inline bddv operator+(rational const& r, bddv const& a) { return a + r; }
inline bddv operator-(rational const& r, bddv const& a) { return a.rev_sub(r); }
inline bdd operator<=(int i, bddv const& a) { return a >= rational(i); }
inline bdd operator<=(bddv const& a, int i) { return a <= rational(i); }
}

366
src/math/dd/dd_fdd.cpp Normal file
View file

@ -0,0 +1,366 @@
/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
dd_fdd
Abstract:
Finite domain abstraction for using BDDs as sets of integers, inspired by BuDDy's fdd module.
Author:
Nikolaj Bjorner (nbjorner) 2021-04-20
Jakob Rath 2021-04-20
--*/
#include "math/dd/dd_fdd.h"
namespace dd {
fdd::fdd(bdd_manager& manager, unsigned_vector&& vars)
: m_pos2var(std::move(vars))
, m_var2pos()
, m(&manager)
, m_var(manager.mk_var(m_pos2var))
{
for (unsigned pos = 0; pos < m_pos2var.size(); ++pos) {
unsigned const var = m_pos2var[pos];
while (var >= m_var2pos.size())
m_var2pos.push_back(UINT_MAX);
m_var2pos[var] = pos;
}
}
bdd fdd::non_zero() const {
bdd non_zero = m->mk_false();
for (unsigned var : m_pos2var) {
non_zero |= m->mk_var(var);
}
return non_zero;
}
unsigned fdd::var2pos(unsigned var) const {
return var < m_var2pos.size() ? m_var2pos[var] : UINT_MAX;
}
bool fdd::contains(bdd b, rational const& val) const {
while (!b.is_const()) {
unsigned const pos = var2pos(b.var());
SASSERT(pos != UINT_MAX && "Unexpected BDD variable");
b = val.get_bit(pos) ? b.hi() : b.lo();
}
return b.is_true();
}
find_t fdd::find(bdd b, rational& out_val) const {
return find_hint(b, rational::zero(), out_val);
}
find_t fdd::find_hint(bdd b, rational const& hint, rational& out_val) const {
out_val = 0;
if (b.is_false())
return find_t::empty;
bool is_unique = true;
bool hint_ok = !hint.is_zero(); // since we choose the 'lo' branch by default, we don't need to check the hint when it is 0.
unsigned num_vars = 0;
while (!b.is_true()) {
++num_vars;
unsigned const pos = var2pos(b.var());
SASSERT(pos != UINT_MAX && "Unexpected BDD variable");
bool go_hi = false;
if (b.lo().is_false()) {
go_hi = true;
if (hint_ok && !hint.get_bit(pos))
hint_ok = false;
}
else if (b.hi().is_false()) {
if (hint_ok && hint.get_bit(pos))
hint_ok = false;
}
else {
// This is the only case where we have a choice
// => follow the hint
SASSERT(!b.lo().is_false() && !b.hi().is_false());
is_unique = false;
if (hint_ok && hint.get_bit(pos))
go_hi = true;
}
if (go_hi) {
out_val += rational::power_of_two(pos);
b = b.hi();
}
else
b = b.lo();
}
if (num_vars != num_bits())
is_unique = false;
// If a variable corresponding to a 1-bit in hint does not appear in the BDD,
// out_val is wrong at this point, so we set it explicitly.
if (hint_ok)
out_val = hint;
// TODO: instead of computing out_val incrementally, we could mark the visited 'hi'-positions and only compute out_val from the marks when !hint_ok.
return is_unique ? find_t::singleton : find_t::multiple;
}
std::ostream& operator<<(std::ostream& out, find_t x) {
switch (x) {
case find_t::empty:
return out << "empty";
case find_t::singleton:
return out << "singleton";
case find_t::multiple:
return out << "multiple";
}
UNREACHABLE();
return out;
}
bool fdd::contains(bdd const& x, bool_vector const& value) const {
bdd b = x;
while (!b.is_true()) {
unsigned const pos = var2pos(b.var());
SASSERT(pos != UINT_MAX && "Unexpected BDD variable");
if (value[pos] && b.hi().is_false())
return false;
if (!value[pos] && b.lo().is_false())
return false;
if (value[pos])
b = b.hi();
else
b = b.lo();
}
return true;
}
// subtract one from x
static void dec(bool_vector& x) {
for (auto& b : x) {
b = !b;
if (!b)
break;
}
}
// add one to x
static void inc(bool_vector& x) {
for (auto& b : x) {
b = !b;
if (b)
break;
}
}
static void reset(bool_vector& x, bool value) {
for (auto& b : x)
b = value;
}
bool fdd::sup(bdd const& x, bool_vector& lo) const {
SASSERT(lo.size() == num_bits());
//
// Assumption: common case is that high-order bits are before lower-order bits also
// after re-ordering. Then co-factoring is relatively cheap.
//
if (!contains(x, lo))
return false;
//
// find minimal index where b is false for some
// value larger than lo.
//
// Let ua(x lo) be shorthand for "unbounded-above" of variable
// x with bit-string lo.
//
// we have the following identities:
// ua(_ []) = true
// ua(x 1 ++ lo) = hi(x) = T or ua(hi(x), lo)
// ua(x 0 ++ lo) = hi(x) = T and ua(lo(x), lo)
//
// the least significant bit where ua is false
// represents the position where the smallest number above
// lo resides that violates x.
unsigned idx = UINT_MAX;
vector<bdd> trail;
bdd b = x;
for (unsigned i = lo.size(); i-- > 0; ) {
trail.push_back(b);
unsigned v = m_pos2var[i];
bdd w = m->mk_var(v);
bdd hi = b.cofactor(w);
if (lo[i]) {
if (hi.is_true())
break;
SASSERT(!hi.is_false());
b = hi;
}
else {
if (!hi.is_true())
idx = i;
b = b.cofactor(m->mk_nvar(v));
}
}
if (idx == UINT_MAX) {
// all values above lo satisfy x
reset(lo, true);
return true;
}
SASSERT(!lo[idx]);
lo[idx] = true;
unsigned v = m_pos2var[idx];
b = trail[lo.size() - idx - 1].cofactor(m->mk_var(v));
for (unsigned i = idx; i-- > 0; ) {
SASSERT(!b.is_true());
if (b.is_false()) {
for (unsigned j = 0; j <= i; ++j)
lo[j] = false;
break;
}
lo[i] = b.lo().is_true();
if (lo[i])
b = b.hi();
else
b = b.lo();
}
dec(lo);
return true;
}
bool fdd::inf(bdd const& x, bool_vector& hi) const {
SASSERT(hi.size() == num_bits());
if (!contains(x, hi))
return false;
// Let ub(x hi) be shorthand for "unbounded-below" of variable
// x with bit-string hi.
//
// we have the following identities:
// ub(_ []) = true
// ub(x 0 ++ hi) = lo(x) = T or ub(lo(x), hi)
// ub(x 1 ++ hi) = lo(x) = T and ub(hi(x), hi)
//
unsigned idx = UINT_MAX;
vector<bdd> trail;
bdd b = x;
for (unsigned i = hi.size(); i-- > 0; ) {
trail.push_back(b);
unsigned v = m_pos2var[i];
bdd nw = m->mk_nvar(v);
bdd lo = b.cofactor(nw);
if (!hi[i]) {
if (lo.is_true())
break;
SASSERT(!lo.is_false());
b = lo;
}
else {
if (!lo.is_true())
idx = i;
b = b.cofactor(m->mk_var(v));
}
}
if (idx == UINT_MAX) {
// all values below hi satisfy x
reset(hi, false);
return true;
}
SASSERT(hi[idx]);
hi[idx] = false;
unsigned v = m_pos2var[idx];
b = trail[hi.size() - idx - 1].cofactor(m->mk_nvar(v));
for (unsigned i = idx; i-- > 0; ) {
SASSERT(!b.is_true());
if (b.is_false()) {
for (unsigned j = 0; j <= i; ++j)
hi[j] = true;
break;
}
hi[i] = !b.hi().is_true();
if (!hi[i])
b = b.lo();
else
b = b.hi();
}
inc(hi);
return true;
}
bool_vector fdd::rational2bits(rational const& r) const {
bool_vector result;
for (unsigned i = 0; i < num_bits(); ++i)
result.push_back(r.get_bit(i));
return result;
}
rational fdd::bits2rational(bool_vector const& v) const {
rational result(0);
for (unsigned i = 0; i < num_bits(); ++i)
if (v[i])
result += rational::power_of_two(i);
return result;
}
bool fdd::sup(bdd const& b, rational& _lo) const {
bool_vector lo = rational2bits(_lo);
if (!sup(b, lo))
return false;
_lo = bits2rational(lo);
return true;
}
bool fdd::inf(bdd const& b, rational& _hi) const {
bool_vector hi = rational2bits(_hi);
if (!inf(b, hi))
return false;
_hi = bits2rational(hi);
return true;
}
rational fdd::max(bdd b) const {
SASSERT(!b.is_false());
rational result(0);
for (unsigned i = num_bits(); i-- > 0; ) {
unsigned v = m_pos2var[i];
bdd w = m->mk_var(v);
bdd hi = b.cofactor(w);
if (!hi.is_false()) {
b = hi;
result += rational::power_of_two(i);
}
}
return result;
}
rational fdd::min(bdd b) const {
SASSERT(!b.is_false());
rational result(0);
for (unsigned i = num_bits(); i-- > 0; ) {
unsigned v = m_pos2var[i];
bdd nw = m->mk_nvar(v);
bdd lo = b.cofactor(nw);
if (lo.is_false())
result += rational::power_of_two(i);
else
b = lo;
}
return result;
}
}

108
src/math/dd/dd_fdd.h Normal file
View file

@ -0,0 +1,108 @@
/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
dd_fdd
Abstract:
Finite domain abstraction for using BDDs as sets of integers, inspired by BuDDy's fdd module.
Author:
Jakob Rath 2021-04-20
Nikolaj Bjorner (nbjorner) 2021-04-20
--*/
#pragma once
#include "math/dd/dd_bdd.h"
#include "util/vector.h"
#include "util/rational.h"
namespace dd {
enum class find_t { empty, singleton, multiple };
std::ostream& operator<<(std::ostream& out, find_t x);
/**
* Finite domain abstraction over BDDs.
*/
class fdd {
unsigned_vector m_pos2var; // pos -> BDD var
unsigned_vector m_var2pos; // var -> pos (pos = place number in the bit representation, 0 is LSB's place)
bdd_manager* m;
bddv m_var;
static unsigned_vector seq(unsigned count, unsigned start = 0, unsigned step = 1) {
unsigned_vector result;
unsigned k = start;
for (unsigned i = 0; i < count; ++i, k += step)
result.push_back(k);
return result;
}
unsigned var2pos(unsigned var) const;
bool contains(bdd const& b, bool_vector const& value) const;
rational bits2rational(bool_vector const& v) const;
bool_vector rational2bits(rational const& r) const;
public:
/** Initialize FDD using BDD variables from 0 to num_bits-1. */
fdd(bdd_manager& manager, unsigned num_bits, unsigned start = 0, unsigned step = 1) : fdd(manager, seq(num_bits, start, step)) { }
fdd(bdd_manager& manager, unsigned_vector const& vars) : fdd(manager, unsigned_vector(vars)) { }
fdd(bdd_manager& manager, unsigned_vector&& vars);
unsigned num_bits() const { return m_pos2var.size(); }
unsigned_vector const& bdd_vars() const { return m_pos2var; }
bddv const& var() const { return m_var; }
/** Equivalent to var() != 0 */
bdd non_zero() const;
/** Checks whether the integer val is contained in the BDD when viewed as set of integers.
* Precondition: the bdd only contains variables managed by this fdd.
*/
bool contains(bdd b, rational const& val) const;
/** Returns an integer contained in the BDD, if any, and whether the BDD is a singleton.
* Precondition: the bdd only contains variables managed by this fdd.
*/
find_t find(bdd b, rational& out_val) const;
/** Like find, but returns hint if it is contained in the BDD. */
find_t find_hint(bdd b, rational const& hint, rational& out_val) const;
/*
* find largest value at lo or above such that bdd b evaluates to true
* at lo and all values between.
* dually, find smallest value below hi that evaluates b to true
* and all values between the value and hi also evaluate b to true.
* \param b - a bdd using variables from this
* \param lo/hi - bound to be traversed.
* \return false if b is false at lo/hi
* \pre variables in b are a subset of variables from the fdd
*/
bool sup(bdd const& b, bool_vector& lo) const;
bool inf(bdd const& b, bool_vector& hi) const;
bool sup(bdd const& b, rational& lo) const;
bool inf(bdd const& b, rational& hi) const;
/*
* Find the min-max satisfying assignment.
* \pre b is not false.
*/
rational max(bdd b) const;
rational min(bdd b) const;
};
}