3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-22 16:45:31 +00:00

Add level to conflict

- reset conflict at correct level when popping user scopes
- functions as flag when handling inconsistent input (e.g., opposite literals)
- now all constraints in the conflict core should have bvalue == l_true
This commit is contained in:
Jakob Rath 2022-09-23 15:54:37 +02:00
parent 86d00b536a
commit a4f0e3a228
5 changed files with 87 additions and 28 deletions

View file

@ -140,10 +140,14 @@ namespace polysat {
}
bool conflict::empty() const {
return m_literals.empty()
&& m_vars.empty()
&& m_bail_vars.empty()
&& m_lemmas.empty();
bool const is_empty = (m_level == UINT_MAX);
if (is_empty) {
SASSERT(m_literals.empty());
SASSERT(m_vars.empty());
SASSERT(m_bail_vars.empty());
SASSERT(m_lemmas.empty());
}
return is_empty;
}
void conflict::reset() {
@ -154,6 +158,7 @@ namespace polysat {
m_var_occurrences.reset();
m_lemmas.reset();
m_kind = conflict_kind_t::ok;
m_level = UINT_MAX;
SASSERT(empty());
}
@ -182,7 +187,6 @@ namespace polysat {
return true;
case conflict_kind_t::backtrack:
return pvar_occurs_in_constraints(v) || m_relevant_vars.contains(v);
// return m_relevant_vars.contains(v);
case conflict_kind_t::backjump:
UNREACHABLE(); // we don't follow the regular loop when backjumping
return false;
@ -195,8 +199,30 @@ namespace polysat {
return contains(lit) || contains(~lit);
}
void conflict::init_at_base_level() {
SASSERT(empty());
SASSERT(s.at_base_level());
m_level = s.m_level;
SASSERT(!empty());
}
void conflict::init(signed_constraint c) {
SASSERT(empty());
m_level = s.m_level;
/*
// NOTE: c.is_always_false() may happen e.g. if we add an always false constraint in the input
if (c.is_always_false()) {
SASSERT(c.bvalue(s) == l_true);
SASSERT(s.m_bvars.is_assumption(c.blit()));
SASSERT(s.at_base_level());
// TODO: this kind of constraint should be handled when it is being added to the solver.
return;
}
if (c.bvalue(s) == l_false && s.at_base_level()) {
// We have opposite literals in the input.
return;
}
*/
set_impl(c);
logger().begin_conflict();
}
@ -238,6 +264,7 @@ namespace polysat {
// return;
// LOG("Conflict: " << cl);
SASSERT(empty());
m_level = s.m_level;
for (auto lit : cl) {
auto c = s.lit2cnstr(lit);
SASSERT(c.bvalue(s) == l_false);
@ -248,6 +275,8 @@ namespace polysat {
}
void conflict::init(pvar v, bool by_viable_fallback) {
SASSERT(empty());
m_level = s.m_level;
if (by_viable_fallback) {
logger().begin_conflict(header_with_var("unsat core from viable fallback for v", v));
// Conflict detected by viable fallback:
@ -264,6 +293,7 @@ namespace polysat {
// but each branch exclude the current assignment.
// in those cases we will (additionally?) need an abstraction that is asserting to make sure viable is updated properly.
}
SASSERT(!empty());
}
bool conflict::contains(sat::literal lit) const {
@ -276,7 +306,8 @@ namespace polysat {
return;
if (c.is_always_true())
return;
LOG("Inserting: " << c);
LOG("Inserting " << lit_pp(s, c));
SASSERT(c.bvalue(s) == l_true);
SASSERT(!c.is_always_false()); // if we added c, the core would be a tautology
SASSERT(!c->vars().empty());
m_literals.insert(c.blit().index());
@ -424,9 +455,12 @@ namespace polysat {
LOG("core: " << *this);
clause_builder lemma(s);
// TODO: is this sound, doing it for each constraint separately?
for (auto c : *this)
#if 0
if (m_literals.size() == 1) {
auto c = *begin();
minimize_vars(c);
}
#endif
for (auto c : *this)
lemma.push(~c);

View file

@ -32,6 +32,7 @@ Notes:
- All literals should be assigned in the stack prior to their use;
or justified by one of the side lemmas.
(thus: all literals in the core must have bvalue == l_true)
l <- D => l, < Vars, { l } u C > ===> < Vars, C u D >
l <- ?, < Vars, { l } u C > ===> ~l <- (C & Vars = value(Vars) => ~l)
@ -112,6 +113,9 @@ namespace polysat {
conflict_kind_t m_kind = conflict_kind_t::ok;
// Level at which the conflict was discovered
unsigned m_level = UINT_MAX;
void set_impl(signed_constraint c);
bool minimize_vars(signed_constraint c);
@ -131,6 +135,8 @@ namespace polysat {
uint_set const& vars() const { return m_vars; }
uint_set const& bail_vars() const { return m_bail_vars; }
unsigned level() const { return m_level; }
conflict_kind_t kind() const { return m_kind; }
bool is_bailout() const { return m_kind == conflict_kind_t::bailout; }
bool is_backtracking() const { return m_kind == conflict_kind_t::backtrack; }
@ -142,6 +148,8 @@ namespace polysat {
bool is_relevant_pvar(pvar v) const;
bool is_relevant(sat::literal lit) const;
/** conflict due to obvious input inconsistency */
void init_at_base_level();
/** conflict because the constraint c is false under current variable assignment */
void init(signed_constraint c);
/** boolean conflict with the given clause */

View file

@ -185,14 +185,21 @@ namespace polysat {
LOG("New constraint: " << c);
switch (m_bvars.value(lit)) {
case l_false:
set_conflict(c);
// Input literal contradicts current boolean state (e.g., opposite literals in the input)
// => conflict only flags the inconsistency
set_conflict_at_base_level();
SASSERT(dep == null_dependency && "track dependencies is TODO");
break;
return;
case l_true:
// constraint c is already asserted
SASSERT(m_bvars.level(lit) <= m_level);
break;
case l_undef:
if (c.is_always_false()) {
// asserted an always-false constraint
set_conflict_at_base_level();
return;
}
m_bvars.assumption(lit, m_level, dep);
m_trail.push_back(trail_instr_t::assign_bool_i);
m_search.push_boolean(lit);
@ -915,10 +922,11 @@ namespace polysat {
}
void solver::pop(unsigned num_scopes) {
unsigned base_level = m_base_levels[m_base_levels.size() - num_scopes];
LOG("Pop " << num_scopes << " user scopes; lowest popped level = " << base_level << "; current level = " << m_level);
unsigned const base_level = m_base_levels[m_base_levels.size() - num_scopes];
LOG("Pop " << num_scopes << " user scopes");
pop_levels(m_level - base_level + 1);
m_conflict.reset();
if (m_level < m_conflict.level())
m_conflict.reset();
m_base_levels.shrink(m_base_levels.size() - num_scopes);
}

View file

@ -185,6 +185,7 @@ namespace polysat {
void erase_pwatch(pvar v, constraint* c);
void erase_pwatch(constraint* c);
void set_conflict_at_base_level() { m_conflict.init_at_base_level(); }
void set_conflict(signed_constraint c) { m_conflict.init(c); }
void set_conflict(clause& cl) { m_conflict.init(cl); }
void set_conflict(pvar v, bool by_viable_fallback) { m_conflict.init(v, by_viable_fallback); }
@ -428,6 +429,7 @@ namespace polysat {
solver const& s;
sat::literal lit;
public:
lit_pp(solver const& s, signed_constraint c): s(s), lit(c.blit()) {}
lit_pp(solver const& s, sat::literal lit): s(s), lit(lit) {}
std::ostream& display(std::ostream& out) const;
};

View file

@ -1120,22 +1120,29 @@ namespace polysat {
s.check();
}
}
// Goal: we probably mix up polysat variables and PDD variables at several points; try to uncover such cases
// NOTE: actually, add_var seems to keep them in sync, so this is not an issue at the moment (but we should still test it later)
// static void test_mixed_vars() {
// scoped_solver s(__func__);
// auto a = s.var(s.add_var(2));
// auto b = s.var(s.add_var(4));
// auto c = s.var(s.add_var(2));
// s.add_eq(a + 2*c + 4);
// s.add_eq(3*b + 4);
// s.check();
// // Expected result:
// }
static void test_pop_conflict() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(32));
s.add_ule(a, 5);
s.push();
s.add_ult(5, a);
s.push();
s.add_ule(1, a);
s.check();
s.expect_unsat();
s.pop();
s.check();
s.expect_unsat();
s.pop();
s.add_ult(4, a);
// s.add_ule(100, a);
s.check();
s.expect_sat({{a, 5}});
}
}; // class test_polysat
// Here we deal with linear constraints of the form
//