3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-22 16:45:31 +00:00

Variant of variable elimination

This commit is contained in:
Clemens Eisenhofer 2023-01-02 20:05:13 +01:00
parent 1c7ac12af8
commit 0301686856
3 changed files with 91 additions and 27 deletions

View file

@ -870,19 +870,24 @@ namespace polysat {
if (s.try_eval(p, val))
return val == 0 ? N : val.trailing_zeros();
if (!p.is_var() && p.is_monomial()) {
// it's just a product => sum them up
dd::pdd_monomial monomial = *p.begin();
unsigned parity_sum = monomial.coeff.trailing_zeros();
for (pvar c : monomial.vars)
parity_sum += min_parity(m.mk_var(c));
return std::min(N, parity_sum);
unsigned min = 0;
if (!p.is_var()) {
// parity of a product => sum of parities
// parity of sum => minimum of monomial's minimal parities
min = UINT32_MAX;
for (const auto& monomial : p) {
unsigned parity_sum = monomial.coeff.trailing_zeros();
for (pvar c : monomial.vars)
parity_sum += min_parity(m.mk_var(c));
min = std::min(min, parity_sum);
}
}
SASSERT(min <= N);
for (unsigned j = N; j > 0; --j)
for (unsigned j = N; j > min; --j)
if (is_forced_true(s.parity_at_least(p, j)))
return j;
return 0;
return min;
}
unsigned saturation::max_parity(pdd const& p) {
@ -892,19 +897,21 @@ namespace polysat {
if (s.try_eval(p, val))
return val == 0 ? N : val.trailing_zeros();
unsigned max = N;
if (!p.is_var() && p.is_monomial()) {
// it's just a product => sum them up
// the case of a sum is harder as the lower bound (because of carry bits)
// ==> restricted for now to monomials
dd::pdd_monomial monomial = *p.begin();
unsigned parity_sum = monomial.coeff.trailing_zeros();
max = monomial.coeff.trailing_zeros();
for (pvar c : monomial.vars)
parity_sum += max_parity(m.mk_var(c));
return std::min(N, parity_sum);
max += max_parity(m.mk_var(c));
}
for (unsigned j = 0; j < N; ++j)
for (unsigned j = 0; j < max; ++j)
if (is_forced_true(s.parity_at_most(p, j)))
return j;
return N;
return max;
}
bool saturation::try_parity(pvar x, conflict& core, inequality const& axb_l_y) {
@ -1220,7 +1227,7 @@ namespace polysat {
m_lemma.reset();
m_lemma.insert(~c);
m_lemma.insert_eval(~s.eq(y));
for (auto& sc_lhs : side_condition_lhs) // TODO: Do we really need the path as a side-condition in case of parity elimination?
for (auto& sc_lhs : side_condition_lhs) // the "path to get the parities"
m_lemma.insert(sc_lhs);
for (auto& sc_rhs : side_condition_rhs)
m_lemma.insert(sc_rhs);

View file

@ -537,6 +537,32 @@ namespace polysat {
return id;
}
pdd parity_tracker::get_pseudo_inverse(const pdd &p, unsigned parity) {
SASSERT(parity < p.power_of_2()); // parity == p.power_of_two() does not make sense. It would mean a * a' = 0
LOG("Getting pseudo-inverse of " << p << " for parity " << parity);
if (p.is_val())
return p.manager().mk_val(get_pseudo_inverse_val(p.val(), parity, p.power_of_2()));
unsigned v = get_id(p);
if (m_pseudo_inverse.size() <= v)
m_pseudo_inverse.setx(v, unsigned_vector(p.power_of_2(), -1), {});
if (m_pseudo_inverse[v].size() <= parity)
m_pseudo_inverse[v].reserve(p.power_of_2(), -1);
pvar p_inv = m_pseudo_inverse[v][parity];
if (p_inv == -1) {
p_inv = s.add_var(p.power_of_2());
m_pseudo_inverse[v][parity] = p_inv;
// NB: Strictly speaking this condition does not say that "p_inv" is the pseudo-inverse.
// However, the variable elimination lemma stays valid even if "p_inv" is not really the pseudo-inverse anymore (e.g., because of parity was reduced)
s.add_clause(~s.parity_at_most(p, parity), s.eq(p * s.var(p_inv), rational::power_of_two(parity)), true); // TODO: Depends on the definition of redundancy
verbose_stream() << "Pseudo-Inverse v" << p_inv << " of " << p << " introduced\n";
}
return s.var(p_inv);
}
pdd parity_tracker::get_inverse(const pdd &p) {
LOG("Getting inverse of " << p);
if (p.is_val()) {
@ -550,7 +576,8 @@ namespace polysat {
return s.var(m_inverse[v]);
pvar inv = s.add_var(p.power_of_2());
pdd inv_pdd = p.manager().mk_var(inv);
verbose_stream() << "Inverse v" << inv << " of " << p << " introduced\n";
pdd inv_pdd = s.var(inv);
m_inverse.setx(v, inv, -1);
s.add_clause(s.eq(inv_pdd * p, p.manager().one()), false);
return inv_pdd;
@ -576,6 +603,7 @@ namespace polysat {
}
else {
odd_v = s.add_var(p.power_of_2());
verbose_stream() << "Odd part v" << odd_v << " of " << p << " introduced\n";
m_odd.setx(v, optional<std::pair<pvar, bool_vector>>({ odd_v, bool_vector(p.power_of_2(), false) }), optional<std::pair<pvar, bool_vector>>::undef());
}
@ -593,12 +621,14 @@ namespace polysat {
path.push_back(~c);
if (needs_propagate)
m_builder.insert(~c);
verbose_stream() << "Side-condition: " << ~c << "\n";
}
else {
upper = middle;
path.push_back(c);
if (needs_propagate)
m_builder.insert(c);
verbose_stream() << "Side-condition: " << c << "\n";
}
LOG("Its in [" << lower << "; " << upper << ")");
}
@ -638,29 +668,47 @@ namespace polysat {
if (is_multiple == l_true) // we multiply with a factor to make them equal
return { b1 - b * mul_fac, true, {} };
#if 1
#if 1
return { p, false, {} };
#else
#else
if (!a.is_monomial() || !a1.is_monomial())
return { p , false, {} };
if (!a1.is_var() && !a1.is_val()) {
// TODO: Compromise: Maybe only monomials...? Does this make sense?
//return { p, false, {} };
LOG("Warning: Inverting " << a1 << " although it is not a single variable - might not be a good idea"); // TODO: Compromise: Maybe only monomials...?
LOG("Warning: Inverting " << a1 << " although it is not a single variable - might not be a good idea");
}
if (!a.is_var() && !a.is_val()) {
//return { p, false, {} };
LOG("Warning: Inverting " << a << " although it is not a single variable - might not be a good idea");
}
if (!a.is_monomial() || !a1.is_monomial())
return { p , false, {} };
// We don't know whether it will work. Use the parity of the assignment
#if 1
unsigned a_parity;
if ((a_parity = saturation.min_parity(a)) != saturation.max_parity(a) || saturation.min_parity(a1) < a_parity)
return { p, false, {} }; // We need the parity of a and this has to be for sure less than the parity of a1
svector<signed_constraint> precondition;
pdd a_pi = get_pseudo_inverse(a, a_parity);
if (a_parity > 0) {
pdd shift = s.lshr(a1, a1.manager().mk_val(a_parity));
precondition.push_back(s.eq(rational::power_of_two(a_parity) * shift, a1)); // TODO: Or s.parity_at_least(a1, a_parity) but we want to reuse the variable introduced by the shift
return { a_pi * (-b) * shift + b1, true, {std::move(precondition)} };
}
// Special case: If it is already odd we can directly use the pseudo inverse (as it is the inverse in this case!)
return { a_pi * (-b) * a + b1, true, {std::move(precondition)} };
#else
unsigned a_parity;
unsigned a1_parity;
if ((a_parity = saturation.min_parity(a)) != saturation.max_parity(a) || (a1_parity = saturation.min_parity(a1)) != saturation.max_parity(a1))
return { p, false, {} }; // We need the parity, but we failed to get it precisely
if (a_parity > a1_parity) {
SASSERT(false); // get_multiple should have excluded this case already
return { p, false, {} };
@ -673,9 +721,10 @@ namespace polysat {
pdd inv_odd_a = get_inverse(odd_a);
LOG("Forced elimination: " << odd_a1 * inv_odd_a * rational::power_of_two(a1_parity - a_parity) * b + b1);
verbose_stream() << "Forced elimination: " << odd_a1 * inv_odd_a * rational::power_of_two(a1_parity - a_parity) * b + b1 << "\n";
verbose_stream() << "From: " << "eliminated v" << x << " with a = " << a << "; b = " << b << "; p = " << p << "\n";
return { odd_a1 * inv_odd_a * rational::power_of_two(a1_parity - a_parity) * b + b1, true, {std::move(precondition)} };
verbose_stream() << "Forced elimination: " << odd_a1 * inv_odd_a * rational::power_of_two(a1_parity - a_parity) * (-b) + b1 << "\n";
verbose_stream() << "From: " << "eliminated v" << x << " with a = " << a << "; -b = " << -b << "; p = " << p << "\n";
return { odd_a1 * inv_odd_a * rational::power_of_two(a1_parity - a_parity) * (-b) + b1, true, {std::move(precondition)} };
#endif
#endif
}
}

View file

@ -62,6 +62,7 @@ namespace polysat {
vector<optional<std::pair<pvar, bool_vector>>> m_odd;
unsigned_vector m_inverse;
vector<unsigned_vector> m_pseudo_inverse;
struct optional_pdd_hash {
unsigned operator()(optional<pdd> const& args) const {
@ -72,12 +73,19 @@ namespace polysat {
pdd_to_id m_pdd_to_id; // if we want to use arbitrary pdds instead of pvars
rational get_pseudo_inverse_val(const rational &p, unsigned parity, unsigned power_of_two) {
rational iv;
VERIFY((p / rational::power_of_two(parity)).mult_inverse(power_of_two - parity, iv));
return iv;
}
unsigned get_id(const pdd& p);
public:
parity_tracker(solver& s) : s(s), m_builder(s) {}
pdd get_pseudo_inverse(const pdd& p, unsigned parity);
pdd get_inverse(const pdd& p);
pdd get_odd(const pdd& p, unsigned parity, svector<signed_constraint>& pat);