3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-24 20:16:00 +00:00
z3/src/math/polysat/op_constraint.cpp
2022-06-29 14:26:25 +02:00

318 lines
11 KiB
C++

/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
polysat constraints for bit operations.
Author:
Jakob Rath, Nikolaj Bjorner (nbjorner) 2021-12-09
Notes:
Additional possible functionality on constraints:
- activate - when operation is first activated. It may be created and only activated later.
- bit-wise assignments - narrow based on bit assignment, not entire word assignment.
- integration with congruence tables
- integration with conflict resolution
--*/
#include "math/polysat/op_constraint.h"
#include "math/polysat/solver.h"
namespace polysat {
op_constraint::op_constraint(constraint_manager& m, code c, pdd const& p, pdd const& q, pdd const& r) :
constraint(m, ckind_t::op_t), m_op(c), m_p(p), m_q(q), m_r(r) {
m_vars.append(p.free_vars());
for (auto v : q.free_vars())
if (!m_vars.contains(v))
m_vars.push_back(v);
for (auto v : r.free_vars())
if (!m_vars.contains(v))
m_vars.push_back(v);
switch (c) {
case code::and_op:
case code::or_op:
case code::xor_op:
if (p.index() > q.index())
std::swap(m_p, m_q);
break;
default:
break;
}
}
lbool op_constraint::eval(pdd const& p, pdd const& q, pdd const& r) const {
switch (m_op) {
case code::lshr_op:
return eval_lshr(p, q, r);
case code::and_op:
return eval_and(p, q, r);
default:
return l_undef;
}
}
bool op_constraint::is_always_false(bool is_positive, pdd const& p, pdd const& q, pdd const& r) const {
switch (eval(p, q, r)) {
case l_true: return !is_positive;
case l_false: return is_positive;
default: return false;
}
}
bool op_constraint::is_always_true(bool is_positive, pdd const& p, pdd const& q, pdd const& r) const {
switch (eval(p, q, r)) {
case l_true: return is_positive;
case l_false: return !is_positive;
default: return false;
}
}
bool op_constraint::is_always_false(bool is_positive) const {
return is_always_false(is_positive, p(), q(), r());
}
bool op_constraint::is_currently_false(solver& s, bool is_positive) const {
return is_always_false(is_positive, s.subst(p()), s.subst(q()), s.subst(r()));
}
bool op_constraint::is_currently_true(solver& s, bool is_positive) const {
return is_always_true(is_positive, s.subst(p()), s.subst(q()), s.subst(r()));
}
std::ostream& op_constraint::display(std::ostream& out, lbool status) const {
switch (status) {
case l_true: return display(out);
case l_false: return display(out << "~");
default: return display(out << "?");
}
}
std::ostream& operator<<(std::ostream& out, op_constraint::code c) {
switch (c) {
case op_constraint::code::ashr_op:
return out << ">>a";
case op_constraint::code::lshr_op:
return out << ">>";
case op_constraint::code::shl_op:
return out << "<<";
case op_constraint::code::and_op:
return out << "&";
case op_constraint::code::or_op:
return out << "|";
case op_constraint::code::xor_op:
return out << "^";
default:
UNREACHABLE();
return out;
}
return out;
}
std::ostream& op_constraint::display(std::ostream& out) const {
if (m_op == code::not_op)
return out << r() << " == ~" << p();
else
return out << r() << " == " << p() << " " << m_op << " " << q();
}
/**
* 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::and_op:
narrow_and(s);
break;
default:
NOT_IMPLEMENTED_YET();
break;
}
if (!s.is_conflict() && is_currently_false(s, is_positive))
s.set_conflict(signed_constraint(this, is_positive));
}
unsigned op_constraint::hash() const {
return mk_mix(p().hash(), q().hash(), r().hash());
}
bool op_constraint::operator==(constraint const& other) const {
if (other.kind() != ckind_t::op_t)
return false;
auto const& o = other.to_op();
return m_op == o.m_op && p() == o.p() && q() == o.q() && r() == o.r();
}
/**
* Enforce basic axioms for r == p >> q:
*
* q >= k -> r[i] = 0 for i > K - k
* q >= K -> r = 0
* q >= k -> r <= 2^{K-k-1}
* q = k -> r[i - k] = p[i] for i <= K - k
* r <= p
* q != 0 => r <= p
* q = 0 => r = p
*
* when q is a constant, several axioms can be enforced at activation time.
*
* Enforce also inferences and bounds
*
* TODO use also
* s.m_viable.min_viable();
* s.m_viable.max_viable()
* when r, q are variables.
*/
void op_constraint::narrow_lshr(solver& s) {
auto pv = s.subst(p());
auto qv = s.subst(q());
auto rv = s.subst(r());
unsigned K = p().manager().power_of_2();
signed_constraint 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);
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);
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);
else if (qv.is_val() && !qv.is_zero() && pv.is_val() && rv.is_val() && !pv.is_zero() && pv.val() <= rv.val())
// q != 0 & p > 0 => r < p
s.add_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);
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);
// q = k -> r[i - k] = p[i] for K - k <= i < K
else if (pv.is_val() && rv.is_val() && qv.is_val() && !qv.is_zero()) {
unsigned k = qv.val().get_unsigned();
for (unsigned i = K - k; i < K; ++i) {
if (rv.val().get_bit(i - k) && !pv.val().get_bit(i)) {
s.add_clause(~lshr, ~s.eq(q(), k), ~s.bit(r(), i - k), s.bit(p(), i), true);
return;
}
if (!rv.val().get_bit(i - k) && pv.val().get_bit(i)) {
s.add_clause(~lshr, ~s.eq(q(), k), s.bit(r(), i - k), ~s.bit(p(), i), true);
return;
}
}
}
else {
SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
}
}
lbool op_constraint::eval_lshr(pdd const& p, pdd const& q, pdd const& r) {
auto& m = p.manager();
if (p.is_val() && q.is_val() && r.is_val())
return r == p * m.mk_val(rational::power_of_two(q.val().get_unsigned())) ? l_true : l_false;
if (q.is_val() && q.val() >= m.power_of_2() && r.is_val())
return r.is_zero() ? l_true : l_false;
// other cases when we know lower
// bound of q, e.g, q = 2^k*q1 + q2, where q2 is a constant.
return l_undef;
}
/**
* Produce lemmas:
* p & q <= p
* p & q <= q
* p = q => p & q = r
* p = 0 => r = 0
* q = 0 => r = 0
* p[i] && q[i] = r[i]
*
* Possible other:
* 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());
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);
else if (qv.is_val() && rv.is_val() && rv.val() > qv.val())
s.add_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);
else if (pv.is_zero() && rv.is_val() && !rv.is_zero())
s.add_clause(~andc, ~s.eq(p()), s.eq(r()), true);
else if (qv.is_zero() && rv.is_val() && !rv.is_zero())
s.add_clause(~andc, ~s.eq(q()), s.eq(r()), 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) {
bool pb = pv.val().get_bit(i);
bool qb = qv.val().get_bit(i);
bool rb = rv.val().get_bit(i);
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);
else if (!pb && rb)
s.add_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);
else
UNREACHABLE();
return;
}
}
}
lbool op_constraint::eval_and(pdd const& p, pdd const& q, pdd const& r) {
if ((p.is_zero() || q.is_zero()) && r.is_zero())
return l_true;
if (p.is_val() && q.is_val() && r.is_val())
return r.val() == bitwise_and(p.val(), q.val()) ? l_true : l_false;
return l_undef;
}
void op_constraint::add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
auto p_coeff = s.subst(p()).get_univariate_coefficients();
auto q_coeff = s.subst(q()).get_univariate_coefficients();
auto r_coeff = s.subst(r()).get_univariate_coefficients();
switch (m_op) {
case code::lshr_op:
us.add_lshr(p_coeff, q_coeff, r_coeff, !is_positive, dep);
break;
case code::and_op:
us.add_and(p_coeff, q_coeff, r_coeff, !is_positive, dep);
break;
default:
NOT_IMPLEMENTED_YET();
break;
}
}
}