3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-07-03 11:25:40 +00:00

Polysat: conflict resolution wip (#5529)

* conflict_core doesn't need gc() anymore

* update comments, ensure_bvar for new constraints

* Make sure constraints can only be created through constraint_manager

* fix constraint::display if no boolean variable is assigned

* Move clause into separate file

* Add conflict_core binary resolution

* conflict_core additions

* reactivate conflict resolution outer loop

* wip

* seems commented includes break CI build
This commit is contained in:
Jakob Rath 2021-09-01 18:10:10 +02:00 committed by GitHub
parent 8b374c3745
commit dc547510db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 423 additions and 335 deletions

View file

@ -1,6 +1,7 @@
z3_add_component(polysat z3_add_component(polysat
SOURCES SOURCES
boolean.cpp boolean.cpp
clause.cpp
clause_builder.cpp clause_builder.cpp
conflict_core.cpp conflict_core.cpp
constraint.cpp constraint.cpp
@ -14,6 +15,7 @@ z3_add_component(polysat
solver.cpp solver.cpp
ule_constraint.cpp ule_constraint.cpp
viable.cpp viable.cpp
variable_elimination.cpp
COMPONENT_DEPENDENCIES COMPONENT_DEPENDENCIES
util util
dd dd

View file

@ -0,0 +1,58 @@
/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
polysat clauses
Author:
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-6
--*/
#include "math/polysat/clause.h"
#include "math/polysat/solver.h"
namespace polysat {
clause_ref clause::from_unit(signed_constraint c, p_dependency_ref d) {
SASSERT(c->has_bvar());
unsigned const lvl = c->level();
sat::literal_vector lits;
lits.push_back(c.blit());
return clause::from_literals(lvl, std::move(d), std::move(lits));
}
clause_ref clause::from_literals(unsigned lvl, p_dependency_ref d, sat::literal_vector literals) {
return alloc(clause, lvl, std::move(d), std::move(literals));
}
bool clause::is_always_false(solver& s) const {
return std::all_of(m_literals.begin(), m_literals.end(), [&s](sat::literal lit) {
signed_constraint c = s.m_constraints.lookup(lit);
return c.is_always_false();
});
}
bool clause::is_currently_false(solver& s) const {
return std::all_of(m_literals.begin(), m_literals.end(), [&s](sat::literal lit) {
signed_constraint c = s.m_constraints.lookup(lit);
return c.is_currently_false(s);
});
}
std::ostream& clause::display(std::ostream& out) const {
bool first = true;
for (auto lit : *this) {
if (first)
first = false;
else
out << " \\/ ";
out << lit;
}
return out;
}
}

85
src/math/polysat/clause.h Normal file
View file

@ -0,0 +1,85 @@
/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
polysat clauses
Author:
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-6
--*/
#pragma once
#include "math/polysat/boolean.h"
#include "math/polysat/types.h"
namespace polysat {
class signed_constraint;
class clause;
using clause_ref = ref<clause>;
using clause_ref_vector = sref_vector<clause>;
/// Disjunction of constraints represented by boolean literals
// NB code review:
// right, ref-count is unlikely the right mechanism.
// In the SAT solver all clauses are managed in one arena (auxiliarary and redundant)
// and deleted when they exist the arena.
//
class clause {
friend class constraint_manager;
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;
/* TODO: embed literals to save an indirection?
unsigned m_num_literals;
constraint* m_literals[0];
static size_t object_size(unsigned m_num_literals) {
return sizeof(clause) + m_num_literals * sizeof(constraint*);
}
*/
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);
}
public:
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(signed_constraint c, p_dependency_ref d);
static clause_ref from_literals(unsigned lvl, p_dependency_ref d, sat::literal_vector literals);
p_dependency* dep() const { return m_dep; }
unsigned level() const { return m_level; }
bool empty() const { return m_literals.empty(); }
unsigned size() const { return m_literals.size(); }
sat::literal operator[](unsigned idx) const { return m_literals[idx]; }
using const_iterator = typename sat::literal_vector::const_iterator;
const_iterator begin() const { return m_literals.begin(); }
const_iterator end() const { return m_literals.end(); }
bool is_always_false(solver& s) const;
bool is_currently_false(solver& s) const;
unsigned next_guess() {
SASSERT(m_next_guess < size());
return m_next_guess++;
}
std::ostream& display(std::ostream& out) const;
};
inline std::ostream& operator<<(std::ostream& out, clause const& c) { return c.display(out); }
}

View file

@ -16,6 +16,7 @@ Author:
#include "math/polysat/solver.h" #include "math/polysat/solver.h"
#include "math/polysat/log.h" #include "math/polysat/log.h"
#include "math/polysat/log_helper.h" #include "math/polysat/log_helper.h"
#include <algorithm>
namespace polysat { namespace polysat {
@ -46,40 +47,71 @@ namespace polysat {
m_needs_model = true; m_needs_model = true;
} }
void conflict_core::set(pvar v, vector<signed_constraint> const& cjust_v) { void conflict_core::set(pvar v) {
LOG("Conflict for v" << v << ": " << cjust_v); LOG("Conflict: v" << v);
SASSERT(empty()); SASSERT(empty());
NOT_IMPLEMENTED_YET();
m_conflict_var = v; m_conflict_var = v;
m_constraints.append(cjust_v);
if (cjust_v.empty())
m_constraints.push_back({});
m_needs_model = true; m_needs_model = true;
} }
void conflict_core::resolve(sat::bool_var var, clause const& cl) { void conflict_core::push(signed_constraint c) {
// TODO: fix the implementation: should resolve the given clause with the current conflict core. SASSERT(!empty()); // should use set() to enter conflict state
#if 0 // Skip trivial constraints
DEBUG_CODE({ // (e.g., constant ones such as "4 > 1"... only true ones should appear, otherwise the lemma would be a tautology)
bool this_has_pos = std::count(begin(), end(), sat::literal(var)) > 0; if (c.is_always_true())
bool this_has_neg = std::count(begin(), end(), ~sat::literal(var)) > 0; return;
bool other_has_pos = std::count(other.begin(), other.end(), sat::literal(var)) > 0; SASSERT(!c.is_always_false());
bool other_has_neg = std::count(other.begin(), other.end(), ~sat::literal(var)) > 0; m_constraints.push_back(c);
SASSERT(!this_has_pos || !this_has_neg); // otherwise this is tautology
SASSERT(!other_has_pos || !other_has_neg); // otherwise other is tautology
SASSERT((this_has_pos && other_has_neg) || (this_has_neg && other_has_pos));
});
// The resolved var should not be one of the new constraints
int j = 0;
for (auto lit : m_literals)
if (lit.var() != var)
m_literals[j++] = lit;
m_literals.shrink(j);
for (sat::literal lit : other)
if (lit.var() != var)
m_literals.push_back(lit);
return true;
#endif
} }
void conflict_core::resolve(constraint_manager const& m, sat::bool_var var, clause const& cl) {
// Note: core: x, y, z; corresponds to clause ~x \/ ~y \/ ~z
// clause: x \/ u \/ v
// resolvent: ~y \/ ~z \/ u \/ v; as core: y, z, ~u, ~v
SASSERT(var != sat::null_bool_var);
DEBUG_CODE({
bool core_has_pos = std::count_if(begin(), end(), [var](auto c){ return c.blit() == sat::literal(var); }) > 0;
bool core_has_neg = std::count_if(begin(), end(), [var](auto c){ return c.blit() == ~sat::literal(var); }) > 0;
bool clause_has_pos = std::count(cl.begin(), cl.end(), sat::literal(var)) > 0;
bool clause_has_neg = std::count(cl.begin(), cl.end(), ~sat::literal(var)) > 0;
SASSERT(!core_has_pos || !core_has_neg); // otherwise core is tautology
SASSERT(!clause_has_pos || !clause_has_neg); // otherwise clause is tautology
SASSERT((core_has_pos && clause_has_pos) || (core_has_neg && clause_has_neg));
});
int j = 0;
for (auto c : m_constraints)
if (c->bvar() == var)
m_constraints[j++] = c;
m_constraints.shrink(j);
for (sat::literal lit : cl)
if (lit.var() != var)
m_constraints.push_back(m.lookup(~lit));
}
clause_ref conflict_core::build_lemma(solver& s, unsigned trail_idx) {
sat::literal_vector literals;
p_dependency_ref dep = s.mk_dep_ref(null_dependency);
unsigned lvl = 0;
// TODO: another core reduction step?
for (auto c : m_constraints) {
if (c->unit_clause()) {
dep = s.m_dm.mk_join(dep, c->unit_dep());
continue;
}
lvl = std::max(lvl, c->level());
s.m_constraints.ensure_bvar(c.get());
literals.push_back(~c.blit());
}
if (m_needs_model) {
// TODO: add equalities corresponding to model up to trail_idx
}
return clause::from_literals(lvl, std::move(dep), std::move(literals));
}
} }

View file

@ -16,14 +16,12 @@ Author:
namespace polysat { namespace polysat {
class solver;
/** Conflict state, represented as core (~negation of clause). */ /** Conflict state, represented as core (~negation of clause). */
class conflict_core { class conflict_core {
vector<signed_constraint> m_constraints; vector<signed_constraint> m_constraints;
/** Storage for new constraints that may not yet have a boolean variable yet */
// TODO: not necessary anymore, if we keep constraint_manager::gc()
ptr_vector<constraint> m_storage;
// If this is not null_var, the conflict was due to empty viable set for this variable. // If this is not null_var, the conflict was due to empty viable set for this variable.
// Can be treated like "v = x" for any value x. // Can be treated like "v = x" for any value x.
pvar m_conflict_var = null_var; pvar m_conflict_var = null_var;
@ -35,21 +33,20 @@ namespace polysat {
// For example: if we have 4x+y=2 and y=0, then we have a conflict no matter the value of x, so we should drop x=? from the core. // For example: if we have 4x+y=2 and y=0, then we have a conflict no matter the value of x, so we should drop x=? from the core.
public: public:
~conflict_core() {
gc();
}
vector<signed_constraint> const& constraints() const { return m_constraints; } vector<signed_constraint> const& constraints() const { return m_constraints; }
bool needs_model() const { return m_needs_model; } bool needs_model() const { return m_needs_model; }
pvar conflict_var() const { return m_conflict_var; }
bool is_bailout() const { return m_constraints.size() == 1 && !m_constraints[0]; }
bool empty() const { bool empty() const {
return m_constraints.empty() && !m_needs_model; return m_constraints.empty() && !m_needs_model && m_conflict_var == null_var;
} }
void reset() { void reset() {
m_constraints.reset(); m_constraints.reset();
m_needs_model = false; m_needs_model = false;
gc(); m_conflict_var = null_var;
SASSERT(empty()); SASSERT(empty());
} }
@ -58,20 +55,21 @@ namespace polysat {
/** conflict because the constraint c is false under current variable assignment */ /** conflict because the constraint c is false under current variable assignment */
void set(signed_constraint c); void set(signed_constraint c);
/** conflict because there is no viable value for the variable v */ /** conflict because there is no viable value for the variable v */
void set(pvar v, vector<signed_constraint> const& cjust_v); void set(pvar v);
/** Garbage-collect temporary constraints */ void push(signed_constraint c);
void gc() {
for (auto* c : m_storage)
if (!c->has_bvar())
dealloc(c);
m_storage.reset();
}
/** Perform boolean resolution with the clause upon variable 'var'. /** Perform boolean resolution with the clause upon variable 'var'.
* Precondition: core/clause contain complementary 'var'-literals. * Precondition: core/clause contain complementary 'var'-literals.
*/ */
void resolve(sat::bool_var var, clause const& cl); void resolve(constraint_manager const& m, sat::bool_var var, clause const& cl);
/** Convert the core into a lemma to be learned. */
clause_ref build_lemma(solver& s, unsigned trail_idx);
using const_iterator = decltype(m_constraints)::const_iterator;
const_iterator begin() { return constraints().begin(); }
const_iterator end() { return constraints().end(); }
std::ostream& display(std::ostream& out) const; std::ostream& display(std::ostream& out) const;
}; };

View file

@ -13,6 +13,7 @@ Author:
--*/ --*/
#include "math/polysat/constraint.h" #include "math/polysat/constraint.h"
#include "math/polysat/clause.h"
#include "math/polysat/solver.h" #include "math/polysat/solver.h"
#include "math/polysat/log.h" #include "math/polysat/log.h"
#include "math/polysat/log_helper.h" #include "math/polysat/log_helper.h"
@ -21,9 +22,6 @@ Author:
namespace polysat { namespace polysat {
//static_assert(!std::is_copy_assignable_v<scoped_signed_constraint>);
//static_assert(!std::is_copy_constructible_v<scoped_signed_constraint>);
void constraint_manager::assign_bv2c(sat::bool_var bv, constraint* c) { void constraint_manager::assign_bv2c(sat::bool_var bv, constraint* c) {
SASSERT_EQ(get_bv2c(bv), nullptr); SASSERT_EQ(get_bv2c(bv), nullptr);
SASSERT(!c->has_bvar()); SASSERT(!c->has_bvar());
@ -42,8 +40,9 @@ namespace polysat {
return m_bv2constraint.get(bv, nullptr); return m_bv2constraint.get(bv, nullptr);
} }
void constraint_manager::assign_bvar(constraint* c) { void constraint_manager::ensure_bvar(constraint* c) {
assign_bv2c(m_bvars.new_var(), c); if (!c->has_bvar())
assign_bv2c(m_bvars.new_var(), c);
} }
void constraint_manager::erase_bvar(constraint* c) { void constraint_manager::erase_bvar(constraint* c) {
@ -141,16 +140,6 @@ namespace polysat {
if (it == m_constraint_table.end()) { if (it == m_constraint_table.end()) {
store(c1); store(c1);
m_constraint_table.insert(c1); m_constraint_table.insert(c1);
// Assuming c is a temporary constraint, we need to:
// 1. erase(c);
// 2. m_constraint_table.remove(c);
// before we dealloc it.
// But even if we don't do this, there is not a real memory leak because the constraint will be deallocated properly when the level is popped.
// So maybe the best way is to just do periodic GC passes where we throw out constraints that do not have a boolean variable,
// instead of precise lifetime tracking for temprorary constraints.
// It should be safe to do a GC pass outside of conflict resolution.
// TODO: if this is the path we take, then we can drop the scoped_signed_constraint and the scoped_constraint_ptr classes.
// TODO: we could maintain a counter of temporary constraints (#constraints - #bvars) to decide when to do a GC, or just do it after every conflict resolution
return c1; return c1;
} }
constraint* c0 = *it; constraint* c0 = *it;
@ -182,6 +171,7 @@ namespace polysat {
bool constraint_manager::should_gc() { bool constraint_manager::should_gc() {
// TODO: maybe replace this by a better heuristic // TODO: maybe replace this by a better heuristic
// maintain a counter of temporary constraints? (#constraints - #bvars)
return true; return true;
} }
@ -241,7 +231,9 @@ namespace polysat {
} }
std::ostream& constraint::display_extra(std::ostream& out, lbool status) const { std::ostream& constraint::display_extra(std::ostream& out, lbool status) const {
out << " @" << level() << " (b" << bvar() << ")"; out << " @" << level() << " (b";
if (has_bvar()) { out << bvar(); } else { out << "_"; }
out << ")";
(void)status; (void)status;
// if (is_positive()) out << " [pos]"; // if (is_positive()) out << " [pos]";
// if (is_negative()) out << " [neg]"; // if (is_negative()) out << " [neg]";
@ -277,42 +269,4 @@ namespace polysat {
narrow(s, is_positive); narrow(s, is_positive);
} }
clause_ref clause::from_unit(signed_constraint c, p_dependency_ref d) {
SASSERT(c->has_bvar());
unsigned const lvl = c->level();
sat::literal_vector lits;
lits.push_back(c.blit());
return clause::from_literals(lvl, std::move(d), std::move(lits));
}
clause_ref clause::from_literals(unsigned lvl, p_dependency_ref d, sat::literal_vector literals) {
return alloc(clause, lvl, std::move(d), std::move(literals));
}
bool clause::is_always_false(solver& s) const {
return std::all_of(m_literals.begin(), m_literals.end(), [&s](sat::literal lit) {
signed_constraint c = s.m_constraints.lookup(lit);
return c.is_always_false();
});
}
bool clause::is_currently_false(solver& s) const {
return std::all_of(m_literals.begin(), m_literals.end(), [&s](sat::literal lit) {
signed_constraint c = s.m_constraints.lookup(lit);
return c.is_currently_false(s);
});
}
std::ostream& clause::display(std::ostream& out) const {
bool first = true;
for (auto lit : *this) {
if (first)
first = false;
else
out << " \\/ ";
out << lit;
}
return out;
}
} }

View file

@ -13,13 +13,9 @@ Author:
--*/ --*/
#pragma once #pragma once
#include "math/polysat/boolean.h" #include "math/polysat/boolean.h"
#include "math/polysat/clause.h"
#include "math/polysat/types.h" #include "math/polysat/types.h"
#include "math/polysat/interval.h" #include "math/polysat/interval.h"
#include "math/polysat/log.h"
#include "util/map.h"
#include "util/ref.h"
#include "util/ref_vector.h"
#include <type_traits>
namespace polysat { namespace polysat {
@ -30,11 +26,6 @@ namespace polysat {
class ule_constraint; class ule_constraint;
class signed_constraint; class signed_constraint;
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>>; using constraint_table = ptr_hashtable<constraint, obj_ptr_hash<constraint>, deref_eq<constraint>>;
// Manage constraint lifetime, deduplication, and connection to boolean variables/literals. // Manage constraint lifetime, deduplication, and connection to boolean variables/literals.
@ -71,9 +62,9 @@ namespace polysat {
constraint_manager(bool_var_manager& bvars): m_bvars(bvars) {} constraint_manager(bool_var_manager& bvars): m_bvars(bvars) {}
~constraint_manager(); ~constraint_manager();
void assign_bvar(constraint* c); void ensure_bvar(constraint* c);
void erase_bvar(constraint* c); void erase_bvar(constraint* c);
sat::literal get_or_assign_blit(signed_constraint& c); // sat::literal get_or_assign_blit(signed_constraint& c);
clause* store(clause_ref cl); clause* store(clause_ref cl);
@ -129,6 +120,7 @@ namespace polysat {
*/ */
// NB code review: the convention would make sense. Unfortunately, elsewhere in z3 we use "true" for negative literals // NB code review: the convention would make sense. Unfortunately, elsewhere in z3 we use "true" for negative literals
// and "false" for positive literals. It is called the "sign" bit. // and "false" for positive literals. It is called the "sign" bit.
// TODO: replace parameter 'is_positive' everywhere by 'sign'? (also in signed_constraint)
sat::bool_var m_bvar = sat::null_bool_var; sat::bool_var m_bvar = sat::null_bool_var;
constraint(constraint_manager& m, unsigned lvl, ckind_t k): constraint(constraint_manager& m, unsigned lvl, ckind_t k):
@ -169,7 +161,7 @@ namespace polysat {
clause* unit_clause() const { return m_unit_clause; } 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; } 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; p_dependency* unit_dep() const { return m_unit_clause ? m_unit_clause->dep() : nullptr; }
/** Precondition: all variables other than v are assigned. /** Precondition: all variables other than v are assigned.
* *
@ -200,9 +192,8 @@ namespace polysat {
SASSERT_EQ(blit(), lit); SASSERT_EQ(blit(), lit);
} }
void negate() { void negate() { m_positive = !m_positive; }
m_positive = !m_positive; signed_constraint operator~() const { return {get(), !is_positive()}; }
}
bool is_positive() const { return m_positive; } bool is_positive() const { return m_positive; }
bool is_negative() const { return !is_positive(); } bool is_negative() const { return !is_positive(); }
@ -210,6 +201,7 @@ namespace polysat {
bool propagate(solver& s, pvar v) { return get()->propagate(s, is_positive(), v); } 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); } 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_always_false() { return get()->is_always_false(is_positive()); }
bool is_always_true() { return get()->is_always_false(is_negative()); }
bool is_currently_false(solver& s) { return get()->is_currently_false(s, 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()); } bool is_currently_true(solver& s) { return get()->is_currently_true(s, is_positive()); }
void narrow(solver& s) { get()->narrow(s, is_positive()); } void narrow(solver& s) { get()->narrow(s, is_positive()); }
@ -219,7 +211,6 @@ namespace polysat {
sat::literal blit() const { return sat::literal(bvar(), is_negative()); } sat::literal blit() const { return sat::literal(bvar(), is_negative()); }
constraint* get() const { return m_constraint; } constraint* get() const { return m_constraint; }
explicit operator bool() const { return !!m_constraint; } explicit operator bool() const { return !!m_constraint; }
bool operator!() const { return !m_constraint; } bool operator!() const { return !m_constraint; }
constraint* operator->() const { return get(); } constraint* operator->() const { return get(); }
@ -243,71 +234,4 @@ namespace polysat {
inline std::ostream& operator<<(std::ostream& out, signed_constraint const& c) { inline std::ostream& operator<<(std::ostream& out, signed_constraint const& c) {
return c.display(out); return c.display(out);
} }
inline signed_constraint operator~(signed_constraint const& c) {
return {c.get(), !c.is_positive()};
}
/// Disjunction of constraints represented by boolean literals
// NB code review:
// right, ref-count is unlikely the right mechanism.
// In the SAT solver all clauses are managed in one arena (auxiliarary and redundant)
// and deleted when they exist the arena.
//
class clause {
friend class constraint_manager;
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;
/* TODO: embed literals to save an indirection?
unsigned m_num_literals;
constraint* m_literals[0];
static size_t object_size(unsigned m_num_literals) {
return sizeof(clause) + m_num_literals * sizeof(constraint*);
}
*/
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);
}
public:
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(signed_constraint c, p_dependency_ref d);
static clause_ref from_literals(unsigned lvl, p_dependency_ref d, sat::literal_vector literals);
p_dependency* dep() const { return m_dep; }
unsigned level() const { return m_level; }
bool empty() const { return m_literals.empty(); }
unsigned size() const { return m_literals.size(); }
sat::literal operator[](unsigned idx) const { return m_literals[idx]; }
using const_iterator = typename sat::literal_vector::const_iterator;
const_iterator begin() const { return m_literals.begin(); }
const_iterator end() const { return m_literals.end(); }
bool is_always_false(solver& s) const;
bool is_currently_false(solver& s) const;
unsigned next_guess() {
SASSERT(m_next_guess < size());
return m_next_guess++;
}
std::ostream& display(std::ostream& out) const;
};
inline std::ostream& operator<<(std::ostream& out, clause const& c) { return c.display(out); }
inline p_dependency* constraint::unit_dep() const { return m_unit_clause ? m_unit_clause->dep() : nullptr; }
} }

View file

@ -18,12 +18,16 @@ Author:
namespace polysat { namespace polysat {
class eq_constraint final : public constraint { class eq_constraint final : public constraint {
friend class constraint_manager;
pdd m_poly; pdd m_poly;
public:
eq_constraint(constraint_manager& m, unsigned lvl, pdd const& p): eq_constraint(constraint_manager& m, unsigned lvl, pdd const& p):
constraint(m, lvl, ckind_t::eq_t), m_poly(p) { constraint(m, lvl, ckind_t::eq_t), m_poly(p) {
m_vars.append(p.free_vars()); m_vars.append(p.free_vars());
} }
public:
~eq_constraint() override {} ~eq_constraint() override {}
pdd const & p() const { return m_poly; } pdd const & p() const { return m_poly; }
std::ostream& display(std::ostream& out, lbool status) const override; std::ostream& display(std::ostream& out, lbool status) const override;

View file

@ -22,6 +22,7 @@ namespace polysat {
return false; return false;
} }
#if 0
conflict_explainer::conflict_explainer(solver& s): m_solver(s) { conflict_explainer::conflict_explainer(solver& s): m_solver(s) {
inference_engines.push_back(alloc(inf_polynomial_superposition)); inference_engines.push_back(alloc(inf_polynomial_superposition));
} }
@ -32,6 +33,7 @@ namespace polysat {
return true; return true;
return false; return false;
} }
#endif
@ -582,7 +584,4 @@ namespace polysat {
// return true; // return true;
// } // }
void conflict_explainer::resolve()
{
}
} }

View file

@ -29,7 +29,7 @@ namespace polysat {
virtual bool perform(conflict_explainer& ce) = 0; virtual bool perform(conflict_explainer& ce) = 0;
}; };
class inf_polynomial_superposition : public inference_engine { class inf_polynomial_superposition final : public inference_engine {
public: public:
bool perform(conflict_explainer& ce) override; bool perform(conflict_explainer& ce) override;
}; };
@ -40,14 +40,35 @@ namespace polysat {
// clause_ref by_ugt_z(); // clause_ref by_ugt_z();
// clause_ref y_ule_ax_and_x_ule_z(); // clause_ref y_ule_ax_and_x_ule_z();
class core_saturation final {
scoped_ptr_vector<inference_engine> inference_engines;
public:
/// Derive new constraints from constraints containing the variable v (i.e., at least one premise must contain v)
bool saturate(pvar v, conflict_core& core) { NOT_IMPLEMENTED_YET(); return false; }
};
#if 0
class conflict_explainer { class conflict_explainer {
solver& m_solver; solver& m_solver;
conflict_core m_conflict; // conflict_core m_conflict;
vector<constraint> m_new_assertions; // to be inserted into Gamma (conclusions from saturation)
scoped_ptr_vector<inference_engine> inference_engines; scoped_ptr_vector<inference_engine> inference_engines;
bool push_omega_mul(clause_builder& clause, unsigned level, unsigned p, pdd const& x, pdd const& y); bool push_omega_mul(clause_builder& clause, unsigned level, unsigned p, pdd const& x, pdd const& y);
// Gamma
// search_state& search() { return m_solver.m_search; }
// Core
// conflict_core& conflict() { return m_solver.m_conflict; }
public: public:
/** Create empty conflict */ /** Create empty conflict */
conflict_explainer(solver& s); conflict_explainer(solver& s);
@ -58,7 +79,7 @@ namespace polysat {
bool saturate(); bool saturate();
/** resolve conflict state against assignment to v */ /** resolve conflict state against assignment to v */
void resolve(pvar v, ptr_vector<constraint> const& cjust_v); void resolve(pvar v, ptr_vector<constraint> const& cjust_v); // TODO: try variable elimination of 'v', if not possible, core saturation and core reduction. (actually reduction could be one specific VE method).
void resolve(sat::literal lit); void resolve(sat::literal lit);
// TODO: move conflict resolution from solver into this class. // TODO: move conflict resolution from solver into this class.
@ -68,4 +89,5 @@ namespace polysat {
/** conflict resolution until first (relevant) decision */ /** conflict resolution until first (relevant) decision */
void resolve(); void resolve();
}; };
#endif
} }

View file

@ -20,6 +20,7 @@ Author:
#include "math/polysat/explain.h" #include "math/polysat/explain.h"
#include "math/polysat/log.h" #include "math/polysat/log.h"
#include "math/polysat/forbidden_intervals.h" #include "math/polysat/forbidden_intervals.h"
#include "math/polysat/variable_elimination.h"
// For development; to be removed once the linear solver works well enough // For development; to be removed once the linear solver works well enough
#define ENABLE_LINEAR_SOLVER 0 #define ENABLE_LINEAR_SOLVER 0
@ -142,6 +143,7 @@ namespace polysat {
VERIFY(at_base_level()); VERIFY(at_base_level());
SASSERT(c); SASSERT(c);
SASSERT(activate || dep != null_dependency); // if we don't activate the constraint, we need the dependency to access it again later. SASSERT(activate || dep != null_dependency); // if we don't activate the constraint, we need the dependency to access it again later.
m_constraints.ensure_bvar(c.get());
clause* unit = m_constraints.store(clause::from_unit(c, mk_dep_ref(dep))); clause* unit = m_constraints.store(clause::from_unit(c, mk_dep_ref(dep)));
c->set_unit_clause(unit); c->set_unit_clause(unit);
if (dep != null_dependency) if (dep != null_dependency)
@ -399,17 +401,19 @@ namespace polysat {
} }
void solver::set_conflict(pvar v) { void solver::set_conflict(pvar v) {
m_conflict.set(v, m_cjust[v]); m_conflict.set(v);
} }
void solver::set_marks(conflict_core const& cc) { void solver::set_marks(conflict_core const& cc) {
if (cc.conflict_var() != null_var)
set_mark(cc.conflict_var());
for (auto c : cc.constraints()) for (auto c : cc.constraints())
if (c) if (c)
set_marks(*c); set_marks(*c);
} }
void solver::set_marks(constraint const& c) { void solver::set_marks(constraint const& c) {
if (c.bvar() != sat::null_bool_var) if (c.has_bvar())
m_bvars.set_mark(c.bvar()); m_bvars.set_mark(c.bvar());
for (auto v : c.vars()) for (auto v : c.vars())
set_mark(v); set_mark(v);
@ -440,183 +444,110 @@ namespace polysat {
SASSERT(is_conflict()); SASSERT(is_conflict());
NOT_IMPLEMENTED_YET(); // TODO: needs to be refactored to use conflict_core, will be moved to conflict_explainer if (m_conflict.is_bailout()) {
/*
if (m_conflict.units().size() == 1 && !m_conflict.units()[0]) {
report_unsat(); report_unsat();
return; return;
} }
pvar conflict_var = null_var; if (m_conflict.conflict_var() != null_var) {
clause_ref lemma; // This case corresponds to a propagation of conflict_var, except it's not explicitly on the stack.
for (auto v : m_conflict.vars(m_constraints)) resolve_value(m_conflict.conflict_var());
if (!m_viable.has_viable(v)) {
SASSERT(conflict_var == null_var || conflict_var == v); // at most one variable can be empty
conflict_var = v;
}
reset_marks();
m_bvars.reset_marks();
set_marks(m_conflict);
if (m_conflict.clauses().empty() && conflict_var != null_var) {
LOG_H2("Conflict due to empty viable set for pvar " << conflict_var);
clause_ref new_lemma;
if (forbidden_intervals::explain(*this, m_conflict.units(), conflict_var, new_lemma)) {
SASSERT(new_lemma);
clause& cl = *new_lemma.get();
LOG_H3("Lemma from forbidden intervals (size: " << cl.size() << ")");
for (sat::literal lit : cl) {
LOG("Literal: " << lit);
constraint* c = m_constraints.lookup(lit.var());
for (auto v : c->vars())
set_mark(v);
}
SASSERT(cl.size() > 0);
lemma = std::move(new_lemma);
m_conflict.reset();
m_conflict.push_back(lemma);
reset_marks();
m_bvars.reset_marks();
set_marks(*lemma.get());
}
else {
conflict_explainer cx(*this, m_conflict);
lemma = cx.resolve(conflict_var, {});
LOG("resolved: " << show_deref(lemma));
// SASSERT(false && "pause on explanation");
}
} }
reset_marks();
set_marks(m_conflict);
for (unsigned i = m_search.size(); i-- > 0; ) { for (unsigned i = m_search.size(); i-- > 0; ) {
LOG("Conflict: " << m_conflict);
auto const& item = m_search[i]; auto const& item = m_search[i];
if (item.is_assignment()) { if (item.is_assignment()) {
// Resolve over variable assignment // Resolve over variable assignment
pvar v = item.var(); pvar v = item.var();
LOG_H2("Working on pvar " << v); LOG_H2("Working on pvar v" << v);
if (!is_marked(v)) if (!is_marked(v))
continue; continue;
justification& j = m_justification[v]; justification& j = m_justification[v];
LOG("Justification: " << j); LOG("Justification: " << j);
if (j.level() <= base_level()) { if (j.level() <= base_level())
report_unsat(); break;
return;
}
if (j.is_decision()) { if (j.is_decision()) {
revert_decision(v, lemma); revert_decision(v);
return; return;
} }
SASSERT(j.is_propagation()); SASSERT(j.is_propagation());
LOG("Lemma: " << show_deref(lemma)); if (!resolve_value(v)) {
clause_ref new_lemma = resolve(v); resolve_bailout(i);
LOG("New Lemma: " << show_deref(new_lemma));
// SASSERT(new_lemma); // TODO: only for debugging, to have a breakpoint on resolution failure
if (!new_lemma) {
backtrack(i, lemma);
return; return;
} }
if (new_lemma->is_always_false(*this)) {
clause* cl = new_lemma.get();
learn_lemma(v, std::move(new_lemma));
m_conflict.reset();
m_conflict.push_back(cl);
report_unsat();
return;
}
if (!new_lemma->is_currently_false(*this)) {
backtrack(i, lemma);
return;
}
lemma = std::move(new_lemma);
reset_marks(); reset_marks();
m_bvars.reset_marks(); set_marks(m_conflict);
set_marks(*lemma.get());
m_conflict.reset();
m_conflict.push_back(lemma.get());
} }
else { else {
// Resolve over boolean literal // Resolve over boolean literal
SASSERT(item.is_boolean()); SASSERT(item.is_boolean());
sat::literal const lit = item.lit(); sat::literal const lit = item.lit();
LOG_H2("Working on boolean literal " << lit); LOG_H2("Working on blit " << lit);
sat::bool_var const var = lit.var(); sat::bool_var const var = lit.var();
if (!m_bvars.is_marked(var)) if (!m_bvars.is_marked(var))
continue; continue;
if (m_bvars.level(var) <= base_level()) { if (m_bvars.level(var) <= base_level())
report_unsat(); break;
return;
}
if (m_bvars.is_decision(var)) { if (m_bvars.is_decision(var)) {
// SASSERT(std::count(lemma->begin(), lemma->end(), ~lit) > 0); revert_bool_decision(lit);
revert_bool_decision(lit, lemma);
return; return;
} }
SASSERT(m_bvars.is_propagation(var)); SASSERT(m_bvars.is_propagation(var));
LOG("Lemma: " << show_deref(lemma)); resolve_bool(lit);
clause_ref new_lemma = resolve_bool(lit);
if (!new_lemma) {
backtrack(i, lemma);
return;
}
SASSERT(new_lemma);
LOG("new_lemma: " << show_deref(new_lemma));
LOG("new_lemma is always false: " << new_lemma->is_always_false(*this));
if (new_lemma->is_always_false(*this)) {
// learn_lemma(v, new_lemma);
m_conflict.reset();
m_conflict.push_back(std::move(new_lemma));
report_unsat();
return;
}
LOG("new_lemma is currently false: " << new_lemma->is_currently_false(*this));
// if (!new_lemma->is_currently_false(*this)) {
// backtrack(i, lemma);
// return;
// }
lemma = std::move(new_lemma);
reset_marks(); reset_marks();
m_bvars.reset_marks(); set_marks(m_conflict);
set_marks(*lemma.get());
m_conflict.reset();
m_conflict.push_back(lemma.get());
} }
} }
report_unsat(); report_unsat();
*/
} }
clause_ref solver::resolve_bool(sat::literal lit) { /** Conflict resolution case where propagation 'v := ...' is on top of the stack */
NOT_IMPLEMENTED_YET(); return nullptr; bool solver::resolve_value(pvar v) {
/* SASSERT(m_justification[v].is_propagation());
if (m_conflict.size() != 1) // Conceptually:
return nullptr; // - Value Resolution
if (m_conflict.clauses().size() != 1) // - Variable Elimination
return nullptr; // - if VE isn't possible, try to derive new constraints using core saturation
LOG_H3("resolve_bool");
clause* lemma = m_conflict.clauses()[0];
SASSERT(lemma);
SASSERT(m_bvars.is_propagation(lit.var()));
clause* other = m_bvars.reason(lit.var());
SASSERT(other);
LOG("lemma: " << show_deref(lemma));
LOG("other: " << show_deref(other));
VERIFY(lemma->resolve(lit.var(), *other));
LOG("resolved: " << show_deref(lemma));
// unassign constraints whose current value does not agree with their occurrence in the lemma // Value Resolution
for (sat::literal lit : *lemma) { for (auto c : m_cjust[v])
constraint *c = m_constraints.lookup(lit.var()); m_conflict.push(c);
if (!c->is_undef() && c ->blit() != lit) {
LOG("unassigning: " << show_deref(c)); // Variable elimination
c->unassign(); while (true) {
} // TODO:
// 1. Try variable elimination of 'v'
// 2. If not possible, try saturation and core reduction (actually reduction could be one specific VE method?).
// 3. as a last resort, substitute v by m_value[v]?
variable_elimination ve;
if (ve.perform(v, m_conflict))
return true;
core_saturation cs;
if (!cs.saturate(v, m_conflict))
return false;
} }
return lemma; // currently modified in-place return false;
*/
} }
void solver::backtrack(unsigned i, clause_ref lemma) { /** Conflict resolution case where boolean literal 'lit' is on top of the stack */
void solver::resolve_bool(sat::literal lit) {
LOG_H3("resolve_bool: " << lit);
SASSERT(m_bvars.is_propagation(lit.var()));
clause* other = m_bvars.reason(lit.var());
m_conflict.resolve(m_constraints, lit.var(), *other);
}
void solver::resolve_bailout(unsigned i) {
// TODO: conflict resolution failed or was aborted. what to do with the current conflict core?
// (we could still use it as lemma, but it probably doesn't help much)
NOT_IMPLEMENTED_YET(); NOT_IMPLEMENTED_YET();
/* /*
do { do {
@ -766,7 +697,7 @@ namespace polysat {
* In general form it can rely on factoring. * In general form it can rely on factoring.
* Root finding can further prune viable. * Root finding can further prune viable.
*/ */
void solver::revert_decision(pvar v, clause_ref reason) { void solver::revert_decision(pvar v) {
rational val = m_value[v]; rational val = m_value[v];
LOG_H3("Reverting decision: pvar " << v << " := " << val); LOG_H3("Reverting decision: pvar " << v << " := " << val);
NOT_IMPLEMENTED_YET(); NOT_IMPLEMENTED_YET();
@ -818,7 +749,7 @@ namespace polysat {
*/ */
} }
void solver::revert_bool_decision(sat::literal lit, clause_ref reason) { void solver::revert_bool_decision(sat::literal lit) {
sat::bool_var const var = lit.var(); sat::bool_var const var = lit.var();
LOG_H3("Reverting boolean decision: " << lit); LOG_H3("Reverting boolean decision: " << lit);
SASSERT(m_bvars.is_decision(var)); SASSERT(m_bvars.is_decision(var));
@ -1010,6 +941,7 @@ namespace polysat {
} }
void solver::reset_marks() { void solver::reset_marks() {
m_bvars.reset_marks();
LOG_V("-------------------------- (reset variable marks)"); LOG_V("-------------------------- (reset variable marks)");
m_marks.reserve(m_vars.size()); m_marks.reserve(m_vars.size());
m_clock++; m_clock++;

View file

@ -53,6 +53,7 @@ namespace polysat {
friend class ule_constraint; friend class ule_constraint;
friend class clause; friend class clause;
friend class clause_builder; friend class clause_builder;
friend class conflict_core;
friend class conflict_explainer; friend class conflict_explainer;
friend class forbidden_intervals; friend class forbidden_intervals;
friend class linear_solver; friend class linear_solver;
@ -191,8 +192,6 @@ namespace polysat {
unsigned m_conflict_level { 0 }; unsigned m_conflict_level { 0 };
clause_ref resolve_bool(sat::literal lit);
bool can_decide() const { return !m_free_vars.empty(); } bool can_decide() const { return !m_free_vars.empty(); }
void decide(); void decide();
void decide(pvar v); void decide(pvar v);
@ -211,10 +210,13 @@ namespace polysat {
unsigned base_level() const; unsigned base_level() const;
void resolve_conflict(); void resolve_conflict();
void backtrack(unsigned i, clause_ref lemma); bool resolve_value(pvar v);
void resolve_bool(sat::literal lit);
void resolve_bailout(unsigned i);
void revert_decision(pvar v);
void revert_bool_decision(sat::literal lit);
void report_unsat(); void report_unsat();
void revert_decision(pvar v, clause_ref reason);
void revert_bool_decision(sat::literal lit, clause_ref reason);
void learn_lemma(pvar v, clause_ref lemma); void learn_lemma(pvar v, clause_ref lemma);
void backjump(unsigned new_level); void backjump(unsigned new_level);
void add_lemma(clause_ref lemma); void add_lemma(clause_ref lemma);

View file

@ -14,6 +14,7 @@ Author:
#include "util/dependency.h" #include "util/dependency.h"
#include "util/trail.h" #include "util/trail.h"
#include "util/lbool.h" #include "util/lbool.h"
#include "util/map.h"
#include "util/rlimit.h" #include "util/rlimit.h"
#include "util/scoped_ptr_vector.h" #include "util/scoped_ptr_vector.h"
#include "util/var_queue.h" #include "util/var_queue.h"

View file

@ -18,9 +18,11 @@ Author:
namespace polysat { namespace polysat {
class ule_constraint final : public constraint { class ule_constraint final : public constraint {
friend class constraint_manager;
pdd m_lhs; pdd m_lhs;
pdd m_rhs; pdd m_rhs;
public:
ule_constraint(constraint_manager& m, unsigned lvl, pdd const& l, pdd const& r): ule_constraint(constraint_manager& m, unsigned lvl, pdd const& l, pdd const& r):
constraint(m, lvl, ckind_t::ule_t), m_lhs(l), m_rhs(r) { constraint(m, lvl, ckind_t::ule_t), m_lhs(l), m_rhs(r) {
m_vars.append(l.free_vars()); m_vars.append(l.free_vars());
@ -28,6 +30,8 @@ namespace polysat {
if (!m_vars.contains(v)) if (!m_vars.contains(v))
m_vars.push_back(v); m_vars.push_back(v);
} }
public:
~ule_constraint() override {} ~ule_constraint() override {}
pdd const& lhs() const { return m_lhs; } pdd const& lhs() const { return m_lhs; }
pdd const& rhs() const { return m_rhs; } pdd const& rhs() const { return m_rhs; }

View file

@ -0,0 +1,19 @@
/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
Polysat variable elimination
Author:
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-6
--*/
#include "math/polysat/variable_elimination.h"
namespace polysat {
}

View file

@ -0,0 +1,52 @@
/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
Polysat variable elimination
Author:
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-6
--*/
#pragma once
#include "math/polysat/conflict_core.h"
namespace polysat {
class variable_elimination_engine {
public:
virtual ~variable_elimination_engine() {}
virtual bool perform(pvar v, conflict_core& core) = 0;
};
// discovers when a variable has already been removed... (special case of ve_reduction?)
class ve_trivial final : public variable_elimination_engine {
public:
bool perform(pvar v, conflict_core& core) override { NOT_IMPLEMENTED_YET(); return false; }
};
// ve by core reduction: try core reduction on all constraints that contain the variable to be eliminated.
// if we cannot eliminate all such constraints, then should we keep all of them instead of eliminating only some? since they might still be useful for saturation.
class ve_reduction final : public variable_elimination_engine {
public:
bool perform(pvar v, conflict_core& core) override { NOT_IMPLEMENTED_YET(); return false; }
};
class ve_forbidden_intervals final : public variable_elimination_engine {
public:
bool perform(pvar v, conflict_core& core) override { NOT_IMPLEMENTED_YET(); return false; }
};
class variable_elimination final {
scoped_ptr_vector<variable_elimination_engine> ve_engines;
public:
variable_elimination() {}
/// Try to eliminate the variable v from the given core
bool perform(pvar v, conflict_core& core) { NOT_IMPLEMENTED_YET(); return false; }
};
}