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

Merge remote-tracking branch 'origin/polysat' into polysat

This commit is contained in:
Jakob Rath 2023-02-20 17:37:44 +01:00
commit 8347c043e1
17 changed files with 1106 additions and 136 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

@ -513,4 +513,49 @@ namespace polysat {
return m.mk_val(p.val().pseudo_inverse(m.power_of_2()));
return mk_op_term(op_constraint::code::inv_op, p, m.zero());
}
pdd constraint_manager::udiv(pdd const& p, pdd const& q) {
return div_rem_op_constraint(p, q).first;
}
pdd constraint_manager::urem(pdd const& p, pdd const& q) {
return div_rem_op_constraint(p, q).second;
}
/** unsigned quotient/remainder */
std::pair<pdd, pdd> constraint_manager::div_rem_op_constraint(pdd const& a, pdd const& b) {
auto& m = a.manager();
unsigned sz = m.power_of_2();
if (b.is_zero()) {
// By SMT-LIB specification, b = 0 ==> q = -1, r = a.
return {m.mk_val(m.max_value()), a};
}
if (b.is_one()) {
return {a, m.zero()};
}
if (a.is_val() && b.is_val()) {
rational const av = a.val();
rational const bv = b.val();
SASSERT(!bv.is_zero());
rational rv;
rational qv = machine_div_rem(av, bv, rv);
pdd q = m.mk_val(qv);
pdd r = m.mk_val(rv);
SASSERT_EQ(a, b * q + r);
SASSERT(b.val()*q.val() + r.val() <= m.max_value());
SASSERT(r.val() <= (b*q+r).val());
SASSERT(r.val() < b.val());
return {std::move(q), std::move(r)};
}
pdd quot = mk_op_term(op_constraint::code::udiv_op, a, b);
pdd rem = mk_op_term(op_constraint::code::urem_op, a, b);
op_constraint* op1 = (op_constraint*)mk_op_constraint(op_constraint::code::udiv_op, a, b, quot).get();
op_constraint* op2 = (op_constraint*)mk_op_constraint(op_constraint::code::urem_op, a, b, quot).get();
op1->m_linked = op2;
op2->m_linked = op1;
return {std::move(quot), std::move(rem)};
}
}

View file

@ -87,6 +87,8 @@ namespace polysat {
signed_constraint mk_op_constraint(op_constraint::code op, pdd const& p, pdd const& q, pdd const& r);
pdd mk_op_term(op_constraint::code op, pdd const& p, pdd const& q);
std::pair<pdd, pdd> div_rem_op_constraint(const pdd& p, const pdd& q);
public:
constraint_manager(solver& s);
~constraint_manager();
@ -132,6 +134,9 @@ namespace polysat {
pdd bnor(pdd const& p, pdd const& q);
pdd bxnor(pdd const& p, pdd const& q);
pdd pseudo_inv(pdd const& p);
pdd udiv(pdd const& a, pdd const& b);
pdd urem(pdd const& a, pdd const& b);
constraint* const* begin() const { return m_constraints.data(); }
constraint* const* end() const { return m_constraints.data() + m_constraints.size(); }

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

@ -65,6 +65,10 @@ namespace polysat {
return eval_and(p, q, r);
case code::inv_op:
return eval_inv(p, r);
case code::udiv_op:
return eval_udiv(p, q, r);
case code::urem_op:
return eval_urem(p, q, r);
default:
return l_undef;
}
@ -90,6 +94,10 @@ namespace polysat {
return out << "&";
case op_constraint::code::inv_op:
return out << "inv";
case op_constraint::code::udiv_op:
return out << "/";
case op_constraint::code::urem_op:
return out << "%";
default:
UNREACHABLE();
return out;
@ -142,6 +150,10 @@ namespace polysat {
return propagate_bits_shl(s, is_positive);
case code::and_op:
return propagate_bits_and(s, is_positive);
case code::inv_op:
case code::udiv_op:
case code::urem_op:
return false;
default:
NOT_IMPLEMENTED_YET();
return false;
@ -180,15 +192,12 @@ namespace polysat {
void op_constraint::activate(solver& s) {
switch (m_op) {
case code::lshr_op:
break;
case code::shl_op:
break;
case code::and_op:
// handle masking of high order bits
activate_and(s);
break;
case code::inv_op:
case code::udiv_op: // division & remainder axioms (as they always occur as pairs; we do this only for divisions)
activate_udiv(s);
break;
default:
break;
@ -266,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())
@ -276,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 {};
}
@ -356,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())
@ -366,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 {};
}
@ -543,8 +552,8 @@ namespace polysat {
return 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
@ -553,8 +562,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 {};
}
@ -643,8 +653,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 {};
@ -699,6 +709,131 @@ namespace polysat {
return to_lbool(p.val().pseudo_inverse(p.power_of_2()) == r.val());
}
void op_constraint::activate_udiv(solver& s) {
// signed_constraint const udivc(this, true); Do we really need this premiss? We anyway assert these constraints as unit clauses
pdd const& quot = r();
pdd const& rem = m_linked->r();
// Axioms for quotient/remainder:
// a = b*q + r
// multiplication does not overflow in b*q
// addition does not overflow in (b*q) + r; for now expressed as: r <= bq+r
// b ≠ 0 ==> r < b
// b = 0 ==> q = -1
// TODO: when a,b become evaluable, can we actually propagate q,r? doesn't seem like it.
// Maybe we need something like an op_constraint for better propagation.
s.add_clause(s.eq(q() * quot + rem - p()), false);
s.add_clause(~s.umul_ovfl(q(), quot), false);
// r <= b*q+r
// { apply equivalence: p <= q <=> q-p <= -p-1 }
// b*q <= -r-1
s.add_clause(s.ule(q() * quot, -rem - 1), false);
auto c_eq = s.eq(q());
s.add_clause(c_eq, s.ult(rem, q()), false);
s.add_clause(~c_eq, s.eq(quot + 1), false);
}
/**
* Produce lemmas for constraint: r == p / q
* q = 0 ==> r = max_value
* p = 0 ==> r = 0 || r = max_value
* q = 1 ==> r = p
*/
clause_ref op_constraint::lemma_udiv(solver& s, assignment const& a) {
auto& m = p().manager();
auto pv = a.apply_to(p());
auto qv = a.apply_to(q());
auto rv = a.apply_to(r());
if (eval_udiv(pv, qv, rv) == l_true)
return {};
signed_constraint const udivc(this, true);
if (qv.is_zero() && !rv.is_val())
return s.mk_clause(~udivc, ~s.eq(q()), s.eq(r(), r().manager().max_value()), true);
if (pv.is_zero() && !rv.is_val())
return s.mk_clause(~udivc, ~s.eq(p()), s.eq(r()), s.eq(r(), r().manager().max_value()), true);
if (qv.is_one())
return s.mk_clause(~udivc, ~s.eq(q(), 1), s.eq(r(), p()), true);
if (pv.is_val() && qv.is_val() && !rv.is_val()) {
SASSERT(!qv.is_zero());
// TODO: We could actually propagate an interval. Instead of p = 9 & q = 4 => r = 2 we could do p >= 8 && p < 12 && q = 4 => r = 2
return s.mk_clause(~udivc, ~s.eq(p(), pv.val()), ~s.eq(q(), qv.val()), s.eq(r(), div(pv.val(), qv.val())), true);
}
return {};
}
/** Evaluate constraint: r == p / q */
lbool op_constraint::eval_udiv(pdd const& p, pdd const& q, pdd const& r) {
if (q.is_zero() && r.is_val()) {
return r.val() == r.manager().max_value() ? l_true : l_false;
}
if (q.is_one()) {
if (r == p)
return l_true;
}
if (!p.is_val() || !q.is_val() || !r.is_val())
return l_undef;
return r.val() == div(p.val(), q.val()) ? l_true : l_false;
}
/**
* Produce lemmas for constraint: r == p % q
* p = 0 ==> r = 0
* q = 1 ==> r = 0
* q = 0 ==> r = p
*/
clause_ref op_constraint::lemma_urem(solver& s, assignment const& a) {
auto& m = p().manager();
auto pv = a.apply_to(p());
auto qv = a.apply_to(q());
auto rv = a.apply_to(r());
if (eval_urem(pv, qv, rv) == l_true)
return {};
signed_constraint const urem(this, true);
if (pv.is_zero() && !rv.is_val())
return s.mk_clause(~urem, ~s.eq(p()), s.eq(r()), true);
if (qv.is_one() && !rv.is_val())
return s.mk_clause(~urem, ~s.eq(q(), 1), s.eq(r()), true);
if (qv.is_zero())
return s.mk_clause(~urem, ~s.eq(q()), s.eq(r(), p()), true);
if (pv.is_val() && qv.is_val() && !rv.is_val()) {
SASSERT(!qv.is_zero());
return s.mk_clause(~urem, ~s.eq(p(), pv.val()), ~s.eq(q(), qv.val()), s.eq(r(), mod(pv.val(), qv.val())), true);
}
return {};
}
/** Evaluate constraint: r == p % q */
lbool op_constraint::eval_urem(pdd const& p, pdd const& q, pdd const& r) {
if (q.is_one() && r.is_val()) {
return r.val().is_zero() ? l_true : l_false;
}
if (q.is_zero()) {
if (r == p)
return l_true;
}
if (!p.is_val() || !q.is_val() || !r.is_val())
return l_undef;
return r.val() == mod(p.val(), q.val()) ? l_true : l_false; // mod == rem as we know hat q > 0
}
void op_constraint::add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
pdd pv = s.subst(p());
@ -723,6 +858,12 @@ namespace polysat {
case code::inv_op:
us.add_inv(pv.get_univariate_coefficients(), rv.get_univariate_coefficients(), !is_positive, dep);
break;
case code::udiv_op:
us.add_udiv(pv.get_univariate_coefficients(), qv.get_univariate_coefficients(), rv.get_univariate_coefficients(), !is_positive, dep);
break;
case code::urem_op:
us.add_urem(pv.get_univariate_coefficients(), qv.get_univariate_coefficients(), rv.get_univariate_coefficients(), !is_positive, dep);
break;
default:
NOT_IMPLEMENTED_YET();
break;

View file

@ -38,7 +38,11 @@ namespace polysat {
/// r is the smallest multiplicative pseudo-inverse of p;
/// by definition we set r == 0 when p == 0.
/// Note that in general, there are 2^parity(p) many pseudo-inverses of p.
inv_op
inv_op,
// r is the quotient of dividing p by q
udiv_op,
// r is the remainder of dividing p by q
urem_op,
};
protected:
friend class constraint_manager;
@ -47,6 +51,8 @@ namespace polysat {
pdd m_p; // operand1
pdd m_q; // operand2
pdd m_r; // result
op_constraint* m_linked; // for linking remainder/quotient
op_constraint(code c, pdd const& p, pdd const& q, pdd const& r);
lbool eval(pdd const& p, pdd const& q, pdd const& r) const;
@ -66,12 +72,19 @@ namespace polysat {
clause_ref lemma_inv(solver& s, assignment const& a);
static lbool eval_inv(pdd const& p, pdd const& r);
clause_ref lemma_udiv(solver& s, assignment const& a);
static lbool eval_udiv(pdd const& p, pdd const& q, pdd const& r);
clause_ref lemma_urem(solver& s, assignment const& a);
static lbool eval_urem(pdd const& p, pdd const& q, pdd const& r);
std::ostream& display(std::ostream& out, char const* eq) const;
void activate(solver& s);
void activate_and(solver& s);
void activate_udiv(solver& s);
public:
~op_constraint() override {}

View file

@ -33,7 +33,7 @@ namespace polysat {
saturation::saturation(solver& s) : s(s), m_lemma(s), m_parity_tracker(s) {}
void saturation::log_lemma(pvar v, conflict& core) {
IF_VERBOSE(1, {
IF_VERBOSE(2, {
auto const& cl = core.lemmas().back();
verbose_stream() << m_rule << " v" << v << " ";
for (auto lit : *cl)
@ -1987,6 +1987,17 @@ namespace polysat {
<< s.var(q1) << "\n");
};
// monotonicity0 lemma should be asserted eagerly.
auto monotonicity0 = [&](auto& x1, auto& x1_val, auto& y1, auto& y1_val, auto& q1, auto& q1_val) {
if (q1_val * y1_val <= x1_val)
return;
// q1*y1 + r1 = x1, q1*y1 <= -r1 - 1, q1*y1 <= x1
propagated = true;
set_rule("[x1, y1] (x1 / y1) * y1 <= x1");
m_lemma.reset();
propagate(q1, core, s.ule(s.var(q1) * y1, x1));
};
auto monotonicity1 = [&](auto& x1, auto& x1_val, auto& y1, auto& y1_val, auto& q1, auto& q1_val,
auto& x2, auto& x2_val, auto& y2, auto& y2_val, auto& q2, auto& q2_val) {
if (!(x1_val <= x2_val && y1_val >= y2_val && q1_val > q2_val))
@ -2023,6 +2034,10 @@ namespace polysat {
if (q1_val == expected1)
continue;
// force that q1 * y1 <= x1 if it isn't the case.
// monotonicity0(x1, x1_val, y1, y1_val, q1, q1_val);
for (auto const& [x2, y2, q2, r2] : s.m_constraints.div_constraints()) {
if (x1 == x2 && y1 == y2)

View file

@ -86,6 +86,10 @@ namespace polysat {
#endif
if (try_equal_body_subsumptions(cl))
simplified = true;
#if 0
if (try_bit_subsumptions(cl))
simplified = true;
#endif
return simplified;
}
@ -344,8 +348,257 @@ namespace polysat {
return true;
}
// decomposes into a plain constant and a part containing variables. e.g., 2*x*y + 3*z - 2 gets { 2*x*y + 3*z, -2 }
static std::pair<pdd, pdd> decouple_constant(const pdd& p) {
for (const auto& m : p) {
if (m.vars.empty())
return { p - m.coeff, p.manager().mk_val(m.coeff) };
}
return { p, p.manager().mk_val(0) };
}
// 2^(k - d) * x = m * 2^(k - d)
bool simplify_clause::get_trailing_mask(pdd lhs, pdd rhs, pdd& p, trailing_bits& mask, bool pos) {
auto lhs_decomp = decouple_constant(lhs);
auto rhs_decomp = decouple_constant(rhs);
lhs = lhs_decomp.first - rhs_decomp.first;
rhs = rhs_decomp.second - lhs_decomp.second;
SASSERT(rhs.is_val());
unsigned k = lhs.manager().power_of_2();
unsigned d = lhs.max_pow2_divisor();
unsigned span = k - d;
if (span == 0 || lhs.is_val())
return false;
p = lhs.div(rational::power_of_two(d));
rational rhs_val = rhs.val();
mask.bits = rhs_val / rational::power_of_two(d);
if (!mask.bits.is_int())
return false;
auto it = p.begin();
auto first = *it;
it++;
if (it == p.end()) {
// if the lhs contains only one monomial it is of the form: odd * x = mask. We can multiply by the inverse to get the mask for x
SASSERT(first.coeff.is_odd());
rational inv;
VERIFY(first.coeff.mult_inverse(lhs.power_of_2(), inv));
p *= inv;
mask.bits = mod2k(mask.bits * inv, span);
}
mask.length = span;
mask.positive = pos;
return true;
}
// 2^(k - 1) <= 2^(k - i - 1) * x (original definition) // TODO: Have this as well
// 2^(k - i - 1) * x + 2^(k - 1) <= 2^(k - 1) - 1 (currently we test only for this form)
bool simplify_clause::get_bit(const pdd& lhs, const pdd& rhs, pdd& p, single_bit& bit, bool pos) {
if (!rhs.is_val())
return false;
rational rhs_val = rhs.val() + 1;
unsigned k = rhs.power_of_2();
if (rhs_val != rational::power_of_two(k - 1))
return false;
pdd rest = lhs - rhs_val;
unsigned d = rest.max_pow2_divisor();
bit.position = k - d - 1;
bit.positive = pos;
p = rest.div(rational::power_of_two(d));
return true;
}
// Compares with respect to "subsumption"
// -1: mask1 < mask2 (e.g., 101 < 0101)
// 0: incomparable
// 1: mask1 > mask2
// failure mask1 == mask2
static int compare(const trailing_bits& mask1, const trailing_bits& mask2) {
if (mask1.length == mask2.length) {
SASSERT(mask1.bits != mask2.bits); // otw. we would have already eliminated the duplicate constraint
return 0;
}
if (mask1.length < mask2.length) {
for (unsigned i = 0; i < mask1.length; i++)
if (mask1.bits.get_bit(i) != mask2.bits.get_bit(i))
return 0;
return -1;
}
SASSERT(mask1.length > mask2.length);
for (unsigned i = 0; i < mask2.length; i++)
if (mask1.bits.get_bit(i) != mask2.bits.get_bit(i))
return 0;
return 1;
}
/**
* Test simple subsumption between bit and parity constraints.
*
* let lsb(t, d) = m := 2^(k - d)*t = m * 2^(k - d) denotes that the last (least significant) d bits of t are the binary representation of m
* let bit(t, i) := 2^(k - 1) <= 2^(k - i - 1)*t
* TODO: 2^(k - 1 - d) <= 2^(k - i - 1)*t denotes that bits i-d...i are set to 0
*
* lsb(t, d) = m with log2(m) >= d => false
*
* parity(t) >= d denotes lsb(t, d) = 0
* parity(t) <= d denotes lsb(t, d + 1) != 0
*
* parity(t) >= d1 || parity(t) >= d2 with d1 < d2 implies parity(t) >= d1
* parity(t) <= d1 || parity(t) <= d2 with d1 < d2 implies parity(t) <= d2
*
* parity(t) >= d1 || !bit(t, d2) with d2 < d1 implies bit(t, d2)
* parity(t) <= d1 || bit(t, d2) with d2 < d1 implies parity(t) <= d1
*
* parity(t) >= d1 || parity(t) <= d2 with d1 <= d2 implies true
*
* More generally: parity can be replaced by lsb in case we check for subsumption between the bit-masks rather than comparing the parities (special case)
*/
bool simplify_clause::try_bit_subsumptions(clause& cl) {
struct pdd_info {
unsigned sz;
vector<trailing_bits> leading;
vector<single_bit> fixed_bits;
};
struct optional_pdd_hash {
unsigned operator()(optional<pdd> const& args) const {
return args->hash();
}
};
ptr_vector<pdd_info> info_list;
map<optional<pdd>, pdd_info*, optional_pdd_hash, default_eq<optional<pdd>>> info_table;
bool is_valid = false;
auto get_info = [&info_table, &info_list](const pdd& p) -> pdd_info& {
auto it = info_table.find_iterator(optional(p));
if (it != info_table.end())
return *it->m_value;
auto* info = alloc(pdd_info);
info->sz = p.manager().power_of_2();
info_list.push_back(info);
info_table.insert(optional(p), info);
return *info;
};
bool changed = false;
bool_vector removed(cl.size(), false);
for (unsigned i = 0; i < cl.size(); i++) {
signed_constraint c = s.lit2cnstr(cl[i]);
if (!c->is_ule())
continue;
trailing_bits mask;
single_bit bit;
pdd p = c->to_ule().lhs();
if ((c.is_eq() || c.is_diseq()) && get_trailing_mask(c->to_ule().lhs(), c->to_ule().rhs(), p, mask, c.is_positive())) {
if (mask.bits.bitsize() > mask.length) {
removed[i] = true; // skip this constraint. e.g., 2^(k-3)*x = 9*2^(k-3) is false as 9 >= 2^3
continue;
}
mask.src_idx = i;
get_info(p).leading.push_back(mask);
}
else if (c->is_ule() && get_bit(c->to_ule().lhs(), c->to_ule().rhs(), p, bit, c.is_positive())) {
bit.src_idx = i;
get_info(p).fixed_bits.push_back(bit);
}
}
for (const auto& entry : info_list) {
for (unsigned i = 0; i < entry->leading.size(); i++) {
auto& p1 = entry->leading[i];
// trailing vs. positive
for (unsigned j = i + 1; !removed[p1.src_idx] && j < entry->leading.size(); j++) {
auto& p2 = entry->leading[j];
if (!removed[p2.src_idx])
continue;
if (p1.positive == p2.positive) {
int cmp = compare(p1, p2);
if (cmp != 0) {
if ((cmp == -1) == p1.positive) {
LOG("Removed: " << s.lit2cnstr(cl[p2.src_idx]) << " because of " << s.lit2cnstr(cl[p1.src_idx]) << "\n");
removed[p2.src_idx] = true;
changed = true;
}
else if ((cmp == 1) == p1.positive) {
LOG("Removed: " << s.lit2cnstr(cl[p1.src_idx]) << " because of " << s.lit2cnstr(cl[p2.src_idx]) << "\n");
removed[p1.src_idx] = true;
changed = true;
}
}
}
else {
auto& pos = p1.positive ? p1 : p2;
auto& neg = p1.positive ? p2 : p1;
int cmp = compare(pos, neg);
if (cmp == -1) {
is_valid = true;
changed = true;
LOG("Tautology: " << s.lit2cnstr(cl[pos.src_idx]) << " and " << s.lit2cnstr(cl[neg.src_idx]) << "\n");
goto done;
}
}
}
// trailing vs. bit
for (unsigned j = 0; !removed[p1.src_idx] && j < entry->fixed_bits.size(); j++) {
auto& p2 = entry->fixed_bits[j];
if (removed[p2.src_idx])
continue;
if (p2.position >= p1.length)
continue;
if (p1.positive) {
if (p1.bits.get_bit(p2.position) == p2.positive) {
LOG("Removed: " << s.lit2cnstr(cl[p1.src_idx]) << " because of " << s.lit2cnstr(cl[p2.src_idx]) << " (bit)\n");
removed[p1.src_idx] = true;
changed = true;
}
}
else {
if (p1.bits.get_bit(p2.position) != p2.positive) {
LOG("Removed: " << s.lit2cnstr(cl[p2.src_idx]) << " (bit) because of " << s.lit2cnstr(cl[p1.src_idx]) << "\n");
removed[p2.src_idx] = true;
changed = true;
}
}
}
}
}
done:
for (auto entry : info_list)
dealloc(entry);
if (is_valid) {
SASSERT(!cl.empty());
cl.literals().clear();
cl.literals().push_back(s.eq(s.value(rational::zero(), 1)).blit()); // an obvious tautology
return true;
}
// Remove subsuming literals
if (!changed)
return false;
verbose_stream() << "Bit simplified\n";
unsigned cli = 0;
for (unsigned i = 0; i < cl.size(); ++i)
if (!removed[i])
cl[cli++] = cl[i];
cl.m_literals.shrink(cli);
return true;
}
#if 0
// All variables of clause 'cl' except 'z' are assigned.

View file

@ -17,6 +17,18 @@ Author:
namespace polysat {
class solver;
struct trailing_bits {
unsigned length;
rational bits;
bool positive;
unsigned src_idx;
};
struct single_bit {
bool positive;
unsigned position;
unsigned src_idx;
};
class simplify_clause {
@ -33,6 +45,7 @@ namespace polysat {
bool try_remove_equations(clause& cl);
bool try_recognize_bailout(clause& cl);
bool try_equal_body_subsumptions(clause& cl);
bool try_bit_subsumptions(clause& cl);
void prepare_subs_entry(subs_entry& entry, signed_constraint c);
@ -46,6 +59,9 @@ namespace polysat {
simplify_clause(solver& s);
bool apply(clause& cl);
static bool get_trailing_mask(pdd lhs, pdd rhs, pdd& p, trailing_bits& mask, bool pos);
static bool get_bit(const pdd& lhs, const pdd& rhs, pdd& p, single_bit& bit, bool pos);
};
}

View file

@ -539,7 +539,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) {
@ -554,8 +556,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: {
@ -1160,7 +1163,9 @@ namespace polysat {
void solver::assign_eval(sat::literal lit) {
signed_constraint const c = lit2cnstr(lit);
LOG_V(10, "Evaluate: " << lit_pp(*this, lit));
if (!c.is_currently_true(*this)) IF_VERBOSE(0, verbose_stream() << c << " is not currently true\n");
if (!c.is_currently_true(*this)) {
IF_VERBOSE(0, verbose_stream() << "\n" << c << " is not currently true\n");
}
SASSERT(c.is_currently_true(*this));
VERIFY_EQ(c.eval(*this), l_true);
unsigned level = 0;

View file

@ -255,6 +255,14 @@ namespace polysat {
add(bv->mk_ule(v_inv, v_inv_max), false, dep);
}
void add_udiv(univariate const& in1, univariate const& in2, univariate const& out, bool sign, dep_t dep) override {
add(m.mk_eq(bv->mk_bv_udiv(mk_poly(in1), mk_poly(in2)), mk_poly(out)), sign, dep);
}
void add_urem(univariate const& in1, univariate const& in2, univariate const& out, bool sign, dep_t dep) override {
add(m.mk_eq(bv->mk_bv_urem(mk_poly(in1), mk_poly(in2)), mk_poly(out)), sign, dep);
}
void add_ule_const(rational const& val, bool sign, dep_t dep) override {
if (val == 0)
add(m.mk_eq(x, mk_poly(val)), sign, dep);

View file

@ -105,6 +105,8 @@ namespace polysat {
virtual void add_xor(univariate const& in1, univariate const& in2, univariate const& out, bool sign, dep_t dep) = 0;
virtual void add_not(univariate const& in, univariate const& out, bool sign, dep_t dep) = 0;
virtual void add_inv(univariate const& in, univariate const& out, bool sign, dep_t dep) = 0;
virtual void add_udiv(univariate const& in1, univariate const& in2, univariate const& out, bool sign, dep_t dep) = 0;
virtual void add_urem(univariate const& in1, univariate const& in2, univariate const& out, bool sign, dep_t dep) = 0;
/// Add x <= val or x > val, depending on sign
virtual void add_ule_const(rational const& val, bool sign, dep_t dep) = 0;

View file

@ -7,6 +7,7 @@ Module Name:
Author:
Clemens Eisenhofer 2023-01-03
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-06
@ -33,9 +34,10 @@ namespace polysat {
const unsigned char pattern_33 = 0x33; // 00110011
const unsigned char pattern_0f = 0x0f; // 00001111
const unsigned char pattern_01 = 0x01; // 00000001
unsigned to_alloc = (p.power_of_2() + sizeof(unsigned) - 1) / sizeof(unsigned);
unsigned to_alloc_bits = to_alloc * sizeof(unsigned);
unsigned to_alloc_bits = p.power_of_2();
unsigned to_alloc = to_alloc_bits / sizeof(unsigned);
SASSERT(to_alloc_bits == sizeof(unsigned)*to_alloc && "8 already divides power of power_of_2");
// Cache this?
auto* scaled_55 = (unsigned*)alloca(to_alloc_bits);
@ -62,16 +64,33 @@ namespace polysat {
//final_shift = (final_shift + 7) / 8 * 8 - 1; // ceil final_shift to the next multiple of 8
return s.lshr(w * m.mk_val(rational_scaled_01), m.mk_val(p.power_of_2() - 8));
}
// [nsb cr]: m_reste_constants assume the clause added for power * rest == p is
// alive even after backtracking. This assumes that irredundant clauses are retained, probably
// still true for PolySAT, but fragile.
// same comment goes for get_inverse.
//
// the caches should be invalidated if irredundant clauses get purged.
// old z3 uses a deletion event handler with clauses to track state; it is
// a bit fragile to program with.
//
/**
* Encode:
*
* p = rest << log(p)
*
*/
pdd free_variable_elimination::get_odd(pdd p) {
SASSERT(p.is_val() || p.is_var()); // For now
auto& m = p.manager();
if (p.is_val()) {
const rational& v = p.val();
unsigned d = v.trailing_zeros();
if (!d)
return p.manager().mk_val(v);
return p.manager().mk_val(div(v, rational::power_of_two(d))); // TODO: Is there no shift?
if (d == 0)
return p;
return m.mk_val(div(v, rational::power_of_two(d))); // TODO: Is there no shift?
}
pvar v = p.var();
if (m_rest_constants.size() > v && m_rest_constants[v] != -1)
@ -80,7 +99,7 @@ namespace polysat {
pdd power = get_dyadic_valuation(p).second;
pvar rest = s.add_var(p.power_of_2());
pdd rest_pdd = p.manager().mk_var(rest);
pdd rest_pdd = m.mk_var(rest);
m_rest_constants.setx(v, rest, -1);
s.add_clause(s.eq(power * rest_pdd, p), false);
return rest_pdd;
@ -88,9 +107,10 @@ namespace polysat {
optional<pdd> free_variable_elimination::get_inverse(pdd p) {
SASSERT(p.is_val() || p.is_var()); // For now
auto& m = p.manager();
if (p.is_val()) {
pdd i = p.manager().zero();
pdd i = m.zero();
if (!inv(p, i))
return {};
return optional<pdd>(i);
@ -100,9 +120,9 @@ namespace polysat {
return optional<pdd>(s.var(m_inverse_constants[v]));
pvar inv = s.add_var(p.power_of_2());
pdd inv_pdd = p.manager().mk_var(inv);
pdd inv_pdd = m.mk_var(inv);
m_inverse_constants.setx(v, inv, -1);
s.add_clause(s.eq(inv_pdd * p, p.manager().one()), false);
s.add_clause(s.eq(inv_pdd * p, m.one()), false);
return optional<pdd>(inv_pdd);
}
@ -118,10 +138,11 @@ namespace polysat {
pvar pv2;
bool new_var = false;
if (m_pv_constants.size() <= v || m_pv_constants[v] == -1) {
pv = s.add_var(m.power_of_2()); // TODO: What's a good value? Unfortunately we cannot use a integer
pv = s.add_var(m.power_of_2()); // TODO: What's a good value? Unfortunately we cannot use an integer
pv2 = s.add_var(m.power_of_2());
m_pv_constants.setx(v, pv, -1);
m_pv_power_constants.setx(v, pv2, -1);
// [nsb cr]: why these calls to m.mk_var()?
m.mk_var(pv);
m.mk_var(pv2);
new_var = true;
@ -137,14 +158,20 @@ namespace polysat {
// For testing some different implementations
#if PV_MOD == 1
// brute-force bit extraction and <=
//
// 2^N-k-1 * p = 0 <=> k <= pv
// pv2 = 1 << pv
//
// [nsb cr] why are these clauses always added, when some are only added by new_var?
signed_constraint c1 = s.eq(rational::power_of_two(p.power_of_2() - k - 1) * p, m.zero());
signed_constraint c2 = s.ule(m.mk_val(k), s.var(pv));
s.add_clause(~c1, c2, false);
s.add_clause(c1, ~c2, false);
if (new_var) {
if (new_var)
s.add_clause(s.eq(s.var(pv2), s.shl(m.one(), s.var(pv))), false);
}
#elif PV_MOD == 2
// symbolic "maximal divisible"
signed_constraint c1 = s.eq(s.shl(s.lshr(p, s.var(pv)), s.var(pv)), p);
@ -153,6 +180,8 @@ namespace polysat {
signed_constraint z = ~s.eq(p, p.manager().zero());
// v != 0 ==> [(v >> pv) << pv == v && (v >> pv + 1) << pv + 1 != v]
// [nsb cr] why are these clauses always added, when some are only added by new_var?
s.add_clause(~z, c1, false);
s.add_clause(~z, c2, false);
@ -162,6 +191,8 @@ namespace polysat {
#elif PV_MOD == 3
// computing the complete function by hamming-distance
// proven equivalent with case 2 via bit-blasting for small sizes
// [nsb cr] why are these clauses always added, when some are only added by new_var?
s.add_clause(s.eq(s.var(pv), get_hamming_distance(s.bxor(p, p - 1)) - 1), false);
// in case v == 0 ==> pv == k - 1 (we don't care)
@ -180,6 +211,8 @@ namespace polysat {
signed_constraint c1 = s.eq(s.var(pv), k);
signed_constraint c2 = s.eq(s.var(pv2), rational::power_of_two(k));
signed_constraint c3 = s.eq(masked, rational::power_of_two(k));
// [nsb cr] why are these clauses always added, when some are only added by new_var?
s.add_clause(c1, ~c3, false);
s.add_clause(c2, ~c3, false);
@ -292,8 +325,8 @@ namespace polysat {
signed_constraint p1 = s.ule(m.zero(), m.zero());
signed_constraint p2 = s.ule(m.zero(), m.zero());
pdd new_lhs = p.manager().zero();
pdd new_rhs = p.manager().zero();
pdd new_lhs = m.zero();
pdd new_rhs = m.zero();
pdd fac_lhs = m.zero();
pdd fac_rhs = m.zero();
@ -309,10 +342,10 @@ namespace polysat {
LOG("fac_rhs: " << fac_rhs);
LOG("rest_rhs: " << rest_rhs);
pdd pv_equality = p.manager().zero();
pdd lhs_multiple = p.manager().zero();
pdd rhs_multiple = p.manager().zero();
pdd coeff_odd = p.manager().zero();
pdd pv_equality = m.zero();
pdd lhs_multiple = m.zero();
pdd rhs_multiple = m.zero();
pdd coeff_odd = m.zero();
optional<pdd> fac_odd_inv;
get_multiple_result multiple1 = get_multiple(fac_lhs, fac, new_lhs);
@ -642,7 +675,33 @@ namespace polysat {
}
// a * x + b = 0 (x not in a or b; i.e., the equation is linear in x)
// C[x, ...] resp., C[..., x]
// replace x in p(x)
//
// 0. x does not occur in p
// 1. a is an odd numeral.
// x = a^-1 * -b, substitute in p(x)
// 2. p has degree > 1.
// bail
// 3. p := a1*x + b1
// 3.1 a1 = a*a2 - a divides a1
// p := a2*a*x + b1 = a2*-b + b1
// 3.2 a is known to not divide a1
// bail
// 3.3 min_parity(a) == max_parity(a), min_parity(a1) >= max_parity(a)
// Result is modulo pre-conditions for min/max parity
// 3.3.1 b = 0
// p := b1
// 3.3.2 b != 0
// ainv:
// with property: a * ainv = 2^min_parity(a)
// 3.3.2.1. min_parity(a) > 0
// shift := a1 >> min_parity(a)
// lemma: shift << min_parity(a) == a1, note that lemma is implied by pre-conditions
// p := ainv * -b * shift + b1
// 3.3.2.2
// p := ainv * -b * a + b1
// [nsb cr: isn't it a1 not a in this definition?]
//
std::tuple<pdd, bool> parity_tracker::eliminate_variable(saturation& saturation, pvar x, const pdd& a, const pdd& b, const pdd& p, clause_builder& precondition) {
unsigned p_degree = p.degree(x);
@ -684,30 +743,33 @@ namespace polysat {
LOG("Warning: Inverting " << a << " although it is not a single variable - might not be a good idea");
}
unsigned a_parity;
if ((a_parity = saturation.min_parity(a)) != saturation.max_parity(a) || saturation.min_parity(a1) < a_parity)
vector<signed_constraint> explain_a_parity;
unsigned a_parity = saturation.min_parity(a, explain_a_parity);
unsigned a_max_parity = saturation.max_parity(a, explain_a_parity);
if (a_parity != a_max_parity || (a_parity > 0 && saturation.min_parity(a1, explain_a_parity) < a_parity))
return { p, false }; // We need the parity of a and this has to be for sure less than the parity of a1
if (b.is_zero())
return { b1, true };
#if 0
pdd a_pi = get_pseudo_inverse(a, a_parity);
#else
pdd a_pi = s.pseudo_inv(a);
//precondition.insert_eval(~s.eq(a_pi * a, rational::power_of_two(a_parity))); // TODO: This is unfortunately not a justification as the inverse might not be set yet (Can we make it to one?)
precondition.insert_eval(~s.parity_at_most(a, a_parity));
for (auto c : explain_a_parity)
precondition.insert_eval(~c);
if (b.is_zero())
return { b1, true };
#endif
pdd shift = a;
pdd shift = a1;
if (a_parity > 0) {
shift = s.lshr(a1, a1.manager().mk_val(a_parity));
signed_constraint least_parity = s.parity_at_least(a1, a_parity);
signed_constraint shift_right_left = s.eq(rational::power_of_two(a_parity) * shift, a1);
s.add_clause(~least_parity, shift_right_left, true);
//signed_constraint least_parity = s.parity_at_least(a1, a_parity);
//signed_constraint shift_right_left = s.eq(rational::power_of_two(a_parity) * shift, a1);
//s.add_clause(~least_parity, shift_right_left, true);
// s.add_clause(~shift_right_left, least_parity, true); Might be interesting as well [although not needed]; needs to consider special case 0
precondition.insert_eval(~shift_right_left);
// [nsb cr: this pre-condition is already implied from the parity explanations]
// precondition.insert_eval(~shift_right_left);
}
LOG("Forced elimination: " << a_pi * (-b) * shift + b1);
LOG("a: " << a);
@ -716,7 +778,6 @@ namespace polysat {
LOG("pseudo inverse: " << a_pi);
LOG("-b: " << (-b));
LOG("shifted a" << shift);
LOG("Forced elimination: " << a_pi * (-b) * shift + b1);
return { a_pi * (-b) * shift + b1, true };
#endif
}

View file

@ -78,9 +78,10 @@ namespace polysat {
if (m_alloc.empty())
return alloc(entry);
auto* e = m_alloc.back();
e->src.reset();
e->side_cond.reset();
e->refined.reset();
e->coeff = 1;
e->refined = nullptr;
m_alloc.pop_back();
return e;
}
@ -238,6 +239,7 @@ namespace polysat {
bool viable::intersect(pvar v, entry* ne) {
SASSERT(!s.is_assigned(v));
SASSERT(!ne->src.empty());
entry* e = m_units[v];
if (e && e->interval.is_full()) {
m_alloc.push_back(ne);
@ -311,8 +313,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 {
@ -493,7 +496,7 @@ namespace {
y_max = compute_y_max(y_max + 1, a, lo, hi, M);
if (++i == max_refinements) {
// verbose_stream() << "y0=" << y0 << ", a=" << a << ", lo=" << lo << ", hi=" << hi << "\n";
verbose_stream() << "refined y_max: " << y_max << "\n";
// verbose_stream() << "refined y_max: " << y_max << "\n";
break;
}
}
@ -505,7 +508,7 @@ namespace {
y_min = compute_y_min(y_min - 1, a, lo, hi, M);
if (++i == max_refinements) {
// verbose_stream() << "y0=" << y0 << ", a=" << a << ", lo=" << lo << ", hi=" << hi << "\n";
verbose_stream() << "refined y_min: " << y_min << "\n";
// verbose_stream() << "refined y_min: " << y_min << "\n";
break;
}
}
@ -520,6 +523,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, ne->refined);
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.
@ -547,7 +579,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()) {
@ -561,7 +597,7 @@ namespace {
// No solution
LOG("refined: no solution due to parity");
entry* ne = alloc_entry();
ne->refined = e;
ne->refined.push_back(e);
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
@ -578,7 +614,7 @@ namespace {
LOG("refined to [" << num_pp(s, v, lo) << ", " << num_pp(s, v, hi) << "[");
SASSERT_EQ(mod(a * hi, mod_value), b); // hi is the solution
entry* ne = alloc_entry();
ne->refined = e;
ne->refined.push_back(e);
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
@ -610,7 +646,7 @@ namespace {
SASSERT_EQ(mod(a * (lo - 1), mod_value), b); // lo-1 is a solution
SASSERT_EQ(mod(a * hi, mod_value), b); // hi is a solution
entry* ne = alloc_entry();
ne->refined = e;
ne->refined.push_back(e);
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
@ -638,7 +674,7 @@ namespace {
if (hi == mod_value)
hi = 0;
entry* ne = alloc_entry();
ne->refined = e;
ne->refined.push_back(e);
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
@ -670,7 +706,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()
@ -681,12 +721,13 @@ namespace {
rational const& r = e->interval.hi_val();
rational const& s_ = e->interval.hi().val();
SASSERT(p != r && p != 0 && r != 0);
SASSERT(e->src.size() == 1);
rational const a = mod(p * val + q_, mod_value);
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;
@ -719,7 +760,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;
@ -731,7 +772,7 @@ namespace {
pdd lop = s.var2pdd(v).mk_val(lo);
pdd hip = s.var2pdd(v).mk_val(hi);
entry* ne = alloc_entry();
ne->refined = e;
ne->refined.push_back(e);
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
@ -745,11 +786,280 @@ namespace {
return true;
}
// 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, ptr_vector<entry const>& refined) 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);
refined.push_back(add);
}
};
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 (i == firstFail - 1 || 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
}
}
SASSERT(!src.empty());
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;
clause_builder builder(s, "bit check");
vector<std::pair<entry*, trailing_bits>> postponed;
auto add_entry = [&builder](entry* e) {
for (const auto& sc : e->side_cond)
builder.insert_eval(~sc);
for (const auto& src : e->src)
builder.insert_eval(~src);
};
auto add_entry_list = [add_entry](const ptr_vector<entry>& list) {
for (const auto& e : list)
add_entry(e);
};
unsigned largest_mask = 0;
do {
if (e->src.size() != 1) {
// We just consider the ordinary constraints and not already contracted ones
e = e->next();
continue;
}
signed_constraint& src = e->src[0];
single_bit bit;
trailing_bits mask;
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);
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 ((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 (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);
if (add_conflict) {
add_entry_list(justifications[i]);
add_entry(e);
s.set_conflict(*builder.build());
}
return false;
}
else {
// Prefer justifications from larger masks (less premisses)
if (largest_mask < mask.length) {
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";
}
}
}
else
postponed.push_back({ e, mask });
}
e = e->next();
} while(e != first);
// TODO: Incomplete - e.g., if we know the trailing bits are not 00 not 10 not 01 and not 11 we could also detect a conflict
// This would require partially clause solving (worth the effort?)
bool_vector removed(postponed.size(), false);
bool changed;
do { // fixed-point required?
changed = false;
for (unsigned j = 0; j < postponed.size(); j++) {
if (removed[j])
continue;
const auto& neg = postponed[j];
unsigned indet = 0;
unsigned last_indet = 0;
unsigned i = 0;
for (; i < neg.second.length; i++) {
if (fixed[i] != l_undef) {
if (fixed[i] != (neg.second.bits.get_bit(i) ? l_true : l_false)) {
removed[j] = true;
break; // this is already satisfied
}
}
else {
indet++;
last_indet = i;
}
}
if (i == neg.second.length) {
if (indet == 0) {
// Already false
LOG("Found conflict with constraint " << neg.first->src);
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) {
// Simple BCP
auto& justification = justifications[last_indet];
SASSERT(justification.empty());
for (unsigned k = 0; k < neg.second.length; k++) {
if (k != last_indet) {
SASSERT(fixed[k] != l_undef);
for (const auto& just : justifications[k])
justification.push_back(just);
}
}
justification.push_back(neg.first);
fixed[last_indet] = neg.second.bits.get_bit(last_indet) ? l_false : l_true;
removed[j] = true;
LOG("Applying fast BCP on bit " << last_indet << " from constraint " << neg.first->src);
changed = true;
}
}
}
} while(changed);
return true;
}
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());
@ -784,9 +1094,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))
@ -795,9 +1111,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) {
@ -835,17 +1151,19 @@ namespace {
do {
found = false;
do {
if (!e->refined && e->side_cond.empty()) {
if (e->refined.empty() && e->side_cond.empty()) {
auto const& lo = e->interval.lo();
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 (signed_constraint 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 (signed_constraint src : e->src)
out_c.push_back(src);
out_hi = lo.val() - 1;
found = true;
}
@ -869,17 +1187,19 @@ namespace {
do {
found = false;
do {
if (!e->refined && e->side_cond.empty()) {
if (e->refined.empty() && e->side_cond.empty()) {
auto const& lo = e->interval.lo();
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 (signed_constraint 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 (signed_constraint src : e->src)
out_c.push_back(src);
out_lo = hi.val();
found = true;
}
@ -904,14 +1224,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;
@ -945,7 +1268,7 @@ namespace {
e0_prev = e;
out_hi = e->interval.hi_val();
}
else if (e->src == c) {
else if (e->src.contains(c)) {
// multiple intervals from the same constraint c
// TODO: adjacent intervals would fine but they should be merged at insertion instead of considering them here.
return false;
@ -956,9 +1279,10 @@ namespace {
out_c.push_back(c);
}
if (e != e0) {
for (auto sc : e->side_cond)
for (signed_constraint sc : e->side_cond)
out_c.push_back(sc);
out_c.push_back(e->src);
for (signed_constraint src : e->src)
out_c.push_back(src);
}
e = n;
}
@ -991,6 +1315,13 @@ namespace {
template <query_t mode>
lbool viable::query(pvar v, typename query_result<mode>::result_t& result) {
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;
@ -999,11 +1330,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();
}
@ -1018,8 +1349,8 @@ namespace {
LOG("Refinement budget exhausted! Fall back to univariate solver.");
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;
@ -1028,11 +1359,11 @@ namespace {
// For this reason, we start chasing the intervals from the start again.
lo = 0;
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;
@ -1049,9 +1380,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;
}
@ -1083,18 +1414,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;
@ -1109,17 +1440,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;
@ -1132,7 +1463,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;
@ -1151,16 +1482,26 @@ namespace {
entry const* first = m_units[v];
entry const* e = first;
do {
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());
ptr_vector<entry const> to_process = e->refined;
while (!to_process.empty()) {
auto current = to_process.back();
to_process.pop_back();
if (!current->refined.empty()) {
for (auto& ref : current->refined)
to_process.push_back(ref);
continue;
}
const entry* origin = current;
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();
}
@ -1250,6 +1591,7 @@ namespace {
SASSERT(!core.vars().contains(v));
core.add_lemma("viable unsat core", core.build_lemma());
verbose_stream() << "unsat core " << core << "\n";
//exit(0);
return true;
}
@ -1262,9 +1604,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
@ -1310,15 +1666,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);
@ -1339,7 +1696,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);
@ -1363,7 +1725,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 << " ] ";
}
@ -1371,7 +1733,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

@ -72,12 +72,13 @@ namespace polysat {
class viable {
friend class test_fi;
friend class test_polysat;
solver& s;
forbidden_intervals m_forbidden_intervals;
struct entry final : public dll_base<entry>, public fi_record {
entry const* refined = nullptr;
ptr_vector<entry const> refined;
};
enum class entry_kind { unit_e, equal_e, diseq_e };
@ -93,12 +94,21 @@ 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);
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, ptr_vector<entry const>& refined) 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;
@ -110,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.
@ -249,7 +259,7 @@ namespace polysat {
curr(curr), visited(visited || !curr) {}
iterator& operator++() {
if (idx < curr->side_cond.size())
if (idx < curr->side_cond.size() + curr->src.size() - 1)
++idx;
else {
idx = 0;
@ -260,7 +270,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

@ -668,6 +668,36 @@ namespace polysat {
VERIFY_EQ((*cl)[0], s.ule(p, q).blit());
}
// 2^1*x + 2^1 == 0 and 2^2*x == 0
static void test_fi_quickcheck1() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(3));
signed_constraint c1 = s.eq(x * 2 + 2, 0);
signed_constraint c2 = s.eq(4 * x, 0);
s.add_clause(c1, false);
s.add_clause(c2, false);
s.m_viable.intersect(x.var(), c1);
s.m_viable.intersect(x.var(), c2);
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)
static void test_fi_quickcheck2() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(4));
signed_constraint c1 = s.parity_at_least(x, 3);
signed_constraint c2 = s.bit(x, 1);
s.add_clause(c1, false);
s.add_clause(c2, false);
s.m_viable.intersect(x.var(), c1);
s.m_viable.intersect(x.var(), c2);
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
static void test_parity1() {
scoped_solver s(__func__);
@ -1462,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));
@ -1922,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;
@ -2037,7 +2067,9 @@ void tst_polysat() {
RUN(test_polysat::test_ineq_axiom6());
RUN(test_polysat::test_ineq_non_axiom1());
RUN(test_polysat::test_ineq_non_axiom4());
RUN(test_polysat::test_fi_quickcheck1());
RUN(test_polysat::test_fi_quickcheck2());
if (collect_test_records)
test_records.display(std::cout);
}