3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-24 17:45:32 +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);
}
}

View file

@ -25,18 +25,29 @@ namespace polysat {
/** Conflict state, represented as core (~negation of clause). */
class conflict_core {
vector<signed_constraint> m_constraints;
vector<signed_constraint> m_constraints; // constraints used as premises
uint_set m_vars; // variable assignments used as premises
// 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.
pvar m_conflict_var = null_var;
/** True iff the conflict depends on the current variable assignment. (If so, additional constraints must be added to the final learned clause.) */
bool m_needs_model = false;
// NOTE: for now we keep this simple implementation.
// The drawback is that we may get weaker lemmas in some cases (but they are still correct).
// 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.
unsigned_vector m_pvar2count; // reference count of variables
void inc_pref(pvar v);
void dec_pref(pvar v);
bool_vector m_bvar2mark; // mark of Boolean variables
void set_bmark(sat::bool_var b);
void unset_bmark(sat::bool_var b);
void set_mark(constraint* c);
void unset_mark(constraint* c);
/** Whether we are in a bailout state. We enter a bailout state when we give up on proper conflict resolution. */
bool m_bailout = false;
std::optional<clause_builder> m_bailout_lemma;
@ -56,19 +67,20 @@ namespace polysat {
~conflict_core();
vector<signed_constraint> const& constraints() const { return m_constraints; }
bool needs_model() const { return m_needs_model; }
pvar conflict_var() const { return m_conflict_var; }
bool is_bailout() const { return m_bailout; }
void set_bailout() { SASSERT(!is_bailout()); m_bailout = true; }
bool empty() const {
return m_constraints.empty() && !m_needs_model && m_conflict_var == null_var;
return m_constraints.empty() && m_vars.empty() && conflict_var() == null_var;
}
void reset() {
for (auto c : m_constraints)
unset_mark(c.get());
m_constraints.reset();
m_needs_model = false;
m_vars.reset();
m_conflict_var = null_var;
m_saturation_premises.reset();
m_bailout = false;
@ -76,6 +88,9 @@ namespace polysat {
SASSERT(empty());
}
bool is_pmarked(pvar v) const;
bool is_bmarked(sat::bool_var b) const;
/** conflict because the constraint c is false under current variable assignment */
void set(signed_constraint c);
/** conflict because there is no viable value for the variable v */

View file

@ -121,6 +121,8 @@ namespace polysat {
ckind_t m_kind;
unsigned_vector m_vars;
bool m_is_external = false;
bool m_is_marked = false;
bool m_is_bool_propagated = false;
/** 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.
@ -169,6 +171,14 @@ namespace polysat {
void set_external() { m_is_external = true; }
void unset_external() { m_is_external = false; }
bool is_external() const { return m_is_external; }
void set_mark() { m_is_marked = true; }
void unset_mark() { m_is_marked = false; }
bool is_marked() const { return m_is_marked; }
void set_bool_propagated() { m_is_bool_propagated = true; }
void unset_bool_propagated() { m_is_bool_propagated = false; }
bool is_bool_propagated() { return m_is_bool_propagated; }
};
inline std::ostream& operator<<(std::ostream& out, constraint const& c) { return c.display(out); }

View file

@ -20,26 +20,26 @@ namespace polysat {
constraint_manager& explainer::cm() { return s().m_constraints; }
signed_constraint ex_polynomial_superposition::resolve1(pvar v, signed_constraint c1, signed_constraint c2) {
// c1 is true, c2 is false
SASSERT(c1.is_currently_true(s()));
SASSERT(c2.is_currently_false(s()));
LOG_H3("Resolving upon v" << v);
LOG("c1: " << c1);
LOG("c2: " << c2);
pdd a = c1->to_ule().p();
pdd b = c2->to_ule().p();
pdd r = a;
if (!a.resolve(v, b, r) && !b.resolve(v, a, r))
return {};
// Only keep result if the degree in c2 was reduced.
// (this condition might be too strict, but we use it for now to prevent looping)
if (b.degree(v) <= r.degree(v))
return {};
signed_constraint c = cm().eq(r);
LOG("resolved: " << c << " currently false? " << c.is_currently_false(s()));
if (!c.is_currently_false(s()))
return {};
return c;
// c1 is true, c2 is false
SASSERT(c1.is_currently_true(s()));
SASSERT(c2.is_currently_false(s()));
LOG_H3("Resolving upon v" << v);
LOG("c1: " << c1);
LOG("c2: " << c2);
pdd a = c1->to_ule().p();
pdd b = c2->to_ule().p();
pdd r = a;
if (!a.resolve(v, b, r) && !b.resolve(v, a, r))
return {};
// Only keep result if the degree in c2 was reduced.
// (this condition might be too strict, but we use it for now to prevent looping)
if (b.degree(v) <= r.degree(v))
return {};
signed_constraint c = cm().eq(r);
LOG("resolved: " << c << " currently false? " << c.is_currently_false(s()));
if (!c.is_currently_false(s()))
return {};
return c;
}
bool ex_polynomial_superposition::is_positive_equality_over(pvar v, signed_constraint const& c) {

View file

@ -79,13 +79,13 @@ namespace polysat {
lemma.push(~src);
}
bool forbidden_intervals::perform(solver& s, pvar v, conflict_core& core, clause_builder& lemma) {
bool forbidden_intervals::perform(solver& s, pvar v, vector<signed_constraint> const& just, clause_builder& lemma) {
// Extract forbidden intervals from conflicting constraints
vector<fi_record> records;
rational longest_len;
unsigned longest_i = UINT_MAX;
for (signed_constraint c : core) {
for (signed_constraint c : just) {
LOG_H3("Computing forbidden interval for: " << c);
eval_interval interval = eval_interval::full();
signed_constraint neg_cond;

View file

@ -23,6 +23,6 @@ namespace polysat {
void full_interval_conflict(signed_constraint c, signed_constraint neg_cond, clause_builder& lemma);
bool get_interval(solver& s, signed_constraint const& c, pvar v, eval_interval& out_interval, signed_constraint& out_neg_cond);
public:
bool perform(solver& s, pvar v, conflict_core& core, clause_builder& lemma);
bool perform(solver& s, pvar v, vector<signed_constraint> const& just, clause_builder& lemma);
};
}

View file

@ -61,9 +61,9 @@ namespace polysat {
if (crit.as_signed_constraint().is_currently_false(s()) && c.is_currently_true(s()))
return false;
core.insert(c);
reason.push(c);
core.remove(crit.as_signed_constraint());
reason.push(c);
s().propagate_bool(c.blit(), reason.build().get());
core.remove(crit.as_signed_constraint()); // needs to be after propagation so we know it is propagated
return true;
}

View file

@ -418,21 +418,6 @@ namespace polysat {
m_conflict.set(v);
}
void solver::set_marks(conflict_core const& cc) {
if (cc.conflict_var() != null_var)
set_mark(cc.conflict_var());
for (auto c : cc.constraints())
if (c)
set_marks(*c);
}
void solver::set_marks(constraint const& c) {
if (c.has_bvar())
m_bvars.set_mark(c.bvar());
for (auto v : c.vars())
set_mark(v);
}
/**
* Conflict resolution.
* - m_conflict are constraints that are infeasible in the current assignment.
@ -460,15 +445,11 @@ namespace polysat {
SASSERT(is_conflict());
reset_marks();
set_marks(m_conflict);
// TODO: subsequent changes to the conflict should update the marks incrementally
if (m_conflict.conflict_var() != null_var) {
// This case corresponds to a propagation of conflict_var, except it's not explicitly on the stack.
resolve_value(m_conflict.conflict_var());
reset_marks();
set_marks(m_conflict);
}
search_iterator search_it(m_search);
@ -479,7 +460,7 @@ namespace polysat {
// Resolve over variable assignment
pvar v = item.var();
LOG_H2("Working on pvar v" << v);
if (!is_marked(v))
if (!m_conflict.is_pmarked(v))
continue;
justification& j = m_justification[v];
LOG("Justification: " << j);
@ -491,8 +472,6 @@ namespace polysat {
}
SASSERT(j.is_propagation());
resolve_value(v);
reset_marks();
set_marks(m_conflict);
}
else {
// Resolve over boolean literal
@ -500,7 +479,7 @@ namespace polysat {
sat::literal const lit = item.lit();
LOG_H2("Working on blit " << lit);
sat::bool_var const var = lit.var();
if (!m_bvars.is_marked(var))
if (!m_conflict.is_bmarked(var))
continue;
if (m_bvars.level(var) <= base_level())
break;
@ -508,10 +487,9 @@ namespace polysat {
revert_bool_decision(lit);
return;
}
SASSERT(m_bvars.is_propagation(var));
resolve_bool(lit);
reset_marks();
set_marks(m_conflict);
}
}
report_unsat();
@ -803,18 +781,7 @@ namespace polysat {
LOG_V("INSERTING: " << c);
cs.push_back(c);
SASSERT(invariant(cs));
}
void solver::reset_marks() {
m_bvars.reset_marks();
LOG_V("-------------------------- (reset variable marks)");
m_marks.reserve(m_vars.size());
m_clock++;
if (m_clock != 0)
return;
m_clock++;
m_marks.fill(0);
}
}
void solver::push() {
LOG("Push user scope");

View file

@ -180,15 +180,6 @@ namespace polysat {
void set_conflict(signed_constraint c);
void set_conflict(pvar v);
unsigned_vector m_marks;
unsigned m_clock { 0 };
void reset_marks();
bool is_marked(pvar v) const { return m_clock == m_marks[v]; }
void set_mark(pvar v) { LOG_V("Marking: v" << v); m_marks[v] = m_clock; }
void set_marks(conflict_core const& cc);
void set_marks(constraint const& c);
bool can_decide() const { return !m_free_vars.empty(); }
void decide();
void decide(pvar v);