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

reorganize variable tracking for lemmas

this is going to break a bunch
This commit is contained in:
Nikolaj Bjorner 2021-09-12 18:05:29 +02:00
parent deeb6c7784
commit f5fd295e01
9 changed files with 191 additions and 136 deletions

View file

@ -20,6 +20,16 @@ Notes:
TODO: maybe implement by marking literals instead (like SAT solvers are doing); if we need to iterate, keep an indexed_uint_set (from util/uint_set.h)
(i.e., instead of keeping an explicit list of constraints as core, we just mark them.)
(we still need the list though, for new/temporary constraints.)
The approach would be as follows:
m_vars - set of variables used in conflict
m_constraints - set of new constraints used in conflict
indexed_uint_set - for Boolean variables that are in the conflict
When iterating over the core acessing the uint_set would require some support
TODO: fallback lemma is redundant:
The core lemma uses m_vars and m_conflict directly instead of walking the stack.
It should be an invariant that the core is false (the negation of the core is valid modulo assertions).
The fallback lemma prunes at least the last value assignment.
--*/
@ -48,34 +58,44 @@ namespace polysat {
conflict_core::~conflict_core() {}
constraint_manager& conflict_core::cm() { return m_solver->m_constraints; }
constraint_manager& conflict_core::cm() { return s().m_constraints; }
std::ostream& conflict_core::display(std::ostream& out) const {
bool first = true;
for (auto c : m_constraints) {
if (first)
first = false;
else
out << " ; ";
out << c;
}
if (m_needs_model)
out << " ; + current model";
for (auto c : m_constraints)
out << (first ? "" : " ; ") << c, first = false;
if (!m_vars.empty())
out << " vars";
for (auto v : m_vars)
out << " " << v;
return out;
}
/**
* The constraint is false under the current assignment of variables.
* The core is then the conjuction of this constraint and assigned variables.
*/
void conflict_core::set(signed_constraint c) {
LOG("Conflict: " << c);
SASSERT(empty());
m_needs_model = true;
insert(c);
for (auto v : c->vars())
if (s().is_assigned(v))
m_vars.insert(v);
}
/**
* The variable v cannot be assigned.
* The conflict is the set of justifications accumulated for the viable values for v.
* These constraints are (in the current form) not added to the core, but passed directly
* to the forbidden interval module.
* A consistent approach could be to add these constraints to the core and then also include the
* variable assignments.
*/
void conflict_core::set(pvar v) {
LOG("Conflict: v" << v);
SASSERT(empty());
m_conflict_var = v;
m_needs_model = true;
}
void conflict_core::insert(signed_constraint c) {
@ -86,6 +106,9 @@ namespace polysat {
if (c.is_always_true())
return;
SASSERT(!c.is_always_false());
if (c->is_marked())
return;
set_mark(c.get());
m_constraints.push_back(c);
}
@ -95,6 +118,7 @@ namespace polysat {
}
void conflict_core::remove(signed_constraint c) {
unset_mark(c.get());
m_constraints.erase(c);
}
@ -106,8 +130,11 @@ namespace polysat {
void conflict_core::remove_var(pvar v) {
unsigned j = 0;
for (unsigned i = 0; i < m_constraints.size(); ++i)
if (!m_constraints[i]->contains_var(v))
m_constraints[j++] = m_constraints[i];
if (m_constraints[i]->contains_var(v))
unset_mark(m_constraints[i].get());
else
m_constraints[j++] = m_constraints[i];
m_constraints.shrink(j);
}
@ -136,14 +163,17 @@ namespace polysat {
});
int j = 0;
for (auto c : m_constraints)
for (auto c : m_constraints) {
if (c->bvar() != var)
m_constraints[j++] = c;
else
unset_mark(c.get());
}
m_constraints.shrink(j);
for (sat::literal lit : cl)
if (lit.var() != var)
m_constraints.push_back(m.lookup(~lit));
insert(m.lookup(~lit));
}
/** If the constraint c is a temporary constraint derived by core saturation, insert it (and recursively, its premises) into \Gamma */
@ -154,20 +184,20 @@ namespace polysat {
return;
unsigned active_level = 0;
auto& premises = it->m_value;
clause_builder c_lemma(*m_solver);
clause_builder c_lemma(s());
for (auto premise : premises) {
cm().ensure_bvar(premise.get());
// keep(premise);
handle_saturation_premises(premise);
SASSERT(premise->has_bvar());
c_lemma.push(~premise.blit());
active_level = std::max(active_level, m_solver->m_bvars.level(premise.blit()));
active_level = std::max(active_level, s().m_bvars.level(premise.blit()));
}
c_lemma.push(c.blit());
clause* cl = cm().store(c_lemma.build());
if (cl->size() == 1)
c->set_unit_clause(cl);
m_solver->propagate_bool_at(active_level, c.blit(), cl);
s().propagate_bool_at(active_level, c.blit(), cl);
}
/** Create fallback lemma that excludes the current search state */
@ -178,18 +208,18 @@ namespace polysat {
*/
clause_builder conflict_core::build_fallback_lemma(unsigned lvl) {
LOG_H3("Creating fallback lemma for level " << lvl);
LOG_V("m_search: " << m_solver->m_search);
LOG_V("m_search: " << s().m_search);
clause_builder lemma(*m_solver);
unsigned todo = lvl;
unsigned i = 0;
while (todo > 0) {
auto const& item = m_solver->m_search[i++];
if (!m_solver->is_decision(item))
auto const& item = s().m_search[i++];
if (!s().is_decision(item))
continue;
LOG_V("Adding: " << item);
if (item.is_assignment()) {
pvar v = item.var();
auto c = ~cm().eq(m_solver->var(v) - m_solver->m_value[v]);
auto c = ~cm().eq(s().var(v) - s().m_value[v]);
cm().ensure_bvar(c.get());
lemma.push(c.blit());
} else {
@ -213,27 +243,18 @@ namespace polysat {
lemma.push(~c);
}
// TODO: need to revisit this for when there are literals obtained by semantic propagation
if (m_needs_model) {
// TODO: add equalities corresponding to current model.
// until we properly track variables (use marks from solver?), we just use all of them (reverted decision and the following ones should have been popped from the stack)
uint_set vars;
for (auto c : m_constraints)
for (pvar v : c->vars())
vars.insert(v);
// Add v != val for each variable
for (pvar v : vars) {
// SASSERT(!m_solver->m_justification[v].is_unassigned()); // TODO: why does this trigger????
if (m_solver->m_justification[v].is_unassigned())
continue;
if (m_solver->m_justification[v].level() > model_level)
continue;
auto diseq = ~cm().eq(m_solver->var(v) - m_solver->m_value[v]);
cm().ensure_bvar(diseq.get());
lemma.push(diseq);
}
}
for (unsigned v : m_vars) {
if (!is_pmarked(v))
continue;
// SASSERT(!s().is_assigned()); // TODO: why does this trigger????
if (!s().is_assigned())
continue;
if (s().m_justification[v].level() > model_level)
continue;
auto diseq = ~cm().eq(s().var(v) - s().m_value[v]);
cm().ensure_bvar(diseq.get());
lemma.push(diseq);
}
return lemma;
}
@ -252,29 +273,21 @@ namespace polysat {
// In the "standard" case where "v = val" is on the stack:
// - cjust_v contains true constraints
// - core contains both false and true constraints... (originally only false ones, but additional true ones may come from saturation?).
// In the case where no assignment to v is on the stack, i.e., conflict_var == v and viable(v) = \emptyset:
// - the constraints in cjust_v cannot be evaluated.
// - for now, we just pick a value. TODO: revise later
/*
if (conflict_var() == v) {
// Temporary assignment
// (actually we shouldn't just pick any value, but one that makes at least one constraint true...)
LOG_H1("WARNING: temporary assignment of conflict_var");
m_solver->assign_core(v, m_solver->m_value[v], justification::propagation(m_solver->m_level));
}
*/
if (conflict_var() == v) {
clause_builder lemma(s());
forbidden_intervals fi;
if (fi.perform(s(), v, *this, lemma)) {
if (fi.perform(s(), v, cjust_v, lemma)) {
set_bailout();
m_bailout_lemma = std::move(lemma);
return true;
}
}
for (auto c : cjust_v)
insert(c);
m_vars.remove(v);
for (auto c : cjust_v)
insert(c);
for (auto* engine : ex_engines)
if (engine->try_explain(v, *this))
@ -288,6 +301,7 @@ namespace polysat {
if (try_eliminate(v))
return true;
}
m_vars.insert(v);
return false;
}
@ -298,7 +312,7 @@ namespace polysat {
if (!has_v)
return true;
for (auto* engine : ve_engines)
if (engine->perform(*m_solver, v, *this))
if (engine->perform(s(), v, *this))
return true;
return false;
}
@ -309,4 +323,62 @@ namespace polysat {
return true;
return false;
}
void conflict_core::set_mark(constraint* c) {
if (c->is_marked())
return;
bool bool_propagated = c->has_bvar() && s().m_bvars.is_assigned(c->bvar());
c->set_mark();
if (c->has_bvar())
set_bmark(c->bvar());
if (bool_propagated)
c->set_bool_propagated();
else
for (auto v : c->vars())
inc_pref(v);
}
void conflict_core::unset_mark(constraint* c) {
if (!c->is_marked())
return;
c->unset_mark();
if (c->has_bvar())
unset_bmark(c->bvar());
if (c->is_bool_propagated())
c->unset_bool_propagated();
else
for (auto v : c->vars())
dec_pref(v);
}
void conflict_core::inc_pref(pvar v) {
if (v >= m_pvar2count.size())
m_pvar2count.resize(v + 1);
m_pvar2count[v]++;
}
void conflict_core::dec_pref(pvar v) {
SASSERT(m_pvar2count[v] > 0);
m_pvar2count[v]--;
}
bool conflict_core::is_pmarked(pvar v) const {
return m_pvar2count.get(v, 0) > 0;
}
void conflict_core::set_bmark(sat::bool_var b) {
if (b >= m_bvar2mark.size())
m_bvar2mark.resize(b + 1);
SASSERT(!m_bvar2mark[b]);
m_bvar2mark[b] = true;
}
void conflict_core::unset_bmark(sat::bool_var b) {
SASSERT(m_bvar2mark[b]);
m_bvar2mark[b] = false;
}
bool conflict_core::is_bmarked(sat::bool_var b) const {
return m_bvar2mark.get(b, false);
}
}