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

Allow creation of op_constraint lemmas without adding them

This commit is contained in:
Jakob Rath 2022-11-30 14:57:14 +01:00
parent 5069796755
commit e338d42cff
3 changed files with 84 additions and 58 deletions

View file

@ -84,6 +84,12 @@ namespace polysat {
bool is_currently_false(solver const& s, bool is_positive) const { return is_currently_true(s, !is_positive); }
virtual void narrow(solver& s, bool is_positive, bool first) = 0;
/**
* If possible, produce a lemma that contradicts the given assignment.
* This method should not modify the solver's search state.
* TODO: don't pass the solver, but an interface that only allows creation of constraints
*/
virtual clause_ref produce_lemma(solver& s, assignment const& a, bool is_positive) { return {}; }
ule_constraint& to_ule();
ule_constraint const& to_ule() const;
@ -153,6 +159,7 @@ namespace polysat {
bool is_currently_true(solver const& s) const { return get()->is_currently_true(s, is_positive()); }
lbool bvalue(solver& s) const;
void narrow(solver& s, bool first) { get()->narrow(s, is_positive(), first); }
clause_ref produce_lemma(solver& s, assignment const& a) { return get()->produce_lemma(s, a, is_positive()); }
void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep) const { get()->add_to_univariate_solver(s, us, dep, is_positive()); }

View file

@ -109,34 +109,51 @@ namespace polysat {
}
/**
* Propagate consequences or detect conflicts based on partial assignments.
*
* We can assume that op_constraint is only asserted positive.
*/
* Propagate consequences or detect conflicts based on partial assignments.
*
* We can assume that op_constraint is only asserted positive.
*/
void op_constraint::narrow(solver& s, bool is_positive, bool first) {
SASSERT(is_positive);
if (is_currently_true(s, is_positive))
return;
switch (m_op) {
case code::lshr_op:
narrow_lshr(s);
break;
case code::shl_op:
narrow_shl(s);
break;
case code::and_op:
narrow_and(s);
break;
default:
NOT_IMPLEMENTED_YET();
break;
}
if (clause_ref lemma = produce_lemma(s, s.assignment()))
s.add_clause(*lemma);
if (!s.is_conflict() && is_currently_false(s, is_positive))
s.set_conflict(signed_constraint(this, is_positive));
}
/**
* Produce lemmas that contradict the given assignment.
*
* We can assume that op_constraint is only asserted positive.
*/
clause_ref op_constraint::produce_lemma(solver& s, assignment const& a, bool is_positive) {
SASSERT(is_positive);
if (is_currently_true(a, is_positive))
return {};
return produce_lemma(s, a);
}
clause_ref op_constraint::produce_lemma(solver& s, assignment const& a) {
switch (m_op) {
case code::lshr_op:
return lemma_lshr(s, a);
case code::shl_op:
return lemma_shl(s, a);
case code::and_op:
return lemma_and(s, a);
default:
NOT_IMPLEMENTED_YET();
return {};
}
}
unsigned op_constraint::hash() const {
return mk_mix(p().hash(), q().hash(), r().hash());
}
@ -169,47 +186,46 @@ namespace polysat {
* s.m_viable.max_viable()
* when r, q are variables.
*/
void op_constraint::narrow_lshr(solver& s) {
auto const pv = s.subst(p());
auto const qv = s.subst(q());
auto const rv = s.subst(r());
clause_ref op_constraint::lemma_lshr(solver& s, assignment const& a) {
auto const pv = a.apply_to(p());
auto const qv = a.apply_to(q());
auto const rv = a.apply_to(r());
unsigned const K = p().manager().power_of_2();
signed_constraint const lshr(this, true);
if (pv.is_val() && rv.is_val() && rv.val() > pv.val())
// r <= p
s.add_clause(~lshr, s.ule(r(), p()), true);
return s.mk_clause(~lshr, s.ule(r(), p()), true);
else if (qv.is_val() && qv.val() >= K && rv.is_val() && !rv.is_zero())
// q >= K -> r = 0
s.add_clause(~lshr, ~s.ule(K, q()), s.eq(r()), true);
return s.mk_clause(~lshr, ~s.ule(K, q()), s.eq(r()), true);
else if (qv.is_zero() && pv.is_val() && rv.is_val() && pv != rv)
// q = 0 -> p = r
s.add_clause(~lshr, ~s.eq(q()), s.eq(p(), r()), true);
return s.mk_clause(~lshr, ~s.eq(q()), s.eq(p(), r()), true);
else if (qv.is_val() && !qv.is_zero() && pv.is_val() && rv.is_val() && !pv.is_zero() && rv.val() >= pv.val())
// q != 0 & p > 0 -> r < p
s.add_clause(~lshr, s.eq(q()), s.ule(p(), 0), s.ult(r(), p()), true);
return s.mk_clause(~lshr, s.eq(q()), s.ule(p(), 0), s.ult(r(), p()), true);
else if (qv.is_val() && !qv.is_zero() && qv.val() < K && rv.is_val() &&
rv.val() > rational::power_of_two(K - qv.val().get_unsigned()) - 1)
// q >= k -> r <= 2^{K-k} - 1
s.add_clause(~lshr, ~s.ule(qv.val(), q()), s.ule(r(), rational::power_of_two(K - qv.val().get_unsigned()) - 1), true);
return s.mk_clause(~lshr, ~s.ule(qv.val(), q()), s.ule(r(), rational::power_of_two(K - qv.val().get_unsigned()) - 1), true);
else if (pv.is_val() && rv.is_val() && qv.is_val() && !qv.is_zero()) {
unsigned const k = qv.val().get_unsigned();
// q = k -> r[i] = p[i+k] for 0 <= i < K - k
for (unsigned i = 0; i < K - k; ++i) {
if (rv.val().get_bit(i) && !pv.val().get_bit(i + k)) {
s.add_clause(~lshr, ~s.eq(q(), k), ~s.bit(r(), i), s.bit(p(), i + k), true);
return;
return s.mk_clause(~lshr, ~s.eq(q(), k), ~s.bit(r(), i), s.bit(p(), i + k), true);
}
if (!rv.val().get_bit(i) && pv.val().get_bit(i + k)) {
s.add_clause(~lshr, ~s.eq(q(), k), s.bit(r(), i), ~s.bit(p(), i + k), true);
return;
return s.mk_clause(~lshr, ~s.eq(q(), k), s.bit(r(), i), ~s.bit(p(), i + k), true);
}
}
}
else {
SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
}
return {};
}
/** Evaluate constraint: r == p >> q */
@ -244,10 +260,10 @@ namespace polysat {
* r != 0 -> r >= p
* q = 0 -> r = p
*/
void op_constraint::narrow_shl(solver& s) {
auto const pv = s.subst(p());
auto const qv = s.subst(q());
auto const rv = s.subst(r());
clause_ref op_constraint::lemma_shl(solver& s, assignment const& a) {
auto const pv = a.apply_to(p());
auto const qv = a.apply_to(q());
auto const rv = a.apply_to(r());
unsigned const K = p().manager().power_of_2();
signed_constraint const shl(this, true);
@ -256,35 +272,34 @@ namespace polysat {
// r != 0 -> r >= p
// r = 0 \/ r >= p (equivalent)
// r-1 >= p-1 (equivalent unit constraint to better support narrowing)
s.add_clause(~shl, s.ule(p() - 1, r() - 1), true);
return s.mk_clause(~shl, s.ule(p() - 1, r() - 1), true);
else if (qv.is_val() && qv.val() >= K && rv.is_val() && !rv.is_zero())
// q >= K -> r = 0
s.add_clause(~shl, ~s.ule(K, q()), s.eq(r()), true);
return s.mk_clause(~shl, ~s.ule(K, q()), s.eq(r()), true);
else if (qv.is_zero() && pv.is_val() && rv.is_val() && rv != pv)
// q = 0 -> r = p
s.add_clause(~shl, ~s.eq(q()), s.eq(r(), p()), true);
return s.mk_clause(~shl, ~s.eq(q()), s.eq(r(), p()), true);
else if (qv.is_val() && !qv.is_zero() && qv.val() < K && rv.is_val() &&
!rv.is_zero() && rv.val() < rational::power_of_two(qv.val().get_unsigned()))
// q >= k -> r = 0 \/ r >= 2^k (intuitive version)
// q >= k -> r - 1 >= 2^k - 1 (equivalent unit constraint to better support narrowing)
s.add_clause(~shl, ~s.ule(qv.val(), q()), s.ule(rational::power_of_two(qv.val().get_unsigned()) - 1, r() - 1), true);
return s.mk_clause(~shl, ~s.ule(qv.val(), q()), s.ule(rational::power_of_two(qv.val().get_unsigned()) - 1, r() - 1), true);
else if (pv.is_val() && rv.is_val() && qv.is_val() && !qv.is_zero()) {
unsigned const k = qv.val().get_unsigned();
// q = k -> r[i+k] = p[i] for 0 <= i < K - k
for (unsigned i = 0; i < K - k; ++i) {
if (rv.val().get_bit(i + k) && !pv.val().get_bit(i)) {
s.add_clause(~shl, ~s.eq(q(), k), ~s.bit(r(), i + k), s.bit(p(), i), true);
return;
return s.mk_clause(~shl, ~s.eq(q(), k), ~s.bit(r(), i + k), s.bit(p(), i), true);
}
if (!rv.val().get_bit(i + k) && pv.val().get_bit(i)) {
s.add_clause(~shl, ~s.eq(q(), k), s.bit(r(), i + k), ~s.bit(p(), i), true);
return;
return s.mk_clause(~shl, ~s.eq(q(), k), s.bit(r(), i + k), ~s.bit(p(), i), true);
}
}
}
else {
SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
}
return {};
}
/** Evaluate constraint: r == p << q */
@ -323,18 +338,19 @@ namespace polysat {
* p = max_value => q = r
* q = max_value => p = r
*/
void op_constraint::narrow_and(solver& s) {
auto pv = s.subst(p());
auto qv = s.subst(q());
auto rv = s.subst(r());
clause_ref op_constraint::lemma_and(solver& s, assignment const& a) {
auto pv = a.apply_to(p());
auto qv = a.apply_to(q());
auto rv = a.apply_to(r());
signed_constraint const andc(this, true);
signed_constraint andc(this, true);
if (pv.is_val() && rv.is_val() && rv.val() > pv.val())
s.add_clause(~andc, s.ule(r(), p()), true);
return s.mk_clause(~andc, s.ule(r(), p()), true);
else if (qv.is_val() && rv.is_val() && rv.val() > qv.val())
s.add_clause(~andc, s.ule(r(), q()), true);
return s.mk_clause(~andc, s.ule(r(), q()), true);
else if (pv.is_val() && qv.is_val() && rv.is_val() && pv == qv && rv != pv)
s.add_clause(~andc, ~s.eq(p(), q()), s.eq(r(), p()), true);
return s.mk_clause(~andc, ~s.eq(p(), q()), s.eq(r(), p()), true);
else if (pv.is_val() && qv.is_val() && rv.is_val()) {
unsigned K = p().manager().power_of_2();
for (unsigned i = 0; i < K; ++i) {
@ -344,16 +360,17 @@ namespace polysat {
if (rb == (pb && qb))
continue;
if (pb && qb && !rb)
s.add_clause(~andc, ~s.bit(p(), i), ~s.bit(q(), i), s.bit(r(), i), true);
return s.mk_clause(~andc, ~s.bit(p(), i), ~s.bit(q(), i), s.bit(r(), i), true);
else if (!pb && rb)
s.add_clause(~andc, s.bit(p(), i), ~s.bit(r(), i), true);
return s.mk_clause(~andc, s.bit(p(), i), ~s.bit(r(), i), true);
else if (!qb && rb)
s.add_clause(~andc, s.bit(q(), i), ~s.bit(r(), i), true);
return s.mk_clause(~andc, s.bit(q(), i), ~s.bit(r(), i), true);
else
UNREACHABLE();
return;
return {};
}
}
return {};
}
/** Evaluate constraint: r == p & q */

View file

@ -39,14 +39,15 @@ namespace polysat {
op_constraint(constraint_manager& m, code c, pdd const& p, pdd const& q, pdd const& r);
lbool eval(pdd const& p, pdd const& q, pdd const& r) const;
clause_ref produce_lemma(solver& s, assignment const& a);
void narrow_lshr(solver& s);
clause_ref lemma_lshr(solver& s, assignment const& a);
static lbool eval_lshr(pdd const& p, pdd const& q, pdd const& r);
void narrow_shl(solver& s);
clause_ref lemma_shl(solver& s, assignment const& a);
static lbool eval_shl(pdd const& p, pdd const& q, pdd const& r);
void narrow_and(solver& s);
clause_ref lemma_and(solver& s, assignment const& a);
static lbool eval_and(pdd const& p, pdd const& q, pdd const& r);
std::ostream& display(std::ostream& out, char const* eq) const;
@ -61,6 +62,7 @@ namespace polysat {
lbool eval() const override;
lbool eval(assignment const& a) const override;
void narrow(solver& s, bool is_positive, bool first) override;
virtual clause_ref produce_lemma(solver& s, assignment const& a, bool is_positive) override;
unsigned hash() const override;
bool operator==(constraint const& other) const override;
bool is_eq() const override { return false; }