3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-29 03:45:51 +00:00

Add BDD utilities; use them for narrowing/propagation of linear equality constraints (#5192)

* Add a few helper methods for encoding sets of integers as BDDs

* Use BDD functions in solver

* Add bdd::find_int

* Use bdd::find_int in solver

* Add narrowing for linear equality constraints

* Simplify code for linear propagation

* Add test for later

* Narrowing can only handle linear constraints with a single variable

* Need to push_cjust
This commit is contained in:
Jakob Rath 2021-04-16 17:44:18 +02:00 committed by GitHub
parent e970fe5034
commit f72e30e539
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 208 additions and 104 deletions

View file

@ -36,7 +36,6 @@ namespace polysat {
return true;
}
}
LOG("Assignments: " << s.m_search);
auto q = p().subst_val(s.m_search);
@ -52,71 +51,24 @@ namespace polysat {
// at most one variable remains unassigned.
auto other_var = vars()[1 - idx];
SASSERT(!q.is_val() && q.var() == other_var);
// Detect and apply unit propagation.
if (!q.is_linear())
return false;
// a*x + b == 0
rational a = q.hi().val();
rational b = q.lo().val();
rational inv_a;
if (a.is_odd()) {
// v1 = -b * inverse(a)
unsigned sz = q.power_of_2();
VERIFY(a.mult_inverse(sz, inv_a));
rational val = mod(inv_a * -b, rational::power_of_two(sz));
s.m_cjust[other_var].push_back(this);
s.propagate(other_var, val, *this);
return false;
}
SASSERT(!b.is_odd()); // otherwise p.is_never_zero() would have been true above
// TBD
// constrain viable using condition on x
// 2*x + 2 == 0 mod 4 => x is odd
//
// We have:
// 2^j*a'*x + 2^j*b' == 0 mod m, where a' is odd (but not necessarily b')
// <=> 2^j*(a'*x + b') == 0 mod m
// <=> a'*x + b' == 0 mod (m-j)
// <=> x == -b' * inverse_{m-j}(a') mod (m-j)
// ( <=> 2^j*x == 2^j * -b' * inverse_{m-j}(a') mod m )
//
// x == c mod (m-j)
// Which x in 2^m satisfy this?
// => x \in { c + k * 2^(m-j) | k = 0, ..., 2^j - 1 }
unsigned rank_a = a.trailing_zeros(); // j
SASSERT(b == 0 || rank_a <= b.trailing_zeros());
rational aa = a / rational::power_of_two(rank_a); // a'
rational bb = b / rational::power_of_two(rank_a); // b'
rational inv_aa;
unsigned small_sz = q.power_of_2() - rank_a; // m - j
VERIFY(aa.mult_inverse(small_sz, inv_aa));
rational cc = mod(inv_aa * -bb, rational::power_of_two(small_sz));
LOG(m_vars[other_var] << " = " << cc << " + k * 2^" << small_sz);
// TODO: better way to update the BDD, e.g. construct new one (only if rank_a is small?)
vector<rational> viable;
for (rational k = rational::zero(); k < rational::power_of_two(rank_a); k += 1) {
rational val = cc + k * rational::power_of_two(small_sz);
viable.push_back(val);
}
LOG_V("still viable: " << viable);
unsigned i = 0;
for (rational r = rational::zero(); r < rational::power_of_two(q.power_of_2()); r += 1) {
while (i < viable.size() && viable[i] < r)
++i;
if (i < viable.size() && viable[i] == r)
continue;
if (s.is_viable(other_var, r)) {
s.add_non_viable(other_var, r);
if (try_narrow_with(q, s)) {
rational val;
switch (s.find_viable(other_var, val)) {
case dd::find_int_t::empty:
s.set_conflict(*this);
return false;
case dd::find_int_t::singleton:
s.propagate(other_var, val, *this);
return false;
case dd::find_int_t::multiple:
/* do nothing */
break;
}
}
LOG("TODO");
return false;
}
@ -137,10 +89,23 @@ namespace polysat {
return nullptr;
}
bool eq_constraint::try_narrow_with(pdd const& q, solver& s) {
if (q.is_linear() && q.free_vars().size() == 1) {
// a*x + b == 0
pvar v = q.var();
rational a = q.hi().val();
rational b = q.lo().val();
bdd xs = s.m_bdd.mk_affine(a, b, s.size(v));
s.intersect_viable(v, xs);
s.push_cjust(v, this);
return true;
}
// TODO: what other constraints can be extracted cheaply?
return false;
}
void eq_constraint::narrow(solver& s) {
if (!p().is_linear())
return;
// TODO apply affine constraints and other that can be extracted cheaply
(void)try_narrow_with(p(), s);
}
bool eq_constraint::is_always_false() {

View file

@ -35,6 +35,7 @@ namespace polysat {
bool is_always_false() override;
bool is_currently_false(solver& s) override;
void narrow(solver& s) override;
bool try_narrow_with(pdd const& q, solver& s);
};
}

View file

@ -30,44 +30,23 @@ namespace polysat {
}
bool solver::is_viable(pvar v, rational const& val) {
bdd b = m_viable[v];
for (unsigned k = size(v); k-- > 0 && !b.is_false(); )
b &= val.get_bit(k) ? m_bdd.mk_var(k) : m_bdd.mk_nvar(k);
return !b.is_false();
return m_viable[v].contains_int(val, size(v));
}
void solver::add_non_viable(pvar v, rational const& val) {
LOG("pvar " << v << " /= " << val);
TRACE("polysat", tout << "v" << v << " /= " << val << "\n";);
bdd value = m_bdd.mk_true();
for (unsigned k = size(v); k-- > 0; )
value &= val.get_bit(k) ? m_bdd.mk_var(k) : m_bdd.mk_nvar(k);
SASSERT((value && !m_viable[v]).is_false());
push_viable(v);
m_viable[v] &= !value;
SASSERT(is_viable(v, val));
intersect_viable(v, !m_bdd.mk_int(val, size(v)));
}
lbool solver::find_viable(pvar v, rational & val) {
val = 0;
bdd viable = m_viable[v];
if (viable.is_false())
return l_false;
bool is_unique = true;
unsigned num_vars = 0;
while (!viable.is_true()) {
++num_vars;
if (!viable.lo().is_false() && !viable.hi().is_false())
is_unique = false;
if (viable.lo().is_false()) {
val += rational::power_of_two(viable.var());
viable = viable.hi();
}
else
viable = viable.lo();
}
is_unique &= num_vars == size(v);
TRACE("polysat", tout << "v" << v << " := " << val << " unique " << is_unique << "\n";);
return is_unique ? l_true : l_undef;
void solver::intersect_viable(pvar v, bdd vals) {
push_viable(v);
m_viable[v] &= vals;
}
dd::find_int_t solver::find_viable(pvar v, rational & val) {
return m_viable[v].find_int(size(v), val);
}
@ -312,15 +291,15 @@ namespace polysat {
IF_LOGGING(log_viable(v));
rational val;
switch (find_viable(v, val)) {
case l_false:
case dd::find_int_t::empty:
LOG("Conflict: no value for pvar " << v);
set_conflict(v);
break;
case l_true:
case dd::find_int_t::singleton:
LOG("Propagation: pvar " << v << " := " << val << " (due to unique value)");
assign_core(v, val, justification::propagation(m_level));
break;
case l_undef:
case dd::find_int_t::multiple:
LOG("Decision: pvar " << v << " := " << val);
push_level();
assign_core(v, val, justification::decision(m_level));

View file

@ -111,6 +111,11 @@ namespace polysat {
*/
void add_non_viable(pvar v, rational const& val);
/**
* Register all values that are not contained in vals as non-viable.
*/
void intersect_viable(pvar v, bdd vals);
/**
* Add dependency for variable viable range.
*/
@ -119,11 +124,8 @@ namespace polysat {
/**
* Find a next viable value for variable.
* l_false - there are no viable values.
* l_true - there is only one viable value left.
* l_undef - there are multiple viable values, return a guess
*/
lbool find_viable(pvar v, rational & val);
dd::find_int_t find_viable(pvar v, rational & val);
/** Log all viable values for the given variable.
* (Inefficient, but useful for debugging small instances.)