3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-06-24 23:03:41 +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

@ -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
@ -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,
@ -616,11 +615,12 @@ namespace polysat {
/**
* 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,14 +632,15 @@ 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);
@ -649,15 +650,14 @@ namespace polysat {
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,18 +665,21 @@ 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 {};
@ -688,9 +691,9 @@ namespace polysat {
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 to_lbool(p.is_zero() && r.is_zero());
return p.val().pseudo_inverse(p.power_of_2()) == r.val() ? l_true : l_false;
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 {

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;