mirror of
https://github.com/Z3Prover/z3
synced 2025-06-19 04:13:38 +00:00
First try to generalize variable elimination
This commit is contained in:
parent
adc9f7abe4
commit
98d572b48b
9 changed files with 218 additions and 31 deletions
|
@ -277,10 +277,12 @@ namespace polysat {
|
||||||
auto& m = a.manager();
|
auto& m = a.manager();
|
||||||
unsigned sz = m.power_of_2();
|
unsigned sz = m.power_of_2();
|
||||||
if (a.is_val() && b.is_val()) {
|
if (a.is_val() && b.is_val()) {
|
||||||
// TODO: just evaluate?
|
rational r;
|
||||||
|
rational q = machine_div_rem(a.val(), b.val(), r);
|
||||||
|
return { m.mk_val(r), m.mk_val(q) };
|
||||||
}
|
}
|
||||||
|
|
||||||
constraint_dedup::quot_rem_args args({a, b});
|
constraint_dedup::quot_rem_args args({ a, b });
|
||||||
auto it = m_dedup.quot_rem_expr.find_iterator(args);
|
auto it = m_dedup.quot_rem_expr.find_iterator(args);
|
||||||
if (it != m_dedup.quot_rem_expr.end())
|
if (it != m_dedup.quot_rem_expr.end())
|
||||||
return { m.mk_var(it->m_value.first), m.mk_var(it->m_value.second) };
|
return { m.mk_var(it->m_value.first), m.mk_var(it->m_value.second) };
|
||||||
|
@ -297,13 +299,13 @@ namespace polysat {
|
||||||
// b = 0 ==> q = -1
|
// b = 0 ==> q = -1
|
||||||
s.add_eq(a, b * q + r);
|
s.add_eq(a, b * q + r);
|
||||||
s.add_umul_noovfl(b, q);
|
s.add_umul_noovfl(b, q);
|
||||||
s.add_ule(r, b*q+r);
|
s.add_ule(r, b * q + r);
|
||||||
|
|
||||||
auto c_eq = eq(b);
|
auto c_eq = eq(b);
|
||||||
s.add_clause(c_eq, ult(r, b), false);
|
s.add_clause(c_eq, ult(r, b), false);
|
||||||
s.add_clause(~c_eq, eq(q + 1), false);
|
s.add_clause(~c_eq, eq(q + 1), false);
|
||||||
|
|
||||||
return {q, r};
|
return { q, r };
|
||||||
}
|
}
|
||||||
|
|
||||||
pdd constraint_manager::bnot(pdd const& p) {
|
pdd constraint_manager::bnot(pdd const& p) {
|
||||||
|
|
|
@ -81,7 +81,7 @@ namespace polysat {
|
||||||
|
|
||||||
void ensure_bvar(constraint* c);
|
void ensure_bvar(constraint* c);
|
||||||
void erase_bvar(constraint* c);
|
void erase_bvar(constraint* c);
|
||||||
|
|
||||||
signed_constraint mk_op_constraint(op_constraint::code op, pdd const& p, pdd const& q, pdd const& r);
|
signed_constraint mk_op_constraint(op_constraint::code op, pdd const& p, pdd const& q, pdd const& r);
|
||||||
pdd mk_op_term(op_constraint::code op, pdd const& p, pdd const& q);
|
pdd mk_op_term(op_constraint::code op, pdd const& p, pdd const& q);
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,10 @@ void set_log_enabled(bool log_enabled) {
|
||||||
g_log_enabled = log_enabled;
|
g_log_enabled = log_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get_log_enabled() {
|
||||||
|
return g_log_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
static LogLevel get_max_log_level(std::string const& fn, std::string const& pretty_fn) {
|
static LogLevel get_max_log_level(std::string const& fn, std::string const& pretty_fn) {
|
||||||
(void)fn;
|
(void)fn;
|
||||||
(void)pretty_fn;
|
(void)pretty_fn;
|
||||||
|
|
|
@ -27,6 +27,7 @@ char const* color_reset();
|
||||||
#if POLYSAT_LOGGING_ENABLED
|
#if POLYSAT_LOGGING_ENABLED
|
||||||
|
|
||||||
void set_log_enabled(bool log_enabled);
|
void set_log_enabled(bool log_enabled);
|
||||||
|
bool get_log_enabled();
|
||||||
|
|
||||||
class polysat_log_indent
|
class polysat_log_indent
|
||||||
{
|
{
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace polysat {
|
||||||
|
|
||||||
void narrow_shl(solver& s);
|
void narrow_shl(solver& s);
|
||||||
static lbool eval_shl(pdd const& p, pdd const& q, pdd const& r);
|
static lbool eval_shl(pdd const& p, pdd const& q, pdd const& r);
|
||||||
|
|
||||||
void narrow_and(solver& s);
|
void narrow_and(solver& s);
|
||||||
static lbool eval_and(pdd const& p, pdd const& q, pdd const& r);
|
static lbool eval_and(pdd const& p, pdd const& q, pdd const& r);
|
||||||
|
|
||||||
|
|
|
@ -1042,6 +1042,11 @@ namespace polysat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void solver::add_clause(signed_constraint c, bool is_redundant) {
|
||||||
|
signed_constraint cs[1] = { c };
|
||||||
|
add_clause(1, cs, is_redundant);
|
||||||
|
}
|
||||||
|
|
||||||
void solver::add_clause(signed_constraint c1, signed_constraint c2, bool is_redundant) {
|
void solver::add_clause(signed_constraint c1, signed_constraint c2, bool is_redundant) {
|
||||||
signed_constraint cs[2] = { c1, c2 };
|
signed_constraint cs[2] = { c1, c2 };
|
||||||
add_clause(2, cs, is_redundant);
|
add_clause(2, cs, is_redundant);
|
||||||
|
|
|
@ -224,6 +224,7 @@ namespace polysat {
|
||||||
void learn_lemma(clause& lemma);
|
void learn_lemma(clause& lemma);
|
||||||
void backjump(unsigned new_level);
|
void backjump(unsigned new_level);
|
||||||
void add_clause(clause& clause);
|
void add_clause(clause& clause);
|
||||||
|
void add_clause(signed_constraint c, bool is_redundant);
|
||||||
void add_clause(signed_constraint c1, signed_constraint c2, bool is_redundant);
|
void add_clause(signed_constraint c1, signed_constraint c2, bool is_redundant);
|
||||||
void add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, bool is_redundant);
|
void add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, bool is_redundant);
|
||||||
void add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, bool is_redundant);
|
void add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, bool is_redundant);
|
||||||
|
@ -309,7 +310,7 @@ namespace polysat {
|
||||||
|
|
||||||
/** Create expression for the logical left shift of p by q. */
|
/** Create expression for the logical left shift of p by q. */
|
||||||
pdd shl(pdd const& p, pdd const& q) { return m_constraints.shl(p, q); }
|
pdd shl(pdd const& p, pdd const& q) { return m_constraints.shl(p, q); }
|
||||||
|
|
||||||
/** Create expression for the bit-wise negation of p. */
|
/** Create expression for the bit-wise negation of p. */
|
||||||
pdd bnot(pdd const& p) { return m_constraints.bnot(p); }
|
pdd bnot(pdd const& p) { return m_constraints.bnot(p); }
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,114 @@ Author:
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace polysat {
|
namespace polysat {
|
||||||
|
|
||||||
|
pdd free_variable_elimination::get_odd(pdd p) {
|
||||||
|
SASSERT(p.is_val() || p.is_var()); // For now
|
||||||
|
|
||||||
|
if (p.is_val()) {
|
||||||
|
const rational& v = p.val();
|
||||||
|
unsigned d = v.trailing_zeros();
|
||||||
|
if (!d)
|
||||||
|
return s.sz2pdd(p.power_of_2()).mk_val(v);
|
||||||
|
return s.sz2pdd(p.power_of_2()).mk_val(div(v, rational::power_of_two(d))); // TODO: Is there no shift?
|
||||||
|
}
|
||||||
|
pvar v = p.var();
|
||||||
|
if (m_rest_constants.size() > v && m_rest_constants[v] != -1)
|
||||||
|
return s.var(m_rest_constants[v]);
|
||||||
|
|
||||||
|
get_dyadic_valuation(p);
|
||||||
|
|
||||||
|
pvar rest = s.add_var(p.power_of_2());
|
||||||
|
m_rest_constants.setx(v, rest, -1);
|
||||||
|
s.add_clause(s.eq(s.var(m_pv_power_constants[v]) * s.var(rest), p), false);
|
||||||
|
return s.var(rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<pdd> free_variable_elimination::get_inverse(pdd p) {
|
||||||
|
SASSERT(p.is_val() || p.is_var()); // For now
|
||||||
|
|
||||||
|
if (p.is_val()) {
|
||||||
|
pdd i = s.sz2pdd(p.power_of_2()).zero();
|
||||||
|
if (!inv(p, i))
|
||||||
|
return {};
|
||||||
|
return optional<pdd>(i);
|
||||||
|
}
|
||||||
|
pvar v = p.var();
|
||||||
|
if (m_inverse_constants.size() > v && m_inverse_constants[v] != -1)
|
||||||
|
return optional<pdd>(s.var(m_inverse_constants[v]));
|
||||||
|
|
||||||
|
pvar inv = s.add_var(s.var(v).power_of_2());
|
||||||
|
m_inverse_constants.setx(v, inv, -1);
|
||||||
|
s.add_clause(s.eq(s.var(inv) * s.var(v), s.sz2pdd(s.var(v).power_of_2()).one()), false);
|
||||||
|
return optional<pdd>(s.var(inv));
|
||||||
|
}
|
||||||
|
|
||||||
|
// symbolic version of "max_pow2_divisor" for checking if it is exactly "k"
|
||||||
|
void free_variable_elimination::add_dyadic_valuation(pvar v, unsigned k) {
|
||||||
|
pvar pv;
|
||||||
|
pvar pv2;
|
||||||
|
if (m_pv_constants.size() <= v || m_pv_constants[v] == -1) {
|
||||||
|
pv = s.add_var(s.var(v).power_of_2()); // TODO: What's a good value? Unfortunately we cannot use a integer
|
||||||
|
pv2 = s.add_var(s.var(v).power_of_2());
|
||||||
|
m_pv_constants.setx(v, pv, -1);
|
||||||
|
m_pv_power_constants.setx(v, pv2, -1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pv = m_pv_constants[v];
|
||||||
|
pv2 = m_pv_power_constants[v];
|
||||||
|
}
|
||||||
|
|
||||||
|
// (pv = k && pv2 = 2^k) <==> ((v & (2^(k + 1) - 1)) = 2^k)
|
||||||
|
|
||||||
|
rational mask = rational::power_of_two(k + 1) - 1;
|
||||||
|
pdd masked = s.band(s.var(v), s.sz2pdd(s.var(v).power_of_2()).mk_val(mask));
|
||||||
|
std::pair<pdd, pdd> odd_part = s.quot_rem(s.var(v), s.var(pv2));
|
||||||
|
|
||||||
|
signed_constraint c1 = s.eq(s.var(pv), k);
|
||||||
|
signed_constraint c2 = s.eq(s.var(pv2), rational::power_of_two(k));
|
||||||
|
signed_constraint c3 = s.eq(masked, rational::power_of_two(k));
|
||||||
|
bool e = get_log_enabled();
|
||||||
|
set_log_enabled(false);
|
||||||
|
s.add_clause(c1, ~c3, false);
|
||||||
|
s.add_clause(c2, ~c3, false);
|
||||||
|
s.add_clause(~c1, ~c2, c3, false);
|
||||||
|
|
||||||
|
s.add_clause(s.eq(odd_part.second, 0), false); // The division has to be exact
|
||||||
|
set_log_enabled(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<pdd, pdd> free_variable_elimination::get_dyadic_valuation(pdd p, unsigned short lower, unsigned short upper) {
|
||||||
|
SASSERT(p.is_val() || p.is_var()); // For now
|
||||||
|
|
||||||
|
if (p.is_val()) {
|
||||||
|
rational pv(p.val().trailing_zeros());
|
||||||
|
rational pv2 = rational::power_of_two(p.val().trailing_zeros());
|
||||||
|
return { s.sz2pdd(p.power_of_2()).mk_val(pv), s.sz2pdd(p.power_of_2()).mk_val(pv2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pvar v = p.var();
|
||||||
|
unsigned short prev_lower = 0, prev_upper = 0;
|
||||||
|
if (m_has_validation_of_range.size() > v) {
|
||||||
|
unsigned range = m_has_validation_of_range[v];
|
||||||
|
prev_lower = range & 0xFFFF;
|
||||||
|
prev_upper = range >> 16;
|
||||||
|
if (lower >= prev_lower && upper <= prev_upper)
|
||||||
|
return { s.var(m_pv_constants[v]), s.var(m_pv_power_constants[v]) }; // exists already in the required range
|
||||||
|
}
|
||||||
|
LOG("Adding valuation function for variable " << v << " in [" << lower << "; " << upper << ")");
|
||||||
|
m_has_validation_of_range.setx(v, lower | (unsigned)upper << 16, 0);
|
||||||
|
for (unsigned i = lower; i < prev_lower; i++) {
|
||||||
|
add_dyadic_valuation(v, i);
|
||||||
|
}
|
||||||
|
for (unsigned i = prev_upper; i < upper; i++) {
|
||||||
|
add_dyadic_valuation(v, i);
|
||||||
|
}
|
||||||
|
return { s.var(m_pv_constants[v]), s.var(m_pv_power_constants[v]) };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<pdd, pdd> free_variable_elimination::get_dyadic_valuation(pdd p) {
|
||||||
|
return get_dyadic_valuation(p, 0, p.power_of_2());
|
||||||
|
}
|
||||||
|
|
||||||
void free_variable_elimination::find_lemma(conflict& core) {
|
void free_variable_elimination::find_lemma(conflict& core) {
|
||||||
LOG_H1("Free Variable Elimination");
|
LOG_H1("Free Variable Elimination");
|
||||||
|
@ -42,6 +150,27 @@ namespace polysat {
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_variable_elimination::find_lemma(pvar v, signed_constraint c, conflict& core) {
|
void free_variable_elimination::find_lemma(pvar v, signed_constraint c, conflict& core) {
|
||||||
|
vector<signed_constraint> to_check;
|
||||||
|
// Find another constraint where we want to substitute v
|
||||||
|
for (signed_constraint c_target : core) {
|
||||||
|
if (c == c_target)
|
||||||
|
continue;
|
||||||
|
if (c_target.vars().size() <= 1)
|
||||||
|
continue;
|
||||||
|
if (!c_target.contains_var(v))
|
||||||
|
continue;
|
||||||
|
// TODO: helper method constraint::subst(pvar v, pdd const& p)
|
||||||
|
// (or rather, add it on constraint_manager since we need to allocate/dedup the new constraint)
|
||||||
|
// For now, just restrict to ule_constraint.
|
||||||
|
if (!c_target->is_ule()) // TODO: Remove?
|
||||||
|
continue;
|
||||||
|
// TODO: Eliminate without inversion? 2x = y && 2x <= z
|
||||||
|
|
||||||
|
to_check.push_back(c_target);
|
||||||
|
}
|
||||||
|
if (to_check.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
LOG_H3("Free Variable Elimination for v" << v << " using equation " << c);
|
LOG_H3("Free Variable Elimination for v" << v << " using equation " << c);
|
||||||
pdd const& p = c.eq();
|
pdd const& p = c.eq();
|
||||||
SASSERT_EQ(p.degree(v), 1);
|
SASSERT_EQ(p.degree(v), 1);
|
||||||
|
@ -60,39 +189,69 @@ namespace polysat {
|
||||||
LOG("lc: " << lc);
|
LOG("lc: " << lc);
|
||||||
LOG("rest: " << rest);
|
LOG("rest: " << rest);
|
||||||
|
|
||||||
assignment_t a;
|
//pdd rs = rest;
|
||||||
|
|
||||||
|
if (!lc.is_val() && lc.is_var())
|
||||||
|
// TODO: We could introduce a new variable "new_var = lc" and add the valuation for this new variable
|
||||||
|
return;
|
||||||
|
|
||||||
|
pdd coeff_odd = get_odd(lc); // a'
|
||||||
|
optional<pdd> lci = get_inverse(coeff_odd); // a'^-1
|
||||||
|
if (!lci)
|
||||||
|
return; // For sure not invertible
|
||||||
|
|
||||||
|
LOG("lci: " << lci);
|
||||||
|
|
||||||
|
/*assignment_t a;
|
||||||
pdd const lcs = eval(lc, core, a);
|
pdd const lcs = eval(lc, core, a);
|
||||||
LOG("lcs: " << lcs);
|
LOG("lcs: " << lcs);
|
||||||
pdd lci = m.zero();
|
pdd lci = m.zero();
|
||||||
if (!inv(lcs, lci))
|
if (!inv(lcs, lci))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
pdd lci = m.zero();
|
||||||
|
|
||||||
pdd const rs = s.subst(a, rest);
|
pdd const rs = s.subst(a, rest);
|
||||||
pdd const vs = -rs * lci; // this is the polynomial that computes v
|
*/
|
||||||
|
|
||||||
|
/*pdd const vs = -rs * lci; // this is the polynomial that computes v
|
||||||
LOG("vs: " << vs);
|
LOG("vs: " << vs);
|
||||||
SASSERT(!vs.free_vars().contains(v));
|
SASSERT(!vs.free_vars().contains(v));*/
|
||||||
|
|
||||||
// Find another constraint where we want to substitute v
|
// Find another constraint where we want to substitute v
|
||||||
for (signed_constraint c_target : core) {
|
for (signed_constraint c_target : to_check) {
|
||||||
if (c == c_target)
|
|
||||||
continue;
|
// Move the variable to eliminate to one side
|
||||||
if (c_target.vars().size() <= 1)
|
pdd fac = m.zero();
|
||||||
continue;
|
pdd fac2 = m.zero();
|
||||||
if (!c_target.contains_var(v))
|
pdd rest2 = m.zero();
|
||||||
continue;
|
pdd rest3 = m.zero();
|
||||||
|
c_target->to_ule().lhs().factor(v, 1, fac, rest2);
|
||||||
// TODO: helper method constraint::subst(pvar v, pdd const& p)
|
c_target->to_ule().rhs().factor(v, 1, fac2, rest3);
|
||||||
// (or rather, add it on constraint_manager since we need to allocate/dedup the new constraint)
|
|
||||||
// For now, just restrict to ule_constraint.
|
rest2 = rest3 - rest2;
|
||||||
if (!c_target->is_ule())
|
fac = fac - fac2;
|
||||||
continue;
|
|
||||||
// TODO: maybe apply assignment a here as well
|
LOG("c_target: " << lit_pp(s, c_target));
|
||||||
pdd const lhs = c_target->to_ule().lhs().subst_pdd(v, vs);
|
LOG("fac: " << fac);
|
||||||
pdd const rhs = c_target->to_ule().rhs().subst_pdd(v, vs);
|
LOG("rest2: " << rest2);
|
||||||
signed_constraint c_new = s.ule(lhs, rhs);
|
|
||||||
|
if (!fac.is_val() && !fac.is_var())
|
||||||
|
return; // TODO: Again, we might bind this to a variable
|
||||||
|
|
||||||
|
pdd pv2 = get_dyadic_valuation(fac).first;
|
||||||
|
pdd pv1 = get_dyadic_valuation(lc).first;
|
||||||
|
pdd odd_fac = get_odd(fac);
|
||||||
|
pdd power_diff = s.shl(m.one(), pv2 - pv1);
|
||||||
|
|
||||||
|
LOG("pv2: " << pv2);
|
||||||
|
LOG("pv1: " << pv1);
|
||||||
|
LOG("odd_fac: " << odd_fac);
|
||||||
|
LOG("power_diff: " << power_diff);
|
||||||
|
|
||||||
|
signed_constraint c_new = s.ule(-rest * lci * power_diff * odd_fac, rest2);
|
||||||
if (c_target.is_negative())
|
if (c_target.is_negative())
|
||||||
c_new.negate();
|
c_new.negate();
|
||||||
LOG("c_target: " << lit_pp(s, c_target));
|
|
||||||
LOG("c_new: " << lit_pp(s, c_new));
|
LOG("c_new: " << lit_pp(s, c_new));
|
||||||
|
|
||||||
// New constraint is already true (maybe we already derived it previously?)
|
// New constraint is already true (maybe we already derived it previously?)
|
||||||
|
@ -102,12 +261,15 @@ namespace polysat {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
clause_builder cb(s);
|
clause_builder cb(s);
|
||||||
for (auto [w, wv] : a)
|
/*for (auto [w, wv] : a)
|
||||||
cb.insert(~s.eq(s.var(w), wv));
|
cb.push(~s.eq(s.var(w), wv));*/
|
||||||
cb.insert(~c);
|
cb.insert(~c);
|
||||||
cb.insert(~c_target);
|
cb.insert(~c_target);
|
||||||
|
cb.insert(~s.ule(get_dyadic_valuation(lc).first, get_dyadic_valuation(fac).first));
|
||||||
cb.insert(c_new);
|
cb.insert(c_new);
|
||||||
core.add_lemma(cb.build());
|
ref<clause> c = cb.build();
|
||||||
|
if (c) // Can we get tautologies this way?
|
||||||
|
core.add_lemma(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,19 @@ namespace polysat {
|
||||||
class conflict;
|
class conflict;
|
||||||
|
|
||||||
class free_variable_elimination {
|
class free_variable_elimination {
|
||||||
|
|
||||||
solver& s;
|
solver& s;
|
||||||
|
unsigned_vector m_has_validation_of_range; // TODO: Find a better name
|
||||||
|
unsigned_vector m_pv_constants;
|
||||||
|
unsigned_vector m_pv_power_constants;
|
||||||
|
unsigned_vector m_inverse_constants;
|
||||||
|
unsigned_vector m_rest_constants;
|
||||||
|
|
||||||
|
pdd get_odd(pdd p); // add lemma "2^pv(v) * v' = v"
|
||||||
|
optional<pdd> get_inverse(pdd v); // add lemma "v' * v'^-1 = 1 (where v' is the odd part of v)"
|
||||||
|
void add_dyadic_valuation(pvar v, unsigned k); // add lemma "pv(v) = k" ==> "pv_v = k"
|
||||||
|
std::pair<pdd, pdd> get_dyadic_valuation(pdd p, unsigned short lower, unsigned short upper); // lower bound inclusive; upper exclusive
|
||||||
|
std::pair<pdd, pdd> get_dyadic_valuation(pdd p);
|
||||||
void find_lemma(pvar v, conflict& core);
|
void find_lemma(pvar v, conflict& core);
|
||||||
void find_lemma(pvar v, signed_constraint c, conflict& core);
|
void find_lemma(pvar v, signed_constraint c, conflict& core);
|
||||||
pdd eval(pdd const& p, conflict& core, assignment_t& out_assignment);
|
pdd eval(pdd const& p, conflict& core, assignment_t& out_assignment);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue