mirror of
https://github.com/Z3Prover/z3
synced 2025-04-24 09:35:32 +00:00
Merge branch 'polysat' of https://github.com/Z3Prover/z3 into polysat
This commit is contained in:
commit
47cb83f578
34 changed files with 1540 additions and 888 deletions
|
@ -1629,26 +1629,26 @@ namespace dd {
|
|||
std::ostream& pdd_manager::display(std::ostream& out, pdd const& b) {
|
||||
auto mons = to_monomials(b);
|
||||
bool first = true;
|
||||
for (auto& m : mons) {
|
||||
for (auto& [a, vs] : mons) {
|
||||
if (!first)
|
||||
out << " ";
|
||||
if (m.first.is_neg())
|
||||
if (a.is_neg())
|
||||
out << "- ";
|
||||
else if (!first)
|
||||
out << "+ ";
|
||||
first = false;
|
||||
rational c = abs(m.first);
|
||||
m.second.reverse();
|
||||
if (!c.is_one() || m.second.empty()) {
|
||||
rational c = abs(a);
|
||||
vs.reverse();
|
||||
if (!c.is_one() || vs.empty()) {
|
||||
if (m_semantics == mod2N_e)
|
||||
out << normalize(c);
|
||||
out << val_pp(*this, c, !vs.empty());
|
||||
else
|
||||
out << c;
|
||||
if (!m.second.empty()) out << "*";
|
||||
if (!vs.empty()) out << "*";
|
||||
}
|
||||
unsigned v_prev = UINT_MAX;
|
||||
unsigned pow = 0;
|
||||
for (unsigned v : m.second) {
|
||||
for (unsigned v : vs) {
|
||||
if (v == v_prev) {
|
||||
pow++;
|
||||
continue;
|
||||
|
@ -1672,6 +1672,23 @@ namespace dd {
|
|||
return out;
|
||||
}
|
||||
|
||||
std::ostream& val_pp::display(std::ostream& out) const {
|
||||
if (m.get_semantics() != pdd_manager::mod2N_e)
|
||||
return out << val;
|
||||
unsigned pow;
|
||||
if (val.is_power_of_two(pow) && pow > 10)
|
||||
return out << "2^" << pow;
|
||||
else if (val < m.max_value() && (val + 1).is_power_of_two(pow) && pow > 10) {
|
||||
if (require_parens)
|
||||
out << "(";
|
||||
out << "2^" << pow << "-1";
|
||||
if (require_parens)
|
||||
out << ")";
|
||||
return out;
|
||||
} else
|
||||
return out << m.normalize(val);
|
||||
}
|
||||
|
||||
bool pdd_manager::well_formed() {
|
||||
bool ok = true;
|
||||
for (unsigned n : m_free_nodes) {
|
||||
|
|
|
@ -315,6 +315,11 @@ namespace dd {
|
|||
pdd_manager(unsigned num_vars, semantics s = free_e, unsigned power_of_2 = 0);
|
||||
~pdd_manager();
|
||||
|
||||
pdd_manager(pdd_manager const&) = delete;
|
||||
pdd_manager(pdd_manager&&) = delete;
|
||||
pdd_manager& operator=(pdd_manager const&) = delete;
|
||||
pdd_manager& operator=(pdd_manager&&) = delete;
|
||||
|
||||
semantics get_semantics() const { return m_semantics; }
|
||||
|
||||
void reset(unsigned_vector const& level2var);
|
||||
|
@ -416,8 +421,9 @@ namespace dd {
|
|||
bool is_linear() const { return m.is_linear(root); }
|
||||
bool is_var() const { return m.is_var(root); }
|
||||
bool is_max() const { return m.is_max(root); }
|
||||
/** Polynomial is of the form a * x + b for numerals a, b. */
|
||||
/** Polynomial is of the form a * x + b for some numerals a, b. */
|
||||
bool is_unilinear() const { return !is_val() && lo().is_val() && hi().is_val(); }
|
||||
/** Polynomial is of the form a * x for some numeral a. */
|
||||
bool is_unary() const { return !is_val() && lo().is_zero() && hi().is_val(); }
|
||||
bool is_offset() const { return !is_val() && lo().is_val() && hi().is_one(); }
|
||||
bool is_binary() const { return m.is_binary(root); }
|
||||
|
@ -544,6 +550,16 @@ namespace dd {
|
|||
bool operator!=(pdd_iterator const& other) const { return m_nodes != other.m_nodes; }
|
||||
};
|
||||
|
||||
class val_pp {
|
||||
pdd_manager const& m;
|
||||
rational const& val;
|
||||
bool require_parens;
|
||||
public:
|
||||
val_pp(pdd_manager const& m, rational const& val, bool require_parens): m(m), val(val), require_parens(require_parens) {}
|
||||
std::ostream& display(std::ostream& out) const;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, val_pp const& v) { return v.display(out); }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,11 @@ namespace polysat {
|
|||
return p.subst_val(m_subst);
|
||||
}
|
||||
|
||||
bool substitution::contains(pvar var) const {
|
||||
rational out_value;
|
||||
return value(var, out_value);
|
||||
}
|
||||
|
||||
bool substitution::value(pvar var, rational& out_value) const {
|
||||
return m_subst.subst_get(var, out_value);
|
||||
}
|
||||
|
@ -49,6 +54,14 @@ namespace polysat {
|
|||
return a;
|
||||
}
|
||||
|
||||
bool assignment::contains(pvar var) const {
|
||||
return subst(s().size(var)).contains(var);
|
||||
}
|
||||
|
||||
bool assignment::value(pvar var, rational& out_value) const {
|
||||
return subst(s().size(var)).value(var, out_value);
|
||||
}
|
||||
|
||||
substitution& assignment::subst(unsigned sz) {
|
||||
return const_cast<substitution&>(std::as_const(*this).subst(sz));
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ namespace polysat {
|
|||
substitution add(pvar var, rational const& value) const;
|
||||
pdd apply_to(pdd const& p) const;
|
||||
|
||||
bool contains(pvar var) const;
|
||||
bool value(pvar var, rational& out_value) const;
|
||||
|
||||
bool empty() const { return m_subst.is_one(); }
|
||||
|
@ -96,6 +97,9 @@ namespace polysat {
|
|||
|
||||
pdd apply_to(pdd const& p) const;
|
||||
|
||||
bool contains(pvar var) const;
|
||||
bool value(pvar var, rational& out_value) const;
|
||||
rational value(pvar var) const { rational val; VERIFY(value(var, val)); return val; }
|
||||
bool empty() const { return pairs().empty(); }
|
||||
substitution const& subst(unsigned sz) const;
|
||||
vector<assignment_item_t> const& pairs() const { return m_pairs; }
|
||||
|
|
|
@ -20,10 +20,6 @@ namespace polysat {
|
|||
class signed_constraint;
|
||||
class simplify_clause;
|
||||
|
||||
class clause;
|
||||
using clause_ref = ref<clause>;
|
||||
using clause_ref_vector = sref_vector<clause>;
|
||||
|
||||
/// Disjunction of constraints represented by boolean literals
|
||||
// NB code review:
|
||||
// right, ref-count is unlikely the right mechanism.
|
||||
|
@ -31,11 +27,14 @@ namespace polysat {
|
|||
// and deleted when they exist the arena.
|
||||
//
|
||||
class clause {
|
||||
public:
|
||||
static inline const bool redundant_default = true;
|
||||
private:
|
||||
friend class constraint_manager;
|
||||
friend class simplify_clause;
|
||||
|
||||
unsigned m_ref_count = 0; // TODO: remove refcount once we confirm it's not needed anymore
|
||||
bool m_redundant = true;
|
||||
bool m_redundant = redundant_default;
|
||||
sat::literal_vector m_literals;
|
||||
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace polysat {
|
|||
void clause_builder::reset() {
|
||||
m_literals.reset();
|
||||
m_is_tautology = false;
|
||||
m_redundant = clause::redundant_default;
|
||||
SASSERT(empty());
|
||||
}
|
||||
|
||||
|
@ -49,6 +50,9 @@ namespace polysat {
|
|||
}
|
||||
m_literals.shrink(j);
|
||||
clause_ref cl = clause::from_literals(std::move(m_literals));
|
||||
SASSERT(cl);
|
||||
cl->set_redundant(m_redundant);
|
||||
m_redundant = clause::redundant_default;
|
||||
SASSERT(empty());
|
||||
return cl;
|
||||
}
|
||||
|
@ -58,6 +62,8 @@ namespace polysat {
|
|||
insert(m_solver->lit2cnstr(lit));
|
||||
}
|
||||
|
||||
// TODO: in the final version, we may also skip assumptions (and even literals propagated at the base level),
|
||||
// provided we correctly track external dependencies/level for the clause.
|
||||
void clause_builder::insert(signed_constraint c) {
|
||||
SASSERT(c);
|
||||
if (c.is_always_false()) // filter out trivial constraints such as "4 < 2"
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace polysat {
|
|||
/// True iff clause contains a literal that is always true
|
||||
/// (only this specific case of tautology is covered by this flag)
|
||||
bool m_is_tautology = false;
|
||||
bool m_redundant = clause::redundant_default;
|
||||
|
||||
public:
|
||||
clause_builder(solver& s);
|
||||
|
@ -36,9 +37,11 @@ namespace polysat {
|
|||
clause_builder& operator=(clause_builder const& s) = delete;
|
||||
clause_builder& operator=(clause_builder&& s) = default;
|
||||
|
||||
bool empty() const { return m_literals.empty() && !m_is_tautology; }
|
||||
bool empty() const { return m_literals.empty() && !m_is_tautology && m_redundant == clause::redundant_default; }
|
||||
void reset();
|
||||
|
||||
void set_redundant(bool r) { m_redundant = r; }
|
||||
|
||||
/// Build the clause. This will reset the clause builder so it can be reused.
|
||||
/// Returns nullptr if the clause is a tautology and should not be added to the solver.
|
||||
clause_ref build();
|
||||
|
|
|
@ -57,23 +57,28 @@ TODO:
|
|||
namespace polysat {
|
||||
|
||||
class conflict_resolver {
|
||||
inf_saturate m_saturate;
|
||||
saturation m_saturation;
|
||||
ex_polynomial_superposition m_poly_sup;
|
||||
free_variable_elimination m_free_variable_elimination;
|
||||
|
||||
public:
|
||||
conflict_resolver(solver& s)
|
||||
: m_saturate(s)
|
||||
: m_saturation(s)
|
||||
, m_poly_sup(s)
|
||||
, m_free_variable_elimination(s)
|
||||
{}
|
||||
|
||||
bool try_resolve_value(pvar v, conflict& core) {
|
||||
if (m_poly_sup.perform(v, core))
|
||||
return true;
|
||||
// if (m_saturate.perform(v, core))
|
||||
// return true;
|
||||
return false;
|
||||
//
|
||||
// NSB review: the plugins need not be mutually exclusive
|
||||
// Shouldn't saturation and superposition be allowed independently?
|
||||
// If they create propagations or conflict lemmas we select the
|
||||
// tightest propagation as part of backjumping.
|
||||
//
|
||||
void infer_lemmas_for_value(pvar v, conflict& core) {
|
||||
if (m_poly_sup.perform(v, core))
|
||||
return;
|
||||
if (m_saturation.perform(v, core))
|
||||
return;
|
||||
}
|
||||
|
||||
// Analyse current conflict core to extract additional lemmas
|
||||
|
@ -252,6 +257,8 @@ namespace polysat {
|
|||
logger().begin_conflict(header_with_var("forbidden interval lemma for v", v));
|
||||
VERIFY(s.m_viable.resolve(v, *this));
|
||||
}
|
||||
|
||||
// NSB TODO - disabled: revert_decision(v);
|
||||
SASSERT(!empty());
|
||||
}
|
||||
|
||||
|
@ -298,26 +305,27 @@ namespace polysat {
|
|||
m_vars.insert(v);
|
||||
}
|
||||
|
||||
void conflict::add_lemma(std::initializer_list<signed_constraint> cs) {
|
||||
add_lemma(std::data(cs), cs.size());
|
||||
void conflict::add_lemma(char const* name, std::initializer_list<signed_constraint> cs) {
|
||||
add_lemma(name, std::data(cs), cs.size());
|
||||
}
|
||||
|
||||
void conflict::add_lemma(signed_constraint const* cs, size_t cs_len) {
|
||||
void conflict::add_lemma(char const* name, signed_constraint const* cs, size_t cs_len) {
|
||||
clause_builder cb(s);
|
||||
for (size_t i = 0; i < cs_len; ++i)
|
||||
cb.insert_eval(cs[i]);
|
||||
add_lemma(cb.build());
|
||||
add_lemma(name, cb.build());
|
||||
}
|
||||
|
||||
void conflict::add_lemma(clause_ref lemma) {
|
||||
void conflict::add_lemma(char const* name, clause_ref lemma) {
|
||||
LOG_H3("Lemma " << (name ? name : "<unknown>") << ": " << show_deref(lemma));
|
||||
SASSERT(lemma);
|
||||
lemma->set_redundant(true);
|
||||
LOG_H3("Lemma: " << *lemma);
|
||||
for (sat::literal lit : *lemma) {
|
||||
LOG(lit_pp(s, lit));
|
||||
SASSERT(s.m_bvars.value(lit) != l_true);
|
||||
}
|
||||
m_lemmas.push_back(std::move(lemma));
|
||||
// TODO: pass to inference_logger (with name)
|
||||
}
|
||||
|
||||
void conflict::remove(signed_constraint c) {
|
||||
|
@ -391,7 +399,11 @@ namespace polysat {
|
|||
logger().log(inf_resolve_with_assignment(s, lit, c));
|
||||
}
|
||||
|
||||
bool conflict::resolve_value(pvar v) {
|
||||
void conflict::revert_decision(pvar v) {
|
||||
m_resolver->infer_lemmas_for_value(v, *this);
|
||||
}
|
||||
|
||||
void conflict::resolve_value(pvar v) {
|
||||
SASSERT(contains_pvar(v));
|
||||
SASSERT(s.m_justification[v].is_propagation());
|
||||
|
||||
|
@ -405,11 +417,8 @@ namespace polysat {
|
|||
insert(s.eq(i.hi(), i.hi_val()));
|
||||
}
|
||||
logger().log(inf_resolve_value(s, v));
|
||||
|
||||
if (m_resolver->try_resolve_value(v, *this))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
revert_decision(v);
|
||||
}
|
||||
|
||||
clause_ref conflict::build_lemma() {
|
||||
|
|
|
@ -160,9 +160,9 @@ namespace polysat {
|
|||
void insert_eval(signed_constraint c);
|
||||
|
||||
/** Add a side lemma to the conflict; to be learned in addition to the main lemma after conflict resolution finishes. */
|
||||
void add_lemma(std::initializer_list<signed_constraint> cs);
|
||||
void add_lemma(signed_constraint const* cs, size_t cs_len);
|
||||
void add_lemma(clause_ref lemma);
|
||||
void add_lemma(char const* name, std::initializer_list<signed_constraint> cs);
|
||||
void add_lemma(char const* name, signed_constraint const* cs, size_t cs_len);
|
||||
void add_lemma(char const* name, clause_ref lemma);
|
||||
|
||||
/** Remove c from core */
|
||||
void remove(signed_constraint c);
|
||||
|
@ -181,7 +181,10 @@ namespace polysat {
|
|||
void resolve_with_assignment(sat::literal lit);
|
||||
|
||||
/** Perform resolution with "v = value <- ..." */
|
||||
bool resolve_value(pvar v);
|
||||
void resolve_value(pvar v);
|
||||
|
||||
/** Revert decision, add auxiliary lemmas for the decision variable **/
|
||||
void revert_decision(pvar v);
|
||||
|
||||
/** Convert the core into a lemma to be learned. */
|
||||
clause_ref build_lemma();
|
||||
|
|
|
@ -37,8 +37,13 @@ namespace polysat {
|
|||
return m_constraint->to_ule().lhs();
|
||||
}
|
||||
|
||||
signed_constraint inequality::as_signed_constraint() const {
|
||||
return signed_constraint(const_cast<constraint*>(src), !is_strict);
|
||||
inequality inequality::from_ule(signed_constraint src)
|
||||
{
|
||||
ule_constraint& c = src->to_ule();
|
||||
if (src.is_positive())
|
||||
return inequality(c.lhs(), c.rhs(), src);
|
||||
else
|
||||
return inequality(c.rhs(), c.lhs(), src);
|
||||
}
|
||||
|
||||
ule_constraint& constraint::to_ule() {
|
||||
|
|
|
@ -34,19 +34,6 @@ namespace polysat {
|
|||
using constraints = ptr_vector<constraint>;
|
||||
using signed_constraints = vector<signed_constraint>;
|
||||
|
||||
/// Normalized inequality:
|
||||
/// lhs <= rhs, if !is_strict
|
||||
/// lhs < rhs, otherwise
|
||||
struct inequality {
|
||||
pdd lhs;
|
||||
pdd rhs;
|
||||
bool is_strict;
|
||||
constraint const* src; // TODO: should be signed_constraint now
|
||||
inequality(pdd const & lhs, pdd const & rhs, bool is_strict, constraint const* src):
|
||||
lhs(lhs), rhs(rhs), is_strict(is_strict), src(src) {}
|
||||
signed_constraint as_signed_constraint() const;
|
||||
};
|
||||
|
||||
class constraint {
|
||||
friend class constraint_manager;
|
||||
friend class signed_constraint;
|
||||
|
@ -97,7 +84,12 @@ namespace polysat {
|
|||
bool is_currently_false(solver const& s, bool is_positive) const { return is_currently_true(s, !is_positive); }
|
||||
|
||||
virtual void narrow(solver& s, bool is_positive, bool first) = 0;
|
||||
virtual inequality as_inequality(bool is_positive) const = 0;
|
||||
/**
|
||||
* If possible, produce a lemma that contradicts the given assignment.
|
||||
* This method should not modify the solver's search state.
|
||||
* TODO: don't pass the solver, but an interface that only allows creation of constraints
|
||||
*/
|
||||
virtual clause_ref produce_lemma(solver& s, assignment const& a, bool is_positive) { return {}; }
|
||||
|
||||
ule_constraint& to_ule();
|
||||
ule_constraint const& to_ule() const;
|
||||
|
@ -167,7 +159,7 @@ namespace polysat {
|
|||
bool is_currently_true(solver const& s) const { return get()->is_currently_true(s, is_positive()); }
|
||||
lbool bvalue(solver& s) const;
|
||||
void narrow(solver& s, bool first) { get()->narrow(s, is_positive(), first); }
|
||||
inequality as_inequality() const { return get()->as_inequality(is_positive()); }
|
||||
clause_ref produce_lemma(solver& s, assignment const& a) { return get()->produce_lemma(s, a, is_positive()); }
|
||||
|
||||
void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep) const { get()->add_to_univariate_solver(s, us, dep, is_positive()); }
|
||||
|
||||
|
@ -196,7 +188,8 @@ namespace polysat {
|
|||
return combine_hash(get_ptr_hash(get()), bool_hash()(is_positive()));
|
||||
}
|
||||
bool operator==(signed_constraint const& other) const {
|
||||
return get() == other.get() && is_positive() == other.is_positive();
|
||||
SASSERT_EQ(blit() == other.blit(), get() == other.get() && is_positive() == other.is_positive());
|
||||
return blit() == other.blit();
|
||||
}
|
||||
bool operator!=(signed_constraint const& other) const { return !operator==(other); }
|
||||
|
||||
|
@ -212,6 +205,25 @@ namespace polysat {
|
|||
return c.display(out);
|
||||
}
|
||||
|
||||
/// Normalized inequality:
|
||||
/// lhs <= rhs, if !is_strict
|
||||
/// lhs < rhs, otherwise
|
||||
class inequality {
|
||||
pdd m_lhs;
|
||||
pdd m_rhs;
|
||||
signed_constraint m_src;
|
||||
|
||||
inequality(pdd lhs, pdd rhs, signed_constraint src):
|
||||
m_lhs(std::move(lhs)), m_rhs(std::move(rhs)), m_src(std::move(src)) {}
|
||||
|
||||
public:
|
||||
static inequality from_ule(signed_constraint src);
|
||||
pdd const& lhs() const { return m_lhs; }
|
||||
pdd const& rhs() const { return m_rhs; }
|
||||
bool is_strict() const { return m_src.is_negative(); }
|
||||
signed_constraint as_signed_constraint() const { return m_src; }
|
||||
};
|
||||
|
||||
class constraint_pp {
|
||||
constraint const* c;
|
||||
lbool status;
|
||||
|
|
|
@ -275,6 +275,7 @@ namespace polysat {
|
|||
return ult(a + shift, b + shift);
|
||||
}
|
||||
|
||||
/** unsigned quotient/remainder */
|
||||
std::pair<pdd, pdd> constraint_manager::quot_rem(pdd const& a, pdd const& b) {
|
||||
auto& m = a.manager();
|
||||
unsigned sz = m.power_of_2();
|
||||
|
@ -282,6 +283,9 @@ namespace polysat {
|
|||
// By SMT-LIB specification, b = 0 ==> q = -1, r = a.
|
||||
return {m.mk_val(m.max_value()), a};
|
||||
}
|
||||
if (b.is_one()) {
|
||||
return {a, m.zero()};
|
||||
}
|
||||
if (a.is_val() && b.is_val()) {
|
||||
rational const av = a.val();
|
||||
rational const bv = b.val();
|
||||
|
@ -296,7 +300,14 @@ namespace polysat {
|
|||
SASSERT(r.val() < b.val());
|
||||
return {std::move(q), std::move(r)};
|
||||
}
|
||||
constraint_dedup::quot_rem_args args({ a, b });
|
||||
#if 0
|
||||
unsigned pow;
|
||||
if (b.is_val() && b.val().is_power_of_two(pow)) {
|
||||
// TODO: q = a >> b.val()
|
||||
// r = 0 \circ a[pow:] ???
|
||||
}
|
||||
#endif
|
||||
constraint_dedup::quot_rem_args args({a, b});
|
||||
auto it = m_dedup.quot_rem_expr.find_iterator(args);
|
||||
if (it != m_dedup.quot_rem_expr.end())
|
||||
return { m.mk_var(it->m_value.first), m.mk_var(it->m_value.second) };
|
||||
|
@ -311,17 +322,17 @@ namespace polysat {
|
|||
// addition does not overflow in (b*q) + r; for now expressed as: r <= bq+r
|
||||
// b ≠ 0 ==> r < b
|
||||
// b = 0 ==> q = -1
|
||||
s.add_eq(a, b * q + r);
|
||||
s.add_umul_noovfl(b, q);
|
||||
s.add_clause(eq(b * q + r - a), false);
|
||||
s.add_clause(~umul_ovfl(b, q), false);
|
||||
// r <= b*q+r
|
||||
// { apply equivalence: p <= q <=> q-p <= -p-1 }
|
||||
// b*q <= -r-1
|
||||
s.add_ule(b*q, -r-1);
|
||||
s.add_clause(ule(b*q, -r-1), false);
|
||||
#if 0
|
||||
// b*q <= b*q+r
|
||||
// { apply equivalence: p <= q <=> q-p <= -p-1 }
|
||||
// r <= - b*q - 1
|
||||
s.add_ule(r, -b*q-1); // redundant, but may help propagation
|
||||
s.add_clause(ule(r, -b*q-1), false); // redundant, but may help propagation
|
||||
#endif
|
||||
|
||||
auto c_eq = eq(b);
|
||||
|
|
|
@ -127,6 +127,8 @@ namespace polysat {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
static char* _last_function = "";
|
||||
|
||||
bool forbidden_intervals::get_interval_ule(signed_constraint const& c, pvar v, fi_record& fi) {
|
||||
|
||||
|
@ -135,6 +137,26 @@ namespace polysat {
|
|||
fi.coeff = 1;
|
||||
fi.src = c;
|
||||
|
||||
struct show {
|
||||
forbidden_intervals& f;
|
||||
signed_constraint const& c;
|
||||
pvar v;
|
||||
fi_record& fi;
|
||||
backtrack& _backtrack;
|
||||
show(forbidden_intervals& f,
|
||||
signed_constraint const& c,
|
||||
pvar v,
|
||||
fi_record& fi,
|
||||
backtrack& _backtrack):f(f), c(c), v(v), fi(fi), _backtrack(_backtrack) {}
|
||||
~show() {
|
||||
if (!_backtrack.released)
|
||||
return;
|
||||
IF_VERBOSE(0, verbose_stream() << _last_function << " " << v << " " << c << " " << fi.interval << " " << fi.side_cond << "\n");
|
||||
}
|
||||
};
|
||||
// uncomment to trace intervals
|
||||
// show _show(*this, c, v, fi, _backtrack);
|
||||
|
||||
// eval(lhs) = a1*v + eval(e1) = a1*v + b1
|
||||
// eval(rhs) = a2*v + eval(e2) = a2*v + b2
|
||||
// We keep the e1, e2 around in case we need side conditions such as e1=b1, e2=b2.
|
||||
|
@ -261,12 +283,13 @@ namespace polysat {
|
|||
|
||||
/**
|
||||
* Match e1 + t <= e2, with t = a1*y
|
||||
* condition for empty/full: e2 == -1
|
||||
* condition for empty/full: e2 == -1
|
||||
*/
|
||||
bool forbidden_intervals::match_linear1(signed_constraint const& c,
|
||||
rational const & a1, pdd const& b1, pdd const& e1,
|
||||
rational const & a2, pdd const& b2, pdd const& e2,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (a2.is_zero() && !a1.is_zero()) {
|
||||
SASSERT(!a1.is_zero());
|
||||
bool is_trivial = (b2 + 1).is_zero();
|
||||
|
@ -291,6 +314,7 @@ namespace polysat {
|
|||
rational const & a1, pdd const& b1, pdd const& e1,
|
||||
rational const & a2, pdd const& b2, pdd const& e2,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (a1.is_zero() && !a2.is_zero()) {
|
||||
SASSERT(!a2.is_zero());
|
||||
bool is_trivial = b1.is_zero();
|
||||
|
@ -315,6 +339,7 @@ namespace polysat {
|
|||
rational const & a1, pdd const& b1, pdd const& e1,
|
||||
rational const & a2, pdd const& b2, pdd const& e2,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (a1 == a2 && !a1.is_zero()) {
|
||||
bool is_trivial = b1.val() == b2.val();
|
||||
push_eq(is_trivial, e1 - e2, fi.side_cond);
|
||||
|
@ -337,6 +362,7 @@ namespace polysat {
|
|||
rational const & a1, pdd const& b1, pdd const& e1,
|
||||
rational const & a2, pdd const& b2, pdd const& e2,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (a1 != a2 && !a1.is_zero() && !a2.is_zero()) {
|
||||
// NOTE: we don't have an interval here in the same sense as in the other cases.
|
||||
// We use the interval to smuggle out the values a1,b1,a2,b2 without adding additional fields.
|
||||
|
@ -368,6 +394,7 @@ namespace polysat {
|
|||
rational const & a1, pdd const& b1, pdd const& e1,
|
||||
rational const & a2, pdd const& b2, pdd const& e2,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (c.is_positive() && a1.is_odd() && b1.is_zero() && a2.is_zero() && b2.is_zero()) {
|
||||
auto& m = e1.manager();
|
||||
rational lo_val(1);
|
||||
|
@ -397,6 +424,7 @@ namespace polysat {
|
|||
rational const & a1, pdd const& b1, pdd const& e1,
|
||||
rational const & a2, pdd const& b2, pdd const& e2,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (c.is_negative() && a1.is_odd() && a2.is_zero() && b2.is_zero()) {
|
||||
// a*v + b > 0
|
||||
// <=> a*v + b != 0
|
||||
|
@ -435,6 +463,7 @@ namespace polysat {
|
|||
signed_constraint const& c,
|
||||
rational const & a1, pdd const& b1, pdd const& e1,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (a1.is_odd() && b1.is_zero() && c.is_negative()) {
|
||||
auto& m = e1.manager();
|
||||
rational lo_val(0);
|
||||
|
@ -475,6 +504,7 @@ namespace polysat {
|
|||
signed_constraint const& c,
|
||||
rational const & a2, pdd const& b2, pdd const& e2,
|
||||
fi_record& fi) {
|
||||
_last_function = __func__;
|
||||
if (a2.is_one() && b2.is_val() && c.is_negative()) {
|
||||
auto& m = e2.manager();
|
||||
rational const& mod_value = m.two_to_N();
|
||||
|
|
|
@ -24,12 +24,22 @@ Other:
|
|||
- code diverges on coding conventions.
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO: add "conditional" logs, i.e., the messages are held back and only printed when a non-conditional message is logged.
|
||||
Purpose: reduce noise, e.g., when printing prerequisites for transformations that do not always apply.
|
||||
*/
|
||||
|
||||
char const* color_red() { return "\x1B[31m"; }
|
||||
char const* color_yellow() { return "\x1B[33m"; }
|
||||
char const* color_blue() { return "\x1B[34m"; }
|
||||
char const* color_reset() { return "\x1B[0m"; }
|
||||
|
||||
#if POLYSAT_LOGGING_ENABLED
|
||||
|
||||
std::atomic<bool> g_log_enabled(true);
|
||||
|
||||
void set_log_enabled(bool log_enabled) {
|
||||
g_log_enabled = log_enabled;
|
||||
g_log_enabled = log_enabled;
|
||||
}
|
||||
|
||||
bool get_log_enabled() {
|
||||
|
@ -62,11 +72,6 @@ bool polysat_should_log(LogLevel msg_level, std::string fn, std::string pretty_f
|
|||
return msg_level <= max_log_level;
|
||||
}
|
||||
|
||||
char const* color_red() { return "\x1B[31m"; }
|
||||
char const* color_yellow() { return "\x1B[33m"; }
|
||||
char const* color_blue() { return "\x1B[34m"; }
|
||||
char const* color_reset() { return "\x1B[0m"; }
|
||||
|
||||
static char const* level_color(LogLevel msg_level) {
|
||||
switch (msg_level) {
|
||||
case LogLevel::Heading1: return color_red();
|
||||
|
|
|
@ -29,6 +29,18 @@ char const* color_reset();
|
|||
void set_log_enabled(bool log_enabled);
|
||||
bool get_log_enabled();
|
||||
|
||||
class scoped_set_log_enabled {
|
||||
bool m_prev;
|
||||
public:
|
||||
scoped_set_log_enabled(bool enabled) {
|
||||
m_prev = get_log_enabled();
|
||||
set_log_enabled(enabled);
|
||||
}
|
||||
~scoped_set_log_enabled() {
|
||||
set_log_enabled(m_prev);
|
||||
}
|
||||
};
|
||||
|
||||
class polysat_log_indent
|
||||
{
|
||||
int m_amount;
|
||||
|
@ -96,6 +108,7 @@ polysat_log(LogLevel msg_level, std::string fn, std::string pretty_fn);
|
|||
|
||||
inline void set_log_enabled(bool) {}
|
||||
inline bool get_log_enabled() { return false; }
|
||||
class scoped_set_log_enabled {};
|
||||
|
||||
#define LOG_(lvl, x) \
|
||||
do { \
|
||||
|
|
|
@ -37,18 +37,12 @@ namespace polysat {
|
|||
|
||||
switch (c) {
|
||||
case code::and_op:
|
||||
case code::or_op:
|
||||
case code::xor_op:
|
||||
if (p.index() > q.index())
|
||||
std::swap(m_p, m_q);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// The following can currently not be used as standalone constraints
|
||||
SASSERT(c != code::or_op);
|
||||
SASSERT(c != code::xor_op);
|
||||
SASSERT(c != code::not_op);
|
||||
}
|
||||
|
||||
lbool op_constraint::eval() const {
|
||||
|
@ -74,9 +68,9 @@ namespace polysat {
|
|||
|
||||
std::ostream& op_constraint::display(std::ostream& out, lbool status) const {
|
||||
switch (status) {
|
||||
case l_true: return display(out);
|
||||
case l_false: return display(out << "~");
|
||||
default: return display(out << "?");
|
||||
case l_true: return display(out, "==");
|
||||
case l_false: return display(out, "!=");
|
||||
default: return display(out, "?=");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,10 +84,6 @@ namespace polysat {
|
|||
return out << "<<";
|
||||
case op_constraint::code::and_op:
|
||||
return out << "&";
|
||||
case op_constraint::code::or_op:
|
||||
return out << "|";
|
||||
case op_constraint::code::xor_op:
|
||||
return out << "^";
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return out;
|
||||
|
@ -102,41 +92,78 @@ namespace polysat {
|
|||
}
|
||||
|
||||
std::ostream& op_constraint::display(std::ostream& out) const {
|
||||
if (m_op == code::not_op)
|
||||
return out << r() << " == ~" << p();
|
||||
else
|
||||
return out << r() << " == " << p() << " " << m_op << " " << q();
|
||||
return display(out, l_true);
|
||||
}
|
||||
|
||||
std::ostream& op_constraint::display(std::ostream& out, char const* eq) const {
|
||||
return out << r() << " " << eq << " " << p() << " " << m_op << " " << q();
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate consequences or detect conflicts based on partial assignments.
|
||||
*
|
||||
* We can assume that op_constraint is only asserted positive.
|
||||
*/
|
||||
* Propagate consequences or detect conflicts based on partial assignments.
|
||||
*
|
||||
* We can assume that op_constraint is only asserted positive.
|
||||
*/
|
||||
void op_constraint::narrow(solver& s, bool is_positive, bool first) {
|
||||
SASSERT(is_positive);
|
||||
|
||||
if (is_currently_true(s, is_positive))
|
||||
return;
|
||||
|
||||
switch (m_op) {
|
||||
case code::lshr_op:
|
||||
narrow_lshr(s);
|
||||
break;
|
||||
case code::shl_op:
|
||||
narrow_shl(s);
|
||||
break;
|
||||
case code::and_op:
|
||||
narrow_and(s);
|
||||
break;
|
||||
default:
|
||||
NOT_IMPLEMENTED_YET();
|
||||
break;
|
||||
}
|
||||
if (first)
|
||||
activate(s);
|
||||
|
||||
if (clause_ref lemma = produce_lemma(s, s.assignment()))
|
||||
s.add_clause(*lemma);
|
||||
|
||||
if (!s.is_conflict() && is_currently_false(s, is_positive))
|
||||
s.set_conflict(signed_constraint(this, is_positive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce lemmas that contradict the given assignment.
|
||||
*
|
||||
* We can assume that op_constraint is only asserted positive.
|
||||
*/
|
||||
clause_ref op_constraint::produce_lemma(solver& s, assignment const& a, bool is_positive) {
|
||||
SASSERT(is_positive);
|
||||
|
||||
if (is_currently_true(a, is_positive))
|
||||
return {};
|
||||
|
||||
return produce_lemma(s, a);
|
||||
}
|
||||
|
||||
clause_ref op_constraint::produce_lemma(solver& s, assignment const& a) {
|
||||
switch (m_op) {
|
||||
case code::lshr_op:
|
||||
return lemma_lshr(s, a);
|
||||
case code::shl_op:
|
||||
return lemma_shl(s, a);
|
||||
case code::and_op:
|
||||
return lemma_and(s, a);
|
||||
default:
|
||||
NOT_IMPLEMENTED_YET();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void op_constraint::activate(solver& s) {
|
||||
switch (m_op) {
|
||||
case code::lshr_op:
|
||||
break;
|
||||
case code::shl_op:
|
||||
// TODO: if shift amount is constant p << k, then add p << k == p*2^k
|
||||
break;
|
||||
case code::and_op:
|
||||
// handle masking of high order bits
|
||||
activate_and(s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned op_constraint::hash() const {
|
||||
return mk_mix(p().hash(), q().hash(), r().hash());
|
||||
}
|
||||
|
@ -169,47 +196,46 @@ namespace polysat {
|
|||
* s.m_viable.max_viable()
|
||||
* when r, q are variables.
|
||||
*/
|
||||
void op_constraint::narrow_lshr(solver& s) {
|
||||
auto const pv = s.subst(p());
|
||||
auto const qv = s.subst(q());
|
||||
auto const rv = s.subst(r());
|
||||
clause_ref op_constraint::lemma_lshr(solver& s, assignment const& a) {
|
||||
auto const pv = a.apply_to(p());
|
||||
auto const qv = a.apply_to(q());
|
||||
auto const rv = a.apply_to(r());
|
||||
unsigned const K = p().manager().power_of_2();
|
||||
|
||||
signed_constraint const lshr(this, true);
|
||||
|
||||
if (pv.is_val() && rv.is_val() && rv.val() > pv.val())
|
||||
// r <= p
|
||||
s.add_clause(~lshr, s.ule(r(), p()), true);
|
||||
return s.mk_clause(~lshr, s.ule(r(), p()), true);
|
||||
else if (qv.is_val() && qv.val() >= K && rv.is_val() && !rv.is_zero())
|
||||
// q >= K -> r = 0
|
||||
s.add_clause(~lshr, ~s.ule(K, q()), s.eq(r()), true);
|
||||
return s.mk_clause(~lshr, ~s.ule(K, q()), s.eq(r()), true);
|
||||
else if (qv.is_zero() && pv.is_val() && rv.is_val() && pv != rv)
|
||||
// q = 0 -> p = r
|
||||
s.add_clause(~lshr, ~s.eq(q()), s.eq(p(), r()), true);
|
||||
return s.mk_clause(~lshr, ~s.eq(q()), s.eq(p(), r()), true);
|
||||
else if (qv.is_val() && !qv.is_zero() && pv.is_val() && rv.is_val() && !pv.is_zero() && rv.val() >= pv.val())
|
||||
// q != 0 & p > 0 -> r < p
|
||||
s.add_clause(~lshr, s.eq(q()), s.ule(p(), 0), s.ult(r(), p()), true);
|
||||
return s.mk_clause(~lshr, s.eq(q()), s.ule(p(), 0), s.ult(r(), p()), true);
|
||||
else if (qv.is_val() && !qv.is_zero() && qv.val() < K && rv.is_val() &&
|
||||
rv.val() > rational::power_of_two(K - qv.val().get_unsigned()) - 1)
|
||||
// q >= k -> r <= 2^{K-k} - 1
|
||||
s.add_clause(~lshr, ~s.ule(qv.val(), q()), s.ule(r(), rational::power_of_two(K - qv.val().get_unsigned()) - 1), true);
|
||||
return s.mk_clause(~lshr, ~s.ule(qv.val(), q()), s.ule(r(), rational::power_of_two(K - qv.val().get_unsigned()) - 1), true);
|
||||
else if (pv.is_val() && rv.is_val() && qv.is_val() && !qv.is_zero()) {
|
||||
unsigned const k = qv.val().get_unsigned();
|
||||
// q = k -> r[i] = p[i+k] for 0 <= i < K - k
|
||||
for (unsigned i = 0; i < K - k; ++i) {
|
||||
if (rv.val().get_bit(i) && !pv.val().get_bit(i + k)) {
|
||||
s.add_clause(~lshr, ~s.eq(q(), k), ~s.bit(r(), i), s.bit(p(), i + k), true);
|
||||
return;
|
||||
return s.mk_clause(~lshr, ~s.eq(q(), k), ~s.bit(r(), i), s.bit(p(), i + k), true);
|
||||
}
|
||||
if (!rv.val().get_bit(i) && pv.val().get_bit(i + k)) {
|
||||
s.add_clause(~lshr, ~s.eq(q(), k), s.bit(r(), i), ~s.bit(p(), i + k), true);
|
||||
return;
|
||||
return s.mk_clause(~lshr, ~s.eq(q(), k), s.bit(r(), i), ~s.bit(p(), i + k), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Evaluate constraint: r == p >> q */
|
||||
|
@ -244,10 +270,10 @@ namespace polysat {
|
|||
* r != 0 -> r >= p
|
||||
* q = 0 -> r = p
|
||||
*/
|
||||
void op_constraint::narrow_shl(solver& s) {
|
||||
auto const pv = s.subst(p());
|
||||
auto const qv = s.subst(q());
|
||||
auto const rv = s.subst(r());
|
||||
clause_ref op_constraint::lemma_shl(solver& s, assignment const& a) {
|
||||
auto const pv = a.apply_to(p());
|
||||
auto const qv = a.apply_to(q());
|
||||
auto const rv = a.apply_to(r());
|
||||
unsigned const K = p().manager().power_of_2();
|
||||
|
||||
signed_constraint const shl(this, true);
|
||||
|
@ -256,35 +282,34 @@ namespace polysat {
|
|||
// r != 0 -> r >= p
|
||||
// r = 0 \/ r >= p (equivalent)
|
||||
// r-1 >= p-1 (equivalent unit constraint to better support narrowing)
|
||||
s.add_clause(~shl, s.ule(p() - 1, r() - 1), true);
|
||||
return s.mk_clause(~shl, s.ule(p() - 1, r() - 1), true);
|
||||
else if (qv.is_val() && qv.val() >= K && rv.is_val() && !rv.is_zero())
|
||||
// q >= K -> r = 0
|
||||
s.add_clause(~shl, ~s.ule(K, q()), s.eq(r()), true);
|
||||
return s.mk_clause(~shl, ~s.ule(K, q()), s.eq(r()), true);
|
||||
else if (qv.is_zero() && pv.is_val() && rv.is_val() && rv != pv)
|
||||
// q = 0 -> r = p
|
||||
s.add_clause(~shl, ~s.eq(q()), s.eq(r(), p()), true);
|
||||
return s.mk_clause(~shl, ~s.eq(q()), s.eq(r(), p()), true);
|
||||
else if (qv.is_val() && !qv.is_zero() && qv.val() < K && rv.is_val() &&
|
||||
!rv.is_zero() && rv.val() < rational::power_of_two(qv.val().get_unsigned()))
|
||||
// q >= k -> r = 0 \/ r >= 2^k (intuitive version)
|
||||
// q >= k -> r - 1 >= 2^k - 1 (equivalent unit constraint to better support narrowing)
|
||||
s.add_clause(~shl, ~s.ule(qv.val(), q()), s.ule(rational::power_of_two(qv.val().get_unsigned()) - 1, r() - 1), true);
|
||||
return s.mk_clause(~shl, ~s.ule(qv.val(), q()), s.ule(rational::power_of_two(qv.val().get_unsigned()) - 1, r() - 1), true);
|
||||
else if (pv.is_val() && rv.is_val() && qv.is_val() && !qv.is_zero()) {
|
||||
unsigned const k = qv.val().get_unsigned();
|
||||
// q = k -> r[i+k] = p[i] for 0 <= i < K - k
|
||||
for (unsigned i = 0; i < K - k; ++i) {
|
||||
if (rv.val().get_bit(i + k) && !pv.val().get_bit(i)) {
|
||||
s.add_clause(~shl, ~s.eq(q(), k), ~s.bit(r(), i + k), s.bit(p(), i), true);
|
||||
return;
|
||||
return s.mk_clause(~shl, ~s.eq(q(), k), ~s.bit(r(), i + k), s.bit(p(), i), true);
|
||||
}
|
||||
if (!rv.val().get_bit(i + k) && pv.val().get_bit(i)) {
|
||||
s.add_clause(~shl, ~s.eq(q(), k), s.bit(r(), i + k), ~s.bit(p(), i), true);
|
||||
return;
|
||||
return s.mk_clause(~shl, ~s.eq(q(), k), s.bit(r(), i + k), ~s.bit(p(), i), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SASSERT(!(pv.is_val() && qv.is_val() && rv.is_val()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Evaluate constraint: r == p << q */
|
||||
|
@ -310,37 +335,83 @@ namespace polysat {
|
|||
return l_undef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce lemmas:
|
||||
* p & q <= p
|
||||
* p & q <= q
|
||||
* p = q => p & q = r
|
||||
* p = 0 => r = 0
|
||||
* q = 0 => r = 0
|
||||
* p[i] && q[i] = r[i]
|
||||
*
|
||||
* Possible other:
|
||||
* p = max_value => q = r
|
||||
* q = max_value => p = r
|
||||
*/
|
||||
void op_constraint::narrow_and(solver& s) {
|
||||
auto pv = s.subst(p());
|
||||
auto qv = s.subst(q());
|
||||
auto rv = s.subst(r());
|
||||
void op_constraint::activate_and(solver& s) {
|
||||
auto x = p(), y = q();
|
||||
if (x.is_val())
|
||||
std::swap(x, y);
|
||||
if (!y.is_val())
|
||||
return;
|
||||
auto& m = x.manager();
|
||||
auto yv = y.val();
|
||||
if (!(yv + 1).is_power_of_two())
|
||||
return;
|
||||
signed_constraint const andc(this, true);
|
||||
if (yv == m.max_value())
|
||||
s.add_clause(~andc, s.eq(x, r()), false);
|
||||
else if (yv == 0)
|
||||
s.add_clause(~andc, s.eq(r()), false);
|
||||
else {
|
||||
unsigned K = m.power_of_2();
|
||||
unsigned k = yv.get_num_bits();
|
||||
SASSERT(k < K);
|
||||
rational exp = rational::power_of_two(K - k);
|
||||
s.add_clause(~andc, s.eq(x * exp, r() * exp), false);
|
||||
s.add_clause(~andc, s.ule(r(), y), false); // maybe always activate these constraints regardless?
|
||||
}
|
||||
}
|
||||
|
||||
signed_constraint andc(this, true);
|
||||
/**
|
||||
* Produce lemmas for constraint: r == p & q
|
||||
* r <= p
|
||||
* r <= q
|
||||
* p = q => r = p
|
||||
* p[i] && q[i] = r[i]
|
||||
* p = 2^K - 1 => q = r
|
||||
* q = 2^K - 1 => p = r
|
||||
* p = 2^k - 1 => r*2^{K - k} = q*2^{K - k}
|
||||
* q = 2^k - 1 => r*2^{K - k} = p*2^{K - k}
|
||||
* r = 0 && q != 0 & p = 2^k - 1 => q >= 2^k
|
||||
* r = 0 && p != 0 & q = 2^k - 1 => p >= 2^k
|
||||
*/
|
||||
clause_ref op_constraint::lemma_and(solver& s, assignment const& a) {
|
||||
auto& m = p().manager();
|
||||
auto pv = a.apply_to(p());
|
||||
auto qv = a.apply_to(q());
|
||||
auto rv = a.apply_to(r());
|
||||
|
||||
signed_constraint const andc(this, true);
|
||||
|
||||
// r <= p
|
||||
if (pv.is_val() && rv.is_val() && rv.val() > pv.val())
|
||||
s.add_clause(~andc, s.ule(r(), p()), true);
|
||||
else if (qv.is_val() && rv.is_val() && rv.val() > qv.val())
|
||||
s.add_clause(~andc, s.ule(r(), q()), true);
|
||||
else if (pv.is_val() && qv.is_val() && rv.is_val() && pv == qv && rv != pv)
|
||||
s.add_clause(~andc, ~s.eq(p(), q()), s.eq(r(), p()), true);
|
||||
else if (pv.is_zero() && rv.is_val() && !rv.is_zero())
|
||||
s.add_clause(~andc, ~s.eq(p()), s.eq(r()), true);
|
||||
else if (qv.is_zero() && rv.is_val() && !rv.is_zero())
|
||||
s.add_clause(~andc, ~s.eq(q()), s.eq(r()), true);
|
||||
else if (pv.is_val() && qv.is_val() && rv.is_val()) {
|
||||
unsigned K = p().manager().power_of_2();
|
||||
return s.mk_clause(~andc, s.ule(r(), p()), true);
|
||||
// r <= q
|
||||
if (qv.is_val() && rv.is_val() && rv.val() > qv.val())
|
||||
return s.mk_clause(~andc, s.ule(r(), q()), true);
|
||||
// p = q => r = p
|
||||
if (pv.is_val() && qv.is_val() && rv.is_val() && pv == qv && rv != pv)
|
||||
return s.mk_clause(~andc, ~s.eq(p(), q()), s.eq(r(), p()), true);
|
||||
if (pv.is_val() && qv.is_val() && rv.is_val()) {
|
||||
// p = -1 => r = q
|
||||
if (pv.val() == m.max_value() && qv != rv)
|
||||
return s.mk_clause(~andc, ~s.eq(p(), m.max_value()), s.eq(q(), r()), true);
|
||||
// q = -1 => r = p
|
||||
if (qv.val() == m.max_value() && pv != rv)
|
||||
return s.mk_clause(~andc, ~s.eq(q(), m.max_value()), s.eq(p(), r()), true);
|
||||
|
||||
unsigned K = m.power_of_2();
|
||||
// p = 2^k - 1 => r*2^{K - k} = q*2^{K - k}
|
||||
// TODO
|
||||
// if ((pv.val() + 1).is_power_of_two() ...)
|
||||
|
||||
// q = 2^k - 1 => r*2^{K - k} = p*2^{K - k}
|
||||
|
||||
// r = 0 && q != 0 & p = 2^k - 1 => q >= 2^k
|
||||
if ((pv.val() + 1).is_power_of_two() && rv.val() > pv.val())
|
||||
return s.mk_clause(~andc, ~s.eq(r()), ~s.eq(p(), pv.val()), s.eq(q()), s.ult(p(), q()), true);
|
||||
// r = 0 && p != 0 & q = 2^k - 1 => p >= 2^k
|
||||
if (rv.is_zero() && (qv.val() + 1).is_power_of_two() && pv.val() <= qv.val())
|
||||
return s.mk_clause(~andc, ~s.eq(r()), ~s.eq(q(), qv.val()), s.eq(p()),s.ult(q(), p()), true);
|
||||
|
||||
for (unsigned i = 0; i < K; ++i) {
|
||||
bool pb = pv.val().get_bit(i);
|
||||
bool qb = qv.val().get_bit(i);
|
||||
|
@ -348,16 +419,22 @@ namespace polysat {
|
|||
if (rb == (pb && qb))
|
||||
continue;
|
||||
if (pb && qb && !rb)
|
||||
s.add_clause(~andc, ~s.bit(p(), i), ~s.bit(q(), i), s.bit(r(), i), true);
|
||||
return s.mk_clause(~andc, ~s.bit(p(), i), ~s.bit(q(), i), s.bit(r(), i), true);
|
||||
else if (!pb && rb)
|
||||
s.add_clause(~andc, s.bit(p(), i), ~s.bit(r(), i), true);
|
||||
return s.mk_clause(~andc, s.bit(p(), i), ~s.bit(r(), i), true);
|
||||
else if (!qb && rb)
|
||||
s.add_clause(~andc, s.bit(q(), i), ~s.bit(r(), i), true);
|
||||
return s.mk_clause(~andc, s.bit(q(), i), ~s.bit(r(), i), true);
|
||||
else
|
||||
UNREACHABLE();
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// Propagate r if p or q are 0
|
||||
if (pv.is_zero() && !rv.is_zero()) // rv not necessarily fully evaluated
|
||||
return s.mk_clause(~andc, s.ule(r(), p()), true);
|
||||
if (qv.is_zero() && !rv.is_zero()) // rv not necessarily fully evaluated
|
||||
return s.mk_clause(~andc, s.ule(r(), q()), true);
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Evaluate constraint: r == p & q */
|
||||
|
@ -379,6 +456,9 @@ namespace polysat {
|
|||
case code::lshr_op:
|
||||
us.add_lshr(p_coeff, q_coeff, r_coeff, !is_positive, dep);
|
||||
break;
|
||||
case code::shl_op:
|
||||
us.add_shl(p_coeff, q_coeff, r_coeff, !is_positive, dep);
|
||||
break;
|
||||
case code::and_op:
|
||||
us.add_and(p_coeff, q_coeff, r_coeff, !is_positive, dep);
|
||||
break;
|
||||
|
|
|
@ -9,9 +9,7 @@ Module Name:
|
|||
ashr: r == p >>a q
|
||||
lshl: r == p << q
|
||||
and: r == p & q
|
||||
or: r == p | q
|
||||
not: r == ~p
|
||||
xor: r == p ^ q
|
||||
|
||||
Author:
|
||||
|
||||
|
@ -28,7 +26,7 @@ namespace polysat {
|
|||
|
||||
class op_constraint final : public constraint {
|
||||
public:
|
||||
enum class code { lshr_op, ashr_op, shl_op, and_op, or_op, xor_op, not_op };
|
||||
enum class code { lshr_op, ashr_op, shl_op, and_op };
|
||||
protected:
|
||||
friend class constraint_manager;
|
||||
|
||||
|
@ -39,16 +37,23 @@ namespace polysat {
|
|||
|
||||
op_constraint(constraint_manager& m, code c, pdd const& p, pdd const& q, pdd const& r);
|
||||
lbool eval(pdd const& p, pdd const& q, pdd const& r) const;
|
||||
clause_ref produce_lemma(solver& s, assignment const& a);
|
||||
|
||||
void narrow_lshr(solver& s);
|
||||
clause_ref lemma_lshr(solver& s, assignment const& a);
|
||||
static lbool eval_lshr(pdd const& p, pdd const& q, pdd const& r);
|
||||
|
||||
void narrow_shl(solver& s);
|
||||
clause_ref lemma_shl(solver& s, assignment const& a);
|
||||
static lbool eval_shl(pdd const& p, pdd const& q, pdd const& r);
|
||||
|
||||
void narrow_and(solver& s);
|
||||
|
||||
clause_ref lemma_and(solver& s, assignment const& a);
|
||||
static lbool eval_and(pdd const& p, pdd const& q, pdd const& r);
|
||||
|
||||
std::ostream& display(std::ostream& out, char const* eq) const;
|
||||
|
||||
void activate(solver& s);
|
||||
|
||||
void activate_and(solver& s);
|
||||
|
||||
public:
|
||||
~op_constraint() override {}
|
||||
pdd const& p() const { return m_p; }
|
||||
|
@ -59,7 +64,7 @@ namespace polysat {
|
|||
lbool eval() const override;
|
||||
lbool eval(assignment const& a) const override;
|
||||
void narrow(solver& s, bool is_positive, bool first) override;
|
||||
inequality as_inequality(bool is_positive) const override { throw default_exception("is not an inequality"); }
|
||||
virtual clause_ref produce_lemma(solver& s, assignment const& a, bool is_positive) override;
|
||||
unsigned hash() const override;
|
||||
bool operator==(constraint const& other) const override;
|
||||
bool is_eq() const override { return false; }
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,38 +12,28 @@ Author:
|
|||
|
||||
--*/
|
||||
#pragma once
|
||||
#include "math/polysat/clause_builder.h"
|
||||
#include "math/polysat/conflict.h"
|
||||
|
||||
namespace polysat {
|
||||
|
||||
class solver;
|
||||
/**
|
||||
* Introduce lemmas that derive new (simpler) constraints from the current conflict and partial model.
|
||||
*/
|
||||
class saturation {
|
||||
|
||||
class inference_engine {
|
||||
friend class conflict;
|
||||
protected:
|
||||
solver& s;
|
||||
public:
|
||||
inference_engine(solver& s) :s(s) {}
|
||||
virtual ~inference_engine() {}
|
||||
/** Try to apply an inference rule.
|
||||
* Derive new constraints from constraints containing the variable v (i.e., at least one premise must contain v).
|
||||
* Returns true if a new constraint was added to the core.
|
||||
*/
|
||||
virtual bool perform(pvar v, conflict& core) = 0;
|
||||
};
|
||||
|
||||
class inf_saturate : public inference_engine {
|
||||
|
||||
vector<signed_constraint> m_new_constraints;
|
||||
clause_builder m_lemma;
|
||||
char const* m_rule = nullptr;
|
||||
|
||||
void set_rule(char const* r) { m_rule = r; }
|
||||
void set_rule(char const* r) { m_rule = r; }
|
||||
|
||||
void push_omega(pdd const& x, pdd const& y);
|
||||
void push_omega_bisect(pdd const& x, rational x_max, pdd const& y, rational y_max);
|
||||
bool is_non_overflow(pdd const& x, pdd const& y, signed_constraint& c);
|
||||
signed_constraint ineq(bool strict, pdd const& lhs, pdd const& rhs);
|
||||
bool propagate(char const* inf_name, conflict& core, inequality const& crit1, inequality const& crit2, signed_constraint& c);
|
||||
bool propagate(char const* inf_name, conflict& core, inequality const& crit1, inequality const& crit2, bool strict, pdd const& lhs, pdd const& rhs);
|
||||
|
||||
bool propagate(conflict& core, inequality const& crit1, signed_constraint c);
|
||||
bool add_conflict(conflict& core, inequality const& crit1, signed_constraint c);
|
||||
bool add_conflict(conflict& core, inequality const& crit1, inequality const& crit2, signed_constraint c);
|
||||
|
||||
bool try_ugt_x(pvar v, conflict& core, inequality const& c);
|
||||
|
||||
|
@ -52,14 +42,18 @@ namespace polysat {
|
|||
|
||||
bool try_y_l_ax_and_x_l_z(pvar x, conflict& core, inequality const& c);
|
||||
bool try_y_l_ax_and_x_l_z(pvar x, conflict& core, inequality const& x_l_z, inequality const& y_l_ax, pdd const& a, pdd const& y);
|
||||
|
||||
|
||||
bool try_ugt_z(pvar z, conflict& core, inequality const& c);
|
||||
bool try_ugt_z(pvar z, conflict& core, inequality const& x_l_z0, inequality const& yz_l_xz, pdd const& y, pdd const& x);
|
||||
|
||||
bool try_parity(pvar x, conflict& core, inequality const& axb_l_y);
|
||||
bool try_mul_bounds(pvar x, conflict& core, inequality const& axb_l_y);
|
||||
bool try_mul_eq_1(pvar x, conflict& core, inequality const& axb_l_y);
|
||||
bool try_mul_odd(pvar x, conflict& core, inequality const& axb_l_y);
|
||||
bool try_tangent(pvar v, conflict& core, inequality const& c);
|
||||
|
||||
// c := lhs ~ v
|
||||
// where ~ is < or <=
|
||||
// c := lhs ~ v
|
||||
// where ~ is < or <=
|
||||
bool is_l_v(pvar v, inequality const& c);
|
||||
|
||||
// c := v ~ rhs
|
||||
|
@ -73,12 +67,25 @@ namespace polysat {
|
|||
bool verify_Xy_l_XZ(pvar y, inequality const& c, pdd const& x, pdd const& z);
|
||||
|
||||
// c := Y ~ Ax
|
||||
bool is_Y_l_Ax(pvar x, inequality const& d, pdd& a, pdd& y);
|
||||
bool verify_Y_l_Ax(pvar x, inequality const& d, pdd const& a, pdd const& y);
|
||||
bool is_Y_l_Ax(pvar x, inequality const& c, pdd& a, pdd& y);
|
||||
bool verify_Y_l_Ax(pvar x, inequality const& c, pdd const& a, pdd const& y);
|
||||
|
||||
// c := Ax ~ Y
|
||||
bool is_Ax_l_Y(pvar x, inequality const& c, pdd& a, pdd& y);
|
||||
bool verify_Ax_l_Y(pvar x, inequality const& c, pdd const& a, pdd const& y);
|
||||
|
||||
// c := Ax + B ~ Y
|
||||
bool is_AxB_l_Y(pvar x, inequality const& c, pdd& a, pdd& b, pdd& y);
|
||||
bool verify_AxB_l_Y(pvar x, inequality const& c, pdd const& a, pdd const& b, pdd const& y);
|
||||
|
||||
// c := Ax + B ~ Y, val(Y) = 0
|
||||
bool is_AxB_eq_0(pvar x, inequality const& c, pdd& a, pdd& b, pdd& y);
|
||||
bool verify_AxB_eq_0(pvar x, inequality const& c, pdd const& a, pdd const& b, pdd const& y);
|
||||
|
||||
|
||||
// c := Y*X ~ z*X
|
||||
bool is_YX_l_zX(pvar z, inequality const& c, pdd& x, pdd& y);
|
||||
bool verify_YX_l_zX(pvar z, inequality const& c, pdd const& x, pdd const& y);
|
||||
bool verify_YX_l_zX(pvar z, inequality const& c, pdd const& x, pdd const& y);
|
||||
|
||||
// c := xY <= xZ
|
||||
bool is_xY_l_xZ(pvar x, inequality const& c, pdd& y, pdd& z);
|
||||
|
@ -92,11 +99,20 @@ namespace polysat {
|
|||
// p := coeff*x*y where coeff_x = coeff*x, x a variable
|
||||
bool is_coeffxY(pdd const& coeff_x, pdd const& p, pdd& y);
|
||||
|
||||
rational max_value(pdd const& x);
|
||||
bool is_forced_eq(pdd const& p, rational const& val);
|
||||
bool is_forced_eq(pdd const& p, int i) { return is_forced_eq(p, rational(i)); }
|
||||
|
||||
bool is_forced_diseq(pdd const& p, int i, signed_constraint& c);
|
||||
|
||||
bool is_forced_odd(pdd const& p, signed_constraint& c);
|
||||
|
||||
bool is_forced_false(signed_constraint const& sc);
|
||||
|
||||
bool is_forced_true(signed_constraint const& sc);
|
||||
|
||||
public:
|
||||
inf_saturate(solver& s) : inference_engine(s) {}
|
||||
bool perform(pvar v, conflict& core) override;
|
||||
saturation(solver& s);
|
||||
bool perform(pvar v, conflict& core);
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -38,7 +38,6 @@ namespace polysat {
|
|||
lbool eval(assignment const& a) const override { return l_undef; } // TODO
|
||||
void narrow(solver& s, bool is_positive, bool first) override;
|
||||
|
||||
inequality as_inequality(bool is_positive) const override { throw default_exception("is not an inequality"); }
|
||||
unsigned hash() const override;
|
||||
bool operator==(constraint const& other) const override;
|
||||
bool is_eq() const override { return false; }
|
||||
|
|
|
@ -62,6 +62,10 @@ namespace polysat {
|
|||
}
|
||||
|
||||
lbool solver::check_sat() {
|
||||
#ifndef NDEBUG
|
||||
SASSERT(!m_is_solving);
|
||||
flet<bool> save_(m_is_solving, true);
|
||||
#endif
|
||||
LOG("Starting");
|
||||
while (should_search()) {
|
||||
m_stats.m_num_iterations++;
|
||||
|
@ -72,6 +76,7 @@ namespace polysat {
|
|||
IF_LOGGING(m_viable.log());
|
||||
if (is_conflict() && at_base_level()) { LOG_H2("UNSAT"); return l_false; }
|
||||
else if (is_conflict()) resolve_conflict();
|
||||
else if (should_add_pwatch()) add_pwatch();
|
||||
else if (can_propagate()) propagate();
|
||||
else if (!can_decide()) { LOG_H2("SAT"); SASSERT(verify_sat()); return l_true; }
|
||||
else if (m_constraints.should_gc()) m_constraints.gc();
|
||||
|
@ -136,6 +141,8 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void solver::assign_eh(signed_constraint c, dependency dep) {
|
||||
// This method is part of the external interface and should not be used to create internal constraints during solving.
|
||||
SASSERT(!m_is_solving);
|
||||
backjump(base_level());
|
||||
SASSERT(at_base_level());
|
||||
SASSERT(c);
|
||||
|
@ -184,8 +191,8 @@ namespace polysat {
|
|||
if (!can_propagate())
|
||||
return;
|
||||
#ifndef NDEBUG
|
||||
SASSERT(!m_propagating);
|
||||
flet<bool> save_(m_propagating, true);
|
||||
SASSERT(!m_is_propagating);
|
||||
flet<bool> save_(m_is_propagating, true);
|
||||
#endif
|
||||
push_qhead();
|
||||
while (can_propagate()) {
|
||||
|
@ -242,7 +249,8 @@ namespace polysat {
|
|||
* Return true if a new watch was found; or false to keep the existing one.
|
||||
*/
|
||||
bool solver::propagate(pvar v, constraint* c) {
|
||||
LOG_H3("Propagate " << m_vars[v] << " in " << constraint_pp(c, m_bvars.value(c->bvar())));
|
||||
lbool const bvalue = m_bvars.value(c->bvar());
|
||||
LOG_H3("Propagate " << m_vars[v] << " in " << constraint_pp(c, bvalue));
|
||||
SASSERT(is_assigned(v));
|
||||
SASSERT(!c->vars().empty());
|
||||
unsigned idx = 0;
|
||||
|
@ -259,9 +267,9 @@ namespace polysat {
|
|||
}
|
||||
}
|
||||
// at most one pvar remains unassigned
|
||||
if (m_bvars.is_assigned(c->bvar())) {
|
||||
if (bvalue != l_undef) {
|
||||
// constraint state: bool-propagated
|
||||
signed_constraint sc(c, m_bvars.value(c->bvar()) == l_true);
|
||||
signed_constraint sc(c, bvalue == l_true);
|
||||
if (c->vars().size() >= 2) {
|
||||
unsigned other_v = c->var(1 - idx);
|
||||
if (!is_assigned(other_v))
|
||||
|
@ -328,6 +336,25 @@ namespace polysat {
|
|||
#endif
|
||||
}
|
||||
|
||||
/** Enqueue constraint c to perform add_pwatch(c) on the next solver iteration */
|
||||
void solver::enqueue_pwatch(constraint* c) {
|
||||
SASSERT(c);
|
||||
if (c->is_pwatched())
|
||||
return;
|
||||
m_pwatch_queue.push_back(c);
|
||||
}
|
||||
|
||||
bool solver::should_add_pwatch() const {
|
||||
return !m_pwatch_queue.empty();
|
||||
}
|
||||
|
||||
void solver::add_pwatch() {
|
||||
for (constraint* c : m_pwatch_queue) {
|
||||
add_pwatch(c);
|
||||
}
|
||||
m_pwatch_queue.reset();
|
||||
}
|
||||
|
||||
void solver::add_pwatch(constraint* c) {
|
||||
SASSERT(c);
|
||||
if (c->is_pwatched())
|
||||
|
@ -342,8 +369,10 @@ namespace polysat {
|
|||
if (vars.size() > 1)
|
||||
add_pwatch(c, vars[1]);
|
||||
c->set_pwatched(true);
|
||||
#if 0
|
||||
m_pwatch_trail.push_back(c);
|
||||
m_trail.push_back(trail_instr_t::pwatch_i);
|
||||
#endif
|
||||
}
|
||||
|
||||
void solver::add_pwatch(constraint* c, pvar v) {
|
||||
|
@ -412,12 +441,15 @@ namespace polysat {
|
|||
m_lemmas.pop_back();
|
||||
break;
|
||||
}
|
||||
#if 0
|
||||
// NOTE: erase_pwatch should be called when the constraint is deleted from the solver.
|
||||
case trail_instr_t::pwatch_i: {
|
||||
constraint* c = m_pwatch_trail.back();
|
||||
erase_pwatch(c);
|
||||
m_pwatch_trail.pop_back();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case trail_instr_t::add_var_i: {
|
||||
// NOTE: currently we cannot delete variables during solving,
|
||||
// since lemmas may introduce new helper variables if they use operations like bitwise and or pseudo-inverse.
|
||||
|
@ -626,15 +658,38 @@ namespace polysat {
|
|||
/// Verify the value we're trying to assign against the univariate solver
|
||||
void solver::assign_verify(pvar v, rational val, justification j) {
|
||||
SASSERT(j.is_decision() || j.is_propagation());
|
||||
// First, check evaluation of the currently-univariate constraints
|
||||
// TODO: we should add a better way to test constraints under assignments, without modifying the solver state.
|
||||
m_value[v] = val;
|
||||
m_search.push_assignment(v, val);
|
||||
m_justification[v] = j;
|
||||
bool is_valid = m_viable_fallback.check_constraints(v);
|
||||
m_search.pop();
|
||||
m_justification[v] = justification::unassigned();
|
||||
if (!is_valid) {
|
||||
#ifndef NDEBUG
|
||||
unsigned const old_size = m_search.size();
|
||||
#endif
|
||||
signed_constraint c;
|
||||
clause_ref lemma;
|
||||
{
|
||||
// Fake the assignment v := val so we can check the constraints using the new value.
|
||||
// NOTE: we modify the global state here because cloning the assignment is expensive.
|
||||
m_search.push_assignment(v, val);
|
||||
assignment_t const& a = m_search.assignment();
|
||||
on_scope_exit _undo([&](){
|
||||
m_search.pop();
|
||||
});
|
||||
|
||||
// Check evaluation of the currently-univariate constraints.
|
||||
c = m_viable_fallback.find_violated_constraint(a, v);
|
||||
|
||||
if (c) {
|
||||
LOG("Violated constraint: " << c);
|
||||
lemma = c.produce_lemma(*this, a);
|
||||
LOG("Produced lemma: " << show_deref(lemma));
|
||||
}
|
||||
}
|
||||
SASSERT(m_search.size() == old_size);
|
||||
SASSERT(!m_search.assignment().contains(v));
|
||||
if (lemma) {
|
||||
add_clause(*lemma);
|
||||
SASSERT(!is_conflict()); // if we have a conflict here, we could have produced this lemma already earlier
|
||||
if (can_propagate())
|
||||
return;
|
||||
}
|
||||
if (c) {
|
||||
LOG_H2("Chosen assignment " << assignment_pp(*this, v, val) << " is not actually viable!");
|
||||
++m_stats.m_num_viable_fallback;
|
||||
// Try to find a valid replacement value
|
||||
|
@ -675,7 +730,7 @@ namespace polysat {
|
|||
// Decision should satisfy all univariate constraints.
|
||||
// Propagation might violate some other constraint; but we will notice that in the propagation loop when v is propagated.
|
||||
// TODO: on the other hand, checking constraints here would have us discover some conflicts earlier.
|
||||
SASSERT(!j.is_decision() || m_viable_fallback.check_constraints(v));
|
||||
SASSERT(!j.is_decision() || m_viable_fallback.check_constraints(assignment(), v));
|
||||
#if ENABLE_LINEAR_SOLVER
|
||||
// TODO: convert justification into a format that can be tracked in a dependency core.
|
||||
m_linear_solver.set_value(v, val, UINT_MAX);
|
||||
|
@ -715,6 +770,7 @@ namespace polysat {
|
|||
continue;
|
||||
}
|
||||
if (j.is_decision()) {
|
||||
// NSB TODO - disabled m_conflict.revert_decision(v);
|
||||
revert_decision(v);
|
||||
return;
|
||||
}
|
||||
|
@ -740,7 +796,7 @@ namespace polysat {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
SASSERT(!m_bvars.is_assumption(var));
|
||||
SASSERT(!m_bvars.is_assumption(var)); // TODO: "assumption" is basically "propagated by unit clause" (or "at base level"); except we do not explicitly store the unit clause.
|
||||
if (m_bvars.is_decision(var)) {
|
||||
revert_bool_decision(lit);
|
||||
return;
|
||||
|
@ -758,119 +814,47 @@ namespace polysat {
|
|||
report_unsat();
|
||||
}
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* Simple backjumping for lemmas:
|
||||
* jump to the level where the lemma can be (bool-)propagated,
|
||||
* even without reverting the last decision.
|
||||
*/
|
||||
void solver::backjump_lemma() {
|
||||
clause_ref lemma = m_conflict.build_lemma();
|
||||
LOG_H2("backjump_lemma: " << show_deref(lemma));
|
||||
SASSERT(lemma);
|
||||
|
||||
// find second-highest level of the literals in the lemma
|
||||
unsigned max_level = 0;
|
||||
unsigned jump_level = 0;
|
||||
for (auto lit : *lemma) {
|
||||
if (!m_bvars.is_assigned(lit))
|
||||
continue;
|
||||
unsigned lit_level = m_bvars.level(lit);
|
||||
if (lit_level > max_level) {
|
||||
jump_level = max_level;
|
||||
max_level = lit_level;
|
||||
} else if (max_level > lit_level && lit_level > jump_level) {
|
||||
jump_level = lit_level;
|
||||
}
|
||||
}
|
||||
|
||||
jump_level = std::max(jump_level, base_level());
|
||||
backjump_and_learn(jump_level, *lemma);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Revert a decision that caused a conflict.
|
||||
* Variable v was assigned by a decision at position i in the search stack.
|
||||
*
|
||||
* C & v = val is conflict.
|
||||
*
|
||||
* C => v != val
|
||||
*
|
||||
* l1 \/ l2 \/ ... \/ lk \/ v != val
|
||||
*
|
||||
*/
|
||||
void solver::revert_decision(pvar v) {
|
||||
#if 0
|
||||
rational val = m_value[v];
|
||||
LOG_H2("Reverting decision: pvar " << v << " := " << val);
|
||||
SASSERT(m_justification[v].is_decision());
|
||||
|
||||
clause_ref lemma = m_conflict.build_lemma();
|
||||
SASSERT(lemma);
|
||||
|
||||
if (lemma->empty()) {
|
||||
report_unsat();
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned jump_level = get_level(v) - 1;
|
||||
backjump_and_learn(jump_level, *lemma);
|
||||
#endif
|
||||
unsigned max_jump_level = get_level(v) - 1;
|
||||
backjump_and_learn(max_jump_level);
|
||||
}
|
||||
|
||||
void solver::revert_bool_decision(sat::literal const lit) {
|
||||
#if 0
|
||||
LOG_H2("Reverting decision: " << lit_pp(*this, lit));
|
||||
sat::bool_var const var = lit.var();
|
||||
|
||||
clause_ref lemma_ref = m_conflict.build_lemma();
|
||||
SASSERT(lemma_ref);
|
||||
clause& lemma = *lemma_ref;
|
||||
|
||||
SASSERT(!lemma.empty());
|
||||
SASSERT(count(lemma, ~lit) > 0);
|
||||
SASSERT(all_of(lemma, [this](sat::literal lit1) { return m_bvars.is_false(lit1); }));
|
||||
SASSERT(all_of(lemma, [this, var](sat::literal lit1) { return var == lit1.var() || m_bvars.level(lit1) < m_bvars.level(var); }));
|
||||
|
||||
unsigned jump_level = m_bvars.level(var) - 1;
|
||||
backjump_and_learn(jump_level, lemma);
|
||||
// At this point, the lemma is asserting for ~lit,
|
||||
// and has been propagated by learn_lemma/add_clause.
|
||||
SASSERT(all_of(lemma, [this](sat::literal lit1) { return m_bvars.is_assigned(lit1); }));
|
||||
// so the regular propagation loop will propagate ~lit.
|
||||
// Recall that lit comes from a non-asserting lemma.
|
||||
// If there is more than one undef choice left in that lemma,
|
||||
// then the next bdecide will take care of that (after all outstanding propagations).
|
||||
SASSERT(can_bdecide());
|
||||
#endif
|
||||
unsigned max_jump_level = m_bvars.level(lit) - 1;
|
||||
backjump_and_learn(max_jump_level);
|
||||
}
|
||||
|
||||
//
|
||||
// NSB review: this code assumes that these lemmas are false.
|
||||
// It does not allow saturation to add unit propagation into freshly created literals.
|
||||
//
|
||||
std::optional<lemma_score> solver::compute_lemma_score(clause const& lemma) {
|
||||
unsigned max_level = 0; // highest level in lemma
|
||||
unsigned at_max_level = 0; // how many literals at the highest level in lemma
|
||||
unsigned lits_at_max_level = 0; // how many literals at the highest level in lemma
|
||||
unsigned snd_level = 0; // second-highest level in lemma
|
||||
bool is_propagation = false;
|
||||
for (sat::literal lit : lemma) {
|
||||
SASSERT(m_bvars.is_assigned(lit)); // any new constraints should have been assign_eval'd
|
||||
if (m_bvars.is_true(lit)) // may happen if we only use the clause to justify a new constraint; it is not a real lemma
|
||||
return std::nullopt;
|
||||
if (!m_bvars.is_assigned(lit)) {
|
||||
SASSERT(!is_propagation);
|
||||
is_propagation = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned const lit_level = m_bvars.level(lit);
|
||||
if (lit_level > max_level) {
|
||||
snd_level = max_level;
|
||||
max_level = lit_level;
|
||||
at_max_level = 1;
|
||||
} else if (lit_level == max_level) {
|
||||
at_max_level++;
|
||||
} else if (max_level > lit_level && lit_level > snd_level) {
|
||||
snd_level = lit_level;
|
||||
lits_at_max_level = 1;
|
||||
}
|
||||
else if (lit_level == max_level)
|
||||
lits_at_max_level++;
|
||||
else if (max_level > lit_level && lit_level > snd_level)
|
||||
snd_level = lit_level;
|
||||
}
|
||||
SASSERT(lemma.empty() || at_max_level > 0);
|
||||
SASSERT(lemma.empty() || lits_at_max_level > 0);
|
||||
// The MCSAT paper distinguishes between "UIP clauses" and "semantic split clauses".
|
||||
// It is the same as our distinction between "asserting" and "non-asserting" lemmas.
|
||||
// - UIP clause: a single literal on the highest decision level in the lemma.
|
||||
|
@ -879,11 +863,13 @@ namespace polysat {
|
|||
// Backtrack to "highest level - 1" and split on the lemma there.
|
||||
// For now, we follow the same convention for computing the jump levels.
|
||||
unsigned jump_level;
|
||||
if (at_max_level <= 1)
|
||||
if (is_propagation)
|
||||
jump_level = max_level;
|
||||
if (lits_at_max_level <= 1)
|
||||
jump_level = snd_level;
|
||||
else
|
||||
jump_level = (max_level == 0) ? 0 : (max_level - 1);
|
||||
return {{jump_level, at_max_level}};
|
||||
return {{jump_level, lits_at_max_level}};
|
||||
}
|
||||
|
||||
void solver::backjump_and_learn(unsigned max_jump_level) {
|
||||
|
@ -916,7 +902,7 @@ namespace polysat {
|
|||
SASSERT(best_score < lemma_score::max());
|
||||
SASSERT(best_lemma);
|
||||
|
||||
unsigned const jump_level = best_score.jump_level();
|
||||
unsigned const jump_level = std::max(best_score.jump_level(), base_level());
|
||||
SASSERT(jump_level <= max_jump_level);
|
||||
|
||||
m_conflict.reset();
|
||||
|
@ -948,9 +934,10 @@ namespace polysat {
|
|||
if (is_conflict()) {
|
||||
// until this is fixed (if possible; and there may be other causes of conflict at this point),
|
||||
// we just forget about the remaining lemmas and restart conflict analysis.
|
||||
// TODO: we could also insert the remaining lemmas into the conflict and keep them for later.
|
||||
return;
|
||||
}
|
||||
SASSERT(!is_conflict()); // TODO: is this true in general? No lemma by itself should lead to a conflict here. But can there be conflicting asserting lemmas?
|
||||
SASSERT(!is_conflict());
|
||||
}
|
||||
|
||||
LOG("best_score: " << best_score);
|
||||
|
@ -980,62 +967,6 @@ namespace polysat {
|
|||
}
|
||||
} // backjump_and_learn
|
||||
|
||||
#if 0
|
||||
void solver::backjump_and_learn(unsigned jump_level, clause& lemma) {
|
||||
clause_ref_vector lemmas = m_conflict.take_lemmas();
|
||||
sat::literal_vector narrow_queue = m_conflict.take_narrow_queue();
|
||||
|
||||
m_conflict.reset();
|
||||
backjump(jump_level);
|
||||
|
||||
for (sat::literal lit : narrow_queue) {
|
||||
switch (m_bvars.value(lit)) {
|
||||
case l_true:
|
||||
lit2cnstr(lit).narrow(*this, false);
|
||||
break;
|
||||
case l_false:
|
||||
lit2cnstr(~lit).narrow(*this, false);
|
||||
break;
|
||||
case l_undef:
|
||||
/* do nothing */
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
for (clause* cl : lemmas) {
|
||||
m_simplify_clause.apply(*cl);
|
||||
add_clause(*cl);
|
||||
}
|
||||
learn_lemma(lemma);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
void solver::learn_lemma(clause& lemma) {
|
||||
SASSERT(!lemma.empty());
|
||||
m_simplify_clause.apply(lemma);
|
||||
add_clause(lemma); // propagates undef literals, if possible
|
||||
// At this point, all literals in lemma have been value- or bool-propated, if possible.
|
||||
// So if the lemma is/was asserting, all its literals are now assigned.
|
||||
bool is_asserting = all_of(lemma, [this](sat::literal lit) { return m_bvars.is_assigned(lit); });
|
||||
if (!is_asserting) {
|
||||
LOG("Lemma is not asserting!");
|
||||
m_lemmas.push_back(&lemma);
|
||||
m_trail.push_back(trail_instr_t::add_lemma_i);
|
||||
// TODO: currently we forget non-asserting lemmas when backjumping over them.
|
||||
// We surely don't want to keep them in m_lemmas because then we will start doing case splits
|
||||
// even the lemma should instead be waiting for propagations.
|
||||
// We could instead watch its pvars and re-insert into m_lemmas when all but one are assigned.
|
||||
// The same could even be done in general for all lemmas, instead of distinguishing between
|
||||
// asserting and non-asserting lemmas.
|
||||
// (Note that the same lemma can be asserting in one branch of the search but non-asserting in another,
|
||||
// depending on which pvars are assigned.)
|
||||
SASSERT(can_bdecide());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned solver::level(sat::literal lit0, clause const& cl) {
|
||||
unsigned lvl = 0;
|
||||
for (auto lit : cl) {
|
||||
|
@ -1099,8 +1030,25 @@ namespace polysat {
|
|||
LOG("Activating constraint: " << c);
|
||||
SASSERT_EQ(m_bvars.value(c.blit()), l_true);
|
||||
add_pwatch(c.get());
|
||||
pvar v = null_var;
|
||||
if (c->vars().size() == 1)
|
||||
m_viable_fallback.push_constraint(c->var(0), c);
|
||||
// If there is exactly one variable in c, then c is always univariate.
|
||||
v = c->vars()[0];
|
||||
else {
|
||||
// Otherwise, check if exactly one variable in c remains unassigned.
|
||||
for (pvar w : c->vars()) {
|
||||
if (is_assigned(w))
|
||||
continue;
|
||||
if (v != null_var) {
|
||||
// two or more unassigned vars; abort
|
||||
v = null_var;
|
||||
break;
|
||||
}
|
||||
v = w;
|
||||
}
|
||||
}
|
||||
if (v != null_var)
|
||||
m_viable_fallback.push_constraint(v, c);
|
||||
c.narrow(*this, true);
|
||||
#if ENABLE_LINEAR_SOLVER
|
||||
m_linear_solver.activate_constraint(c);
|
||||
|
@ -1108,6 +1056,7 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void solver::backjump(unsigned new_level) {
|
||||
SASSERT(new_level >= base_level());
|
||||
if (m_level != new_level) {
|
||||
LOG_H3("Backjumping to level " << new_level << " from level " << m_level);
|
||||
pop_levels(m_level - new_level);
|
||||
|
@ -1131,44 +1080,68 @@ namespace polysat {
|
|||
SASSERT(!clause.empty());
|
||||
m_constraints.store(&clause, true);
|
||||
|
||||
if (!clause.is_redundant()) {
|
||||
// for (at least) non-redundant clauses, we also need to watch the constraints
|
||||
// so we can discover when the clause should propagate
|
||||
// TODO: check if we also need pwatch for redundant clauses
|
||||
for (sat::literal lit : clause)
|
||||
add_pwatch(m_constraints.lookup(lit.var()));
|
||||
}
|
||||
// Defer add_pwatch until the next solver iteration, because during propagation of a variable v the watchlist for v is locked.
|
||||
// NOTE: for non-redundant clauses, pwatching its constraints is required for soundness.
|
||||
for (sat::literal lit : clause)
|
||||
enqueue_pwatch(lit2cnstr(lit).get());
|
||||
}
|
||||
|
||||
void solver::add_clause(unsigned n, signed_constraint* cs, bool is_redundant) {
|
||||
clause_builder cb(*this);
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
cb.insert(cs[i]);
|
||||
clause_ref clause = cb.build();
|
||||
if (clause) {
|
||||
clause->set_redundant(is_redundant);
|
||||
void solver::add_clause(unsigned n, signed_constraint const* cs, bool is_redundant) {
|
||||
clause_ref clause = mk_clause(n, cs, is_redundant);
|
||||
if (clause)
|
||||
add_clause(*clause);
|
||||
}
|
||||
}
|
||||
|
||||
void solver::add_clause(signed_constraint c, bool is_redundant) {
|
||||
signed_constraint cs[1] = { c };
|
||||
add_clause(1, cs, is_redundant);
|
||||
void solver::add_clause(std::initializer_list<signed_constraint> cs, bool is_redundant) {
|
||||
add_clause(static_cast<unsigned>(cs.size()), std::data(cs), is_redundant);
|
||||
}
|
||||
|
||||
void solver::add_clause(signed_constraint c1, bool is_redundant) {
|
||||
add_clause({ c1 }, is_redundant);
|
||||
}
|
||||
|
||||
void solver::add_clause(signed_constraint c1, signed_constraint c2, bool is_redundant) {
|
||||
signed_constraint cs[2] = { c1, c2 };
|
||||
add_clause(2, cs, is_redundant);
|
||||
add_clause({ c1, c2 }, is_redundant);
|
||||
}
|
||||
|
||||
void solver::add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, bool is_redundant) {
|
||||
signed_constraint cs[3] = { c1, c2, c3 };
|
||||
add_clause(3, cs, is_redundant);
|
||||
add_clause({ c1, c2, c3 }, is_redundant);
|
||||
}
|
||||
|
||||
void solver::add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, bool is_redundant) {
|
||||
signed_constraint cs[4] = { c1, c2, c3, c4 };
|
||||
add_clause(4, cs, is_redundant);
|
||||
add_clause({ c1, c2, c3, c4 }, is_redundant);
|
||||
}
|
||||
|
||||
clause_ref solver::mk_clause(std::initializer_list<signed_constraint> cs, bool is_redundant) {
|
||||
return mk_clause(static_cast<unsigned>(cs.size()), std::data(cs), is_redundant);
|
||||
}
|
||||
|
||||
clause_ref solver::mk_clause(unsigned n, signed_constraint const* cs, bool is_redundant) {
|
||||
clause_builder cb(*this);
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
cb.insert(cs[i]);
|
||||
cb.set_redundant(is_redundant);
|
||||
return cb.build();
|
||||
}
|
||||
|
||||
clause_ref solver::mk_clause(signed_constraint c1, bool is_redundant) {
|
||||
return mk_clause({ c1 }, is_redundant);
|
||||
}
|
||||
|
||||
clause_ref solver::mk_clause(signed_constraint c1, signed_constraint c2, bool is_redundant) {
|
||||
return mk_clause({ c1, c2 }, is_redundant);
|
||||
}
|
||||
|
||||
clause_ref solver::mk_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, bool is_redundant) {
|
||||
return mk_clause({ c1, c2, c3 }, is_redundant);
|
||||
}
|
||||
|
||||
clause_ref solver::mk_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, bool is_redundant) {
|
||||
return mk_clause({ c1, c2, c3, c4 }, is_redundant);
|
||||
}
|
||||
|
||||
clause_ref solver::mk_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, signed_constraint c5, bool is_redundant) {
|
||||
return mk_clause({ c1, c2, c3, c4, c5 }, is_redundant);
|
||||
}
|
||||
|
||||
void solver::push() {
|
||||
|
|
|
@ -133,7 +133,7 @@ namespace polysat {
|
|||
friend class assignments_pp;
|
||||
friend class ex_polynomial_superposition;
|
||||
friend class free_variable_elimination;
|
||||
friend class inf_saturate;
|
||||
friend class saturation;
|
||||
friend class constraint_manager;
|
||||
friend class scoped_solverv;
|
||||
friend class test_polysat;
|
||||
|
@ -165,7 +165,8 @@ namespace polysat {
|
|||
vector<constraints> m_pwatch; // watch list datastructure into constraints.
|
||||
#ifndef NDEBUG
|
||||
std::optional<pvar> m_locked_wlist; // restrict watch list modification while it is being propagated
|
||||
bool m_propagating = false; // set to true during propagation
|
||||
bool m_is_propagating = false; // set to true during propagation
|
||||
bool m_is_solving = false; // set to true during solving
|
||||
#endif
|
||||
|
||||
unsigned_vector m_activity;
|
||||
|
@ -179,7 +180,10 @@ namespace polysat {
|
|||
|
||||
svector<trail_instr_t> m_trail;
|
||||
unsigned_vector m_qhead_trail;
|
||||
constraints m_pwatch_queue;
|
||||
#if 0
|
||||
constraints m_pwatch_trail;
|
||||
#endif
|
||||
|
||||
ptr_vector<clause> m_lemmas; ///< the non-asserting lemmas
|
||||
unsigned m_lemmas_qhead = 0;
|
||||
|
@ -233,6 +237,9 @@ namespace polysat {
|
|||
void propagate(pvar v);
|
||||
bool propagate(pvar v, constraint* c);
|
||||
bool propagate(sat::literal lit, clause& cl);
|
||||
void enqueue_pwatch(constraint* c);
|
||||
bool should_add_pwatch() const;
|
||||
void add_pwatch();
|
||||
void add_pwatch(constraint* c);
|
||||
void add_pwatch(constraint* c, pvar v);
|
||||
void erase_pwatch(pvar v, constraint* c);
|
||||
|
@ -273,12 +280,23 @@ namespace polysat {
|
|||
void report_unsat();
|
||||
void learn_lemma(clause& lemma);
|
||||
void backjump(unsigned new_level);
|
||||
|
||||
void add_clause(clause& clause);
|
||||
void add_clause(signed_constraint c, bool is_redundant);
|
||||
void add_clause(signed_constraint c1, bool is_redundant);
|
||||
void add_clause(signed_constraint c1, signed_constraint c2, bool is_redundant);
|
||||
void add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, bool is_redundant);
|
||||
void add_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, bool is_redundant);
|
||||
void add_clause(unsigned n, signed_constraint* cs, bool is_redundant);
|
||||
void add_clause(std::initializer_list<signed_constraint> cs, bool is_redundant);
|
||||
void add_clause(unsigned n, signed_constraint const* cs, bool is_redundant);
|
||||
|
||||
// Create a clause without adding it to the solver.
|
||||
clause_ref mk_clause(signed_constraint c1, bool is_redundant);
|
||||
clause_ref mk_clause(signed_constraint c1, signed_constraint c2, bool is_redundant);
|
||||
clause_ref mk_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, bool is_redundant);
|
||||
clause_ref mk_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, bool is_redundant);
|
||||
clause_ref mk_clause(signed_constraint c1, signed_constraint c2, signed_constraint c3, signed_constraint c4, signed_constraint c5, bool is_redundant);
|
||||
clause_ref mk_clause(std::initializer_list<signed_constraint> cs, bool is_redundant);
|
||||
clause_ref mk_clause(unsigned n, signed_constraint const* cs, bool is_redundant);
|
||||
|
||||
signed_constraint lit2cnstr(sat::literal lit) const { return m_constraints.lookup(lit); }
|
||||
|
||||
|
@ -393,6 +411,9 @@ namespace polysat {
|
|||
signed_constraint diseq(pdd const& p, pdd const& q) { return diseq(p - q); }
|
||||
signed_constraint eq(pdd const& p, rational const& q) { return eq(p - q); }
|
||||
signed_constraint eq(pdd const& p, unsigned q) { return eq(p - q); }
|
||||
signed_constraint odd(pdd const& p) { return ~even(p); }
|
||||
signed_constraint even(pdd const& p) { return parity(p, 1); }
|
||||
signed_constraint parity(pdd const& p, unsigned k) { return eq(p*rational::power_of_two(p.manager().power_of_2() - k)); }
|
||||
signed_constraint diseq(pdd const& p, rational const& q) { return diseq(p - q); }
|
||||
signed_constraint diseq(pdd const& p, unsigned q) { return diseq(p - q); }
|
||||
signed_constraint ule(pdd const& p, pdd const& q) { return m_constraints.ule(p, q); }
|
||||
|
@ -400,9 +421,13 @@ namespace polysat {
|
|||
signed_constraint ule(rational const& p, pdd const& q) { return ule(q.manager().mk_val(p), q); }
|
||||
signed_constraint ule(pdd const& p, int n) { return ule(p, rational(n)); }
|
||||
signed_constraint ule(int n, pdd const& p) { return ule(rational(n), p); }
|
||||
signed_constraint uge(pdd const& p, pdd const& q) { return ule(q, p); }
|
||||
signed_constraint uge(pdd const& p, rational const& q) { return ule(q, p); }
|
||||
signed_constraint ult(pdd const& p, pdd const& q) { return m_constraints.ult(p, q); }
|
||||
signed_constraint ult(pdd const& p, rational const& q) { return ult(p, p.manager().mk_val(q)); }
|
||||
signed_constraint ult(rational const& p, pdd const& q) { return ult(q.manager().mk_val(p), q); }
|
||||
signed_constraint ult(int p, pdd const& q) { return ult(rational(p), q); }
|
||||
signed_constraint ult(pdd const& p, int q) { return ult(p, rational(q)); }
|
||||
signed_constraint sle(pdd const& p, pdd const& q) { return m_constraints.sle(p, q); }
|
||||
signed_constraint slt(pdd const& p, pdd const& q) { return m_constraints.slt(p, q); }
|
||||
signed_constraint slt(pdd const& p, rational const& q) { return slt(p, p.manager().mk_val(q)); }
|
||||
|
@ -418,7 +443,7 @@ namespace polysat {
|
|||
signed_constraint smul_udfl(pdd const& p, pdd const& q) { return m_constraints.smul_udfl(p, q); }
|
||||
signed_constraint bit(pdd const& p, unsigned i) { return m_constraints.bit(p, i); }
|
||||
|
||||
/** Create and activate polynomial constraints. */
|
||||
/** Create and activate constraints */
|
||||
void add_eq(pdd const& p, dependency dep = null_dependency) { assign_eh(eq(p), dep); }
|
||||
void add_eq(pdd const& p, pdd const& q, dependency dep = null_dependency) { assign_eh(eq(p, q), dep); }
|
||||
void add_eq(pdd const& p, rational const& q, dependency dep = null_dependency) { assign_eh(eq(p, q), dep); }
|
||||
|
|
|
@ -72,23 +72,19 @@ namespace polysat {
|
|||
SASSERT(c1.is_currently_true(s));
|
||||
SASSERT(c2.is_currently_false(s));
|
||||
SASSERT_EQ(c1.bvalue(s), l_true);
|
||||
SASSERT(c2.bvalue(s) != l_false);
|
||||
SASSERT_EQ(c2.bvalue(s), l_true);
|
||||
|
||||
signed_constraint c = resolve1(v, c1, c2);
|
||||
if (!c)
|
||||
continue;
|
||||
SASSERT(c.is_currently_false(s));
|
||||
|
||||
// char const* inf_name = "?";
|
||||
switch (c.bvalue(s)) {
|
||||
case l_false:
|
||||
core.add_lemma({c, ~c1, ~c2});
|
||||
// core.log_inference(inference_sup("l_false", v, c2, c1));
|
||||
core.add_lemma("superposition l_false", {c, ~c1, ~c2});
|
||||
return l_true;
|
||||
case l_undef:
|
||||
// inf_name = "l_undef";
|
||||
core.add_lemma({c, ~c1, ~c2});
|
||||
// core.log_inference(inference_sup("l_undef lemma", v, c2, c1));
|
||||
core.add_lemma("superposition l_undef", {c, ~c1, ~c2});
|
||||
break;
|
||||
case l_true:
|
||||
break;
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace polysat {
|
|||
qhead_i,
|
||||
lemma_qhead_i,
|
||||
add_lemma_i,
|
||||
pwatch_i,
|
||||
// pwatch_i,
|
||||
add_var_i,
|
||||
inc_level_i,
|
||||
viable_add_i,
|
||||
|
|
|
@ -28,6 +28,9 @@ namespace polysat {
|
|||
class solver;
|
||||
class clause;
|
||||
|
||||
using clause_ref = ref<clause>;
|
||||
using clause_ref_vector = sref_vector<clause>;
|
||||
|
||||
typedef dd::pdd pdd;
|
||||
typedef dd::bdd bdd;
|
||||
typedef dd::bddv bddv;
|
||||
|
@ -47,7 +50,10 @@ namespace polysat {
|
|||
inline const dependency null_dependency = dependency(UINT_MAX);
|
||||
typedef svector<dependency> dependency_vector;
|
||||
|
||||
inline bool operator<(dependency const& d1, dependency const& d2) { return d1.val() < d2.val(); }
|
||||
inline bool operator< (dependency const& d1, dependency const& d2) { return d1.val() < d2.val(); }
|
||||
inline bool operator<=(dependency const& d1, dependency const& d2) { return d1.val() <= d2.val(); }
|
||||
inline bool operator> (dependency const& d1, dependency const& d2) { return d1.val() > d2.val(); }
|
||||
inline bool operator>=(dependency const& d1, dependency const& d2) { return d1.val() >= d2.val(); }
|
||||
inline bool operator==(dependency const& d1, dependency const& d2) { return d1.val() == d2.val(); }
|
||||
inline bool operator!=(dependency const& d1, dependency const& d2) { return d1.val() != d2.val(); }
|
||||
|
||||
|
|
|
@ -87,12 +87,25 @@ namespace {
|
|||
lhs = 0, rhs = 0, is_positive = !is_positive;
|
||||
return;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// !(k <= p) -> p <= k - 1
|
||||
|
||||
if (!is_positive && lhs.is_val() && lhs.val() > 0) {
|
||||
pdd k = lhs;
|
||||
lhs = rhs;
|
||||
rhs = k - 1;
|
||||
is_positive = true;
|
||||
}
|
||||
#endif
|
||||
// k <= p --> p - k <= - k - 1
|
||||
if (lhs.is_val()) {
|
||||
pdd k = lhs;
|
||||
lhs = rhs - k;
|
||||
rhs = - k - 1;
|
||||
}
|
||||
// NSB: why this?
|
||||
|
||||
// p > -2 --> p + 1 <= 0
|
||||
// p <= -2 --> p + 1 > 0
|
||||
if (rhs.is_val() && (rhs + 2).is_zero()) {
|
||||
|
@ -162,7 +175,7 @@ namespace polysat {
|
|||
}
|
||||
|
||||
std::ostream& ule_constraint::display(std::ostream& out) const {
|
||||
return out << m_lhs << (is_eq() ? " == " : " <= ") << m_rhs;
|
||||
return display(out, l_true, m_lhs, m_rhs);
|
||||
}
|
||||
|
||||
void ule_constraint::narrow(solver& s, bool is_positive, bool first) {
|
||||
|
@ -187,6 +200,15 @@ namespace polysat {
|
|||
}
|
||||
|
||||
s.m_viable.intersect(p, q, sc);
|
||||
|
||||
if (first && !is_positive) {
|
||||
if (!p.is_val())
|
||||
// -1 > q
|
||||
s.add_clause(~sc, s.ult(q, -1), false);
|
||||
if (!q.is_val())
|
||||
// p > 0
|
||||
s.add_clause(~sc, s.ult(0, q), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate lhs <= rhs
|
||||
|
@ -215,13 +237,6 @@ namespace polysat {
|
|||
return eval(a.apply_to(lhs()), a.apply_to(rhs()));
|
||||
}
|
||||
|
||||
inequality ule_constraint::as_inequality(bool is_positive) const {
|
||||
if (is_positive)
|
||||
return inequality(lhs(), rhs(), false, this);
|
||||
else
|
||||
return inequality(rhs(), lhs(), true, this);
|
||||
}
|
||||
|
||||
unsigned ule_constraint::hash() const {
|
||||
return mk_mix(lhs().hash(), rhs().hash(), kind());
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ namespace polysat {
|
|||
lbool eval() const override;
|
||||
lbool eval(assignment const& a) const override;
|
||||
void narrow(solver& s, bool is_positive, bool first) override;
|
||||
inequality as_inequality(bool is_positive) const override;
|
||||
unsigned hash() const override;
|
||||
bool operator==(constraint const& other) const override;
|
||||
bool is_eq() const override { return m_rhs.is_zero(); }
|
||||
|
|
|
@ -137,7 +137,6 @@ namespace polysat {
|
|||
return s.m_viable.intersect(p0, q0, sc);
|
||||
}
|
||||
|
||||
|
||||
unsigned umul_ovfl_constraint::hash() const {
|
||||
return mk_mix(p().hash(), q().hash(), kind());
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ namespace polysat {
|
|||
lbool eval(assignment const& a) const override;
|
||||
void narrow(solver& s, bool is_positive, bool first) override;
|
||||
|
||||
inequality as_inequality(bool is_positive) const override { throw default_exception("is not an inequality"); }
|
||||
unsigned hash() const override;
|
||||
bool operator==(constraint const& other) const override;
|
||||
bool is_eq() const override { return false; }
|
||||
|
|
|
@ -390,7 +390,7 @@ namespace polysat {
|
|||
cb.insert(c_new);
|
||||
ref<clause> c = cb.build();
|
||||
if (c) // Can we get tautologies this way?
|
||||
core.add_lemma(c);
|
||||
core.add_lemma("variable elimination", cb.build());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ namespace polysat {
|
|||
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;
|
||||
bool first = true;
|
||||
bool prop = false;
|
||||
|
@ -205,7 +205,7 @@ namespace polysat {
|
|||
}
|
||||
|
||||
bool viable::intersect(pvar v, entry* ne) {
|
||||
SASSERT(!s.is_assigned(v));
|
||||
// SASSERT(!s.is_assigned(v)); // TODO: do we get unsoundness if this condition is violated? (see comment on cyclic dependencies in solver::pop_levels)
|
||||
entry* e = m_units[v];
|
||||
if (e && e->interval.is_full()) {
|
||||
m_alloc.push_back(ne);
|
||||
|
@ -726,7 +726,7 @@ namespace polysat {
|
|||
while (e != first);
|
||||
|
||||
SASSERT(all_of(lemma, [this](sat::literal lit) { return s.m_bvars.value(lit) == l_false; }));
|
||||
core.add_lemma(lemma.build());
|
||||
core.add_lemma("viable", lemma.build());
|
||||
core.logger().log(inf_fi(*this, v));
|
||||
return true;
|
||||
}
|
||||
|
@ -837,17 +837,17 @@ namespace polysat {
|
|||
m_constraints[v].pop_back();
|
||||
}
|
||||
|
||||
bool viable_fallback::check_constraints(pvar v) {
|
||||
for (auto const& c : m_constraints[v]) {
|
||||
signed_constraint viable_fallback::find_violated_constraint(assignment const& a, pvar v) {
|
||||
for (signed_constraint const c : m_constraints[v]) {
|
||||
// for this check, all variables need to be assigned
|
||||
DEBUG_CODE(for (pvar w : c->vars()) { SASSERT(s.is_assigned(w)); });
|
||||
if (c.is_currently_false(s)) {
|
||||
LOG(assignment_pp(s, v, s.get_value(v)) << " violates constraint " << lit_pp(s, c));
|
||||
return false;
|
||||
DEBUG_CODE(for (pvar w : c->vars()) { SASSERT(a.contains(w)); });
|
||||
if (c.is_currently_false(a)) {
|
||||
LOG(assignment_pp(s, v, a.value(v)) << " violates constraint " << lit_pp(s, c));
|
||||
return c;
|
||||
}
|
||||
SASSERT(c.is_currently_true(s));
|
||||
SASSERT(c.is_currently_true(a));
|
||||
}
|
||||
return true;
|
||||
return {};
|
||||
}
|
||||
|
||||
dd::find_t viable_fallback::find_viable(pvar v, rational& out_val) {
|
||||
|
@ -869,6 +869,7 @@ namespace polysat {
|
|||
|
||||
auto const& cs = m_constraints[v];
|
||||
for (unsigned i = cs.size(); i-- > 0; ) {
|
||||
LOG("Univariate constraint: " << cs[i]);
|
||||
cs[i].add_to_univariate_solver(s, *us, i);
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,11 @@ namespace polysat {
|
|||
*/
|
||||
bool intersect(pvar v, signed_constraint const& c);
|
||||
|
||||
bool intersect(pdd const & p, pdd const & q, signed_constraint const& c);
|
||||
/**
|
||||
* Extract remaining variable v from p and q and try updating viable state for v.
|
||||
* NOTE: does not require a particular constraint type (e.g., we call this for ule_constraint and umul_ovfl_constraint)
|
||||
*/
|
||||
bool intersect(pdd const& p, pdd const& q, signed_constraint const& c);
|
||||
|
||||
/**
|
||||
* Check whether variable v has any viable values left according to m_viable.
|
||||
|
@ -98,10 +102,9 @@ namespace polysat {
|
|||
bool is_viable(pvar v, rational const& val);
|
||||
|
||||
/*
|
||||
* Extract min and max viable values for v
|
||||
*/
|
||||
* Extract min and max viable values for v
|
||||
*/
|
||||
rational min_viable(pvar v);
|
||||
|
||||
rational max_viable(pvar v);
|
||||
|
||||
/**
|
||||
|
@ -245,8 +248,10 @@ namespace polysat {
|
|||
void push_constraint(pvar v, signed_constraint const& c);
|
||||
void pop_constraint();
|
||||
|
||||
// Check whether all constraints for 'v' are satisfied.
|
||||
bool check_constraints(pvar v);
|
||||
// Check whether all constraints for 'v' are satisfied;
|
||||
// or find an arbitrary violated constraint.
|
||||
bool check_constraints(assignment const& a, pvar v) { return !find_violated_constraint(a, v); }
|
||||
signed_constraint find_violated_constraint(assignment const& a, pvar v);
|
||||
|
||||
dd::find_t find_viable(pvar v, rational& out_val);
|
||||
signed_constraints unsat_core(pvar v);
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include <vector>
|
||||
#include <signal.h>
|
||||
|
||||
// TODO: collect stats on how often each inference rule is used, so we can see which ones are useful or if any are useless/untested
|
||||
|
||||
namespace {
|
||||
using namespace dd;
|
||||
|
||||
|
@ -78,92 +80,153 @@ namespace polysat {
|
|||
error,
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
T* with_default(T* value, T* default_value) {
|
||||
return value ? value : default_value;
|
||||
}
|
||||
|
||||
struct test_record {
|
||||
std::string m_name;
|
||||
unsigned m_index = 0; ///< m_index-th check_sat() call in this unit test.
|
||||
lbool m_answer; ///< what the solver returned
|
||||
lbool m_answer = l_undef; ///< what the solver returned
|
||||
lbool m_expected = l_undef; ///< the answer we expect (l_undef if unspecified)
|
||||
test_result m_result = test_result::undefined;
|
||||
std::string m_error_message;
|
||||
bool m_finished = false;
|
||||
|
||||
using clock_t = std::chrono::steady_clock;
|
||||
clock_t::time_point start;
|
||||
clock_t::time_point end;
|
||||
clock_t::time_point m_start;
|
||||
clock_t::time_point m_end;
|
||||
|
||||
void set_error(char const* msg) {
|
||||
m_result = test_result::error;
|
||||
m_error_message = msg;
|
||||
m_finished = true;
|
||||
}
|
||||
|
||||
std::ostream& display(std::ostream& out, unsigned name_width = 0) const;
|
||||
};
|
||||
|
||||
vector<test_record> test_records;
|
||||
bool collect_test_records = true;
|
||||
std::ostream& operator<<(std::ostream& out, test_record const& r) { return r.display(out); }
|
||||
|
||||
void display_test_records(vector<test_record> const& recs, std::ostream& out) {
|
||||
out << "\n\nTest Results:\n";
|
||||
class test_record_manager {
|
||||
scoped_ptr_vector<test_record> m_records;
|
||||
|
||||
std::string m_name;
|
||||
unsigned m_index = 0;
|
||||
|
||||
public:
|
||||
void begin_batch(std::string name);
|
||||
void end_batch();
|
||||
test_record* new_record();
|
||||
test_record* active_or_new_record();
|
||||
void display(std::ostream& out) const;
|
||||
};
|
||||
|
||||
void test_record_manager::begin_batch(std::string name) {
|
||||
end_batch();
|
||||
if (m_name != name) {
|
||||
m_name = name;
|
||||
m_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void test_record_manager::end_batch() {
|
||||
// Kick out unfinished records (they have the wrong name)
|
||||
while (!m_records.empty() && !m_records.back()->m_finished)
|
||||
m_records.pop_back();
|
||||
}
|
||||
|
||||
test_record* test_record_manager::new_record() {
|
||||
auto* rec = alloc(test_record);
|
||||
rec->m_name = m_name;
|
||||
rec->m_index = ++m_index;
|
||||
m_records.push_back(rec);
|
||||
return rec;
|
||||
}
|
||||
|
||||
test_record* test_record_manager::active_or_new_record() {
|
||||
if (m_records.empty() || m_records.back()->m_finished)
|
||||
return new_record();
|
||||
else
|
||||
return m_records.back();
|
||||
}
|
||||
|
||||
std::ostream& test_record::display(std::ostream& out, unsigned name_width) const {
|
||||
if (!m_finished)
|
||||
out << "UNFINISHED ";
|
||||
out << m_name;
|
||||
if (m_index != 1)
|
||||
out << " (" << m_index << ") ";
|
||||
else
|
||||
out << " ";
|
||||
for (size_t i = m_name.length(); i < name_width; ++i)
|
||||
out << ' ';
|
||||
std::chrono::duration<double> d = m_end - m_start;
|
||||
if (d.count() >= 0.15) {
|
||||
out << std::fixed << std::setprecision(1);
|
||||
out << std::setw(4) << d.count() << "s ";
|
||||
}
|
||||
else
|
||||
out << " ";
|
||||
switch (m_answer) {
|
||||
case l_undef: out << " "; break;
|
||||
case l_true: out << "SAT "; break;
|
||||
case l_false: out << "UNSAT "; break;
|
||||
}
|
||||
switch (m_result) {
|
||||
case test_result::undefined: out << "???"; break;
|
||||
case test_result::ok: out << "ok"; break;
|
||||
case test_result::wrong_answer: out << color_red() << "wrong answer, expected " << m_expected << color_reset(); break;
|
||||
case test_result::wrong_model: out << color_red() << "wrong model" << color_reset(); break;
|
||||
case test_result::resource_out: out << color_yellow() << "resource out" << color_reset(); break;
|
||||
case test_result::error: out << color_red() << "error: " << m_error_message << color_reset(); break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void test_record_manager::display(std::ostream& out) const {
|
||||
out << "\n\n\nTest Results:\n\n";
|
||||
|
||||
size_t max_name_len = 0;
|
||||
for (test_record const& r : recs)
|
||||
max_name_len = std::max(max_name_len, r.m_name.length());
|
||||
for (test_record const* r : m_records) {
|
||||
if (!r->m_finished)
|
||||
continue;
|
||||
max_name_len = std::max(max_name_len, r->m_name.length());
|
||||
}
|
||||
|
||||
size_t n_total = recs.size();
|
||||
size_t n_total = m_records.size();
|
||||
size_t n_sat = 0;
|
||||
size_t n_unsat = 0;
|
||||
size_t n_wrong = 0;
|
||||
size_t n_error = 0;
|
||||
|
||||
for (test_record const& r : recs) {
|
||||
out << r.m_name;
|
||||
if (r.m_index != 1)
|
||||
out << " (" << r.m_index << ") ";
|
||||
else
|
||||
out << " ";
|
||||
for (size_t i = r.m_name.length(); i < max_name_len; ++i)
|
||||
out << ' ';
|
||||
std::chrono::duration<double> d = r.end - r.start;
|
||||
if (d.count() >= 0.15) {
|
||||
out << std::fixed << std::setprecision(1);
|
||||
out << std::setw(4) << d.count() << "s ";
|
||||
}
|
||||
else
|
||||
out << " ";
|
||||
switch (r.m_answer) {
|
||||
case l_undef: out << " "; break;
|
||||
case l_true: out << "SAT "; break;
|
||||
case l_false: out << "UNSAT "; break;
|
||||
}
|
||||
switch (r.m_result) {
|
||||
case test_result::undefined:
|
||||
out << "???";
|
||||
break;
|
||||
case test_result::ok:
|
||||
out << "ok";
|
||||
if (r.m_answer == l_true)
|
||||
n_sat++;
|
||||
if (r.m_answer == l_false)
|
||||
n_unsat++;
|
||||
break;
|
||||
case test_result::wrong_answer:
|
||||
out << color_red() << "wrong answer, expected " << r.m_expected << color_reset();
|
||||
n_wrong++;
|
||||
break;
|
||||
case test_result::wrong_model:
|
||||
out << color_red() << "wrong model" << color_reset();
|
||||
n_wrong++;
|
||||
break;
|
||||
case test_result::resource_out:
|
||||
out << color_yellow() << "resource out" << color_reset();
|
||||
break;
|
||||
case test_result::error:
|
||||
out << color_red() << "error: " << r.m_error_message << color_reset();
|
||||
n_error++;
|
||||
break;
|
||||
}
|
||||
for (test_record const* r : m_records) {
|
||||
if (!r->m_finished)
|
||||
continue;
|
||||
r->display(out, max_name_len);
|
||||
out << std::endl;
|
||||
if (r->m_result == test_result::ok && r->m_answer == l_true)
|
||||
n_sat++;
|
||||
if (r->m_result == test_result::ok && r->m_answer == l_false)
|
||||
n_unsat++;
|
||||
if (r->m_result == test_result::wrong_answer || r->m_result == test_result::wrong_model)
|
||||
n_wrong++;
|
||||
if (r->m_result == test_result::error)
|
||||
n_error++;
|
||||
}
|
||||
out << n_total << " tests, " << (n_sat + n_unsat) << " ok (" << n_sat << " sat, " << n_unsat << " unsat)";
|
||||
out << "\n" << n_total << " tests, " << (n_sat + n_unsat) << " ok (" << n_sat << " sat, " << n_unsat << " unsat)";
|
||||
if (n_wrong)
|
||||
out << ", " << color_red() << n_wrong << " wrong!" << color_reset();
|
||||
if (n_error)
|
||||
out << ", " << color_red() << n_error << " error" << color_reset();
|
||||
out << std::endl;
|
||||
out << "\n" << std::endl;
|
||||
}
|
||||
|
||||
test_record_manager test_records;
|
||||
bool collect_test_records = true;
|
||||
unsigned test_max_conflicts = 10;
|
||||
|
||||
// test resolve, factoring routines
|
||||
// auxiliary
|
||||
|
||||
|
@ -174,13 +237,26 @@ namespace polysat {
|
|||
class scoped_solver : public solver_scope, public solver {
|
||||
std::string m_name;
|
||||
lbool m_last_result = l_undef;
|
||||
test_record* m_record = nullptr;
|
||||
unsigned m_record_idx = 0;
|
||||
test_record* m_last_finished = nullptr;
|
||||
|
||||
public:
|
||||
scoped_solver(std::string name): solver(lim), m_name(name) {
|
||||
std::cout << std::string(78, '#') << "\n\nSTART: " << m_name << "\n";
|
||||
set_max_conflicts(10);
|
||||
if (collect_test_records) {
|
||||
std::cout << m_name << ":";
|
||||
std::cout.flush();
|
||||
} else {
|
||||
std::cout << std::string(78, '#') << "\n\n";
|
||||
std::cout << "START: " << m_name << "\n";
|
||||
}
|
||||
set_max_conflicts(test_max_conflicts);
|
||||
|
||||
test_records.begin_batch(name);
|
||||
}
|
||||
|
||||
~scoped_solver() {
|
||||
test_records.end_batch();
|
||||
if (collect_test_records)
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void set_max_conflicts(unsigned c) {
|
||||
|
@ -190,69 +266,49 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void check() {
|
||||
test_records.push_back({});
|
||||
m_record = &test_records.back();
|
||||
m_record->m_name = m_name;
|
||||
m_record->m_index = ++m_record_idx;
|
||||
m_record->m_answer = l_undef;
|
||||
m_record->m_expected = l_undef;
|
||||
m_record->m_result = test_result::undefined;
|
||||
m_record->m_error_message = "";
|
||||
try {
|
||||
m_record->start = test_record::clock_t::now();
|
||||
if (collect_test_records) {
|
||||
std::cout << " ...";
|
||||
std::cout.flush();
|
||||
}
|
||||
auto* rec = test_records.active_or_new_record();
|
||||
m_last_finished = nullptr;
|
||||
SASSERT(rec->m_answer == l_undef);
|
||||
SASSERT(rec->m_expected == l_undef);
|
||||
SASSERT(rec->m_result == test_result::undefined);
|
||||
SASSERT(rec->m_error_message == "");
|
||||
SASSERT(!rec->m_finished);
|
||||
{
|
||||
rec->m_start = test_record::clock_t::now();
|
||||
on_scope_exit end_timer([rec]() {
|
||||
rec->m_end = test_record::clock_t::now();
|
||||
});
|
||||
m_last_result = check_sat();
|
||||
m_record->end = test_record::clock_t::now();
|
||||
std::cout << "DONE: " << m_name << ": " << m_last_result << "\n";
|
||||
statistics st;
|
||||
collect_statistics(st);
|
||||
}
|
||||
rec->m_finished = true;
|
||||
m_last_finished = rec;
|
||||
if (collect_test_records)
|
||||
std::cout << " " << m_last_result;
|
||||
else
|
||||
std::cout << "DONE: " << m_name << ": " << m_last_result << "\n";
|
||||
statistics st;
|
||||
collect_statistics(st);
|
||||
if (!collect_test_records)
|
||||
std::cout << st << "\n";
|
||||
|
||||
m_record->m_answer = m_last_result;
|
||||
m_record->m_result = (m_last_result == l_undef) ? test_result::resource_out : test_result::ok;
|
||||
}
|
||||
catch(z3_exception const& e) {
|
||||
m_record->end = test_record::clock_t::now();
|
||||
char const* msg = e.msg();
|
||||
if (!msg)
|
||||
msg = "(unknown z3_exception)";
|
||||
std::cout << "\n\nEXCEPTION (z3_exception) during test: " << m_name << ": " << msg << "\n\n";
|
||||
m_record->m_result = test_result::error;
|
||||
m_record->m_error_message = msg;
|
||||
if (!collect_test_records)
|
||||
throw;
|
||||
}
|
||||
catch(std::exception const& e) {
|
||||
m_record->end = test_record::clock_t::now();
|
||||
char const* msg = e.what();
|
||||
if (!msg)
|
||||
msg = "(unknown std::exception)";
|
||||
std::cout << "\n\nEXCEPTION (std::exception) during test: " << m_name << ": " << msg << "\n\n";
|
||||
m_record->m_result = test_result::error;
|
||||
m_record->m_error_message = msg;
|
||||
if (!collect_test_records)
|
||||
throw;
|
||||
}
|
||||
catch(...) {
|
||||
m_record->end = test_record::clock_t::now();
|
||||
char const* msg = "(unknown throwable)";
|
||||
std::cout << "\n\nEXCEPTION (unknown throwable) during test: " << m_name << ": " << msg << "\n\n";
|
||||
m_record->m_result = test_result::error;
|
||||
m_record->m_error_message = msg;
|
||||
if (!collect_test_records)
|
||||
throw;
|
||||
}
|
||||
rec->m_answer = m_last_result;
|
||||
rec->m_result = (m_last_result == l_undef) ? test_result::resource_out : test_result::ok;
|
||||
}
|
||||
// TODO: all unit tests should save result somewhere, and then at the end we print a table
|
||||
// Highlight undef results
|
||||
// If low conflict limit doesn't work, increase automatically and try again?
|
||||
|
||||
void expect_unsat() {
|
||||
SASSERT_EQ(m_record->m_expected, l_undef);
|
||||
m_record->m_expected = l_false;
|
||||
if (m_record->m_result == test_result::error)
|
||||
auto* rec = m_last_finished;
|
||||
SASSERT(rec);
|
||||
SASSERT_EQ(rec->m_expected, l_undef);
|
||||
SASSERT_EQ(rec->m_answer, m_last_result);
|
||||
rec->m_expected = l_false;
|
||||
if (rec->m_result == test_result::error)
|
||||
return;
|
||||
if (m_last_result != l_false && m_last_result != l_undef) {
|
||||
m_record->m_result = test_result::wrong_answer;
|
||||
rec->m_result = test_result::wrong_answer;
|
||||
LOG_H1("FAIL: " << m_name << ": expected UNSAT, got " << m_last_result << "!");
|
||||
if (!collect_test_records)
|
||||
VERIFY(false);
|
||||
|
@ -260,9 +316,12 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void expect_sat(std::vector<std::pair<dd::pdd, unsigned>> const& expected_assignment = {}) {
|
||||
SASSERT_EQ(m_record->m_expected, l_undef);
|
||||
m_record->m_expected = l_true;
|
||||
if (m_record->m_result == test_result::error)
|
||||
auto* rec = m_last_finished;
|
||||
SASSERT(rec);
|
||||
SASSERT_EQ(rec->m_expected, l_undef);
|
||||
SASSERT_EQ(rec->m_answer, m_last_result);
|
||||
rec->m_expected = l_true;
|
||||
if (rec->m_result == test_result::error)
|
||||
return;
|
||||
if (m_last_result == l_true) {
|
||||
for (auto const& p : expected_assignment) {
|
||||
|
@ -271,7 +330,7 @@ namespace polysat {
|
|||
SASSERT(v_pdd.is_monomial() && !v_pdd.is_val());
|
||||
auto const v = v_pdd.var();
|
||||
if (get_value(v) != expected_value) {
|
||||
m_record->m_result = test_result::wrong_model;
|
||||
rec->m_result = test_result::wrong_model;
|
||||
LOG_H1("FAIL: " << m_name << ": expected assignment v" << v << " := " << expected_value << ", got value " << get_value(v) << "!");
|
||||
if (!collect_test_records)
|
||||
VERIFY(false);
|
||||
|
@ -279,7 +338,7 @@ namespace polysat {
|
|||
}
|
||||
}
|
||||
else if (m_last_result == l_false) {
|
||||
m_record->m_result = test_result::wrong_answer;
|
||||
rec->m_result = test_result::wrong_answer;
|
||||
LOG_H1("FAIL: " << m_name << ": expected SAT, got " << m_last_result << "!");
|
||||
if (!collect_test_records)
|
||||
VERIFY(false);
|
||||
|
@ -287,29 +346,37 @@ namespace polysat {
|
|||
}
|
||||
};
|
||||
|
||||
std::string run_filter;
|
||||
|
||||
template <typename Test, typename... Args>
|
||||
void run(Test tst, Args... args)
|
||||
void run(char const* name, Test tst, Args... args)
|
||||
{
|
||||
bool got_exception = false;
|
||||
if (!run_filter.empty()) {
|
||||
std::string sname = name;
|
||||
if (sname.find(run_filter) == std::string::npos)
|
||||
return;
|
||||
}
|
||||
try {
|
||||
tst(args...);
|
||||
}
|
||||
catch(z3_exception const& e) {
|
||||
// TODO: collect in record
|
||||
got_exception = true;
|
||||
test_records.active_or_new_record()->set_error(with_default(e.msg(), "unknown z3_exception"));
|
||||
if (!collect_test_records)
|
||||
throw;
|
||||
}
|
||||
catch(std::exception const& e) {
|
||||
// TODO: collect in record
|
||||
got_exception = true;
|
||||
test_records.active_or_new_record()->set_error(with_default(e.what(), "unknown std::exception"));
|
||||
if (!collect_test_records)
|
||||
throw;
|
||||
}
|
||||
catch(...) {
|
||||
got_exception = true;
|
||||
test_records.active_or_new_record()->set_error("unknown throwable");
|
||||
if (!collect_test_records)
|
||||
throw;
|
||||
}
|
||||
if (got_exception && !collect_test_records)
|
||||
throw;
|
||||
}
|
||||
|
||||
#define RUN(tst) run([]() { tst; })
|
||||
#define RUN(tst) run(#tst, []() { tst; })
|
||||
|
||||
class test_polysat {
|
||||
public:
|
||||
|
@ -702,13 +769,12 @@ namespace polysat {
|
|||
*
|
||||
* We do overflow checks by doubling the base bitwidth here.
|
||||
*/
|
||||
static void test_fixed_point_arith_mul_div_inverse() {
|
||||
static void test_fixed_point_arith_mul_div_inverse(unsigned base_bw = 5) {
|
||||
scoped_solver s(__func__);
|
||||
|
||||
auto baseBw = 5;
|
||||
auto max_int_const = 31; // (2^5 - 1) -- change this when you change baseBw
|
||||
auto max_int_const = rational::power_of_two(base_bw) - 1;
|
||||
|
||||
auto bw = 2 * baseBw;
|
||||
auto bw = 2 * base_bw;
|
||||
auto max_int = s.var(s.add_var(bw));
|
||||
s.add_eq(max_int - max_int_const);
|
||||
|
||||
|
@ -720,8 +786,9 @@ namespace polysat {
|
|||
s.add_ult(0, b); // b > 0
|
||||
|
||||
// scaling factor (setting it, somewhat arbitrarily, to max_int/3)
|
||||
auto sf_val = div(max_int_const, rational(3));
|
||||
auto sf = s.var(s.add_var(bw));
|
||||
s.add_eq(sf - (max_int_const/3));
|
||||
s.add_eq(sf - sf_val);
|
||||
|
||||
// (a * b) / sf = quot1 <=> quot1 * sf + rem1 - (a * b) = 0
|
||||
auto quot1 = s.var(s.add_var(bw));
|
||||
|
@ -753,15 +820,14 @@ namespace polysat {
|
|||
s.push();
|
||||
s.add_ult(a, quot3);
|
||||
s.check();
|
||||
s.expect_unsat();
|
||||
// s.expect_unsat();
|
||||
s.pop();
|
||||
|
||||
|
||||
// s.push();
|
||||
// s.add_ult(quot3 + em, a);
|
||||
// s.check();
|
||||
s.push();
|
||||
s.add_ult(quot3 + em, a);
|
||||
s.check();
|
||||
// s.expect_unsat();
|
||||
// s.pop();
|
||||
s.pop();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -832,10 +898,6 @@ namespace polysat {
|
|||
s.check();
|
||||
// s.expect_unsat();
|
||||
s.pop();
|
||||
|
||||
|
||||
|
||||
//exit(0);
|
||||
}
|
||||
|
||||
/** Monotonicity under bounds,
|
||||
|
@ -1024,7 +1086,7 @@ namespace polysat {
|
|||
// xy < xz and !Omega(x*y) => y < z
|
||||
static void test_ineq_axiom1(unsigned bw = 32, std::optional<unsigned> perm = std::nullopt) {
|
||||
if (perm) {
|
||||
scoped_solver s(std::string(__func__) + " perm=" + std::to_string(*perm));
|
||||
scoped_solver s(concat(__func__, " bw=", bw, " perm=", *perm));
|
||||
auto const bound = rational::power_of_two(bw/2);
|
||||
auto x = s.var(s.add_var(bw));
|
||||
auto y = s.var(s.add_var(bw));
|
||||
|
@ -1297,12 +1359,31 @@ namespace polysat {
|
|||
s.expect_sat();
|
||||
}
|
||||
|
||||
/**
|
||||
* p <= p & q
|
||||
* p != p & q
|
||||
* is unsatisfiable.
|
||||
*/
|
||||
static void test_band5(unsigned bw = 32) {
|
||||
scoped_solver s(concat(__func__, " bw=", bw));
|
||||
auto p = s.var(s.add_var(bw));
|
||||
auto q = s.var(s.add_var(bw));
|
||||
s.add_ule(p, s.band(p, q));
|
||||
s.add_diseq(p - s.band(p, q));
|
||||
s.add_diseq(p, s.band(p, q));
|
||||
// Immediate solution with clause: p <= r /\ r <= p ==> p = r
|
||||
// s.add_clause({ ~s.ule(p, s.band(p, q)), ~s.ule(s.band(p, q), p), s.eq(p, s.band(p, q)) }, true);
|
||||
s.check();
|
||||
s.expect_unsat();
|
||||
}
|
||||
|
||||
/** Like test_band5, but represent disequality as clause of less-than constraints */
|
||||
static void test_band5_clause(unsigned bw = 32) {
|
||||
scoped_solver s(concat(__func__, " bw=", bw));
|
||||
auto p = s.var(s.add_var(bw));
|
||||
auto q = s.var(s.add_var(bw));
|
||||
s.add_ule(p, s.band(p, q));
|
||||
// Rewrite p - q > 0 --> p < q || q < p
|
||||
s.add_clause({ s.ult(p, s.band(p, q)), s.ult(s.band(p, q), p) }, false);
|
||||
s.check();
|
||||
s.expect_unsat();
|
||||
}
|
||||
|
@ -1435,6 +1516,8 @@ namespace polysat {
|
|||
VERIFY(false);
|
||||
}
|
||||
}
|
||||
// TODO: now try to extend lo/hi one by one to find out how "bad" our interval really is.
|
||||
// (probably slow so maybe add a flag to enable/disable that.)
|
||||
e = e->next();
|
||||
}
|
||||
while (e != first);
|
||||
|
@ -1500,9 +1583,7 @@ namespace polysat {
|
|||
static void STD_CALL polysat_on_ctrl_c(int) {
|
||||
signal(SIGINT, SIG_DFL);
|
||||
using namespace polysat;
|
||||
if (!test_records.empty())
|
||||
test_records.pop_back(); // last record is likely incomplete
|
||||
display_test_records(test_records, std::cout);
|
||||
test_records.display(std::cout);
|
||||
raise(SIGINT);
|
||||
}
|
||||
|
||||
|
@ -1511,6 +1592,11 @@ void tst_polysat() {
|
|||
|
||||
#if 0 // Enable this block to run a single unit test with detailed output.
|
||||
collect_test_records = false;
|
||||
test_max_conflicts = 50;
|
||||
test_polysat::test_band5();
|
||||
// test_polysat::test_band5_clause();
|
||||
// test_polysat::test_ineq_axiom1(32, 1);
|
||||
// test_polysat::test_pop_conflict();
|
||||
// test_polysat::test_l2();
|
||||
// test_polysat::test_ineq1();
|
||||
// test_polysat::test_quot_rem();
|
||||
|
@ -1519,10 +1605,14 @@ void tst_polysat() {
|
|||
// test_polysat::test_band2();
|
||||
// test_polysat::test_quot_rem_incomplete();
|
||||
// test_polysat::test_monot();
|
||||
test_polysat::test_fixed_point_arith_div_mul_inverse();
|
||||
// test_polysat::test_fixed_point_arith_div_mul_inverse();
|
||||
return;
|
||||
#endif
|
||||
|
||||
// If non-empty, only run tests whose name contains the run_filter
|
||||
run_filter = "";
|
||||
test_max_conflicts = 10;
|
||||
|
||||
collect_test_records = true;
|
||||
|
||||
if (collect_test_records) {
|
||||
|
@ -1569,6 +1659,7 @@ void tst_polysat() {
|
|||
RUN(test_polysat::test_monot_bounds());
|
||||
RUN(test_polysat::test_monot_bounds_full());
|
||||
RUN(test_polysat::test_monot_bounds_simple(8));
|
||||
RUN(test_polysat::test_fixed_point_arith_mul_div_inverse());
|
||||
RUN(test_polysat::test_fixed_point_arith_div_mul_inverse());
|
||||
|
||||
RUN(test_polysat::test_ineq_axiom1());
|
||||
|
@ -1587,6 +1678,7 @@ void tst_polysat() {
|
|||
RUN(test_polysat::test_band3());
|
||||
RUN(test_polysat::test_band4());
|
||||
RUN(test_polysat::test_band5());
|
||||
RUN(test_polysat::test_band5_clause());
|
||||
RUN(test_polysat::test_quot_rem());
|
||||
|
||||
RUN(test_polysat::test_fi_zero());
|
||||
|
@ -1598,5 +1690,5 @@ void tst_polysat() {
|
|||
// test_fi::randomized();
|
||||
|
||||
if (collect_test_records)
|
||||
display_test_records(test_records, std::cout);
|
||||
test_records.display(std::cout);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ Revision History:
|
|||
#include "util/debug.h"
|
||||
#include "util/util.h"
|
||||
|
||||
#define DLIST_EXTRA_ASSERTIONS 0
|
||||
|
||||
template <typename T> class dll_iterator;
|
||||
|
||||
template <typename T>
|
||||
|
@ -58,7 +60,7 @@ public:
|
|||
}
|
||||
|
||||
void insert_after(T* other) {
|
||||
#ifndef NDEBUG
|
||||
#if DLIST_EXTRA_ASSERTIONS
|
||||
SASSERT(other);
|
||||
SASSERT(invariant());
|
||||
SASSERT(other->invariant());
|
||||
|
@ -74,7 +76,7 @@ public:
|
|||
other->m_prev = static_cast<T*>(this);
|
||||
other_end->m_next = next;
|
||||
next->m_prev = other_end;
|
||||
#ifndef NDEBUG
|
||||
#if DLIST_EXTRA_ASSERTIONS
|
||||
SASSERT(invariant());
|
||||
SASSERT(other->invariant());
|
||||
size_t const new_sz = count_if(*static_cast<T*>(this), [](T const&) { return true; });
|
||||
|
@ -83,7 +85,7 @@ public:
|
|||
}
|
||||
|
||||
void insert_before(T* other) {
|
||||
#ifndef NDEBUG
|
||||
#if DLIST_EXTRA_ASSERTIONS
|
||||
SASSERT(other);
|
||||
SASSERT(invariant());
|
||||
SASSERT(other->invariant());
|
||||
|
@ -99,7 +101,7 @@ public:
|
|||
other->m_prev = prev;
|
||||
other_end->m_next = static_cast<T*>(this);
|
||||
this->m_prev = other_end;
|
||||
#ifndef NDEBUG
|
||||
#if DLIST_EXTRA_ASSERTIONS
|
||||
SASSERT(invariant());
|
||||
SASSERT(other->invariant());
|
||||
size_t const new_sz = count_if(*static_cast<T*>(this), [](T const&) { return true; });
|
||||
|
@ -108,10 +110,12 @@ public:
|
|||
}
|
||||
|
||||
static void remove_from(T*& list, T* elem) {
|
||||
#if DLIST_EXTRA_ASSERTIONS
|
||||
SASSERT(list);
|
||||
SASSERT(elem);
|
||||
SASSERT(list->invariant());
|
||||
SASSERT(elem->invariant());
|
||||
#endif
|
||||
if (list->m_next == list) {
|
||||
SASSERT(elem == list);
|
||||
list = nullptr;
|
||||
|
@ -123,7 +127,9 @@ public:
|
|||
auto* prev = elem->m_prev;
|
||||
prev->m_next = next;
|
||||
next->m_prev = prev;
|
||||
#if DLIST_EXTRA_ASSERTIONS
|
||||
SASSERT(list->invariant());
|
||||
#endif
|
||||
}
|
||||
|
||||
static void push_to_front(T*& list, T* elem) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue