mirror of
https://github.com/Z3Prover/z3
synced 2025-06-27 00:18:45 +00:00
move constraint handler functionality to self-contained / separate classes.
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
parent
0d78a10630
commit
5163492d5b
5 changed files with 134 additions and 117 deletions
|
@ -12,16 +12,126 @@ Author:
|
||||||
--*/
|
--*/
|
||||||
|
|
||||||
#include "math/polysat/constraint.h"
|
#include "math/polysat/constraint.h"
|
||||||
|
#include "math/polysat/solver.h"
|
||||||
|
#include "math/polysat/log.h"
|
||||||
|
|
||||||
namespace polysat {
|
namespace polysat {
|
||||||
|
|
||||||
|
eq_constraint& constraint::to_eq() {
|
||||||
constraint* constraint::eq(unsigned lvl, pdd const& p, p_dependency_ref& d) {
|
return *dynamic_cast<eq_constraint*>(this);
|
||||||
return alloc(eq_constraint, lvl, p, dep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& constraint::display(std::ostream& out) const {
|
eq_constraint const& constraint::to_eq() const {
|
||||||
|
return *dynamic_cast<eq_constraint const*>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint* constraint::eq(unsigned lvl, pdd const& p, p_dependency_ref& d) {
|
||||||
|
return alloc(eq_constraint, lvl, p, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& eq_constraint::display(std::ostream& out) const {
|
||||||
return out << p() << " == 0";
|
return out << p() << " == 0";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool eq_constraint::propagate(solver& s, pvar v) {
|
||||||
|
LOG_H3("Propagate " << s.m_vars[v] << " in " << *this);
|
||||||
|
SASSERT(!vars().empty());
|
||||||
|
auto var = s.m_vars[v].var();
|
||||||
|
unsigned idx = 0;
|
||||||
|
if (vars()[idx] != v)
|
||||||
|
idx = 1;
|
||||||
|
SASSERT(v == vars()[idx]);
|
||||||
|
// find other watch variable.
|
||||||
|
for (unsigned i = vars().size(); i-- > 2; ) {
|
||||||
|
if (!s.is_assigned(vars()[i])) {
|
||||||
|
std::swap(vars()[idx], vars()[i]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LOG("Assignments: " << s.m_search);
|
||||||
|
auto q = p().subst_val(s.m_search);
|
||||||
|
LOG("Substituted: " << p() << " := " << q);
|
||||||
|
TRACE("polysat", tout << p() << " := " << q << "\n";);
|
||||||
|
if (q.is_zero())
|
||||||
|
return false;
|
||||||
|
if (q.is_never_zero()) {
|
||||||
|
LOG("Conflict (never zero under current assignment)");
|
||||||
|
// we could tag constraint to allow early substitution before
|
||||||
|
// swapping watch variable in case we can detect conflict earlier.
|
||||||
|
s.set_conflict(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// at most one variable remains unassigned.
|
||||||
|
auto other_var = vars()[1 - idx];
|
||||||
|
|
||||||
|
// Detect and apply unit propagation.
|
||||||
|
|
||||||
|
if (!q.is_linear())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// a*x + b == 0
|
||||||
|
rational a = q.hi().val();
|
||||||
|
rational b = q.lo().val();
|
||||||
|
rational inv_a;
|
||||||
|
if (a.is_odd()) {
|
||||||
|
// v1 = -b * inverse(a)
|
||||||
|
unsigned sz = q.power_of_2();
|
||||||
|
VERIFY(a.mult_inverse(sz, inv_a));
|
||||||
|
rational val = mod(inv_a * -b, rational::power_of_two(sz));
|
||||||
|
s.m_cjust[other_var].push_back(this);
|
||||||
|
s.propagate(other_var, val, *this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SASSERT(!b.is_odd()); // otherwise p.is_never_zero() would have been true above
|
||||||
|
|
||||||
|
// TBD
|
||||||
|
// constrain viable using condition on x
|
||||||
|
// 2*x + 2 == 0 mod 4 => x is odd
|
||||||
|
//
|
||||||
|
// We have:
|
||||||
|
// 2^j*a'*x + 2^j*b' == 0 mod m, where a' is odd (but not necessarily b')
|
||||||
|
// <=> 2^j*(a'*x + b') == 0 mod m
|
||||||
|
// <=> a'*x + b' == 0 mod (m-j)
|
||||||
|
// <=> x == -b' * inverse_{m-j}(a') mod (m-j)
|
||||||
|
// ( <=> 2^j*x == 2^j * -b' * inverse_{m-j}(a') mod m )
|
||||||
|
//
|
||||||
|
// x == c mod (m-j)
|
||||||
|
// Which x in 2^m satisfy this?
|
||||||
|
// => x \in { c + k * 2^(m-j) | k = 0, ..., 2^j - 1 }
|
||||||
|
unsigned rank_a = a.trailing_zeros(); // j
|
||||||
|
SASSERT(b == 0 || rank_a <= b.trailing_zeros());
|
||||||
|
rational aa = a / rational::power_of_two(rank_a); // a'
|
||||||
|
rational bb = b / rational::power_of_two(rank_a); // b'
|
||||||
|
rational inv_aa;
|
||||||
|
unsigned small_sz = q.power_of_2() - rank_a; // m - j
|
||||||
|
VERIFY(aa.mult_inverse(small_sz, inv_aa));
|
||||||
|
rational cc = mod(inv_aa * -bb, rational::power_of_two(small_sz));
|
||||||
|
LOG(m_vars[other_var] << " = " << cc << " + k * 2^" << small_sz);
|
||||||
|
// TODO: better way to update the BDD, e.g. construct new one (only if rank_a is small?)
|
||||||
|
vector<rational> viable;
|
||||||
|
for (rational k = rational::zero(); k < rational::power_of_two(rank_a); k += 1) {
|
||||||
|
rational val = cc + k * rational::power_of_two(small_sz);
|
||||||
|
viable.push_back(val);
|
||||||
|
}
|
||||||
|
LOG_V("still viable: " << viable);
|
||||||
|
unsigned i = 0;
|
||||||
|
for (rational r = rational::zero(); r < rational::power_of_two(q.power_of_2()); r += 1) {
|
||||||
|
while (i < viable.size() && viable[i] < r)
|
||||||
|
++i;
|
||||||
|
if (i < viable.size() && viable[i] == r)
|
||||||
|
continue;
|
||||||
|
if (s.is_viable(other_var, r)) {
|
||||||
|
s.add_non_viable(other_var, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("TODO");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ namespace polysat {
|
||||||
class ule_constraint;
|
class ule_constraint;
|
||||||
|
|
||||||
class constraint {
|
class constraint {
|
||||||
|
friend class eq_constraint;
|
||||||
|
friend class ule_constraint;
|
||||||
unsigned m_level;
|
unsigned m_level;
|
||||||
ckind_t m_kind;
|
ckind_t m_kind;
|
||||||
p_dependency_ref m_dep;
|
p_dependency_ref m_dep;
|
||||||
|
@ -39,8 +41,9 @@ namespace polysat {
|
||||||
bool is_sle() const { return m_kind == ckind_t::sle_t; }
|
bool is_sle() const { return m_kind == ckind_t::sle_t; }
|
||||||
ckind_t kind() const { return m_kind; }
|
ckind_t kind() const { return m_kind; }
|
||||||
virtual std::ostream& display(std::ostream& out) const = 0;
|
virtual std::ostream& display(std::ostream& out) const = 0;
|
||||||
eq_constraint& to_eq() { return *dynamic_cast<eq_constraint*>(this); }
|
virtual bool propagate(solver& s, pvar v) = 0;
|
||||||
eq_constraint const& to_eq() { return *dynamic_cast<eq_constraint const*>(this); }
|
eq_constraint& to_eq();
|
||||||
|
eq_constraint const& to_eq() const;
|
||||||
p_dependency* dep() const { return m_dep; }
|
p_dependency* dep() const { return m_dep; }
|
||||||
unsigned_vector& vars() { return m_vars; }
|
unsigned_vector& vars() { return m_vars; }
|
||||||
unsigned level() const { return m_level; }
|
unsigned level() const { return m_level; }
|
||||||
|
@ -49,12 +52,14 @@ namespace polysat {
|
||||||
class eq_constraint : public constraint {
|
class eq_constraint : public constraint {
|
||||||
pdd m_poly;
|
pdd m_poly;
|
||||||
public:
|
public:
|
||||||
eq_constraint(unsigned lvl, pdd const& p, pdd const& q, p_dependency_ref& dep,):
|
eq_constraint(unsigned lvl, pdd const& p, p_dependency_ref& dep):
|
||||||
constraint(lvl, dep, ckind_t::eq_t), m_poly(p) {
|
constraint(lvl, dep, ckind_t::eq_t), m_poly(p) {
|
||||||
m_vars.append(p.free_vars());
|
m_vars.append(p.free_vars());
|
||||||
}
|
}
|
||||||
pdd const & p() const { return m_poly; }
|
pdd const & p() const { return m_poly; }
|
||||||
std::ostream& display(std::ostream& out) const override;
|
std::ostream& display(std::ostream& out) const override;
|
||||||
|
|
||||||
|
bool propagate(solver& s, pvar v) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::ostream& operator<<(std::ostream& out, constraint const& c) { return c.display(out); }
|
inline std::ostream& operator<<(std::ostream& out, constraint const& c) { return c.display(out); }
|
||||||
|
|
|
@ -66,6 +66,7 @@ std::pair<std::ostream&, bool>
|
||||||
polysat_log(LogLevel msg_level, std::string fn, std::string /* pretty_fn */)
|
polysat_log(LogLevel msg_level, std::string fn, std::string /* pretty_fn */)
|
||||||
{
|
{
|
||||||
std::ostream& os = std::cerr;
|
std::ostream& os = std::cerr;
|
||||||
|
#if 0
|
||||||
int const fd = fileno(stderr);
|
int const fd = fileno(stderr);
|
||||||
|
|
||||||
size_t width = 20;
|
size_t width = 20;
|
||||||
|
@ -82,6 +83,10 @@ polysat_log(LogLevel msg_level, std::string fn, std::string /* pretty_fn */)
|
||||||
os << "[" << fn << "] " << std::string(padding, ' ');
|
os << "[" << fn << "] " << std::string(padding, ' ');
|
||||||
os << std::string(polysat_log_indent_level, ' ');
|
os << std::string(polysat_log_indent_level, ' ');
|
||||||
return {os, (bool)color};
|
return {os, (bool)color};
|
||||||
|
#else
|
||||||
|
return {os, false};
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
polysat_log_indent::polysat_log_indent(int amount)
|
polysat_log_indent::polysat_log_indent(int amount)
|
||||||
|
|
|
@ -215,7 +215,7 @@ namespace polysat {
|
||||||
bool solver::propagate(pvar v, constraint& c) {
|
bool solver::propagate(pvar v, constraint& c) {
|
||||||
switch (c.kind()) {
|
switch (c.kind()) {
|
||||||
case ckind_t::eq_t:
|
case ckind_t::eq_t:
|
||||||
return propagate_eq(v, c);
|
return c.to_eq().propagate(*this, v);
|
||||||
case ckind_t::ule_t:
|
case ckind_t::ule_t:
|
||||||
case ckind_t::sle_t:
|
case ckind_t::sle_t:
|
||||||
NOT_IMPLEMENTED_YET();
|
NOT_IMPLEMENTED_YET();
|
||||||
|
@ -225,110 +225,6 @@ namespace polysat {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool solver::propagate_eq(pvar v, constraint& c) {
|
|
||||||
LOG_H3("Propagate " << m_vars[v] << " in " << c);
|
|
||||||
SASSERT(c.kind() == ckind_t::eq_t);
|
|
||||||
SASSERT(!c.vars().empty());
|
|
||||||
auto var = m_vars[v].var();
|
|
||||||
auto& vars = c.vars();
|
|
||||||
unsigned idx = 0;
|
|
||||||
if (vars[idx] != v)
|
|
||||||
idx = 1;
|
|
||||||
SASSERT(v == vars[idx]);
|
|
||||||
// find other watch variable.
|
|
||||||
for (unsigned i = vars.size(); i-- > 2; ) {
|
|
||||||
if (!is_assigned(vars[i])) {
|
|
||||||
std::swap(vars[idx], vars[i]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
LOG("Assignments: " << m_search);
|
|
||||||
auto p = c.p().subst_val(m_search);
|
|
||||||
LOG("Substituted: " << c.p() << " := " << p);
|
|
||||||
TRACE("polysat", tout << c.p() << " := " << p << "\n";);
|
|
||||||
if (p.is_zero())
|
|
||||||
return false;
|
|
||||||
if (p.is_never_zero()) {
|
|
||||||
LOG("Conflict (never zero under current assignment)");
|
|
||||||
// we could tag constraint to allow early substitution before
|
|
||||||
// swapping watch variable in case we can detect conflict earlier.
|
|
||||||
set_conflict(c);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// at most one variable remains unassigned.
|
|
||||||
auto other_var = vars[1 - idx];
|
|
||||||
// SASSERT(!is_assigned(other_var));
|
|
||||||
|
|
||||||
// Detect and apply unit propagation.
|
|
||||||
|
|
||||||
if (!p.is_linear())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// a*x + b == 0
|
|
||||||
rational a = p.hi().val();
|
|
||||||
rational b = p.lo().val();
|
|
||||||
rational inv_a;
|
|
||||||
if (a.is_odd()) {
|
|
||||||
// v1 = -b * inverse(a)
|
|
||||||
unsigned sz = p.power_of_2();
|
|
||||||
VERIFY(a.mult_inverse(sz, inv_a));
|
|
||||||
rational val = mod(inv_a * -b, rational::power_of_two(sz));
|
|
||||||
m_cjust[other_var].push_back(&c);
|
|
||||||
propagate(other_var, val, c);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SASSERT(!b.is_odd()); // otherwise p.is_never_zero() would have been true above
|
|
||||||
|
|
||||||
// TBD
|
|
||||||
// constrain viable using condition on x
|
|
||||||
// 2*x + 2 == 0 mod 4 => x is odd
|
|
||||||
//
|
|
||||||
// We have:
|
|
||||||
// 2^j*a'*x + 2^j*b' == 0 mod m, where a' is odd (but not necessarily b')
|
|
||||||
// <=> 2^j*(a'*x + b') == 0 mod m
|
|
||||||
// <=> a'*x + b' == 0 mod (m-j)
|
|
||||||
// <=> x == -b' * inverse_{m-j}(a') mod (m-j)
|
|
||||||
// ( <=> 2^j*x == 2^j * -b' * inverse_{m-j}(a') mod m )
|
|
||||||
//
|
|
||||||
// x == c mod (m-j)
|
|
||||||
// Which x in 2^m satisfy this?
|
|
||||||
// => x \in { c + k * 2^(m-j) | k = 0, ..., 2^j - 1 }
|
|
||||||
unsigned rank_a = a.trailing_zeros(); // j
|
|
||||||
SASSERT(b == 0 || rank_a <= b.trailing_zeros());
|
|
||||||
rational aa = a / rational::power_of_two(rank_a); // a'
|
|
||||||
rational bb = b / rational::power_of_two(rank_a); // b'
|
|
||||||
rational inv_aa;
|
|
||||||
unsigned small_sz = p.power_of_2() - rank_a; // m - j
|
|
||||||
VERIFY(aa.mult_inverse(small_sz, inv_aa));
|
|
||||||
rational cc = mod(inv_aa * -bb, rational::power_of_two(small_sz));
|
|
||||||
LOG(m_vars[other_var] << " = " << cc << " + k * 2^" << small_sz);
|
|
||||||
// TODO: better way to update the BDD, e.g. construct new one (only if rank_a is small?)
|
|
||||||
vector<rational> viable;
|
|
||||||
for (rational k = rational::zero(); k < rational::power_of_two(rank_a); k += 1) {
|
|
||||||
rational val = cc + k * rational::power_of_two(small_sz);
|
|
||||||
viable.push_back(val);
|
|
||||||
}
|
|
||||||
LOG_V("still viable: " << viable);
|
|
||||||
unsigned i = 0;
|
|
||||||
for (rational r = rational::zero(); r < rational::power_of_two(p.power_of_2()); r += 1) {
|
|
||||||
while (i < viable.size() && viable[i] < r)
|
|
||||||
++i;
|
|
||||||
if (i < viable.size() && viable[i] == r)
|
|
||||||
continue;
|
|
||||||
if (is_viable(other_var, r)) {
|
|
||||||
add_non_viable(other_var, r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG("TODO");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void solver::propagate(pvar v, rational const& val, constraint& c) {
|
void solver::propagate(pvar v, rational const& val, constraint& c) {
|
||||||
if (is_viable(v, val)) {
|
if (is_viable(v, val)) {
|
||||||
m_free_vars.del_var_eh(v);
|
m_free_vars.del_var_eh(v);
|
||||||
|
@ -645,8 +541,8 @@ namespace polysat {
|
||||||
constraint* c = m_conflict[0];
|
constraint* c = m_conflict[0];
|
||||||
constraint* d = m_cjust[v].back();
|
constraint* d = m_cjust[v].back();
|
||||||
if (c->is_eq() && d->is_eq()) {
|
if (c->is_eq() && d->is_eq()) {
|
||||||
pdd p = c->p();
|
pdd p = c->to_eq().p();
|
||||||
pdd q = d->p();
|
pdd q = d->to_eq().p();
|
||||||
pdd r = p;
|
pdd r = p;
|
||||||
if (!p.resolve(v, q, r))
|
if (!p.resolve(v, q, r))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -668,13 +564,13 @@ namespace polysat {
|
||||||
|
|
||||||
bool solver::is_always_false(constraint& c) {
|
bool solver::is_always_false(constraint& c) {
|
||||||
if (c.is_eq())
|
if (c.is_eq())
|
||||||
return c.p().is_never_zero();
|
return c.to_eq().p().is_never_zero();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool solver::eval_to_false(constraint& c) {
|
bool solver::eval_to_false(constraint& c) {
|
||||||
if (c.is_eq()) {
|
if (c.is_eq()) {
|
||||||
pdd r = c.p().subst_val(m_search);
|
pdd r = c.to_eq().p().subst_val(m_search);
|
||||||
return r.is_never_zero();
|
return r.is_never_zero();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -32,6 +32,8 @@ namespace polysat {
|
||||||
stats() { reset(); }
|
stats() { reset(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
friend class eq_constraint;
|
||||||
|
|
||||||
typedef ptr_vector<constraint> constraints;
|
typedef ptr_vector<constraint> constraints;
|
||||||
|
|
||||||
trail_stack& m_trail;
|
trail_stack& m_trail;
|
||||||
|
@ -120,7 +122,6 @@ namespace polysat {
|
||||||
|
|
||||||
void propagate(pvar v);
|
void propagate(pvar v);
|
||||||
bool propagate(pvar v, constraint& c);
|
bool propagate(pvar v, constraint& c);
|
||||||
bool propagate_eq(pvar v, constraint& c);
|
|
||||||
void propagate(pvar v, rational const& val, constraint& c);
|
void propagate(pvar v, rational const& val, constraint& c);
|
||||||
void erase_watch(pvar v, constraint& c);
|
void erase_watch(pvar v, constraint& c);
|
||||||
void erase_watch(constraint& c);
|
void erase_watch(constraint& c);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue