mirror of
https://github.com/Z3Prover/z3
synced 2025-06-28 00:48:45 +00:00
slicing with fixed bits (wip)
This commit is contained in:
parent
f22ed9002f
commit
46a794ff67
5 changed files with 246 additions and 18 deletions
144
src/math/polysat/fixed_bits.cpp
Normal file
144
src/math/polysat/fixed_bits.cpp
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
/*++
|
||||||
|
Copyright (c) 2022 Microsoft Corporation
|
||||||
|
|
||||||
|
Module Name:
|
||||||
|
|
||||||
|
Extract fixed bits from constraints
|
||||||
|
|
||||||
|
Author:
|
||||||
|
|
||||||
|
Jakob Rath, Nikolaj Bjorner (nbjorner), Clemens Eisenhofer 2022-08-22
|
||||||
|
|
||||||
|
--*/
|
||||||
|
|
||||||
|
#include "math/polysat/fixed_bits.h"
|
||||||
|
#include "math/polysat/ule_constraint.h"
|
||||||
|
#include "math/polysat/clause.h"
|
||||||
|
|
||||||
|
namespace polysat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constraint lhs <= rhs.
|
||||||
|
*
|
||||||
|
* 2^(k - d) * x = 2^(k - d) * c
|
||||||
|
* ==> x[|d|:0] = c[|d|:0]
|
||||||
|
*
|
||||||
|
* -2^(k - 2) * x > 2^(k - 1)
|
||||||
|
* <=> 2 + x[1:0] > 2 (mod 4)
|
||||||
|
* ==> x[1:0] = 1
|
||||||
|
* -- TODO: Generalize [the obvious solution does not work]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2^(k - d) * x = 2^(k - d) * c
|
||||||
|
* ==> x[|d|:0] = c[|d|:0]
|
||||||
|
*/
|
||||||
|
bool get_eq_fixed_lsb(pdd const& p, fixed_bits& out) {
|
||||||
|
if (!p.hi().is_val())
|
||||||
|
return false;
|
||||||
|
// TODO:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_eq_fixed_bits(pdd const& p, fixed_bits& out) {
|
||||||
|
return get_eq_fixed_lsb(p, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constraint lhs <= rhs.
|
||||||
|
*
|
||||||
|
* -2^(k - 2) * x > 2^(k - 1)
|
||||||
|
* <=> 2 + x[1:0] > 2 (mod 4)
|
||||||
|
* ==> x[1:0] = 1
|
||||||
|
* -- TODO: Generalize [the obvious solution does not work]
|
||||||
|
*/
|
||||||
|
bool get_ule_fixed_lsb(pdd const& lhs, pdd const& rhs, bool is_positive, fixed_bits& out) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_ule_fixed_bits(pdd const& lhs, pdd const& rhs, bool is_positive, fixed_bits& out) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_fixed_bits(signed_constraint c, fixed_bits& out) {
|
||||||
|
SASSERT_EQ(c->vars().size(), 1); // this only makes sense for univariate constraints
|
||||||
|
if (c->is_ule())
|
||||||
|
return get_ule_fixed_bits(c->to_ule().lhs(), c->to_ule().rhs(), c.is_positive(), out);
|
||||||
|
// if (c->is_op())
|
||||||
|
// ; // TODO: x & constant = constant ==> bitmask ... but we have trouble recognizing that because we introduce a new variable for '&' before we see the equality.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// 2^(k - d) * x = m * 2^(k - d)
|
||||||
|
// Special case [still seems to occur frequently]: -2^(k - 2) * x > 2^(k - 1) - TODO: Generalize [the obvious solution does not work] => lsb(x, 2) = 1
|
||||||
|
bool get_lsb(pdd lhs, pdd rhs, pdd& p, trailing_bits& info, bool pos) {
|
||||||
|
SASSERT(lhs.is_univariate() && lhs.degree() <= 1);
|
||||||
|
SASSERT(rhs.is_univariate() && rhs.degree() <= 1);
|
||||||
|
|
||||||
|
if (rhs.is_zero()) { // equality
|
||||||
|
auto lhs_decomp = decouple_constant(lhs);
|
||||||
|
|
||||||
|
lhs = lhs_decomp.first;
|
||||||
|
rhs = -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();
|
||||||
|
info.bits = rhs_val / rational::power_of_two(d);
|
||||||
|
if (!info.bits.is_int())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SASSERT(lhs.is_univariate() && lhs.degree() <= 1);
|
||||||
|
|
||||||
|
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;
|
||||||
|
info.bits = mod2k(info.bits * inv, span);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.length = span;
|
||||||
|
info.positive = pos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else { // inequality - check for special case
|
||||||
|
if (pos || lhs.power_of_2() < 3)
|
||||||
|
return false;
|
||||||
|
auto it = lhs.begin();
|
||||||
|
if (it == lhs.end())
|
||||||
|
return false;
|
||||||
|
if (it->vars.size() != 1)
|
||||||
|
return false;
|
||||||
|
rational coeff = it->coeff;
|
||||||
|
it++;
|
||||||
|
if (it != lhs.end())
|
||||||
|
return false;
|
||||||
|
if ((mod2k(-coeff, lhs.power_of_2())) != rational::power_of_two(lhs.power_of_2() - 2))
|
||||||
|
return false;
|
||||||
|
p = lhs.div(coeff);
|
||||||
|
SASSERT(p.is_var());
|
||||||
|
info.bits = 1;
|
||||||
|
info.length = 2;
|
||||||
|
info.positive = true; // this is a conjunction
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
} // namespace polysat
|
41
src/math/polysat/fixed_bits.h
Normal file
41
src/math/polysat/fixed_bits.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*++
|
||||||
|
Copyright (c) 2022 Microsoft Corporation
|
||||||
|
|
||||||
|
Module Name:
|
||||||
|
|
||||||
|
Extract fixed bits from (univariate) constraints
|
||||||
|
|
||||||
|
Author:
|
||||||
|
|
||||||
|
Jakob Rath, Nikolaj Bjorner (nbjorner), Clemens Eisenhofer 2022-08-22
|
||||||
|
|
||||||
|
--*/
|
||||||
|
#pragma once
|
||||||
|
#include "math/polysat/types.h"
|
||||||
|
#include "math/polysat/constraint.h"
|
||||||
|
#include "util/vector.h"
|
||||||
|
|
||||||
|
namespace polysat {
|
||||||
|
|
||||||
|
struct fixed_bits {
|
||||||
|
unsigned hi = 0;
|
||||||
|
unsigned lo = 0;
|
||||||
|
rational value;
|
||||||
|
|
||||||
|
/// The constraint is equivalent to setting fixed bits on a variable.
|
||||||
|
// bool is_equivalent;
|
||||||
|
|
||||||
|
fixed_bits() = default;
|
||||||
|
fixed_bits(unsigned hi, unsigned lo, rational value): hi(hi), lo(lo), value(value) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
using fixed_bits_vector = vector<fixed_bits>;
|
||||||
|
|
||||||
|
bool get_eq_fixed_lsb(pdd const& p, fixed_bits& out);
|
||||||
|
bool get_eq_fixed_bits(pdd const& p, fixed_bits& out);
|
||||||
|
|
||||||
|
bool get_ule_fixed_lsb(pdd const& lhs, pdd const& rhs, bool is_positive, fixed_bits& out);
|
||||||
|
bool get_ule_fixed_bits(pdd const& lhs, pdd const& rhs, bool is_positive, fixed_bits& out);
|
||||||
|
bool get_fixed_bits(signed_constraint c, fixed_bits& out);
|
||||||
|
|
||||||
|
}
|
|
@ -506,7 +506,9 @@ namespace polysat {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool slicing::try_get_value(enode* s, rational& val) const {
|
bool slicing::try_get_value(enode* s, rational& val) const {
|
||||||
return m_bv->is_numeral(s->get_expr(), val);
|
bool const ok = m_bv->is_numeral(s->get_expr(), val);
|
||||||
|
SASSERT_EQ(ok, is_value(s));
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void slicing::explain_class(enode* x, enode* y, ptr_vector<void>& out_deps) {
|
void slicing::explain_class(enode* x, enode* y, ptr_vector<void>& out_deps) {
|
||||||
|
@ -1115,23 +1117,55 @@ namespace polysat {
|
||||||
void slicing::add_constraint(signed_constraint c) {
|
void slicing::add_constraint(signed_constraint c) {
|
||||||
LOG(c);
|
LOG(c);
|
||||||
SASSERT(!is_conflict());
|
SASSERT(!is_conflict());
|
||||||
if (!c->is_eq())
|
#if 0
|
||||||
|
if (!add_fixed_bits(c))
|
||||||
return;
|
return;
|
||||||
pdd const& p = c->to_eq();
|
#endif
|
||||||
|
if (c->is_eq())
|
||||||
|
add_constraint_eq(c->to_eq(), c.blit());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool slicing::add_fixed_bits(signed_constraint c) {
|
||||||
|
// TODO: what is missing here:
|
||||||
|
// - we don't prioritize constraints that set larger bit ranges
|
||||||
|
// e.g., c1 sets 3 lower bits, and c2 sets 5 lower bits.
|
||||||
|
// slicing may have both {c1,c2} in justifications while previously we always prefer c2.
|
||||||
|
// - (we could wait until propagate() to add fixed bits to the egraph. but that would only work on a single decision level.)
|
||||||
|
if (c->vars().size() != 1)
|
||||||
|
return true;
|
||||||
|
fixed_bits fb;
|
||||||
|
if (!get_fixed_bits(c, fb))
|
||||||
|
return true;
|
||||||
|
pvar const x = c->vars()[0];
|
||||||
|
return add_fixed_bits(x, fb.hi, fb.lo, fb.value, c.blit());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool slicing::add_fixed_bits(pvar x, unsigned hi, unsigned lo, rational const& value, sat::literal lit) {
|
||||||
|
enode_vector& xs = m_tmp3;
|
||||||
|
SASSERT(xs.empty());
|
||||||
|
mk_slice(var2slice(x), hi, lo, xs, false, false);
|
||||||
|
enode* const sval = mk_value_slice(value, hi - lo + 1);
|
||||||
|
// 'xs' will be cleared by 'merge'.
|
||||||
|
// NOTE: the 'nullptr' argument will be fixed by 'egraph_merge'
|
||||||
|
return merge(xs, sval, mk_var_dep(x, nullptr, lit));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool slicing::add_constraint_eq(pdd const& p, sat::literal lit) {
|
||||||
auto& m = p.manager();
|
auto& m = p.manager();
|
||||||
for (auto& [a, x] : p.linear_monomials()) {
|
for (auto& [a, x] : p.linear_monomials()) {
|
||||||
if (a != 1 && a != m.max_value())
|
if (a != 1 && a != m.max_value())
|
||||||
continue;
|
continue;
|
||||||
pdd const body = a.is_one() ? (m.mk_var(x) - p) : (m.mk_var(x) + p);
|
pdd const body = a.is_one() ? (m.mk_var(x) - p) : (m.mk_var(x) + p);
|
||||||
// c is either x = body or x != body, depending on polarity
|
// c is either x = body or x != body, depending on polarity
|
||||||
if (!add_equation(x, body, c.blit())) {
|
if (!add_equation(x, body, lit)) {
|
||||||
SASSERT(is_conflict());
|
SASSERT(is_conflict());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
// without this check, when p = x - y we would handle both x = y and y = x separately
|
// without this check, when p = x - y we would handle both x = y and y = x separately
|
||||||
if (body.is_unary())
|
if (body.is_unary())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool slicing::add_equation(pvar x, pdd const& body, sat::literal lit) {
|
bool slicing::add_equation(pvar x, pdd const& body, sat::literal lit) {
|
||||||
|
@ -1288,28 +1322,29 @@ namespace polysat {
|
||||||
SASSERT(all_of(m_egraph.nodes(), [](enode* n) { return !n->is_marked1(); }));
|
SASSERT(all_of(m_egraph.nodes(), [](enode* n) { return !n->is_marked1(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
void slicing::collect_fixed(pvar v, rational& mask, rational& value) {
|
void slicing::collect_fixed(pvar v, fixed_bits_vector& out, euf::enode_pair_vector& out_just) {
|
||||||
enode_vector& base = m_tmp2;
|
enode_vector& base = m_tmp2;
|
||||||
SASSERT(base.empty());
|
SASSERT(base.empty());
|
||||||
get_base(var2slice(v), base);
|
get_base(var2slice(v), base);
|
||||||
mask = 0;
|
|
||||||
value = 0;
|
|
||||||
rational a;
|
rational a;
|
||||||
unsigned lo = 0;
|
unsigned lo = 0;
|
||||||
for (auto it = base.rbegin(); it != base.rend(); ++it) {
|
for (enode* n : base) {
|
||||||
enode* n = *it;
|
|
||||||
enode* r = n->get_root();
|
enode* r = n->get_root();
|
||||||
unsigned const w = width(n);
|
unsigned const w = width(n);
|
||||||
|
unsigned const hi = lo + w - 1;
|
||||||
if (try_get_value(r, a)) {
|
if (try_get_value(r, a)) {
|
||||||
rational const factor = rational::power_of_two(lo);
|
out.push_back({hi, lo, a});
|
||||||
// TODO: probably better to return vector of {w, lo, a} instead
|
out_just.push_back({n, r});
|
||||||
mask += (rational::power_of_two(w) - 1) * factor;
|
|
||||||
value += a * factor;
|
|
||||||
}
|
}
|
||||||
lo += w;
|
lo += w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void slicing::explain_fixed(euf::enode_pair const& just, std::function<void(sat::literal)> const& on_lit, std::function<void(pvar)> const& on_var) {
|
||||||
|
auto [n, r] = just;
|
||||||
|
NOT_IMPLEMENTED_YET(); // TODO: like explain_value
|
||||||
|
}
|
||||||
|
|
||||||
std::ostream& slicing::display(std::ostream& out) const {
|
std::ostream& slicing::display(std::ostream& out) const {
|
||||||
enode_vector base;
|
enode_vector base;
|
||||||
for (pvar v = 0; v < m_var2slice.size(); ++v) {
|
for (pvar v = 0; v < m_var2slice.size(); ++v) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ Notation:
|
||||||
#include "ast/bv_decl_plugin.h"
|
#include "ast/bv_decl_plugin.h"
|
||||||
#include "math/polysat/types.h"
|
#include "math/polysat/types.h"
|
||||||
#include "math/polysat/constraint.h"
|
#include "math/polysat/constraint.h"
|
||||||
|
#include "math/polysat/fixed_bits.h"
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
namespace polysat {
|
namespace polysat {
|
||||||
|
@ -269,8 +270,11 @@ namespace polysat {
|
||||||
pvar mk_concat(unsigned num_args, pvar const* args, pvar replay_var);
|
pvar mk_concat(unsigned num_args, pvar const* args, pvar replay_var);
|
||||||
void replay_concat(unsigned num_args, pvar const* args, pvar r);
|
void replay_concat(unsigned num_args, pvar const* args, pvar r);
|
||||||
|
|
||||||
|
bool add_constraint_eq(pdd const& p, sat::literal lit);
|
||||||
bool add_equation(pvar x, pdd const& body, sat::literal lit);
|
bool add_equation(pvar x, pdd const& body, sat::literal lit);
|
||||||
bool add_value(pvar v, rational const& value, sat::literal lit);
|
bool add_value(pvar v, rational const& value, sat::literal lit);
|
||||||
|
bool add_fixed_bits(signed_constraint c);
|
||||||
|
bool add_fixed_bits(pvar x, unsigned hi, unsigned lo, rational const& value, sat::literal lit);
|
||||||
|
|
||||||
bool invariant() const;
|
bool invariant() const;
|
||||||
bool invariant_needs_congruence() const;
|
bool invariant_needs_congruence() const;
|
||||||
|
@ -335,7 +339,8 @@ namespace polysat {
|
||||||
void collect_simple_overlaps(pvar v, pvar_vector& out);
|
void collect_simple_overlaps(pvar v, pvar_vector& out);
|
||||||
|
|
||||||
/** Collect fixed portions of the variable v */
|
/** Collect fixed portions of the variable v */
|
||||||
void collect_fixed(pvar v, rational& mask, rational& value);
|
void collect_fixed(pvar v, fixed_bits_vector& out, euf::enode_pair_vector& out_just);
|
||||||
|
void explain_fixed(euf::enode_pair const& just, std::function<void(sat::literal)> const& on_lit, std::function<void(pvar)> const& on_var);
|
||||||
|
|
||||||
std::ostream& display(std::ostream& out) const;
|
std::ostream& display(std::ostream& out) const;
|
||||||
std::ostream& display_tree(std::ostream& out) const;
|
std::ostream& display_tree(std::ostream& out) const;
|
||||||
|
|
|
@ -976,14 +976,17 @@ namespace {
|
||||||
#if 0
|
#if 0
|
||||||
// TODO: wip
|
// TODO: wip
|
||||||
fixed_bits_vector fbs;
|
fixed_bits_vector fbs;
|
||||||
s.m_slicing.collect_fixed(v, fbs);
|
euf::enode_pair_vector fbs_just;
|
||||||
|
s.m_slicing.collect_fixed(v, fbs, fbs_just);
|
||||||
|
|
||||||
for (fixed_bits const& fb : fbs) {
|
for (unsigned idx = fbs.size(); idx-- > 0; ) {
|
||||||
|
fixed_bits const& fb = fbs[idx];
|
||||||
|
euf::enode_pair const& just = fbs_just[idx];
|
||||||
for (unsigned i = fb.lo; i <= fb.hi; ++i) {
|
for (unsigned i = fb.lo; i <= fb.hi; ++i) {
|
||||||
SASSERT(out_fbi.just_src[i].empty()); // since we don't get overlapping ranges from collect_fixed.
|
SASSERT(out_fbi.just_src[i].empty()); // since we don't get overlapping ranges from collect_fixed.
|
||||||
SASSERT(out_fbi.just_side_cond[i].empty());
|
SASSERT(out_fbi.just_side_cond[i].empty());
|
||||||
out_fbi.fixed[i] = to_lbool(fb.value.get_bit(i - fb.lo));
|
out_fbi.fixed[i] = to_lbool(fb.value.get_bit(i - fb.lo));
|
||||||
// TODO: out_fbi.just_src[i].push_back(
|
// TODO: s.m_slicing.explain_fixed( ... ); with out_fbi.just_src[i].push_back(...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue