mirror of
https://github.com/Z3Prover/z3
synced 2025-09-29 20:59:01 +00:00
Support watching inactive constraints
This allows us to handle non-redundant clauses (like we get from quot_rem)
This commit is contained in:
parent
6bf897aad8
commit
2345fb6428
5 changed files with 119 additions and 34 deletions
|
@ -339,4 +339,11 @@ namespace polysat {
|
||||||
lbool signed_constraint::bvalue(solver& s) const {
|
lbool signed_constraint::bvalue(solver& s) const {
|
||||||
return get()->has_bvar() ? s.m_bvars.value(blit()) : l_undef;
|
return get()->has_bvar() ? s.m_bvars.value(blit()) : l_undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream& constraint_pp::display(std::ostream& out) const {
|
||||||
|
if (c)
|
||||||
|
return c->display(out, status);
|
||||||
|
else
|
||||||
|
return out << "<null>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,7 @@ namespace polysat {
|
||||||
unsigned_vector m_vars;
|
unsigned_vector m_vars;
|
||||||
lbool m_external_sign = l_undef;
|
lbool m_external_sign = l_undef;
|
||||||
bool m_is_active = false;
|
bool m_is_active = false;
|
||||||
|
bool m_is_pwatched = false;
|
||||||
/** The boolean variable associated to this constraint, if any.
|
/** The boolean variable associated to this constraint, if any.
|
||||||
* If this is not null_bool_var, then the constraint corresponds to a literal on the assignment stack.
|
* If this is not null_bool_var, then the constraint corresponds to a literal on the assignment stack.
|
||||||
* Convention: the plain constraint corresponds the positive sat::literal.
|
* Convention: the plain constraint corresponds the positive sat::literal.
|
||||||
|
@ -207,6 +208,9 @@ namespace polysat {
|
||||||
bool is_active() const { return m_is_active; }
|
bool is_active() const { return m_is_active; }
|
||||||
void set_active(bool f) { m_is_active = f; }
|
void set_active(bool f) { m_is_active = f; }
|
||||||
|
|
||||||
|
bool is_pwatched() const { return m_is_pwatched; }
|
||||||
|
void set_pwatched(bool f) { m_is_pwatched = f; }
|
||||||
|
|
||||||
/// Assuming the constraint is univariate under the current assignment of 's',
|
/// Assuming the constraint is univariate under the current assignment of 's',
|
||||||
/// adds the constraint to the univariate solver 'us'.
|
/// adds the constraint to the univariate solver 'us'.
|
||||||
virtual void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const = 0;
|
virtual void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const = 0;
|
||||||
|
@ -297,4 +301,15 @@ namespace polysat {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class constraint_pp {
|
||||||
|
constraint const* c;
|
||||||
|
lbool status;
|
||||||
|
public:
|
||||||
|
constraint_pp(constraint const* c, lbool status): c(c), status(status) {}
|
||||||
|
std::ostream& display(std::ostream& out) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::ostream& operator<<(std::ostream& out, constraint_pp const& p) { return p.display(out); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,15 +226,14 @@ namespace polysat {
|
||||||
#endif
|
#endif
|
||||||
push_qhead();
|
push_qhead();
|
||||||
while (can_propagate()) {
|
while (can_propagate()) {
|
||||||
if (is_conflict())
|
|
||||||
return;
|
|
||||||
auto const& item = m_search[m_qhead++];
|
auto const& item = m_search[m_qhead++];
|
||||||
if (item.is_assignment())
|
if (item.is_assignment())
|
||||||
propagate(item.var());
|
propagate(item.var());
|
||||||
else
|
else
|
||||||
propagate(item.lit());
|
propagate(item.lit());
|
||||||
}
|
}
|
||||||
linear_propagate();
|
if (!is_conflict())
|
||||||
|
linear_propagate();
|
||||||
SASSERT(wlist_invariant());
|
SASSERT(wlist_invariant());
|
||||||
SASSERT(assignment_invariant());
|
SASSERT(assignment_invariant());
|
||||||
}
|
}
|
||||||
|
@ -277,8 +276,9 @@ namespace polysat {
|
||||||
DEBUG_CODE(m_locked_wlist = std::nullopt;);
|
DEBUG_CODE(m_locked_wlist = std::nullopt;);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool solver::propagate(pvar v, signed_constraint c) {
|
/** Return true if a new watch was found; or false to keep the existing one. */
|
||||||
LOG_H3("Propagate " << m_vars[v] << " in " << c);
|
bool solver::propagate(pvar v, constraint* c) {
|
||||||
|
LOG_H3("Propagate " << m_vars[v] << " in " << constraint_pp(c, m_bvars.value(c->bvar())));
|
||||||
SASSERT(is_assigned(v));
|
SASSERT(is_assigned(v));
|
||||||
SASSERT(!c->vars().empty());
|
SASSERT(!c->vars().empty());
|
||||||
unsigned idx = 0;
|
unsigned idx = 0;
|
||||||
|
@ -289,19 +289,48 @@ namespace polysat {
|
||||||
for (unsigned i = c->vars().size(); i-- > 2; ) {
|
for (unsigned i = c->vars().size(); i-- > 2; ) {
|
||||||
unsigned other_v = c->vars()[i];
|
unsigned other_v = c->vars()[i];
|
||||||
if (!is_assigned(other_v)) {
|
if (!is_assigned(other_v)) {
|
||||||
add_watch(c, other_v);
|
add_pwatch(c, other_v);
|
||||||
std::swap(c->vars()[idx], c->vars()[i]);
|
std::swap(c->vars()[idx], c->vars()[i]);
|
||||||
if (!is_assigned(c->var(1 - idx)))
|
// if (!is_assigned(c->var(1 - idx))) // TODO: why this check? if the other watch is already assigned (because e.g. all other variables have been propagated from other constraints first), then we end up with 3 watches on c
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// at most one variable remains unassigned.
|
|
||||||
if (c->vars().size() >= 2) {
|
// TODO: aren't we visiting many constraints twice here?
|
||||||
unsigned other_v = c->var(1 - idx);
|
// first, when one variable is unassigned -> intersect viable with constraint
|
||||||
if (!is_assigned(other_v))
|
// second, when all variables are assigned -> check if there's a conflict.
|
||||||
m_viable_fallback.push_constraint(other_v, c);
|
// maybe not really a problem.
|
||||||
|
|
||||||
|
// at most one poly variable remains unassigned.
|
||||||
|
if (m_bvars.is_assigned(c->bvar())) {
|
||||||
|
// constraint is active, propagate it
|
||||||
|
SASSERT(c->is_active());
|
||||||
|
signed_constraint sc(c, m_bvars.value(c->bvar()) == l_true);
|
||||||
|
if (c->vars().size() >= 2) {
|
||||||
|
unsigned other_v = c->var(1 - idx);
|
||||||
|
if (!is_assigned(other_v))
|
||||||
|
m_viable_fallback.push_constraint(other_v, sc);
|
||||||
|
}
|
||||||
|
sc.narrow(*this, false);
|
||||||
|
} else {
|
||||||
|
// constraint is not yet active, try to evaluate it
|
||||||
|
SASSERT(!c->is_active());
|
||||||
|
if (c->vars().size() >= 2) {
|
||||||
|
unsigned other_v = c->var(1 - idx);
|
||||||
|
if (!is_assigned(other_v)) {
|
||||||
|
// wait for the remaining variable to be assigned (might not be necessary in all cases)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Evaluate constraint
|
||||||
|
signed_constraint sc(c, true);
|
||||||
|
if (sc.is_currently_true(*this))
|
||||||
|
assign_eval(sc.blit());
|
||||||
|
else {
|
||||||
|
SASSERT(sc.is_currently_false(*this));
|
||||||
|
assign_eval(~sc.blit());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.narrow(*this, false);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,6 +367,7 @@ namespace polysat {
|
||||||
}
|
}
|
||||||
|
|
||||||
void solver::propagate(pvar v, rational const& val, signed_constraint c) {
|
void solver::propagate(pvar v, rational const& val, signed_constraint c) {
|
||||||
|
// this looks weird... mixing propagation and conflict with c? also, the conflict should not be c but the whole of viable+c.
|
||||||
LOG("Propagation: " << assignment_pp(*this, v, val) << ", due to " << c);
|
LOG("Propagation: " << assignment_pp(*this, v, val) << ", due to " << c);
|
||||||
if (m_viable.is_viable(v, val)) {
|
if (m_viable.is_viable(v, val)) {
|
||||||
m_free_pvars.del_var_eh(v);
|
m_free_pvars.del_var_eh(v);
|
||||||
|
@ -374,6 +404,12 @@ namespace polysat {
|
||||||
pop_qhead();
|
pop_qhead();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case trail_instr_t::pwatch_i: {
|
||||||
|
constraint* c = m_pwatch_trail.back();
|
||||||
|
erase_pwatch(c);
|
||||||
|
m_pwatch_trail.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case trail_instr_t::add_var_i: {
|
case trail_instr_t::add_var_i: {
|
||||||
del_var();
|
del_var();
|
||||||
break;
|
break;
|
||||||
|
@ -433,38 +469,46 @@ namespace polysat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void solver::add_watch(signed_constraint c) {
|
void solver::add_pwatch(constraint* c) {
|
||||||
SASSERT(c);
|
SASSERT(c);
|
||||||
|
if (c->is_pwatched())
|
||||||
|
return;
|
||||||
auto& vars = c->vars();
|
auto& vars = c->vars();
|
||||||
unsigned i = 0, j = 0, sz = vars.size();
|
unsigned i = 0, j = 0, sz = vars.size();
|
||||||
for (; i < sz && j < 2; ++i)
|
for (; i < sz && j < 2; ++i)
|
||||||
if (!is_assigned(vars[i]))
|
if (!is_assigned(vars[i]))
|
||||||
std::swap(vars[i], vars[j++]);
|
std::swap(vars[i], vars[j++]);
|
||||||
if (vars.size() > 0)
|
if (vars.size() > 0)
|
||||||
add_watch(c, vars[0]);
|
add_pwatch(c, vars[0]);
|
||||||
if (vars.size() > 1)
|
if (vars.size() > 1)
|
||||||
add_watch(c, vars[1]);
|
add_pwatch(c, vars[1]);
|
||||||
|
c->set_pwatched(true);
|
||||||
|
m_pwatch_trail.push_back(c);
|
||||||
|
m_trail.push_back(trail_instr_t::pwatch_i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void solver::add_watch(signed_constraint c, pvar v) {
|
void solver::add_pwatch(constraint* c, pvar v) {
|
||||||
SASSERT(c);
|
|
||||||
// SASSERT(m_locked_wlist != v); // appending doesn't interfere with propagation, so it should be fine
|
// SASSERT(m_locked_wlist != v); // appending doesn't interfere with propagation, so it should be fine
|
||||||
LOG("Watching v" << v << " in constraint " << c);
|
LOG("Watching v" << v << " in constraint " << show_deref(c));
|
||||||
m_pwatch[v].push_back(c);
|
m_pwatch[v].push_back(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void solver::erase_watch(signed_constraint c) {
|
void solver::erase_pwatch(constraint* c) {
|
||||||
|
if (!c->is_pwatched())
|
||||||
|
return;
|
||||||
auto const& vars = c->vars();
|
auto const& vars = c->vars();
|
||||||
if (vars.size() > 0)
|
if (vars.size() > 0)
|
||||||
erase_watch(vars[0], c);
|
erase_pwatch(vars[0], c);
|
||||||
if (vars.size() > 1)
|
if (vars.size() > 1)
|
||||||
erase_watch(vars[1], c);
|
erase_pwatch(vars[1], c);
|
||||||
|
c->set_pwatched(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void solver::erase_watch(pvar v, signed_constraint c) {
|
void solver::erase_pwatch(pvar v, constraint* c) {
|
||||||
if (v == null_var)
|
if (v == null_var)
|
||||||
return;
|
return;
|
||||||
SASSERT(m_locked_wlist != v);
|
SASSERT(m_locked_wlist != v);
|
||||||
|
LOG("Unwatching v" << v << " in constraint " << show_deref(c));
|
||||||
auto& wlist = m_pwatch[v];
|
auto& wlist = m_pwatch[v];
|
||||||
unsigned sz = wlist.size();
|
unsigned sz = wlist.size();
|
||||||
for (unsigned i = 0; i < sz; ++i) {
|
for (unsigned i = 0; i < sz; ++i) {
|
||||||
|
@ -941,7 +985,7 @@ namespace polysat {
|
||||||
SASSERT(m_bvars.value(c.blit()) == l_true);
|
SASSERT(m_bvars.value(c.blit()) == l_true);
|
||||||
SASSERT(!c->is_active());
|
SASSERT(!c->is_active());
|
||||||
c->set_active(true);
|
c->set_active(true);
|
||||||
add_watch(c);
|
add_pwatch(c.get());
|
||||||
if (c->vars().size() == 1)
|
if (c->vars().size() == 1)
|
||||||
m_viable_fallback.push_constraint(c->var(0), c);
|
m_viable_fallback.push_constraint(c->var(0), c);
|
||||||
c.narrow(*this, true);
|
c.narrow(*this, true);
|
||||||
|
@ -954,7 +998,6 @@ namespace polysat {
|
||||||
void solver::deactivate_constraint(signed_constraint c) {
|
void solver::deactivate_constraint(signed_constraint c) {
|
||||||
LOG("Deactivating constraint: " << c.blit());
|
LOG("Deactivating constraint: " << c.blit());
|
||||||
c->set_active(false);
|
c->set_active(false);
|
||||||
erase_watch(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void solver::backjump(unsigned new_level) {
|
void solver::backjump(unsigned new_level) {
|
||||||
|
@ -977,9 +1020,22 @@ namespace polysat {
|
||||||
for (sat::literal lit : clause) {
|
for (sat::literal lit : clause) {
|
||||||
LOG(" Literal " << lit << " is: " << lit2cnstr(lit));
|
LOG(" Literal " << lit << " is: " << lit2cnstr(lit));
|
||||||
// SASSERT(m_bvars.value(lit) != l_true);
|
// SASSERT(m_bvars.value(lit) != l_true);
|
||||||
|
// it could be that such a literal has been created previously but we don't detect it when e.g. narrowing a mul_ovfl_constraint
|
||||||
|
if (m_bvars.value(lit) == l_true) {
|
||||||
|
// in this case the clause is useless
|
||||||
|
LOG(" Clause is already true, skippping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SASSERT(!clause.empty());
|
SASSERT(!clause.empty());
|
||||||
m_constraints.store(&clause, *this, true);
|
m_constraints.store(&clause, *this, true);
|
||||||
|
|
||||||
|
if (!clause.is_redundant()) {
|
||||||
|
// for (at least) non-redundant clauses, we also need to watch the constraints
|
||||||
|
// so we can discover when the clause should propagate
|
||||||
|
for (sat::literal lit : clause)
|
||||||
|
add_pwatch(m_constraints.lookup(lit.var()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void solver::add_clause(unsigned n, signed_constraint* cs, bool is_redundant) {
|
void solver::add_clause(unsigned n, signed_constraint* cs, bool is_redundant) {
|
||||||
|
@ -1125,6 +1181,12 @@ namespace polysat {
|
||||||
* Check that two variables of each constraint are watched.
|
* Check that two variables of each constraint are watched.
|
||||||
*/
|
*/
|
||||||
bool solver::wlist_invariant() {
|
bool solver::wlist_invariant() {
|
||||||
|
for (pvar v = 0; v < m_value.size(); ++v) {
|
||||||
|
std::stringstream s;
|
||||||
|
for (constraint* c : m_pwatch[v])
|
||||||
|
s << " " << c->bvar();
|
||||||
|
LOG("Watch for v" << v << ": " << s.str());
|
||||||
|
}
|
||||||
// Skip boolean variables that aren't active yet
|
// Skip boolean variables that aren't active yet
|
||||||
uint_set skip;
|
uint_set skip;
|
||||||
for (unsigned i = m_qhead; i < m_search.size(); ++i)
|
for (unsigned i = m_qhead; i < m_search.size(); ++i)
|
||||||
|
@ -1143,7 +1205,7 @@ namespace polysat {
|
||||||
int64_t num_watches = 0;
|
int64_t num_watches = 0;
|
||||||
signed_constraint sc(c, is_positive);
|
signed_constraint sc(c, is_positive);
|
||||||
for (auto const& wlist : m_pwatch) {
|
for (auto const& wlist : m_pwatch) {
|
||||||
auto n = std::count(wlist.begin(), wlist.end(), sc);
|
auto n = std::count(wlist.begin(), wlist.end(), c);
|
||||||
if (n > 1)
|
if (n > 1)
|
||||||
std::cout << sc << "\n" << * this << "\n";
|
std::cout << sc << "\n" << * this << "\n";
|
||||||
VERIFY(n <= 1); // no duplicates in the watchlist
|
VERIFY(n <= 1); // no duplicates in the watchlist
|
||||||
|
@ -1151,7 +1213,7 @@ namespace polysat {
|
||||||
}
|
}
|
||||||
unsigned expected_watches = std::min(2u, c->vars().size());
|
unsigned expected_watches = std::min(2u, c->vars().size());
|
||||||
if (num_watches != expected_watches)
|
if (num_watches != expected_watches)
|
||||||
LOG("wrong number of watches: " << sc << " vars: " << sc->vars());
|
LOG("wrong number of watches: " << sc.blit() << ": " << sc << " (vars: " << sc->vars() << ")");
|
||||||
SASSERT_EQ(num_watches, expected_watches);
|
SASSERT_EQ(num_watches, expected_watches);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -108,7 +108,7 @@ namespace polysat {
|
||||||
// Per variable information
|
// Per variable information
|
||||||
vector<rational> m_value; // assigned value
|
vector<rational> m_value; // assigned value
|
||||||
vector<justification> m_justification; // justification for variable assignment
|
vector<justification> m_justification; // justification for variable assignment
|
||||||
vector<signed_constraints> m_pwatch; // watch list datastructure into constraints.
|
vector<constraints> m_pwatch; // watch list datastructure into constraints.
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
std::optional<pvar> m_locked_wlist; // restrict watch list modification while it is being propagated
|
std::optional<pvar> m_locked_wlist; // restrict watch list modification while it is being propagated
|
||||||
bool m_propagating = false; // set to true during propagation
|
bool m_propagating = false; // set to true during propagation
|
||||||
|
@ -128,7 +128,7 @@ namespace polysat {
|
||||||
|
|
||||||
svector<trail_instr_t> m_trail;
|
svector<trail_instr_t> m_trail;
|
||||||
unsigned_vector m_qhead_trail;
|
unsigned_vector m_qhead_trail;
|
||||||
unsigned_vector m_cjust_trail;
|
constraints m_pwatch_trail;
|
||||||
|
|
||||||
unsigned_vector m_base_levels; // External clients can push/pop scope.
|
unsigned_vector m_base_levels; // External clients can push/pop scope.
|
||||||
|
|
||||||
|
@ -179,13 +179,13 @@ namespace polysat {
|
||||||
|
|
||||||
void propagate(sat::literal lit);
|
void propagate(sat::literal lit);
|
||||||
void propagate(pvar v);
|
void propagate(pvar v);
|
||||||
bool propagate(pvar v, signed_constraint c);
|
bool propagate(pvar v, constraint* c);
|
||||||
void propagate(pvar v, rational const& val, signed_constraint c);
|
void propagate(pvar v, rational const& val, signed_constraint c);
|
||||||
bool propagate(sat::literal lit, clause& cl);
|
bool propagate(sat::literal lit, clause& cl);
|
||||||
void erase_watch(pvar v, signed_constraint c);
|
void add_pwatch(constraint* c);
|
||||||
void erase_watch(signed_constraint c);
|
void add_pwatch(constraint* c, pvar v);
|
||||||
void add_watch(signed_constraint c);
|
void erase_pwatch(pvar v, constraint* c);
|
||||||
void add_watch(signed_constraint c, pvar v);
|
void erase_pwatch(constraint* c);
|
||||||
|
|
||||||
void set_conflict(signed_constraint c) { m_conflict.set(c); }
|
void set_conflict(signed_constraint c) { m_conflict.set(c); }
|
||||||
void set_conflict(clause& cl) { m_conflict.set(cl); }
|
void set_conflict(clause& cl) { m_conflict.set(cl); }
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace polysat {
|
||||||
|
|
||||||
enum class trail_instr_t {
|
enum class trail_instr_t {
|
||||||
qhead_i,
|
qhead_i,
|
||||||
|
pwatch_i,
|
||||||
add_var_i,
|
add_var_i,
|
||||||
inc_level_i,
|
inc_level_i,
|
||||||
viable_add_i,
|
viable_add_i,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue