3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-30 04:15:51 +00:00

Polysat: use constraint_literal and begin move to core-based conflict representation (#5489)

* Rename solver_scope for fixplex tests

(otherwise the wrong constructor is called for polysat's solver_scope)

* Update conflict_core

* simplify

* Be clearer about constraint_literal lifetime

* remove old comment

* Remove status (positive/negative) from constraint

* Use constraint_literal in the solver

* Fix build (constraint -> get_constraint)
This commit is contained in:
Jakob Rath 2021-08-18 20:02:46 +02:00 committed by GitHub
parent 30e9f24fa3
commit ebaea2159e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 933 additions and 1004 deletions

View file

@ -26,6 +26,7 @@ namespace polysat {
enum csign_t : bool { neg_t = false, pos_t = true };
class constraint_literal;
class constraint_literal_ref;
class constraint;
class constraint_manager;
class clause;
@ -34,6 +35,7 @@ namespace polysat {
class ule_constraint;
using constraint_ref = ref<constraint>;
using constraint_ref_vector = sref_vector<constraint>;
using constraint_literal_ref_vector = vector<constraint_literal_ref>;
using clause_ref = ref<clause>;
using clause_ref_vector = sref_vector<clause>;
@ -49,7 +51,7 @@ namespace polysat {
void erase_bv2c(sat::bool_var bv) { m_bv2constraint[bv] = nullptr; }
constraint* get_bv2c(sat::bool_var bv) const { return m_bv2constraint.get(bv, nullptr); }
// Constraint storage per level; should be destructed before m_bv2constraint
// Constraint storage per level; should be destructed before m_bv2constraint because constraint's destructor calls erase_bv2c
vector<vector<constraint_ref>> m_constraints;
vector<vector<clause_ref>> m_clauses;
@ -74,13 +76,14 @@ namespace polysat {
void release_level(unsigned lvl);
constraint* lookup(sat::bool_var var) const;
constraint_literal lookup(sat::literal lit) const;
constraint* lookup_external(unsigned dep) const { return m_external_constraints.get(dep, nullptr); }
constraint_literal eq(unsigned lvl, pdd const& p);
constraint_literal ule(unsigned lvl, pdd const& a, pdd const& b);
constraint_literal ult(unsigned lvl, pdd const& a, pdd const& b);
constraint_literal sle(unsigned lvl, pdd const& a, pdd const& b);
constraint_literal slt(unsigned lvl, pdd const& a, pdd const& b);
constraint_literal_ref eq(unsigned lvl, pdd const& p);
constraint_literal_ref ule(unsigned lvl, pdd const& a, pdd const& b);
constraint_literal_ref ult(unsigned lvl, pdd const& a, pdd const& b);
constraint_literal_ref sle(unsigned lvl, pdd const& a, pdd const& b);
constraint_literal_ref slt(unsigned lvl, pdd const& a, pdd const& b);
// p_dependency_ref null_dep() const { return {nullptr, m_dm}; }
};
@ -109,12 +112,10 @@ namespace polysat {
constraint_manager* m_manager;
clause* m_unit_clause = nullptr; ///< If this constraint was asserted by a unit clause, we store that clause here.
unsigned m_ref_count = 0;
// TODO: we could remove the level on constraints and instead store constraint_refs for all literals inside the clause? (clauses will then be 4 times larger though...)
unsigned m_storage_level; ///< Controls lifetime of the constraint object. Always a base level.
ckind_t m_kind;
unsigned_vector m_vars;
sat::bool_var m_bvar; ///< boolean variable associated to this constraint; convention: a constraint itself always represents the positive sat::literal
lbool m_status = l_undef; ///< current constraint status; intended to be the same as m_manager->m_bvars.value(bvar()) if that value is set.
constraint(constraint_manager& m, unsigned lvl, ckind_t k):
m_manager(&m), m_storage_level(lvl), m_kind(k), m_bvar(m_manager->m_bvars.new_var()) {
@ -123,7 +124,7 @@ namespace polysat {
}
protected:
std::ostream& display_extra(std::ostream& out) const;
std::ostream& display_extra(std::ostream& out, lbool status) const;
public:
void inc_ref() { m_ref_count++; }
@ -140,14 +141,16 @@ namespace polysat {
bool is_eq() const { return m_kind == ckind_t::eq_t; }
bool is_ule() const { return m_kind == ckind_t::ule_t; }
ckind_t kind() const { return m_kind; }
virtual std::ostream& display(std::ostream& out) const = 0;
bool propagate(solver& s, pvar v);
virtual void propagate_core(solver& s, pvar v, pvar other_v);
virtual bool is_always_false() = 0;
virtual bool is_currently_false(solver& s) = 0;
virtual bool is_currently_true(solver& s) = 0;
virtual void narrow(solver& s) = 0;
virtual inequality as_inequality() const = 0;
virtual std::ostream& display(std::ostream& out, lbool status = l_undef) const = 0;
bool propagate(solver& s, bool is_positive, pvar v);
virtual void propagate_core(solver& s, bool is_positive, pvar v, pvar other_v);
virtual bool is_always_false(bool is_positive) = 0;
virtual bool is_currently_false(solver& s, bool is_positive) = 0;
virtual bool is_currently_true(solver& s, bool is_positive) = 0;
virtual void narrow(solver& s, bool is_positive) = 0;
virtual inequality as_inequality(bool is_positive) const = 0;
eq_constraint& to_eq();
eq_constraint const& to_eq() const;
ule_constraint& to_ule();
@ -155,27 +158,8 @@ namespace polysat {
unsigned_vector& vars() { return m_vars; }
unsigned_vector const& vars() const { return m_vars; }
unsigned level() const { return m_storage_level; }
// unsigned active_level() const {
// SASSERT(!is_undef());
// return m_manager->m_bvars.level(bvar());
// }
// unsigned active_at_base_level(solver& s) const {
// return !is_undef() && active_level() <= s.base_level();
// }
sat::bool_var bvar() const { return m_bvar; }
sat::literal blit() const { SASSERT(!is_undef()); return m_status == l_true ? sat::literal(m_bvar) : ~sat::literal(m_bvar); }
void assign(bool is_true) {
SASSERT(m_status == l_undef || m_status == to_lbool(is_true));
// SASSERT(m_status == l_undef /* || m_status == to_lbool(is_true) */);
m_status = to_lbool(is_true);
// SASSERT(m_manager->m_bvars.value(bvar()) == l_undef || m_manager->m_bvars.value(bvar()) == m_status); // TODO: is this always true? maybe we sometimes want to check the opposite phase temporarily.
}
void unassign() { m_status = l_undef; }
bool is_undef() const { return m_status == l_undef; }
bool is_positive() const { return m_status == l_true; }
bool is_negative() const { return m_status == l_false; }
clause* unit_clause() const { return m_unit_clause; }
void set_unit_clause(clause* cl) { SASSERT(cl); SASSERT(!m_unit_clause || m_unit_clause == cl); m_unit_clause = cl; }
p_dependency* unit_dep() const;
@ -186,43 +170,111 @@ namespace polysat {
* \param[out] out_neg_cond Negation of the side condition (the side condition is true when the forbidden interval is trivial). May be NULL if the condition is constant.
* \returns True iff a forbidden interval exists and the output parameters were set.
*/
virtual bool forbidden_interval(solver& s, pvar v, eval_interval& out_interval, constraint_literal& out_neg_cond) { return false; }
// TODO: we can probably remove this and unify the implementations for both cases by relying on as_inequality().
virtual bool forbidden_interval(solver& s, bool is_positive, pvar v, eval_interval& out_interval, constraint_literal_ref& out_neg_cond) { return false; }
};
inline std::ostream& operator<<(std::ostream& out, constraint const& c) { return c.display(out); }
/// Literal together with the constraint it represents.
/// (or: constraint with polarity)
/// Literal together with the constraint it represents (i.e., constraint with polarity).
/// Non-owning version.
class constraint_literal {
sat::literal m_literal = sat::null_literal;
constraint* m_constraint = nullptr;
public:
constraint_literal() {}
constraint_literal(sat::literal lit, constraint* c):
m_literal(lit), m_constraint(c) {
SASSERT(get_constraint());
SASSERT(literal().var() == get_constraint()->bvar());
}
constraint_literal(constraint* c, bool is_positive): constraint_literal(sat::literal(c->bvar(), !is_positive), c) {}
constraint_literal operator~() const {
return {~m_literal, m_constraint};
}
void negate() {
m_literal = ~m_literal;
}
bool is_positive() const { return !m_literal.sign(); }
bool is_negative() const { return m_literal.sign(); }
bool propagate(solver& s, pvar v) { return get_constraint()->propagate(s, !literal().sign(), v); }
void propagate_core(solver& s, pvar v, pvar other_v) { get_constraint()->propagate_core(s, !literal().sign(), v, other_v); }
bool is_always_false() { return get_constraint()->is_always_false(!literal().sign()); }
bool is_currently_false(solver& s) { return get_constraint()->is_currently_false(s, !literal().sign()); }
bool is_currently_true(solver& s) { return get_constraint()->is_currently_true(s, !literal().sign()); }
void narrow(solver& s) { get_constraint()->narrow(s, !literal().sign()); }
inequality as_inequality() const { return get_constraint()->as_inequality(!literal().sign()); }
sat::literal literal() const { return m_literal; }
constraint* get_constraint() const { return m_constraint; }
explicit operator bool() const { return !!m_constraint; }
bool operator!() const { return !m_constraint; }
constraint* operator->() const { return m_constraint; }
constraint const& operator*() const { return *m_constraint; }
constraint_literal& operator=(std::nullptr_t) { m_literal = sat::null_literal; m_constraint = nullptr; return *this; }
std::ostream& display(std::ostream& out) const;
};
inline std::ostream& operator<<(std::ostream& out, constraint_literal const& c) { return c.display(out); }
inline bool operator==(constraint_literal const& lhs, constraint_literal const& rhs) {
if (lhs.literal() == rhs.literal())
SASSERT(lhs.get_constraint() == rhs.get_constraint());
return lhs.literal() == rhs.literal();
}
/// Version of constraint_literal that owns the constraint.
class constraint_literal_ref {
sat::literal m_literal = sat::null_literal;
constraint_ref m_constraint = nullptr;
public:
constraint_literal() {}
constraint_literal(sat::literal lit, constraint_ref c):
constraint_literal_ref() {}
constraint_literal_ref(sat::literal lit, constraint_ref c):
m_literal(lit), m_constraint(std::move(c)) {
SASSERT(get());
SASSERT(literal().var() == get()->bvar());
SASSERT(get_constraint());
SASSERT(literal().var() == get_constraint()->bvar());
}
constraint_literal operator~() const&& {
constraint_literal_ref(constraint_literal cl): constraint_literal_ref(cl.literal(), cl.get_constraint()) {}
constraint_literal_ref operator~() const&& {
return {~m_literal, std::move(m_constraint)};
}
void negate() {
m_literal = ~m_literal;
}
sat::literal literal() const { return m_literal; }
constraint* get() const { return m_constraint.get(); }
constraint* get_constraint() const { return m_constraint.get(); }
constraint_literal get() const { return {literal(), get_constraint()}; }
constraint_ref detach() { m_literal = sat::null_literal; return std::move(m_constraint); }
explicit operator bool() const { return !!m_constraint; }
bool operator!() const { return !m_constraint; }
constraint* operator->() const { return m_constraint.get(); }
constraint* operator->() const { return m_constraint.operator->(); }
constraint const& operator*() const { return *m_constraint; }
constraint_literal& operator=(std::nullptr_t) { m_literal = sat::null_literal; m_constraint = nullptr; return *this; }
constraint_literal_ref& operator=(std::nullptr_t) { m_literal = sat::null_literal; m_constraint = nullptr; return *this; }
std::ostream& display(std::ostream& out) const;
private:
friend class constraint_manager;
explicit constraint_literal(constraint* c): constraint_literal(sat::literal(c->bvar()), c) {}
explicit constraint_literal_ref(constraint* c): constraint_literal_ref(sat::literal(c->bvar()), c) {}
};
inline std::ostream& operator<<(std::ostream& out, constraint_literal_ref const& c) { return c.display(out); }
/// Disjunction of constraints represented by boolean literals
class clause {
@ -254,7 +306,7 @@ namespace polysat {
void inc_ref() { m_ref_count++; }
void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (!m_ref_count) dealloc(this); }
static clause_ref from_unit(constraint_literal c, p_dependency_ref d);
static clause_ref from_unit(constraint_literal_ref c, p_dependency_ref d);
static clause_ref from_literals(unsigned lvl, p_dependency_ref d, sat::literal_vector literals, constraint_ref_vector new_constraints);
// Resolve with 'other' upon 'var'.
@ -286,98 +338,5 @@ namespace polysat {
inline std::ostream& operator<<(std::ostream& out, clause const& c) { return c.display(out); }
// Container for unit constraints and clauses.
class constraints_and_clauses {
constraint_ref_vector m_units;
clause_ref_vector m_clauses;
public:
constraint_ref_vector const& units() const { return m_units; }
constraint_ref_vector& units() { return m_units; }
clause_ref_vector const& clauses() const { return m_clauses; }
clause_ref_vector& clauses() { return m_clauses; }
bool is_unit() const { return units().size() == 1 && clauses().empty(); }
constraint* get_unit() const { SASSERT(is_unit()); return units()[0]; }
bool is_clause() const { return units().empty() && clauses().size() == 1; }
clause* get_clause() const { SASSERT(is_clause()); return clauses()[0]; }
unsigned size() const {
return m_units.size() + m_clauses.size();
}
bool empty() const {
return m_units.empty() && m_clauses.empty();
}
void reset() {
m_units.reset();
m_clauses.reset();
}
void append(ptr_vector<constraint> const& cs) {
for (constraint* c : cs)
m_units.push_back(c);
}
void push_back(std::nullptr_t) { m_units.push_back(nullptr); }
void push_back(constraint_ref c) { m_units.push_back(std::move(c)); }
void push_back(clause_ref cl) { m_clauses.push_back(std::move(cl)); }
// TODO: use iterator instead
unsigned_vector vars(constraint_manager const& cm) const {
unsigned_vector vars;
for (auto const& c : m_units)
vars.append(c->vars());
for (auto const& cl : m_clauses)
for (auto lit : *cl) {
constraint* c = cm.lookup(lit.var());
if (c)
vars.append(c->vars());
}
return vars;
}
std::ostream& display(std::ostream& out) const;
};
inline std::ostream& operator<<(std::ostream& out, constraints_and_clauses const& c) { return c.display(out); }
/// Temporarily assign a constraint according to the sign of the given literal.
class tmp_assign final {
constraint* m_constraint;
bool m_should_unassign = false;
public:
/// c must live longer than tmp_assign.
tmp_assign(constraint* c, sat::literal lit):
m_constraint(c) {
SASSERT(c);
SASSERT_EQ(c->bvar(), lit.var());
if (c->is_undef()) {
c->assign(!lit.sign());
m_should_unassign = true;
}
else
SASSERT_EQ(c->blit(), lit);
}
void revert() {
if (m_should_unassign) {
m_constraint->unassign();
m_should_unassign = false;
}
}
~tmp_assign() {
revert();
}
tmp_assign(tmp_assign&) = delete;
tmp_assign(tmp_assign&&) = delete;
tmp_assign& operator=(tmp_assign&) = delete;
tmp_assign& operator=(tmp_assign&&) = delete;
};
inline p_dependency* constraint::unit_dep() const { return m_unit_clause ? m_unit_clause->dep() : nullptr; }
}