3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-06-06 14:13:23 +00:00

Connect conflict2

This commit is contained in:
Jakob Rath 2022-09-21 12:12:57 +02:00
parent a978604a7e
commit b43971bb4a
9 changed files with 420 additions and 293 deletions

View file

@ -4,10 +4,9 @@ z3_add_component(polysat
clause.cpp clause.cpp
clause_builder.cpp clause_builder.cpp
conflict.cpp conflict.cpp
conflict2.cpp
constraint.cpp constraint.cpp
eq_explain.cpp # eq_explain.cpp
explain.cpp # explain.cpp
forbidden_intervals.cpp forbidden_intervals.cpp
inference_logger.cpp inference_logger.cpp
justification.cpp justification.cpp
@ -15,7 +14,7 @@ z3_add_component(polysat
log.cpp log.cpp
op_constraint.cpp op_constraint.cpp
restart.cpp restart.cpp
saturation.cpp # saturation.cpp
search_state.cpp search_state.cpp
simplify.cpp simplify.cpp
simplify_clause.cpp simplify_clause.cpp
@ -23,7 +22,7 @@ z3_add_component(polysat
solver.cpp solver.cpp
ule_constraint.cpp ule_constraint.cpp
umul_ovfl_constraint.cpp umul_ovfl_constraint.cpp
variable_elimination.cpp # variable_elimination.cpp
viable.cpp viable.cpp
COMPONENT_DEPENDENCIES COMPONENT_DEPENDENCIES
util util

View file

@ -14,7 +14,8 @@ Notes:
--*/ --*/
#include "math/polysat/conflict2.h" #include "math/polysat/conflict.h"
#include "math/polysat/clause_builder.h"
#include "math/polysat/solver.h" #include "math/polysat/solver.h"
#include "math/polysat/inference_logger.h" #include "math/polysat/inference_logger.h"
#include "math/polysat/log.h" #include "math/polysat/log.h"
@ -25,10 +26,17 @@ Notes:
#include "math/polysat/saturation.h" #include "math/polysat/saturation.h"
#include "math/polysat/variable_elimination.h" #include "math/polysat/variable_elimination.h"
#include <algorithm> #include <algorithm>
#include <fstream>
namespace polysat { namespace polysat {
class header_with_var : public displayable {
char const* m_text;
pvar m_var;
public:
header_with_var(char const* text, pvar var) : m_text(text), m_var(var) { SASSERT(text); }
std::ostream& display(std::ostream& out) const override { return out << m_text << m_var; }
};
struct inf_resolve_bool : public inference { struct inf_resolve_bool : public inference {
sat::literal m_lit; sat::literal m_lit;
clause const& m_clause; clause const& m_clause;
@ -61,7 +69,7 @@ namespace polysat {
} }
}; };
conflict2::conflict2(solver& s) : s(s) { conflict::conflict(solver& s) : s(s) {
// TODO: m_log_conflicts is always false even if "polysat.log_conflicts=true" is given on the command line // TODO: m_log_conflicts is always false even if "polysat.log_conflicts=true" is given on the command line
if (true || s.get_config().m_log_conflicts) if (true || s.get_config().m_log_conflicts)
m_logger = alloc(file_inference_logger, s); m_logger = alloc(file_inference_logger, s);
@ -69,35 +77,73 @@ namespace polysat {
m_logger = alloc(dummy_inference_logger); m_logger = alloc(dummy_inference_logger);
} }
inference_logger& conflict2::logger() { inference_logger& conflict::logger() {
return *m_logger; return *m_logger;
} }
bool conflict2::empty() const { conflict::const_iterator conflict::begin() const {
return m_literals.empty() && m_vars.empty() && m_lemmas.empty(); return conflict_iterator::begin(s.m_constraints, m_literals);
} }
void conflict2::reset() { conflict::const_iterator conflict::end() const {
return conflict_iterator::end(s.m_constraints, m_literals);
}
bool conflict::empty() const {
return m_literals.empty()
&& m_vars.empty()
&& m_bail_vars.empty()
&& m_lemmas.empty();
}
void conflict::reset() {
m_literals.reset(); m_literals.reset();
m_vars.reset(); m_vars.reset();
m_bail_vars.reset();
m_relevant_vars.reset();
m_var_occurrences.reset(); m_var_occurrences.reset();
m_lemmas.reset(); m_lemmas.reset();
m_kind = conflict2_kind_t::ok; m_kind = conflict_kind_t::ok;
SASSERT(empty()); SASSERT(empty());
} }
void conflict2::set_bailout() { void conflict::set_bailout() {
SASSERT(m_kind == conflict2_kind_t::ok); SASSERT(m_kind == conflict_kind_t::ok);
m_kind = conflict2_kind_t::bailout; m_kind = conflict_kind_t::bailout;
s.m_stats.m_num_bailouts++; s.m_stats.m_num_bailouts++;
} }
void conflict2::set_backjump() { void conflict::set_backtrack() {
SASSERT(m_kind == conflict2_kind_t::ok); SASSERT(m_kind == conflict_kind_t::ok);
m_kind = conflict2_kind_t::backjump; SASSERT(m_relevant_vars.empty());
m_kind = conflict_kind_t::backtrack;
}
void conflict::set_backjump() {
SASSERT(m_kind == conflict_kind_t::ok);
m_kind = conflict_kind_t::backjump;
} }
void conflict2::init(signed_constraint c) { bool conflict::is_relevant_pvar(pvar v) const {
switch (m_kind) {
case conflict_kind_t::ok:
return contains_pvar(v);
case conflict_kind_t::bailout:
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;
}
}
bool conflict::is_relevant(sat::literal lit) const {
return contains(lit) || contains(~lit);
}
void conflict::init(signed_constraint c) {
SASSERT(empty()); SASSERT(empty());
if (c.bvalue(s) == l_false) { if (c.bvalue(s) == l_false) {
// boolean conflict // boolean conflict
@ -110,34 +156,48 @@ namespace polysat {
insert_vars(c); insert_vars(c);
} }
SASSERT(!empty()); SASSERT(!empty());
logger().begin_conflict(); // TODO: we often call reset/set so doing this here doesn't really work... make subsequent begins a no-op? or separate init and set? (set could then do reset() internally ... and we only need set() for signed_constraint, not all three variations)
}
void conflict::init(clause const& cl) {
// if (!empty())
// return;
// LOG("Conflict: " << cl);
SASSERT(empty());
for (auto lit : cl) {
auto c = s.lit2cnstr(lit);
SASSERT(c.bvalue(s) == l_false);
insert(~c);
}
SASSERT(!empty());
logger().begin_conflict(); logger().begin_conflict();
} }
void conflict2::init(pvar v, bool by_viable_fallback) { void conflict::init(pvar v, bool by_viable_fallback) {
if (by_viable_fallback) { if (by_viable_fallback) {
logger().begin_conflict(header_with_var("unsat core from viable fallback for v", v));
// Conflict detected by viable fallback: // Conflict detected by viable fallback:
// initial conflict is the unsat core of the univariate solver // initial conflict is the unsat core of the univariate solver
signed_constraints unsat_core = s.m_viable_fallback.unsat_core(v); signed_constraints unsat_core = s.m_viable_fallback.unsat_core(v);
for (auto c : unsat_core) for (auto c : unsat_core)
insert(c); insert(c);
logger().begin_conflict("unsat core from viable fallback");
// TODO: apply conflict resolution plugins here too? // TODO: apply conflict resolution plugins here too?
} else { } else {
logger().begin_conflict(header_with_var("forbidden interval lemma for v", v));
set_backtrack();
VERIFY(s.m_viable.resolve(v, *this)); VERIFY(s.m_viable.resolve(v, *this));
// TODO: in general the forbidden interval lemma is not asserting. // TODO: in general the forbidden interval lemma is not asserting.
// but each branch exclude the current assignment. // 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. // in those cases we will (additionally?) need an abstraction that is asserting to make sure viable is updated properly.
set_backjump();
logger().begin_conflict("forbidden interval lemma");
} }
} }
bool conflict2::contains(sat::literal lit) const { bool conflict::contains(sat::literal lit) const {
SASSERT(lit != sat::null_literal); SASSERT(lit != sat::null_literal);
return m_literals.contains(lit.index()); return m_literals.contains(lit.index());
} }
void conflict2::insert(signed_constraint c) { void conflict::insert(signed_constraint c) {
if (contains(c)) if (contains(c))
return; return;
if (c.is_always_true()) if (c.is_always_true())
@ -153,7 +213,7 @@ namespace polysat {
} }
} }
void conflict2::insert_eval(signed_constraint c) { void conflict::insert_eval(signed_constraint c) {
switch (c.bvalue(s)) { switch (c.bvalue(s)) {
case l_undef: case l_undef:
s.assign_eval(c.blit()); s.assign_eval(c.blit());
@ -166,20 +226,20 @@ namespace polysat {
insert(c); insert(c);
} }
void conflict2::insert_vars(signed_constraint c) { void conflict::insert_vars(signed_constraint c) {
for (pvar v : c->vars()) for (pvar v : c->vars())
if (s.is_assigned(v)) if (s.is_assigned(v))
m_vars.insert(v); m_vars.insert(v);
} }
void conflict2::remove(signed_constraint c) { void conflict::remove(signed_constraint c) {
SASSERT(contains(c)); SASSERT(contains(c));
m_literals.remove(c.blit().index()); m_literals.remove(c.blit().index());
for (pvar v : c->vars()) for (pvar v : c->vars())
m_var_occurrences[v]--; m_var_occurrences[v]--;
} }
void conflict2::resolve_bool(sat::literal lit, clause const& cl) { void conflict::resolve_bool(sat::literal lit, clause const& cl) {
// Note: core: x, y, z; corresponds to clause ~x \/ ~y \/ ~z // Note: core: x, y, z; corresponds to clause ~x \/ ~y \/ ~z
// clause: x \/ u \/ v // clause: x \/ u \/ v
// resolvent: ~y \/ ~z \/ u \/ v; as core: y, z, ~u, ~v // resolvent: ~y \/ ~z \/ u \/ v; as core: y, z, ~u, ~v
@ -197,7 +257,7 @@ namespace polysat {
logger().log(inf_resolve_bool(lit, cl)); logger().log(inf_resolve_bool(lit, cl));
} }
void conflict2::resolve_with_assignment(sat::literal lit) { void conflict::resolve_with_assignment(sat::literal lit) {
// The reason for lit is conceptually: // The reason for lit is conceptually:
// x1 = v1 /\ ... /\ xn = vn ==> lit // x1 = v1 /\ ... /\ xn = vn ==> lit
@ -227,19 +287,21 @@ namespace polysat {
logger().log(inf_resolve_with_assignment(s, lit, c)); logger().log(inf_resolve_with_assignment(s, lit, c));
} }
bool conflict2::resolve_value(pvar v) { bool conflict::resolve_value(pvar v) {
SASSERT(contains_pvar(v));
if (is_backjumping()) {
for (auto const& c : s.m_viable.get_constraints(v))
for (pvar v : c->vars())
logger().log_var(v);
return false;
}
if (is_bailout()) if (is_bailout())
return false; return false;
if (is_backtracking()) {
for (auto const& c : s.m_viable.get_constraints(v))
for (pvar v : c->vars()) {
m_relevant_vars.insert(v);
logger().log_var(v);
}
return false;
}
SASSERT(contains_pvar(v));
auto const& j = s.m_justification[v]; auto const& j = s.m_justification[v];
if (j.is_decision() && m_bail_vars.contains(v)) // TODO: what if also m_vars.contains(v)? might have a chance at elimination if (j.is_decision() && m_bail_vars.contains(v)) // TODO: what if also m_vars.contains(v)? might have a chance at elimination
@ -265,12 +327,79 @@ namespace polysat {
// Need to keep the variable in case of decision // Need to keep the variable in case of decision
if (s.is_assigned(v) && j.is_decision()) if (s.is_assigned(v) && j.is_decision())
m_vars.insert(v); m_vars.insert(v);
logger().log("bailout"); logger().log("Bailout");
return false; return false;
} }
std::ostream& conflict2::display(std::ostream& out) const { clause_ref conflict::build_lemma() {
out << "TODO\n"; LOG_H3("Build lemma from core");
LOG("core: " << *this);
clause_builder lemma(s);
// TODO: is this sound, doing it for each constraint separately?
// for (auto c : *this)
// minimize_vars(c);
for (auto c : *this)
lemma.push(~c);
for (unsigned v : m_vars) {
auto eq = s.eq(s.var(v), s.get_value(v));
if (eq.bvalue(s) == l_undef)
s.assign_eval(eq.blit());
lemma.push(~eq);
}
s.decay_activity();
logger().log_lemma(lemma);
logger().end_conflict();
return lemma.build();
}
bool conflict::minimize_vars(signed_constraint c) {
if (m_vars.empty())
return false;
if (!c.is_currently_false(s))
return false;
assignment_t a;
for (auto v : m_vars)
a.push_back(std::make_pair(v, s.get_value(v)));
for (unsigned i = 0; i < a.size(); ++i) {
std::pair<pvar, rational> save = a[i];
std::pair<pvar, rational> last = a.back();
a[i] = last;
a.pop_back();
if (c.is_currently_false(s, a))
--i;
else {
a.push_back(last);
a[i] = save;
}
}
if (a.size() == m_vars.num_elems())
return false;
m_vars.reset();
for (auto const& [v, val] : a)
m_vars.insert(v);
logger().log("minimize vars");
LOG("reduced " << m_vars);
return true;
}
std::ostream& conflict::display(std::ostream& out) const {
char const* sep = "";
for (auto c : *this)
out << sep << c->bvar2string() << " " << c, sep = " ; ";
if (!m_vars.empty())
out << " vars";
for (auto v : m_vars)
out << " v" << v;
if (!m_bail_vars.empty())
out << " bail vars";
for (auto v : m_bail_vars)
out << " v" << v;
return out; return out;
} }
} }

View file

@ -77,42 +77,54 @@ TODO:
- bailout lemma if no method applies (log these cases in particular because it indicates where we are missing something) - bailout lemma if no method applies (log these cases in particular because it indicates where we are missing something)
- force a restart if we get a bailout lemma or non-asserting conflict? - force a restart if we get a bailout lemma or non-asserting conflict?
- consider case if v is both in vars and bail_vars (do we need to keep it in bail_vars even if we can eliminate it from vars?) - consider case if v is both in vars and bail_vars (do we need to keep it in bail_vars even if we can eliminate it from vars?)
- Find a way to use resolve_value with forbidden interval lemmas.
Then get rid of conflict_kind_t::backtrack and m_relevant_vars.
Maybe:
x := a, y := b, z has no viable value
- assume y was propagated
- FI-Lemma C1 \/ ... \/ Cn without z.
- for each i, we should have x := a /\ Ci ==> y != b
- can we choose one of the Ci to cover the domain of y and extract an FI-Lemma D1 \/ ... \/ Dk without y,z?
- or try to find an L(x,y) such that C1 -> L, ..., Cn -> L, and L -> y != b (under x := a); worst case y != b can work as L
- minimize_vars... is it sound to do for each constraint separately, like we are doing now?
--*/ --*/
#pragma once #pragma once
#include "math/polysat/constraint.h" #include "math/polysat/constraint.h"
#include "math/polysat/clause_builder.h"
#include "math/polysat/inference_logger.h" #include "math/polysat/inference_logger.h"
#include <optional> #include <optional>
namespace polysat { namespace polysat {
class solver; class solver;
class explainer;
class inference_engine;
class variable_elimination_engine;
class conflict_iterator; class conflict_iterator;
class inference_logger;
enum class conflict2_kind_t { enum class conflict_kind_t {
// standard conflict resolution // standard conflict resolution
ok, ok,
// bailout lemma because no appropriate conflict resolution method applies // bailout lemma because no appropriate conflict resolution method applies
bailout, bailout,
// conflict contains the final lemma;
// backtrack to and revert the last relevant decision
// NOTE: this is currently used for the forbidden intervals lemmas.
// we should find a way to use resolve_value with these lemmas,
// to properly eliminate value propagations. (see todo notes above)
backtrack,
// conflict contains the final lemma;
// force backjumping without further conflict resolution because a good lemma has been found // force backjumping without further conflict resolution because a good lemma has been found
// TODO: distinguish backtrack/revert of last decision from backjump to second-highest level in the lemma
backjump, backjump,
}; };
class conflict2 { class conflict {
solver& s; solver& s;
scoped_ptr<inference_logger> m_logger; scoped_ptr<inference_logger> m_logger;
// current conflict core consists of m_literals and m_vars // current conflict core consists of m_literals and m_vars
indexed_uint_set m_literals; // set of boolean literals in the conflict indexed_uint_set m_literals; // set of boolean literals in the conflict
uint_set m_vars; // variable assignments used as premises, shorthand for literals (x := v) uint_set m_vars; // variable assignments used as premises, shorthand for literals (x := v)
uint_set m_bail_vars; // tracked for cone of influence but not directly involved in conflict resolution uint_set m_bail_vars; // decision variables that are only used to evaluate a constraint; see resolve_with_assignment.
uint_set m_relevant_vars; // tracked for cone of influence but not directly involved in conflict resolution
unsigned_vector m_var_occurrences; // for each variable, the number of constraints in m_literals that contain it unsigned_vector m_var_occurrences; // for each variable, the number of constraints in m_literals that contain it
@ -120,31 +132,47 @@ namespace polysat {
// TODO: we might not need all of these in the end. add only the side lemmas which justify a constraint in the final lemma (recursively)? // TODO: we might not need all of these in the end. add only the side lemmas which justify a constraint in the final lemma (recursively)?
vector<clause_ref> m_lemmas; vector<clause_ref> m_lemmas;
conflict2_kind_t m_kind = conflict2_kind_t::ok; conflict_kind_t m_kind = conflict_kind_t::ok;
bool minimize_vars(signed_constraint c);
public: public:
conflict2(solver& s); conflict(solver& s);
inference_logger& logger(); inference_logger& logger();
bool empty() const; bool empty() const;
void reset(); void reset();
uint_set const& vars() const { return m_vars; } using const_iterator = conflict_iterator;
const_iterator begin() const;
const_iterator end() const;
bool is_bailout() const { return m_kind == conflict2_kind_t::bailout; } uint_set const& vars() const { return m_vars; }
bool is_backjumping() const { return m_kind == conflict2_kind_t::backjump; } uint_set const& bail_vars() const { return m_bail_vars; }
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; }
bool is_backjumping() const { return m_kind == conflict_kind_t::backjump; }
void set_bailout(); void set_bailout();
void set_backtrack();
void set_backjump(); void set_backjump();
bool is_relevant_pvar(pvar v) const;
bool is_relevant(sat::literal lit) const;
/** conflict because the constraint c is false under current variable assignment */ /** conflict because the constraint c is false under current variable assignment */
void init(signed_constraint c); void init(signed_constraint c);
/** boolean conflict with the given clause */
void init(clause const& cl);
/** conflict because there is no viable value for the variable v */ /** conflict because there is no viable value for the variable v */
void init(pvar v, bool by_viable_fallback = false); void init(pvar v, bool by_viable_fallback);
bool contains(signed_constraint c) const { SASSERT(c); return contains(c.blit()); } bool contains(signed_constraint c) const { SASSERT(c); return contains(c.blit()); }
bool contains(sat::literal lit) const; bool contains(sat::literal lit) const;
bool contains_pvar(pvar v) const { return m_vars.contains(v) || m_bail_vars.contains(v); } bool contains_pvar(pvar v) const { return m_vars.contains(v) || m_bail_vars.contains(v); }
bool pvar_occurs_in_constraints(pvar v) const { return v < m_var_occurrences.size() && m_var_occurrences[v] > 0; }
/** /**
* Insert constraint c into conflict state. * Insert constraint c into conflict state.
@ -172,9 +200,53 @@ namespace polysat {
/** Perform resolution with "v = value <- ..." */ /** Perform resolution with "v = value <- ..." */
bool resolve_value(pvar v); bool resolve_value(pvar v);
/** Convert the core into a lemma to be learned. */
clause_ref build_lemma();
std::ostream& display(std::ostream& out) const; std::ostream& display(std::ostream& out) const;
}; };
inline std::ostream& operator<<(std::ostream& out, conflict2 const& c) { return c.display(out); } inline std::ostream& operator<<(std::ostream& out, conflict const& c) { return c.display(out); }
class conflict_iterator {
friend class conflict;
using inner_t = indexed_uint_set::iterator;
constraint_manager* m_cm;
inner_t m_inner;
conflict_iterator(constraint_manager& cm, inner_t inner):
m_cm(&cm), m_inner(inner) {}
static conflict_iterator begin(constraint_manager& cm, indexed_uint_set const& lits) {
return {cm, lits.begin()};
}
static conflict_iterator end(constraint_manager& cm, indexed_uint_set const& lits) {
return {cm, lits.end()};
}
public:
using value_type = signed_constraint;
using difference_type = unsigned;
using pointer = signed_constraint const*;
using reference = signed_constraint const&;
using iterator_category = std::input_iterator_tag;
conflict_iterator& operator++() {
++m_inner;
return *this;
}
signed_constraint operator*() const {
return m_cm->lookup(sat::to_literal(*m_inner));
}
bool operator==(conflict_iterator const& other) const {
return m_inner == other.m_inner;
}
bool operator!=(conflict_iterator const& other) const { return !operator==(other); }
};
} }

View file

@ -40,48 +40,53 @@ namespace polysat {
return std::string(70, '-'); return std::string(70, '-');
} }
void file_inference_logger::begin_conflict(char const* text) { void file_inference_logger::begin_conflict(displayable const& header) {
++m_num_conflicts; ++m_num_conflicts;
if (text) LOG("Begin CONFLICT #" << m_num_conflicts << ": " << header);
LOG("Begin CONFLICT #" << m_num_conflicts << " (" << text << ")");
else
LOG("Begin CONFLICT #" << m_num_conflicts);
m_used_constraints.reset(); m_used_constraints.reset();
m_used_vars.reset(); m_used_vars.reset();
if (!m_out) if (!m_out)
m_out = alloc(std::ofstream, "conflicts.txt"); m_out = alloc(std::ofstream, "conflicts.txt");
else else
out() << "\n\n\n\n\n\n\n\n\n\n\n\n"; out() << "\n\n\n\n\n\n\n\n\n\n\n\n";
out() << "CONFLICT #" << m_num_conflicts; out() << "CONFLICT #" << m_num_conflicts << ": " << header << "\n";
if (text)
out() << " (" << text << ")";
out() << "\n";
// log initial conflict state // log initial conflict state
out() << hline() << "\n"; out() << hline() << "\n";
log_conflict_state(); log_conflict_state();
} }
void file_inference_logger::log_conflict_state() { void file_inference_logger::log_conflict_state() {
out() << "TODO"; conflict const& core = s.m_conflict;
/* TODO: update for new conflict
conflict2 const& core = s.m_conflict2;
for (auto const& c : core) { for (auto const& c : core) {
out_indent() << c.blit() << ": " << c << '\n'; sat::literal const lit = c.blit();
m_used_constraints.insert(c.blit().index()); out_indent() << lit << ": " << c << '\n';
// TODO: if justified by a side lemma, print it here
// out_indent() << " justified by: " << lemma << '\n';
m_used_constraints.insert(lit.index());
for (pvar v : c->vars()) for (pvar v : c->vars())
m_used_vars.insert(v); m_used_vars.insert(v);
} }
for (auto v : core.vars()) { for (auto v : core.vars()) {
out_indent() << assignment_pp(core.s, v, core.s.get_value(v)) << "\n"; out_indent() << assignment_pp(s, v, s.get_value(v)) << "\n";
m_used_vars.insert(v); m_used_vars.insert(v);
} }
for (auto v : core.bail_vars()) { for (auto v : core.bail_vars()) {
out_indent() << assignment_pp(core.s, v, core.s.get_value(v)) << " (bail)\n"; out_indent() << assignment_pp(s, v, s.get_value(v)) << " (bail)\n";
m_used_vars.insert(v); m_used_vars.insert(v);
} }
if (core.is_bailout()) switch (core.kind()) {
case conflict_kind_t::ok:
break;
case conflict_kind_t::bailout:
out_indent() << "(bailout)\n"; out_indent() << "(bailout)\n";
*/ break;
case conflict_kind_t::backtrack:
out_indent() << "(backtrack)\n";
break;
case conflict_kind_t::backjump:
out_indent() << "(backjump)\n";
break;
}
out().flush(); out().flush();
} }
@ -102,7 +107,6 @@ namespace polysat {
out() << "\n"; out() << "\n";
for (auto const& lit : cb) for (auto const& lit : cb)
out_indent() << lit_pp(s, lit) << "\n"; out_indent() << lit_pp(s, lit) << "\n";
// out_indent() << lit << ": " << s.lit2cnstr(lit) << "\n";
out().flush(); out().flush();
} }

View file

@ -26,13 +26,26 @@ namespace polysat {
class search_item; class search_item;
class solver; class solver;
class inference { class displayable {
public: public:
virtual ~inference() {} virtual ~displayable() {}
virtual std::ostream& display(std::ostream& out) const = 0; virtual std::ostream& display(std::ostream& out) const = 0;
}; };
inline std::ostream& operator<<(std::ostream& out, inference const& i) { return i.display(out); } inline std::ostream& operator<<(std::ostream& out, displayable const& d) { return d.display(out); }
class display_c_str : public displayable {
char const* m_str;
public:
display_c_str(char const* str) : m_str(str) { }
std::ostream& display(std::ostream& out) const override {
if (m_str)
out << m_str;
return out;
}
};
class inference : public displayable { };
class inference_named : public inference { class inference_named : public inference {
char const* m_name; char const* m_name;
@ -46,12 +59,14 @@ namespace polysat {
virtual ~inference_logger() {} virtual ~inference_logger() {}
/// Begin next conflict /// Begin next conflict
virtual void begin_conflict(char const* text = nullptr) = 0; void begin_conflict(char const* text = nullptr) { begin_conflict(display_c_str(text)); }
virtual void begin_conflict(displayable const& header) = 0;
/// Log inference and the current state. /// Log inference and the current state.
virtual void log(inference const& inf) = 0; virtual void log(inference const& inf) = 0;
virtual void log(char const* name) { log(inference_named(name)); } virtual void log(char const* name) { log(inference_named(name)); }
virtual void log_var(pvar v) = 0; virtual void log_var(pvar v) = 0;
/// Log relevant part of search state and viable. /// Log relevant part of search state and viable.
/// Call end_conflict before backjumping in the solver.
virtual void end_conflict() = 0; virtual void end_conflict() = 0;
/// Log current conflict state (implicitly done by log_inference) /// Log current conflict state (implicitly done by log_inference)
@ -62,7 +77,7 @@ namespace polysat {
class dummy_inference_logger : public inference_logger { class dummy_inference_logger : public inference_logger {
public: public:
virtual void begin_conflict(char const* text) override {} virtual void begin_conflict(displayable const& header) override {}
virtual void log(inference const& inf) override {} virtual void log(inference const& inf) override {}
virtual void log_var(pvar v) override {} virtual void log_var(pvar v) override {}
virtual void end_conflict() override {} virtual void end_conflict() override {}
@ -85,18 +100,11 @@ namespace polysat {
public: public:
file_inference_logger(solver& s); file_inference_logger(solver& s);
void begin_conflict(displayable const& header) override;
/// Begin next conflict
void begin_conflict(char const* text) override;
/// Log inference and the current state.
void log(inference const& inf) override; void log(inference const& inf) override;
void log_var(pvar v) override; void log_var(pvar v) override;
/// Log relevant part of search state and viable.
void end_conflict() override; void end_conflict() override;
/// Log current conflict state (implicitly done by log_inference)
void log_conflict_state() override; void log_conflict_state() override;
void log_lemma(clause_builder const& cb) override; void log_lemma(clause_builder const& cb) override;
}; };

View file

@ -33,7 +33,6 @@ namespace polysat {
m_viable_fallback(*this), m_viable_fallback(*this),
m_linear_solver(*this), m_linear_solver(*this),
m_conflict(*this), m_conflict(*this),
m_conflict2(*this),
m_simplify_clause(*this), m_simplify_clause(*this),
m_simplify(*this), m_simplify(*this),
m_restart(*this), m_restart(*this),
@ -545,7 +544,7 @@ namespace polysat {
// (fail here in debug mode so we notice if we miss some) // (fail here in debug mode so we notice if we miss some)
DEBUG_CODE( UNREACHABLE(); ); DEBUG_CODE( UNREACHABLE(); );
m_free_pvars.unassign_var_eh(v); m_free_pvars.unassign_var_eh(v);
set_conflict(v); set_conflict(v, false);
return; return;
case dd::find_t::singleton: case dd::find_t::singleton:
// NOTE: this case may happen legitimately if all other possibilities were excluded by brute force search // NOTE: this case may happen legitimately if all other possibilities were excluded by brute force search
@ -578,8 +577,7 @@ namespace polysat {
case dd::find_t::empty: case dd::find_t::empty:
LOG("Fallback solver: unsat"); LOG("Fallback solver: unsat");
m_free_pvars.unassign_var_eh(v); m_free_pvars.unassign_var_eh(v);
auto core = m_viable_fallback.unsat_core(v); // TODO: add constraints from unsat_core to conflict set_conflict(v, true);
set_conflict(v);
return; return;
} }
} }
@ -620,17 +618,6 @@ namespace polysat {
SASSERT(is_conflict()); SASSERT(is_conflict());
if (m_conflict.conflict_var() != null_var) {
m_conflict.begin_conflict("backtrack_fi");
pvar v = m_conflict.conflict_var();
// This case corresponds to a propagation of conflict_var, except it's not explicitly on the stack.
// TODO: use unsat core from m_viable_fallback if the conflict is from there
VERIFY(m_viable.resolve(v, m_conflict));
backtrack_fi();
return;
}
m_conflict.begin_conflict("resolve_conflict");
search_iterator search_it(m_search); search_iterator search_it(m_search);
while (search_it.next()) { while (search_it.next()) {
auto& item = *search_it; auto& item = *search_it;
@ -638,23 +625,21 @@ namespace polysat {
if (item.is_assignment()) { if (item.is_assignment()) {
// Resolve over variable assignment // Resolve over variable assignment
pvar v = item.var(); pvar v = item.var();
if (!m_conflict.contains_pvar(v) && !m_conflict.is_bailout()) { // if (!m_conflict.contains_pvar(v) && !m_conflict.is_bailout()) {
if (!m_conflict.is_relevant_pvar(v)) {
m_search.pop_assignment(); m_search.pop_assignment();
continue; continue;
} }
LOG_H2("Working on " << search_item_pp(m_search, item)); LOG_H2("Working on " << search_item_pp(m_search, item));
LOG(m_justification[v]); LOG(m_justification[v]);
LOG("Conflict: " << m_conflict); LOG("Conflict: " << m_conflict);
// inc_activity(v); // done in resolve_value
justification& j = m_justification[v]; justification& j = m_justification[v];
if (j.level() > base_level() && !m_conflict.resolve_value(v) && j.is_decision()) { if (j.level() > base_level() && !m_conflict.resolve_value(v) && j.is_decision()) {
m_conflict.end_conflict();
revert_decision(v); revert_decision(v);
return; return;
} }
if (m_conflict.is_bailout_lemma()) { if (m_conflict.is_backjumping()) {
m_conflict.end_conflict(); backjump_lemma();
backtrack_lemma();
return; return;
} }
m_search.pop_assignment(); m_search.pop_assignment();
@ -664,36 +649,37 @@ namespace polysat {
SASSERT(item.is_boolean()); SASSERT(item.is_boolean());
sat::literal const lit = item.lit(); sat::literal const lit = item.lit();
sat::bool_var const var = lit.var(); sat::bool_var const var = lit.var();
if (!m_conflict.is_marked(var)) if (!m_conflict.is_relevant(lit))
continue; continue;
LOG_H2("Working on " << search_item_pp(m_search, item)); LOG_H2("Working on " << search_item_pp(m_search, item));
LOG(bool_justification_pp(m_bvars, lit)); LOG(bool_justification_pp(m_bvars, lit));
LOG("Literal " << lit << " is " << lit2cnstr(lit)); LOG("Literal " << lit << " is " << lit2cnstr(lit));
LOG("Conflict: " << m_conflict); LOG("Conflict: " << m_conflict);
if (m_bvars.is_assumption(var)) // TODO: wouldn't this mean we're already at the base level? if (m_bvars.is_assumption(var)) {
continue; SASSERT(m_bvars.level(var) <= base_level());
break;
}
else if (m_bvars.is_bool_propagation(var)) else if (m_bvars.is_bool_propagation(var))
m_conflict.resolve(lit, *m_bvars.reason(lit)); m_conflict.resolve_bool(lit, *m_bvars.reason(lit));
else { else {
SASSERT(m_bvars.is_value_propagation(var)); SASSERT(m_bvars.is_value_propagation(var));
m_conflict.resolve_with_assignment(lit, m_bvars.level(lit)); m_conflict.resolve_with_assignment(lit);
} }
} }
} }
LOG("End of resolve_conflict loop"); LOG("End of resolve_conflict loop");
m_conflict.end_conflict(); m_conflict.logger().end_conflict();
report_unsat(); report_unsat();
} }
/** /**
* Simple backtracking for lemmas: * Simple backjumping for lemmas:
* jump to the level where the lemma can be (bool-)propagated, * jump to the level where the lemma can be (bool-)propagated,
* even without reverting the last decision. * even without reverting the last decision.
*/ */
void solver::backtrack_lemma() { void solver::backjump_lemma() {
clause_ref lemma = m_conflict.build_lemma().build(); clause_ref lemma = m_conflict.build_lemma();
LOG_H2("backtrack_lemma: " << show_deref(lemma)); LOG_H2("backjump_lemma: " << show_deref(lemma));
SASSERT(lemma); SASSERT(lemma);
LOG("Lemma: " << *lemma); LOG("Lemma: " << *lemma);
for (sat::literal lit : *lemma) { for (sat::literal lit : *lemma) {
@ -715,6 +701,7 @@ namespace polysat {
jump_level = lit_level; jump_level = lit_level;
} }
} }
SASSERT(max_level == m_level); // not required; see comment on max_level
jump_level = std::max(jump_level, base_level()); jump_level = std::max(jump_level, base_level());
@ -728,69 +715,6 @@ namespace polysat {
learn_lemma(*lemma); learn_lemma(*lemma);
} }
/**
* Simpler backtracking for forbidden interval lemmas:
* since forbidden intervals already gives us a lemma where the conflict variable has been eliminated,
* we can backtrack to the last relevant decision and learn this lemma.
*/
void solver::backtrack_fi() {
uint_set relevant_vars;
search_iterator search_it(m_search);
while (search_it.next()) {
auto& item = *search_it;
search_it.set_resolved();
if (item.is_assignment()) {
// Resolve over variable assignment
pvar v = item.var();
if (!m_conflict.pvar_occurs_in_constraints(v) && !relevant_vars.contains(v)) {
m_search.pop_assignment();
continue;
}
LOG_H2("Working on " << search_item_pp(m_search, item));
LOG(m_justification[v]);
LOG("Conflict: " << m_conflict);
inc_activity(v);
justification& j = m_justification[v];
if (j.level() > base_level() && j.is_decision()) {
m_conflict.end_conflict();
revert_decision(v);
return;
}
SASSERT(j.is_propagation());
// If a variable was propagated:
// we don't really care about the constraints in this case, but just about the variables it depends on
for (auto const& c : m_viable.get_constraints(v))
for (pvar v : c->vars()) {
relevant_vars.insert(v);
m_conflict.log_var(v);
}
m_search.pop_assignment();
}
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_marked(var))
continue;
LOG_H2("Working on " << search_item_pp(m_search, item));
LOG(bool_justification_pp(m_bvars, lit));
LOG("Literal " << lit << " is " << lit2cnstr(lit));
LOG("Conflict: " << m_conflict);
if (m_bvars.is_assumption(var)) // TODO: wouldn't this mean we're already at the base level?
continue;
else if (m_bvars.is_bool_propagation(var))
m_conflict.resolve(lit, *m_bvars.reason(lit));
else {
SASSERT(m_bvars.is_value_propagation(var));
continue;
}
}
}
m_conflict.end_conflict();
report_unsat();
}
/** /**
* Variable activity accounting. * Variable activity accounting.
* As a placeholder we increment activity * As a placeholder we increment activity
@ -839,6 +763,7 @@ namespace polysat {
LOG("Learning: "<< lemma); LOG("Learning: "<< lemma);
SASSERT(!lemma.empty()); SASSERT(!lemma.empty());
m_simplify_clause.apply(lemma); m_simplify_clause.apply(lemma);
// TODO: print warning if the lemma is non-asserting?
add_clause(lemma); add_clause(lemma);
} }
@ -858,7 +783,7 @@ namespace polysat {
LOG_H3("Reverting decision: pvar " << v << " := " << val); LOG_H3("Reverting decision: pvar " << v << " := " << val);
SASSERT(m_justification[v].is_decision()); SASSERT(m_justification[v].is_decision());
clause_ref lemma = m_conflict.build_lemma().build(); clause_ref lemma = m_conflict.build_lemma();
if (lemma->empty()) if (lemma->empty())
report_unsat(); report_unsat();
else { else {
@ -887,7 +812,7 @@ namespace polysat {
} }
void solver::assign_eval(sat::literal lit) { void solver::assign_eval(sat::literal lit) {
SASSERT(lit2cnstr(lit).is_currently_true(*this)); // SASSERT(lit2cnstr(lit).is_currently_true(*this)); // "morally" this should hold, but currently fails because of pop_assignment during resolve_conflict
unsigned level = 0; unsigned level = 0;
// NOTE: constraint may be evaluated even if some variables are still unassigned (e.g., 0*x doesn't depend on x). // NOTE: constraint may be evaluated even if some variables are still unassigned (e.g., 0*x doesn't depend on x).
for (auto v : lit2cnstr(lit)->vars()) for (auto v : lit2cnstr(lit)->vars())
@ -934,12 +859,13 @@ namespace polysat {
void solver::add_clause(clause& clause) { void solver::add_clause(clause& clause) {
LOG((clause.is_redundant() ? "Lemma: ": "Aux: ") << clause); LOG((clause.is_redundant() ? "Lemma: ": "Aux: ") << clause);
for (sat::literal lit : clause) { for (sat::literal lit : clause) {
LOG(" Literal " << lit << " is: " << lit_pp(*this, lit)); LOG(" " << lit_pp(*this, lit));
// SASSERT(m_bvars.value(lit) != l_true); // SASSERT(m_bvars.value(lit) != l_true);
// it could be that such a literal has been created previously but we don't detect it when e.g. narrowing a mul_ovfl_constraint // it could be that such a literal has been created previously but we don't detect it when e.g. narrowing a mul_ovfl_constraint
if (m_bvars.value(lit) == l_true) { if (m_bvars.value(lit) == l_true) {
// in this case the clause is useless // in this case the clause is useless
LOG(" Clause is already true, skipping..."); LOG(" Clause is already true, skipping...");
// SASSERT(false); // should never happen (at least for redundant clauses)
return; return;
} }
} }
@ -1106,12 +1032,14 @@ namespace polysat {
* Check that two variables of each constraint are watched. * Check that two variables of each constraint are watched.
*/ */
bool solver::wlist_invariant() { bool solver::wlist_invariant() {
#if 0
for (pvar v = 0; v < m_value.size(); ++v) { for (pvar v = 0; v < m_value.size(); ++v) {
std::stringstream s; std::stringstream s;
for (constraint* c : m_pwatch[v]) for (constraint* c : m_pwatch[v])
s << " " << c->bvar(); s << " " << c->bvar();
LOG("Watch for v" << v << ": " << s.str()); LOG("Watch for v" << v << ": " << s.str());
} }
#endif
// Skip boolean variables that aren't active yet // Skip boolean variables that aren't active yet
uint_set skip; uint_set skip;
for (unsigned i = m_qhead; i < m_search.size(); ++i) for (unsigned i = m_qhead; i < m_search.size(); ++i)

View file

@ -22,7 +22,6 @@ Author:
#include "util/params.h" #include "util/params.h"
#include "math/polysat/boolean.h" #include "math/polysat/boolean.h"
#include "math/polysat/conflict.h" #include "math/polysat/conflict.h"
#include "math/polysat/conflict2.h"
#include "math/polysat/constraint.h" #include "math/polysat/constraint.h"
#include "math/polysat/clause_builder.h" #include "math/polysat/clause_builder.h"
#include "math/polysat/simplify_clause.h" #include "math/polysat/simplify_clause.h"
@ -37,8 +36,6 @@ Author:
#include "math/polysat/viable.h" #include "math/polysat/viable.h"
#include "math/polysat/log.h" #include "math/polysat/log.h"
namespace polysat { namespace polysat {
struct config { struct config {
@ -70,14 +67,12 @@ namespace polysat {
friend class clause; friend class clause;
friend class clause_builder; friend class clause_builder;
friend class conflict; friend class conflict;
friend class conflict2;
friend class conflict_explainer; friend class conflict_explainer;
friend class simplify_clause; friend class simplify_clause;
friend class simplify; friend class simplify;
friend class restart; friend class restart;
friend class explainer; friend class explainer;
friend class inference_engine; friend class inference_engine;
friend class old_inference_logger;
friend class file_inference_logger; friend class file_inference_logger;
friend class forbidden_intervals; friend class forbidden_intervals;
friend class linear_solver; friend class linear_solver;
@ -94,7 +89,6 @@ namespace polysat {
friend class scoped_solverv; friend class scoped_solverv;
friend class test_polysat; friend class test_polysat;
friend class test_fi; friend class test_fi;
friend struct inference_resolve_with_assignment;
friend struct inf_resolve_with_assignment; friend struct inf_resolve_with_assignment;
reslimit& m_lim; reslimit& m_lim;
@ -105,7 +99,6 @@ namespace polysat {
viable_fallback m_viable_fallback; // fallback for viable, using bitblasting over univariate constraints viable_fallback m_viable_fallback; // fallback for viable, using bitblasting over univariate constraints
linear_solver m_linear_solver; linear_solver m_linear_solver;
conflict m_conflict; conflict m_conflict;
conflict2 m_conflict2;
simplify_clause m_simplify_clause; simplify_clause m_simplify_clause;
simplify m_simplify; simplify m_simplify;
restart m_restart; restart m_restart;
@ -192,9 +185,9 @@ namespace polysat {
void erase_pwatch(pvar v, constraint* c); void erase_pwatch(pvar v, constraint* c);
void erase_pwatch(constraint* c); void erase_pwatch(constraint* c);
void set_conflict(signed_constraint c) { m_conflict.set(c); } void set_conflict(signed_constraint c) { m_conflict.init(c); }
void set_conflict(clause& cl) { m_conflict.set(cl); } void set_conflict(clause& cl) { m_conflict.init(cl); }
void set_conflict(pvar v) { m_conflict.set(v); } void set_conflict(pvar v, bool by_viable_fallback) { m_conflict.init(v, by_viable_fallback); }
bool can_decide() const { return !m_free_pvars.empty(); } bool can_decide() const { return !m_free_pvars.empty(); }
void decide(); void decide();
@ -202,14 +195,12 @@ namespace polysat {
void linear_propagate(); void linear_propagate();
bool is_conflict() const { return !m_conflict.empty(); } bool is_conflict() const { return !m_conflict.empty(); }
bool at_base_level() const; bool at_base_level() const;
unsigned base_level() const; unsigned base_level() const;
void resolve_conflict(); void resolve_conflict();
void backtrack_fi(); void backjump_lemma();
void backtrack_lemma();
void revert_decision(pvar v); void revert_decision(pvar v);
void revert_bool_decision(sat::literal lit); void revert_bool_decision(sat::literal lit);
@ -461,7 +452,4 @@ namespace polysat {
inline std::ostream& operator<<(std::ostream& out, assignments_pp const& a) { return a.display(out); } inline std::ostream& operator<<(std::ostream& out, assignments_pp const& a) { return a.display(out); }
} }

View file

@ -29,9 +29,19 @@ TODO: improve management of the fallback univariate solvers:
#include "util/debug.h" #include "util/debug.h"
#include "math/polysat/viable.h" #include "math/polysat/viable.h"
#include "math/polysat/solver.h" #include "math/polysat/solver.h"
#include "math/polysat/univariate/univariate_solver.h"
namespace polysat { namespace polysat {
struct inf_fi : public inference {
viable& v;
pvar var;
inf_fi(viable& v, pvar var) : v(v), var(var) {}
std::ostream& display(std::ostream& out) const override {
return out << "Forbidden intervals for v" << var << ": " << viable::var_pp(v, var);
}
};
viable::viable(solver& s): viable::viable(solver& s):
s(s), s(s),
m_forbidden_intervals(s) { m_forbidden_intervals(s) {
@ -54,7 +64,7 @@ namespace polysat {
m_diseq_lin.pop_back(); m_diseq_lin.pop_back();
} }
viable::entry* viable::alloc_entry() { viable::entry* viable::alloc_entry() {
if (m_alloc.empty()) if (m_alloc.empty())
return alloc(entry); return alloc(entry);
auto* e = m_alloc.back(); auto* e = m_alloc.back();
@ -68,21 +78,21 @@ namespace polysat {
auto& [v, k, e] = m_trail.back(); auto& [v, k, e] = m_trail.back();
SASSERT(well_formed(m_units[v])); SASSERT(well_formed(m_units[v]));
switch (k) { switch (k) {
case entry_kind::unit_e: case entry_kind::unit_e:
e->remove_from(m_units[v], e); e->remove_from(m_units[v], e);
SASSERT(well_formed(m_units[v])); SASSERT(well_formed(m_units[v]));
break; break;
case entry_kind::equal_e: case entry_kind::equal_e:
e->remove_from(m_equal_lin[v], e); e->remove_from(m_equal_lin[v], e);
break; break;
case entry_kind::diseq_e: case entry_kind::diseq_e:
e->remove_from(m_diseq_lin[v], e); e->remove_from(m_diseq_lin[v], e);
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
break; break;
} }
m_alloc.push_back(e); m_alloc.push_back(e);
m_trail.pop_back(); m_trail.pop_back();
} }
@ -98,12 +108,12 @@ namespace polysat {
if (e->interval.lo_val() < m_units[v]->interval.lo_val()) if (e->interval.lo_val() < m_units[v]->interval.lo_val())
m_units[v] = e; m_units[v] = e;
} }
else else
m_units[v] = e; m_units[v] = e;
SASSERT(well_formed(m_units[v])); SASSERT(well_formed(m_units[v]));
m_trail.pop_back(); m_trail.pop_back();
} }
bool viable::intersect(pdd const & p, pdd const & q, signed_constraint const& sc) { bool viable::intersect(pdd const & p, pdd const & q, signed_constraint const& sc) {
pvar v = null_var; pvar v = null_var;
bool first = true; bool first = true;
@ -124,7 +134,7 @@ namespace polysat {
prop = true; prop = true;
break; break;
case dd::find_t::empty: case dd::find_t::empty:
s.set_conflict(v); s.set_conflict(v, false);
return true; return true;
default: default:
break; break;
@ -136,7 +146,7 @@ namespace polysat {
goto try_viable; goto try_viable;
} }
return prop; return prop;
} }
bool viable::intersect(pvar v, signed_constraint const& c) { bool viable::intersect(pvar v, signed_constraint const& c) {
entry* ne = alloc_entry(); entry* ne = alloc_entry();
@ -184,12 +194,11 @@ namespace polysat {
m_alloc.push_back(ne); m_alloc.push_back(ne);
return false; return false;
} }
auto create_entry = [&]() { auto create_entry = [&]() {
m_trail.push_back({ v, entry_kind::unit_e, ne }); m_trail.push_back({ v, entry_kind::unit_e, ne });
s.m_trail.push_back(trail_instr_t::viable_add_i); s.m_trail.push_back(trail_instr_t::viable_add_i);
ne->init(ne); ne->init(ne);
return ne; return ne;
}; };
@ -200,13 +209,13 @@ namespace polysat {
}; };
if (ne->interval.is_full()) { if (ne->interval.is_full()) {
while (m_units[v]) while (m_units[v])
remove_entry(m_units[v]); remove_entry(m_units[v]);
m_units[v] = create_entry(); m_units[v] = create_entry();
return true; return true;
} }
if (!e) if (!e)
m_units[v] = create_entry(); m_units[v] = create_entry();
else { else {
entry* first = e; entry* first = e;
@ -222,10 +231,10 @@ namespace polysat {
m_units[v] = create_entry(); m_units[v] = create_entry();
return true; return true;
} }
if (e == first) if (e == first)
first = n; first = n;
e = n; e = n;
} }
SASSERT(e->interval.lo_val() != ne->interval.lo_val()); SASSERT(e->interval.lo_val() != ne->interval.lo_val());
if (e->interval.lo_val() > ne->interval.lo_val()) { if (e->interval.lo_val() > ne->interval.lo_val()) {
if (first->prev()->interval.currently_contains(ne->interval)) { if (first->prev()->interval.currently_contains(ne->interval)) {
@ -239,7 +248,7 @@ namespace polysat {
return true; return true;
} }
e = e->next(); e = e->next();
} }
while (e != first); while (e != first);
// otherwise, append to end of list // otherwise, append to end of list
first->insert_before(create_entry()); first->insert_before(create_entry());
@ -255,10 +264,10 @@ namespace polysat {
/** /**
* Traverse all interval constraints with coefficients to check whether current value 'val' for * Traverse all interval constraints with coefficients to check whether current value 'val' for
* 'v' is feasible. If not, extract a (maximal) interval to block 'v' from being assigned val. * 'v' is feasible. If not, extract a (maximal) interval to block 'v' from being assigned val.
* *
* To investigate: * To investigate:
* - side conditions are stronger than for unit intervals. They constrain the lower and upper bounds to * - side conditions are stronger than for unit intervals. They constrain the lower and upper bounds to
* be precisely the assigned values. This is to ensure that lo/hi that are computed based on lo_val * be precisely the assigned values. This is to ensure that lo/hi that are computed based on lo_val
* and division with coeff are valid. Is there a more relaxed scheme? * and division with coeff are valid. Is there a more relaxed scheme?
*/ */
bool viable::refine_equal_lin(pvar v, rational const& val) { bool viable::refine_equal_lin(pvar v, rational const& val) {
@ -330,7 +339,7 @@ namespace polysat {
if (e->interval.lo_val() < e->interval.hi_val()) { if (e->interval.lo_val() < e->interval.hi_val()) {
increase_hi(hi); increase_hi(hi);
decrease_lo(lo); decrease_lo(lo);
} }
else if (e->interval.lo_val() <= coeff_val) { else if (e->interval.lo_val() <= coeff_val) {
rational lambda_u = floor((max_value - coeff_val) / e->coeff); rational lambda_u = floor((max_value - coeff_val) / e->coeff);
@ -342,13 +351,13 @@ namespace polysat {
else { else {
SASSERT(coeff_val < e->interval.hi_val()); SASSERT(coeff_val < e->interval.hi_val());
rational lambda_l = floor(coeff_val / e->coeff); rational lambda_l = floor(coeff_val / e->coeff);
lo = val - lambda_l; lo = val - lambda_l;
increase_hi(hi); increase_hi(hi);
} }
LOG("forbidden interval v" << v << " " << val << " " << e->coeff << " * " << e->interval << " [" << lo << ", " << hi << "["); LOG("forbidden interval v" << v << " " << val << " " << e->coeff << " * " << e->interval << " [" << lo << ", " << hi << "[");
SASSERT(hi <= mod_value); SASSERT(hi <= mod_value);
bool full = (lo == 0 && hi == mod_value); bool full = (lo == 0 && hi == mod_value);
if (hi == mod_value) if (hi == mod_value)
hi = 0; hi = 0;
pdd lop = s.var2pdd(v).mk_val(lo); pdd lop = s.var2pdd(v).mk_val(lo);
pdd hip = s.var2pdd(v).mk_val(hi); pdd hip = s.var2pdd(v).mk_val(hi);
@ -358,13 +367,13 @@ namespace polysat {
ne->coeff = 1; ne->coeff = 1;
if (full) if (full)
ne->interval = eval_interval::full(); ne->interval = eval_interval::full();
else else
ne->interval = eval_interval::proper(lop, lo, hip, hi); ne->interval = eval_interval::proper(lop, lo, hip, hi);
intersect(v, ne); intersect(v, ne);
return false; return false;
} }
e = e->next(); e = e->next();
} }
while (e != first); while (e != first);
return true; return true;
} }
@ -453,7 +462,7 @@ namespace polysat {
return true; return true;
} }
bool viable::has_viable(pvar v) { bool viable::has_viable(pvar v) {
refined: refined:
auto* e = m_units[v]; auto* e = m_units[v];
@ -468,9 +477,9 @@ namespace polysat {
return false; return false;
// quick check: last interval doesn't wrap around, so hi_val // quick check: last interval doesn't wrap around, so hi_val
// has not been covered // has not been covered
if (last->interval.lo_val() < last->interval.hi_val()) if (last->interval.lo_val() < last->interval.hi_val())
CHECK_RETURN(last->interval.hi_val()); CHECK_RETURN(last->interval.hi_val());
do { do {
if (e->interval.is_full()) if (e->interval.is_full())
return false; return false;
@ -482,16 +491,16 @@ namespace polysat {
if (n == first) { if (n == first) {
if (e->interval.lo_val() > e->interval.hi_val()) if (e->interval.lo_val() > e->interval.hi_val())
return false; return false;
CHECK_RETURN(e->interval.hi_val()); CHECK_RETURN(e->interval.hi_val());
} }
e = n; e = n;
} }
while (e != first); while (e != first);
return false; return false;
#undef CHECK_RETURN #undef CHECK_RETURN
} }
bool viable::is_viable(pvar v, rational const& val) { bool viable::is_viable(pvar v, rational const& val) {
auto* e = m_units[v]; auto* e = m_units[v];
if (!e) if (!e)
return refine_viable(v, val); return refine_viable(v, val);
@ -499,17 +508,17 @@ namespace polysat {
entry* last = first->prev(); entry* last = first->prev();
if (last->interval.currently_contains(val)) if (last->interval.currently_contains(val))
return false; return false;
for (; e != last; e = e->next()) { for (; e != last; e = e->next()) {
if (e->interval.currently_contains(val)) if (e->interval.currently_contains(val))
return false; return false;
if (val < e->interval.lo_val()) if (val < e->interval.lo_val())
return refine_viable(v, val); return refine_viable(v, val);
} }
return refine_viable(v, val); return refine_viable(v, val);
} }
rational viable::min_viable(pvar v) { rational viable::min_viable(pvar v) {
refined: refined:
rational lo(0); rational lo(0);
auto* e = m_units[v]; auto* e = m_units[v];
@ -521,20 +530,20 @@ namespace polysat {
entry* last = first->prev(); entry* last = first->prev();
if (last->interval.currently_contains(lo)) if (last->interval.currently_contains(lo))
lo = last->interval.hi_val(); lo = last->interval.hi_val();
do { do {
if (!e->interval.currently_contains(lo)) if (!e->interval.currently_contains(lo))
break; break;
lo = e->interval.hi_val(); lo = e->interval.hi_val();
e = e->next(); e = e->next();
} }
while (e != first); while (e != first);
if (!refine_viable(v, lo)) if (!refine_viable(v, lo))
goto refined; goto refined;
SASSERT(is_viable(v, lo)); SASSERT(is_viable(v, lo));
return lo; return lo;
} }
rational viable::max_viable(pvar v) { rational viable::max_viable(pvar v) {
refined: refined:
rational hi = s.var2pdd(v).max_value(); rational hi = s.var2pdd(v).max_value();
auto* e = m_units[v]; auto* e = m_units[v];
@ -549,7 +558,7 @@ namespace polysat {
break; break;
hi = e->interval.lo_val() - 1; hi = e->interval.lo_val() - 1;
e = e->prev(); e = e->prev();
} }
while (e != last); while (e != last);
if (!refine_viable(v, hi)) if (!refine_viable(v, hi))
goto refined; goto refined;
@ -557,7 +566,7 @@ namespace polysat {
return hi; return hi;
} }
dd::find_t viable::find_viable(pvar v, rational& lo) { dd::find_t viable::find_viable(pvar v, rational& lo) {
refined: refined:
lo = 0; lo = 0;
auto* e = m_units[v]; auto* e = m_units[v];
@ -594,7 +603,7 @@ namespace polysat {
break; break;
lo = e->interval.hi_val(); lo = e->interval.hi_val();
e = e->next(); e = e->next();
} }
while (e != first); while (e != first);
if (e->interval.currently_contains(lo)) if (e->interval.currently_contains(lo))
@ -608,7 +617,7 @@ namespace polysat {
break; break;
hi = e->interval.lo_val() - 1; hi = e->interval.lo_val() - 1;
e = e->prev(); e = e->prev();
} }
while (e != last); while (e != last);
if (!refine_viable(v, lo)) if (!refine_viable(v, lo))
goto refined; goto refined;
@ -620,20 +629,6 @@ namespace polysat {
return dd::find_t::multiple; return dd::find_t::multiple;
} }
struct inference_fi : public inference {
viable& v;
pvar var;
inference_fi(viable& v, pvar var) : v(v), var(var) {}
std::ostream& display(std::ostream& out) const override {
return out << "Forbidden intervals for v" << var << ": " << viable::var_pp(v, var);
}
};
bool viable::resolve(pvar v, conflict2& core) {
NOT_IMPLEMENTED_YET();
return false;
}
bool viable::resolve(pvar v, conflict& core) { bool viable::resolve(pvar v, conflict& core) {
if (has_viable(v)) if (has_viable(v))
return false; return false;
@ -652,7 +647,7 @@ namespace polysat {
auto lhs = hi - next_lo; auto lhs = hi - next_lo;
auto rhs = next_hi - next_lo; auto rhs = next_hi - next_lo;
signed_constraint c = s.m_constraints.ult(lhs, rhs); signed_constraint c = s.m_constraints.ult(lhs, rhs);
core.propagate(c); core.insert_eval(c);
#if 0 #if 0
if (n != first) { if (n != first) {
while { while {
@ -666,21 +661,26 @@ namespace polysat {
} }
#endif #endif
} }
for (auto sc : e->side_cond) for (auto sc : e->side_cond)
core.propagate(sc); core.insert_eval(sc);
core.insert(e->src); core.insert(e->src);
e = n; e = n;
} }
while (e != first); while (e != first);
core.logger().log(inf_fi(*this, v));
// TODO: should not be here, too general
/*
for (auto c : core) { for (auto c : core) {
if (c.bvalue(s) == l_false) { if (c.bvalue(s) == l_false) {
core.reset(); core.reset();
core.set(~c); core.set(~c);
core.logger().log("");
break; break;
} }
} }
core.log_inference(inference_fi(*this, v)); */
return true; return true;
} }
@ -694,7 +694,7 @@ namespace polysat {
do { do {
LOG("v" << v << ": " << e->interval << " " << e->side_cond << " " << e->src); LOG("v" << v << ": " << e->interval << " " << e->side_cond << " " << e->src);
e = e->next(); e = e->next();
} }
while (e != first); while (e != first);
} }
@ -712,7 +712,7 @@ namespace polysat {
out << e->coeff << " * v" << v << " "; out << e->coeff << " * v" << v << " ";
out << e->interval << " " << e->side_cond << " " << e->src << "; "; out << e->interval << " " << e->side_cond << " " << e->src << "; ";
e = e->next(); e = e->next();
} }
while (e != first); while (e != first);
return out; return out;
} }
@ -732,7 +732,7 @@ namespace polysat {
/* /*
* Lower bounds are strictly ascending. * Lower bounds are strictly ascending.
* intervals don't contain each-other (since lower bounds are ascending, * intervals don't contain each-other (since lower bounds are ascending,
* it suffices to check containment in one direction). * it suffices to check containment in one direction).
*/ */
bool viable::well_formed(entry* e) { bool viable::well_formed(entry* e) {
@ -742,15 +742,15 @@ namespace polysat {
while (true) { while (true) {
if (e->interval.is_full()) if (e->interval.is_full())
return e->next() == e; return e->next() == e;
if (e->interval.is_currently_empty()) if (e->interval.is_currently_empty())
return false; return false;
auto* n = e->next(); auto* n = e->next();
if (n != e && e->interval.currently_contains(n->interval)) if (n != e && e->interval.currently_contains(n->interval))
return false; return false;
if (n == first) if (n == first)
break; break;
if (e->interval.lo_val() >= n->interval.lo_val()) if (e->interval.lo_val() >= n->interval.lo_val())
return false; return false;
e = n; e = n;

View file

@ -5,7 +5,7 @@ Module Name:
maintain viable domains maintain viable domains
It uses the interval extraction functions from forbidden intervals. It uses the interval extraction functions from forbidden intervals.
An empty viable set corresponds directly to a conflict that does not rely on An empty viable set corresponds directly to a conflict that does not rely on
the non-viable variable. the non-viable variable.
Author: Author:
@ -23,24 +23,24 @@ Author:
#include "util/small_object_allocator.h" #include "util/small_object_allocator.h"
#include "math/polysat/types.h" #include "math/polysat/types.h"
#include "math/polysat/conflict.h" #include "math/polysat/conflict.h"
#include "math/polysat/conflict2.h"
#include "math/polysat/constraint.h" #include "math/polysat/constraint.h"
#include "math/polysat/forbidden_intervals.h" #include "math/polysat/forbidden_intervals.h"
#include "math/polysat/univariate/univariate_solver.h"
namespace polysat { namespace polysat {
class solver; class solver;
class univariate_solver;
class univariate_solver_factory;
class viable { class viable {
friend class test_fi; friend class test_fi;
solver& s; solver& s;
forbidden_intervals m_forbidden_intervals; forbidden_intervals m_forbidden_intervals;
struct entry : public dll_base<entry>, public fi_record {}; struct entry : public dll_base<entry>, public fi_record {};
enum class entry_kind { unit_e, equal_e, diseq_e }; enum class entry_kind { unit_e, equal_e, diseq_e };
ptr_vector<entry> m_alloc; ptr_vector<entry> m_alloc;
ptr_vector<entry> m_units; // set of viable values based on unit multipliers ptr_vector<entry> m_units; // set of viable values based on unit multipliers
ptr_vector<entry> m_equal_lin; // entries that have non-unit multipliers, but are equal ptr_vector<entry> m_equal_lin; // entries that have non-unit multipliers, but are equal
@ -112,7 +112,6 @@ namespace polysat {
* \pre there are no viable values for v * \pre there are no viable values for v
*/ */
bool resolve(pvar v, conflict& core); bool resolve(pvar v, conflict& core);
bool resolve(pvar v, conflict2& core);
/** Log all viable values for the given variable. /** Log all viable values for the given variable.
* (Inefficient, but useful for debugging small instances.) * (Inefficient, but useful for debugging small instances.)
@ -129,11 +128,11 @@ namespace polysat {
bool visited = false; bool visited = false;
unsigned idx = 0; unsigned idx = 0;
public: public:
iterator(entry* curr, bool visited) : iterator(entry* curr, bool visited) :
curr(curr), visited(visited || !curr) {} curr(curr), visited(visited || !curr) {}
iterator& operator++() { iterator& operator++() {
if (idx < curr->side_cond.size()) if (idx < curr->side_cond.size())
++idx; ++idx;
else { else {
idx = 0; idx = 0;
@ -143,7 +142,7 @@ namespace polysat {
return *this; return *this;
} }
signed_constraint& operator*() { signed_constraint& operator*() {
return idx < curr->side_cond.size() ? curr->side_cond[idx] : curr->src; return idx < curr->side_cond.size() ? curr->side_cond[idx] : curr->src;
} }
@ -165,8 +164,8 @@ namespace polysat {
iterator end() const { return iterator(v.m_units[var], true); } iterator end() const { return iterator(v.m_units[var], true); }
}; };
constraints get_constraints(pvar v) const { constraints get_constraints(pvar v) const {
return constraints(*this, v); return constraints(*this, v);
} }
class int_iterator { class int_iterator {
@ -181,8 +180,8 @@ namespace polysat {
return *this; return *this;
} }
eval_interval const& operator*() { eval_interval const& operator*() {
return curr->interval; return curr->interval;
} }
bool operator==(int_iterator const& other) const { bool operator==(int_iterator const& other) const {
@ -210,10 +209,10 @@ namespace polysat {
struct var_pp { struct var_pp {
viable const& v; viable const& v;
pvar var; pvar var;
var_pp(viable const& v, pvar var) : v(v), var(var) {} var_pp(viable const& v, pvar var) : v(v), var(var) {}
}; };
}; };
inline std::ostream& operator<<(std::ostream& out, viable const& v) { inline std::ostream& operator<<(std::ostream& out, viable const& v) {