3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-24 17:45:32 +00:00

Contract bit information to large unit-intervals

This commit is contained in:
Clemens Eisenhofer 2023-02-17 15:32:43 +01:00
parent 0dae2d40b5
commit 3f8edb9aac
8 changed files with 346 additions and 118 deletions

View file

@ -234,7 +234,7 @@ namespace polysat {
m_lemmas.push_back(&cl);
SASSERT(!empty());
logger().begin_conflict();
logger().begin_conflict(cl.name());
}
void conflict::init_by_viable_interval(pvar v) {

View file

@ -41,7 +41,7 @@ namespace polysat {
backtrack _backtrack(fi.side_cond);
fi.coeff = 1;
fi.src = c;
fi.src.push_back(c);
// eval(lhs) = a1*v + eval(e1) = a1*v + b1
// eval(rhs) = a2*v + eval(e2) = a2*v + b2
@ -135,7 +135,7 @@ namespace polysat {
backtrack _backtrack(fi.side_cond);
fi.coeff = 1;
fi.src = c;
fi.src.push_back(c);
struct show {
forbidden_intervals& f;

View file

@ -23,7 +23,7 @@ namespace polysat {
struct fi_record {
eval_interval interval;
vector<signed_constraint> side_cond;
signed_constraint src;
vector<signed_constraint> src; // only units may have multiple src (as they can consist of contracted bit constraints)
rational coeff;
/** Create invalid fi_record */

View file

@ -275,7 +275,7 @@ namespace polysat {
}
else {
// forward propagation
SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
/*SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
if (qv.is_val() && !rv.is_val()) {
const rational& qr = qv.val();
if (qr >= m.power_of_2())
@ -285,7 +285,7 @@ namespace polysat {
const rational& pr = pv.val();
return s.mk_clause(~lshr, ~s.eq(p(), m.mk_val(pr)), ~s.eq(q(), m.mk_val(qr)), s.eq(r(), m.mk_val(machine_div(pr, rational::power_of_two(qr.get_unsigned())))), true);
}
}
}*/
}
return {};
}
@ -365,7 +365,7 @@ namespace polysat {
}
else {
// forward propagation
SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
/*SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
if (qv.is_val() && !rv.is_val()) {
const rational& qr = qv.val();
if (qr >= m.power_of_2())
@ -375,7 +375,7 @@ namespace polysat {
const rational& pr = pv.val();
return s.mk_clause(~shl, ~s.eq(p(), m.mk_val(pr)), ~s.eq(q(), m.mk_val(qr)), s.eq(r(), m.mk_val(rational::power_of_two(qr.get_unsigned()) * pr)), true);
}
}
}*/
}
return {};
}
@ -544,16 +544,25 @@ namespace polysat {
bool rb = rv.val().get_bit(i);
if (rb == (pb && qb))
continue;
if (pb && qb && !rb)
return s.mk_clause(~andc, ~s.bit(p(), i), ~s.bit(q(), i), s.bit(r(), i), true);
else if (!pb && rb)
return s.mk_clause(~andc, s.bit(p(), i), ~s.bit(r(), i), true);
else if (!qb && rb)
return s.mk_clause(~andc, s.bit(q(), i), ~s.bit(r(), i), true);
if (pb && qb && !rb) {
verbose_stream() << "Conflict propagation " << pv << " (" << p() << ") & " << qv << " (" << q() << ") = " << bitwise_and(pv.val(), qv.val()) << " (" << r() << ")\n";
verbose_stream() << "1 & 1 = 0 bit " << i << "\n";
s.add_clause(s.mk_clause(~andc, ~s.bit(p(), i), ~s.bit(q(), i), s.bit(r(), i), true));
}
else if (!pb && rb) {
verbose_stream() << "Conflict propagation " << pv << " (" << p() << ") & " << qv << " (" << q() << ") = " << bitwise_and(pv.val(), qv.val()) << " (" << r() << ")\n";
verbose_stream() << "0 & ? = 1 bit " << i << "\n";
s.add_clause(s.mk_clause(~andc, s.bit(p(), i), ~s.bit(r(), i), true));
}
else if (!qb && rb) {
verbose_stream() << "Conflict propagation " << pv << " (" << p() << ") & " << qv << " (" << q() << ") = " << bitwise_and(pv.val(), qv.val()) << " (" << r() << ")\n";
verbose_stream() << "? & 0 = 1 bit " << i << "\n";
s.add_clause(s.mk_clause(~andc, s.bit(q(), i), ~s.bit(r(), i), true));
}
else
UNREACHABLE();
return {};
}
return {};
}
// Propagate r if p or q are 0
@ -562,8 +571,9 @@ namespace polysat {
if (qv.is_zero() && !rv.is_zero()) // rv not necessarily fully evaluated
return s.mk_clause(~andc, s.ule(r(), q()), true);
// p = a && q = b ==> r = a & b
if (pv.is_val() && qv.is_val() && !rv.is_val())
/*if (pv.is_val() && qv.is_val() && !rv.is_val()) {
return s.mk_clause(~andc, ~s.eq(p(), pv), ~s.eq(q(), qv), s.eq(r(), bitwise_and(pv.val(), qv.val())), true);
}*/
return {};
}
@ -652,8 +662,8 @@ namespace polysat {
// forward propagation: p assigned ==> r = pseudo_inverse(eval(p))
// TODO: (later) this should be propagated instead of adding a clause
if (pv.is_val() && !rv.is_val())
return s.mk_clause(~invc, ~s.eq(p(), pv), s.eq(r(), pv.val().pseudo_inverse(m.power_of_2())), true);
/*if (pv.is_val() && !rv.is_val())
return s.mk_clause(~invc, ~s.eq(p(), pv), s.eq(r(), pv.val().pseudo_inverse(m.power_of_2())), true);*/
if (!pv.is_val() || !rv.is_val())
return {};

View file

@ -443,7 +443,9 @@ namespace polysat {
#if ENABLE_LINEAR_SOLVER
m_linear_solver.push();
#endif
#if 0
m_fixed_bits.push();
#endif
}
void solver::pop_levels(unsigned num_levels) {
@ -458,8 +460,9 @@ namespace polysat {
#if ENABLE_LINEAR_SOLVER
m_linear_solver.pop(num_levels);
#endif
#if 0
m_fixed_bits.pop();
#endif
while (num_levels > 0) {
switch (m_trail.back()) {
case trail_instr_t::qhead_i: {

View file

@ -78,6 +78,7 @@ namespace polysat {
if (m_alloc.empty())
return alloc(entry);
auto* e = m_alloc.back();
e->src.reset();
e->side_cond.reset();
e->coeff = 1;
e->refined = nullptr;
@ -311,8 +312,9 @@ namespace polysat {
return true;
}
bool viable::refine_viable(pvar v, rational const& val) {
return refine_equal_lin(v, val) && refine_disequal_lin(v, val);
template<bool FORWARD>
bool viable::refine_viable(pvar v, rational const& val, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications) {
return refine_bits<FORWARD>(v, val, fixed, justifications) && refine_equal_lin(v, val) && refine_disequal_lin(v, val);
}
namespace {
@ -518,6 +520,35 @@ namespace {
}
}
template<bool FORWARD>
bool viable::refine_bits(pvar v, rational const& val, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications) {
pdd v_pdd = s.var(v);
// TODO: We might also extend simultaneously up and downwards if we want the actual interval (however, this might make use of more fixed bits and is weaker - worse - therefore)
entry* ne = alloc_entry();
rational new_val = extend_by_bits<FORWARD>(v_pdd, val, fixed, justifications, ne->src, ne->side_cond);
if (new_val == val) {
m_alloc.push_back(ne);
return true;
}
ne->coeff = 1;
if (FORWARD) {
LOG("refine-bits for v" << v << " [" << val << ", " << new_val << "[");
ne->interval = eval_interval::proper(v_pdd.manager().mk_val(val), val, v_pdd.manager().mk_val(new_val), new_val);
}
else {
rational upper_bound = val == v_pdd.manager().max_value() ? rational::zero() : val + 1;;
LOG("refine-bits for v" << v << " [" << new_val << ", " << upper_bound << "[");
ne->interval = eval_interval::proper(v_pdd.manager().mk_val(new_val), new_val, v_pdd.manager().mk_val(upper_bound), upper_bound);
}
SASSERT(ne->interval.currently_contains(val));
intersect(v, ne);
return false;
}
/**
* Traverse all interval constraints with coefficients to check whether current value 'val' for
* 'v' is feasible. If not, extract a (maximal) interval to block 'v' from being assigned val.
@ -545,7 +576,11 @@ namespace {
do {
rational coeff_val = mod(e->coeff * val, mod_value);
if (e->interval.currently_contains(coeff_val)) {
LOG("refine-equal-lin for v" << v << " in src: " << lit_pp(s, e->src));
IF_LOGGING(
verbose_stream() << "refine-equal-lin for v" << v << " in src: ";
for (const auto& src : e->src)
verbose_stream() << lit_pp(s, src) << "\n";
);
LOG("forbidden interval v" << v << " " << num_pp(s, v, val) << " " << num_pp(s, v, e->coeff, true) << " * " << e->interval);
if (mod(e->interval.hi_val() + 1, mod_value) == e->interval.lo_val()) {
@ -668,7 +703,11 @@ namespace {
m_diseq_lin[v] = m_diseq_lin[v]->next();
do {
LOG("refine-disequal-lin for src: " << e->src);
IF_LOGGING(
verbose_stream() << "refine-disequal-lin for v" << v << " in src: ";
for (const auto& src : e->src)
verbose_stream() << lit_pp(s, src) << "\n";
);
// We compute an interval if the concrete value 'val' violates the constraint:
// p*val + q > r*val + s if e->src.is_positive()
// p*val + q >= r*val + s if e->src.is_negative()
@ -684,7 +723,7 @@ namespace {
rational const b = mod(r * val + s_, mod_value);
rational const np = mod_value - p;
rational const nr = mod_value - r;
int const corr = e->src.is_negative() ? 1 : 0;
int const corr = e->src[0].is_negative() ? 1 : 0;
auto delta_l = [&](rational const& val) {
rational num = a - b + corr;
@ -717,7 +756,7 @@ namespace {
return std::min(max_value - val, dmax);
};
if (a > b || (e->src.is_negative() && a == b)) {
if (a > b || (e->src[0].is_negative() && a == b)) {
rational lo = val - delta_l(val);
rational hi = val + delta_u(val) + 1;
@ -742,72 +781,185 @@ namespace {
while (e != first);
return true;
}
bool viable::quick_bit_check(pvar v) {
#if 0
return true;
#endif
// Skips all values that are not feasible w.r.t. fixed bits
template<bool FORWARD>
rational viable::extend_by_bits(const pdd& var, const rational& bound, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications, vector<signed_constraint>& src, vector<signed_constraint>& side_cond) const {
unsigned k = var.power_of_2();
if (fixed.empty())
return bound;
SASSERT(k == fixed.size());
auto add_justification = [&](unsigned i) {
auto& to_add = justifications[i];
SASSERT(!to_add.empty());
for (auto& add : to_add) {
// TODO: Check for duplicates; maybe we add the same src/side_cond over and over again
for (auto& sc : add->side_cond)
side_cond.push_back(sc);
for (auto& c : add->src)
src.push_back(c);
}
};
unsigned firstFail;
for (firstFail = k; firstFail > 0; firstFail--) {
if (fixed[firstFail - 1] != l_undef) {
lbool current = bound.get_bit(firstFail - 1) ? l_true : l_false;
if (current != fixed[firstFail - 1])
break;
}
}
if (firstFail == 0)
return bound; // the value is feasible according to fixed bits
svector<lbool> new_bound(fixed.size());
for (unsigned i = 0; i < firstFail; i++) {
if (fixed[i] != l_undef) {
SASSERT(fixed[i] == l_true || fixed[i] == l_false);
new_bound[i] = fixed[i];
if (FORWARD != (fixed[i] == l_false))
add_justification(i); // Minimize number of responsible fixed bits; we only add those justifications we need for sure
}
else
new_bound[i] = FORWARD ? l_false : l_true;
}
bool carry = fixed[firstFail - 1] == (FORWARD ? l_false : l_true);
for (unsigned i = firstFail; i < new_bound.size(); i++) {
if (fixed[i] == l_undef) {
lbool current = bound.get_bit(i) ? l_true : l_false;
if (carry) {
if (FORWARD) {
if (current == l_false) {
new_bound[i] = l_true;
carry = false;
}
else
new_bound[i] = l_false;
}
else {
if (current == l_true) {
new_bound[i] = l_false;
carry = false;
}
else
new_bound[i] = l_true;
}
}
else
new_bound[i] = current;
}
else {
new_bound[i] = fixed[i];
if (carry)
add_justification(i); // Again, we need this justification; if carry is false we don't need it
}
}
if (carry) {
// We covered everything
/*if (FORWARD)
return rational::power_of_two(k);
else*/
return rational::zero();
}
// TODO: Directly convert new_bound in rational?
rational ret = rational::zero();
for (unsigned i = new_bound.size(); i > 0; i--) {
ret *= 2;
SASSERT(new_bound[i - 1] != l_undef);
ret += new_bound[i - 1] == l_true ? 1 : 0;
}
if (!FORWARD)
return ret + 1;
return ret;
}
// returns true iff no conflict was encountered
bool viable::collect_bit_information(pvar v, bool add_conflict, svector<lbool>& fixed, vector<ptr_vector<entry>>& justifications) {
pdd p = s.var(v);
// maybe pass them as arguments rather than having them as fields...
fixed.clear();
justifications.clear();
fixed.resize(p.power_of_2(), l_undef);
justifications.resize(p.power_of_2(), ptr_vector<entry>());
auto* e = m_equal_lin[v];
auto* first = e;
if (!e)
return true;
pdd p = s.var(v);
clause_builder builder(s, "bit check");
svector<lbool> fixed(p.power_of_2(), l_undef);
vector<ptr_vector<entry>> justifications(p.power_of_2(), ptr_vector<entry>());
vector<std::pair<entry*, trailing_bits>> postponed;
auto add_entry = [&builder](entry* e) {
for (const auto& sc : e->side_cond) {
builder.insert_eval(~sc);
LOG("Side cond: " << sc);
}
builder.insert_eval(~e->src);
LOG("Adding to core: " << e->src);
SASSERT(e->src.size() == 1);
for (const auto& src : e->src) {
builder.insert_eval(~src);
LOG("Adding to core: " << e->src);
}
};
auto add_entry_list = [add_entry](const ptr_vector<entry>& list) {
for (const auto& e : list)
for (const auto& e : list)
add_entry(e);
};
unsigned largest_mask = 0;
do {
if (e->src.size() != 1) {
e = e->next();
continue;
}
signed_constraint& src = e->src[0];
single_bit bit;
trailing_bits mask;
if (e->src->is_ule() &&
simplify_clause::get_bit(s.subst(e->src->to_ule().lhs()), s.subst(e->src->to_ule().rhs()), p, bit, e->src.is_positive()) && p.is_var()) {
if (src->is_ule() &&
simplify_clause::get_bit(s.subst(src->to_ule().lhs()), s.subst(src->to_ule().rhs()), p, bit, src.is_positive()) && p.is_var()) {
lbool prev = fixed[bit.position];
fixed[bit.position] = bit.positive ? l_true : l_false;
//verbose_stream() << "Setting bit " << bit.position << " to " << bit.positive << " because of " << e->src << "\n";
if (prev != l_undef && fixed[bit.position] != prev) {
LOG("Bit conflicting " << e->src << " with " << justifications[bit.position][0]->src);
add_entry_list(justifications[bit.position]);
add_entry(e);
s.set_conflict(*builder.build());
verbose_stream() << "Bit conflicting " << e->src << " with " << justifications[bit.position][0]->src << "\n";
if (add_conflict) {
add_entry_list(justifications[bit.position]);
add_entry(e);
s.set_conflict(*builder.build());
}
return false;
}
// just override; we prefer bit constraints over parity as those are easier for subsumption to remove
// verbose_stream() << "Adding bit constraint: " << e->src[0] << " (" << bit.position << ")\n";
justifications[bit.position].clear();
justifications[bit.position].push_back(e);
}
else if ((e->src->is_eq() || e->src.is_diseq()) &&
simplify_clause::get_trailing_mask(s.subst(e->src->to_ule().lhs()), s.subst(e->src->to_ule().rhs()), p, mask, e->src.is_positive()) && p.is_var()) {
else if ((src->is_eq() || src.is_diseq()) &&
simplify_clause::get_trailing_mask(s.subst(src->to_ule().lhs()), s.subst(src->to_ule().rhs()), p, mask, src.is_positive()) && p.is_var()) {
if (e->src.is_positive()) {
if (src.is_positive()) {
for (unsigned i = 0; i < mask.length; i++) {
lbool prev = fixed[i];
fixed[i] = mask.bits.get_bit(i) ? l_true : l_false;
//verbose_stream() << "Setting bit " << i << " to " << mask.bits.get_bit(i) << " because of parity " << e->src << "\n";
if (prev != l_undef) {
if (fixed[i] != prev) {
LOG("Positive parity conflicting " << e->src << " with " << justifications[i][0]->src);
add_entry_list(justifications[i]);
add_entry(e);
s.set_conflict(*builder.build());
verbose_stream() << "Positive parity conflicting " << e->src << " with " << justifications[i][0]->src << "\n";
if (add_conflict) {
add_entry_list(justifications[i]);
add_entry(e);
s.set_conflict(*builder.build());
}
return false;
}
else {
@ -816,12 +968,14 @@ namespace {
largest_mask = mask.length;
justifications[i].clear();
justifications[i].push_back(e);
// verbose_stream() << "Adding parity constraint: " << e->src[0] << " (" << i << ")\n";
}
}
}
else {
SASSERT(justifications[i].empty());
justifications[i].push_back(e);
// verbose_stream() << "Adding parity constraint: " << e->src[0] << " (" << i << ")\n";
}
}
}
@ -859,11 +1013,13 @@ namespace {
if (i == neg.second.length) {
if (indet == 0) {
// Already false
LOG("Found conflict with constraint " << neg.first->src);
for (unsigned k = 0; k < neg.second.length; k++)
add_entry_list(justifications[k]);
add_entry(neg.first);
s.set_conflict(*builder.build());
verbose_stream() << "Found conflict with constraint " << neg.first->src << "\n";
if (add_conflict) {
for (unsigned k = 0; k < neg.second.length; k++)
add_entry_list(justifications[k]);
add_entry(neg.first);
s.set_conflict(*builder.build());
}
return false;
}
else if (indet == 1) {
@ -891,10 +1047,17 @@ namespace {
}
bool viable::has_viable(pvar v) {
svector<lbool> fixed;
vector<ptr_vector<entry>> justifications;
if (!collect_bit_information(v, false, fixed, justifications))
return false;
refined:
auto* e = m_units[v];
#define CHECK_RETURN(val) { if (refine_viable(v, val)) return true; else goto refined; }
#define CHECK_RETURN(val) { if (refine_viable<true>(v, val, fixed, justifications)) return true; else goto refined; }
if (!e)
CHECK_RETURN(rational::zero());
@ -929,9 +1092,15 @@ namespace {
}
bool viable::is_viable(pvar v, rational const& val) {
svector<lbool> fixed;
vector<ptr_vector<entry>> justifications;
if (!collect_bit_information(v, false, fixed, justifications))
return false;
auto* e = m_units[v];
if (!e)
return refine_viable(v, val);
return refine_viable<true>(v, val, fixed, justifications);
entry* first = e;
entry* last = first->prev();
if (last->interval.currently_contains(val))
@ -940,9 +1109,9 @@ namespace {
if (e->interval.currently_contains(val))
return false;
if (val < e->interval.lo_val())
return refine_viable(v, val);
return refine_viable<true>(v, val, fixed, justifications);
}
return refine_viable(v, val);
return refine_viable<true>(v, val, fixed, justifications);
}
find_t viable::find_viable(pvar v, rational& lo) {
@ -985,12 +1154,14 @@ namespace {
auto const& hi = e->interval.hi();
if (lo.is_val() && hi.is_val()) {
if (out_c.empty() && lo.val() > hi.val()) {
out_c.push_back(e->src);
for (const auto& src : e->src)
out_c.push_back(src);
out_hi = lo.val() - 1;
found = true;
}
else if (!out_c.empty() && lo.val() <= out_hi && out_hi < hi.val()) {
out_c.push_back(e->src);
for (const auto& src : e->src)
out_c.push_back(src);
out_hi = lo.val() - 1;
found = true;
}
@ -1019,12 +1190,14 @@ namespace {
auto const& hi = e->interval.hi();
if (lo.is_val() && hi.is_val()) {
if (out_c.empty() && hi.val() != 0 && (lo.val() == 0 || lo.val() > hi.val())) {
out_c.push_back(e->src);
for (const auto& src : e->src)
out_c.push_back(src);
out_lo = hi.val();
found = true;
}
else if (!out_c.empty() && lo.val() <= out_lo && out_lo < hi.val()) {
out_c.push_back(e->src);
for (const auto& src : e->src)
out_c.push_back(src);
out_lo = hi.val();
found = true;
}
@ -1048,14 +1221,17 @@ namespace {
if (!e)
return false;
bool found = false;
do {
if (e->src == c)
found = e->src.contains(c);
if (found)
break;
e = e->next();
}
while (e != first);
if (e->src != c)
if (!found)
return false;
entry const* e0 = e;
// display_one(verbose_stream() << "selected e0 = ", v, e0) << "\n";
@ -1101,7 +1277,8 @@ namespace {
out_c.push_back(sc);
}
// verbose_stream() << "E: " << lit_pp(s, e->src) << "\n";
out_c.push_back(e->src);
for (const auto& src : e->src)
out_c.push_back(src);
}
e = n;
}
@ -1117,8 +1294,13 @@ namespace {
template <query_t mode>
lbool viable::query(pvar v, typename query_result<mode>::result_t& result) {
if (!quick_bit_check(v))
return l_false;
svector<lbool> fixed;
vector<ptr_vector<entry>> justifications;
if (!collect_bit_information(v, true, fixed, justifications))
return l_false; // conflict already added
// max number of interval refinements before falling back to the univariate solver
unsigned const refinement_budget = 1000;
unsigned refinements = refinement_budget;
@ -1127,11 +1309,11 @@ namespace {
lbool res = l_undef;
if constexpr (mode == query_t::find_viable)
res = query_find(v, result.first, result.second);
res = query_find(v, result.first, result.second, fixed, justifications);
else if constexpr (mode == query_t::min_viable)
res = query_min(v, result);
res = query_min(v, result, fixed, justifications);
else if constexpr (mode == query_t::max_viable)
res = query_max(v, result);
res = query_max(v, result, fixed, justifications);
else if constexpr (mode == query_t::has_viable) {
NOT_IMPLEMENTED_YET();
}
@ -1147,7 +1329,7 @@ namespace {
return query_fallback<mode>(v, result);
}
lbool viable::query_find(pvar v, rational& lo, rational& hi) {
lbool viable::query_find(pvar v, rational& lo, rational& hi, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications) {
auto const& max_value = s.var2pdd(v).max_value();
lbool const refined = l_undef;
@ -1158,9 +1340,9 @@ namespace {
hi = max_value;
auto* e = m_units[v];
if (!e && !refine_viable(v, lo))
if (!e && !refine_viable<true>(v, lo, fixed, justifications))
return refined;
if (!e && !refine_viable(v, hi))
if (!e && !refine_viable<false>(v, hi, fixed, justifications))
return refined;
if (!e)
return l_true;
@ -1177,9 +1359,9 @@ namespace {
if (last->interval.lo_val() < last->interval.hi_val() &&
last->interval.hi_val() < max_value) {
lo = last->interval.hi_val();
if (!refine_viable(v, lo))
if (!refine_viable<true>(v, lo, fixed, justifications))
return refined;
if (!refine_viable(v, max_value))
if (!refine_viable<false>(v, max_value, fixed, justifications))
return refined;
return l_true;
}
@ -1211,18 +1393,18 @@ namespace {
}
while (e != last);
if (!refine_viable(v, lo))
if (!refine_viable<true>(v, lo, fixed, justifications))
return refined;
if (!refine_viable(v, hi))
if (!refine_viable<false>(v, hi, fixed, justifications))
return refined;
return l_true;
}
lbool viable::query_min(pvar v, rational& lo) {
lbool viable::query_min(pvar v, rational& lo, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications) {
// TODO: should be able to deal with UNSAT case; since also min_viable has to deal with it due to fallback solver
lo = 0;
entry* e = m_units[v];
if (!e && !refine_viable(v, lo))
if (!e && !refine_viable<true>(v, lo, fixed, justifications))
return l_undef;
if (!e)
return l_true;
@ -1237,17 +1419,17 @@ namespace {
e = e->next();
}
while (e != first);
if (!refine_viable(v, lo))
if (!refine_viable<true>(v, lo, fixed, justifications))
return l_undef;
SASSERT(is_viable(v, lo));
return l_true;
}
lbool viable::query_max(pvar v, rational& hi) {
lbool viable::query_max(pvar v, rational& hi, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications) {
// TODO: should be able to deal with UNSAT case; since also max_viable has to deal with it due to fallback solver
hi = s.var2pdd(v).max_value();
auto* e = m_units[v];
if (!e && !refine_viable(v, hi))
if (!e && !refine_viable<false>(v, hi, fixed, justifications))
return l_undef;
if (!e)
return l_true;
@ -1260,7 +1442,7 @@ namespace {
e = e->prev();
}
while (e != last);
if (!refine_viable(v, hi))
if (!refine_viable<false>(v, hi, fixed, justifications))
return l_undef;
SASSERT(is_viable(v, hi));
return l_true;
@ -1282,13 +1464,15 @@ namespace {
entry const* origin = e;
while (origin->refined)
origin = origin->refined;
signed_constraint const c = origin->src;
sat::literal const lit = c.blit();
if (!added.contains(lit)) {
added.insert(lit);
LOG("Adding " << lit_pp(s, lit));
IF_VERBOSE(10, verbose_stream() << ";; " << lit_pp(s, lit) << "\n");
c.add_to_univariate_solver(v, s, *us, lit.to_uint());
for (const auto& src : origin->src) {
sat::literal const lit = src.blit();
if (!added.contains(lit)) {
added.insert(lit);
LOG("Adding " << lit_pp(s, lit));
IF_VERBOSE(10, verbose_stream() << ";; " << lit_pp(s, lit) << "\n");
verbose_stream() << ";; " << lit_pp(s, lit) << "\n";
src.add_to_univariate_solver(v, s, *us, lit.to_uint());
}
}
e = e->next();
}
@ -1391,9 +1575,23 @@ namespace {
entry const* first = e;
SASSERT(e);
// If there is a full interval, all others would have been removed
SASSERT(!e->interval.is_full() || e->next() == e);
SASSERT(e->interval.is_full() || all_of(*e, [](entry const& f) { return !f.interval.is_full(); }));
clause_builder lemma(s);
if (first->interval.is_full()) {
SASSERT(first->next() == first);
for (auto sc : first->side_cond)
lemma.insert_eval(~sc);
for (const auto& src : first->src) {
lemma.insert(~src);
core.insert(src);
core.insert_vars(src);
}
core.add_lemma("viable", lemma.build());
core.logger().log(inf_fi(*this, v));
return true;
}
SASSERT(all_of(*first, [](entry const& f) { return !f.interval.is_full(); }));
do {
// Build constraint: upper bound of each interval is not contained in the next interval,
// using the equivalence: t \in [l;h[ <=> t-l < h-l
@ -1439,15 +1637,16 @@ namespace {
// verbose_stream() << e->interval << " " << e->side_cond << " " << e->src << ";\n";
if (!e->interval.is_full()) {
signed_constraint c = s.m_constraints.elem(e->interval.hi(), n->interval.symbolic());
lemma.insert_try_eval(~c);
}
signed_constraint c = s.m_constraints.elem(e->interval.hi(), n->interval.symbolic());
lemma.insert_try_eval(~c);
for (auto sc : e->side_cond)
lemma.insert_eval(~sc);
lemma.insert(~e->src);
core.insert(e->src);
core.insert_vars(e->src);
for (const auto& src : e->src) {
lemma.insert(~src);
core.insert(src);
core.insert_vars(src);
}
e = n;
}
while (e != first);
@ -1471,7 +1670,12 @@ namespace {
return;
entry* first = e;
do {
LOG("v" << v << ": " << e->interval << " " << e->side_cond << " " << e->src);
IF_LOGGING(
verbose_stream() << "v" << v << ": " << e->interval << " " << e->side_cond << " ";
for (const auto& src : e->src)
verbose_stream() << src << " ";
verbose_stream() << "\n";
);
e = e->next();
}
while (e != first);
@ -1495,7 +1699,7 @@ namespace {
rational const& s_ = e->interval.hi().val();
out << "[ ";
out << val_pp(m, p, true) << "*v" << v << " + " << val_pp(m, q_);
out << (e->src.is_positive() ? " > " : " >= ");
out << (e->src[0].is_positive() ? " > " : " >= ");
out << val_pp(m, r, true) << "*v" << v << " + " << val_pp(m, s_);
out << " ] ";
}
@ -1503,7 +1707,9 @@ namespace {
out << e->coeff << " * v" << v << " " << e->interval << " ";
else
out << e->interval << " ";
out << e->side_cond << " " << e->src << "; ";
out << e->side_cond << " ";
for (const auto& src : e->src)
out << src << "; ";
return out;
}

View file

@ -94,13 +94,20 @@ namespace polysat {
bool intersect(pvar v, entry* e);
bool refine_viable(pvar v, rational const& val);
template<bool FORWARD>
bool refine_viable(pvar v, rational const& val, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications);
template<bool FORWARD>
bool refine_bits(pvar v, rational const& val, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications);
bool refine_equal_lin(pvar v, rational const& val);
bool refine_disequal_lin(pvar v, rational const& val);
bool quick_bit_check(pvar v);
template<bool FORWARD>
rational extend_by_bits(const pdd& var, const rational& bounds, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications, vector<signed_constraint>& src, vector<signed_constraint>& side_cond) const;
bool collect_bit_information(pvar v, bool add_conflict, svector<lbool>& fixed, vector<ptr_vector<entry>>& justifications);
std::ostream& display_one(std::ostream& out, pvar v, entry const* e) const;
std::ostream& display_all(std::ostream& out, pvar v, entry const* e, char const* delimiter = "") const;
@ -113,9 +120,9 @@ namespace polysat {
* Interval-based queries
* @return l_true on success, l_false on conflict, l_undef on refinement
*/
lbool query_min(pvar v, rational& out_lo);
lbool query_max(pvar v, rational& out_hi);
lbool query_find(pvar v, rational& out_lo, rational& out_hi);
lbool query_min(pvar v, rational& out_lo, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications);
lbool query_max(pvar v, rational& out_hi, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications);
lbool query_find(pvar v, rational& out_lo, rational& out_hi, const svector<lbool>& fixed, const vector<ptr_vector<entry>>& justifications);
/**
* Bitblasting-based queries.
@ -261,7 +268,7 @@ namespace polysat {
}
signed_constraint& operator*() {
return idx < curr->side_cond.size() ? curr->side_cond[idx] : curr->src;
return idx < curr->side_cond.size() ? curr->side_cond[idx] : curr->src[idx - curr->side_cond.size()];
}
bool operator==(iterator const& other) const {

View file

@ -678,8 +678,9 @@ namespace polysat {
s.add_clause(c2, false);
s.m_viable.intersect(x.var(), c1);
s.m_viable.intersect(x.var(), c2);
VERIFY(!s.m_viable.quick_bit_check(x.var()));
svector<lbool> fixed;
vector<ptr_vector<viable::entry>> justifications;
VERIFY(!s.m_viable.collect_bit_information(x.var(), false, fixed, justifications));
}
// parity(x) >= 3 and bit_1(x)
@ -692,8 +693,9 @@ namespace polysat {
s.add_clause(c2, false);
s.m_viable.intersect(x.var(), c1);
s.m_viable.intersect(x.var(), c2);
VERIFY(!s.m_viable.quick_bit_check(x.var()));
svector<lbool> fixed;
vector<ptr_vector<viable::entry>> justifications;
VERIFY(!s.m_viable.collect_bit_information(x.var(), false, fixed, justifications));
}
// 8 * x + 3 == 0 or 8 * x + 5 == 0 is unsat
@ -1490,9 +1492,9 @@ namespace polysat {
}
// x*y <= b & a <= x & !Omega(x*y) => a*y <= b
static void test_ineq_axiom4(unsigned bw = 32) {
static void test_ineq_axiom4(unsigned bw = 32, unsigned i = 0) {
auto const bound = rational::power_of_two(bw/2);
for (unsigned i = 0; i < 24; ++i) {
for (; i < 24; ++i) {
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
@ -1950,7 +1952,7 @@ static void STD_CALL polysat_on_ctrl_c(int) {
void tst_polysat() {
using namespace polysat;
test_polysat::test_ineq_axiom4(32, 7);
#if 0 // Enable this block to run a single unit test with detailed output.
collect_test_records = false;
test_max_conflicts = 50;