3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-05-02 21:37:02 +00:00
z3/src/math/polysat/saturation.cpp
Jakob Rath a81e05e660 We shouldn't assume that v is assigned
Happens if it is a viable conflict for v
2022-12-08 16:19:11 +01:00

851 lines
29 KiB
C++

/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
Polysat core saturation
Author:
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-6
TODO: preserve falsification
- each rule selects a certain premises that are problematic.
If the problematic premise is false under the current assignment, the newly inferred
literal should also be false in the assignment in order to preserve conflicts.
TODO: when we check that 'x' is "unary":
- in principle, 'x' could be any polynomial. However, we need to divide the lhs by x, and we don't have general polynomial division yet.
so for now we just allow the form 'value*variable'.
(extension to arbitrary monomials for 'x' should be fairly easy too)
--*/
#include "math/polysat/saturation.h"
#include "math/polysat/solver.h"
#include "math/polysat/log.h"
#include "math/polysat/umul_ovfl_constraint.h"
namespace polysat {
saturation::saturation(solver& s) : s(s), m_lemma(s) {}
bool saturation::perform(pvar v, conflict& core) {
for (auto c : core) {
if (!c->is_ule())
continue;
if (c.is_currently_true(s))
continue;
auto i = inequality::from_ule(c);
if (try_mul_bounds(v, core, i))
return true;
if (try_parity(v, core, i))
return true;
if (try_factor_equality(v, core, i))
return true;
if (try_ugt_x(v, core, i))
return true;
if (try_ugt_y(v, core, i))
return true;
if (try_ugt_z(v, core, i))
return true;
if (try_y_l_ax_and_x_l_z(v, core, i))
return true;
if (try_tangent(v, core, i))
return true;
}
return false;
}
signed_constraint saturation::ineq(bool is_strict, pdd const& lhs, pdd const& rhs) {
if (is_strict)
return s.ult(lhs, rhs);
else
return s.ule(lhs, rhs);
}
bool saturation::propagate(conflict& core, inequality const& crit, signed_constraint c) {
if (is_forced_true(c))
return false;
// NSB - review is it enough to propagate a new literal even if it is not false?
// unit propagation does not require conflicts.
// it should just avoid redundant propagation on literals that are true
//
// Furthermore propagation cannot be used when the resolved variable comes from
// forbidden interval conflicts. The propagated literal effectively adds a new and simpler bound
// on the non-viable variable. This bound then enables tighter non-viability conflicts.
// Effectively c is forced false, but it is forced false within the context of constraints used for viability.
//
// The effective level of the propagation is the level of all the other literals. If their level is below the
// last decision level (conflict level) we expect the propagation to be useful.
// The current assumptions on how conflict lemmas are used do not accomodate propagation it seems.
//
m_lemma.insert(~crit.as_signed_constraint());
IF_VERBOSE(10, verbose_stream() << "propagate " << m_rule << " ";
for (auto lit : m_lemma) verbose_stream() << s.lit2cnstr(lit) << " ";
verbose_stream() << c << "\n";
);
SASSERT(all_of(m_lemma, [this](sat::literal lit) { return is_forced_false(s.lit2cnstr(lit)); }));
m_lemma.insert(c);
core.add_lemma(m_rule, m_lemma.build());
return true;
}
bool saturation::add_conflict(conflict& core, inequality const& crit1, signed_constraint c) {
return add_conflict(core, crit1, crit1, c);
}
bool saturation::add_conflict(conflict& core, inequality const& _crit1, inequality const& _crit2, signed_constraint const c) {
auto crit1 = _crit1.as_signed_constraint();
auto crit2 = _crit2.as_signed_constraint();
m_lemma.insert(~crit1);
if (crit1 != crit2)
m_lemma.insert(~crit2);
LOG("critical " << m_rule << " " << crit1);
LOG("consequent " << c << " value: " << c.bvalue(s) << " is-false: " << c.is_currently_false(s));
SASSERT(all_of(m_lemma, [this](sat::literal lit) { return s.m_bvars.value(lit) == l_false; }));
// Ensure lemma is a conflict lemma
if (!is_forced_false(c))
return false;
// Constraint c is already on the search stack, so the lemma will not derive anything new.
if (c.bvalue(s) == l_true)
return false;
m_lemma.insert_eval(c);
core.add_lemma(m_rule, m_lemma.build());
return true;
}
bool saturation::is_non_overflow(pdd const& x, pdd const& y, signed_constraint& c) {
if (is_non_overflow(x, y)) {
c = ~s.umul_ovfl(x, y);
return true;
}
// TODO: do we really search the stack or can we just create the literal s.umul_ovfl(x, y)
// and check if it is assigned, or not even create the literal but look up whether it is assigned?
// constraint_manager uses m_dedup, alloc
// but to probe whether a literal occurs these are not needed.
// m_dedup.constraints.contains(&c);
for (auto si : s.m_search) {
if (!si.is_boolean())
continue;
if (si.is_resolved())
continue;
auto d = s.lit2cnstr(si.lit());
if (!d->is_umul_ovfl() || !d.is_negative())
continue;
auto const& ovfl = d->to_umul_ovfl();
if (x != ovfl.p() && x != ovfl.q())
continue;
if (y != ovfl.p() && y != ovfl.q())
continue;
c = d;
return true;
}
return false;
}
/*
* Match [v] .. <= v
*/
bool saturation::is_l_v(pvar v, inequality const& i) {
return i.rhs() == s.var(v);
}
/*
* Match [v] v <= ...
*/
bool saturation::is_g_v(pvar v, inequality const& i) {
return i.lhs() == s.var(v);
}
/*
* Match [x] x <= y
*/
bool saturation::is_x_l_Y(pvar x, inequality const& i, pdd& y) {
y = i.rhs();
return is_g_v(x, i);
}
/*
* Match [x] y <= a*x
*/
bool saturation::is_Y_l_Ax(pvar x, inequality const& i, pdd& a, pdd& y) {
y = i.lhs();
return is_xY(x, i.rhs(), a);
}
bool saturation::verify_Y_l_Ax(pvar x, inequality const& i, pdd const& a, pdd const& y) {
return i.lhs() == y && i.rhs() == a * s.var(x);
}
/**
* Match [x] a*x <= y
*/
bool saturation::is_Ax_l_Y(pvar x, inequality const& i, pdd& a, pdd& y) {
y = i.rhs();
return is_xY(x, i.lhs(), a);
}
bool saturation::verify_Ax_l_Y(pvar x, inequality const& i, pdd const& a, pdd const& y) {
return i.rhs() == y && i.lhs() == a * s.var(x);
}
/**
* Match [x] a*x + b <= y
*/
bool saturation::is_AxB_l_Y(pvar x, inequality const& i, pdd& a, pdd& b, pdd& y) {
y = i.rhs();
pdd aa = a, bb = b;
return i.lhs().degree(x) == 1 && (i.lhs().factor(x, 1, aa, bb), aa == a && bb == b);
}
bool saturation::verify_AxB_l_Y(pvar x, inequality const& i, pdd const& a, pdd const& b, pdd const& y) {
return i.rhs() == y && i.lhs() == a * s.var(x) + b;
}
/**
* Match [x] a*x + b <= y, val(y) = 0
*/
bool saturation::is_AxB_eq_0(pvar x, inequality const& i, pdd& a, pdd& b, pdd& y) {
y = i.rhs();
rational y_val;
if (!s.try_eval(y, y_val) || y_val != 0)
return false;
return i.lhs().degree(x) == 1 && (i.lhs().factor(x, 1, a, b), true);
}
bool saturation::verify_AxB_eq_0(pvar x, inequality const& i, pdd const& a, pdd const& b, pdd const& y) {
return y.is_val() && y.val() == 0 && i.rhs() == y && i.lhs() == a * s.var(x) + b;
}
/**
* Match [coeff*x] coeff*x*Y where x is a variable
*/
bool saturation::is_coeffxY(pdd const& x, pdd const& p, pdd& y) {
pdd xy = x.manager().zero();
return x.is_unary() && p.try_div(x.hi().val(), xy) && xy.factor(x.var(), 1, y);
}
/**
* Determine whether values of x * y is non-overflowing.
*/
bool saturation::is_non_overflow(pdd const& x, pdd const& y) {
rational x_val, y_val;
rational bound = x.manager().two_to_N();
return s.try_eval(x, x_val) && s.try_eval(y, y_val) && x_val * y_val < bound;
}
/**
* Match [v] v*x <= z*x with x a variable
*/
bool saturation::is_Xy_l_XZ(pvar v, inequality const& i, pdd& x, pdd& z) {
return is_xY(v, i.lhs(), x) && is_coeffxY(x, i.rhs(), z);
}
bool saturation::verify_Xy_l_XZ(pvar v, inequality const& i, pdd const& x, pdd const& z) {
return i.lhs() == s.var(v) * x && i.rhs() == z * x;
}
/**
* Match [z] yx <= zx with x a variable
*/
bool saturation::is_YX_l_zX(pvar z, inequality const& c, pdd& x, pdd& y) {
return is_xY(z, c.rhs(), x) && is_coeffxY(x, c.lhs(), y);
}
bool saturation::verify_YX_l_zX(pvar z, inequality const& c, pdd const& x, pdd const& y) {
return c.lhs() == y * x && c.rhs() == s.var(z) * x;
}
/**
* Match [x] xY <= xZ
*/
bool saturation::is_xY_l_xZ(pvar x, inequality const& c, pdd& y, pdd& z) {
return is_xY(x, c.lhs(), y) && is_xY(x, c.rhs(), z);
}
/**
* Match xy = x * Y
*/
bool saturation::is_xY(pvar x, pdd const& xy, pdd& y) {
return xy.degree(x) == 1 && xy.factor(x, 1, y);
}
//
// overall comment: we use value propagation to check if p is val
// but we could also use literal propagation and establish there is a literal p = 0 that is true.
// in this way the value of p doesn't have to be fixed.
//
// is_forced_diseq already creates a literal.
// is_non_overflow also creates a literal
//
// The condition that p = val may be indirect.
// it could be a literal
// it could be by propagation of literals
// Example:
// -35: v90 + v89*v43 + -1*v87 != 0 [ l_false bprop@0 pwatched ]
// 36: ovfl*(v43, v89) [ l_false bprop@0 pwatched ]
// -218: v90 + -1*v87 + -1 != 0 [ l_false eval@6 pwatched ]
//
// what should we "pay" to establish this condition?
// or do we just afford us to add this lemma?
//
bool saturation::is_forced_eq(pdd const& p, rational const& val) {
rational pv;
if (s.try_eval(p, pv) && pv == val)
return true;
return false;
}
bool saturation::is_forced_diseq(pdd const& p, int i, signed_constraint& c) {
c = s.eq(p, i);
return is_forced_false(c);
}
bool saturation::is_forced_odd(pdd const& p, signed_constraint& c) {
c = s.odd(p);
return is_forced_true(c);
}
bool saturation::is_forced_false(signed_constraint const& c) {
return c.bvalue(s) == l_false || c.is_currently_false(s);
}
bool saturation::is_forced_true(signed_constraint const& c) {
return c.bvalue(s) == l_true || c.is_currently_true(s);
}
/**
* Implement the inferences
* [x] yx < zx ==> Ω*(x,y) \/ y < z
* [x] yx <= zx ==> Ω*(x,y) \/ y <= z \/ x = 0
*/
bool saturation::try_ugt_x(pvar v, conflict& core, inequality const& xy_l_xz) {
set_rule("[x] yx <= zx");
pdd x = s.var(v);
pdd y = x;
pdd z = x;
signed_constraint non_ovfl;
if (!is_xY_l_xZ(v, xy_l_xz, y, z))
return false;
if (!xy_l_xz.is_strict() && s.is_assigned(v) && s.get_value(v).is_zero())
return false;
if (!is_non_overflow(x, y, non_ovfl))
return false;
m_lemma.reset();
m_lemma.insert_eval(~non_ovfl);
if (!xy_l_xz.is_strict())
m_lemma.insert_eval(s.eq(x));
return add_conflict(core, xy_l_xz, ineq(xy_l_xz.is_strict(), y, z));
}
/**
* [y] z' <= y /\ yx <= zx ==> Ω*(x,y) \/ z'x <= zx
* [y] z' <= y /\ yx < zx ==> Ω*(x,y) \/ z'x < zx
* [y] z' < y /\ yx <= zx ==> Ω*(x,y) \/ z'x < zx
* [y] z' < y /\ yx < zx ==> Ω*(x,y) \/ z'x < zx TODO: could strengthen the conclusion to z'x + 1 < zx
*/
bool saturation::try_ugt_y(pvar v, conflict& core, inequality const& yx_l_zx) {
set_rule("[y] z' <= y & yx <= zx");
auto& m = s.var2pdd(v);
pdd x = m.zero();
pdd z = m.zero();
if (!is_Xy_l_XZ(v, yx_l_zx, x, z))
return false;
for (auto si : s.m_search) {
if (!si.is_boolean())
continue;
if (si.is_resolved())
continue;
auto d = s.lit2cnstr(si.lit());
if (!d->is_ule())
continue;
auto l_y = inequality::from_ule(d);
if (is_l_v(v, l_y) && try_ugt_y(v, core, l_y, yx_l_zx, x, z))
return true;
}
return false;
}
bool saturation::try_ugt_y(pvar v, conflict& core, inequality const& l_y, inequality const& yx_l_zx, pdd const& x, pdd const& z) {
SASSERT(is_l_v(v, l_y));
SASSERT(verify_Xy_l_XZ(v, yx_l_zx, x, z));
pdd const y = s.var(v);
signed_constraint non_ovfl;
if (!is_non_overflow(x, y, non_ovfl))
return false;
pdd const& z_prime = l_y.lhs();
m_lemma.reset();
m_lemma.insert_eval(~non_ovfl);
return add_conflict(core, l_y, yx_l_zx, ineq(yx_l_zx.is_strict() || l_y.is_strict(), z_prime * x, z * x));
}
/**
* [z] z <= y' /\ yx <= zx ==> Ω*(x,y') \/ yx <= y'x
* [z] z <= y' /\ yx < zx ==> Ω*(x,y') \/ yx < y'x
* [z] z < y' /\ yx <= zx ==> Ω*(x,y') \/ yx < y'x
* [z] z < y' /\ yx < zx ==> Ω*(x,y') \/ yx < y'x TODO: could strengthen the conclusion to yx + 1 < y'x
*/
bool saturation::try_ugt_z(pvar z, conflict& core, inequality const& yx_l_zx) {
set_rule("[z] z <= y' && yx <= zx");
auto& m = s.var2pdd(z);
pdd y = m.zero();
pdd x = m.zero();
if (!is_YX_l_zX(z, yx_l_zx, x, y))
return false;
for (auto si : s.m_search) {
if (!si.is_boolean())
continue;
if (si.is_resolved())
continue;
auto d = s.lit2cnstr(si.lit());
if (!d->is_ule())
continue;
auto z_l_y = inequality::from_ule(d);
if (is_g_v(z, z_l_y) && try_ugt_z(z, core, z_l_y, yx_l_zx, x, y))
return true;
}
return false;
}
bool saturation::try_ugt_z(pvar z, conflict& core, inequality const& z_l_y, inequality const& yx_l_zx, pdd const& x, pdd const& y) {
SASSERT(is_g_v(z, z_l_y));
SASSERT(verify_YX_l_zX(z, yx_l_zx, x, y));
pdd const& y_prime = z_l_y.rhs();
signed_constraint non_ovfl;
if (!is_non_overflow(x, y_prime, non_ovfl))
return false;
m_lemma.reset();
m_lemma.insert_eval(~non_ovfl);
return add_conflict(core, yx_l_zx, z_l_y, ineq(z_l_y.is_strict() || yx_l_zx.is_strict(), y * x, y_prime * x));
}
/**
* [x] y <= ax /\ x <= z (non-overflow case)
* ==> Ω*(a, z) \/ y <= az
* ... (other combinations of <, <=)
*/
bool saturation::try_y_l_ax_and_x_l_z(pvar x, conflict& core, inequality const& y_l_ax) {
set_rule("[x] y <= ax & x <= z");
auto& m = s.var2pdd(x);
pdd y = m.zero();
pdd a = m.zero();
if (!is_Y_l_Ax(x, y_l_ax, a, y))
return false;
if (a.is_one())
return false;
for (auto si : s.m_search) {
if (!si.is_boolean())
continue;
if (si.is_resolved())
continue;
auto d = s.lit2cnstr(si.lit());
if (!d->is_ule())
continue;
auto x_l_z = inequality::from_ule(d);
if (is_g_v(x, x_l_z) && try_y_l_ax_and_x_l_z(x, core, y_l_ax, x_l_z, a, y))
return true;
}
return false;
}
bool saturation::try_y_l_ax_and_x_l_z(pvar x, conflict& core, inequality const& y_l_ax, inequality const& x_l_z, pdd const& a, pdd const& y) {
SASSERT(is_g_v(x, x_l_z));
SASSERT(verify_Y_l_Ax(x, y_l_ax, a, y));
pdd const& z = x_l_z.rhs();
signed_constraint non_ovfl;
if (!is_non_overflow(a, z, non_ovfl))
return false;
m_lemma.reset();
m_lemma.insert_eval(~non_ovfl);
return add_conflict(core, y_l_ax, x_l_z, ineq(x_l_z.is_strict() || y_l_ax.is_strict(), y, a * z));
}
/**
* [x] a <= k & a*x + b = 0 & b = 0 => a = 0 or x = 0 or x >= 2^K/k
* [x] x <= k & a*x + b = 0 & b = 0 => x = 0 or a = 0 or a >= 2^K/k
* Better?
* [x] a*x + b = 0 & b = 0 => a = 0 or x = 0 or Ω*(a, x)
* We need up to four versions of this for all sign combinations of a, x
*/
bool saturation::try_mul_bounds(pvar x, conflict& core, inequality const& axb_l_y) {
set_rule("[x] a*x + b = 0 & b = 0 => a = 0 or x = 0 or ovfl(a, x)");
auto& m = s.var2pdd(x);
pdd y = m.zero();
pdd a = m.zero();
pdd b = m.zero();
pdd k = m.zero();
pdd X = s.var(x);
rational k_val;
if (!is_AxB_eq_0(x, axb_l_y, a, b, y))
return false;
if (a.is_val())
return false;
if (!is_forced_eq(b, 0))
return false;
signed_constraint x_eq_0, a_eq_0;
if (!is_forced_diseq(X, 0, x_eq_0))
return false;
if (!is_forced_diseq(a, 0, a_eq_0))
return false;
auto prop1 = [&](signed_constraint c) {
m_lemma.reset();
m_lemma.insert_eval(~s.eq(b));
m_lemma.insert_eval(~s.eq(y));
m_lemma.insert_eval(x_eq_0);
m_lemma.insert_eval(a_eq_0);
return propagate(core, axb_l_y, c);
};
auto prop2 = [&](signed_constraint ante, signed_constraint c) {
m_lemma.reset();
m_lemma.insert_eval(~s.eq(b));
m_lemma.insert_eval(~s.eq(y));
m_lemma.insert_eval(x_eq_0);
m_lemma.insert_eval(a_eq_0);
m_lemma.insert_eval(~ante);
return propagate(core, axb_l_y, c);
};
pdd minus_a = -a;
pdd minus_X = -X;
pdd Y = X;
for (auto si : s.m_search) {
if (!si.is_boolean())
continue;
if (si.is_resolved())
continue;
auto d = s.lit2cnstr(si.lit());
if (!d->is_ule())
continue;
auto u_l_k = inequality::from_ule(d);
// a <= k or x <= k
k = u_l_k.rhs();
if (!k.is_val())
continue;
k_val = k.val();
if (u_l_k.is_strict())
k_val -= 1;
if (k_val <= 1)
continue;
if (u_l_k.lhs() == a || u_l_k.lhs() == minus_a)
Y = X;
else if (u_l_k.lhs() == X || u_l_k.lhs() == minus_X)
Y = a;
else
continue;
//
// NSB review: should we handle cases where k_val >= 2^{K-1}, but exploit that x*y = 0 iff -x*y = 0?
//
IF_VERBOSE(0, verbose_stream() << "mult-bounds2 " << Y << " " << axb_l_y.as_signed_constraint() << " " << u_l_k.as_signed_constraint() << " \n");
rational bound = ceil(rational::power_of_two(m.power_of_2()) / k_val);
if (prop2(d, s.uge(Y, bound)))
return true;
if (prop2(d, s.uge(-Y, bound)))
return true;
}
IF_VERBOSE(0, verbose_stream() << "mult-bounds1 " << a << " " << axb_l_y.as_signed_constraint() << " \n");
IF_VERBOSE(0, verbose_stream() << core << "\n");
if (prop1(s.umul_ovfl(a, X)))
return true;
if (prop1(s.umul_ovfl(a, -X)))
return true;
if (prop1(s.umul_ovfl(-a, X)))
return true;
if (prop1(s.umul_ovfl(-a, -X)))
return true;
return false;
}
/*
* x*y = 1 & ~ovfl(x,y) => x = 1
* x*y = -1 & ~ovfl(-x,y) => -x = 1
*/
bool saturation::try_mul_eq_1(pvar x, conflict& core, inequality const& axb_l_y) {
set_rule("[x] ax + b <= y & y = 0 & b = -1 & ~ovfl(a,x) => x = 1");
auto& m = s.var2pdd(x);
pdd y = m.zero();
pdd a = m.zero();
pdd b = m.zero();
pdd X = s.var(x);
signed_constraint non_ovfl;
if (!is_AxB_eq_0(x, axb_l_y, a, b, y))
return false;
if (!is_forced_eq(b, -1))
return false;
if (!is_non_overflow(a, X, non_ovfl))
return false;
m_lemma.reset();
m_lemma.insert_eval(~s.eq(b, rational(-1)));
m_lemma.insert_eval(~s.eq(y));
m_lemma.insert_eval(~non_ovfl);
if (propagate(core, axb_l_y, s.eq(X, 1)))
return true;
if (propagate(core, axb_l_y, s.eq(a, 1)))
return true;
return false;
}
/**
* odd(x*y) => odd(x)
* even(x) => even(x*y)
*
* parity(x) <= parity(x*y)
* parity(x) = k & parity(x*y) = k + j => parity(y) = j
* parity(x) = k & parity(y) = j => parity(x*y) = k + j
*
* odd(x) & even(y) => x + y != 0
*
* General rule:
*
* a*x + y = 0 => min(K, parity(a) + parity(x)) = parity(y)
*
* Currently implemented special case: a*x + y = 0 => (odd(b) <=> odd(a) & odd(x))
*
* general rule can be obtained by adding an
* 'is_forced_parity(x, p, x_has_parity_p)'
*
* Should we also check 'is_forced_parity(a*x, p, ax_has_parity_p)'
* if a*x has a parity but not a, x?
*
*/
bool saturation::try_parity(pvar x, conflict& core, inequality const& axb_l_y) {
set_rule("[x] a*x + b = 0 => (odd(a) & odd(x) <=> odd(b))");
auto& m = s.var2pdd(x);
unsigned N = m.power_of_2();
pdd y = m.zero();
pdd a = m.zero();
pdd b = m.zero();
pdd X = s.var(x);
if (!is_AxB_eq_0(x, axb_l_y, a, b, y))
return false;
if (a.is_max() && b.is_var()) // x == y, we propagate values in each direction and don't need a lemma
return false;
if (a.is_one() && (-b).is_var()) // y == x
return false;
signed_constraint b_is_odd = s.odd(b);
signed_constraint a_is_odd = s.odd(a);
signed_constraint x_is_odd = s.odd(X);
#if 0
LOG_H1("try_parity: " << X << " on: " << lit_pp(s, axb_l_y.as_signed_constraint()));
LOG("y: " << y << " a: " << a << " b: " << b);
LOG("b_is_odd: " << lit_pp(s, b_is_odd));
LOG("a_is_odd: " << lit_pp(s, a_is_odd));
LOG("x_is_odd: " << lit_pp(s, x_is_odd));
#endif
if (a_is_odd.is_currently_true(s) &&
x_is_odd.is_currently_true(s)) {
m_lemma.reset();
m_lemma.insert_eval(~s.eq(y));
m_lemma.insert_eval(~a_is_odd);
m_lemma.insert_eval(~x_is_odd);
if (propagate(core, axb_l_y, b_is_odd))
return true;
}
#if 0
// TODO - enable this code and test
// a is divisibly by 4,
// max divisor of x is k
// -> b has parity k + 4
if ((~a_is_odd).is_currently_true(s) ||
(~x_is_odd).is_currently_true(s)) {
unsigned a_parity = 0;
unsigned x_parity = 0;
while (a_parity <= N && s.parity(a, a_parity+1).is_currently_true(s))
++a_parity;
while (x_parity <= N && s.parity(X, x_parity+1).is_currently_true(s))
++x_parity;
SASSERT(a_parity > 0 || x_parity > 0);
unsigned b_parity = std::min(N + 1, a_parity + x_parity);
if (a_parity > 0)
m_lemma.insert_eval(~s.parity(a, a_parity));
if (x_parity > 0)
m_lemma.insert_eval(~s.parity(X, x_parity));
if (propagate(core, axb_l_y, s.parity(b, b_parity)))
return true;
}
#endif
if (b_is_odd.is_currently_true(s)) {
m_lemma.reset();
m_lemma.insert_eval(~s.eq(y));
m_lemma.insert_eval(~b_is_odd);
if (propagate(core, axb_l_y, a_is_odd))
return true;
m_lemma.reset();
m_lemma.insert_eval(~s.eq(y));
m_lemma.insert_eval(~b_is_odd);
if (propagate(core, axb_l_y, x_is_odd))
return true;
}
#if 0
//
// if b has at least b_parity, then a*x has at least b_parity
// establish lower bound on parity of b
//
if ((~b_is_odd).is_currently_true(s) && !is_forced_eq(b, 0)) {
unsigned b_parity = 1;
while (b_parity <= N && s.parity(b, b_parity+1).is_currently_true(s))
++b_parity;
SASSERT(b_parity <= N);
// TODO:
// something like this (but fixed)
for (unsigned i = 0; i <= b_parity; ++i) {
if (s.parity(a, b_parity - i).is_currently_false(s)) {
m_lemma.reset();
m_lemma.insert_eval(~s.eq(y));
m_lemma.insert_eval(~s.parity(b, b_parity));
m_lemma.insert_eval(s.parity(a, b_parity - i));
if (propagate(core, axb_l_y, s.parity(x, i)))
return true;
}
}
}
#endif
return false;
}
/**
* a*x = 0 => a = 0 or even(x)
* a*x = 0 => a = 0 or x = 0 or even(a)
*/
bool saturation::try_mul_odd(pvar x, conflict& core, inequality const& axb_l_y) {
set_rule("[x] ax = 0 => a = 0 or even(x)");
auto& m = s.var2pdd(x);
pdd y = m.zero();
pdd a = m.zero();
pdd b = m.zero();
pdd X = s.var(x);
signed_constraint a_eq_0, x_eq_0;
if (!is_AxB_eq_0(x, axb_l_y, a, b, y))
return false;
if (!is_forced_eq(b, 0))
return false;
if (!is_forced_diseq(a, 0, a_eq_0))
return false;
m_lemma.reset();
m_lemma.insert_eval(s.eq(y));
m_lemma.insert_eval(~s.eq(b));
m_lemma.insert_eval(a_eq_0);
if (propagate(core, axb_l_y, s.even(X)))
return true;
if (!is_forced_diseq(X, 0, x_eq_0))
return false;
m_lemma.insert_eval(x_eq_0);
if (propagate(core, axb_l_y, s.even(a)))
return true;
return false;
}
/**
* [x] ax + p <= q, ax + r = 0 => -r + p <= q
* [x] p <= ax + q, ax + r = 0 => p <= -r + q
* generalizations
* [x] abx + p <= q, ax + r = 0 => -rb + p <= q
* [x] p <= abx + q, ax + r = 0 => p <= -rb + q
*/
bool saturation::try_factor_equality(pvar x, conflict& core, inequality const& a_l_b) {
// search for abx+p pattern in a_l_b.lhs()
// search for ax + r = 0 equality in core (or in search but maybe not needed)
// replace abx by -rb in patterns on either a_l_b.lhs() or a_l_b.rhs() or both if available to form new implied
// literal wtihout occurrence of x
return false;
}
/*
* TODO
*
* Maybe also
* x*y = k => \/_{j is such that there is j', j*j' = k} x = j
* x*y = k & ~ovfl(x,y) & x = j => y = k/j where j is a divisor of k
*/
/**
* [x] p(x) <= q(x) where value(p) > value(q)
* ==> q <= value(q) => p <= value(q)
*
* for strict?
* p(x) < q(x) where value(p) >= value(q)
* ==> value(p) <= p => value(p) < q
*/
bool saturation::try_tangent(pvar v, conflict& core, inequality const& c) {
set_rule("[x] p(x) <= q(x) where value(p) > value(q)");
// if (c.is_strict())
// return false;
if (!c.as_signed_constraint()->contains_var(v))
return false;
if (c.lhs().is_val() || c.rhs().is_val())
return false;
auto& m = s.var2pdd(v);
pdd q_l(m), e_l(m), q_r(m), e_r(m);
bool is_linear = true;
is_linear &= c.lhs().degree(v) <= 1;
is_linear &= c.rhs().degree(v) <= 1;
if (c.lhs().degree(v) == 1) {
c.lhs().factor(v, 1, q_l, e_l);
is_linear &= q_l.is_val();
}
if (c.rhs().degree(v) == 1) {
c.rhs().factor(v, 1, q_r, e_r);
is_linear &= q_r.is_val();
}
if (is_linear)
return false;
if (!c.as_signed_constraint().is_currently_false(s))
return false;
rational l_val, r_val;
if (!s.try_eval(c.lhs(), l_val))
return false;
if (!s.try_eval(c.rhs(), r_val))
return false;
SASSERT(c.is_strict() || l_val > r_val);
SASSERT(!c.is_strict() || l_val >= r_val);
m_lemma.reset();
if (c.is_strict()) {
auto d = s.ule(l_val, c.lhs());
if (d.bvalue(s) == l_false) // it is a different value conflict that contains v
return false;
m_lemma.insert_eval(~d);
auto conseq = s.ult(r_val, c.rhs());
return add_conflict(core, c, conseq);
}
else {
auto d = s.ule(c.rhs(), r_val);
if (d.bvalue(s) == l_false) // it is a different value conflict that contains v
return false;
m_lemma.insert_eval(~d);
auto conseq = s.ule(c.lhs(), r_val);
return add_conflict(core, c, conseq);
}
}
}