3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-22 16:45:31 +00:00

Merge remote-tracking branch 'Z3Prover/polysat' into polysat

This commit is contained in:
Clemens Eisenhofer 2022-12-25 12:41:39 +01:00
commit 74ec28201e
41 changed files with 2206 additions and 661 deletions

View file

@ -37,6 +37,7 @@ void bv_rewriter::updt_local_params(params_ref const & _p) {
m_ite2id = p.bv_ite2id();
m_le_extra = p.bv_le_extra();
m_le2extract = p.bv_le2extract();
m_le2extract = false; //
set_sort_sums(p.bv_sort_ac());
}
@ -1724,6 +1725,9 @@ br_status bv_rewriter::mk_bv_or(unsigned num, expr * const * args, expr_ref & re
}
}
if (!m_le2extract)
return BR_FAILED;
if (!v1.is_zero() && new_args.size() == 1) {
v1 = m_util.norm(v1, sz);
#ifdef _TRACE

View file

@ -1208,6 +1208,11 @@ namespace dd {
return true;
}
/** Return true iff p contains no variables other than v. */
bool pdd_manager::is_univariate_in(PDD p, unsigned v) {
return (is_val(p) || var(p) == v) && is_univariate(p);
}
/**
* Push coefficients of univariate polynomial in order of ascending degree.
* Example: a*x^2 + b*x + c ==> [ c, b, a ]
@ -1719,7 +1724,7 @@ namespace dd {
unsigned pow;
if (val.is_power_of_two(pow) && pow > 10)
return out << "2^" << pow;
for (int offset : {-1, 1})
for (int offset : {-2, -1, 1, 2})
if (val < m.max_value() && (val - offset).is_power_of_two(pow) && pow > 10)
return out << lparen() << "2^" << pow << (offset >= 0 ? "+" : "") << offset << rparen();
rational neg_val = mod(-val, m.two_to_N());

View file

@ -364,6 +364,7 @@ namespace dd {
bool is_monomial(PDD p);
bool is_univariate(PDD p);
bool is_univariate_in(PDD p, unsigned v);
void get_univariate_coefficients(PDD p, vector<rational>& coeff);
// create an spoly r if leading monomials of a and b overlap
@ -430,6 +431,7 @@ namespace dd {
bool is_binary() const { return m.is_binary(root); }
bool is_monomial() const { return m.is_monomial(root); }
bool is_univariate() const { return m.is_univariate(root); }
bool is_univariate_in(unsigned v) const { return m.is_univariate_in(root, v); }
void get_univariate_coefficients(vector<rational>& coeff) const { m.get_univariate_coefficients(root, coeff); }
vector<rational> get_univariate_coefficients() const { vector<rational> coeff; m.get_univariate_coefficients(root, coeff); return coeff; }
bool is_never_zero() const { return m.is_never_zero(root); }

View file

@ -53,13 +53,13 @@ namespace polysat {
public:
substitution(dd::pdd_manager& m);
substitution add(pvar var, rational const& value) const;
pdd apply_to(pdd const& p) const;
[[nodiscard]] substitution add(pvar var, rational const& value) const;
[[nodiscard]] pdd apply_to(pdd const& p) const;
bool contains(pvar var) const;
bool value(pvar var, rational& out_value) const;
[[nodiscard]] bool contains(pvar var) const;
[[nodiscard]] bool value(pvar var, rational& out_value) const;
bool empty() const { return m_subst.is_one(); }
[[nodiscard]] bool empty() const { return m_subst.is_one(); }
pdd const& to_pdd() const { return m_subst; }
unsigned bit_width() const { return to_pdd().power_of_2(); }
@ -77,7 +77,7 @@ namespace polysat {
/** Full variable assignment, may include variables of varying bit widths. */
class assignment {
solver* m_solver;
vector<assignment_item_t> m_pairs; // TODO: do we still need this?
vector<assignment_item_t> m_pairs;
mutable scoped_ptr_vector<substitution> m_subst;
vector<substitution> m_subst_trail;

View file

@ -72,7 +72,7 @@ namespace polysat {
}
void bool_var_manager::eval(sat::literal lit, unsigned lvl) {
LOG_V("Evaluate " << lit << " @ " << lvl);
LOG_V(10, "Evaluate " << lit << " @ " << lvl);
assign(kind_t::evaluation, lit, lvl, nullptr);
SASSERT(is_evaluation(lit));
}

View file

@ -75,15 +75,23 @@ namespace polysat {
m_literals.push_back(c.blit());
}
void clause_builder::insert_eval(sat::literal lit, bool status) {
insert_eval(m_solver->lit2cnstr(lit), status);
void clause_builder::insert_eval(sat::literal lit) {
insert_eval(m_solver->lit2cnstr(lit));
}
void clause_builder::insert_eval(signed_constraint c, bool status) {
if (c.bvalue(*m_solver) == l_undef) {
sat::literal lit = c.blit();
m_solver->assign_eval(status ? lit : ~lit);
}
void clause_builder::insert_eval(signed_constraint c) {
if (c.bvalue(*m_solver) == l_undef)
m_solver->assign_eval(~c.blit());
insert(c);
}
void clause_builder::insert_try_eval(sat::literal lit) {
insert_eval(m_solver->lit2cnstr(lit));
}
void clause_builder::insert_try_eval(signed_constraint c) {
if (c.bvalue(*m_solver) == l_undef && c.is_currently_false(*m_solver))
m_solver->assign_eval(~c.blit());
insert(c);
}
}

View file

@ -51,19 +51,20 @@ namespace polysat {
void insert(signed_constraint c);
void insert(inequality const& i) { insert(i.as_signed_constraint()); }
/// Insert constraints into the clause.
/// If they are not yet on the search stack, add them as evaluated to the given status.
/// \pre Constraint must evaluate to true or false according to the given status in the current assignment.
void insert_eval(sat::literal lit, bool status);
void insert_eval(signed_constraint c, bool status);
/// Insert constraints into the clause.
/// If they are not yet on the search stack, add them as evaluated to \b false.
/// \pre Constraint must be \b false in the current assignment.
void insert_eval(sat::literal lit) { insert_eval(lit, false); }
void insert_eval(signed_constraint c) { insert_eval(c, false); }
void insert_eval(sat::literal lit);
void insert_eval(signed_constraint c);
void insert_eval(inequality const& i) { insert_eval(i.as_signed_constraint()); }
/// Insert constraints into the clause.
/// If possible, evaluate them as in insert_eval.
/// Evaluation might not be possible if not all variables in the constraint are assigned.
/// \pre Constraint must be \b non-true in the current assignment.
void insert_try_eval(sat::literal lit);
void insert_try_eval(signed_constraint lit);
using const_iterator = decltype(m_literals)::const_iterator;
const_iterator begin() const { return m_literals.begin(); }
const_iterator end() const { return m_literals.end(); }

View file

@ -186,6 +186,8 @@ namespace polysat {
SASSERT(s.at_base_level());
m_level = s.m_level;
SASSERT(!empty());
// TODO: logger().begin_conflict???
// TODO: check uses of logger().begin_conflict(). sometimes we call it before adding constraints, sometimes after...
}
void conflict::init(signed_constraint c) {
@ -193,17 +195,6 @@ namespace polysat {
SASSERT(empty());
m_level = s.m_level;
m_narrow_queue.push_back(c.blit()); // if the conflict is only due to a missed propagation of c
set_impl(c);
logger().begin_conflict();
}
void conflict::set(signed_constraint c) {
SASSERT(!empty());
remove_all();
set_impl(c);
}
void conflict::set_impl(signed_constraint c) {
if (c.bvalue(s) == l_false) {
// boolean conflict
// This case should not happen:
@ -219,6 +210,7 @@ namespace polysat {
insert_vars(c);
}
SASSERT(!empty());
logger().begin_conflict();
}
void conflict::init(clause const& cl) {
@ -234,31 +226,24 @@ namespace polysat {
logger().begin_conflict();
}
void conflict::init(pvar v, bool by_viable_fallback) {
LOG("Conflict: viable v" << v);
void conflict::init_by_viable_interval(pvar v) {
LOG("Conflict: viable_interval v" << v);
SASSERT(empty());
SASSERT(!s.is_assigned(v));
m_level = s.m_level;
if (by_viable_fallback) {
logger().begin_conflict(header_with_var("unsat core from viable fallback for v", v));
// Conflict detected by viable fallback:
// initial conflict is the unsat core of the univariate solver,
// and the assignment (under which the constraints are univariate in v)
// TODO:
// - currently we add variables directly, which is sound:
// e.g.: v^2 + w^2 == 0; w := 1
// - but we could use side constraints on the coefficients instead (coefficients when viewed as polynomial over v):
// e.g.: v^2 + w^2 == 0; w^2 == 1
signed_constraints unsat_core = s.m_viable_fallback.unsat_core(v);
for (auto c : unsat_core) {
insert(c);
insert_vars(c);
}
SASSERT(!m_vars.contains(v));
}
else {
logger().begin_conflict(header_with_var("forbidden interval lemma for v", v));
VERIFY(s.m_viable.resolve(v, *this));
}
logger().begin_conflict(header_with_var("viable_interval v", v));
VERIFY(s.m_viable.resolve_interval(v, *this));
SASSERT(!empty());
revert_pvar(v); // at this point, v is not assigned
}
void conflict::init_by_viable_fallback(pvar v, univariate_solver& us) {
LOG("Conflict: viable_fallback v" << v);
SASSERT(empty());
SASSERT(!s.is_assigned(v));
m_level = s.m_level;
logger().begin_conflict(header_with_var("viable_fallback v", v));
VERIFY(s.m_viable.resolve_fallback(v, us, *this));
SASSERT(!empty());
revert_pvar(v); // at this point, v is not assigned
}
@ -318,8 +303,14 @@ namespace polysat {
}
void conflict::add_lemma(char const* name, clause_ref lemma) {
for (auto lit : *lemma)
if (s.m_bvars.is_true(lit))
verbose_stream() << "REDUNDANT lemma " << lit << " : " << show_deref(lemma) << "\n";
LOG_H3("Lemma " << (name ? name : "<unknown>") << ": " << show_deref(lemma));
SASSERT(lemma);
s.m_simplify_clause.apply(*lemma);
lemma->set_redundant(true);
for (sat::literal lit : *lemma) {
LOG(lit_pp(s, lit));

View file

@ -85,7 +85,7 @@ namespace polysat {
scoped_ptr<conflict_resolver> m_resolver;
// current conflict core consists of m_literals and m_vars
indexed_uint_set m_literals; // set of boolean literals in the conflict
indexed_uint_set m_literals; // set of boolean literals in the conflict; TODO: why not sat::literal_set
uint_set m_vars; // variable assignments used as premises, shorthand for literals (x := v)
unsigned_vector m_var_occurrences; // for each variable, the number of constraints in m_literals that contain it
@ -102,8 +102,6 @@ namespace polysat {
// Level at which the conflict was discovered
unsigned m_level = UINT_MAX;
void set_impl(signed_constraint c);
public:
conflict(solver& s);
~conflict();
@ -133,11 +131,10 @@ namespace polysat {
void init(signed_constraint c);
/** boolean conflict with the given clause */
void init(clause const& cl);
/** conflict because there is no viable value for the variable v */
void init(pvar v, bool by_viable_fallback);
/** replace the current conflict by a single constraint */
void set(signed_constraint c);
/** conflict because there is no viable value for the variable v, by interval reasoning */
void init_by_viable_interval(pvar v);
/** conflict because there is no viable value for the variable v, by fallback solver */
void init_by_viable_fallback(pvar v, univariate_solver& us);
bool contains(signed_constraint c) const { SASSERT(c); return contains(c.blit()); }
bool contains(sat::literal lit) const;

View file

@ -51,7 +51,7 @@ namespace polysat {
/** The boolean variable associated to this constraint */
sat::bool_var m_bvar = sat::null_bool_var;
constraint(constraint_manager& m, ckind_t k): m_kind(k) {}
constraint(ckind_t k): m_kind(k) {}
public:
virtual ~constraint() {}
@ -115,9 +115,11 @@ namespace polysat {
bool is_pwatched() const { return m_is_pwatched; }
void set_pwatched(bool f) { m_is_pwatched = f; }
/// Assuming the constraint is univariate under the current assignment of 's',
/// adds the constraint to the univariate solver 'us'.
virtual void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const = 0;
/**
* If the constraint is univariate in variable 'v' under the current assignment of 's',
* add the constraint to the univariate solver 'us'.
*/
virtual void add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const = 0;
};
inline std::ostream& operator<<(std::ostream& out, constraint const& c) { return c.display(out); }
@ -162,7 +164,7 @@ namespace polysat {
void narrow(solver& s, bool first) { get()->narrow(s, is_positive(), first); }
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()); }
void add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep) const { get()->add_to_univariate_solver(v, s, us, dep, is_positive()); }
unsigned_vector const& vars() const { return m_constraint->vars(); }
unsigned var(unsigned idx) const { return m_constraint->var(idx); }

View file

@ -56,7 +56,7 @@ namespace polysat {
/** Add constraint to per-level storage */
void constraint_manager::store(constraint* c) {
LOG_V("Store constraint: " << show_deref(c));
LOG_V(20, "Store constraint: " << show_deref(c));
m_constraints.push_back(c);
}
@ -231,7 +231,7 @@ namespace polysat {
}
/** Look up constraint among stored constraints. */
constraint* constraint_manager::dedup(constraint* c1) {
constraint* constraint_manager::dedup_store(constraint* c1) {
constraint* c2 = nullptr;
if (m_dedup.constraints.find(c1, c2)) {
dealloc(c1);
@ -246,6 +246,15 @@ namespace polysat {
}
}
/** Find stored constraint */
constraint* constraint_manager::dedup_find(constraint* c1) const {
constraint* c = nullptr;
if (!m_dedup.constraints.find(c1, c)) {
SASSERT(c == nullptr);
}
return c;
}
void constraint_manager::gc() {
LOG_H1("gc");
gc_clauses();
@ -294,7 +303,7 @@ namespace polysat {
pdd lhs = a;
pdd rhs = b;
ule_constraint::simplify(is_positive, lhs, rhs);
return { dedup(alloc(ule_constraint, *this, lhs, rhs)), is_positive };
return { dedup_store(alloc(ule_constraint, lhs, rhs)), is_positive };
}
signed_constraint constraint_manager::eq(pdd const& p) {
@ -305,6 +314,19 @@ namespace polysat {
return ~ule(b, a);
}
signed_constraint constraint_manager::find_eq(pdd const& p) const {
return find_ule(p, p.manager().zero());
}
signed_constraint constraint_manager::find_ule(pdd const& a, pdd const& b) const {
bool is_positive = true;
pdd lhs = a;
pdd rhs = b;
ule_constraint::simplify(is_positive, lhs, rhs);
ule_constraint tmp(lhs, rhs); // TODO: this still allocates ule_constraint::m_vars
return { dedup_find(&tmp), is_positive };
}
/**
* encode that the i'th bit of p is 1.
* It holds if p << (K - i - 1) >= 2^{K-1}, where K is the bit-width.
@ -317,19 +339,19 @@ namespace polysat {
}
signed_constraint constraint_manager::umul_ovfl(pdd const& a, pdd const& b) {
return { dedup(alloc(umul_ovfl_constraint, *this, a, b)), true };
return { dedup_store(alloc(umul_ovfl_constraint, a, b)), true };
}
signed_constraint constraint_manager::smul_ovfl(pdd const& a, pdd const& b) {
return { dedup(alloc(smul_fl_constraint, *this, a, b, true)), true };
return { dedup_store(alloc(smul_fl_constraint, a, b, true)), true };
}
signed_constraint constraint_manager::smul_udfl(pdd const& a, pdd const& b) {
return { dedup(alloc(smul_fl_constraint, *this, a, b, false)), true };
return { dedup_store(alloc(smul_fl_constraint, a, b, false)), true };
}
signed_constraint constraint_manager::mk_op_constraint(op_constraint::code op, pdd const& p, pdd const& q, pdd const& r) {
return { dedup(alloc(op_constraint, *this, op, p, q, r)), true };
return { dedup_store(alloc(op_constraint, op, p, q, r)), true };
}
// To do signed comparison of bitvectors, flip the msb and do unsigned comparison:
@ -405,6 +427,8 @@ 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
// TODO: when a,b become evaluable, can we actually propagate q,r? doesn't seem like it.
// Maybe we need something like an op_constraint for better propagation.
s.add_clause(eq(b * q + r - a), false);
s.add_clause(~umul_ovfl(b, q), false);
// r <= b*q+r

View file

@ -67,9 +67,9 @@ namespace polysat {
constraint* get_bv2c(sat::bool_var bv) const;
void store(constraint* c);
void erase(constraint* c);
constraint* dedup(constraint* c);
constraint* dedup_store(constraint* c);
constraint* dedup_find(constraint* c) const;
void gc_constraints();
void gc_clauses();
@ -102,6 +102,10 @@ namespace polysat {
constraint* lookup(sat::bool_var var) const;
signed_constraint lookup(sat::literal lit) const;
/** Find constraint p == 0; returns null if it doesn't exist yet */
signed_constraint find_eq(pdd const& p) const;
signed_constraint find_ule(pdd const& a, pdd const& b) const;
signed_constraint eq(pdd const& p);
signed_constraint ule(pdd const& a, pdd const& b);
signed_constraint ult(pdd const& a, pdd const& b);
@ -126,9 +130,65 @@ namespace polysat {
constraint* const* begin() const { return m_constraints.data(); }
constraint* const* end() const { return m_constraints.data() + m_constraints.size(); }
using clause_iterator = decltype(m_clauses)::const_iterator;
clause_iterator clauses_begin() const { return m_clauses.begin(); }
clause_iterator clauses_end() const { return m_clauses.end(); }
class clause_iterator {
friend class constraint_manager;
using storage_t = decltype(constraint_manager::m_clauses);
using outer_iterator = storage_t::const_iterator;
outer_iterator outer_it;
outer_iterator outer_end;
using inner_iterator = storage_t::data_t::const_iterator;
inner_iterator inner_it;
inner_iterator inner_end;
clause_iterator(storage_t const& cls, bool begin) {
if (begin) {
outer_it = cls.begin();
outer_end = cls.end();
}
else {
outer_it = cls.end();
outer_end = cls.end();
}
begin_inner();
}
void begin_inner() {
if (outer_it == outer_end) {
inner_it = nullptr;
inner_end = nullptr;
}
else {
inner_it = outer_it->begin();
inner_end = outer_it->end();
}
}
public:
clause const& operator*() const {
return *inner_it->get();
}
clause_iterator& operator++() {
++inner_it;
while (inner_it == inner_end && outer_it != outer_end) {
++outer_it;
begin_inner();
}
return *this;
}
bool operator==(clause_iterator const& other) const {
return outer_it == other.outer_it && inner_it == other.inner_it;
}
bool operator!=(clause_iterator const& other) const {
return !operator==(other);
}
};
clause_iterator clauses_begin() const { return clause_iterator(m_clauses, true); }
clause_iterator clauses_end() const { return clause_iterator(m_clauses, false); }
class clauses_t {
constraint_manager const* m_cm;

View file

@ -9,7 +9,7 @@ Module Name:
Author:
Jakob Rath 2021-04-6
Jakob Rath 2021-04-06
Nikolaj Bjorner (nbjorner) 2021-03-19
--*/
@ -21,7 +21,7 @@ Author:
namespace polysat {
/**
/**
*
* \param[in] c Original constraint
* \param[in] v Variable that is bounded by constraint
@ -38,7 +38,7 @@ namespace polysat {
bool forbidden_intervals::get_interval_umul_ovfl(signed_constraint const& c, pvar v, fi_record& fi) {
backtrack _backtrack(fi.side_cond);
backtrack _backtrack(fi.side_cond);
fi.coeff = 1;
fi.src = c;
@ -56,7 +56,7 @@ namespace polysat {
std::swap(a1, a2);
std::swap(e1, e2);
std::swap(b1, b2);
std::swap(ok1, ok2);
std::swap(ok1, ok2);
}
if (ok1 && !ok2 && a1.is_one() && b1.is_zero()) {
if (c.is_positive()) {
@ -69,8 +69,8 @@ namespace polysat {
return true;
}
}
if (!ok1 || !ok2)
if (!ok1 || !ok2)
return false;
@ -129,10 +129,10 @@ namespace polysat {
}
static char const* _last_function = "";
bool forbidden_intervals::get_interval_ule(signed_constraint const& c, pvar v, fi_record& fi) {
backtrack _backtrack(fi.side_cond);
backtrack _backtrack(fi.side_cond);
fi.coeff = 1;
fi.src = c;
@ -166,11 +166,11 @@ namespace polysat {
_backtrack.released = true;
// v > q
if (ok1 && !ok2 && match_non_zero(c, a1, b1, e1, fi))
if (false && ok1 && !ok2 && match_non_zero(c, a1, b1, e1, c->to_ule().rhs(), fi))
return true;
// p > v
if (!ok1 && ok2 && match_non_max(c, a2, b2, e2, fi))
if (false && !ok1 && ok2 && match_non_max(c, c->to_ule().lhs(), a2, b2, e2, fi))
return true;
if (!ok1 || !ok2 || (a1.is_zero() && a2.is_zero())) {
@ -185,6 +185,11 @@ namespace polysat {
if (match_zero(c, a1, b1, e1, a2, b2, e2, fi))
return true;
// -1 <= a*v + b, a odd
// -1 > a*v + b, a odd
if (match_max(c, a1, b1, e1, a2, b2, e2, fi))
return true;
if (match_linear1(c, a1, b1, e1, a2, b2, e2, fi))
return true;
if (match_linear2(c, a1, b1, e1, a2, b2, e2, fi))
@ -231,7 +236,7 @@ namespace polysat {
out_side_cond.push_back(s.eq(q, r));
q = r;
}
auto b = s.subst(e);
auto b = s.subst(e);
return std::tuple(b.is_val(), q.val(), e, b);
};
@ -239,7 +244,7 @@ namespace polysat {
signed_constraint const& c, bool is_trivial, rational & coeff,
rational & lo_val, pdd & lo,
rational & hi_val, pdd & hi) {
dd::pdd_manager& m = lo.manager();
if (is_trivial) {
@ -256,7 +261,7 @@ namespace polysat {
rational pow2 = m.max_value() + 1;
if (coeff > pow2/2) {
coeff = pow2 - coeff;
SASSERT(coeff > 0);
// Transform according to: y \in [l;u[ <=> -y \in [1-u;1-l[
@ -282,10 +287,10 @@ 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 & a1, pdd const& b1, pdd const& e1,
rational const & a2, pdd const& b2, pdd const& e2,
fi_record& fi) {
_last_function = __func__;
@ -382,10 +387,10 @@ namespace polysat {
/**
* a*v <= 0, a odd
* forbidden interval for v is [1,0[
* forbidden interval for v is [1;0[
*
* a*v + b <= 0, a odd
* forbidden interval for v is [n+1,n[ where n = -b * a^-1
* forbidden interval for v is [n+1;n[ where n = -b * a^-1
*
* TODO: extend to
* 2^k*a*v <= 0, a odd
@ -403,7 +408,7 @@ namespace polysat {
rational a1_inv;
VERIFY(a1.mult_inverse(m.power_of_2(), a1_inv));
// interval for a*v + b > 0 is [n,n+1[ where n = -b * a^-1
// interval for a*v + b > 0 is [n;n+1[ where n = -b * a^-1
rational lo_val = mod(-b1.val() * a1_inv, mod_value);
pdd lo = -e1 * a1_inv;
rational hi_val = mod(lo_val + 1, mod_value);
@ -425,34 +430,83 @@ namespace polysat {
return false;
}
/**
/**
* -1 <= a*v + b, a odd
* forbidden interval for v is [n+1;n[ where n = (-b-1) * a^-1
*/
bool forbidden_intervals::match_max(
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 (a1.is_zero() && b1.is_max() && a2.is_odd()) {
auto& m = e2.manager();
rational const& mod_value = m.two_to_N();
rational a2_inv;
VERIFY(a2.mult_inverse(m.power_of_2(), a2_inv));
// interval for -1 > a*v + b is [n;n+1[ where n = (-b-1) * a^-1
rational lo_val = mod((-1 - b2.val()) * a2_inv, mod_value);
pdd lo = (-1 - e2) * a2_inv;
rational hi_val = mod(lo_val + 1, mod_value);
pdd hi = lo + 1;
// interval for -1 <= a*v + b is the complement
if (c.is_positive()) {
std::swap(lo_val, hi_val);
std::swap(lo, hi);
}
fi.coeff = 1;
fi.interval = eval_interval::proper(lo, lo_val, hi, hi_val);
// LHS == -1 is a precondition because we can only multiply with a^-1 in equations, not inequalities
if (b1 != e1)
fi.side_cond.push_back(s.eq(b1, e1));
return true;
}
return false;
}
/**
* v > q
* forbidden interval for v is [0,1[
*
*
* v - k > q
* forbidden interval for v is [k,k+1[
*
*
* v > q
* forbidden interval for v is [0;q+1[ but at least [0;1[
*
* The following cases are implemented, and subsume the simple ones above.
*
* v - k > q
* forbidden interval for v is [k;k+q+1[ but at least [k;k+1[
*
* a*v - k > q, a odd
* forbidden interval for v is [a^-1*k, a^-1*k + 1[
*/
bool forbidden_intervals::match_non_zero(
signed_constraint const& c,
rational const & a1, pdd const& b1, pdd const& e1,
rational const& a1, pdd const& b1, pdd const& e1,
pdd const& q,
fi_record& fi) {
_last_function = __func__;
if (a1.is_odd() && b1.is_zero() && c.is_negative()) {
SASSERT(b1.is_val());
if (a1.is_one() && c.is_negative()) {
// v - k > q
auto& m = e1.manager();
rational lo_val(0);
auto lo = m.zero();
rational hi_val(1);
auto hi = m.one();
rational const& mod_value = m.two_to_N();
rational lo_val = (-b1).val();
pdd lo = -e1;
rational hi_val = mod(lo_val + 1, mod_value);
pdd hi = lo + q + 1;
fi.coeff = 1;
fi.interval = eval_interval::proper(lo, lo_val, hi, hi_val);
if (b1 != e1)
fi.side_cond.push_back(s.eq(b1, e1));
return true;
}
if (a1.is_odd() && b1.is_val() && c.is_negative()) {
if (a1.is_odd() && c.is_negative()) {
// a*v - k > q, a odd
auto& m = e1.manager();
rational const& mod_value = m.two_to_N();
rational a1_inv;
@ -463,8 +517,6 @@ namespace polysat {
auto hi = lo + 1;
fi.coeff = 1;
fi.interval = eval_interval::proper(lo, lo_val, hi, hi_val);
if (b1 != e1)
fi.side_cond.push_back(s.eq(b1, e1));
return true;
}
return false;
@ -472,26 +524,45 @@ namespace polysat {
/**
* p > v
* forbidden interval for v is [-1,0[
* forbidden interval for v is [p;0[ but at least [-1,0[
*
* p > v + k
* forbidden interval for v is [-k-1,-k[
* forbidden interval for v is [p-k;-k[ but at least [-1-k,-k[
*
* p > a*v + k, a odd
* forbidden interval for v is [ a^-1*(-1-k) ; a^-1*(-1-k) + 1 [
*/
bool forbidden_intervals::match_non_max(
signed_constraint const& c,
rational const & a2, pdd const& b2, pdd const& e2,
pdd const& p,
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()) {
SASSERT(b2.is_val());
if (a2.is_one() && c.is_negative()) {
// p > v + k
auto& m = e2.manager();
rational const& mod_value = m.two_to_N();
rational lo_val(mod(-b2.val() - 1, mod_value));
auto lo = -e2 - 1;
rational hi_val(mod(lo_val + 1, mod_value));
auto hi = -e2;
rational hi_val = (-b2).val();
pdd hi = -e2;
rational lo_val = mod(hi_val - 1, mod_value);
pdd lo = p - e2;
fi.coeff = 1;
fi.interval = eval_interval::proper(lo, lo_val, hi, hi_val);
return true;
}
if (a2.is_odd() && c.is_negative()) {
// p > a*v + k, a odd
auto& m = e2.manager();
rational const& mod_value = m.two_to_N();
rational a2_inv;
VERIFY(a2.mult_inverse(m.power_of_2(), a2_inv));
rational lo_val = mod(a2_inv * (-1 - b2.val()), mod_value);
pdd lo = a2_inv * (-1 - e2);
rational hi_val = mod(lo_val + 1, mod_value);
pdd hi = lo + 1;
fi.coeff = 1;
fi.interval = eval_interval::proper(lo, lo_val, hi, hi_val);
if (b2 != e2)
fi.side_cond.push_back(s.eq(b2, e2));
return true;
}
return false;

View file

@ -73,12 +73,19 @@ namespace polysat {
rational const & a2, pdd const& b2, pdd const& e2,
fi_record& fi);
bool match_non_zero(signed_constraint const& c,
bool match_max(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);
bool match_non_zero(signed_constraint const& c,
rational const& a1, pdd const& b1, pdd const& e1,
pdd const& q,
fi_record& fi);
bool match_non_max(signed_constraint const& c,
rational const & a2, pdd const& b2, pdd const& e2,
pdd const& p,
rational const& a2, pdd const& b2, pdd const& e2,
fi_record& fi);
bool get_interval_ule(signed_constraint const& c, pvar v, fi_record& fi);

View file

@ -50,9 +50,6 @@ static LogLevel get_max_log_level(std::string const& fn, std::string const& pret
(void)fn;
(void)pretty_fn;
if (fn == "push_cjust")
return LogLevel::Verbose;
// if (fn == "pop_levels")
// return LogLevel::Default;
@ -65,9 +62,11 @@ static LogLevel get_max_log_level(std::string const& fn, std::string const& pret
}
/// Filter log messages
bool polysat_should_log(LogLevel msg_level, std::string fn, std::string pretty_fn) {
bool polysat_should_log(unsigned verbose_lvl, LogLevel msg_level, std::string fn, std::string pretty_fn) {
if (!g_log_enabled)
return false;
if (get_verbosity_level() < verbose_lvl)
return false;
LogLevel max_log_level = get_max_log_level(fn, pretty_fn);
return msg_level <= max_log_level;
}

View file

@ -56,12 +56,11 @@ enum class LogLevel : int {
Heading2 = 2,
Heading3 = 3,
Default = 4,
Verbose = 5,
};
/// Filter log messages
bool
polysat_should_log(LogLevel msg_level, std::string fn, std::string pretty_fn);
polysat_should_log(unsigned verbose_lvl, LogLevel msg_level, std::string fn, std::string pretty_fn);
std::pair<std::ostream&, bool>
polysat_log(LogLevel msg_level, std::string fn, std::string pretty_fn);
@ -70,31 +69,36 @@ polysat_log(LogLevel msg_level, std::string fn, std::string pretty_fn);
#define __PRETTY_FUNCTION__ __FUNCSIG__
#endif
#define LOG_(lvl, x) \
do { \
if (polysat_should_log(lvl, __func__, __PRETTY_FUNCTION__)) { \
auto pair = polysat_log(lvl, __func__, __PRETTY_FUNCTION__); \
std::ostream& os = pair.first; \
bool should_reset = pair.second; \
os << x; \
if (should_reset) \
os << color_reset(); \
os << std::endl; \
} \
#define LOG_(verbose_lvl, log_lvl, x) \
do { \
if (polysat_should_log(verbose_lvl, log_lvl, __func__, __PRETTY_FUNCTION__)) { \
auto pair = polysat_log(log_lvl, __func__, __PRETTY_FUNCTION__); \
std::ostream& os = pair.first; \
bool should_reset = pair.second; \
os << x; \
if (should_reset) \
os << color_reset(); \
os << std::endl; \
} \
} while (false)
#define LOG_CONCAT_HELPER(a,b) a ## b
#define LOG_CONCAT(a,b) LOG_CONCAT_HELPER(a,b)
#define LOG_INDENT(lvl, x) \
LOG_(lvl, x); \
#define LOG_INDENT(verbose_lvl, log_lvl, x) \
LOG_(verbose_lvl, log_lvl, x); \
polysat_log_indent LOG_CONCAT(polysat_log_indent_obj_, __LINE__) (4);
#define LOG_H1(x) LOG_INDENT(LogLevel::Heading1, x)
#define LOG_H2(x) LOG_INDENT(LogLevel::Heading2, x)
#define LOG_H3(x) LOG_INDENT(LogLevel::Heading3, x)
#define LOG(x) LOG_(LogLevel::Default , x)
#define LOG_V(x) LOG_(LogLevel::Verbose , x)
#define LOG_H1(x) LOG_INDENT(0, LogLevel::Heading1, x)
#define LOG_H2(x) LOG_INDENT(0, LogLevel::Heading2, x)
#define LOG_H3(x) LOG_INDENT(0, LogLevel::Heading3, x)
#define LOG(x) LOG_(0, LogLevel::Default , x)
#define LOG_H1_V(verbose_lvl, x) LOG_INDENT(verbose_lvl, LogLevel::Heading1, x)
#define LOG_H2_V(verbose_lvl, x) LOG_INDENT(verbose_lvl, LogLevel::Heading2, x)
#define LOG_H3_V(verbose_lvl, x) LOG_INDENT(verbose_lvl, LogLevel::Heading3, x)
#define LOG_V(verbose_lvl, x) LOG_(verbose_lvl, LogLevel::Default , x)
#define COND_LOG(c, x) if (c) LOG(x)
#define LOGE(x) LOG(#x << " = " << (x))
@ -110,18 +114,23 @@ inline void set_log_enabled(bool) {}
inline bool get_log_enabled() { return false; }
class scoped_set_log_enabled {};
#define LOG_(lvl, x) \
do { \
/* do nothing */ \
#define LOG_(vlvl, lvl, x) \
do { \
/* do nothing */ \
} while (false)
#define LOG_H1(x) LOG_(0, x)
#define LOG_H2(x) LOG_(0, x)
#define LOG_H3(x) LOG_(0, x)
#define LOG(x) LOG_(0, x)
#define LOG_V(x) LOG_(0, x)
#define COND_LOG(c, x) LOG_(c, x)
#define LOGE(x) LOG_(0, x)
#define LOG_H1(x) LOG_(0, 0, x)
#define LOG_H2(x) LOG_(0, 0, x)
#define LOG_H3(x) LOG_(0, 0, x)
#define LOG(x) LOG_(0, 0, x)
#define LOG_H1_V(v, x) LOG_(v, 0, x)
#define LOG_H2_V(v, x) LOG_(v, 0, x)
#define LOG_H3_V(v, x) LOG_(v, 0, x)
#define LOG_V(v, x) LOG_(v, 0, x)
#define COND_LOG(c, x) LOG_(0, c, x)
#define LOGE(x) LOG_(0, 0, x)
#define IF_LOGGING(x) \
do { \

30
src/math/polysat/number.h Normal file
View file

@ -0,0 +1,30 @@
/*++
Copyright (c) 2021 Microsoft Corporation
Module Name:
polysat numbers
Author:
Nikolaj Bjorner (nbjorner) 2021-03-19
Jakob Rath 2021-04-06
--*/
#pragma once
#include "math/polysat/types.h"
namespace polysat {
inline unsigned get_parity(rational const& val, unsigned num_bits) {
if (val.is_zero())
return num_bits;
return val.trailing_zeros();
};
/** Return val with the lower k bits set to zero. */
inline rational clear_lower_bits(rational const& val, unsigned k) {
return val - mod(val, rational::power_of_two(k));
}
}

View file

@ -25,8 +25,8 @@ Additional possible functionality on constraints:
namespace polysat {
op_constraint::op_constraint(constraint_manager& m, code c, pdd const& p, pdd const& q, pdd const& r) :
constraint(m, ckind_t::op_t), m_op(c), m_p(p), m_q(q), m_r(r) {
op_constraint::op_constraint(code c, pdd const& p, pdd const& q, pdd const& r) :
constraint(ckind_t::op_t), m_op(c), m_p(p), m_q(q), m_r(r) {
m_vars.append(p.free_vars());
for (auto v : q.free_vars())
if (!m_vars.contains(v))
@ -570,20 +570,26 @@ namespace polysat {
}
return true;
}
void op_constraint::add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
auto p_coeff = s.subst(p()).get_univariate_coefficients();
auto q_coeff = s.subst(q()).get_univariate_coefficients();
auto r_coeff = s.subst(r()).get_univariate_coefficients();
void op_constraint::add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
pdd pv = s.subst(p());
if (!pv.is_univariate_in(v))
return;
pdd qv = s.subst(q());
if (!qv.is_univariate_in(v))
return;
pdd rv = s.subst(r());
if (!rv.is_univariate_in(v))
return;
switch (m_op) {
case code::lshr_op:
us.add_lshr(p_coeff, q_coeff, r_coeff, !is_positive, dep);
us.add_lshr(pv.get_univariate_coefficients(), qv.get_univariate_coefficients(), rv.get_univariate_coefficients(), !is_positive, dep);
break;
case code::shl_op:
us.add_shl(p_coeff, q_coeff, r_coeff, !is_positive, dep);
us.add_shl(pv.get_univariate_coefficients(), qv.get_univariate_coefficients(), rv.get_univariate_coefficients(), !is_positive, dep);
break;
case code::and_op:
us.add_and(p_coeff, q_coeff, r_coeff, !is_positive, dep);
us.add_and(pv.get_univariate_coefficients(), qv.get_univariate_coefficients(), rv.get_univariate_coefficients(), !is_positive, dep);
break;
default:
NOT_IMPLEMENTED_YET();

View file

@ -35,7 +35,7 @@ namespace polysat {
pdd m_q; // operand2
pdd m_r; // result
op_constraint(constraint_manager& m, code c, pdd const& p, pdd const& q, pdd const& r);
op_constraint(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);
@ -73,7 +73,7 @@ namespace polysat {
bool operator==(constraint const& other) const override;
bool is_eq() const override { return false; }
void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
void add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
};
struct op_constraint_args {

File diff suppressed because it is too large Load diff

View file

@ -31,9 +31,11 @@ namespace polysat {
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(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);
void log_lemma(pvar v, conflict& core);
bool propagate(pvar v, conflict& core, signed_constraint const& crit1, signed_constraint c);
bool propagate(pvar v, conflict& core, inequality const& crit1, signed_constraint c);
bool add_conflict(pvar v, conflict& core, inequality const& crit1, signed_constraint c);
bool add_conflict(pvar v, conflict& core, inequality const& crit1, inequality const& crit2, signed_constraint c);
bool try_ugt_x(pvar v, conflict& core, inequality const& c);
@ -49,10 +51,16 @@ namespace polysat {
bool try_parity(pvar x, conflict& core, inequality const& axb_l_y);
bool try_parity_diseq(pvar x, conflict& core, inequality const& axb_l_y);
bool try_mul_bounds(pvar x, conflict& core, inequality const& axb_l_y);
bool try_factor_equality(pvar x, conflict& core, inequality const& a_l_b);
bool try_factor_equality1(pvar x, conflict& core, inequality const& a_l_b);
bool try_factor_equality2(pvar x, conflict& core, inequality const& a_l_b);
bool try_infer_equality(pvar x, conflict& core, inequality const& a_l_b);
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_mul_eq_bound(pvar x, conflict& core, inequality const& axb_l_y);
bool try_transitivity(pvar x, conflict& core, inequality const& axb_l_y);
bool try_tangent(pvar v, conflict& core, inequality const& c);
bool try_add_overflow_bound(pvar x, conflict& core, inequality const& axb_l_y);
bool try_add_mul_bound(pvar x, conflict& core, inequality const& axb_l_y);
// c := lhs ~ v
// where ~ is < or <=
@ -62,7 +70,10 @@ namespace polysat {
bool is_g_v(pvar v, inequality const& c);
// c := x ~ Y
bool is_x_l_Y(pvar x, inequality const& c, pdd& y);
bool is_x_l_Y(pvar x, inequality const& i, pdd& y);
// c := Y ~ x
bool is_Y_l_x(pvar x, inequality const& i, pdd& y);
// c := X*y ~ X*Z
bool is_Xy_l_XZ(pvar y, inequality const& c, pdd& x, pdd& z);
@ -107,6 +118,17 @@ 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);
// i := x + y >= x or x + y > x
bool is_add_overflow(pvar x, inequality const& i, pdd& y);
bool has_upper_bound(pvar x, conflict& core, rational& bound, vector<signed_constraint>& x_ge_bound);
bool has_lower_bound(pvar x, conflict& core, rational& bound, vector<signed_constraint>& x_le_bound);
// determine min/max parity of polynomial
unsigned min_parity(pdd const& p);
unsigned max_parity(pdd const& p);
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)); }
@ -118,6 +140,13 @@ namespace polysat {
bool is_forced_true(signed_constraint const& sc);
bool try_inequality(pvar v, inequality const& i, conflict& core);
bool try_umul_ovfl(pvar v, signed_constraint const& c, conflict& core);
bool try_umul_noovfl_lo(pvar v, signed_constraint const& c, conflict& core);
bool try_umul_noovfl_bounds(pvar v, signed_constraint const& c, conflict& core);
bool try_umul_ovfl_bounds(pvar v, signed_constraint const& c, conflict& core);
public:
saturation(solver& s);
void perform(pvar v, conflict& core);

View file

@ -77,13 +77,85 @@ namespace polysat {
bool simplify_clause::apply(clause& cl) {
LOG_H1("Simplifying clause: " << cl);
bool simplified = false;
if (try_remove_equations(cl))
simplified = true;
#if 0
if (try_recognize_bailout(cl))
return true;
simplified = true;
#endif
if (try_equal_body_subsumptions(cl))
return true;
return false;
simplified = true;
return simplified;
}
/**
* If we have:
* p <= q
* p - q == 0
* Then remove the equality.
*
* If we have:
* p < q
* p - q == 0
* Then merge into p <= q.
*/
bool simplify_clause::try_remove_equations(clause& cl) {
LOG_H2("Remove superfluous equations from: " << cl);
bool const has_eqn = any_of(cl, [&](sat::literal lit) { return s.lit2cnstr(lit).is_eq(); });
if (!has_eqn)
return false;
bool any_removed = false;
bool_vector& should_remove = m_bools;
should_remove.fill(cl.size(), false);
for (unsigned i = cl.size(); i-- > 0; ) {
sat::literal const lit = cl[i];
signed_constraint const c = s.lit2cnstr(lit);
if (!c->is_ule())
continue;
if (c->is_eq())
continue;
#if 1
// Disable the case p<q && p=q for now.
// The merging of less-than and equality may remove premises from the lemma.
// See test_band5.
// TODO: fix and re-enable
if (c.is_negative())
continue;
#endif
LOG_V(10, "Examine: " << lit_pp(s, lit));
pdd const p = c->to_ule().lhs();
pdd const q = c->to_ule().rhs();
signed_constraint const eq = s.m_constraints.find_eq(p - q);
if (!eq)
continue;
auto const eq_it = std::find(cl.begin(), cl.end(), eq.blit());
if (eq_it == cl.end())
continue;
unsigned const eq_idx = std::distance(cl.begin(), eq_it);
any_removed = true;
should_remove[eq_idx] = true;
if (c.is_positive()) {
// c: p <= q
// eq: p == q
LOG("Removing " << eq.blit() << ": " << eq << " because it subsumes " << cl[i] << ": " << s.lit2cnstr(cl[i]));
}
else {
// c: p > q
// eq: p == q
cl[i] = s.ule(q, p).blit();
LOG("Merge " << eq.blit() << ": " << eq << " and " << lit << ": " << c << " to obtain " << cl[i] << ": " << s.lit2cnstr(cl[i]));
}
}
// Remove superfluous equations
if (!any_removed)
return false;
unsigned j = 0;
for (unsigned i = 0; i < cl.size(); ++i)
if (!should_remove[i])
cl[j++] = cl[i];
cl.m_literals.shrink(j);
return true;
}
// If x != k appears among the new literals, all others are superfluous.
@ -94,7 +166,7 @@ namespace polysat {
sat::literal eq = sat::null_literal;
rational k;
for (sat::literal lit : cl) {
LOG_V("Examine " << lit_pp(s, lit));
LOG_V(10, "Examine " << lit_pp(s, lit));
lbool status = s.m_bvars.value(lit);
// skip premise literals
if (status == l_false)
@ -237,7 +309,7 @@ namespace polysat {
for (unsigned i = 0; i < cl.size(); ++i) {
subs_entry& entry = m_entries[i];
sat::literal lit = cl[i];
LOG("Literal " << lit_pp(s, lit));
LOG_V(10, "Literal " << lit_pp(s, lit));
signed_constraint c = s.lit2cnstr(lit);
prepare_subs_entry(entry, c);
}
@ -254,7 +326,7 @@ namespace polysat {
continue;
if (e.interval.currently_contains(f.interval)) {
// f subset of e ==> f.src subsumed by e.src
LOG("Removing " << s.lit2cnstr(cl[i]) << " because it subsumes " << s.lit2cnstr(cl[j]));
LOG("Removing " << cl[i] << ": " << s.lit2cnstr(cl[i]) << " because it subsumes " << cl[j] << ": " << s.lit2cnstr(cl[j]));
e.subsuming = true;
any_subsumed = true;
break;

View file

@ -28,7 +28,9 @@ namespace polysat {
solver& s;
vector<subs_entry> m_entries;
bool_vector m_bools;
bool try_remove_equations(clause& cl);
bool try_recognize_bailout(clause& cl);
bool try_equal_body_subsumptions(clause& cl);

View file

@ -15,8 +15,8 @@ Author:
namespace polysat {
smul_fl_constraint::smul_fl_constraint(constraint_manager& m, pdd const& p, pdd const& q, bool is_overflow):
constraint(m, ckind_t::smul_fl_t), m_is_overflow(is_overflow), m_p(p), m_q(q) {
smul_fl_constraint::smul_fl_constraint(pdd const& p, pdd const& q, bool is_overflow):
constraint(ckind_t::smul_fl_t), m_is_overflow(is_overflow), m_p(p), m_q(q) {
simplify();
m_vars.append(m_p.free_vars());
for (auto v : m_q.free_vars())
@ -119,13 +119,17 @@ namespace polysat {
&& q() == other.to_smul_fl().q();
}
void smul_fl_constraint::add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
auto p_coeff = s.subst(p()).get_univariate_coefficients();
auto q_coeff = s.subst(q()).get_univariate_coefficients();
void smul_fl_constraint::add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
auto p1 = s.subst(p());
if (!p1.is_univariate_in(v))
return;
auto q1 = s.subst(q());
if (!q1.is_univariate_in(v))
return;
if (is_overflow())
us.add_smul_ovfl(p_coeff, q_coeff, !is_positive, dep);
us.add_smul_ovfl(p1.get_univariate_coefficients(), q1.get_univariate_coefficients(), !is_positive, dep);
else
us.add_smul_udfl(p_coeff, q_coeff, !is_positive, dep);
us.add_smul_udfl(p1.get_univariate_coefficients(), q1.get_univariate_coefficients(), !is_positive, dep);
}
}

View file

@ -25,7 +25,7 @@ namespace polysat {
pdd m_q;
void simplify();
smul_fl_constraint(constraint_manager& m, pdd const& p, pdd const& q, bool is_overflow);
smul_fl_constraint(pdd const& p, pdd const& q, bool is_overflow);
public:
~smul_fl_constraint() override {}
@ -42,7 +42,7 @@ namespace polysat {
bool operator==(constraint const& other) const override;
bool is_eq() const override { return false; }
void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
void add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
};
}

View file

@ -53,6 +53,7 @@ namespace polysat {
m_config.m_max_conflicts = m_params.get_uint("max_conflicts", UINT_MAX);
m_config.m_max_decisions = m_params.get_uint("max_decisions", UINT_MAX);
m_config.m_log_conflicts = pp.log_conflicts();
m_rand.set_seed(m_params.get_uint("random_seed", 0));
}
bool solver::should_search() {
@ -75,6 +76,7 @@ namespace polysat {
LOG("Assignment: " << assignments_pp(*this));
if (is_conflict()) LOG("Conflict: " << m_conflict);
IF_LOGGING(m_viable.log());
SASSERT(var_queue_invariant());
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();
@ -241,9 +243,11 @@ namespace polysat {
for (; i < sz && !is_conflict(); ++i)
if (!propagate(v, wlist[i]))
wlist[j++] = wlist[i];
for (; i < sz; ++i)
for (; i < sz; ++i)
wlist[j++] = wlist[i];
wlist.shrink(j);
if (is_conflict())
shuffle(wlist.size(), wlist.data(), m_rand);
DEBUG_CODE(m_locked_wlist = std::nullopt;);
}
@ -394,7 +398,7 @@ namespace polysat {
void solver::add_pwatch(constraint* c, pvar v) {
SASSERT(m_locked_wlist != v); // the propagate loop will not discover the new size
LOG_V("Watching v" << v << " in constraint " << show_deref(c));
LOG_V(20, "Watching v" << v << " in constraint " << show_deref(c));
m_pwatch[v].push_back(c);
}
@ -495,7 +499,7 @@ namespace polysat {
}
case trail_instr_t::assign_i: {
auto v = m_search.back().var();
LOG_V("Undo assign_i: v" << v);
LOG_V(20, "Undo assign_i: v" << v);
unsigned active_level = get_level(v);
if (active_level <= target_level) {
@ -511,7 +515,7 @@ namespace polysat {
}
case trail_instr_t::assign_bool_i: {
sat::literal lit = m_search.back().lit();
LOG_V("Undo assign_bool_i: " << lit);
LOG_V(20, "Undo assign_bool_i: " << lit);
unsigned active_level = m_bvars.level(lit);
if (active_level <= target_level)
@ -588,15 +592,13 @@ namespace polysat {
// Simple hack to try deciding the boolean skeleton first
if (!can_bdecide()) {
// enqueue all not-yet-true clauses
for (auto const& cls : m_constraints.clauses()) {
for (auto const& cl : cls) {
bool is_true = any_of(*cl, [&](sat::literal lit) { return m_bvars.is_true(lit); });
if (is_true)
continue;
size_t undefs = count_if(*cl, [&](sat::literal lit) { return !m_bvars.is_assigned(lit); });
if (undefs >= 2)
m_lemmas.push_back(cl.get());
}
for (clause const& cl : m_constraints.clauses()) {
bool is_true = any_of(cl, [&](sat::literal lit) { return m_bvars.is_true(lit); });
if (is_true)
continue;
size_t undefs = count_if(cl, [&](sat::literal lit) { return !m_bvars.is_assigned(lit); });
if (undefs >= 2)
m_lemmas.push_back(&cl);
}
}
#endif
@ -608,7 +610,7 @@ namespace polysat {
}
void solver::bdecide() {
clause& lemma = *m_lemmas[m_lemmas_qhead++];
clause const& lemma = *m_lemmas[m_lemmas_qhead++];
on_scope_exit update_trail = [this]() {
// must be done after push_level, but also if we return early.
m_trail.push_back(trail_instr_t::lemma_qhead_i);
@ -654,7 +656,7 @@ namespace polysat {
// (fail here in debug mode so we notice if we miss some)
DEBUG_CODE( UNREACHABLE(); );
m_free_pvars.unassign_var_eh(v);
set_conflict(v, false);
SASSERT(is_conflict());
return;
case find_t::singleton:
// NOTE: this case may happen legitimately if all other possibilities were excluded by brute force search
@ -734,9 +736,16 @@ namespace polysat {
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())
if (is_conflict()) {
// If we have a conflict here, we should have produced this lemma already earlier
LOG("Conflict after constraint::produce_lemma: TODO: should have found this lemma earlier");
m_free_pvars.unassign_var_eh(v);
return;
}
if (can_propagate()) {
m_free_pvars.unassign_var_eh(v);
return;
}
}
if (c) {
LOG_H2("Chosen assignment " << assignment_pp(*this, v, val) << " is not actually viable!");
@ -753,10 +762,11 @@ namespace polysat {
case find_t::empty:
LOG("Fallback solver: unsat");
m_free_pvars.unassign_var_eh(v);
set_conflict(v, true);
SASSERT(is_conflict());
return;
case find_t::resource_out:
UNREACHABLE(); // TODO: abort solving
m_free_pvars.unassign_var_eh(v);
return;
}
}
@ -938,7 +948,6 @@ namespace polysat {
clause* best_lemma = nullptr;
auto appraise_lemma = [&](clause* lemma) {
m_simplify_clause.apply(*lemma);
auto score = compute_lemma_score(*lemma);
if (score)
LOG(" score: " << *score);
@ -958,14 +967,14 @@ namespace polysat {
appraise_lemma(lemmas.back());
}
SASSERT(best_score < lemma_score::max());
SASSERT(best_lemma);
VERIFY(best_lemma);
unsigned const jump_level = std::max(best_score.jump_level(), base_level());
SASSERT(jump_level <= max_jump_level);
LOG("best_score: " << best_score);
LOG("best_lemma: " << *best_lemma);
m_conflict.reset();
backjump(jump_level);
@ -1054,7 +1063,11 @@ namespace polysat {
void solver::assign_eval(sat::literal lit) {
signed_constraint const c = lit2cnstr(lit);
LOG_V(10, "Evaluate: " << lit_pp(*this ,lit));
// assertion is false
if (!c.is_currently_true(*this)) IF_VERBOSE(0, verbose_stream() << c << " is not currently true\n");
SASSERT(c.is_currently_true(*this));
VERIFY(c.is_currently_true(*this));
unsigned level = 0;
// NOTE: constraint may be evaluated even if some variables are still unassigned (e.g., 0*x doesn't depend on x).
for (auto v : c->vars())
@ -1309,12 +1322,10 @@ namespace polysat {
for (auto c : m_constraints)
out << "\t" << c->bvar2string() << ": " << *c << "\n";
out << "Clauses:\n";
for (auto const& cls : m_constraints.clauses()) {
for (auto const& cl : cls) {
out << "\t" << *cl << "\n";
for (auto lit : *cl)
out << "\t\t" << lit << ": " << lit2cnstr(lit) << "\n";
}
for (clause const& cl : m_constraints.clauses()) {
out << "\t" << cl << "\n";
for (sat::literal lit : cl)
out << "\t\t" << lit << ": " << lit2cnstr(lit) << "\n";
}
return out;
}
@ -1430,23 +1441,20 @@ namespace polysat {
// Check for missed boolean propagations:
// - no clause should have exactly one unassigned literal, unless it is already true.
// - no clause should be false
for (auto const& cls : m_constraints.clauses()) {
for (auto const& clref : cls) {
clause const& cl = *clref;
bool const is_true = any_of(cl, [&](auto lit) { return m_bvars.is_true(lit); });
if (is_true)
continue;
size_t const undefs = count_if(cl, [&](auto lit) { return !m_bvars.is_assigned(lit); });
if (undefs == 1) {
LOG("Missed boolean propagation of clause: " << cl);
for (sat::literal lit : cl) {
LOG(" " << lit_pp(*this, lit));
}
for (clause const& cl : m_constraints.clauses()) {
bool const is_true = any_of(cl, [&](auto lit) { return m_bvars.is_true(lit); });
if (is_true)
continue;
size_t const undefs = count_if(cl, [&](auto lit) { return !m_bvars.is_assigned(lit); });
if (undefs == 1) {
LOG("Missed boolean propagation of clause: " << cl);
for (sat::literal lit : cl) {
LOG(" " << lit_pp(*this, lit));
}
SASSERT(undefs != 1);
bool const is_false = all_of(cl, [&](auto lit) { return m_bvars.is_false(lit); });
SASSERT(!is_false);
}
SASSERT(undefs != 1);
bool const is_false = all_of(cl, [&](auto lit) { return m_bvars.is_false(lit); });
SASSERT(!is_false);
}
return true;
}
@ -1456,7 +1464,7 @@ namespace polysat {
}
/** Check that boolean assignment and constraint evaluation are consistent */
bool solver::assignment_invariant() {
bool solver::assignment_invariant() const {
if (is_conflict())
return true;
bool ok = true;
@ -1475,6 +1483,25 @@ namespace polysat {
return ok;
}
/** Check that each variable is either assigned or queued for decisions */
bool solver::var_queue_invariant() const {
if (is_conflict())
return true;
uint_set active;
for (pvar v : m_free_pvars)
active.insert(v);
for (auto const& [v, val] : assignment())
active.insert(v);
bool ok = true;
for (pvar v = 0; v < num_vars(); ++v) {
if (!active.contains(v)) {
ok = false;
LOG("Lost variable v" << v << " (it is neither assigned nor free)");
}
}
return ok;
}
/// Check that all constraints on the stack are satisfied by the current model.
bool solver::verify_sat() {
LOG_H1("Checking current model...");
@ -1487,19 +1514,17 @@ namespace polysat {
all_ok = all_ok && ok;
}
}
for (auto clauses : m_constraints.clauses()) {
for (auto cl : clauses) {
bool clause_ok = false;
for (sat::literal lit : *cl) {
bool ok = lit2cnstr(lit).is_currently_true(*this);
if (ok) {
clause_ok = true;
break;
}
for (clause const& cl : m_constraints.clauses()) {
bool clause_ok = false;
for (sat::literal lit : cl) {
bool ok = lit2cnstr(lit).is_currently_true(*this);
if (ok) {
clause_ok = true;
break;
}
LOG((clause_ok ? "PASS" : "FAIL") << ": " << show_deref(cl) << (cl->is_redundant() ? " (redundant)" : ""));
all_ok = all_ok && clause_ok;
}
LOG((clause_ok ? "PASS" : "FAIL") << ": " << cl << (cl.is_redundant() ? " (redundant)" : ""));
all_ok = all_ok && clause_ok;
}
if (all_ok) LOG("All good!");
return all_ok;

View file

@ -157,6 +157,7 @@ namespace polysat {
bool_var_manager m_bvars; // Map boolean variables to constraints
var_queue m_free_pvars; // free poly vars
stats m_stats;
random_gen m_rand;
config m_config;
// Per constraint state
@ -188,7 +189,7 @@ namespace polysat {
constraints m_pwatch_trail;
#endif
ptr_vector<clause> m_lemmas; ///< the non-asserting lemmas
ptr_vector<clause const> m_lemmas; ///< the non-asserting lemmas
unsigned m_lemmas_qhead = 0;
unsigned_vector m_base_levels; // External clients can push/pop scope.
@ -215,6 +216,8 @@ namespace polysat {
dd::pdd_manager& sz2pdd(unsigned sz) const;
dd::pdd_manager& var2pdd(pvar v) const;
pvar num_vars() const { return m_value.size(); }
assignment_t const& assignment() const { return m_search.assignment(); }
void push_level();
@ -251,7 +254,8 @@ namespace polysat {
void set_conflict_at_base_level() { m_conflict.init_at_base_level(); }
void set_conflict(signed_constraint c) { m_conflict.init(c); }
void set_conflict(clause& cl) { m_conflict.init(cl); }
void set_conflict(pvar v, bool by_viable_fallback) { m_conflict.init(v, by_viable_fallback); }
void set_conflict_by_viable_interval(pvar v) { m_conflict.init_by_viable_interval(v); }
void set_conflict_by_viable_fallback(pvar v, univariate_solver& us) { m_conflict.init_by_viable_fallback(v, us); }
bool can_decide() const;
bool can_bdecide() const;
@ -309,7 +313,8 @@ namespace polysat {
static bool invariant(signed_constraints const& cs);
bool wlist_invariant() const;
bool bool_watch_invariant() const;
bool assignment_invariant();
bool assignment_invariant() const;
bool var_queue_invariant() const;
bool verify_sat();
bool can_propagate();
@ -417,15 +422,33 @@ namespace polysat {
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); }
/** parity(p) >= k (<=> p * 2^(K-k) == 0) */
signed_constraint parity(pdd const& p, unsigned k) {
/** parity(p) >= k */
signed_constraint parity(pdd const& p, unsigned k) { // TODO: rename to parity_at_least?
unsigned N = p.manager().power_of_2();
// parity(p) >= k
// <=> p * 2^(N - k) == 0
if (k >= N)
return eq(p);
else if (k == 0)
return odd(p);
else if (k == 0) {
// parity(p) >= 0 is a tautology
verbose_stream() << "REDUNDANT parity constraint: parity(" << p << ", " << k << ")\n";
return eq(p.manager().zero());
}
else
return eq(p*rational::power_of_two(N - k));
return eq(p * rational::power_of_two(N - k));
}
/** parity(p) <= k */
signed_constraint parity_at_most(pdd const& p, unsigned k) {
unsigned N = p.manager().power_of_2();
// parity(p) <= k
// <=> ~(parity(p) >= k+1)
if (k >= N) {
// parity(p) <= N is a tautology
verbose_stream() << "REDUNDANT parity constraint: parity(" << p << ", " << k << ")\n";
return eq(p.manager().zero());
}
else
return ~parity(p, k + 1);
}
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); }

View file

@ -135,8 +135,8 @@ namespace {
namespace polysat {
ule_constraint::ule_constraint(constraint_manager& m, pdd const& l, pdd const& r) :
constraint(m, ckind_t::ule_t), m_lhs(l), m_rhs(r) {
ule_constraint::ule_constraint(pdd const& l, pdd const& r) :
constraint(ckind_t::ule_t), m_lhs(l), m_rhs(r) {
m_vars.append(m_lhs.free_vars());
for (auto v : m_rhs.free_vars())
@ -185,9 +185,9 @@ namespace polysat {
signed_constraint sc(this, is_positive);
LOG_H3("Narrowing " << sc);
LOG_V("Assignment: " << assignments_pp(s));
LOG_V("Substituted LHS: " << lhs() << " := " << p);
LOG_V("Substituted RHS: " << rhs() << " := " << q);
LOG_V(10, "Assignment: " << assignments_pp(s));
LOG_V(10, "Substituted LHS: " << lhs() << " := " << p);
LOG_V(10, "Substituted RHS: " << rhs() << " := " << q);
if (is_always_false(is_positive, p, q)) {
s.set_conflict(sc);
@ -353,9 +353,16 @@ namespace polysat {
return other.is_ule() && lhs() == other.to_ule().lhs() && rhs() == other.to_ule().rhs();
}
void ule_constraint::add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
auto p_coeff = s.subst(lhs()).get_univariate_coefficients();
auto q_coeff = s.subst(rhs()).get_univariate_coefficients();
us.add_ule(p_coeff, q_coeff, !is_positive, dep);
void ule_constraint::add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
pdd p = s.subst(lhs());
pdd q = s.subst(rhs());
bool p_ok = p.is_univariate_in(v);
bool q_ok = q.is_univariate_in(v);
if (!is_positive && !q_ok) // add p > 0
us.add_ugt(p.get_univariate_coefficients(), rational::zero(), false, dep);
if (!is_positive && !p_ok) // add -1 > q <==> q+1 > 0
us.add_ugt((q + 1).get_univariate_coefficients(), rational::zero(), false, dep);
if (p_ok && q_ok)
us.add_ule(p.get_univariate_coefficients(), q.get_univariate_coefficients(), !is_positive, dep);
}
}

View file

@ -25,7 +25,7 @@ namespace polysat {
pdd m_lhs;
pdd m_rhs;
ule_constraint(constraint_manager& m, pdd const& l, pdd const& r);
ule_constraint(pdd const& l, pdd const& r);
static void simplify(bool& is_positive, pdd& lhs, pdd& rhs);
static bool is_always_true(bool is_positive, pdd const& lhs, pdd const& rhs) { return eval(lhs, rhs) == to_lbool(is_positive); }
static bool is_always_false(bool is_positive, pdd const& lhs, pdd const& rhs) { return is_always_true(!is_positive, lhs, rhs); }
@ -45,7 +45,7 @@ namespace polysat {
unsigned hash() const override;
bool operator==(constraint const& other) const override;
bool is_eq() const override { return m_rhs.is_zero(); }
void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
void add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
};
struct ule_pp {

View file

@ -15,8 +15,8 @@ Author:
namespace polysat {
umul_ovfl_constraint::umul_ovfl_constraint(constraint_manager& m, pdd const& p, pdd const& q):
constraint(m, ckind_t::umul_ovfl_t), m_p(p), m_q(q) {
umul_ovfl_constraint::umul_ovfl_constraint(pdd const& p, pdd const& q):
constraint(ckind_t::umul_ovfl_t), m_p(p), m_q(q) {
simplify();
m_vars.append(m_p.free_vars());
for (auto v : m_q.free_vars())
@ -25,8 +25,7 @@ namespace polysat {
}
void umul_ovfl_constraint::simplify() {
if (m_p.is_zero() || m_q.is_zero() ||
m_p.is_one() || m_q.is_one()) {
if (m_p.is_zero() || m_q.is_zero() || m_p.is_one() || m_q.is_one()) {
m_q = 0;
m_p = 0;
return;
@ -99,8 +98,8 @@ namespace polysat {
signed_constraint sc(this, is_positive);
// ¬Omega(p, q) ==> q = 0 \/ p <= p*q
// ¬Omega(p, q) ==> p = 0 \/ q <= p*q
s.add_clause(~sc, /* s.eq(p()), */ s.eq(q()), s.ule(p(), p()*q()), false);
s.add_clause(~sc, s.eq(p()), /* s.eq(q()), */ s.ule(q(), p()*q()), false);
s.add_clause(~sc, s.eq(q()), s.ule(p(), p()*q()), false);
s.add_clause(~sc, s.eq(p()), s.ule(q(), p()*q()), false);
}
}
@ -158,9 +157,13 @@ namespace polysat {
return other.is_umul_ovfl() && p() == other.to_umul_ovfl().p() && q() == other.to_umul_ovfl().q();
}
void umul_ovfl_constraint::add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
auto p_coeff = s.subst(p()).get_univariate_coefficients();
auto q_coeff = s.subst(q()).get_univariate_coefficients();
us.add_umul_ovfl(p_coeff, q_coeff, !is_positive, dep);
void umul_ovfl_constraint::add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const {
pdd p1 = s.subst(p());
if (!p1.is_univariate_in(v))
return;
pdd q1 = s.subst(q());
if (!q1.is_univariate_in(v))
return;
us.add_umul_ovfl(p1.get_univariate_coefficients(), q1.get_univariate_coefficients(), !is_positive, dep);
}
}

View file

@ -23,7 +23,7 @@ namespace polysat {
pdd m_p;
pdd m_q;
umul_ovfl_constraint(constraint_manager& m, pdd const& p, pdd const& q);
umul_ovfl_constraint(pdd const& p, pdd const& q);
void simplify();
static bool is_always_true(bool is_positive, pdd const& p, pdd const& q) { return eval(p, q) == to_lbool(is_positive); }
static bool is_always_false(bool is_positive, pdd const& p, pdd const& q) { return is_always_true(!is_positive, p, q); }
@ -46,7 +46,7 @@ namespace polysat {
bool operator==(constraint const& other) const override;
bool is_eq() const override { return false; }
void add_to_univariate_solver(solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
void add_to_univariate_solver(pvar v, solver& s, univariate_solver& us, unsigned dep, bool is_positive) const override;
};
}

View file

@ -26,6 +26,12 @@ Author:
namespace polysat {
univariate_solver::dep_vector univariate_solver::unsat_core() {
dep_vector deps;
unsat_core(deps);
return deps;
}
class univariate_bitblast_solver : public univariate_solver {
// TODO: does it make sense to share m and bv between different solver instances?
// TODO: consider pooling solvers to save setup overhead, see if solver/solver_pool.h can be used
@ -33,6 +39,7 @@ namespace polysat {
scoped_ptr<bv_util> bv;
scoped_ptr<solver> s;
unsigned bit_width;
unsigned m_scope_level = 0;
func_decl_ref x_decl;
expr_ref x;
vector<rational> model_cache;
@ -46,6 +53,7 @@ namespace polysat {
bv = alloc(bv_util, m);
params_ref p;
p.set_bool("bv.polysat", false);
// p.set_bool("smt", true);
s = mk_solver(m, p, false, true, true, symbol::null);
x_decl = m.mk_const_decl("x", bv->mk_sort(bit_width));
x = m.mk_const(x_decl);
@ -59,7 +67,8 @@ namespace polysat {
}
void push_cache() {
model_cache.push_back(model_cache.back());
rational v = model_cache.back();
model_cache.push_back(v);
}
void pop_cache() {
@ -67,19 +76,37 @@ namespace polysat {
}
void push() override {
m_scope_level++;
push_cache();
s->push();
}
void pop(unsigned n) override {
SASSERT(scope_level() >= n);
m_scope_level -= n;
pop_cache();
s->pop(n);
}
unsigned scope_level() override {
return m_scope_level;
}
expr* mk_numeral(rational const& r) const {
return bv->mk_numeral(r, bit_width);
}
bool is_zero(univariate const& p) const {
for (auto n : p)
if (n != 0)
return false;
return true;
}
bool is_zero(rational const& p) const {
return p.is_zero();
}
#if 0
// [d,c,b,a] --> ((a*x + b)*x + c)*x + d
expr* mk_poly(univariate const& p) const {
@ -97,35 +124,43 @@ namespace polysat {
}
}
#else
// TODO: shouldn't the simplification step of the underlying solver already support this transformation? how to enable?
// 2^k*x --> x << k
// n*x --> n * x
expr* mk_poly_term(rational const& coeff, expr* xpow) const {
unsigned pow;
SASSERT(!coeff.is_zero());
if (coeff.is_one())
return xpow;
if (coeff.is_power_of_two(pow))
return bv->mk_bv_shl(xpow, mk_numeral(rational(pow)));
else
return bv->mk_bv_mul(mk_numeral(coeff), xpow);
return bv->mk_bv_mul(mk_numeral(coeff), xpow);
}
// [d,c,b,a] --> d + c*x + b*(x*x) + a*(x*x*x)
expr* mk_poly(univariate const& p) const {
if (p.empty()) {
return mk_numeral(rational::zero());
}
expr_ref mk_poly(univariate const& p) {
expr_ref e(m);
if (p.empty())
e = mk_numeral(rational::zero());
else {
expr* e = mk_numeral(p[0]);
expr* xpow = x;
if (!p[0].is_zero())
e = mk_numeral(p[0]);
expr_ref xpow = x;
for (unsigned i = 1; i < p.size(); ++i) {
if (!p[i].is_zero()) {
expr* t = mk_poly_term(p[i], xpow);
e = bv->mk_bv_add(e, t);
e = e ? bv->mk_bv_add(e, t) : t;
}
if (i + 1 < p.size())
xpow = bv->mk_bv_mul(xpow, x);
}
return e;
if (!e)
e = mk_numeral(p[0]);
}
return e;
}
expr_ref mk_poly(rational const& p) {
return {mk_numeral(p), m};
}
#endif
@ -133,15 +168,29 @@ namespace polysat {
reset_cache();
if (sign)
e = m.mk_not(e);
expr* a = m.mk_const(m.mk_const_decl(symbol(dep), m.mk_bool_sort()));
s->assert_expr(e, a);
IF_VERBOSE(10, verbose_stream() << "(assert (! " << expr_ref(e, m) << " :named " << expr_ref(a, m) << "))\n");
if (dep == null_dep) {
s->assert_expr(e);
IF_VERBOSE(10, verbose_stream() << "(assert " << expr_ref(e, m) << ")\n");
}
else {
expr* a = m.mk_const(m.mk_const_decl(symbol(dep), m.mk_bool_sort()));
s->assert_expr(e, a);
IF_VERBOSE(10, verbose_stream() << "(assert (! " << expr_ref(e, m) << " :named " << expr_ref(a, m) << "))\n");
}
}
void add_ule(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) override {
add(bv->mk_ule(mk_poly(lhs), mk_poly(rhs)), sign, dep);
template <typename lhs_t, typename rhs_t>
void add_ule_impl(lhs_t const& lhs, rhs_t const& rhs, bool sign, dep_t dep) {
if (is_zero(rhs))
add(m.mk_eq(mk_poly(lhs), mk_poly(rhs)), sign, dep);
else
add(bv->mk_ule(mk_poly(lhs), mk_poly(rhs)), sign, dep);
}
void add_ule(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) override { add_ule_impl(lhs, rhs, sign, dep); }
void add_ule(univariate const& lhs, rational const& rhs, bool sign, dep_t dep) override { add_ule_impl(lhs, rhs, sign, dep); }
void add_ule(rational const& lhs, univariate const& rhs, bool sign, dep_t dep) override { add_ule_impl(lhs, rhs, sign, dep); }
void add_umul_ovfl(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) override {
add(bv->mk_bvumul_no_ovfl(mk_poly(lhs), mk_poly(rhs)), !sign, dep);
}
@ -183,11 +232,14 @@ namespace polysat {
}
void add_ule_const(rational const& val, bool sign, dep_t dep) override {
add(bv->mk_ule(x, mk_numeral(val)), sign, dep);
if (val == 0)
add(m.mk_eq(x, mk_poly(val)), sign, dep);
else
add(bv->mk_ule(x, mk_poly(val)), sign, dep);
}
void add_uge_const(rational const& val, bool sign, dep_t dep) override {
add(bv->mk_ule(mk_numeral(val), x), sign, dep);
add(bv->mk_ule(mk_poly(val), x), sign, dep);
}
void add_bit(unsigned idx, bool sign, dep_t dep) override {
@ -198,16 +250,16 @@ namespace polysat {
return s->check_sat();
}
dep_vector unsat_core() override {
dep_vector deps;
void unsat_core(dep_vector& deps) override {
deps.reset();
expr_ref_vector core(m);
s->get_unsat_core(core);
for (expr* a : core) {
unsigned dep = to_app(a)->get_decl()->get_name().get_num();
deps.push_back(dep);
}
IF_VERBOSE(10, verbose_stream() << "core " << deps << "\n");
SASSERT(deps.size() > 0);
return deps;
}
rational model() override {
@ -232,7 +284,7 @@ namespace polysat {
// try reducing val by setting bits to 0, starting at the msb.
for (unsigned k = bit_width; k-- > 0; ) {
if (!val.get_bit(k)) {
add_bit0(k, 0);
add_bit0(k, null_dep);
continue;
}
// try decreasing k-th bit
@ -245,9 +297,9 @@ namespace polysat {
}
pop(1);
if (result == l_true)
add_bit0(k, 0);
add_bit0(k, null_dep);
else if (result == l_false)
add_bit1(k, 0);
add_bit1(k, null_dep);
else
return false;
}
@ -274,9 +326,9 @@ namespace polysat {
}
pop(1);
if (result == l_true)
add_bit1(k, 0);
add_bit1(k, null_dep);
else if (result == l_false)
add_bit0(k, 0);
add_bit0(k, null_dep);
else
return false;
}
@ -284,6 +336,27 @@ namespace polysat {
return true;
}
bool find_two(rational& out1, rational& out2) override {
out1 = model();
bool ok = true;
push();
add(m.mk_eq(mk_numeral(out1), x), true, null_dep);
switch (check()) {
case l_true:
out2 = model();
break;
case l_false:
out2 = out1;
break;
default:
ok = false;
break;
}
pop(1);
IF_VERBOSE(10, verbose_stream() << "viable " << out1 << " " << out2 << "\n");
return ok;
}
std::ostream& display(std::ostream& out) const override {
return out << *s;
}

View file

@ -35,17 +35,21 @@ namespace polysat {
/// e.g., the vector [ c, b, a ] represents a*x^2 + b*x + c.
using univariate = vector<rational>;
const dep_t null_dep = UINT_MAX;
virtual ~univariate_solver() = default;
virtual void push() = 0;
virtual void pop(unsigned n) = 0;
virtual unsigned scope_level() = 0;
virtual lbool check() = 0;
/**
* Precondition: check() returned l_false
*/
virtual dep_vector unsat_core() = 0;
dep_vector unsat_core();
virtual void unsat_core(dep_vector& out_deps) = 0;
/**
* Precondition: check() returned l_true
@ -68,7 +72,31 @@ namespace polysat {
*/
virtual bool find_max(rational& out_max) = 0;
/**
* Find up to two viable values.
*
* Precondition: check() returned l_true
* returns: true on success, false on resource out
*/
virtual bool find_two(rational& out1, rational& out2) = 0;
/** lhs <= rhs */
virtual void add_ule(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) = 0;
virtual void add_ule(univariate const& lhs, rational const& rhs, bool sign, dep_t dep) = 0;
virtual void add_ule(rational const& lhs, univariate const& rhs, bool sign, dep_t dep) = 0;
/** lhs >= rhs */
void add_uge(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) { add_ule(rhs, lhs, sign, dep); }
void add_uge(univariate const& lhs, rational const& rhs, bool sign, dep_t dep) { add_ule(rhs, lhs, sign, dep); }
void add_uge(rational const& lhs, univariate const& rhs, bool sign, dep_t dep) { add_ule(rhs, lhs, sign, dep); }
/** lhs < rhs */
void add_ult(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) { add_ule(rhs, lhs, !sign, dep); }
void add_ult(univariate const& lhs, rational const& rhs, bool sign, dep_t dep) { add_ule(rhs, lhs, !sign, dep); }
void add_ult(rational const& lhs, univariate const& rhs, bool sign, dep_t dep) { add_ule(rhs, lhs, !sign, dep); }
/** lhs > rhs */
void add_ugt(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) { add_ule(lhs, rhs, !sign, dep); }
void add_ugt(univariate const& lhs, rational const& rhs, bool sign, dep_t dep) { add_ule(lhs, rhs, !sign, dep); }
void add_ugt(rational const& lhs, univariate const& rhs, bool sign, dep_t dep) { add_ule(lhs, rhs, !sign, dep); }
virtual void add_umul_ovfl(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) = 0;
virtual void add_smul_ovfl(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) = 0;
virtual void add_smul_udfl(univariate const& lhs, univariate const& rhs, bool sign, dep_t dep) = 0;

View file

@ -23,16 +23,26 @@ TODO: improve management of the fallback univariate solvers:
- incrementally add/remove constraints
- set resource limit of univariate solver
TODO: plan to fix the FI "pumping":
1. simple looping detection and bitblasting fallback. -- done
2. intervals at multiple bit widths
- for equations, this will give us exact solutions for all coefficients
- for inequalities, a coefficient 2^k*a means that intervals are periodic because the upper k bits of x are irrelevant;
storing the interval for x[K-k:0] would take care of this.
--*/
#include "util/debug.h"
#include "math/polysat/viable.h"
#include "math/polysat/solver.h"
#include "math/polysat/number.h"
#include "math/polysat/univariate/univariate_solver.h"
namespace polysat {
using namespace viable_query;
struct inf_fi : public inference {
viable& v;
pvar var;
@ -137,7 +147,7 @@ namespace polysat {
prop = true;
break;
case find_t::empty:
s.set_conflict(v, false);
SASSERT(s.is_conflict());
return true;
default:
break;
@ -159,6 +169,21 @@ namespace polysat {
// - side conditions
// - i.lo() == i.lo_val() for each unit interval i
// - i.hi() == i.hi_val() for each unit interval i
// NSB review:
// the bounds added by x < p and p < x in forbidden_intervals
// match_non_max, match_non_zero
// use values that are approximations. Then the propagations in
// try_assign_eval are incorrect.
// For example, x > p means x has forbidden interval [0, p + 1[,
// the numeric interval is [0, 1[, but p + 1 == 1 is not ensured
// even p may have free variables.
// the proper side condition on p + 1 is -1 > p or -2 >= p or p + 1 != 0
// I am disabling match_non_max and match_non_zero from forbidden_interval
// The narrowing rules in ule_constraint already handle the bounds propagaitons
// as it propagates p != -1 and 0 != q (p < -1, q > 0),
//
for (auto const& c : get_constraints(v)) {
s.try_assign_eval(c);
}
@ -299,8 +324,14 @@ namespace polysat {
if (!e)
return true;
entry const* first = e;
rational const& max_value = s.var2pdd(v).max_value();
rational mod_value = max_value + 1;
auto& m = s.var2pdd(v);
unsigned const N = m.power_of_2();
rational const& max_value = m.max_value();
rational const& mod_value = m.two_to_N();
// Rotate the 'first' entry, to prevent getting stuck in a refinement loop
// with an early entry when a later entry could give a better interval.
m_equal_lin[v] = m_equal_lin[v]->next();
auto delta_l = [&](rational const& coeff_val) {
return floor((coeff_val - e->interval.lo_val()) / e->coeff);
@ -338,22 +369,79 @@ namespace polysat {
}
};
do {
LOG("refine-equal-lin for src: " << e->src);
rational coeff_val = mod(e->coeff * val, mod_value);
if (e->interval.currently_contains(coeff_val)) {
LOG("refine-equal-lin for src: " << lit_pp(s, e->src));
LOG("forbidden interval v" << v << " " << num_pp(s, v, val) << " " << num_pp(s, v, e->coeff, true) << " * " << e->interval);
if (e->interval.lo_val().is_one() && e->interval.hi_val().is_zero() && e->coeff.is_odd()) {
rational lo(1);
rational hi(0);
LOG("refine-equal-lin: " << " [" << lo << ", " << hi << "[");
pdd lop = s.var2pdd(v).mk_val(lo);
pdd hip = s.var2pdd(v).mk_val(hi);
if (mod(e->interval.hi_val() + 1, mod_value) == e->interval.lo_val()) {
// We have an equation: a * v == b
rational const a = e->coeff;
rational const b = e->interval.hi_val();
LOG("refine-equal-lin: equation detected: " << dd::val_pp(m, a, true) << " * v" << v << " == " << dd::val_pp(m, b, false));
unsigned const parity_a = get_parity(a, N);
unsigned const parity_b = get_parity(b, N);
// LOG("a " << a << " parity " << parity_a);
// LOG("b " << b << " parity " << parity_b);
if (parity_a > parity_b) {
// No solution
LOG("refined: no solution due to parity");
entry* ne = alloc_entry();
ne->refined = e;
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
ne->interval = eval_interval::full();
intersect(v, ne);
return false;
}
if (parity_a == 0) {
// "fast path" for odd a
rational a_inv;
VERIFY(a.mult_inverse(N, a_inv));
rational const hi = mod(a_inv * b, mod_value);
rational const lo = mod(hi + 1, mod_value);
LOG("refined to [" << num_pp(s, v, lo) << ", " << num_pp(s, v, hi) << "[");
SASSERT_EQ(mod(a * hi, mod_value), b); // hi is the solution
entry* ne = alloc_entry();
ne->refined = e;
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
ne->interval = eval_interval::proper(m.mk_val(lo), lo, m.mk_val(hi), hi);
SASSERT(ne->interval.currently_contains(val));
intersect(v, ne);
return false;
}
// 2^k * v == a_inv * b
// 2^k solutions because only the lower N-k bits of v are fixed.
//
// Smallest solution is v0 == a_inv * (b >> k)
// Solutions are of the form v_i = v0 + 2^(N-k) * i for i in { 0, 1, ..., 2^k - 1 }.
// Forbidden intervals: [v_i + 1; v_{i+1}[ == [ v_i + 1; v_i + 2^(N-k) [
// We need the interval that covers val:
// v_i + 1 <= val < v_i + 2^(N-k)
//
// TODO: create one interval for v[N-k:] instead of 2^k intervals for v.
unsigned const k = parity_a;
rational const a_inv = a.pseudo_inverse(N);
unsigned const N_minus_k = N - k;
rational const two_to_N_minus_k = rational::power_of_two(N_minus_k);
rational const v0 = mod(a_inv * machine_div2k(b, k), two_to_N_minus_k);
SASSERT(mod(val, two_to_N_minus_k) != v0); // val is not a solution
rational const vi = v0 + clear_lower_bits(mod(val - v0, mod_value), N_minus_k);
rational const lo = mod(vi + 1, mod_value);
rational const hi = mod(vi + two_to_N_minus_k, mod_value);
LOG("refined to [" << num_pp(s, v, lo) << ", " << num_pp(s, v, hi) << "[");
SASSERT_EQ(mod(a * (lo - 1), mod_value), b); // lo-1 is a solution
SASSERT_EQ(mod(a * hi, mod_value), b); // hi is a solution
entry* ne = alloc_entry();
ne->refined = e;
ne->src = e->src;
ne->side_cond = e->side_cond;
ne->coeff = 1;
ne->interval = eval_interval::proper(lop, lo, hip, hi);
ne->interval = eval_interval::proper(m.mk_val(lo), lo, m.mk_val(hi), hi);
SASSERT(ne->interval.currently_contains(val));
intersect(v, ne);
return false;
}
@ -378,13 +466,11 @@ namespace polysat {
lo = val - lambda_l;
increase_hi(hi);
}
LOG("forbidden interval v" << v << " " << num_pp(s, v, val) << " " << num_pp(s, v, e->coeff, true) << " * " << e->interval << " [" << num_pp(s, v, lo) << ", " << num_pp(s, v, hi) << "[");
LOG("refined to [" << num_pp(s, v, lo) << ", " << num_pp(s, v, hi) << "[");
SASSERT(hi <= mod_value);
bool full = (lo == 0 && hi == mod_value);
if (hi == mod_value)
hi = 0;
pdd lop = s.var2pdd(v).mk_val(lo);
pdd hip = s.var2pdd(v).mk_val(hi);
entry* ne = alloc_entry();
ne->refined = e;
ne->src = e->src;
@ -393,7 +479,7 @@ namespace polysat {
if (full)
ne->interval = eval_interval::full();
else
ne->interval = eval_interval::proper(lop, lo, hip, hi);
ne->interval = eval_interval::proper(m.mk_val(lo), lo, m.mk_val(hi), hi);
intersect(v, ne);
return false;
}
@ -412,6 +498,10 @@ namespace polysat {
rational const& max_value = s.var2pdd(v).max_value();
rational const mod_value = max_value + 1;
// Rotate the 'first' entry, to prevent getting stuck in a refinement loop
// with an early entry when a later entry could give a better interval.
m_diseq_lin[v] = m_diseq_lin[v]->next();
do {
LOG("refine-disequal-lin for src: " << e->src);
// We compute an interval if the concrete value 'val' violates the constraint:
@ -543,88 +633,146 @@ namespace polysat {
return refine_viable(v, val);
}
rational viable::min_viable(pvar v) {
refined:
rational lo(0);
auto* e = m_units[v];
if (!e && !refine_viable(v, lo))
goto refined;
if (!e)
return lo;
entry* first = e;
entry* last = first->prev();
if (last->interval.currently_contains(lo))
lo = last->interval.hi_val();
do {
if (!e->interval.currently_contains(lo))
break;
lo = e->interval.hi_val();
e = e->next();
find_t viable::find_viable(pvar v, rational& lo) {
rational hi;
switch (find_viable(v, lo, hi)) {
case l_true:
return (lo == hi) ? find_t::singleton : find_t::multiple;
case l_false:
return find_t::empty;
default:
return find_t::resource_out;
}
while (e != first);
if (!refine_viable(v, lo))
goto refined;
SASSERT(is_viable(v, lo));
return lo;
}
rational viable::max_viable(pvar v) {
refined:
rational hi = s.var2pdd(v).max_value();
auto* e = m_units[v];
if (!e && !refine_viable(v, hi))
goto refined;
if (!e)
return hi;
entry* last = e->prev();
e = last;
do {
if (!e->interval.currently_contains(hi))
break;
hi = e->interval.lo_val() - 1;
e = e->prev();
}
while (e != last);
if (!refine_viable(v, hi))
goto refined;
SASSERT(is_viable(v, hi));
return hi;
lbool viable::find_viable(pvar v, rational& lo, rational& hi) {
std::pair<rational&, rational&> args{lo, hi};
return query<query_t::find_viable>(v, args);
}
// template <viable::query_t mode>
find_t viable::query(query_t mode, pvar v, rational& lo, rational& hi) {
SASSERT(mode == query_t::find_viable); // other modes are TODO
lbool viable::min_viable(pvar v, rational& lo) {
return query<query_t::min_viable>(v, lo);
}
auto const& max_value = s.var2pdd(v).max_value();
lbool viable::max_viable(pvar v, rational& hi) {
return query<query_t::max_viable>(v, hi);
}
bool viable::has_upper_bound(pvar v, rational& out_hi, vector<signed_constraint>& out_c) {
entry const* first = m_units[v];
entry const* e = first;
bool found = false;
out_c.reset();
do {
found = false;
do {
if (!e->refined) {
auto const& lo = e->interval.lo();
auto const& hi = e->interval.hi();
if (lo.is_val() && hi.is_val()) {
if (out_c.empty() && lo.val() > hi.val()) {
out_c.push_back(e->src);
out_hi = lo.val() - 1;
found = true;
}
else if (!out_c.empty() && lo.val() <= out_hi && out_hi < hi.val()) {
out_c.push_back(e->src);
out_hi = lo.val() - 1;
found = true;
}
}
}
e = e->next();
}
while (e != first);
}
while (found);
return !out_c.empty();
}
bool viable::has_lower_bound(pvar v, rational& out_lo, vector<signed_constraint>& out_c) {
entry const* first = m_units[v];
entry const* e = first;
bool found = false;
out_c.reset();
do {
found = false;
do {
if (!e->refined) {
auto const& lo = e->interval.lo();
auto const& hi = e->interval.hi();
if (lo.is_val() && hi.is_val()) {
if (out_c.empty() && hi.val() != 0 && (lo.val() == 0 || lo.val() > hi.val())) {
out_c.push_back(e->src);
out_lo = hi.val();
found = true;
}
else if (!out_c.empty() && lo.val() <= out_lo && out_lo < hi.val()) {
out_c.push_back(e->src);
out_lo = hi.val();
found = true;
}
}
}
e = e->next();
}
while (e != first);
}
while (found);
return !out_c.empty();
}
template <query_t mode>
lbool viable::query(pvar v, typename query_result<mode>::result_t& result) {
// max number of interval refinements before falling back to the univariate solver
unsigned const refinement_budget = 1000;
unsigned refinements = refinement_budget;
refined:
while (refinements--) {
lbool res = l_undef;
if (!refinements) {
LOG("Refinement budget exhausted! Fall back to univariate solver.");
return find_t::resource_out;
if constexpr (mode == query_t::find_viable)
res = query_find(v, result.first, result.second);
else if constexpr (mode == query_t::min_viable)
res = query_min(v, result);
else if constexpr (mode == query_t::max_viable)
res = query_max(v, result);
else if constexpr (mode == query_t::has_viable) {
NOT_IMPLEMENTED_YET();
}
else {
UNREACHABLE();
}
if (res != l_undef)
return res;
}
refinements--;
LOG("Refinement budget exhausted! Fall back to univariate solver.");
return query_fallback<mode>(v, result);
}
lbool viable::query_find(pvar v, rational& lo, rational& hi) {
auto const& max_value = s.var2pdd(v).max_value();
lbool const refined = l_undef;
// After a refinement, any of the existing entries may have been replaced
// (if it is subsumed by the new entry created during refinement).
// For this reason, we start chasing the intervals from the start again.
lo = 0;
hi = max_value;
auto* e = m_units[v];
if (!e && !refine_viable(v, lo))
goto refined;
if (!e && !refine_viable(v, rational::one()))
goto refined;
return refined;
if (!e && !refine_viable(v, hi))
return refined;
if (!e)
return find_t::multiple;
if (e->interval.is_full())
return find_t::empty;
return l_true;
if (e->interval.is_full()) {
s.set_conflict_by_viable_interval(v);
return l_false;
}
entry* first = e;
entry* last = first->prev();
@ -635,10 +783,10 @@ namespace polysat {
last->interval.hi_val() < max_value) {
lo = last->interval.hi_val();
if (!refine_viable(v, lo))
goto refined;
return refined;
if (!refine_viable(v, max_value))
goto refined;
return find_t::multiple;
return refined;
return l_true;
}
// find lower bound
@ -652,8 +800,10 @@ namespace polysat {
}
while (e != first);
if (e->interval.currently_contains(lo))
return find_t::empty;
if (e->interval.currently_contains(lo)) {
s.set_conflict_by_viable_interval(v);
return l_false;
}
// find upper bound
hi = max_value;
@ -666,51 +816,22 @@ namespace polysat {
}
while (e != last);
if (!refine_viable(v, lo))
goto refined;
return refined;
if (!refine_viable(v, hi))
goto refined;
if (lo == hi)
return find_t::singleton;
else
return find_t::multiple;
return refined;
return l_true;
}
find_t viable::find_viable(pvar v, rational& lo) {
#if 1
rational hi;
// return query<query_t::find_viable>(v, lo, hi);
return query(query_t::find_viable, v, lo, hi);
#else
refined:
lbool viable::query_min(pvar v, rational& lo) {
// TODO: should be able to deal with UNSAT case; since also min_viable has to deal with it due to fallback solver
lo = 0;
auto* e = m_units[v];
entry* e = m_units[v];
if (!e && !refine_viable(v, lo))
goto refined;
if (!e && !refine_viable(v, rational::one()))
goto refined;
return l_undef;
if (!e)
return find_t::multiple;
if (e->interval.is_full())
return find_t::empty;
return l_true;
entry* first = e;
entry* last = first->prev();
// quick check: last interval does not wrap around
// and has space for 2 unassigned values.
auto& max_value = s.var2pdd(v).max_value();
if (last->interval.lo_val() < last->interval.hi_val() &&
last->interval.hi_val() < max_value) {
lo = last->interval.hi_val();
if (!refine_viable(v, lo))
goto refined;
if (!refine_viable(v, max_value))
goto refined;
return find_t::multiple;
}
// find lower bound
if (last->interval.currently_contains(lo))
lo = last->interval.hi_val();
do {
@ -720,12 +841,21 @@ namespace polysat {
e = e->next();
}
while (e != first);
if (!refine_viable(v, lo))
return l_undef;
SASSERT(is_viable(v, lo));
return l_true;
}
if (e->interval.currently_contains(lo))
return find_t::empty;
// find upper bound
rational hi = max_value;
lbool viable::query_max(pvar v, rational& hi) {
// TODO: should be able to deal with UNSAT case; since also max_viable has to deal with it due to fallback solver
hi = s.var2pdd(v).max_value();
auto* e = m_units[v];
if (!e && !refine_viable(v, hi))
return l_undef;
if (!e)
return l_true;
entry* last = e->prev();
e = last;
do {
if (!e->interval.currently_contains(hi))
@ -734,18 +864,126 @@ namespace polysat {
e = e->prev();
}
while (e != last);
if (!refine_viable(v, lo))
goto refined;
if (!refine_viable(v, hi))
goto refined;
if (lo == hi)
return find_t::singleton;
else
return find_t::multiple;
#endif
return l_undef;
SASSERT(is_viable(v, hi));
return l_true;
}
bool viable::resolve(pvar v, conflict& core) {
template <query_t mode>
lbool viable::query_fallback(pvar v, typename query_result<mode>::result_t& result) {
unsigned const bit_width = s.size(v);
univariate_solver* us = s.m_viable_fallback.usolver(bit_width);
sat::literal_set added;
// First step: only query the looping constraints and see if they alone are already UNSAT.
// The constraints which caused the refinement loop will be reached from m_units.
LOG_H3("Checking looping univariate constraints for v" << v << "...");
LOG("Assignment: " << assignments_pp(s));
entry const* first = m_units[v];
entry const* e = first;
do {
entry const* origin = e;
while (origin->refined)
origin = origin->refined;
signed_constraint const c = origin->src;
sat::literal const lit = c.blit();
if (!added.contains(lit)) {
added.insert(lit);
LOG("Adding " << lit_pp(s, lit));
c.add_to_univariate_solver(v, s, *us, lit.to_uint());
}
e = e->next();
}
while (e != first);
switch (us->check()) {
case l_false:
s.set_conflict_by_viable_fallback(v, *us);
return l_false;
case l_true:
// At this point we don't know much because we did not add all relevant constraints
break;
default:
// resource limit
return l_undef;
}
// Second step: looping constraints aren't UNSAT, so add the remaining relevant constraints
LOG_H3("Checking all univariate constraints for v" << v << "...");
auto const& cs = s.m_viable_fallback.m_constraints[v];
for (unsigned i = cs.size(); i-- > 0; ) {
sat::literal const lit = cs[i].blit();
if (added.contains(lit))
continue;
LOG("Adding " << lit_pp(s, lit));
added.insert(lit);
cs[i].add_to_univariate_solver(v, s, *us, lit.to_uint());
}
switch (us->check()) {
case l_false:
s.set_conflict_by_viable_fallback(v, *us);
return l_false;
case l_true:
// pass solver to mode-specific query
break;
default:
// resource limit
return l_undef;
}
if constexpr (mode == query_t::find_viable)
return query_find_fallback(v, *us, result.first, result.second);
if constexpr (mode == query_t::min_viable)
return query_min_fallback(v, *us, result);
if constexpr (mode == query_t::max_viable)
return query_max_fallback(v, *us, result);
if constexpr (mode == query_t::has_viable) {
NOT_IMPLEMENTED_YET();
return l_undef;
}
UNREACHABLE();
return l_undef;
}
lbool viable::query_find_fallback(pvar v, univariate_solver& us, rational& lo, rational& hi) {
return us.find_two(lo, hi) ? l_true : l_undef;
}
lbool viable::query_min_fallback(pvar v, univariate_solver& us, rational& lo) {
return us.find_min(lo) ? l_true : l_undef;
}
lbool viable::query_max_fallback(pvar v, univariate_solver& us, rational& hi) {
return us.find_max(hi) ? l_true : l_undef;
}
bool viable::resolve_fallback(pvar v, univariate_solver& us, conflict& core) {
// The conflict is the unsat core of the univariate solver,
// and the current assignment (under which the constraints are univariate in v)
// TODO:
// - currently we add variables directly, which is sound:
// e.g.: v^2 + w^2 == 0; w := 1
// - but we could use side constraints on the coefficients instead (coefficients when viewed as polynomial over v):
// e.g.: v^2 + w^2 == 0; w^2 == 1
for (unsigned dep : us.unsat_core()) {
sat::literal lit = sat::to_literal(dep);
signed_constraint c = s.lit2cnstr(lit);
core.insert(c);
core.insert_vars(c);
}
SASSERT(!core.vars().contains(v));
core.add_lemma("viable unsat core", core.build_lemma());
verbose_stream() << "unsat core " << core << "\n";
return true;
}
bool viable::resolve_interval(pvar v, conflict& core) {
DEBUG_CODE( log(v); );
if (has_viable(v))
return false;
@ -810,7 +1048,7 @@ namespace polysat {
auto lhs = hi - next_lo;
auto rhs = next_hi - next_lo;
signed_constraint c = s.m_constraints.ult(lhs, rhs);
lemma.insert_eval(~c);
lemma.insert_try_eval(~c); // "try" because linking constraint may contain unassigned variables, see test_polysat::test_bench23_fi_lemma for an example.
}
for (auto sc : e->side_cond)
lemma.insert_eval(~sc);
@ -821,8 +1059,12 @@ namespace polysat {
}
while (e != first);
SASSERT(all_of(lemma, [this](sat::literal lit) { return s.m_bvars.value(lit) == l_false || s.lit2cnstr(lit).is_currently_false(s); }));
// Doesn't hold anymore: we may get new constraints with unassigned variables, see test_polysat::test_bench23_fi_lemma.
// SASSERT(all_of(lemma, [this](sat::literal lit) { return s.m_bvars.value(lit) == l_false || s.lit2cnstr(lit).is_currently_false(s); }));
// NSB review: bench23 exposes a scenario where s.m_bvars.value(lit) == l_true. So the viable lemma is mute, but the literal in the premise
// is a conflict.
// SASSERT(all_of(lemma, [this](sat::literal lit) { return s.m_bvars.value(lit) != l_true; }));
core.add_lemma("viable", lemma.build());
core.logger().log(inf_fi(*this, v));
return true;
@ -947,27 +1189,36 @@ namespace polysat {
return {};
}
find_t viable_fallback::find_viable(pvar v, rational& out_val) {
unsigned bit_width = s.m_size[v];
univariate_solver* viable_fallback::usolver(unsigned bit_width) {
univariate_solver* us;
auto it = m_usolver.find_iterator(bit_width);
if (it != m_usolver.end()) {
us = it->m_value.get();
us->pop(1);
} else {
}
else {
auto& mk_solver = *m_usolver_factory;
m_usolver.insert(bit_width, mk_solver(bit_width));
us = m_usolver[bit_width].get();
}
SASSERT_EQ(us->scope_level(), 0);
// push once on the empty solver so we can reset it before the next use
us->push();
return us;
}
find_t viable_fallback::find_viable(pvar v, rational& out_val) {
unsigned const bit_width = s.m_size[v];
univariate_solver* us = usolver(bit_width);
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);
signed_constraint const c = cs[i];
LOG("Univariate constraint: " << c);
c.add_to_univariate_solver(v, s, *us, c.blit().to_uint());
}
switch (us->check()) {
@ -976,22 +1227,13 @@ namespace polysat {
// we don't know whether the SMT instance has a unique solution
return find_t::multiple;
case l_false:
s.set_conflict_by_viable_fallback(v, *us);
return find_t::empty;
default:
return find_t::resource_out;
}
}
signed_constraints viable_fallback::unsat_core(pvar v) {
unsigned bit_width = s.m_size[v];
SASSERT(m_usolver[bit_width]);
signed_constraints cs;
for (unsigned dep : m_usolver[bit_width]->unsat_core()) {
cs.push_back(m_constraints[v][dep]);
}
return cs;
}
std::ostream& operator<<(std::ostream& out, find_t x) {
switch (x) {
case find_t::empty:

View file

@ -25,6 +25,7 @@ Author:
#include "math/polysat/conflict.h"
#include "math/polysat/constraint.h"
#include "math/polysat/forbidden_intervals.h"
#include <optional>
namespace polysat {
@ -39,6 +40,34 @@ namespace polysat {
resource_out,
};
namespace viable_query {
enum class query_t {
has_viable, // currently only used internally in resolve_viable
min_viable, // currently unused
max_viable, // currently unused
find_viable,
};
template <query_t mode>
struct query_result {
};
template <>
struct query_result<query_t::min_viable> {
using result_t = rational;
};
template <>
struct query_result<query_t::max_viable> {
using result_t = rational;
};
template <>
struct query_result<query_t::find_viable> {
using result_t = std::pair<rational&, rational&>;
};
}
std::ostream& operator<<(std::ostream& out, find_t x);
class viable {
@ -76,15 +105,36 @@ namespace polysat {
void propagate(pvar v, rational const& val);
enum class query_t {
has_viable, // currently only used internally in resolve_viable
min_viable, // currently unused
max_viable, // currently unused
find_viable,
};
/**
* Interval-based queries
* @return l_true on success, l_false on conflict, l_undef on refinement
*/
lbool query_min(pvar v, rational& out_lo);
lbool query_max(pvar v, rational& out_hi);
lbool query_find(pvar v, rational& out_lo, rational& out_hi);
// template <query_t mode>
find_t query(query_t mode, pvar v, rational& out_lo, rational& out_hi);
/**
* Bitblasting-based queries.
* The univariate solver has already been filled with all relevant constraints and check() returned l_true.
* @return l_true on success, l_false on conflict, l_undef on resource limit
*/
lbool query_min_fallback(pvar v, univariate_solver& us, rational& out_lo);
lbool query_max_fallback(pvar v, univariate_solver& us, rational& out_hi);
lbool query_find_fallback(pvar v, univariate_solver& us, rational& out_lo, rational& out_hi);
/**
* Interval-based query with bounded refinement and fallback to bitblasting.
* @return l_true on success, l_false on conflict, l_undef on resource limit
*/
template <viable_query::query_t mode>
lbool query(pvar v, typename viable_query::query_result<mode>::result_t& out_result);
/**
* Bitblasting-based query.
* @return l_true on success, l_false on conflict, l_undef on resource limit
*/
template <viable_query::query_t mode>
lbool query_fallback(pvar v, typename viable_query::query_result<mode>::result_t& out_result);
public:
viable(solver& s);
@ -122,23 +172,54 @@ namespace polysat {
*/
bool is_viable(pvar v, rational const& val);
/*
* Extract min and max viable values for v
/**
* Extract min viable value for v.
* @return l_true on success, l_false on conflict, l_undef on resource limit
*/
rational min_viable(pvar v);
rational max_viable(pvar v);
lbool min_viable(pvar v, rational& out_lo);
/**
* Extract max viable value for v.
* @return l_true on success, l_false on conflict, l_undef on resource limit
*/
lbool max_viable(pvar v, rational& out_hi);
/**
* Query for an upper bound literal for v together with justification.
* @return true if a non-trivial upper bound is found, return justifying constraint.
*/
bool has_upper_bound(pvar v, rational& out_hi, vector<signed_constraint>& out_c);
/**
* Query for an lower bound literal for v together with justification.
* @return true if a non-trivial lower bound is found, return justifying constraint.
*/
bool has_lower_bound(pvar v, rational& out_lo, vector<signed_constraint>& out_c);
/**
* Find a next viable value for variable.
*/
find_t find_viable(pvar v, rational& out_val);
/**
* Find a next viable value for variable. Attempts to find two different values, to distinguish propagation/decision.
* @return l_true on success, l_false on conflict, l_undef on resource limit
*/
lbool find_viable(pvar v, rational& out_lo, rational& out_hi);
/**
* Retrieve the unsat core for v,
* and add the forbidden interval lemma for v (which eliminates v from the unsat core).
* \pre there are no viable values for v
* \pre there are no viable values for v (determined by interval reasoning)
*/
bool resolve(pvar v, conflict& core);
bool resolve_interval(pvar v, conflict& core);
/**
* Retrieve the unsat core for v.
* \pre there are no viable values for v (determined by fallback solver)
*/
bool resolve_fallback(pvar v, univariate_solver& us, conflict& core);
/** Log all viable values for the given variable.
* (Inefficient, but useful for debugging small instances.)
@ -251,6 +332,8 @@ namespace polysat {
}
class viable_fallback {
friend class viable;
solver& s;
scoped_ptr<univariate_solver_factory> m_usolver_factory;
@ -258,6 +341,8 @@ namespace polysat {
vector<signed_constraints> m_constraints;
svector<unsigned> m_constraints_trail;
univariate_solver* usolver(unsigned bit_width);
public:
viable_fallback(solver& s);
@ -275,7 +360,6 @@ namespace polysat {
signed_constraint find_violated_constraint(assignment const& a, pvar v);
find_t find_viable(pvar v, rational& out_val);
signed_constraints unsat_core(pvar v);
};
}

View file

@ -626,8 +626,46 @@ namespace polysat {
cb.insert(s.ule(u, v));
auto cl = cb.build();
simp.apply(*cl);
std::cout << *cl << "\n";
SASSERT(cl->size() == 2);
VERIFY_EQ(cl->size(), 2);
}
// p <= q
// p == q (should be removed)
static void test_clause_simplify2() {
scoped_solver s(__func__);
simplify_clause simp(s);
clause_builder cb(s);
auto u = s.var(s.add_var(32));
auto v = s.var(s.add_var(32));
auto w = s.var(s.add_var(32));
auto p = 2*u*v;
auto q = 7*v*w;
cb.insert(s.ule(p, q));
cb.insert(s.eq(p, q));
auto cl = cb.build();
simp.apply(*cl);
VERIFY_EQ(cl->size(), 1);
VERIFY_EQ((*cl)[0], s.ule(p, q).blit());
}
// p < q
// p == q
// should be merged into p <= q
static void test_clause_simplify3() {
scoped_solver s(__func__);
simplify_clause simp(s);
clause_builder cb(s);
auto u = s.var(s.add_var(32));
auto v = s.var(s.add_var(32));
auto w = s.var(s.add_var(32));
auto p = 2*u*v;
auto q = 7*v*w;
cb.insert(s.ult(p, q));
cb.insert(s.eq(p, q));
auto cl = cb.build();
simp.apply(*cl);
VERIFY_EQ(cl->size(), 1);
VERIFY_EQ((*cl)[0], s.ule(p, q).blit());
}
// 8 * x + 3 == 0 or 8 * x + 5 == 0 is unsat
@ -1099,6 +1137,7 @@ namespace polysat {
if (lemmas.size() == cnt)
return;
LOG_H1("FAIL: Expected " << cnt << " learned lemmas; got " << lemmas.size() << "!");
SASSERT(false);
if (!collect_test_records)
VERIFY(false);
}
@ -1115,6 +1154,7 @@ namespace polysat {
}
}
LOG_H1("FAIL: Lemma " << c1 << " not deduced!");
SASSERT(false);
if (!collect_test_records)
VERIFY(false);
}
@ -1361,7 +1401,7 @@ namespace polysat {
static void test_ineq_non_axiom1(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw - 1);
scoped_solver s(std::string(__func__) + " perm=" + std::to_string(i));
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
@ -1383,7 +1423,7 @@ namespace polysat {
static void test_ineq_axiom2(unsigned bw = 32) {
auto const bound = rational::power_of_two(bw/2);
for (unsigned i = 0; i < 6; ++i) {
scoped_solver s(std::string(__func__) + " perm=" + std::to_string(i));
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
@ -1399,30 +1439,33 @@ namespace polysat {
}
// xy < b & a <= x & !Omega(x*y) => a*y < b
static void test_ineq_axiom3(unsigned bw = 32) {
static void test_ineq_axiom3(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw/2);
for (unsigned i = 0; i < 24; ++i) {
scoped_solver s(std::string(__func__) + " perm=" + std::to_string(i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
auto b = s.var(s.add_var(bw));
permute_args(i, x, y, a, b);
s.add_ult(x * y, b);
s.add_ule(a, x);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.add_ule(b, a * y);
s.check();
s.expect_unsat();
}
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
auto b = s.var(s.add_var(bw));
permute_args(i, x, y, a, b);
s.add_ult(x * y, b);
s.add_ule(a, x);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.add_ule(b, a * y);
s.check();
s.expect_unsat();
}
static void test_ineq_axiom3(unsigned bw = 32) {
for (unsigned i = 0; i < 24; ++i)
test_ineq_axiom3(bw, i);
}
// x*y <= b & a <= x & !Omega(x*y) => a*y <= b
static void test_ineq_axiom4(unsigned bw = 32) {
auto const bound = rational::power_of_two(bw/2);
for (unsigned i = 0; i < 24; ++i) {
scoped_solver s(std::string(__func__) + " perm=" + std::to_string(i));
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
@ -1465,7 +1508,7 @@ namespace polysat {
// a < xy & x <= b & !Omega(b*y) => a < b*y
static void test_ineq_axiom5(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw/2);
scoped_solver s(std::string(__func__) + " perm=" + std::to_string(i));
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
@ -1488,7 +1531,7 @@ namespace polysat {
// a <= xy & x <= b & !Omega(b*y) => a <= b*y
static void test_ineq_axiom6(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw/2);
scoped_solver s(std::string(__func__) + " perm=" + std::to_string(i));
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
@ -1757,6 +1800,29 @@ namespace polysat {
s.expect_sat({{a, 5}});
}
/**
* Motivated by weak forbidden intervals lemma in bench23:
* v66 > v41 && v67 == v66 ==> v67 != 0
*
* Cause by omitting v41 from the symbolic bounds of v66 > v41.
*
* The stronger lemma should be:
* v66 > v41 && v67 == v66 ==> v41 + 1 <= v67
*
* The conclusion could also be v67 > v41 (note that -1 > v41 follows from premise v66 > v41, so both conclusions are equivalent in this lemma).
*/
static void test_bench23_fi_lemma() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(256)); // v41
auto y = s.var(s.add_var(256)); // v66
auto z = s.var(s.add_var(256)); // v67
s.add_ult(x, y); // v66 > v41
s.add_eq(y, z); // v66 == v67
s.add_eq(z, 0); // v67 == 0
s.check();
s.expect_unsat();
}
static void test_bench6_viable() {
scoped_solver s(__func__);
rational coeff("12737129816104781496592808350955669863859698313220462044340334240870444260393");
@ -1768,6 +1834,66 @@ namespace polysat {
s.check();
}
static void test_bench27_viable(const char* coeff) {
scoped_solver s(__func__);
rational a(coeff);
rational b = rational::power_of_two(128) - 1;
auto x = s.var(s.add_var(256));
s.add_ult(0, x);
s.add_ule(a * x + b, b);
s.check();
}
static void test_bench27_viable1() {
test_bench27_viable("2787252867449406954228501409473613420036128"); // 2^5 * a'
}
static void test_bench27_viable2() {
test_bench27_viable("5574846017265734846920466193554658608284160"); // 2^9 * a'
}
// -1*v12 <= -1*v12 + v8 + v7*v1 + 2^128*v7 + 1
// v8 := 0 v12 := 1 v4 := 0 v9 := 1 v17 := 0 v1 := 5574846017265734846920466193554658608283648
//
// Substitute assignment:
// -1 <= (5574846017265734846920466193554658608283648 + 2^128) * v7
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ == 2^142
// this is actually an equation
//
// 2^142 * v7 == -1, unsatisfiable due to parity
static void test_bench27_viable3() {
scoped_solver s(__func__);
rational const a("5574846017265734846920466193554658608283648");
rational const b = rational::power_of_two(128);
auto const v1 = s.var(s.add_var(256));
auto const v7 = s.var(s.add_var(256));
auto const v8 = s.var(s.add_var(256));
auto const v12 = s.var(s.add_var(256));
s.add_ule(-v12, -v12 + v8 + v7*v1 + b*v7 + 1);
s.add_eq(v8, 0);
s.add_eq(v12, 1);
s.add_eq(v1, a);
s.check();
s.expect_unsat();
}
// similar as test_bench27_viable3, but satisfiable (to test the other branch)
static void test_bench27_viable3_sat() {
scoped_solver s(__func__);
rational const a("5574846017265734846920466193554658608283648");
rational const b = rational::power_of_two(128) - 1;
auto const v1 = s.var(s.add_var(256));
auto const v7 = s.var(s.add_var(256));
auto const v8 = s.var(s.add_var(256));
auto const v12 = s.var(s.add_var(256));
s.add_ule(-v12, -v12 + v8 + v7*v1 + b*v7 + 1);
s.add_eq(v8, 0);
s.add_eq(v12, 1);
s.add_eq(v1, a);
s.check();
s.expect_sat();
}
}; // class test_polysat
@ -1898,23 +2024,15 @@ static void STD_CALL polysat_on_ctrl_c(int) {
void tst_polysat() {
using namespace polysat;
polysat::test_polysat::test_band6(4);
#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_parity1();
// test_polysat::test_parity1b();
// test_polysat::test_parity2();
// test_polysat::test_parity3();
// test_polysat::test_parity4();
// test_polysat::test_parity4(32);
test_polysat::test_parity4(256);
// test_polysat::test_ineq2();
// test_polysat::test_ineq_non_axiom4(32, 3); // stuck in viable refinement loop
// test_polysat::test_ineq_non_axiom4(32, 4); // stuck in viable refinement loop
// test_polysat::test_band1();
// test_polysat::test_bench6_viable();
test_polysat::test_ineq_axiom3(32, 3); // TODO: assertion
// test_polysat::test_ineq_axiom6(32, 0); // TODO: assertion
// test_polysat::test_band5(); // TODO: assertion when clause simplification (merging p>q and p=q) is enabled
// test_polysat::test_bench27_viable1(); // TODO: refinement
// test_polysat::test_bench27_viable2(); // TODO: refinement
return;
#endif
@ -1930,6 +2048,16 @@ void tst_polysat() {
set_log_enabled(false);
}
#if 0
RUN(test_polysat::test_elim1());
RUN(test_polysat::test_elim2());
RUN(test_polysat::test_elim3());
RUN(test_polysat::test_elim4());
RUN(test_polysat::test_elim5());
RUN(test_polysat::test_elim6());
RUN(test_polysat::test_elim7());
#endif
RUN(test_polysat::test_parity1());
RUN(test_polysat::test_parity1b());
RUN(test_polysat::test_parity2());
@ -1937,6 +2065,9 @@ void tst_polysat() {
RUN(test_polysat::test_parity4());
RUN(test_polysat::test_clause_simplify1());
RUN(test_polysat::test_clause_simplify2());
// RUN(test_polysat::test_clause_simplify3()); // TODO: corresponding simplification is disabled at the moment
RUN(test_polysat::test_bench23_fi_lemma());
RUN(test_polysat::test_add_conflicts());
RUN(test_polysat::test_wlist());
@ -1966,14 +2097,6 @@ void tst_polysat() {
RUN(test_polysat::test_var_minimize()); // works but var_minimize isn't used (UNSAT before lemma is created)
RUN(test_polysat::test_elim1());
RUN(test_polysat::test_elim2());
RUN(test_polysat::test_elim3());
RUN(test_polysat::test_elim4());
RUN(test_polysat::test_elim5());
RUN(test_polysat::test_elim6());
RUN(test_polysat::test_elim7());
RUN(test_polysat::test_ineq1());
RUN(test_polysat::test_ineq2());
RUN(test_polysat::test_monot());
@ -2002,6 +2125,10 @@ void tst_polysat() {
RUN(test_polysat::test_fi_nonmax());
RUN(test_polysat::test_fi_disequal_mild());
RUN(test_polysat::test_bench6_viable());
// RUN(test_polysat::test_bench27_viable1()); // stuck in refinement loop + fallback solver
// RUN(test_polysat::test_bench27_viable2()); // stuck in refinement loop + fallback solver
RUN(test_polysat::test_bench27_viable3());
RUN(test_polysat::test_bench27_viable3_sat());
RUN(test_polysat::test_ineq_axiom1());
RUN(test_polysat::test_ineq_axiom2());
@ -2010,7 +2137,7 @@ void tst_polysat() {
RUN(test_polysat::test_ineq_axiom5());
RUN(test_polysat::test_ineq_axiom6());
RUN(test_polysat::test_ineq_non_axiom1());
//RUN(test_polysat::test_ineq_non_axiom4());
RUN(test_polysat::test_ineq_non_axiom4());
// test_fi::exhaustive();
// test_fi::randomized();

View file

@ -38,16 +38,19 @@ namespace polysat {
auto c = s.ule(x + 3, x + 5);
s.v.intersect(xv, c);
std::cout << s.v << "\n";
std::cout << "min-max " << s.v.min_viable(xv) << " " << s.v.max_viable(xv) << "\n";
std::cout << "min " << s.v.min_viable(xv, val) << " " << val << "\n";
std::cout << "max " << s.v.max_viable(xv, val) << " " << val << "\n";
s.v.intersect(xv, s.ule(x, 2));
std::cout << s.v << "\n";
s.v.intersect(xv, s.ule(1, x));
std::cout << s.v << "\n";
std::cout << "min-max " << s.v.min_viable(xv) << " " << s.v.max_viable(xv) << "\n";
std::cout << "min " << s.v.min_viable(xv, val) << " " << val << "\n";
std::cout << "max " << s.v.max_viable(xv, val) << " " << val << "\n";
s.v.intersect(xv, s.ule(x, 3));
std::cout << s.v << "\n";
std::cout << s.v.find_viable(xv, val) << " " << val << "\n";
std::cout << "min-max " << s.v.min_viable(xv) << " " << s.v.max_viable(xv) << "\n";
std::cout << "min " << s.v.min_viable(xv, val) << " " << val << "\n";
std::cout << "max " << s.v.max_viable(xv, val) << " " << val << "\n";
s.v.intersect(xv, s.ule(3, x));
std::cout << s.v << "\n";
std::cout << s.v.find_viable(xv, val) << " " << val << "\n";

View file

@ -490,6 +490,8 @@ public:
void machine_div_rem(mpz const & a, mpz const & b, mpz & c, mpz & d) { mpz_manager<SYNCH>::machine_div_rem(a, b, c, d); }
void machine_div2k(mpz const & a, unsigned k, mpz & c) { mpz_manager<SYNCH>::machine_div2k(a, k, c); }
void div(mpz const & a, mpz const & b, mpz & c) { mpz_manager<SYNCH>::div(a, b, c); }
void rat_div(mpz const & a, mpz const & b, mpq & c) {
@ -516,6 +518,12 @@ public:
machine_div(a.m_num, b.m_num, c);
}
void machine_idiv2k(mpq const & a, unsigned k, mpq & c) {
SASSERT(is_int(a));
machine_div2k(a.m_num, k, c.m_num);
reset_denominator(c);
}
void idiv(mpq const & a, mpq const & b, mpq & c) {
SASSERT(is_int(a) && is_int(b));
div(a.m_num, b.m_num, c.m_num);

View file

@ -153,3 +153,21 @@ bool rational::mult_inverse(unsigned num_bits, rational & result) const {
return true;
}
/**
* Compute multiplicative pseudo-inverse modulo 2^num_bits:
*
* mod(n * n.pseudo_inverse(bits), 2^bits) == 2^k,
* where k is maximal such that 2^k divides n.
*
* Precondition: number is non-zero.
*/
rational rational::pseudo_inverse(unsigned num_bits) const {
rational result;
rational const& n = *this;
SASSERT(!n.is_zero()); // TODO: or we define pseudo-inverse of 0 as 0.
unsigned const k = n.trailing_zeros();
rational const odd = machine_div2k(n, k);
VERIFY(odd.mult_inverse(num_bits, result));
SASSERT_EQ(mod(n * result, rational::power_of_two(num_bits)), rational::power_of_two(k));
return result;
}

View file

@ -229,6 +229,12 @@ public:
return r;
}
friend inline rational machine_div2k(rational const & r1, unsigned k) {
rational r;
rational::m().machine_idiv2k(r1.m_val, k, r.m_val);
return r;
}
friend inline rational mod(rational const & r1, rational const & r2) {
rational r;
rational::m().mod(r1.m_val, r2.m_val, r.m_val);
@ -355,6 +361,7 @@ public:
}
bool mult_inverse(unsigned num_bits, rational & result) const;
rational pseudo_inverse(unsigned num_bits) const;
static rational const & zero() {
return m_zero;

View file

@ -89,6 +89,10 @@ public:
}
return out;
}
using const_iterator = decltype(m_queue)::const_iterator;
const_iterator begin() const { return m_queue.begin(); }
const_iterator end() const { return m_queue.end(); }
};
inline std::ostream& operator<<(std::ostream& out, var_queue const& queue) {