mirror of
https://github.com/Z3Prover/z3
synced 2025-04-24 17:45:32 +00:00
Polysat: fixes in solver, forbidden intervals for eq_constraint (#5240)
* Rename to neg_cond * Add some logging utilities * Implement case of forbidden interval covering the whole domain * Implement diseq_narrow * Do not activate constraint if we are in a conflict state * comments * Assert that lemma isn't undefined * Update revert_decision to work in the case where narrowing causes propagation * Fix case of non-disjunctive lemma from forbidden intervals * Conflict should not leak outside user scope * Add guard to decide(), some notes * Add test case * Add constraints to watchlist of unassigned variable during propagation * Move common propagation functionality into base class * Combine eq/diseq narrow * Compute forbidden interval for equality constraints by considering them as p <=u 0 (or p >u 0 for disequalities)
This commit is contained in:
parent
04876ba8b7
commit
f7e476a4a0
15 changed files with 350 additions and 130 deletions
|
@ -46,4 +46,32 @@ namespace polysat {
|
|||
return ule(lvl, bvar, static_cast<csign_t>(!sign), b, a, d);
|
||||
}
|
||||
|
||||
bool constraint::propagate(solver& s, pvar v) {
|
||||
LOG_H3("Propagate " << s.m_vars[v] << " in " << *this);
|
||||
SASSERT(!vars().empty());
|
||||
unsigned idx = 0;
|
||||
if (vars()[idx] != v)
|
||||
idx = 1;
|
||||
SASSERT(v == vars()[idx]);
|
||||
// find other watch variable.
|
||||
for (unsigned i = vars().size(); i-- > 2; ) {
|
||||
unsigned other_v = vars()[i];
|
||||
if (!s.is_assigned(other_v)) {
|
||||
s.add_watch(*this, other_v);
|
||||
std::swap(vars()[idx], vars()[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// at most one variable remains unassigned.
|
||||
unsigned other_v = vars()[idx];
|
||||
propagate_core(s, v, other_v);
|
||||
return false;
|
||||
}
|
||||
|
||||
void constraint::propagate_core(solver& s, pvar v, pvar other_v) {
|
||||
(void)v;
|
||||
(void)other_v;
|
||||
narrow(s);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -48,7 +48,8 @@ namespace polysat {
|
|||
bool is_sle() const { return m_kind == ckind_t::sle_t; }
|
||||
ckind_t kind() const { return m_kind; }
|
||||
virtual std::ostream& display(std::ostream& out) const = 0;
|
||||
virtual bool propagate(solver& s, pvar v) = 0;
|
||||
bool propagate(solver& s, pvar v);
|
||||
virtual void propagate_core(solver& s, pvar v, pvar other_v);
|
||||
virtual constraint* resolve(solver& s, pvar v) = 0;
|
||||
virtual bool is_always_false() = 0;
|
||||
virtual bool is_currently_false(solver& s) = 0;
|
||||
|
@ -66,6 +67,8 @@ namespace polysat {
|
|||
bool is_positive() const { return m_status == l_true; }
|
||||
bool is_negative() const { return m_status == l_false; }
|
||||
bool is_undef() const { return m_status == l_undef; }
|
||||
|
||||
/** Precondition: all variables other than v are assigned. */
|
||||
virtual bool forbidden_interval(solver& s, pvar v, eval_interval& i, constraint*& neg_condition) { return false; }
|
||||
};
|
||||
|
||||
|
@ -75,6 +78,10 @@ namespace polysat {
|
|||
scoped_ptr_vector<constraint> m_literals;
|
||||
public:
|
||||
clause() {}
|
||||
clause(scoped_ptr<constraint>&& c) {
|
||||
SASSERT(c);
|
||||
m_literals.push_back(c.detach());
|
||||
}
|
||||
clause(scoped_ptr_vector<constraint>&& literals): m_literals(std::move(literals)) {
|
||||
SASSERT(std::all_of(m_literals.begin(), m_literals.end(), [](constraint* c) { return c != nullptr; }));
|
||||
SASSERT(empty() || std::all_of(m_literals.begin(), m_literals.end(), [this](constraint* c) { return c->level() == level(); }));
|
||||
|
|
|
@ -22,26 +22,6 @@ namespace polysat {
|
|||
return out << p() << (sign() == pos_t ? " == 0" : " != 0") << " [" << m_status << "]";
|
||||
}
|
||||
|
||||
bool eq_constraint::propagate(solver& s, pvar v) {
|
||||
LOG_H3("Propagate " << s.m_vars[v] << " in " << *this);
|
||||
SASSERT(!vars().empty());
|
||||
unsigned idx = 0;
|
||||
if (vars()[idx] != v)
|
||||
idx = 1;
|
||||
SASSERT(v == vars()[idx]);
|
||||
// find other watch variable.
|
||||
for (unsigned i = vars().size(); i-- > 2; ) {
|
||||
if (!s.is_assigned(vars()[i])) {
|
||||
std::swap(vars()[idx], vars()[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// at most one variable remains unassigned.
|
||||
|
||||
narrow(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
constraint* eq_constraint::resolve(solver& s, pvar v) {
|
||||
if (is_positive())
|
||||
return eq_resolve(s, v);
|
||||
|
@ -52,10 +32,50 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void eq_constraint::narrow(solver& s) {
|
||||
if (is_positive())
|
||||
eq_narrow(s);
|
||||
if (is_negative())
|
||||
diseq_narrow(s);
|
||||
SASSERT(!is_undef());
|
||||
LOG("Assignment: " << s.m_search);
|
||||
auto q = p().subst_val(s.m_search);
|
||||
LOG("Substituted: " << p() << " := " << q);
|
||||
if (q.is_zero()) {
|
||||
if (is_positive())
|
||||
return;
|
||||
if (is_negative()) {
|
||||
LOG("Conflict (zero under current assignment)");
|
||||
s.set_conflict(*this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (q.is_never_zero()) {
|
||||
if (is_positive()) {
|
||||
LOG("Conflict (never zero under current assignment)");
|
||||
s.set_conflict(*this);
|
||||
return;
|
||||
}
|
||||
if (is_negative())
|
||||
return;
|
||||
}
|
||||
|
||||
if (q.is_unilinear()) {
|
||||
// a*x + b == 0
|
||||
pvar v = q.var();
|
||||
rational a = q.hi().val();
|
||||
rational b = q.lo().val();
|
||||
bddv const& x = s.var2bits(v).var();
|
||||
bddv lhs = a * x + b;
|
||||
rational zero = rational::zero();
|
||||
bdd xs = is_positive() ? (lhs == zero) : (lhs != zero);
|
||||
s.push_cjust(v, this);
|
||||
s.intersect_viable(v, xs);
|
||||
|
||||
rational val;
|
||||
if (s.find_viable(v, val) == dd::find_t::singleton) {
|
||||
s.propagate(v, val, *this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: what other constraints can be extracted cheaply?
|
||||
}
|
||||
|
||||
bool eq_constraint::is_always_false() {
|
||||
|
@ -87,6 +107,7 @@ namespace polysat {
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Equality constraints
|
||||
*/
|
||||
|
@ -112,39 +133,6 @@ namespace polysat {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void eq_constraint::eq_narrow(solver& s) {
|
||||
LOG("Assignment: " << s.m_search);
|
||||
auto q = p().subst_val(s.m_search);
|
||||
LOG("Substituted: " << p() << " := " << q);
|
||||
if (q.is_zero())
|
||||
return;
|
||||
if (q.is_never_zero()) {
|
||||
LOG("Conflict (never zero under current assignment)");
|
||||
s.set_conflict(*this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (q.is_unilinear()) {
|
||||
// a*x + b == 0
|
||||
pvar v = q.var();
|
||||
rational a = q.hi().val();
|
||||
rational b = q.lo().val();
|
||||
bddv const& x = s.var2bits(v).var();
|
||||
bdd xs = (a * x + b == rational(0));
|
||||
s.push_cjust(v, this);
|
||||
s.intersect_viable(v, xs);
|
||||
|
||||
rational val;
|
||||
if (s.find_viable(v, val) == dd::find_t::singleton) {
|
||||
s.propagate(v, val, *this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: what other constraints can be extracted cheaply?
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disequality constraints
|
||||
|
@ -155,8 +143,67 @@ namespace polysat {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void eq_constraint::diseq_narrow(solver& s) {
|
||||
NOT_IMPLEMENTED_YET();
|
||||
|
||||
|
||||
/// Compute forbidden interval for equality constraint by considering it as p <=u 0 (or p >u 0 for disequality)
|
||||
bool eq_constraint::forbidden_interval(solver& s, pvar v, eval_interval& i, constraint*& neg_condition)
|
||||
{
|
||||
SASSERT(!is_undef());
|
||||
|
||||
// Current only works when degree(v) is at most one
|
||||
unsigned const deg = p().degree(v);
|
||||
if (deg > 1)
|
||||
return false;
|
||||
|
||||
if (deg == 0) {
|
||||
UNREACHABLE(); // this case is not useful for conflict resolution (but it could be handled in principle)
|
||||
// i is empty or full, condition would be this constraint itself?
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned const sz = s.size(v);
|
||||
dd::pdd_manager& m = s.sz2pdd(sz);
|
||||
|
||||
pdd p1 = m.zero();
|
||||
pdd e1 = m.zero();
|
||||
p().factor(v, 1, p1, e1);
|
||||
|
||||
pdd e2 = m.zero();
|
||||
|
||||
// Currently only works if coefficient is a power of two
|
||||
if (!p1.is_val())
|
||||
return false;
|
||||
rational a1 = p1.val();
|
||||
// TODO: to express the interval for coefficient 2^i symbolically, we need right-shift/upper-bits-extract in the language.
|
||||
// So currently we can only do it if the coefficient is 1.
|
||||
if (!a1.is_zero() && !a1.is_one())
|
||||
return false;
|
||||
/*
|
||||
unsigned j1 = 0;
|
||||
if (!a1.is_zero() && !a1.is_power_of_two(j1))
|
||||
return false;
|
||||
*/
|
||||
|
||||
// Concrete values of evaluable terms
|
||||
auto e1s = e1.subst_val(s.m_search);
|
||||
auto e2s = m.zero();
|
||||
SASSERT(e1s.is_val());
|
||||
SASSERT(e2s.is_val());
|
||||
|
||||
// e1 + t <= 0, with t = 2^j1*y
|
||||
// condition for empty/full: 0 == -1, never satisfied, so we always have a proper interval!
|
||||
SASSERT(!a1.is_zero());
|
||||
pdd lo = 1 - e1;
|
||||
rational lo_val = (1 - e1s).val();
|
||||
pdd hi = -e1;
|
||||
rational hi_val = (-e1s).val();
|
||||
if (is_negative()) {
|
||||
swap(lo, hi);
|
||||
lo_val.swap(hi_val);
|
||||
}
|
||||
i = eval_interval::proper(lo, lo_val, hi, hi_val);
|
||||
neg_condition = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,18 +27,16 @@ namespace polysat {
|
|||
~eq_constraint() override {}
|
||||
pdd const & p() const { return m_poly; }
|
||||
std::ostream& display(std::ostream& out) const override;
|
||||
bool propagate(solver& s, pvar v) override;
|
||||
constraint* resolve(solver& s, pvar v) override;
|
||||
bool is_always_false() override;
|
||||
bool is_currently_false(solver& s) override;
|
||||
bool is_currently_true(solver& s) override;
|
||||
void narrow(solver& s) override;
|
||||
bool forbidden_interval(solver& s, pvar v, eval_interval& i, constraint*& neg_condition) override;
|
||||
|
||||
private:
|
||||
constraint* eq_resolve(solver& s, pvar v);
|
||||
void eq_narrow(solver& s);
|
||||
constraint* diseq_resolve(solver& s, pvar v);
|
||||
void diseq_narrow(solver& s);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace polysat {
|
|||
|
||||
struct fi_record {
|
||||
eval_interval interval;
|
||||
scoped_ptr<constraint> cond; // could be multiple constraints later
|
||||
scoped_ptr<constraint> neg_cond; // could be multiple constraints later
|
||||
constraint* src;
|
||||
};
|
||||
|
||||
|
@ -72,15 +72,12 @@ namespace polysat {
|
|||
for (constraint* c : conflict) {
|
||||
LOG("constraint: " << *c);
|
||||
eval_interval interval = eval_interval::full();
|
||||
constraint* cond = nullptr; // TODO: change to scoped_ptr
|
||||
if (c->forbidden_interval(s, v, interval, cond)) {
|
||||
constraint* neg_cond = nullptr; // TODO: change to scoped_ptr
|
||||
if (c->forbidden_interval(s, v, interval, neg_cond)) {
|
||||
LOG("~> interval: " << interval);
|
||||
if (cond)
|
||||
LOG(" cond: " << *cond);
|
||||
else
|
||||
LOG(" cond: <null>");
|
||||
LOG(" neg_cond: " << show_deref(neg_cond));
|
||||
if (interval.is_currently_empty()) {
|
||||
dealloc(cond);
|
||||
dealloc(neg_cond);
|
||||
continue;
|
||||
}
|
||||
if (interval.is_full())
|
||||
|
@ -92,19 +89,20 @@ namespace polysat {
|
|||
longest_i = records.size();
|
||||
}
|
||||
}
|
||||
records.push_back({std::move(interval), cond, c});
|
||||
records.push_back({std::move(interval), neg_cond, c});
|
||||
if (has_full)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("has_full: " << std::boolalpha << has_full);
|
||||
if (has_full) {
|
||||
auto const& full_record = records.back();
|
||||
// We have a single interval covering the whole domain
|
||||
// => the side conditions of that interval are enough to produce a conflict
|
||||
auto& full_record = records.back();
|
||||
SASSERT(full_record.interval.is_full());
|
||||
LOG("has_full: " << std::boolalpha << has_full);
|
||||
// TODO: use full interval to explain
|
||||
NOT_IMPLEMENTED_YET();
|
||||
return false;
|
||||
out_lemma = std::move(full_record.neg_cond);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (records.empty())
|
||||
|
@ -153,9 +151,9 @@ namespace polysat {
|
|||
literals.push_back(c);
|
||||
// Side conditions
|
||||
// TODO: check whether the condition is subsumed by c? maybe at the end do a "lemma reduction" step, to try and reduce branching?
|
||||
scoped_ptr<constraint>& cond = records[i].cond;
|
||||
if (cond)
|
||||
literals.push_back(cond.detach());
|
||||
scoped_ptr<constraint>& neg_cond = records[i].neg_cond;
|
||||
if (neg_cond)
|
||||
literals.push_back(neg_cond.detach());
|
||||
}
|
||||
|
||||
out_lemma = std::move(literals);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "math/polysat/log_helper.h"
|
||||
|
||||
class polysat_log_indent
|
||||
{
|
||||
|
|
44
src/math/polysat/log_helper.h
Normal file
44
src/math/polysat/log_helper.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*++
|
||||
Copyright (c) 2021 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
Logging support
|
||||
|
||||
Abstract:
|
||||
|
||||
Utilities for logging.
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2021-03-19
|
||||
Jakob Rath 2021-04-6
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include "util/util.h"
|
||||
|
||||
template <typename T>
|
||||
struct show_deref_t {
|
||||
T* ptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::ostream& operator<<(std::ostream& os, show_deref_t<T> s) {
|
||||
if (s.ptr)
|
||||
return os << *s.ptr;
|
||||
else
|
||||
return os << "<null>";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
show_deref_t<T> show_deref(T* ptr) {
|
||||
return show_deref_t<T>{ptr};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
show_deref_t<T> show_deref(scoped_ptr<T> const& ptr) {
|
||||
return show_deref_t<T>{ptr.get()};
|
||||
}
|
|
@ -50,7 +50,6 @@ namespace polysat {
|
|||
|
||||
void solver::add_non_viable(pvar v, rational const& val) {
|
||||
LOG("pvar " << v << " /= " << val);
|
||||
TRACE("polysat", tout << "v" << v << " /= " << val << "\n";);
|
||||
SASSERT(is_viable(v, val));
|
||||
auto const& bits = var2bits(v);
|
||||
intersect_viable(v, bits.var() != val);
|
||||
|
@ -120,6 +119,7 @@ namespace polysat {
|
|||
else if (!can_decide()) { LOG_H2("SAT"); return l_true; }
|
||||
else decide();
|
||||
}
|
||||
LOG_H2("UNDEF (resource limit)");
|
||||
return l_undef;
|
||||
}
|
||||
|
||||
|
@ -194,6 +194,21 @@ namespace polysat {
|
|||
}
|
||||
|
||||
bool_var solver::new_sle(pdd const& p, pdd const& q, unsigned dep, csign_t sign) {
|
||||
// To do signed comparison of bitvectors, flip the msb and do unsigned comparison:
|
||||
//
|
||||
// x <=s y <=> x + 2^(w-1) <=u y + 2^(w-1)
|
||||
//
|
||||
// Example for bit width 3:
|
||||
// 111 -1
|
||||
// 110 -2
|
||||
// 101 -3
|
||||
// 100 -4
|
||||
// 011 3
|
||||
// 010 2
|
||||
// 001 1
|
||||
// 000 0
|
||||
//
|
||||
// Argument: flipping the msb swaps the negative and non-negative blocks
|
||||
auto shift = rational::power_of_two(p.power_of_2() - 1);
|
||||
return new_ule(p + shift, q + shift, dep, sign);
|
||||
}
|
||||
|
@ -219,7 +234,11 @@ namespace polysat {
|
|||
LOG("WARN: there is no constraint for bool_var " << v);
|
||||
return;
|
||||
}
|
||||
if (is_conflict())
|
||||
return;
|
||||
SASSERT(c->is_undef());
|
||||
c->assign_eh(is_true);
|
||||
LOG("Activate constraint: " << *c);
|
||||
add_watch(*c);
|
||||
m_assign_eh_history.push_back(v);
|
||||
m_trail.push_back(trail_instr_t::assign_eh_i);
|
||||
|
@ -235,6 +254,7 @@ namespace polysat {
|
|||
push_qhead();
|
||||
while (can_propagate())
|
||||
propagate(m_search[m_qhead++].first);
|
||||
SASSERT(wlist_invariant());
|
||||
}
|
||||
|
||||
void solver::propagate(pvar v) {
|
||||
|
@ -250,6 +270,7 @@ namespace polysat {
|
|||
}
|
||||
|
||||
void solver::propagate(pvar v, rational const& val, constraint& c) {
|
||||
LOG("Propagation: pvar " << v << " := " << val << ", due to " << c);
|
||||
if (is_viable(v, val)) {
|
||||
m_free_vars.del_var_eh(v);
|
||||
assign_core(v, val, justification::propagation(m_level));
|
||||
|
@ -326,10 +347,15 @@ namespace polysat {
|
|||
|
||||
void solver::add_watch(constraint& c) {
|
||||
auto const& vars = c.vars();
|
||||
if (vars.size() > 0)
|
||||
m_watch[vars[0]].push_back(&c);
|
||||
if (vars.size() > 1)
|
||||
m_watch[vars[1]].push_back(&c);
|
||||
if (vars.size() > 0)
|
||||
add_watch(c, vars[0]);
|
||||
if (vars.size() > 1)
|
||||
add_watch(c, vars[1]);
|
||||
}
|
||||
|
||||
void solver::add_watch(constraint &c, pvar v) {
|
||||
LOG("watching v" << v << " of constraint " << c);
|
||||
m_watch[v].push_back(&c);
|
||||
}
|
||||
|
||||
void solver::erase_watch(constraint& c) {
|
||||
|
@ -366,10 +392,14 @@ namespace polysat {
|
|||
switch (find_viable(v, val)) {
|
||||
case dd::find_t::empty:
|
||||
LOG("Conflict: no value for pvar " << v);
|
||||
// NOTE: all such cases should be discovered elsewhere (e.g., during propagation/narrowing)
|
||||
// (fail here in debug mode so we notice if we miss some)
|
||||
DEBUG_CODE( UNREACHABLE(); );
|
||||
set_conflict(v);
|
||||
break;
|
||||
case dd::find_t::singleton:
|
||||
LOG("Propagation: pvar " << v << " := " << val << " (due to unique value)");
|
||||
// NOTE: this case may happen legitimately if all other possibilities were excluded by brute force search
|
||||
assign_core(v, val, justification::propagation(m_level));
|
||||
break;
|
||||
case dd::find_t::multiple:
|
||||
|
@ -387,6 +417,7 @@ namespace polysat {
|
|||
++m_stats.m_num_propagations;
|
||||
LOG("pvar " << v << " := " << val << " by " << j);
|
||||
SASSERT(is_viable(v, val));
|
||||
SASSERT(std::all_of(m_search.begin(), m_search.end(), [v](auto p) { return p.first != v; }));
|
||||
m_value[v] = val;
|
||||
m_search.push_back(std::make_pair(v, val));
|
||||
m_trail.push_back(trail_instr_t::assign_i);
|
||||
|
@ -451,12 +482,22 @@ namespace polysat {
|
|||
LOG_H2("Conflict due to empty viable set for pvar " << conflict_var);
|
||||
clause new_lemma;
|
||||
if (forbidden_intervals::explain(*this, m_conflict, conflict_var, new_lemma)) {
|
||||
LOG_H3("Learning disjunctive lemma");
|
||||
LOG_H3("Lemma from forbidden intervals (size: " << new_lemma.size() << ")");
|
||||
for (constraint* c : new_lemma)
|
||||
LOG("Literal: " << *c);
|
||||
SASSERT(new_lemma.size() > 0);
|
||||
if (new_lemma.size() == 1) {
|
||||
lemma = new_lemma.detach()[0];
|
||||
// TODO: continue normally?
|
||||
} else {
|
||||
SASSERT(lemma);
|
||||
lemma->assign_eh(true);
|
||||
reset_marks();
|
||||
for (auto v : lemma->vars())
|
||||
set_mark(v);
|
||||
m_conflict.reset();
|
||||
m_conflict.push_back(lemma.get());
|
||||
// continue normally
|
||||
}
|
||||
else {
|
||||
SASSERT(m_disjunctive_lemma.empty());
|
||||
reset_marks();
|
||||
for (constraint* c : new_lemma) {
|
||||
|
@ -568,13 +609,16 @@ namespace polysat {
|
|||
void solver::learn_lemma(pvar v, constraint* c) {
|
||||
if (!c)
|
||||
return;
|
||||
LOG("Learning: " << *c);
|
||||
SASSERT(m_conflict_level <= m_justification[v].level());
|
||||
push_cjust(v, c);
|
||||
add_lemma(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* variable v was assigned by a decision at position i in the search stack.
|
||||
* Revert a decision that caused a conflict.
|
||||
* Variable v was assigned by a decision at position i in the search stack.
|
||||
*
|
||||
* TODO: we could resolve constraints in cjust[v] against each other to
|
||||
* obtain stronger propagation. Example:
|
||||
* (x + 1)*P = 0 and (x + 1)*Q = 0, where gcd(P,Q) = 1, then we have x + 1 = 0.
|
||||
|
@ -584,23 +628,31 @@ namespace polysat {
|
|||
*/
|
||||
void solver::revert_decision(pvar v) {
|
||||
rational val = m_value[v];
|
||||
LOG_H3("Reverting decision: pvar " << v << " -> " << val);
|
||||
SASSERT(m_justification[v].is_decision());
|
||||
bdd viable = m_viable[v];
|
||||
constraints just(m_cjust[v]);
|
||||
backjump(m_justification[v].level()-1);
|
||||
for (unsigned i = m_cjust[v].size(); i < just.size(); ++i)
|
||||
push_cjust(v, just[i]);
|
||||
for (constraint* c : m_conflict) {
|
||||
push_cjust(v, c);
|
||||
c->narrow(*this);
|
||||
}
|
||||
m_conflict.reset();
|
||||
// Since decision "v -> val" caused a conflict, we may keep all
|
||||
// viability restrictions on v and additionally exclude val.
|
||||
push_viable(v);
|
||||
m_viable[v] = viable;
|
||||
add_non_viable(v, val);
|
||||
m_free_vars.del_var_eh(v);
|
||||
for (unsigned i = m_cjust[v].size(); i < just.size(); ++i)
|
||||
push_cjust(v, just[i]);
|
||||
for (constraint* c : m_conflict) {
|
||||
// Add the conflict as justification for the exclusion of 'val'
|
||||
push_cjust(v, c);
|
||||
// NOTE: in general, narrow may change the conflict.
|
||||
// But since we just backjumped, narrowing should not result in an additional conflict.
|
||||
c->narrow(*this);
|
||||
}
|
||||
m_conflict.reset();
|
||||
narrow(v);
|
||||
decide(v);
|
||||
if (m_justification[v].is_unassigned()) {
|
||||
m_free_vars.del_var_eh(v);
|
||||
decide(v);
|
||||
}
|
||||
}
|
||||
|
||||
void solver::backjump(unsigned new_level) {
|
||||
|
@ -616,10 +668,13 @@ namespace polysat {
|
|||
constraint* solver::resolve(pvar v) {
|
||||
SASSERT(!m_cjust[v].empty());
|
||||
SASSERT(m_justification[v].is_propagation());
|
||||
LOG("resolve pvar " << v);
|
||||
if (m_cjust[v].size() != 1)
|
||||
return nullptr;
|
||||
constraint* d = m_cjust[v].back();
|
||||
return d->resolve(*this, v);
|
||||
constraint* res = d->resolve(*this, v);
|
||||
LOG("resolved: " << show_deref(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -633,6 +688,7 @@ namespace polysat {
|
|||
if (!c)
|
||||
return;
|
||||
LOG("Lemma: " << *c);
|
||||
SASSERT(!c->is_undef());
|
||||
SASSERT(!get_bv2c(c->bvar()));
|
||||
insert_bv2c(c->bvar(), c);
|
||||
add_watch(*c);
|
||||
|
@ -666,6 +722,7 @@ namespace polysat {
|
|||
unsigned base_level = m_base_levels[m_base_levels.size() - num_scopes];
|
||||
LOG("Pop " << num_scopes << " user scopes; lowest popped level = " << base_level << "; current level = " << m_level);
|
||||
pop_levels(m_level - base_level + 1);
|
||||
m_conflict.reset(); // TODO: maybe keep conflict if level of all constraints is lower than base_level?
|
||||
}
|
||||
|
||||
bool solver::at_base_level() const {
|
||||
|
@ -712,6 +769,29 @@ namespace polysat {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that two variables of each constraint are watched.
|
||||
*/
|
||||
bool solver::wlist_invariant() {
|
||||
constraints cs;
|
||||
cs.append(m_constraints.size(), m_constraints.data());
|
||||
cs.append(m_redundant.size(), m_redundant.data());
|
||||
for (auto* c : cs) {
|
||||
unsigned num_watches = 0;
|
||||
for (auto const& wlist : m_watch) {
|
||||
unsigned n = std::count(wlist.begin(), wlist.end(), c);
|
||||
VERIFY(n <= 1); // no duplicates in the watchlist
|
||||
num_watches += n;
|
||||
}
|
||||
switch (c->vars().size()) {
|
||||
case 0: VERIFY(num_watches == 0); break;
|
||||
case 1: VERIFY(num_watches == 1); break;
|
||||
default: VERIFY(num_watches == 2); break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace polysat {
|
|||
stats() { reset(); }
|
||||
};
|
||||
|
||||
friend class constraint;
|
||||
friend class eq_constraint;
|
||||
friend class var_constraint;
|
||||
friend class ule_constraint;
|
||||
|
@ -187,6 +188,7 @@ namespace polysat {
|
|||
void erase_watch(pvar v, constraint& c);
|
||||
void erase_watch(constraint& c);
|
||||
void add_watch(constraint& c);
|
||||
void add_watch(constraint& c, pvar v);
|
||||
|
||||
void set_conflict(constraint& c);
|
||||
void set_conflict(pvar v);
|
||||
|
@ -225,6 +227,7 @@ namespace polysat {
|
|||
|
||||
bool invariant();
|
||||
bool invariant(scoped_ptr_vector<constraint> const& cs);
|
||||
bool wlist_invariant();
|
||||
|
||||
public:
|
||||
|
||||
|
|
|
@ -22,30 +22,12 @@ namespace polysat {
|
|||
return out << m_lhs << (sign() == pos_t ? " <=u " : " >u ") << m_rhs << " [" << m_status << "]";
|
||||
}
|
||||
|
||||
bool ule_constraint::propagate(solver& s, pvar v) {
|
||||
LOG_H3("Propagate " << s.m_vars[v] << " in " << *this);
|
||||
SASSERT(!vars().empty());
|
||||
unsigned idx = 0;
|
||||
if (vars()[idx] != v)
|
||||
idx = 1;
|
||||
SASSERT(v == vars()[idx]);
|
||||
// find other watch variable.
|
||||
for (unsigned i = vars().size(); i-- > 2; ) {
|
||||
if (!s.is_assigned(vars()[i])) {
|
||||
std::swap(vars()[idx], vars()[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
narrow(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
constraint* ule_constraint::resolve(solver& s, pvar v) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ule_constraint::narrow(solver& s) {
|
||||
SASSERT(!is_undef());
|
||||
LOG("Assignment: " << s.m_search);
|
||||
auto p = lhs().subst_val(s.m_search);
|
||||
LOG("Substituted LHS: " << lhs() << " := " << p);
|
||||
|
@ -136,9 +118,6 @@ namespace polysat {
|
|||
return p.is_val() && q.is_val() && p.val() > q.val();
|
||||
}
|
||||
|
||||
/**
|
||||
* Precondition: all variables other than v are assigned.
|
||||
*/
|
||||
bool ule_constraint::forbidden_interval(solver& s, pvar v, eval_interval& i, constraint*& neg_condition)
|
||||
{
|
||||
SASSERT(!is_undef());
|
||||
|
|
|
@ -32,7 +32,6 @@ namespace polysat {
|
|||
pdd const& lhs() const { return m_lhs; }
|
||||
pdd const& rhs() const { return m_rhs; }
|
||||
std::ostream& display(std::ostream& out) const override;
|
||||
bool propagate(solver& s, pvar v) override;
|
||||
constraint* resolve(solver& s, pvar v) override;
|
||||
bool is_always_false(pdd const& lhs, pdd const& rhs);
|
||||
bool is_always_false() override;
|
||||
|
|
|
@ -21,11 +21,6 @@ namespace polysat {
|
|||
return out << "v" << m_var << ": " << m_viable << "\n";
|
||||
}
|
||||
|
||||
bool var_constraint::propagate(solver& s, pvar v) {
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
|
||||
constraint* var_constraint::resolve(solver& s, pvar v) {
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
|
|
|
@ -33,7 +33,6 @@ namespace polysat {
|
|||
constraint(lvl, bvar, sign, dep, ckind_t::bit_t), m_var(v), m_viable(b) {}
|
||||
~var_constraint() override {}
|
||||
std::ostream& display(std::ostream& out) const override;
|
||||
bool propagate(solver& s, pvar v) override;
|
||||
constraint* resolve(solver& s, pvar v) override;
|
||||
void narrow(solver& s) override;
|
||||
bool is_always_false() override;
|
||||
|
|
|
@ -63,6 +63,21 @@ namespace polysat {
|
|||
s.check();
|
||||
}
|
||||
|
||||
/// Has constraints which must be inserted into other watchlist to discover UNSAT
|
||||
static void test_wlist() {
|
||||
scoped_solver s;
|
||||
auto a = s.var(s.add_var(3));
|
||||
auto b = s.var(s.add_var(3));
|
||||
auto c = s.var(s.add_var(3));
|
||||
auto d = s.var(s.add_var(3));
|
||||
s.add_eq(d + c + b + a + 1);
|
||||
s.add_eq(d + c + b + a);
|
||||
s.add_eq(d + c + b);
|
||||
s.add_eq(d + c);
|
||||
s.add_eq(d);
|
||||
s.check();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* most basic linear equation solving.
|
||||
|
@ -143,6 +158,23 @@ namespace polysat {
|
|||
s.check();
|
||||
}
|
||||
|
||||
/**
|
||||
* unsat
|
||||
* - learns 3*x + 1 == 0 by polynomial resolution
|
||||
* - this forces x == 5, which means the first constraint is unsatisfiable by parity.
|
||||
*/
|
||||
static void test_p3() {
|
||||
scoped_solver s;
|
||||
auto x = s.var(s.add_var(4));
|
||||
auto y = s.var(s.add_var(4));
|
||||
auto z = s.var(s.add_var(4));
|
||||
s.add_eq(x*x*y + 3*y + 7);
|
||||
s.add_eq(2*y + z + 8);
|
||||
s.add_eq(3*x + 4*y*z + 2*z*z + 1);
|
||||
s.check();
|
||||
}
|
||||
|
||||
|
||||
// Unique solution: u = 5
|
||||
static void test_ineq_basic1() {
|
||||
scoped_solver s;
|
||||
|
@ -376,15 +408,23 @@ namespace polysat {
|
|||
|
||||
void tst_polysat() {
|
||||
polysat::test_add_conflicts();
|
||||
polysat::test_wlist();
|
||||
polysat::test_l1();
|
||||
polysat::test_l2();
|
||||
polysat::test_l3();
|
||||
polysat::test_l4();
|
||||
polysat::test_l5();
|
||||
#if 0
|
||||
// worry about this later
|
||||
polysat::test_p1();
|
||||
polysat::test_p2();
|
||||
polysat::test_p3();
|
||||
polysat::test_ineq_basic1();
|
||||
polysat::test_ineq_basic2();
|
||||
polysat::test_ineq_basic3();
|
||||
polysat::test_ineq_basic4();
|
||||
polysat::test_ineq_basic5();
|
||||
polysat::test_ineq_basic6();
|
||||
#if 0
|
||||
// worry about this later
|
||||
polysat::test_ineq1();
|
||||
polysat::test_ineq2();
|
||||
#endif
|
||||
|
|
|
@ -87,6 +87,8 @@ public:
|
|||
return tmp;
|
||||
}
|
||||
|
||||
T* const* data() const { return m_vector.data(); }
|
||||
|
||||
using const_iterator = typename ptr_vector<T>::const_iterator;
|
||||
const_iterator begin() const { return m_vector.begin(); }
|
||||
const_iterator end() const { return m_vector.end(); }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue