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

Update parity lemmas

p != 0  ==>  odd(r)
... added premise p != 0

parity(p) < k    ==>  r <= 2^(N - k) - 1
... do this also in the other branch
    (otherwise we hit the UNREACHABLE in bench3)
This commit is contained in:
Jakob Rath 2023-01-16 16:46:12 +01:00
parent 26e7d0d35a
commit b6f8538d20
2 changed files with 64 additions and 48 deletions

View file

@ -102,9 +102,9 @@ namespace polysat {
}
std::ostream& op_constraint::display(std::ostream& out, char const* eq) const {
if (m_op == code::inv_op)
if (m_op == code::inv_op)
return out << r() << " " << eq << " " << m_op << " " << p();
return out << r() << " " << eq << " " << p() << " " << m_op << " " << q();
}
@ -129,7 +129,7 @@ namespace polysat {
if (clause_ref lemma = produce_lemma(s, s.get_assignment()))
s.add_clause(*lemma);
if (!s.is_conflict() && is_currently_false(s, is_positive))
s.set_conflict(signed_constraint(this, is_positive));
}
@ -184,6 +184,7 @@ namespace polysat {
break;
case code::shl_op:
// TODO: if shift amount is constant p << k, then add p << k == p*2^k
// NOTE: we do that now as simplification in constraint_manager::shl
break;
case code::and_op:
// handle masking of high order bits
@ -193,7 +194,7 @@ namespace polysat {
break;
default:
break;
}
}
}
unsigned op_constraint::hash() const {
@ -294,9 +295,7 @@ namespace polysat {
if (p.is_val() && q.is_val() && r.is_val()) {
SASSERT(q.val().is_unsigned()); // otherwise, previous condition should have been triggered
// TODO: use right-shift operation instead of division
auto divisor = rational::power_of_two(q.val().get_unsigned());
return to_lbool(r.val() == div(p.val(), divisor));
return to_lbool(r.val() == machine_div2k(p.val(), q.val().get_unsigned()));
}
// TODO: other cases when we know lower bound of q,
@ -309,7 +308,7 @@ namespace polysat {
// TODO: Implement: negative case
return true;
}
/**
* Enforce axioms for constraint: r == p << q
*
@ -420,10 +419,10 @@ namespace polysat {
SASSERT(shift_max_u <= sz);
SASSERT(shift_min_u <= shift_max_u);
unsigned span = shift_max_u - shift_min_u;
// Shift by at the value we know q to be at least
// Shift by at the value we know q to be at least
// TODO: Improve performance; we can reuse the justifications from the previous iteration
if (shift_min_u > 0) {
for (unsigned i = 0; i < shift_min_u; i++) {
@ -436,7 +435,7 @@ namespace polysat {
tbit val = p_val[i - shift_min_u];
if (val == BIT_z)
continue;
for (; j < span; j++) {
if (p_val[i - shift_min_u + 1] != val)
break;
@ -461,7 +460,7 @@ namespace polysat {
if (!(yv + 1).is_power_of_two())
return;
signed_constraint const andc(this, true);
if (yv == m.max_value())
if (yv == m.max_value())
s.add_clause(~andc, s.eq(x, r()), false);
else if (yv == 0)
s.add_clause(~andc, s.eq(r()), false);
@ -472,7 +471,7 @@ namespace polysat {
rational exp = rational::power_of_two(K - k);
s.add_clause(~andc, s.eq(x * exp, r() * exp), false);
s.add_clause(~andc, s.ule(r(), y), false); // maybe always activate these constraints regardless?
}
}
}
/**
@ -485,8 +484,8 @@ namespace polysat {
* q = 2^K - 1 => p = r
* p = 2^k - 1 => r*2^{K - k} = q*2^{K - k}
* q = 2^k - 1 => r*2^{K - k} = p*2^{K - k}
* r = 0 && q != 0 & p = 2^k - 1 => q >= 2^k
* r = 0 && p != 0 & q = 2^k - 1 => p >= 2^k
* r = 0 && q != 0 & p = 2^k - 1 => q >= 2^k
* r = 0 && p != 0 & q = 2^k - 1 => p >= 2^k
*/
clause_ref op_constraint::lemma_and(solver& s, assignment const& a) {
auto& m = p().manager();
@ -520,13 +519,13 @@ namespace polysat {
// q = 2^k - 1 => r*2^{K - k} = p*2^{K - k}
// r = 0 && q != 0 & p = 2^k - 1 => q >= 2^k
// r = 0 && q != 0 & p = 2^k - 1 => q >= 2^k
if ((pv.val() + 1).is_power_of_two() && rv.val() > pv.val())
return s.mk_clause(~andc, ~s.eq(r()), ~s.eq(p(), pv.val()), s.eq(q()), s.ult(p(), q()), true);
// r = 0 && p != 0 & q = 2^k - 1 => p >= 2^k
if (rv.is_zero() && (qv.val() + 1).is_power_of_two() && pv.val() <= qv.val())
return s.mk_clause(~andc, ~s.eq(r()), ~s.eq(q(), qv.val()), s.eq(p()),s.ult(q(), p()), true);
for (unsigned i = 0; i < K; ++i) {
bool pb = pv.val().get_bit(i);
bool qb = qv.val().get_bit(i);
@ -584,7 +583,7 @@ namespace polysat {
tbit bp = p_val[i];
tbit bq = q_val[i];
tbit br = r_val[i];
if (bp == BIT_0 || bq == BIT_0) {
// TODO: In case both are 0 use the one with the lower decision-level and not necessarily p
if (!s.m_fixed_bits.fix_bit(s, m_r, i, BIT_0, bit_justification_constraint::mk_unary(s, this, { bp == BIT_0 ? m_p : m_q, i }), true))
@ -613,14 +612,15 @@ namespace polysat {
}
return true;
}
/**
* Produce lemmas for constraint: r == inv p
* p = 0 => r = 0
* r = 0 => p = 0
* odd(r) -- for now we are looking for the smallest pseudo-inverse (there are 2^parity(p) of them)
* parity(p) >= k && p * r < 2^k => p * r >= 2^k
* parity(p) < k && p * r >= 2^k => p * r < 2^k
* p = 0 ==> r = 0
* r = 0 ==> p = 0
* p != 0 ==> odd(r)
* parity(p) >= k ==> p * r >= 2^k
* parity(p) < k ==> p * r <= 2^k - 1
* parity(p) < k ==> r <= 2^(N - k) - 1 (because r is the smallest pseudo-inverse)
*/
clause_ref op_constraint::lemma_inv(solver& s, assignment const& a) {
auto& m = p().manager();
@ -632,32 +632,32 @@ namespace polysat {
signed_constraint const invc(this, true);
// p = 0 => r = 0
// p = 0 ==> r = 0
if (pv.is_zero())
return s.mk_clause(~invc, ~s.eq(p()), s.eq(r()), true);
// r = 0 => p = 0
// r = 0 ==> p = 0
if (rv.is_zero())
return s.mk_clause(~invc, ~s.eq(r()), s.eq(p()), true);
// p assigned => r = pseudo_inverse(eval(p))
// 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 {};
unsigned parity_pv = pv.val().trailing_zeros();
unsigned parity_rv = rv.val().trailing_zeros();
// odd(r)
// p != 0 ==> odd(r)
if (parity_rv != 0)
return s.mk_clause(~invc, s.odd(r()), true);
// parity(p) >= k && p * r < 2^k => p * r >= 2^k
// parity(p) < k && p * r >= 2^k => p * r < 2^k
return s.mk_clause(~invc, ~s.eq(p()), s.odd(r()), true);
pdd prod = p() * r();
rational prodv = (pv * rv).val();
SASSERT(prodv != rational::power_of_two(parity_pv)); // Why did it evaluate to false in this case?
unsigned lower = 0, upper = p().power_of_2();
unsigned lower = 0, upper = m.power_of_2();
// binary search for the parity (otw. we would have justifications like "parity_at_most(k) && parity_at_least(k)" for at most "k" widths
while (lower + 1 < upper) {
unsigned middle = (upper + lower) / 2;
@ -665,34 +665,37 @@ namespace polysat {
if (parity_pv >= middle) { // parity at least middle
lower = middle;
LOG("Its in [" << lower << "; " << upper << ")");
if (prodv < rational::power_of_two(middle)) // product is for sure not 2^parity
// parity(p) >= k ==> p * r >= 2^k
if (prodv < rational::power_of_two(middle))
return s.mk_clause(~invc, ~s.parity_at_least(p(), middle), s.uge(prod, rational::power_of_two(middle)), false);
}
else { // parity at most middle
else { // parity less than middle
upper = middle;
LOG("Its in [" << lower << "; " << upper << ")");
if (prodv > rational::power_of_two(middle)) // product is for sure not 2^parity
return s.mk_clause(~invc, s.parity_at_least(p(), middle), s.ule(prod, rational::power_of_two(middle)), false);
if (rv.val() >= rational::power_of_two(p().power_of_2() - middle)) // enforce that pseudo-inverse is smaller than 2^k-parity (minimal pseudo-inverse)
return s.mk_clause(~invc, s.parity_at_least(p(), middle), s.ule(r(), rational::power_of_two(p().power_of_2() - middle) - 1), false);
// parity(p) < k ==> p * r <= 2^k - 1
if (prodv > rational::power_of_two(middle))
return s.mk_clause(~invc, s.parity_at_least(p(), middle), s.ule(prod, rational::power_of_two(middle) - 1), false);
}
// parity(p) < k ==> r <= 2^(N - k) - 1 (because r is the smallest pseudo-inverse)
rational const max_rv = rational::power_of_two(m.power_of_2() - middle) - 1;
if (rv.val() > max_rv)
return s.mk_clause(~invc, s.parity_at_least(p(), middle), s.ule(r(), max_rv), false);
}
UNREACHABLE();
return {};
}
/** Evaluate constraint: r == inv p */
lbool op_constraint::eval_inv(pdd const& p, pdd const& r) {
if (!p.is_val() || !r.is_val())
return l_undef;
if (p.is_zero() || r.is_zero()) // the inverse of 0 is 0 (by arbitrary definition). Just to have some unique value
return p.is_zero() && r.is_zero() ? l_true : l_false;
return p.val().pseudo_inverse(p.power_of_2()) == r.val() ? l_true : l_false;
return to_lbool(p.is_zero() && r.is_zero());
return to_lbool(p.val().pseudo_inverse(p.power_of_2()) == r.val());
}
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());
if (!pv.is_univariate_in(v))

View file

@ -26,7 +26,20 @@ namespace polysat {
class op_constraint final : public constraint {
public:
enum class code { lshr_op, ashr_op, shl_op, and_op, inv_op };
enum class code {
/// r is the logical right shift of p by q
lshr_op,
/// r is the arithmetic right shift of p by q
ashr_op,
/// r is the left shift of p by q
shl_op,
/// r is the bit-wise 'and' of p and q
and_op,
/// 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
};
protected:
friend class constraint_manager;
@ -53,7 +66,7 @@ namespace polysat {
clause_ref lemma_inv(solver& s, assignment const& a);
static lbool eval_inv(pdd const& p, pdd const& r);
std::ostream& display(std::ostream& out, char const* eq) const;
void activate(solver& s);