mirror of
https://github.com/Z3Prover/z3
synced 2025-04-25 10:05:32 +00:00
937 lines
32 KiB
C++
937 lines
32 KiB
C++
/*++
|
|
Copyright (c) 2021 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
polysat
|
|
|
|
Abstract:
|
|
|
|
Polynomial solver for modular arithmetic.
|
|
|
|
Author:
|
|
|
|
Nikolaj Bjorner (nbjorner) 2021-03-19
|
|
Jakob Rath 2021-04-6
|
|
|
|
--*/
|
|
|
|
#include "math/polysat/solver.h"
|
|
#include "math/polysat/explain.h"
|
|
#include "math/polysat/log.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
|
|
#define ENABLE_LINEAR_SOLVER 0
|
|
|
|
namespace polysat {
|
|
|
|
dd::pdd_manager& solver::sz2pdd(unsigned sz) {
|
|
m_pdd.reserve(sz + 1);
|
|
if (!m_pdd[sz])
|
|
m_pdd.set(sz, alloc(dd::pdd_manager, 1000, dd::pdd_manager::semantics::mod2N_e, sz));
|
|
return *m_pdd[sz];
|
|
}
|
|
|
|
solver::solver(reslimit& lim):
|
|
m_lim(lim),
|
|
m_viable(*this),
|
|
m_dm(m_value_manager, m_alloc),
|
|
m_linear_solver(*this),
|
|
m_conflict(*this),
|
|
m_bvars(),
|
|
m_free_pvars(m_activity),
|
|
m_constraints(m_bvars) {
|
|
}
|
|
|
|
solver::~solver() {
|
|
// Need to remove any lingering clause/constraint references before the constraint manager is destructed
|
|
m_conflict.reset();
|
|
}
|
|
|
|
bool solver::should_search() {
|
|
return
|
|
m_lim.inc() &&
|
|
(m_stats.m_num_conflicts < m_max_conflicts) &&
|
|
(m_stats.m_num_decisions < m_max_decisions);
|
|
}
|
|
|
|
lbool solver::check_sat() {
|
|
LOG("Starting");
|
|
while (inc()) {
|
|
m_stats.m_num_iterations++;
|
|
LOG_H1("Next solving loop iteration (#" << m_stats.m_num_iterations << ")");
|
|
LOG("Free variables: " << m_free_pvars);
|
|
LOG("Assignment: " << assignments_pp(*this));
|
|
COND_LOG(is_conflict(), "Conflict: " << m_conflict);
|
|
IF_LOGGING(m_viable.log());
|
|
if (!is_conflict() && m_constraints.should_gc()) m_constraints.gc(*this);
|
|
else if (is_conflict() && at_base_level()) { LOG_H2("UNSAT"); return l_false; }
|
|
else if (is_conflict()) resolve_conflict();
|
|
else if (can_propagate()) propagate();
|
|
else if (!can_decide()) { LOG_H2("SAT"); SASSERT(verify_sat()); return l_true; }
|
|
else decide();
|
|
}
|
|
LOG_H2("UNDEF (resource limit)");
|
|
return l_undef;
|
|
}
|
|
|
|
unsigned solver::add_var(unsigned sz) {
|
|
pvar v = m_value.size();
|
|
m_value.push_back(rational::zero());
|
|
m_justification.push_back(justification::unassigned());
|
|
m_viable.push(sz);
|
|
m_cjust.push_back({});
|
|
m_pwatch.push_back({});
|
|
m_activity.push_back(0);
|
|
m_vars.push_back(sz2pdd(sz).mk_var(v));
|
|
m_size.push_back(sz);
|
|
m_trail.push_back(trail_instr_t::add_var_i);
|
|
m_free_pvars.mk_var_eh(v);
|
|
return v;
|
|
}
|
|
|
|
pdd solver::value(rational const& v, unsigned sz) {
|
|
return sz2pdd(sz).mk_val(v);
|
|
}
|
|
|
|
void solver::del_var() {
|
|
// TODO also remove v from all learned constraints.
|
|
pvar v = m_value.size() - 1;
|
|
m_viable.pop();
|
|
m_cjust.pop_back();
|
|
m_value.pop_back();
|
|
m_justification.pop_back();
|
|
m_pwatch.pop_back();
|
|
m_activity.pop_back();
|
|
m_vars.pop_back();
|
|
m_size.pop_back();
|
|
m_free_pvars.del_var_eh(v);
|
|
}
|
|
|
|
void solver::ext_constraint(signed_constraint c, unsigned dep, bool activate) {
|
|
VERIFY(at_base_level());
|
|
SASSERT(c);
|
|
SASSERT(activate || dep != null_dependency); // if we don't activate the constraint, we need the dependency to access it again later.
|
|
if (is_conflict())
|
|
return; // no need to do anything if we already have a conflict at base level
|
|
m_constraints.ensure_bvar(c.get());
|
|
clause_ref unit = clause::from_unit(m_level, c, mk_dep_ref(dep));
|
|
m_constraints.store(unit.get(), *this);
|
|
c->set_unit_clause(unit.get());
|
|
if (dep != null_dependency && !c->is_external()) {
|
|
m_constraints.register_external(c);
|
|
m_trail.push_back(trail_instr_t::ext_constraint_i);
|
|
m_ext_constraint_trail.push_back(c.get());
|
|
}
|
|
LOG("New constraint: " << c);
|
|
|
|
#if ENABLE_LINEAR_SOLVER
|
|
m_linear_solver.new_constraint(*c.get());
|
|
#endif
|
|
if (activate)
|
|
assert_ext_constraint(c);
|
|
}
|
|
|
|
void solver::assign_eh(unsigned dep, bool is_true) {
|
|
VERIFY(at_base_level());
|
|
if (is_conflict())
|
|
return; // no need to do anything if we already have a conflict at base level
|
|
signed_constraint c = m_constraints.lookup_external(dep);
|
|
if (!c) {
|
|
LOG("WARN: there is no constraint for dependency " << dep);
|
|
return;
|
|
}
|
|
assert_ext_constraint(is_true ? c : ~c);
|
|
}
|
|
|
|
void solver::assert_ext_constraint(signed_constraint c) {
|
|
SASSERT(at_base_level());
|
|
SASSERT(!is_conflict());
|
|
SASSERT(c->unit_clause());
|
|
if (c.bvalue(*this) == l_false)
|
|
m_conflict.set(~c); // we already added ~c => conflict
|
|
else if (c.is_always_false())
|
|
m_conflict.set(c);
|
|
else
|
|
assign_bool(m_level, c.blit(), c->unit_clause(), nullptr);
|
|
}
|
|
|
|
bool solver::can_propagate() {
|
|
return m_qhead < m_search.size() && !is_conflict();
|
|
}
|
|
|
|
void solver::propagate() {
|
|
if (!can_propagate())
|
|
return;
|
|
push_qhead();
|
|
while (can_propagate()) {
|
|
auto const& item = m_search[m_qhead++];
|
|
if (item.is_assignment())
|
|
propagate(item.var());
|
|
else
|
|
propagate(item.lit());
|
|
}
|
|
linear_propagate();
|
|
SASSERT(wlist_invariant());
|
|
SASSERT(assignment_invariant());
|
|
}
|
|
|
|
/**
|
|
* Propagate assignment to a Boolean variable
|
|
*/
|
|
void solver::propagate(sat::literal lit) {
|
|
LOG_H2("Propagate bool " << lit);
|
|
signed_constraint c = lit2cnstr(lit);
|
|
SASSERT(c);
|
|
activate_constraint(c);
|
|
auto& wlist = m_bvars.watch(~lit);
|
|
unsigned i = 0, j = 0, sz = wlist.size();
|
|
for (; i < sz && !is_conflict(); ++i)
|
|
if (!propagate(lit, *wlist[i]))
|
|
wlist[j++] = wlist[i];
|
|
for (; i < sz; ++i)
|
|
wlist[j++] = wlist[i];
|
|
wlist.shrink(j);
|
|
}
|
|
|
|
/**
|
|
* Propagate assignment to a pvar
|
|
*/
|
|
void solver::propagate(pvar v) {
|
|
LOG_H2("Propagate v" << v);
|
|
auto& wlist = m_pwatch[v];
|
|
unsigned i = 0, j = 0, sz = wlist.size();
|
|
for (; i < sz && !is_conflict(); ++i)
|
|
if (!wlist[i].propagate(*this, v))
|
|
wlist[j++] = wlist[i];
|
|
for (; i < sz; ++i)
|
|
wlist[j++] = wlist[i];
|
|
wlist.shrink(j);
|
|
}
|
|
|
|
bool solver::propagate(sat::literal lit, clause& cl) {
|
|
SASSERT(cl.size() >= 2);
|
|
unsigned idx = cl[0] == ~lit ? 1 : 0;
|
|
SASSERT(cl[1 - idx] == ~lit);
|
|
if (m_bvars.is_true(cl[idx]))
|
|
return true;
|
|
unsigned i = 2;
|
|
for (; i < cl.size() && m_bvars.is_false(cl[i]); ++i);
|
|
if (i < cl.size()) {
|
|
m_bvars.watch(cl[i]).push_back(&cl);
|
|
std::swap(cl[1 - idx], cl[i]);
|
|
return false;
|
|
}
|
|
if (m_bvars.is_false(cl[idx]))
|
|
set_conflict(cl);
|
|
else
|
|
assign_bool(level(cl), cl[idx], &cl, nullptr);
|
|
return true;
|
|
}
|
|
|
|
void solver::linear_propagate() {
|
|
#if ENABLE_LINEAR_SOLVER
|
|
switch (m_linear_solver.check()) {
|
|
case l_false:
|
|
// TODO extract conflict
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void solver::propagate(pvar v, rational const& val, signed_constraint c) {
|
|
LOG("Propagation: " << assignment_pp(*this, v, val) << ", due to " << c);
|
|
if (m_viable.is_viable(v, val)) {
|
|
m_free_pvars.del_var_eh(v);
|
|
assign_core(v, val, justification::propagation(m_level));
|
|
}
|
|
else
|
|
set_conflict(c);
|
|
}
|
|
|
|
void solver::push_level() {
|
|
++m_level;
|
|
m_trail.push_back(trail_instr_t::inc_level_i);
|
|
#if ENABLE_LINEAR_SOLVER
|
|
m_linear_solver.push();
|
|
#endif
|
|
}
|
|
|
|
void solver::pop_levels(unsigned num_levels) {
|
|
if (num_levels == 0)
|
|
return;
|
|
SASSERT(m_level >= num_levels);
|
|
unsigned const target_level = m_level - num_levels;
|
|
vector<sat::literal> replay;
|
|
LOG("Pop " << num_levels << " levels (lvl " << m_level << " -> " << target_level << ")");
|
|
#if ENABLE_LINEAR_SOLVER
|
|
m_linear_solver.pop(num_levels);
|
|
#endif
|
|
while (num_levels > 0) {
|
|
switch (m_trail.back()) {
|
|
case trail_instr_t::qhead_i: {
|
|
pop_qhead();
|
|
for (unsigned i = m_search.size(); i-- > m_qhead; )
|
|
if (m_search[i].is_boolean())
|
|
deactivate_constraint(lit2cnstr(m_search[i].lit()));
|
|
break;
|
|
}
|
|
case trail_instr_t::add_var_i: {
|
|
del_var();
|
|
break;
|
|
}
|
|
case trail_instr_t::inc_level_i: {
|
|
--m_level;
|
|
--num_levels;
|
|
break;
|
|
}
|
|
case trail_instr_t::viable_i: {
|
|
m_viable.pop_viable();
|
|
break;
|
|
}
|
|
case trail_instr_t::assign_i: {
|
|
auto v = m_search.back().var();
|
|
LOG_V("Undo assign_i: v" << v);
|
|
m_free_pvars.unassign_var_eh(v);
|
|
m_justification[v] = justification::unassigned();
|
|
m_search.pop();
|
|
break;
|
|
}
|
|
case trail_instr_t::assign_bool_i: {
|
|
sat::literal lit = m_search.back().lit();
|
|
LOG_V("Undo assign_bool_i: " << lit);
|
|
unsigned active_level = m_bvars.level(lit);
|
|
if (active_level <= target_level)
|
|
replay.push_back(lit);
|
|
else
|
|
m_bvars.unassign(lit);
|
|
m_search.pop();
|
|
break;
|
|
}
|
|
case trail_instr_t::just_i: {
|
|
auto v = m_cjust_trail.back();
|
|
LOG_V("Undo just_i");
|
|
m_cjust[v].pop_back();
|
|
m_cjust_trail.pop_back();
|
|
break;
|
|
}
|
|
case trail_instr_t::ext_constraint_i: {
|
|
constraint* c = m_ext_constraint_trail.back();
|
|
m_constraints.unregister_external(c);
|
|
m_ext_constraint_trail.pop_back();
|
|
break;
|
|
}
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
m_trail.pop_back();
|
|
}
|
|
m_constraints.release_level(m_level + 1);
|
|
SASSERT(m_level == target_level);
|
|
for (unsigned j = replay.size(); j-- > 0; ) {
|
|
auto lit = replay[j];
|
|
m_trail.push_back(trail_instr_t::assign_bool_i);
|
|
m_search.push_boolean(lit);
|
|
}
|
|
}
|
|
|
|
|
|
void solver::add_watch(signed_constraint c) {
|
|
SASSERT(c);
|
|
auto const& vars = c->vars();
|
|
if (vars.size() > 0)
|
|
add_watch(c, vars[0]);
|
|
if (vars.size() > 1)
|
|
add_watch(c, vars[1]);
|
|
}
|
|
|
|
void solver::add_watch(signed_constraint c, pvar v) {
|
|
SASSERT(c);
|
|
LOG("Watching v" << v << " in constraint " << c);
|
|
m_pwatch[v].push_back(c);
|
|
}
|
|
|
|
void solver::erase_watch(signed_constraint c) {
|
|
auto const& vars = c->vars();
|
|
if (vars.size() > 0)
|
|
erase_watch(vars[0], c);
|
|
if (vars.size() > 1)
|
|
erase_watch(vars[1], c);
|
|
}
|
|
|
|
void solver::erase_watch(pvar v, signed_constraint c) {
|
|
if (v == null_var)
|
|
return;
|
|
auto& wlist = m_pwatch[v];
|
|
unsigned sz = wlist.size();
|
|
for (unsigned i = 0; i < sz; ++i) {
|
|
if (c == wlist[i]) {
|
|
wlist[i] = wlist.back();
|
|
wlist.pop_back();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void solver::decide() {
|
|
LOG_H2("Decide");
|
|
SASSERT(can_decide());
|
|
if (m_bvars.can_decide())
|
|
bdecide(m_bvars.next_var());
|
|
else
|
|
pdecide(m_free_pvars.next_var());
|
|
}
|
|
|
|
void solver::pdecide(pvar v) {
|
|
LOG("Decide v" << v);
|
|
IF_LOGGING(m_viable.log(v));
|
|
rational val;
|
|
switch (m_viable.find_viable(v, val)) {
|
|
case dd::find_t::empty:
|
|
// NOTE: all such cases should be discovered elsewhere (e.g., during propagation/narrowing)
|
|
// (fail here in debug mode so we notice if we miss some)
|
|
DEBUG_CODE( UNREACHABLE(); );
|
|
m_free_pvars.unassign_var_eh(v);
|
|
set_conflict(v);
|
|
break;
|
|
case dd::find_t::singleton:
|
|
// NOTE: this case may happen legitimately if all other possibilities were excluded by brute force search
|
|
assign_core(v, val, justification::propagation(m_level));
|
|
break;
|
|
case dd::find_t::multiple:
|
|
push_level();
|
|
assign_core(v, val, justification::decision(m_level));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void solver::bdecide(sat::bool_var b) {
|
|
decide_bool(sat::literal(b), nullptr);
|
|
}
|
|
|
|
void solver::assign_core(pvar v, rational const& val, justification const& j) {
|
|
if (j.is_decision())
|
|
++m_stats.m_num_decisions;
|
|
else
|
|
++m_stats.m_num_propagations;
|
|
LOG(assignment_pp(*this, v, val) << " by " << j);
|
|
SASSERT(m_viable.is_viable(v, val));
|
|
SASSERT(std::all_of(assignment().begin(), assignment().end(), [v](auto p) { return p.first != v; }));
|
|
m_value[v] = val;
|
|
m_search.push_assignment(v, val);
|
|
m_trail.push_back(trail_instr_t::assign_i);
|
|
m_justification[v] = j;
|
|
#if ENABLE_LINEAR_SOLVER
|
|
// TODO: convert justification into a format that can be tracked in a depdendency core.
|
|
m_linear_solver.set_value(v, val, UINT_MAX);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Conflict resolution.
|
|
* - m_conflict are constraints that are infeasible in the current assignment.
|
|
* 1. walk m_search from top down until last variable in m_conflict.
|
|
* 2. resolve constraints in m_cjust to isolate lowest degree polynomials
|
|
* using variable.
|
|
* Use Olm-Seidl division by powers of 2 to preserve invertibility.
|
|
* 3. resolve conflict with result of resolution.
|
|
* 4. If the resulting lemma is still infeasible continue, otherwise bail out
|
|
* and undo the last assignment by accumulating conflict trail (but without resolution).
|
|
* 5. When hitting the last decision, determine whether conflict polynomial is asserting,
|
|
* If so, apply propagation.
|
|
* 6. Otherwise, add accumulated constraints to explanation for the next viable solution, prune
|
|
* viable solutions by excluding the previous guess.
|
|
*
|
|
*/
|
|
void solver::resolve_conflict() {
|
|
IF_VERBOSE(1, verbose_stream() << "resolve conflict\n");
|
|
LOG_H2("Resolve conflict");
|
|
LOG("\n" << *this);
|
|
LOG("search state: " << m_search);
|
|
for (pvar v = 0; v < m_cjust.size(); ++v)
|
|
LOG("cjust[v" << v << "]: " << m_cjust[v]);
|
|
++m_stats.m_num_conflicts;
|
|
|
|
SASSERT(is_conflict());
|
|
|
|
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());
|
|
}
|
|
|
|
search_iterator search_it(m_search);
|
|
while (search_it.next()) {
|
|
LOG("search state: " << m_search);
|
|
LOG("Conflict: " << m_conflict);
|
|
auto const& item = *search_it;
|
|
LOG_H2("Working on " << item);
|
|
if (item.is_assignment()) {
|
|
// Resolve over variable assignment
|
|
pvar v = item.var();
|
|
if (!m_conflict.is_pmarked(v) && !m_conflict.is_bailout())
|
|
continue;
|
|
justification& j = m_justification[v];
|
|
LOG("Justification: " << j);
|
|
if (j.level() <= base_level())
|
|
break;
|
|
if (j.is_decision()) {
|
|
revert_decision(v);
|
|
return;
|
|
}
|
|
SASSERT(j.is_propagation());
|
|
resolve_value(v);
|
|
}
|
|
else {
|
|
// Resolve over boolean literal
|
|
SASSERT(item.is_boolean());
|
|
sat::literal const lit = item.lit();
|
|
sat::bool_var const var = lit.var();
|
|
if (!m_conflict.is_bmarked(var))
|
|
continue;
|
|
if (m_bvars.level(var) <= base_level()) // TODO: this doesn't work with out-of-level-order iteration.
|
|
break;
|
|
if (m_bvars.is_decision(var)) {
|
|
revert_bool_decision(lit);
|
|
return;
|
|
}
|
|
SASSERT(m_bvars.is_propagation(var));
|
|
resolve_bool(lit);
|
|
}
|
|
}
|
|
// here we build conflict clause if it has free variables.
|
|
// the last decision is reverted.
|
|
report_unsat();
|
|
}
|
|
|
|
/** Conflict resolution case where propagation 'v := ...' is on top of the stack */
|
|
void solver::resolve_value(pvar v) {
|
|
m_conflict.resolve_value(v, m_cjust[v]);
|
|
}
|
|
|
|
/** Conflict resolution case where boolean literal 'lit' is on top of the stack */
|
|
void solver::resolve_bool(sat::literal lit) {
|
|
sat::bool_var const var = lit.var();
|
|
SASSERT(m_bvars.is_propagation(var));
|
|
// NOTE: boolean resolution should work normally even in bailout mode.
|
|
clause other = *m_bvars.reason(var);
|
|
LOG_H3("resolve_bool: " << lit << " " << other);
|
|
m_conflict.resolve(m_constraints, var, other);
|
|
}
|
|
|
|
void solver::report_unsat() {
|
|
backjump(base_level());
|
|
SASSERT(!m_conflict.empty());
|
|
}
|
|
|
|
void solver::unsat_core(unsigned_vector& deps) {
|
|
NOT_IMPLEMENTED_YET(); // TODO: needs to be fixed to work with conflict_core
|
|
/*
|
|
deps.reset();
|
|
p_dependency_ref conflict_dep(m_dm);
|
|
for (auto& c : m_conflict.units())
|
|
if (c)
|
|
conflict_dep = m_dm.mk_join(c->unit_dep(), conflict_dep);
|
|
for (auto& c : m_conflict.clauses())
|
|
conflict_dep = m_dm.mk_join(c->dep(), conflict_dep);
|
|
m_dm.linearize(conflict_dep, deps);
|
|
*/
|
|
}
|
|
|
|
void solver::learn_lemma(pvar v, clause& lemma) {
|
|
LOG("Learning: "<< lemma);
|
|
SASSERT(lemma.size() > 0);
|
|
lemma.set_justified_var(v);
|
|
add_lemma(lemma);
|
|
decide_bool(lemma);
|
|
}
|
|
|
|
// Guess a literal from the given clause; returns the guessed constraint
|
|
void solver::decide_bool(clause& lemma) {
|
|
LOG_H3("Guessing literal in lemma: " << lemma);
|
|
IF_LOGGING(m_viable.log());
|
|
LOG("Boolean assignment: " << m_bvars);
|
|
|
|
// To make a guess, we need to find an unassigned literal that is not false in the current model.
|
|
|
|
sat::literal choice = sat::null_literal;
|
|
unsigned num_choices = 0; // TODO: should probably cache this? (or rather the suitability of each literal... it won't change until we backtrack beyond the current point)
|
|
|
|
for (sat::literal lit : lemma) {
|
|
switch (m_bvars.value(lit)) {
|
|
case l_true:
|
|
return;
|
|
case l_false:
|
|
continue;
|
|
default:
|
|
if (lit2cnstr(lit).is_currently_false(*this))
|
|
continue;
|
|
break;
|
|
}
|
|
num_choices++;
|
|
if (choice == sat::null_literal)
|
|
choice = lit;
|
|
}
|
|
LOG_V("num_choices: " << num_choices);
|
|
if (choice == sat::null_literal) {
|
|
// This case may happen when all undefined literals are false under the current variable assignment.
|
|
// TODO: The question is whether such lemmas should be generated? Check test_monot() for such a case.
|
|
set_conflict(lemma);
|
|
return;
|
|
}
|
|
|
|
signed_constraint c = lit2cnstr(choice);
|
|
push_cjust(lemma.justified_var(), c);
|
|
|
|
if (num_choices == 1)
|
|
assign_bool(level(lemma), choice, &lemma, nullptr);
|
|
else
|
|
decide_bool(choice, &lemma);
|
|
}
|
|
|
|
/**
|
|
* Revert a decision that caused a conflict.
|
|
* Variable v was assigned by a decision at position i in the search stack.
|
|
*/
|
|
void solver::revert_decision(pvar v) {
|
|
rational val = m_value[v];
|
|
LOG_H3("Reverting decision: pvar " << v << " := " << val);
|
|
SASSERT(m_justification[v].is_decision());
|
|
|
|
clause_ref lemma = m_conflict.build_lemma().build();
|
|
m_conflict.reset();
|
|
|
|
backjump(get_level(v) - 1);
|
|
|
|
// The justification for this restriction is the guessed constraint from the lemma.
|
|
// cjust[v] will be updated accordingly by decide_bool.
|
|
m_viable.add_non_viable(v, val);
|
|
learn_lemma(v, *lemma);
|
|
|
|
if (!is_conflict())
|
|
narrow(v);
|
|
}
|
|
|
|
bool solver::is_decision(search_item const& item) const {
|
|
if (item.is_assignment())
|
|
return m_justification[item.var()].is_decision();
|
|
else
|
|
return m_bvars.is_decision(item.lit().var());
|
|
}
|
|
|
|
void solver::revert_bool_decision(sat::literal lit) {
|
|
sat::bool_var const var = lit.var();
|
|
LOG_H3("Reverting boolean decision: " << lit);
|
|
SASSERT(m_bvars.is_decision(var));
|
|
|
|
// Current situation: we have a decision for boolean literal L on top of the stack, and a conflict core.
|
|
//
|
|
// In a CDCL solver, this means ~L is in the lemma (actually, as the asserting literal). We drop the decision and replace it by the propagation (~L)^lemma.
|
|
//
|
|
// - we know L must be false
|
|
// - if L isn't in the core, we can still add it (weakening the lemma) to obtain "core => ~L"
|
|
// - then we can add the propagation (~L)^lemma and continue with the next guess
|
|
|
|
// Note that if we arrive at this point, the variables in L are "relevant" to the conflict (otherwise we would have skipped L).
|
|
// So the subsequent steps must have contained one of these:
|
|
// - propagation of some variable v from L (and maybe other constraints)
|
|
// (v := val)^{L, ...}
|
|
// this means L is in core, unless we core-reduced it away
|
|
// - propagation of L' from L
|
|
// (L')^{L' \/ ¬L \/ ...}
|
|
// again L is in core, unless we core-reduced it away
|
|
|
|
clause_builder reason_builder = m_conflict.build_lemma();
|
|
m_conflict.reset();
|
|
|
|
bool contains_lit = std::find(reason_builder.begin(), reason_builder.end(), ~lit);
|
|
if (!contains_lit) {
|
|
// At this point, we do not have ~lit in the reason.
|
|
// For now, we simply add it (thus weakening the reason)
|
|
//
|
|
// Alternative (to be considered later):
|
|
// - 'reason' itself (without ~L) would already be an explanation for ~L
|
|
// - however if it doesn't contain ~L, it breaks the boolean resolution invariant
|
|
// - would need to check what we can gain by relaxing that invariant
|
|
// - drawback: might have to bail out at boolean resolution
|
|
// Also: maybe we can skip ~L in some cases? but in that case it shouldn't be marked.
|
|
//
|
|
reason_builder.push(~lit);
|
|
}
|
|
clause_ref reason = reason_builder.build();
|
|
|
|
// The lemma where 'lit' comes from.
|
|
// Currently, boolean decisions always come from guessing a literal of a learned non-unit lemma.
|
|
clause* lemma = m_bvars.lemma(var); // need to grab this while 'lit' is still assigned
|
|
|
|
backjump(m_bvars.level(var) - 1);
|
|
|
|
add_lemma(*reason);
|
|
if (is_conflict()) {
|
|
LOG_H1("Conflict during revert_bool_decision/propagate_bool!");
|
|
return;
|
|
}
|
|
if (lemma)
|
|
decide_bool(*lemma);
|
|
}
|
|
|
|
void solver::decide_bool(sat::literal lit, clause* lemma) {
|
|
SASSERT(!can_propagate());
|
|
SASSERT(!is_conflict());
|
|
push_level();
|
|
assign_bool(m_level, lit, nullptr, lemma);
|
|
}
|
|
|
|
unsigned solver::level(clause const& cl) {
|
|
unsigned lvl = 0;
|
|
for (auto lit : cl) {
|
|
auto c = lit2cnstr(lit);
|
|
if (m_bvars.is_false(lit) || c.is_currently_false(*this))
|
|
lvl = std::max(lvl, c.level(*this));
|
|
}
|
|
return lvl;
|
|
}
|
|
|
|
/// Assign a boolean literal and put it on the search stack
|
|
void solver::assign_bool(unsigned level, sat::literal lit, clause* reason, clause* lemma) {
|
|
SASSERT(!m_bvars.is_true(lit));
|
|
if (reason)
|
|
LOG("Propagate literal " << lit << " @ " << level << " by " << *reason);
|
|
else
|
|
LOG("Decide literal " << lit << " @ " << m_level);
|
|
|
|
m_bvars.assign(lit, level, reason, lemma);
|
|
m_trail.push_back(trail_instr_t::assign_bool_i);
|
|
m_search.push_boolean(lit);
|
|
}
|
|
|
|
/**
|
|
* Activate constraint immediately
|
|
* Activation and de-activation of constraints follows the scope controlled by push/pop.
|
|
* constraints activated within the linear solver are de-activated when the linear
|
|
* solver is popped.
|
|
*/
|
|
void solver::activate_constraint(signed_constraint c) {
|
|
SASSERT(c);
|
|
LOG("Activating constraint: " << c);
|
|
SASSERT(m_bvars.value(c.blit()) == l_true);
|
|
add_watch(c);
|
|
c.narrow(*this);
|
|
#if ENABLE_LINEAR_SOLVER
|
|
m_linear_solver.activate_constraint(c);
|
|
#endif
|
|
}
|
|
|
|
/// Deactivate constraint
|
|
void solver::deactivate_constraint(signed_constraint c) {
|
|
LOG("Deactivating constraint: " << c);
|
|
erase_watch(c);
|
|
}
|
|
|
|
void solver::backjump(unsigned new_level) {
|
|
LOG_H3("Backjumping to level " << new_level << " from level " << m_level);
|
|
pop_levels(m_level - new_level);
|
|
}
|
|
|
|
/**
|
|
* placeholder for factoring/gcd common factors
|
|
*/
|
|
void solver::narrow(pvar v) {
|
|
|
|
}
|
|
|
|
// Add lemma to storage
|
|
void solver::add_lemma(clause& lemma) {
|
|
LOG("Lemma: " << lemma);
|
|
for (sat::literal lit : lemma) {
|
|
LOG(" Literal " << lit << " is: " << lit2cnstr(lit));
|
|
SASSERT(!lit2cnstr(lit).is_currently_true(*this));
|
|
SASSERT(m_bvars.value(lit) != l_true);
|
|
}
|
|
SASSERT(!lemma.empty());
|
|
m_constraints.store(&lemma, *this);
|
|
if (lemma.size() == 1) {
|
|
signed_constraint c = lit2cnstr(lemma[0]);
|
|
c->set_unit_clause(&lemma);
|
|
}
|
|
propagate();
|
|
}
|
|
|
|
void solver::insert_constraint(signed_constraints& cs, signed_constraint c) {
|
|
SASSERT(c);
|
|
LOG_V("INSERTING: " << c);
|
|
cs.push_back(c);
|
|
SASSERT(invariant(cs));
|
|
}
|
|
|
|
void solver::push() {
|
|
LOG("Push user scope");
|
|
push_level();
|
|
m_base_levels.push_back(m_level);
|
|
}
|
|
|
|
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);
|
|
pop_levels(m_level - base_level + 1);
|
|
m_conflict.reset(); // TODO: maybe keep conflict if level of all constraints is lower than base_level?
|
|
}
|
|
|
|
bool solver::at_base_level() const {
|
|
return m_level == base_level();
|
|
}
|
|
|
|
unsigned solver::base_level() const {
|
|
return m_base_levels.empty() ? 0 : m_base_levels.back();
|
|
}
|
|
|
|
bool solver::try_eval(pdd const& p, rational& out_value) const {
|
|
pdd r = p.subst_val(assignment());
|
|
if (r.is_val())
|
|
out_value = r.val();
|
|
return r.is_val();
|
|
}
|
|
|
|
std::ostream& solver::display(std::ostream& out) const {
|
|
out << "Search Stack:\n";
|
|
for (auto item : m_search) {
|
|
if (item.is_assignment()) {
|
|
pvar v = item.var();
|
|
auto j = m_justification[v];
|
|
out << "\t" << assignment_pp(*this, v, get_value(v)) << " @" << j.level();
|
|
if (j.is_propagation())
|
|
out << " " << m_cjust[v];
|
|
out << "\n";
|
|
}
|
|
else {
|
|
sat::bool_var v = item.lit().var();
|
|
out << "\t" << item.lit() << " @" << m_bvars.level(v);
|
|
if (m_bvars.reason(v))
|
|
out << " " << *m_bvars.reason(v);
|
|
out << "\n";
|
|
}
|
|
}
|
|
out << "Constraints:\n";
|
|
for (auto c : m_constraints)
|
|
out << "\t" << c->bvar2string() << ": " << *c << "\n";
|
|
out << "Clauses:\n";
|
|
for (auto cls : m_constraints.clauses()) {
|
|
for (auto cl : cls) {
|
|
out << "\t" << *cl << "\n";
|
|
for (auto lit : *cl)
|
|
out << "\t\t" << lit << ": " << lit2cnstr(lit) << "\n";
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::ostream& assignments_pp::display(std::ostream& out) const {
|
|
for (auto [var, val] : s.assignment())
|
|
out << assignment_pp(s, var, val) << " ";
|
|
return out;
|
|
}
|
|
|
|
std::ostream& assignment_pp::display(std::ostream& out) const {
|
|
out << "v" << var << " := ";
|
|
rational const& p = rational::power_of_two(s.size(var));
|
|
if (val > mod(-val, p))
|
|
return out << -mod(-val, p);
|
|
else
|
|
return out << val;
|
|
}
|
|
|
|
|
|
void solver::collect_statistics(statistics& st) const {
|
|
st.update("polysat iterations", m_stats.m_num_iterations);
|
|
st.update("polysat decisions", m_stats.m_num_decisions);
|
|
st.update("polysat conflicts", m_stats.m_num_conflicts);
|
|
st.update("polysat bailouts", m_stats.m_num_bailouts);
|
|
st.update("polysat propagations", m_stats.m_num_propagations);
|
|
}
|
|
|
|
bool solver::invariant() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* levels are gone
|
|
*/
|
|
bool solver::invariant(signed_constraints const& cs) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check that two variables of each constraint are watched.
|
|
*/
|
|
bool solver::wlist_invariant() {
|
|
// Skip boolean variables that aren't active yet
|
|
uint_set skip;
|
|
for (unsigned i = m_qhead; i < m_search.size(); ++i)
|
|
if (m_search[i].is_boolean())
|
|
skip.insert(m_search[i].lit().var());
|
|
for (auto c : m_constraints) {
|
|
if (!c->has_bvar())
|
|
continue;
|
|
if (skip.contains(c->bvar()))
|
|
continue;
|
|
|
|
lbool value = m_bvars.value(c->bvar());
|
|
if (value == l_undef)
|
|
continue;
|
|
bool is_positive = value == l_true;
|
|
int64_t num_watches = 0;
|
|
signed_constraint sc(c, is_positive);
|
|
for (auto const& wlist : m_pwatch) {
|
|
auto n = std::count(wlist.begin(), wlist.end(), sc);
|
|
VERIFY(n <= 1); // no duplicates in the watchlist
|
|
num_watches += n;
|
|
}
|
|
unsigned expected_watches = std::min(2u, c->vars().size());
|
|
if (num_watches != expected_watches)
|
|
LOG("wrong number of watches: " << c);
|
|
SASSERT_EQ(num_watches, expected_watches);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Check that boolean assignment and constraint evaluation are consistent */
|
|
bool solver::assignment_invariant() {
|
|
if (is_conflict())
|
|
return true;
|
|
bool ok = true;
|
|
for (sat::bool_var v = m_bvars.size(); v-- > 0; ) {
|
|
sat::literal lit(v);
|
|
auto c = lit2cnstr(lit);
|
|
if (!std::all_of(c->vars().begin(), c->vars().end(), [&](auto v) { return is_assigned(v); }))
|
|
continue;
|
|
ok &= (m_bvars.value(lit) != l_true) || !c.is_currently_false(*this);
|
|
ok &= (m_bvars.value(lit) != l_false) || !c.is_currently_true(*this);
|
|
if (!ok) {
|
|
LOG("assignment invariant is broken " << v << "\n" << *this);
|
|
break;
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/// Check that all constraints on the stack are satisfied by the current model.
|
|
bool solver::verify_sat() {
|
|
LOG_H1("Checking current model...");
|
|
LOG("Assignment: " << assignments_pp(*this));
|
|
bool all_ok = true;
|
|
for (auto s : m_search) {
|
|
if (s.is_boolean()) {
|
|
bool ok = lit2cnstr(s.lit()).is_currently_true(*this);
|
|
LOG((ok ? "PASS" : "FAIL") << ": " << s.lit());
|
|
all_ok = all_ok && ok;
|
|
}
|
|
}
|
|
if (all_ok) LOG("All good!");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|