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

Polysat: constraint refactor cont'd, deduplicate constraints (#5520)

* Assign boolean variables only to long-lived constraints, and deduplicate constraints when they are created

* scoped_signed_constraint

* update other classes

* fix

* Don't use scoped_ptr<constraint> with dedup()
This commit is contained in:
Jakob Rath 2021-08-30 19:00:27 +02:00 committed by GitHub
parent ebaea2159e
commit 0c1e44da77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 365 additions and 298 deletions

View file

@ -19,54 +19,65 @@ Author:
#include "util/map.h"
#include "util/ref.h"
#include "util/ref_vector.h"
#include <type_traits>
namespace polysat {
enum ckind_t { eq_t, ule_t };
enum csign_t : bool { neg_t = false, pos_t = true };
class constraint_literal;
class constraint_literal_ref;
class constraint;
class constraint_manager;
class clause;
class scoped_clause;
class eq_constraint;
class ule_constraint;
using constraint_ref = ref<constraint>;
using constraint_ref_vector = sref_vector<constraint>;
using constraint_literal_ref_vector = vector<constraint_literal_ref>;
class scoped_constraint_ptr;
template <bool is_owned>
class signed_constraint_base;
using signed_constraint = signed_constraint_base<false>;
using scoped_signed_constraint = signed_constraint_base<true>;
class clause;
using clause_ref = ref<clause>;
using clause_ref_vector = sref_vector<clause>;
using constraint_table = ptr_hashtable<constraint, obj_ptr_hash<constraint>, deref_eq<constraint>>;
// Manage constraint lifetime, deduplication, and connection to boolean variables/literals.
class constraint_manager {
friend class constraint;
bool_var_manager& m_bvars;
// Association to boolean variables
ptr_vector<constraint> m_bv2constraint;
void insert_bv2c(sat::bool_var bv, constraint* c) { /* NOTE: will be called with incompletely constructed c! */ m_bv2constraint.setx(bv, c, nullptr); }
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); }
// Constraints indexed by their boolean variable
ptr_vector<constraint> m_bv2constraint;
// Constraints that have a boolean variable, for deduplication
constraint_table m_constraint_table;
// Constraint storage per level; should be destructed before m_bv2constraint because constraint's destructor calls erase_bv2c
vector<vector<constraint_ref>> m_constraints;
// Constraint storage per level
vector<scoped_ptr_vector<constraint>> m_constraints;
vector<vector<clause_ref>> m_clauses;
// Association to external dependency values (i.e., external names for constraints)
u_map<constraint*> m_external_constraints;
// TODO: some hashmaps to look up whether constraint (or its negation) already exists
// Manage association of constraints to boolean variables
void assign_bv2c(sat::bool_var bv, constraint* c);
void erase_bv2c(constraint* c);
constraint* get_bv2c(sat::bool_var bv) const;
constraint* dedup(constraint* c);
public:
constraint_manager(bool_var_manager& bvars): m_bvars(bvars) {}
// constraint_manager(bool_var_manager& bvars, poly_dep_manager& dm): m_bvars(bvars), m_dm(dm) {}
~constraint_manager();
// Start managing lifetime of the given constraint
constraint* store(constraint_ref c);
/** Start managing the lifetime of the given constraint
* - Keeps the constraint until the corresponding level is popped
* - Allocates a boolean variable for the constraint
*/
constraint* store(scoped_constraint_ptr c);
clause* store(clause_ref cl);
/// Register a unit clause with an external dependency.
@ -76,16 +87,14 @@ namespace polysat {
void release_level(unsigned lvl);
constraint* lookup(sat::bool_var var) const;
constraint_literal lookup(sat::literal lit) const;
signed_constraint lookup(sat::literal lit) const;
constraint* lookup_external(unsigned dep) const { return m_external_constraints.get(dep, nullptr); }
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}; }
scoped_signed_constraint eq(unsigned lvl, pdd const& p);
scoped_signed_constraint ule(unsigned lvl, pdd const& a, pdd const& b);
scoped_signed_constraint ult(unsigned lvl, pdd const& a, pdd const& b);
scoped_signed_constraint sle(unsigned lvl, pdd const& a, pdd const& b);
scoped_signed_constraint slt(unsigned lvl, pdd const& a, pdd const& b);
};
@ -105,35 +114,28 @@ namespace polysat {
class constraint {
friend class constraint_manager;
friend class clause;
friend class scoped_clause;
friend class eq_constraint;
friend class ule_constraint;
constraint_manager* m_manager;
// 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;
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
/** 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.
* Convention: the plain constraint corresponds the positive sat::literal.
*/
sat::bool_var m_bvar = sat::null_bool_var;
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()) {
SASSERT_EQ(m_manager->get_bv2c(bvar()), nullptr);
m_manager->insert_bv2c(bvar(), this);
}
/*m_manager(&m),*/ m_storage_level(lvl), m_kind(k) {}
protected:
std::ostream& display_extra(std::ostream& out, lbool status) const;
public:
void inc_ref() { m_ref_count++; }
void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (!m_ref_count) dealloc(this); }
virtual ~constraint() {
SASSERT_EQ(m_manager->get_bv2c(bvar()), this);
m_manager->erase_bv2c(bvar());
m_manager->m_bvars.del_var(m_bvar);
}
virtual ~constraint() {}
virtual unsigned hash() const = 0;
virtual bool operator==(constraint const& other) const = 0;
@ -158,6 +160,7 @@ namespace polysat {
unsigned_vector& vars() { return m_vars; }
unsigned_vector const& vars() const { return m_vars; }
unsigned level() const { return m_storage_level; }
bool has_bvar() const { return m_bvar != sat::null_bool_var; }
sat::bool_var bvar() const { return m_bvar; }
clause* unit_clause() const { return m_unit_clause; }
@ -171,121 +174,148 @@ namespace polysat {
* \returns True iff a forbidden interval exists and the output parameters were set.
*/
// 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; }
virtual bool forbidden_interval(solver& s, bool is_positive, pvar v, eval_interval& out_interval, scoped_signed_constraint& 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 (i.e., constraint with polarity).
/// Non-owning version.
class constraint_literal {
sat::literal m_literal = sat::null_literal;
constraint* m_constraint = nullptr;
// Like scoped_ptr<constraint>, but only deallocates the constraint if it is temporary (i.e., does not have a boolean variable).
// This is needed because when a constraint is created, due to deduplication, we might get either a new constraint or an existing one.
// (We want early deduplication because otherwise we might overlook possible boolean resolutions during conflict resolution.)
// (TODO: we could replace this class by std::unique_ptr with a custom deleter)
class scoped_constraint_ptr {
constraint* m_ptr;
void dealloc_ptr() const {
if (m_ptr && !m_ptr->has_bvar())
dealloc(m_ptr);
}
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) {}
scoped_constraint_ptr(constraint* ptr = nullptr): m_ptr(ptr) {}
constraint_literal operator~() const {
return {~m_literal, m_constraint};
scoped_constraint_ptr(scoped_constraint_ptr &&other) noexcept : m_ptr(nullptr) {
std::swap(m_ptr, other.m_ptr);
}
~scoped_constraint_ptr() {
dealloc_ptr();
}
scoped_constraint_ptr& operator=(scoped_constraint_ptr&& other) {
*this = other.detach();
return *this;
};
scoped_constraint_ptr& operator=(constraint* n) {
if (m_ptr != n) {
dealloc_ptr();
m_ptr = n;
}
return *this;
}
void swap(scoped_constraint_ptr& p) {
std::swap(m_ptr, p.m_ptr);
}
constraint* detach() {
constraint* tmp = m_ptr;
m_ptr = nullptr;
return tmp;
}
explicit operator bool() const { return !!m_ptr; }
bool operator!() const { return !m_ptr; }
constraint* get() const { return m_ptr; }
constraint* operator->() const { return m_ptr; }
const constraint& operator*() const { return *m_ptr; }
constraint &operator*() { return *m_ptr; }
};
template <bool is_owned>
class signed_constraint_base final {
public:
using ptr_t = std::conditional_t<is_owned, scoped_constraint_ptr, constraint*>;
private:
ptr_t m_constraint = nullptr;
bool m_positive = true;
public:
signed_constraint_base() {}
signed_constraint_base(constraint* c, bool is_positive):
m_constraint(c), m_positive(is_positive) {}
signed_constraint_base(constraint* c, sat::literal lit):
signed_constraint_base(c, !lit.sign()) {
SASSERT_EQ(blit(), lit);
}
void negate() {
m_literal = ~m_literal;
m_positive = !m_positive;
}
bool is_positive() const { return !m_literal.sign(); }
bool is_negative() const { return m_literal.sign(); }
bool is_positive() const { return m_positive; }
bool is_negative() const { return !is_positive(); }
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()); }
bool propagate(solver& s, pvar v) { return get()->propagate(s, is_positive(), v); }
void propagate_core(solver& s, pvar v, pvar other_v) { get()->propagate_core(s, is_positive(), v, other_v); }
bool is_always_false() { return get()->is_always_false(is_positive()); }
bool is_currently_false(solver& s) { return get()->is_currently_false(s, is_positive()); }
bool is_currently_true(solver& s) { return get()->is_currently_true(s, is_positive()); }
void narrow(solver& s) { get()->narrow(s, is_positive()); }
inequality as_inequality() const { return get()->as_inequality(is_positive()); }
sat::literal literal() const { return m_literal; }
constraint* get_constraint() const { return m_constraint; }
sat::bool_var bvar() const { return m_constraint->bvar(); }
sat::literal blit() const { return sat::literal(bvar(), is_negative()); }
constraint* get() const { if constexpr (is_owned) return m_constraint.get(); else return m_constraint; }
signed_constraint get_signed() const { return {get(), m_positive}; }
template <bool Owned = is_owned>
std::enable_if_t<Owned, constraint*> detach() { return m_constraint.detach(); }
explicit operator bool() const { return !!m_constraint; }
bool operator!() const { return !m_constraint; }
constraint* operator->() const { return m_constraint; }
constraint* operator->() const { return get(); }
constraint& operator*() { 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; }
signed_constraint_base<is_owned>& operator=(std::nullptr_t) { m_constraint = nullptr; return *this; }
std::ostream& display(std::ostream& out) const;
bool operator==(signed_constraint_base<is_owned> const& other) const {
return get() == other.get() && is_positive() == other.is_positive();
}
std::ostream& display(std::ostream& out) const {
if (m_constraint)
return m_constraint->display(out, to_lbool(is_positive()));
else
return out << "<null>";
}
};
inline std::ostream& operator<<(std::ostream& out, constraint_literal const& c) { return c.display(out); }
template <bool is_owned>
inline std::ostream& operator<<(std::ostream& out, signed_constraint_base<is_owned> 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();
inline signed_constraint operator~(signed_constraint const& c) {
return {c.get(), !c.is_positive()};
}
/// 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_ref() {}
constraint_literal_ref(sat::literal lit, constraint_ref c):
m_literal(lit), m_constraint(std::move(c)) {
SASSERT(get_constraint());
SASSERT(literal().var() == get_constraint()->bvar());
}
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_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.operator->(); }
constraint const& operator*() const { return *m_constraint; }
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_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); }
inline scoped_signed_constraint operator~(scoped_signed_constraint&& c) {
return {c.detach(), !c.is_positive()};
}
/// Disjunction of constraints represented by boolean literals
class clause {
friend class constraint_manager;
unsigned m_ref_count = 0;
unsigned m_ref_count = 0; // TODO: remove refcount once we confirm it's not needed anymore
unsigned m_level;
unsigned m_next_guess = 0; // next guess for enumerative backtracking
p_dependency_ref m_dep;
sat::literal_vector m_literals;
constraint_ref_vector m_new_constraints; // new constraints, temporarily owned by this clause
/* TODO: embed literals to save an indirection?
unsigned m_num_literals;
@ -296,8 +326,8 @@ namespace polysat {
}
*/
clause(unsigned lvl, p_dependency_ref d, sat::literal_vector literals, constraint_ref_vector new_constraints):
m_level(lvl), m_dep(std::move(d)), m_literals(std::move(literals)), m_new_constraints(std::move(new_constraints))
clause(unsigned lvl, p_dependency_ref d, sat::literal_vector literals):
m_level(lvl), m_dep(std::move(d)), m_literals(std::move(literals))
{
SASSERT(std::count(m_literals.begin(), m_literals.end(), sat::null_literal) == 0);
}
@ -306,8 +336,8 @@ 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_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);
static clause_ref from_unit(signed_constraint c, p_dependency_ref d);
static clause_ref from_literals(unsigned lvl, p_dependency_ref d, sat::literal_vector literals);
// Resolve with 'other' upon 'var'.
bool resolve(sat::bool_var var, clause const& other);
@ -316,7 +346,6 @@ namespace polysat {
p_dependency* dep() const { return m_dep; }
unsigned level() const { return m_level; }
constraint_ref_vector const& new_constraints() const { return m_new_constraints; }
bool empty() const { return m_literals.empty(); }
unsigned size() const { return m_literals.size(); }
sat::literal operator[](unsigned idx) const { return m_literals[idx]; }