mirror of
https://github.com/Z3Prover/z3
synced 2026-03-06 13:24:51 +00:00
Move interval projection out of viable
This commit is contained in:
parent
5339a2f70f
commit
f47fbdd714
7 changed files with 638 additions and 456 deletions
|
|
@ -22,13 +22,14 @@ Notes:
|
|||
#include "sat/smt/polysat/core.h"
|
||||
#include "sat/smt/polysat/number.h"
|
||||
#include "sat/smt/polysat/refine.h"
|
||||
#include "sat/smt/polysat/project_interval.h"
|
||||
#include "sat/smt/polysat/ule_constraint.h"
|
||||
|
||||
namespace polysat {
|
||||
|
||||
using dd::val_pp;
|
||||
|
||||
viable::viable(core& c) : c(c), cs(c.cs()), m_forbidden_intervals(c), m_fixed_bits(c) {}
|
||||
viable::viable(core& c) : c(c), cs(c.cs()), m_forbidden_intervals(c), m_projection(c), m_fixed_bits(c) {}
|
||||
|
||||
viable::~viable() {
|
||||
for (auto* e : m_alloc)
|
||||
|
|
@ -640,8 +641,9 @@ namespace polysat {
|
|||
// there is just one entry
|
||||
SASSERT(m_explain.size() == 1);
|
||||
SASSERT(!m_assign_dep.is_null());
|
||||
explain_entry(last.e);
|
||||
entry* e = last.e;
|
||||
|
||||
explain_entry(e);
|
||||
// assignment conflict and propagation from containing slice depends on concrete values,
|
||||
// so we also need the evaluations of linear terms
|
||||
SASSERT(!c.is_assigned(m_var)); // assignment of m_var is justified by m_assign_dep
|
||||
|
|
@ -649,15 +651,17 @@ namespace polysat {
|
|||
if (!index.is_null())
|
||||
result.append(c.explain_weak_eval(c.get_constraint(index)));
|
||||
|
||||
// 'result' so far contains explanation for entry and its weak evaluation
|
||||
switch (propagate_from_containing_slice(last.e, result)) {
|
||||
// 'result' so far contains explanation for e and its weak evaluation
|
||||
m_projection.init(e->var, e->interval, e->bit_width, result);
|
||||
|
||||
switch (m_projection()) {
|
||||
case l_true:
|
||||
// propagated interval onto subslice
|
||||
result = m_containing_slice_deps;
|
||||
m_projection.explain(result);
|
||||
break;
|
||||
case l_false:
|
||||
// conflict (projected interval is full)
|
||||
result = m_containing_slice_deps;
|
||||
m_projection.explain(result);
|
||||
break;
|
||||
case l_undef:
|
||||
// unable to propagate interval to containing slice
|
||||
|
|
@ -672,417 +676,6 @@ namespace polysat {
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* l_true: successfully projected interval onto subslice
|
||||
* l_false: also successfully projected interval onto subslice, resulted in full interval
|
||||
* l_undef: failed
|
||||
*
|
||||
* In case of l_true/l_false, conflict will be in m_containing_slice_deps.
|
||||
*/
|
||||
lbool viable::propagate_from_containing_slice(entry* e, dependency_vector const& e_deps) {
|
||||
verbose_stream() << "\n\n\n\n\n\nNon-viable assignment for v" << m_var << " size " << c.size(m_var) << "\n";
|
||||
display_one(verbose_stream() << "entry: ", e) << "\n";
|
||||
// verbose_stream() << "value " << value << "\n";
|
||||
// verbose_stream() << "m_overlaps " << m_overlaps << "\n";
|
||||
m_fixed_bits.display(verbose_stream() << "fixed: ") << "\n";
|
||||
|
||||
// TODO: each of subslices corresponds to one in fixed, but may occur with different pvars.
|
||||
// for each offset/length with pvar we need to do the projection only once.
|
||||
// although, the max admissible level of fixed slices depends on the child pvar under consideration, so we may not get the optimal interval anymore?
|
||||
// (pvars on the same slice only differ by level. the fixed value is the same for all. so we can limit by the max level of pvars and then the projection will work for at least one.)
|
||||
fixed_bits_vector fixed;
|
||||
c.get_fixed_subslices(e->var, fixed);
|
||||
|
||||
bool has_pvar = any_of(fixed, [](fixed_slice const& f) { return f.child != null_var; });
|
||||
|
||||
// this case occurs if e-graph merges e.g. nodes "x - 2" and "3";
|
||||
// polysat will see assignment "x = 5" but no fixed bits
|
||||
if (!has_pvar)
|
||||
return l_undef;
|
||||
|
||||
// level when subslice of m_var became fixed
|
||||
unsigned_vector fixed_levels;
|
||||
for (auto const& f : fixed) {
|
||||
dependency dep = fixed_claim(e->var, null_var, f.value, f.offset, f.length);
|
||||
unsigned level = c.s.level(dep);
|
||||
fixed_levels.push_back(level);
|
||||
}
|
||||
|
||||
// level when target pvars became fixed
|
||||
unsigned_vector pvar_levels;
|
||||
for (auto const& f : fixed) {
|
||||
unsigned level = UINT_MAX;
|
||||
if (f.child != null_var) {
|
||||
dependency dep = fixed_claim(f.child, null_var, f.value, 0, f.length);
|
||||
level = c.s.level(dep);
|
||||
}
|
||||
pvar_levels.push_back(level);
|
||||
}
|
||||
|
||||
TRACE("bv", c.display(tout));
|
||||
|
||||
unsigned_vector order;
|
||||
for (unsigned i = 0; i < fixed.size(); ++i) {
|
||||
fixed_slice const& f = fixed[i];
|
||||
if (f.child == null_var)
|
||||
continue;
|
||||
order.push_back(i);
|
||||
}
|
||||
|
||||
// order by:
|
||||
// - level descending
|
||||
// usually, a sub-slice at higher level is responsible for the assignment.
|
||||
// not always: e.g., could assign slice at level 5 but merge makes it a sub-slice only at level 10.
|
||||
// (seems to work by not only considering max-level sub-slices.)
|
||||
// - size ascending
|
||||
// e.g., prefers 'c' if we have pvars for both 'c' and 'concat(c,...)'
|
||||
std::sort(order.begin(), order.end(), [&](auto const& i1, auto const& i2) -> bool {
|
||||
unsigned lvl1 = pvar_levels[i1];
|
||||
unsigned lvl2 = pvar_levels[i2];
|
||||
pvar v1 = fixed[i1].child;
|
||||
pvar v2 = fixed[i2].child;
|
||||
return lvl1 > lvl2
|
||||
|| (lvl1 == lvl2 && c.size(v1) < c.size(v2));
|
||||
});
|
||||
|
||||
for (auto const& i : order) {
|
||||
auto const& slice = fixed[i];
|
||||
unsigned const slice_level = pvar_levels[i];
|
||||
SASSERT(slice.child != null_var);
|
||||
lbool r = propagate_from_containing_slice(e, e_deps, fixed, fixed_levels, slice, slice_level);
|
||||
if (r != l_undef)
|
||||
return r;
|
||||
}
|
||||
return l_undef;
|
||||
}
|
||||
|
||||
lbool viable::propagate_from_containing_slice(entry* e, dependency_vector const& e_deps, fixed_bits_vector const& fixed, unsigned_vector const& fixed_levels, fixed_slice const& slice, unsigned slice_level) {
|
||||
pvar const w = slice.child;
|
||||
unsigned const offset = slice.offset;
|
||||
unsigned const w_level = slice_level; // level where value of w was fixed
|
||||
SASSERT(w != null_var);
|
||||
SASSERT_EQ(c.size(w), slice.length);
|
||||
if (w == m_var) {
|
||||
VERIFY(m_var == e->var);
|
||||
return l_undef;
|
||||
}
|
||||
if (w == e->var)
|
||||
return l_undef;
|
||||
|
||||
// verbose_stream() << "v" << m_var << " size " << c.size(m_var) << ", v" << w << " size " << c.size(w) << " offset " << offset << " level " << w_level << "\n";
|
||||
|
||||
// Let:
|
||||
// v = m_var[e->bit_width-1:0]
|
||||
// v = concat(x, y, z)
|
||||
// y = w[y_sz-1:0]
|
||||
|
||||
// e->bit_width
|
||||
// m_var = ???????vvvvvvvvvvvvvvvvvvvvvvv
|
||||
// wwwwwwww
|
||||
// xxxxxxyyyyyyyyzzzzzzzzz
|
||||
// y_sz offset
|
||||
//
|
||||
// or:
|
||||
// m_var = ???????vvvvvvvvvvvvvvvvvvvvvvv
|
||||
// wwwwwwwwwwwwwwwww
|
||||
// yyyyyyyyyyyyyyzzzzzzzzz
|
||||
//
|
||||
// or:
|
||||
// m_var = ?????????????????vvvvvvvvvvvvv
|
||||
// wwwwwwwww
|
||||
unsigned const v_sz = e->bit_width;
|
||||
if (offset >= v_sz)
|
||||
return l_undef;
|
||||
|
||||
unsigned const w_sz = slice.length;
|
||||
unsigned const z_sz = offset;
|
||||
unsigned const y_sz = std::min(w_sz, v_sz - z_sz);
|
||||
unsigned const x_sz = v_sz - y_sz - z_sz;
|
||||
rational const& w_shift = rational::power_of_two(w_sz - y_sz);
|
||||
// verbose_stream() << "v_sz " << v_sz << " w_sz " << w_sz << " / x_sz " << x_sz << " y_sz " << y_sz << " z_sz " << z_sz << "\n";
|
||||
SASSERT_EQ(v_sz, x_sz + y_sz + z_sz);
|
||||
|
||||
SASSERT(!e->interval.is_full());
|
||||
rational const& lo = e->interval.lo_val();
|
||||
rational const& hi = e->interval.hi_val();
|
||||
|
||||
r_interval const v_ivl = r_interval::proper(lo, hi);
|
||||
IF_VERBOSE(3, {
|
||||
verbose_stream() << "propagate interval " << v_ivl << " from v" << e->var << " to v" << w << "[" << y_sz << "]" << "\n";
|
||||
});
|
||||
|
||||
dependency_vector& deps = m_containing_slice_deps;
|
||||
deps.reset();
|
||||
|
||||
r_interval ivl = v_ivl;
|
||||
|
||||
// chop off x-part, taking fixed values into account whenever possible.
|
||||
unsigned const x_off = y_sz + z_sz;
|
||||
unsigned remaining_x_sz = x_sz;
|
||||
while (remaining_x_sz > 0 && !ivl.is_empty() && !ivl.is_full()) {
|
||||
unsigned remaining_x_end = remaining_x_sz + x_off;
|
||||
// find next fixed slice (prefer lower level)
|
||||
fixed_slice best;
|
||||
unsigned best_end = 0;
|
||||
unsigned best_level = UINT_MAX;
|
||||
SASSERT(best_end < x_off); // because y_sz != 0
|
||||
for (unsigned i = 0; i < fixed.size(); ++i) {
|
||||
auto const& f = fixed[i];
|
||||
unsigned const f_level = fixed_levels[i];
|
||||
if (f_level >= w_level)
|
||||
continue;
|
||||
// ??????xxxxxxxyyyyyyzzzz
|
||||
// 1111 not useful at this point
|
||||
// 11111 OK
|
||||
// 1111 OK (has gap without fixed value)
|
||||
// 1111 NOT OK (overlaps y) ... although, why would that not be ok? it just restricts values of y too. maybe this can be used to improve interval for y further.
|
||||
// 111111 not useful at this point
|
||||
if (f.offset >= remaining_x_end)
|
||||
continue;
|
||||
if (f.end() <= x_off)
|
||||
continue;
|
||||
unsigned const f_end = std::min(remaining_x_end, f.end()); // for comparison, values beyond the current range don't matter
|
||||
if (f_end > best_end)
|
||||
best = f, best_end = f_end, best_level = f_level;
|
||||
else if (f_end == best_end && f_level < best_level)
|
||||
best = f, best_end = f_end, best_level = f_level;
|
||||
}
|
||||
|
||||
if (best_end < remaining_x_end) {
|
||||
// there is a gap without a fixed value
|
||||
unsigned const b = std::max(best_end, x_off);
|
||||
unsigned const a = remaining_x_end - b;
|
||||
SASSERT(remaining_x_sz >= a);
|
||||
|
||||
ivl = chop_off_upper(ivl, a, b);
|
||||
remaining_x_sz -= a;
|
||||
remaining_x_end -= a;
|
||||
|
||||
IF_VERBOSE(4, {
|
||||
verbose_stream() << " chop " << a << " upper bits\n";
|
||||
verbose_stream() << " => " << ivl << "\n";
|
||||
});
|
||||
}
|
||||
|
||||
if (best_end > x_off) {
|
||||
SASSERT(remaining_x_end == best_end);
|
||||
SASSERT(remaining_x_end <= best.end());
|
||||
// chop off portion with fixed value
|
||||
unsigned const b = std::max(x_off, best.offset);
|
||||
unsigned const a = remaining_x_end - b;
|
||||
rational value = best.value;
|
||||
if (b > best.offset)
|
||||
value = machine_div2k(value, b - best.offset);
|
||||
value = mod2k(value, a);
|
||||
|
||||
ivl = chop_off_upper(ivl, a, b, &value);
|
||||
dependency best_dep = fixed_claim(e->var, null_var, best.value, best.offset, best.length);
|
||||
deps.push_back(best_dep); // justification for the fixed value
|
||||
remaining_x_sz -= a;
|
||||
remaining_x_end -= a;
|
||||
|
||||
IF_VERBOSE(4, {
|
||||
verbose_stream() << " chop " << a << " upper bits with value " << value << " from fixed slice " << best.value << "[" << best.length << "]@" << best.offset << "\n";
|
||||
verbose_stream() << " => " << ivl << "\n";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ivl.is_empty())
|
||||
return l_undef;
|
||||
|
||||
// chop off z-part
|
||||
unsigned remaining_z_sz = z_sz;
|
||||
while (remaining_z_sz > 0 && !ivl.is_empty() && !ivl.is_full()) {
|
||||
SASSERT(remaining_x_sz == 0);
|
||||
unsigned remaining_z_off = z_sz - remaining_z_sz;
|
||||
// find next fixed slice (prefer lower level)
|
||||
fixed_slice best;
|
||||
unsigned best_off = z_sz;
|
||||
unsigned best_level = UINT_MAX;
|
||||
for (unsigned i = 0; i < fixed.size(); ++i) {
|
||||
auto const& f = fixed[i];
|
||||
unsigned const f_level = fixed_levels[i];
|
||||
if (f_level >= w_level)
|
||||
continue;
|
||||
// ?????????????yyyyyyzzzzz???
|
||||
// 1111 not useful at this point
|
||||
// 1111 OK
|
||||
// 1111 OK (has gap without fixed value)
|
||||
// 111 not useful
|
||||
if (f.offset >= z_sz)
|
||||
continue;
|
||||
if (f.end() <= remaining_z_off)
|
||||
continue;
|
||||
unsigned const f_off = std::max(remaining_z_off, f.offset); // for comparison, values beyond the current range don't matter
|
||||
if (f_off < best_off)
|
||||
best = f, best_off = f_off, best_level = f_level;
|
||||
else if (f_off == best_off && f_level < best_level)
|
||||
best = f, best_off = f_off, best_level = f_level;
|
||||
}
|
||||
|
||||
if (best_off > remaining_z_off) {
|
||||
// there is a gap without a fixed value
|
||||
unsigned const b = best_off - remaining_z_off;
|
||||
unsigned const a = y_sz + z_sz - b;
|
||||
SASSERT(remaining_z_sz >= b);
|
||||
|
||||
ivl = chop_off_lower(ivl, a, b);
|
||||
remaining_z_sz -= b;
|
||||
remaining_z_off += b;
|
||||
|
||||
IF_VERBOSE(4, {
|
||||
verbose_stream() << " chop " << b << " lower bits\n";
|
||||
verbose_stream() << " => " << ivl << "\n";
|
||||
});
|
||||
}
|
||||
|
||||
if (best_off < z_sz) {
|
||||
SASSERT_EQ(best_off, remaining_z_off);
|
||||
unsigned const b = std::min(best.end(), z_sz) - remaining_z_off;
|
||||
unsigned const a = y_sz + z_sz - b;
|
||||
rational value = best.value;
|
||||
if (best.offset < best_off)
|
||||
value = machine_div2k(value, best_off - best.offset);
|
||||
value = mod2k(value, b);
|
||||
|
||||
ivl = chop_off_lower(ivl, a, b, &value);
|
||||
dependency best_dep = fixed_claim(e->var, null_var, best.value, best.offset, best.length);
|
||||
deps.push_back(best_dep); // justification for the fixed value
|
||||
remaining_z_sz -= b;
|
||||
remaining_z_off += b;
|
||||
|
||||
IF_VERBOSE(4, {
|
||||
verbose_stream() << " chop " << b << " lower bits with value " << value << " from fixed slice " << best.value << "[" << best.length << "]@" << best.offset << "\n";
|
||||
verbose_stream() << " => " << ivl << "\n";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ivl.is_empty())
|
||||
return l_undef;
|
||||
|
||||
IF_VERBOSE(3, {
|
||||
verbose_stream() << "=> " << ivl << "\n";
|
||||
});
|
||||
|
||||
deps.append(e_deps); // explains e
|
||||
deps.push_back(offset_claim{e->var, offset_slice{w, slice.offset}}); // explains m_var[...] = w
|
||||
|
||||
// TODO: try first the most general projection (without assuming fixed slices; purpose: get lemma with less dependencies)
|
||||
// TODO: for each fixed slice with multiple pvars, do the projection only once?
|
||||
|
||||
if (ivl.is_full()) {
|
||||
// deps is a conflict
|
||||
return l_false;
|
||||
}
|
||||
else {
|
||||
// proper interval
|
||||
SASSERT(ivl.is_proper() && !ivl.is_empty());
|
||||
// deps => 2^w_shift w not in ivl
|
||||
signed_constraint sc = ~cs.ult(w_shift * (c.var(w) - ivl.lo()), w_shift * (ivl.hi() - ivl.lo()));
|
||||
dependency d = c.propagate(sc, deps, "propagate from containing slice");
|
||||
|
||||
// verbose_stream() << "v" << w << " value " << slice.value << "\n";
|
||||
// verbose_stream() << "v" << w << " value-dep " << slice.dep << "\n";
|
||||
|
||||
if (ivl.contains(slice.value)) {
|
||||
IF_VERBOSE(3, {
|
||||
verbose_stream() << "=> v" << w << "[" << y_sz << "] not in " << ivl << "\n";
|
||||
});
|
||||
// the conflict is projected interval + fixed value
|
||||
deps.reset();
|
||||
deps.push_back(d);
|
||||
deps.push_back(fixed_claim(w, null_var, slice.value, 0, w_sz));
|
||||
// deps.push_back(slice.dep);
|
||||
return l_true;
|
||||
}
|
||||
else {
|
||||
// interval propagation worked but it doesn't conflict the currently assigned value
|
||||
// TODO: also skip propagation of the signed constraint in this case?
|
||||
return l_undef;
|
||||
}
|
||||
}
|
||||
|
||||
return l_undef;
|
||||
}
|
||||
|
||||
|
||||
/// Let x = concat(y, z) and x not in [lo;hi[.
|
||||
/// Returns an interval I such that z not in I.
|
||||
r_interval viable::chop_off_upper(r_interval const& i, unsigned const Ny, unsigned const Nz, rational const* y_fixed_value) {
|
||||
if (i.is_full())
|
||||
return r_interval::full();
|
||||
if (i.is_empty())
|
||||
return r_interval::empty();
|
||||
if (Ny == 0)
|
||||
return i;
|
||||
unsigned const Nx = Ny + Nz;
|
||||
rational const& lo = i.lo();
|
||||
rational const& hi = i.hi();
|
||||
if (y_fixed_value) {
|
||||
rational const& n = *y_fixed_value;
|
||||
rational const lo_d = div2k_floor(lo, Nz);
|
||||
rational const hi_d = div2k_floor(hi, Nz);
|
||||
rational z_lo = (lo_d == n) ? mod2k(lo, Nz) : rational(0);
|
||||
rational z_hi = (hi_d == n) ? mod2k(hi, Nz) : rational(0);
|
||||
if (z_lo != z_hi)
|
||||
return r_interval::proper(std::move(z_lo), std::move(z_hi));
|
||||
else if (r_interval::contains(lo, hi, n * rational::power_of_two(Nz)))
|
||||
return r_interval::full(); // no value for z
|
||||
else
|
||||
return r_interval::empty(); // z unconstrained
|
||||
}
|
||||
else {
|
||||
rational const Mx = rational::power_of_two(Nx);
|
||||
rational const Mz = rational::power_of_two(Nz);
|
||||
rational const len = r_interval::len(lo, hi, Mx);
|
||||
if (len > Mx - Mz)
|
||||
return r_interval::proper(mod2k(lo, Nz), mod2k(hi, Nz));
|
||||
else
|
||||
return r_interval::empty(); // z unconstrained
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
/// Let x = concat(y, z) and x not in [lo;hi[.
|
||||
/// Returns an interval I such that y not in I.
|
||||
r_interval viable::chop_off_lower(r_interval const& i, unsigned const Ny, unsigned const Nz, rational const* z_fixed_value) {
|
||||
if (i.is_full())
|
||||
return r_interval::full();
|
||||
if (i.is_empty())
|
||||
return r_interval::empty();
|
||||
if (Nz == 0)
|
||||
return i;
|
||||
unsigned const Nx = Ny + Nz;
|
||||
rational const& lo = i.lo();
|
||||
rational const& hi = i.hi();
|
||||
if (z_fixed_value) {
|
||||
rational const& n = *z_fixed_value;
|
||||
rational y_lo = mod2k(div2k_ceil(mod2k(lo - n, Nx), Nz), Ny);
|
||||
rational y_hi = mod2k(div2k_ceil(mod2k(hi - n, Nx), Nz), Ny);
|
||||
if (y_lo != y_hi)
|
||||
return r_interval::proper(std::move(y_lo), std::move(y_hi));
|
||||
else if (r_interval::contains(lo, hi, y_hi * rational::power_of_two(Nz) + n))
|
||||
return r_interval::full(); // no value for y
|
||||
else
|
||||
return r_interval::empty(); // y unconstrained
|
||||
}
|
||||
else {
|
||||
rational const Mx = rational::power_of_two(Nx);
|
||||
rational const Mz = rational::power_of_two(Nz);
|
||||
rational const len = r_interval::len(lo, hi, Mx);
|
||||
if (len >= Mz) {
|
||||
rational y_lo = mod2k(div2k_ceil(lo, Nz), Ny);
|
||||
rational y_hi = div2k_floor(hi, Nz);
|
||||
return r_interval::proper(std::move(y_lo), std::move(y_hi));
|
||||
}
|
||||
else
|
||||
return r_interval::empty(); // y unconstrained
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
/*
|
||||
* For two consecutive intervals
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue