Copyright (c) 2013 Microsoft Corporation
Module Name:
theory_pb.cpp
Abstract:
Pseudo-Boolean theory plugin.
Author:
Nikolaj Bjorner (nbjorner) 2013-11-05
Notes:
--*/
#include <typeinfo>
#include "smt/theory_pb.h"
#include "smt/smt_context.h"
#include "smt/smt_kernel.h"
#include "ast/ast_pp.h"
#include "util/sorting_network.h"
#include "util/uint_set.h"
#include "smt/smt_model_generator.h"
#include "ast/rewriter/pb_rewriter_def.h"
#include "math/simplex/sparse_matrix_def.h"
#include "math/simplex/simplex_def.h"
namespace smt {
class pb_lit_rewriter_util {
public:
typedef std::pair<literal, rational> arg_t;
typedef vector<arg_t> args_t;
typedef rational numeral;
literal negate(literal l) {
return ~l;
}
void display(std::ostream& out, literal l) {
out << l;
}
bool is_negated(literal l) const {
return l.sign();
}
bool is_true(literal l) const {
return l == true_literal;
}
bool is_false(literal l) const {
return l == false_literal;
}
struct compare {
bool operator()(arg_t const& a, arg_t const& b) {
return a.first < b.first;
}
};
};
unsigned theory_pb::arg_t::get_hash() const {
return get_composite_hash<arg_t, arg_t::kind_hash, arg_t::child_hash>(*this, size());
}
bool theory_pb::arg_t::operator==(arg_t const& other) const {
if (size() != other.size()) return false;
for (unsigned i = 0; i < size(); ++i) {
if (lit(i) != other.lit(i)) return false;
if (coeff(i) != other.coeff(i)) return false;
}
return true;
}
void theory_pb::arg_t::remove_negations() {
for (unsigned i = 0; i < size(); ++i) {
if (lit(i).sign()) {
(*this)[i].first.neg();
(*this)[i].second.neg();
m_k += coeff(i);
}
}
}
void theory_pb::arg_t::negate() {
numeral sum(0);
for (unsigned i = 0; i < size(); ++i) {
(*this)[i].first.neg();
sum += coeff(i);
}
m_k = sum - m_k + numeral::one();
VERIFY(l_undef == normalize(false));
}
lbool theory_pb::arg_t::normalize(bool is_eq) {
pb_lit_rewriter_util pbu;
pb_rewriter_util<pb_lit_rewriter_util> util(pbu);
return util.normalize(*this, m_k, is_eq);
}
void theory_pb::arg_t::prune(bool is_eq) {
pb_lit_rewriter_util pbu;
pb_rewriter_util<pb_lit_rewriter_util> util(pbu);
util.prune(*this, m_k, is_eq);
}
std::ostream& theory_pb::arg_t::display(context& ctx, std::ostream& out, bool values) const {
for (unsigned i = 0; i < size(); ++i) {
literal l(lit(i));
if (!coeff(i).is_one()) {
out << coeff(i) << "*";
}
out << l;
if (values) {
out << "@(" << ctx.get_assignment(l);
if (ctx.get_assignment(l) != l_undef) {
out << ":" << ctx.get_assign_level(l);
}
out << ")";
}
if (i + 1 < size()) {
out << " + ";
}
}
out << " ~ " << k() << "\n";
return out;
}
app_ref theory_pb::arg_t::to_expr(bool is_eq, context& ctx, ast_manager& m) {
expr_ref tmp(m);
app_ref result(m);
svector<rational> coeffs;
expr_ref_vector args(m);
for (unsigned i = 0; i < size(); ++i) {
ctx.literal2expr(lit(i), tmp);
args.push_back(tmp);
coeffs.push_back(coeff(i));
}
pb_util pb(m);
if (is_eq) {
result = pb.mk_eq(coeffs.size(), coeffs.data(), args.data(), k());
}
else {
result = pb.mk_ge(coeffs.size(), coeffs.data(), args.data(), k());
}
return result;
}
void theory_pb::ineq::reset() {
m_max_watch.reset();
m_watch_sz = 0;
m_watch_sum.reset();
m_num_propagations = 0;
m_args[0].reset();
m_args[0].m_k.reset();
m_args[1].reset();
m_args[1].m_k.reset();
m_nfixed = 0;
m_max_sum.reset();
m_min_sum.reset();
}
void theory_pb::ineq::unique() {
pb_lit_rewriter_util pbu;
pb_rewriter_util<pb_lit_rewriter_util> util(pbu);
util.unique(m_args[0], m_args[0].m_k, m_is_eq);
}
void theory_pb::ineq::post_prune() {
if (!m_args[0].empty() && is_ge()) {
m_args[0].negate();
m_args[0].negate();
m_args[1].reset();
m_args[1].m_k = m_args[0].m_k;
m_args[1].append(m_args[0]);
m_args[1].negate();
SASSERT(m_args[0].size() == m_args[1].size());
SASSERT(m_args[0].well_formed());
SASSERT(m_args[1].well_formed());
}
}
void theory_pb::ineq::negate() {
SASSERT(!m_is_eq);
m_lit.neg();
}
void theory_pb::ineq::prune() {
m_args[0].prune(m_is_eq);
}
lbool theory_pb::ineq::normalize() {
return m_args[0].normalize(m_is_eq);
}
app_ref theory_pb::ineq::to_expr(context& ctx, ast_manager& m) {
return args().to_expr(m_is_eq, ctx, m);
}
bool theory_pb::arg_t::well_formed() const {
SASSERT(k().is_pos());
uint_set vars;
numeral sum = numeral::zero();
for (unsigned i = 0; i < size(); ++i) {
SASSERT(coeff(i) <= k());
SASSERT(numeral::one() <= coeff(i));
SASSERT(lit(i) != true_literal);
SASSERT(lit(i) != false_literal);
SASSERT(lit(i) != null_literal);
SASSERT(!vars.contains(lit(i).var()));
vars.insert(lit(i).var());
sum += coeff(i);
}
SASSERT(sum >= k());
return true;
}
void theory_pb::card::negate() {
m_lit.neg();
unsigned sz = size();
for (unsigned i = 0; i < sz; ++i) {
m_args[i].neg();
}
m_bound = sz - m_bound + 1;
SASSERT(sz >= m_bound && m_bound > 0);
}
app_ref theory_pb::card::to_expr(theory_pb& th) {
ast_manager& m = th.get_manager();
expr_ref_vector args(m);
for (unsigned i = 0; i < size(); ++i) {
args.push_back(th.literal2expr(m_args[i]));
}
return app_ref(th.pb.mk_at_least_k(args.size(), args.data(), k()), m);
}
lbool theory_pb::card::assign(theory_pb& th, literal alit) {
context& ctx = th.get_context();
unsigned sz = size();
unsigned bound = k();
TRACE("pb", th.display(tout << "assign: " << m_lit << " " << ~alit << " " << bound << "\n", *this, true););
SASSERT(0 < bound && bound < sz);
SASSERT(ctx.get_assignment(alit) == l_false);
SASSERT(ctx.get_assignment(m_lit) == l_true);
unsigned index = 0;
for (index = 0; index <= bound; ++index) {
if (lit(index) == alit) {
break;
}
}
if (index == bound + 1) {
return l_undef;
}
SASSERT(index <= bound);
SASSERT(lit(index) == alit);
for (unsigned i = bound + 1; i < sz; ++i) {
literal lit2 = lit(i);
if (ctx.get_assignment(lit2) != l_false) {
TRACE("pb", tout << "swap " << lit2 << "\n";);
std::swap(m_args[index], m_args[i]);
th.watch_literal(lit2, this);
return l_undef;
}
}
if (bound != index && ctx.get_assignment(lit(bound)) == l_false) {
TRACE("pb", tout << "conflict " << lit(bound) << " " << alit << "\n";);
set_conflict(th, alit);
return l_false;
}
TRACE("pb", tout << "no swap " << index << " " << alit << "\n";);
if (index != bound) {
std::swap(m_args[index], m_args[bound]);
}
SASSERT(th.validate_unit_propagation(*this));
for (unsigned i = 0; i < bound && !ctx.inconsistent(); ++i) {
th.add_assign(*this, lit(i));
}
return ctx.inconsistent() ? l_false : l_true;
}
\brief The conflict clause position for cardinality constraint have the following properties:
0. The position for the literal corresponding to the cardinality constraint.
1. The literal at position 0 of the cardinality constraint.
2. The asserting literal.
3. .. the remaining false literals.
*/
void theory_pb::card::set_conflict(theory_pb& th, literal l) {
SASSERT(validate_conflict(th));
context& ctx = th.get_context();
(void)ctx;
literal_vector& lits = th.get_literals();
SASSERT(ctx.get_assignment(l) == l_false);
SASSERT(ctx.get_assignment(lit()) == l_true);
lits.push_back(~lit());
lits.push_back(l);
unsigned sz = size();
for (unsigned i = m_bound; i < sz; ++i) {
SASSERT(ctx.get_assignment(m_args[i]) == l_false);
lits.push_back(m_args[i]);
}
th.add_clause(*this, lits);
}
bool theory_pb::card::validate_conflict(theory_pb& th) {
context& ctx = th.get_context();
unsigned num_false = 0;
for (unsigned i = 0; i < size(); ++i) {
if (ctx.get_assignment(m_args[i]) == l_false) {
++num_false;
}
}
return size() - num_false < m_bound;
}
bool theory_pb::card::validate_assign(theory_pb& th, literal_vector const& lits, literal l) {
context& ctx = th.get_context();
VERIFY(ctx.get_assignment(l) == l_undef);
for (unsigned i = 0; i < lits.size(); ++i) {
SASSERT(ctx.get_assignment(lits[i]) == l_true);
}
return size() - lits.size() <= m_bound;
}
void theory_pb::card::init_watch(theory_pb& th, bool is_true) {
context& ctx = th.get_context();
th.clear_watch(*this);
if (lit().sign() == is_true) {
negate();
}
SASSERT(ctx.get_assignment(lit()) == l_true);
unsigned j = 0, sz = size(), bound = k();
if (bound == sz) {
for (unsigned i = 0; i < sz && !ctx.inconsistent(); ++i) {
th.add_assign(*this, lit(i));
}
return;
}
for (unsigned i = 0; i < sz; ++i) {
if (ctx.get_assignment(lit(i)) != l_false) {
if (j != i) {
std::swap(m_args[i], m_args[j]);
}
++j;
}
}
DEBUG_CODE(
bool is_false = false;
for (unsigned k = 0; k < sz; ++k) {
SASSERT(!is_false || ctx.get_assignment(lit(k)) == l_false);
is_false = ctx.get_assignment(lit(k)) == l_false;
});
if (j < bound) {
SASSERT(0 < bound && bound < sz);
literal alit = lit(j);
for (unsigned i = bound; i < sz; ++i) {
if (ctx.get_assign_level(alit) < ctx.get_assign_level(lit(i))) {
std::swap(m_args[j], m_args[i]);
alit = lit(j);
}
}
set_conflict(th, alit);
}
else if (j == bound) {
for (unsigned i = 0; i < bound && !ctx.inconsistent(); ++i) {
th.add_assign(*this, lit(i));
}
}
else {
for (unsigned i = 0; i <= bound; ++i) {
th.watch_literal(lit(i), this);
}
}
}
void theory_pb::card::add_arg(literal lit) {
if (lit == false_literal) {
return;
}
else if (lit == true_literal) {
if (m_bound > 0) {
--m_bound;
}
}
else {
m_args.push_back(lit);
}
}
void theory_pb::card::inc_propagations(theory_pb& th) {
++m_num_propagations;
}
theory_pb::theory_pb(context& ctx):
theory(ctx, ctx.get_manager().mk_family_id("pb")),
m_params(ctx.get_fparams()),
m_mpz_trail(m_mpz_mgr),
pb(ctx.get_manager()),
m_restart_lim(3),
m_restart_inc(0),
m_antecedent_exprs(ctx.get_manager()),
m_cardinality_exprs(ctx.get_manager())
{
m_learn_complements = ctx.get_fparams().m_pb_learn_complements;
m_conflict_frequency = ctx.get_fparams().m_pb_conflict_frequency;
}
theory_pb::~theory_pb() {
reset_eh();
}
theory * theory_pb::mk_fresh(context * new_ctx) {
return alloc(theory_pb, *new_ctx);
}
bool theory_pb::internalize_atom(app * atom, bool gate_ctx) {
if (ctx.b_internalized(atom)) {
return true;
}
SASSERT(!ctx.b_internalized(atom));
m_stats.m_num_predicates++;
if (pb.is_aux_bool(atom)) {
bool_var abv = ctx.mk_bool_var(atom);
ctx.set_var_theory(abv, get_id());
return true;
}
if (internalize_card(atom, gate_ctx)) {
return true;
}
SASSERT(pb.is_at_most_k(atom) || pb.is_le(atom) ||
pb.is_ge(atom) || pb.is_at_least_k(atom) ||
pb.is_eq(atom));
unsigned num_args = atom->get_num_args();
bool_var abv = ctx.mk_bool_var(atom);
ctx.set_var_theory(abv, get_id());
literal lit(abv);
if (pb.is_eq(atom)) {
expr_ref_vector args(m), nargs(m);
vector<rational> coeffs;
rational sum(0);
for (unsigned i = 0; i < num_args; ++i) {
args.push_back(atom->get_arg(i));
nargs.push_back(::mk_not(m, atom->get_arg(i)));
rational c = pb.get_coeff(atom, i);
coeffs.push_back(c);
sum += c;
}
rational k = pb.get_k(atom);
expr_ref le(pb.mk_ge(num_args, coeffs.data(), nargs.data(), sum - k), m);
expr_ref ge(pb.mk_ge(num_args, coeffs.data(), args.data(), k), m);
ctx.internalize(le, false);
ctx.internalize(ge, false);
literal le_lit = ctx.get_literal(le);
literal ge_lit = ctx.get_literal(ge);
ctx.mark_as_relevant(le_lit);
ctx.mark_as_relevant(ge_lit);
ctx.mk_th_axiom(get_id(), ~lit, le_lit);
ctx.mk_th_axiom(get_id(), ~lit, ge_lit);
ctx.mk_th_axiom(get_id(), ~le_lit, ~ge_lit, lit);
return true;
}
scoped_ptr<ineq> c = alloc(ineq, m_mpz_mgr, literal(abv), pb.is_eq(atom));
c->m_args[0].m_k = pb.get_k(atom);
numeral& k = c->m_args[0].m_k;
arg_t& args = c->m_args[0];
for (unsigned i = 0; i < num_args; ++i) {
expr* arg = atom->get_arg(i);
literal l = compile_arg(arg);
numeral c = pb.get_coeff(atom, i);
switch (ctx.get_assignment(l)) {
case l_true:
k -= c;
break;
case l_false:
break;
default:
args.push_back(std::make_pair(l, c));
break;
}
}
if (pb.is_at_most_k(atom) || pb.is_le(atom)) {
k = -k;
for (auto& a : args) {
a.first.neg();
k += a.second;
}
}
c->unique();
lbool is_true = c->normalize();
c->prune();
c->post_prune();
TRACE("pb", display(tout, *c); tout << " := " << lit << " " << is_true << "\n";);
switch (is_true) {
case l_false:
lit.neg();
case l_true:
ctx.mk_th_axiom(get_id(), 1, &lit);
return true;
case l_undef:
break;
}
if (c->k().is_one() && c->is_ge()) {
literal_vector& lits = get_literals();
lits.push_back(~lit);
for (unsigned i = 0; i < c->size(); ++i) {
lits.push_back(c->lit(i));
SASSERT(c->coeff(i).is_one());
ctx.mk_th_axiom(get_id(), lit, ~c->lit(i));
}
ctx.mk_th_axiom(get_id(), lits.size(), lits.data());
return true;
}
scoped_mpz& max_watch = c->m_max_watch;
max_watch.reset();
for (auto const& a : args) {
mpz const& num = a.second.to_mpq().numerator();
if (m_mpz_mgr.lt(max_watch, num)) {
max_watch = num;
}
}
init_watch_ineq(*c);
init_watch(abv);
m_var_infos[abv].m_ineq = c.detach();
m_ineqs_trail.push_back(abv);
TRACE("pb", display(tout, *c););
return true;
}
literal theory_pb::compile_arg(expr* arg) {
bool_var bv;
bool has_bv = false;
bool negate = m.is_not(arg, arg);
SASSERT(!m.is_not(arg));
if (!ctx.b_internalized(arg)) {
ctx.internalize(arg, false);
}
if (ctx.b_internalized(arg)) {
bv = ctx.get_bool_var(arg);
if (is_uninterp(arg) && null_theory_var == ctx.get_var_theory(bv)) {
ctx.set_var_theory(bv, get_id());
}
has_bv = (ctx.get_var_theory(bv) == get_id());
}
else if (m.is_true(arg)) {
bv = true_bool_var;
has_bv = true;
}
else if (m.is_false(arg)) {
bv = true_bool_var;
has_bv = true;
negate = !negate;
}
if (!has_bv) {
app_ref tmp(m), fml(m);
tmp = pb.mk_fresh_bool();
fml = m.mk_iff(tmp, arg);
TRACE("pb", tout << "create proxy " << fml << "\n";);
ctx.internalize(fml, false);
SASSERT(ctx.b_internalized(tmp));
bv = ctx.get_bool_var(tmp);
SASSERT(get_id() == ctx.get_var_theory(bv));
literal lit(ctx.get_bool_var(fml));
ctx.mk_th_axiom(get_id(), 1, &lit);
ctx.mark_as_relevant(tmp.get());
}
return negate?~literal(bv):literal(bv);
}
void theory_pb::del_watch(ineq_watch& watch, unsigned index, ineq& c, unsigned ineq_index) {
SASSERT(c.is_ge());
if (index < watch.size()) {
std::swap(watch[index], watch[watch.size()-1]);
}
watch.pop_back();
SASSERT(ineq_index < c.watch_size());
scoped_mpz coeff(m_mpz_mgr);
coeff = c.ncoeff(ineq_index);
if (ineq_index + 1 < c.watch_size()) {
std::swap(c.args()[ineq_index], c.args()[c.watch_size()-1]);
}
--c.m_watch_sz;
c.m_watch_sum -= coeff;
if (coeff == c.max_watch()) {
coeff = c.ncoeff(0);
for (unsigned i = 1; coeff != c.max_watch() && i < c.watch_size(); ++i) {
if (coeff < c.ncoeff(i)) coeff = c.ncoeff(i);
}
c.set_max_watch(coeff);
}
}
void theory_pb::add_watch(ineq& c, unsigned i) {
SASSERT(c.is_ge());
literal lit = c.lit(i);
scoped_mpz coeff(m_mpz_mgr);
coeff = c.ncoeff(i);
c.m_watch_sum += coeff;
SASSERT(i >= c.watch_size());
if (i > c.watch_size()) {
std::swap(c.args()[i], c.args()[c.watch_size()]);
}
++c.m_watch_sz;
if (coeff > c.max_watch()) {
c.set_max_watch(coeff);
}
watch_literal(lit, &c);
}
void theory_pb::init_watch(bool_var v) {
if (m_var_infos.size() <= static_cast<unsigned>(v)) {
m_var_infos.resize(static_cast<unsigned>(v)+100);
}
}
void theory_pb::watch_literal(literal lit, ineq* c) {
init_watch(lit.var());
ptr_vector<ineq>* ineqs = m_var_infos[lit.var()].m_lit_watch[lit.sign()];
if (ineqs == nullptr) {
ineqs = alloc(ptr_vector<ineq>);
m_var_infos[lit.var()].m_lit_watch[lit.sign()] = ineqs;
}
DEBUG_CODE(
for (auto* c1 : *ineqs) {
SASSERT (c1 != c);
});
ineqs->push_back(c);
}
void theory_pb::unwatch_literal(literal lit, ineq* c) {
if (static_cast<unsigned>(lit.var()) >= m_var_infos.size())
return;
ptr_vector<ineq>* ineqs = m_var_infos[lit.var()].m_lit_watch[lit.sign()];
if (ineqs) {
remove(*ineqs, c);
}
}
void theory_pb::remove(ptr_vector<ineq>& ineqs, ineq* c) {
unsigned sz = ineqs.size();
for (unsigned j = 0; j < sz; ++j) {
if (ineqs[j] == c) {
std::swap(ineqs[j], ineqs[sz-1]);
ineqs.pop_back();
return;
}
}
}
class theory_pb::card_justification : public justification {
card& m_card;
family_id m_fid;
literal m_lit;
public:
card_justification(card& c, literal lit, family_id fid)
: justification(true), m_card(c), m_fid(fid), m_lit(lit) {}
card& get_card() { return m_card; }
void get_antecedents(conflict_resolution& cr) override {
cr.mark_literal(m_card.lit());
for (unsigned i = m_card.k(); i < m_card.size(); ++i) {
cr.mark_literal(~m_card.lit(i));
}
}
theory_id get_from_theory() const override {
return m_fid;
}
proof* mk_proof(smt::conflict_resolution& cr) override {
ptr_buffer<proof> prs;
ast_manager& m = cr.get_context().get_manager();
expr_ref fact(m);
cr.get_context().literal2expr(m_lit, fact);
bool all_valid = true;
proof* pr = nullptr;
pr = cr.get_proof(m_card.lit());
all_valid &= pr != nullptr;
prs.push_back(pr);
for (unsigned i = m_card.k(); i < m_card.size(); ++i) {
pr = cr.get_proof(~m_card.lit(i));
all_valid &= pr != nullptr;
prs.push_back(pr);
}
if (!all_valid) {
return nullptr;
}
else {
return m.mk_th_lemma(m_fid, fact, prs.size(), prs.data());
}
}
};
bool theory_pb::is_cardinality_constraint(app * atom) {
if (pb.is_ge(atom) && pb.has_unit_coefficients(atom)) {
return true;
}
if (pb.is_at_least_k(atom)) {
return true;
}
return false;
}
bool theory_pb::internalize_card(app * atom, bool gate_ctx) {
if (ctx.b_internalized(atom)) {
return true;
}
if (!is_cardinality_constraint(atom)) {
return false;
}
unsigned num_args = atom->get_num_args();
bool_var abv = ctx.mk_bool_var(atom);
ctx.set_var_theory(abv, get_id());
unsigned bound = pb.get_k(atom).get_unsigned();
literal lit(abv);
if (bound == 0) {
ctx.mk_th_axiom(get_id(), 1, &lit);
return true;
}
if (bound > num_args) {
lit.neg();
ctx.mk_th_axiom(get_id(), 1, &lit);
return true;
}
bool aux = pb.is_at_least_k(atom);
card* c = alloc(card, lit, bound, aux);
for (expr* arg : *atom) {
c->add_arg(compile_arg(arg));
}
if (bound == c->size() || bound == 1) {
}
if (bound == c->size()) {
card2conjunction(*c);
dealloc(c);
}
else if (1 == c->size()) {
card2disjunction(*c);
dealloc(c);
}
else {
SASSERT(0 < c->k() && c->k() < c->size());
init_watch(abv);
m_var_infos[abv].m_card = c;
m_card_trail.push_back(abv);
}
return true;
}
void theory_pb::card2conjunction(card const& c) {
literal lit = c.lit();
literal_vector& lits = get_literals();
for (unsigned i = 0; i < c.size(); ++i) {
lits.push_back(~c.lit(i));
}
lits.push_back(lit);
ctx.mk_th_axiom(get_id(), lits.size(), lits.data());
for (unsigned i = 0; i < c.size(); ++i) {
literal lits2[2] = { ~lit, c.lit(i) };
ctx.mk_th_axiom(get_id(), 2, lits2);
}
}
void theory_pb::card2disjunction(card const& c) {
literal lit = c.lit();
literal_vector& lits = get_literals();
for (unsigned i = 0; i < c.size(); ++i) {
lits.push_back(c.lit(i));
}
lits.push_back(~lit);
ctx.mk_th_axiom(get_id(), lits.size(), lits.data());
for (unsigned i = 0; i < c.size(); ++i) {
literal lits2[2] = { lit, ~c.lit(i) };
ctx.mk_th_axiom(get_id(), 2, lits2);
}
}
void theory_pb::watch_literal(literal lit, card* c) {
init_watch(lit.var());
ptr_vector<card>* cards = m_var_infos[lit.var()].m_lit_cwatch[lit.sign()];
if (cards == nullptr) {
cards = alloc(ptr_vector<card>);
m_var_infos[lit.var()].m_lit_cwatch[lit.sign()] = cards;
}
cards->push_back(c);
}
void theory_pb::unwatch_literal(literal lit, card* c) {
if (m_var_infos.size() <= static_cast<unsigned>(lit.var())) {
return;
}
ptr_vector<card>* cards = m_var_infos[lit.var()].m_lit_cwatch[lit.sign()];
if (cards) {
remove(*cards, c);
}
}
void theory_pb::remove(ptr_vector<card>& cards, card* c) {
for (unsigned j = 0; j < cards.size(); ++j) {
if (cards[j] == c) {
std::swap(cards[j], cards[cards.size()-1]);
cards.pop_back();
break;
}
}
}
std::ostream& theory_pb::display(std::ostream& out, card const& c, bool values) const {
out << c.lit();
if (c.lit() != null_literal) {
if (values) {
out << "@(" << ctx.get_assignment(c.lit());
if (ctx.get_assignment(c.lit()) != l_undef) {
out << ":" << ctx.get_assign_level(c.lit());
}
out << ")";
}
ctx.display_literal_verbose(out, c.lit()); out << "\n";
}
else {
out << " ";
}
for (unsigned i = 0; i < c.size(); ++i) {
literal l = c.lit(i);
out << l;
if (values) {
out << "@(" << ctx.get_assignment(l);
if (ctx.get_assignment(l) != l_undef) {
out << ":" << ctx.get_assign_level(l);
}
out << ") ";
}
}
out << " >= " << c.k() << "\n";
if (c.all_propagations()) out << "propagations: " << c.all_propagations() << "\n";
return out;
}
void theory_pb::add_clause(card& c, literal_vector const& lits) {
m_stats.m_num_conflicts++;
justification* js = nullptr;
c.inc_propagations(*this);
if (!resolve_conflict(c, lits)) {
if (proofs_enabled()) {
js = alloc(theory_lemma_justification, get_id(), ctx, lits.size(), lits.data());
}
ctx.mk_clause(lits.size(), lits.data(), js, CLS_TH_LEMMA, nullptr);
}
SASSERT(ctx.inconsistent());
}
void theory_pb::add_assign(card& c, literal l) {
if (ctx.get_assignment(l) == l_true) {
return;
}
c.inc_propagations(*this);
m_stats.m_num_propagations++;
TRACE("pb", tout << "#prop: " << c.num_propagations() << " - " << c.lit() << " => " << l << "\n";
display(tout, c, true) << "\n";);
SASSERT(validate_unit_propagation(c));
ctx.assign(l, ctx.mk_justification(card_justification(c, l, get_id())));
}
void theory_pb::clear_watch(card& c) {
unsigned sz = std::min(c.k() + 1, c.size());
for (unsigned i = 0; i < sz; ++i) {
unwatch_literal(c.lit(i), &c);
}
}
void theory_pb::collect_statistics(::statistics& st) const {
st.update("pb resolves", m_stats.m_num_resolves);
st.update("pb conflicts", m_stats.m_num_conflicts);
st.update("pb propagations", m_stats.m_num_propagations);
st.update("pb predicates", m_stats.m_num_predicates);
}
void theory_pb::reset_eh() {
for (unsigned i = 0; i < m_var_infos.size(); ++i) {
m_var_infos[i].reset();
}
m_ineqs_trail.reset();
m_ineqs_lim.reset();
m_card_trail.reset();
m_card_lim.reset();
m_stats.reset();
}
void theory_pb::new_eq_eh(theory_var v1, theory_var v2) {
UNREACHABLE();
}
final_check_status theory_pb::final_check_eh() {
TRACE("pb", display(tout););
DEBUG_CODE(validate_final_check(););
return FC_DONE;
}
void theory_pb::assign_eh(bool_var v, bool is_true) {
ptr_vector<ineq>* ineqs = nullptr;
literal nlit(v, is_true);
init_watch(v);
TRACE("pb", tout << "assign: " << ~nlit << "\n";);
ineqs = m_var_infos[v].m_lit_watch[nlit.sign()];
if (ineqs != nullptr) {
for (unsigned i = 0; i < ineqs->size(); ++i) {
SASSERT((*ineqs)[i]->is_ge());
if (assign_watch_ge(v, is_true, *ineqs, i)) {
--i;
}
}
}
ineq* c = m_var_infos[v].m_ineq;
if (c != nullptr) {
VERIFY(c->is_ge());
assign_ineq(*c, is_true);
}
ptr_vector<card>* cards = m_var_infos[v].m_lit_cwatch[nlit.sign()];
if (cards != nullptr && !cards->empty() && !ctx.inconsistent()) {
ptr_vector<card>::iterator it = cards->begin(), it2 = it, end = cards->end();
for (; it != end; ++it) {
if (ctx.get_assignment((*it)->lit()) != l_true) {
continue;
}
switch ((*it)->assign(*this, nlit)) {
case l_false:
for (; it != end; ++it, ++it2) {
*it2 = *it;
}
SASSERT(ctx.inconsistent());
cards->set_end(it2);
return;
case l_undef:
break;
case l_true:
if (it2 != it) {
*it2 = *it;
}
++it2;
break;
}
}
cards->set_end(it2);
}
card* crd = m_var_infos[v].m_card;
if (crd != nullptr && !ctx.inconsistent()) {
crd->init_watch(*this, is_true);
}
}
literal_vector& theory_pb::get_all_literals(ineq& c, bool negate) {
literal_vector& lits = get_literals();
for (unsigned i = 0; i < c.size(); ++i) {
literal l = c.lit(i);
switch(ctx.get_assignment(l)) {
case l_true:
lits.push_back(negate?(~l):l);
break;
case l_false:
lits.push_back(negate?l:(~l));
break;
default:
break;
}
}
return lits;
}
literal_vector& theory_pb::get_helpful_literals(ineq& c, bool negate) {
scoped_mpz sum(m_mpz_mgr);
mpz const& k = c.mpz_k();
literal_vector& lits = get_literals();
for (unsigned i = 0; sum < k && i < c.size(); ++i) {
literal l = c.lit(i);
if (ctx.get_assignment(l) == l_true) {
sum += c.ncoeff(i);
if (negate) l = ~l;
lits.push_back(l);
}
}
SASSERT(sum >= k);
return lits;
}
literal_vector& theory_pb::get_unhelpful_literals(ineq& c, bool negate) {
literal_vector& lits = get_literals();
for (unsigned i = 0; i < c.size(); ++i) {
literal l = c.lit(i);
if (ctx.get_assignment(l) == l_false) {
if (negate) l = ~l;
lits.push_back(l);
}
}
return lits;
}
class theory_pb::negate_ineq : public trail {
ineq& c;
public:
negate_ineq(ineq& c): c(c) {}
void undo() override {
c.negate();
}
};
\brief propagate assignment to inequality.
This is a basic, non-optimized implementation based
on the assumption that inequalities are mostly units
and/or relatively few compared to number of argumets.
*/
void theory_pb::assign_ineq(ineq& c, bool is_true) {
m_mpz_trail.push_back(c.m_max_sum);
m_mpz_trail.push_back(c.m_min_sum);
ctx.push_trail(scoped_value_trail<scoped_mpz, scoped_mpz_vector>(c.m_max_sum, m_mpz_trail));
ctx.push_trail(scoped_value_trail<scoped_mpz, scoped_mpz_vector>(c.m_min_sum, m_mpz_trail));
ctx.push_trail(value_trail<unsigned>(c.m_nfixed));
SASSERT(c.is_ge());
unsigned sz = c.size();
if (c.lit().sign() == is_true) {
c.negate();
ctx.push_trail(negate_ineq(c));
}
scoped_mpz maxsum(m_mpz_mgr), mininc(m_mpz_mgr);
for (unsigned i = 0; i < sz; ++i) {
lbool asgn = ctx.get_assignment(c.lit(i));
if (asgn != l_false) {
maxsum += c.ncoeff(i);
}
if (asgn == l_undef && (mininc.is_zero() || mininc > c.ncoeff(i))) {
mininc = c.ncoeff(i);
}
}
TRACE("pb",
tout << "assign: " << c.lit() << "\n";
display(tout, c); );
if (maxsum < c.mpz_k()) {
literal_vector& lits = get_unhelpful_literals(c, false);
lits.push_back(~c.lit());
add_clause(c, lits);
}
else {
init_watch_literal(c);
SASSERT(c.m_watch_sum >= c.mpz_k());
DEBUG_CODE(validate_watch(c););
}
if (maxsum >= c.mpz_k() && maxsum - mininc < c.mpz_k()) {
literal_vector& lits = get_unhelpful_literals(c, true);
lits.push_back(c.lit());
for (unsigned i = 0; i < sz; ++i) {
literal lit = c.lit(i);
if (ctx.get_assignment(lit) == l_undef) {
DEBUG_CODE(validate_assign(c, lits, lit););
add_assign(c, lits, c.lit(i));
}
}
}
}
\brief propagate assignment to equality.
*/
void theory_pb::assign_eq(ineq& c, bool is_true) {
SASSERT(c.is_eq());
UNREACHABLE();
}
\brief v is assigned in inequality c. Update current bounds and watch list.
Optimize for case where the c.lit() is True. This covers the case where
inequalities are unit literals and formulas in negation normal form
(inequalities are closed under negation).
*/
bool theory_pb::assign_watch_ge(bool_var v, bool is_true, ineq_watch& watch, unsigned watch_index) {
bool removed = false;
ineq& c = *watch[watch_index];
unsigned w = c.find_lit(v, 0, c.watch_size());
SASSERT(ctx.get_assignment(c.lit()) == l_true);
SASSERT(is_true == c.lit(w).sign());
scoped_mpz k_coeff(m_mpz_mgr), k(m_mpz_mgr);
k = c.mpz_k();
k_coeff = k;
k_coeff += c.ncoeff(w);
bool add_more = c.watch_sum() < k_coeff + c.max_watch();
for (unsigned i = c.watch_size(); add_more && i < c.size(); ++i) {
if (ctx.get_assignment(c.lit(i)) != l_false) {
add_watch(c, i);
add_more = c.watch_sum() < k_coeff + c.max_watch();
}
}
if (c.watch_sum() < k_coeff) {
literal_vector& lits = get_unhelpful_literals(c, false);
lits.push_back(~c.lit());
add_clause(c, lits);
}
else {
del_watch(watch, watch_index, c, w);
removed = true;
SASSERT(c.watch_sum() >= k);
if (c.watch_sum() < k + c.max_watch()) {
literal_vector& lits = get_unhelpful_literals(c, true);
lits.push_back(c.lit());
scoped_mpz deficit(m_mpz_mgr);
deficit = c.watch_sum() - k;
for (unsigned i = 0; i < c.size(); ++i) {
if (ctx.get_assignment(c.lit(i)) == l_undef && deficit < c.ncoeff(i)) {
DEBUG_CODE(validate_assign(c, lits, c.lit(i)););
add_assign(c, lits, c.lit(i));
}
}
}
}
TRACE("pb",
tout << "assign: " << literal(v,!is_true) << "\n";
display(tout, c); );
return removed;
}
struct theory_pb::psort_expr {
context& ctx;
ast_manager& m;
theory_pb& th;
pb_util pb;
typedef smt::literal pliteral;
typedef smt::literal_vector pliteral_vector;
psort_expr(context& c, theory_pb& th):
ctx(c),
m(c.get_manager()),
th(th),
pb(m) {}
literal fresh(char const* ) {
app_ref y(m);
y = pb.mk_fresh_bool();
return literal(ctx.mk_bool_var(y));
}
literal mk_max(unsigned n, literal const* lits) {
expr_ref_vector es(m);
expr_ref tmp(m);
for (unsigned i = 0; i < n; ++i) {
ctx.literal2expr(lits[i], tmp);
es.push_back(tmp);
}
tmp = m.mk_or(es.size(), es.data());
bool_var v = ctx.b_internalized(tmp)?ctx.get_bool_var(tmp):ctx.mk_bool_var(tmp);
return literal(v);
}
literal mk_min(unsigned n, literal const* lits) {
expr_ref_vector es(m);
expr_ref tmp(m);
for (unsigned i = 0; i < n; ++i) {
ctx.literal2expr(lits[i], tmp);
es.push_back(tmp);
}
tmp = m.mk_and(es.size(), es.data());
bool_var v = ctx.b_internalized(tmp)?ctx.get_bool_var(tmp):ctx.mk_bool_var(tmp);
return literal(v);
}
literal mk_not(literal a) { return ~a; }
void mk_clause(unsigned n, literal const* ls) {
literal_vector tmp(n, ls);
ctx.mk_clause(n, tmp.data(), th.justify(tmp), CLS_AUX, nullptr);
}
literal mk_false() { return false_literal; }
literal mk_true() { return true_literal; }
std::ostream& pp(std::ostream& out, literal l) { return out << l; }
};
literal theory_pb::assert_ge(context& ctx, unsigned k, unsigned n, literal const* xs) {
theory_pb_params p;
theory_pb th(ctx);
psort_expr ps(ctx, th);
psort_nw<psort_expr> sort(ps);
return sort.ge(false, k, n, xs);
}
void theory_pb::inc_propagations(ineq& c) {
++c.m_num_propagations;
}
void theory_pb::restart_eh() {
}
bool theory_pb::gc() {
unsigned z = 0, nz = 0;
m_occs.reset();
for (unsigned i = 0; i < m_card_trail.size(); ++i) {
bool_var v = m_card_trail[i];
if (v == null_bool_var)
continue;
card* c = m_var_infos[v].m_card;
if (!c)
continue;
c->reset_propagations();
literal lit = c->lit();
if (c->is_aux() && ctx.get_assign_level(lit) > ctx.get_search_level()) {
double activity = ctx.get_activity(v);
if (activity <= 0) {
nz++;
}
else {
z++;
clear_watch(*c);
m_var_infos[v].m_card = nullptr;
dealloc(c);
m_card_trail[i] = null_bool_var;
ctx.remove_watch(v);
m_occs.insert(v);
}
}
}
#if 0
for (clause* cl : ctx.get_lemmas()) {
if (!cl->deleted()) {
for (literal lit : *cl) {
if (m_occs.contains(lit.var())) {
break;
}
}
}
}
m_occs.reset();
for (unsigned i = 0; i < lemmas.size(); ++i) {
clause* cl = lemmas[i];
unsigned sz = cl->get_num_literals();
for (unsigned j = 0; j < sz; ++j) {
unsigned idx = cl->get_literal(j).index();
m_occs.insert(idx);
}
}
#endif
return z*10 >= nz;
}
void theory_pb::init_search_eh() {
}
void theory_pb::push_scope_eh() {
m_ineqs_lim.push_back(m_ineqs_trail.size());
m_card_lim.push_back(m_card_trail.size());
}
void theory_pb::pop_scope_eh(unsigned num_scopes) {
unsigned new_lim = m_ineqs_lim.size()-num_scopes;
unsigned sz = m_ineqs_lim[new_lim];
while (m_ineqs_trail.size() > sz) {
bool_var v = m_ineqs_trail.back();
ineq* c = m_var_infos[v].m_ineq;
clear_watch(*c);
m_var_infos[v].m_ineq = nullptr;
m_ineqs_trail.pop_back();
dealloc(c);
}
m_ineqs_lim.resize(new_lim);
new_lim = m_card_lim.size() - num_scopes;
sz = m_card_lim[new_lim];
while (m_card_trail.size() > sz) {
bool_var v = m_card_trail.back();
m_card_trail.pop_back();
if (v != null_bool_var) {
card* c = m_var_infos[v].m_card;
clear_watch(*c);
m_var_infos[v].m_card = nullptr;
dealloc(c);
}
}
m_card_lim.resize(new_lim);
}
void theory_pb::clear_watch(ineq& c) {
for (unsigned i = 0; i < c.size(); ++i) {
literal w = c.lit(i);
unwatch_literal(w, &c);
}
c.m_watch_sum.reset();
c.m_watch_sz = 0;
c.m_max_watch.reset();
c.m_nfixed = 0;
c.m_max_sum.reset();
c.m_min_sum.reset();
}
class theory_pb::unwatch_ge : public trail {
theory_pb& pb;
ineq& c;
public:
unwatch_ge(theory_pb& p, ineq& c): pb(p), c(c) {}
void undo() override {
for (unsigned i = 0; i < c.watch_size(); ++i) {
pb.unwatch_literal(c.lit(i), &c);
}
c.m_watch_sz = 0;
c.m_watch_sum.reset();
c.m_max_watch.reset();
}
};
void theory_pb::init_watch_literal(ineq& c) {
scoped_mpz max_k(m_mpz_mgr);
c.m_watch_sum.reset();
c.m_watch_sz = 0;
c.m_max_watch.reset();
bool watch_more = true;
for (unsigned i = 0; watch_more && i < c.size(); ++i) {
if (ctx.get_assignment(c.lit(i)) != l_false) {
add_watch(c, i);
max_k = c.mpz_k();
max_k += c.max_watch();
watch_more = c.m_watch_sum < max_k;
}
}
ctx.push_trail(unwatch_ge(*this, c));
}
void theory_pb::init_watch_ineq(ineq& c) {
c.m_min_sum.reset();
c.m_max_sum.reset();
c.m_nfixed = 0;
c.m_watch_sum.reset();
c.m_max_watch.reset();
c.m_watch_sz = 0;
for (unsigned i = 0; i < c.size(); ++i) {
c.m_max_sum += c.ncoeff(i);
}
}
class theory_pb::pb_justification : public theory_propagation_justification {
ineq& m_ineq;
public:
pb_justification(ineq& c, family_id fid, region & r,
unsigned num_lits, literal const * lits, literal consequent):
theory_propagation_justification(fid, r, num_lits, lits, consequent),
m_ineq(c)
{}
ineq& get_ineq() { return m_ineq; }
};
void theory_pb::add_assign(ineq& c, literal_vector const& lits, literal l) {
inc_propagations(c);
m_stats.m_num_propagations++;
TRACE("pb", tout << "#prop:" << c.m_num_propagations << " - " << lits;
tout << " => " << l << "\n";
display(tout, c, true););
SASSERT(validate_antecedents(lits));
ctx.assign(l, ctx.mk_justification(
pb_justification(
c, get_id(), ctx.get_region(), lits.size(), lits.data(), l)));
}
void theory_pb::add_clause(ineq& c, literal_vector const& lits) {
inc_propagations(c);
m_stats.m_num_conflicts++;
TRACE("pb", tout << "#prop:" << c.m_num_propagations << " - " << lits << "\n";
display(tout, c, true););
justification* js = nullptr;
if (proofs_enabled()) {
js = alloc(theory_lemma_justification, get_id(), ctx, lits.size(), lits.data());
}
ctx.mk_clause(lits.size(), lits.data(), js, CLS_TH_LEMMA, nullptr);
}
int theory_pb::get_coeff(bool_var v) const {
return m_coeffs.get(v, 0);
}
int theory_pb::get_abs_coeff(bool_var v) const {
int coeff = get_coeff(v);
if (coeff < 0) coeff = -coeff;
return coeff;
}
void theory_pb::reset_coeffs() {
for (unsigned i = 0; i < m_active_vars.size(); ++i) {
m_coeffs[m_active_vars[i]] = 0;
}
m_active_vars.reset();
}
void theory_pb::process_antecedent(literal l, int offset) {
SASSERT(ctx.get_assignment(l) == l_false);
bool_var v = l.var();
unsigned lvl = ctx.get_assign_level(v);
if (lvl > ctx.get_base_level() && !ctx.is_marked(v) && lvl == m_conflict_lvl) {
ctx.set_mark(v);
++m_num_marks;
}
inc_coeff(l, offset);
}
void theory_pb::process_card(card& c, int offset) {
SASSERT(c.k() <= c.size());
SASSERT(ctx.get_assignment(c.lit()) == l_true);
for (unsigned i = c.k(); i < c.size(); ++i) {
process_antecedent(c.lit(i), offset);
}
for (unsigned i = 0; i < c.k(); ++i) {
inc_coeff(c.lit(i), offset);
}
if (ctx.get_assign_level(c.lit()) > ctx.get_base_level()) {
m_antecedents.push_back(c.lit());
}
}
bool theory_pb::validate_lemma() {
int value = -m_bound;
normalize_active_coeffs();
for (unsigned i = 0; i < m_active_vars.size(); ++i) {
bool_var v = m_active_vars[i];
int coeff = get_coeff(v);
SASSERT(coeff != 0);
if (coeff < 0 && ctx.get_assignment(v) != l_true) {
value -= coeff;
}
else if (coeff > 0 && ctx.get_assignment(v) != l_false) {
value += coeff;
}
}
if (value >= 0) {
display_resolved_lemma(verbose_stream() << "not validated\n");
}
return value < 0;
}
bool theory_pb::validate_implies(app_ref& A, app_ref& B) {
static bool validating = true;
if (validating) return true;
validating = true;
smt_params fp;
kernel k(m, fp);
expr_ref notB(m.mk_not(B), m);
k.assert_expr(A);
k.assert_expr(notB);
lbool is_sat = k.check();
validating = false;
if (is_sat == l_true) {
std::cout << A << "\n";
std::cout << B << "\n";
}
SASSERT(is_sat != l_true);
return true;
}
app_ref theory_pb::justification2expr(b_justification& js, literal conseq) {
app_ref result(m.mk_true(), m);
expr_ref_vector args(m);
vector<rational> coeffs;
switch(js.get_kind()) {
case b_justification::CLAUSE: {
clause& cls = *js.get_clause();
justification* cjs = cls.get_justification();
if (cjs && !is_proof_justification(*cjs)) {
break;
}
for (unsigned i = 0; i < cls.get_num_literals(); ++i) {
literal lit = cls.get_literal(i);
args.push_back(literal2expr(lit));
}
result = m.mk_or(args.size(), args.data());
break;
}
case b_justification::BIN_CLAUSE:
result = m.mk_or(literal2expr(conseq), literal2expr(~js.get_literal()));
break;
case b_justification::AXIOM:
break;
case b_justification::JUSTIFICATION: {
justification* j = js.get_justification();
card_justification* pbj = nullptr;
if (j->get_from_theory() == get_id()) {
pbj = dynamic_cast<card_justification*>(j);
}
if (pbj != nullptr) {
card& c2 = pbj->get_card();
result = card2expr(c2);
}
break;
}
default:
break;
}
return result;
}
int theory_pb::arg_max(int& max_coeff) {
max_coeff = 0;
int arg_max = -1;
while (!m_active_coeffs.empty()) {
max_coeff = m_active_coeffs.back();
if (m_coeff2args[max_coeff].empty()) {
m_active_coeffs.pop_back();
}
else {
arg_max = m_coeff2args[max_coeff].back();
m_coeff2args[max_coeff].pop_back();
break;
}
}
return arg_max;
}
\brief retrieve asserting literal. Prefer p, otherwise find a literal with maximal level.
Note that an asserting literal should be false with respect to the resolvent inequality.
*/
literal theory_pb::get_asserting_literal(literal p) {
unsigned lvl = 0;
TRACE("pb", tout << p << " " << ctx.get_assignment(p) << "\n";);
if (ctx.get_assignment(p) == l_false && get_abs_coeff(p.var()) != 0 && p == literal(p.var(), get_coeff(p.var()) < 0)) {
return p;
}
for (bool_var v : m_active_vars) {
literal lit(v, get_coeff(v) < 0);
if (ctx.get_assignment(lit) == l_false && ctx.get_assign_level(lit) > lvl) {
p = lit;
}
}
return p;
}
void theory_pb::reset_arg_max() {
for (unsigned i = 0; i < m_active_vars.size(); ++i) {
int coeff = get_abs_coeff(m_active_vars[i]);
if (static_cast<int>(m_coeff2args.size()) > coeff) {
m_coeff2args[coeff].reset();
}
}
}
bool theory_pb::init_arg_max() {
if (m_coeff2args.size() < (1 << 10)) {
m_coeff2args.resize(1 << 10);
}
m_active_coeffs.reset();
if (m_active_vars.empty()) {
return false;
}
for (unsigned i = 0; i < m_active_vars.size(); ++i) {
bool_var v = m_active_vars[i];
int coeff = get_abs_coeff(v);
if (coeff >= static_cast<int>(m_coeff2args.size())) {
reset_arg_max();
return false;
}
if (m_coeff2args[coeff].empty()) {
m_active_coeffs.push_back(coeff);
}
m_coeff2args[coeff].push_back(v);
}
std::sort(m_active_coeffs.begin(), m_active_coeffs.end());
return true;
}
void theory_pb::normalize_active_coeffs() {
while (!m_active_var_set.empty()) m_active_var_set.erase();
unsigned i = 0, j = 0, sz = m_active_vars.size();
for (; i < sz; ++i) {
bool_var v = m_active_vars[i];
if (!m_active_var_set.contains(v) && get_coeff(v) != 0) {
m_active_var_set.insert(v);
if (j != i) {
m_active_vars[j] = m_active_vars[i];
}
++j;
}
}
sz = j;
m_active_vars.shrink(sz);
}
void theory_pb::inc_coeff(literal l, int offset) {
SASSERT(offset > 0);
bool_var v = l.var();
SASSERT(v != null_bool_var);
if (static_cast<bool_var>(m_coeffs.size()) <= v) {
m_coeffs.resize(v + 1, 0);
}
int coeff0 = m_coeffs[v];
if (coeff0 == 0) {
m_active_vars.push_back(v);
}
int inc = l.sign() ? -offset : offset;
int coeff1 = inc + coeff0;
m_coeffs[v] = coeff1;
if (coeff0 > 0 && inc < 0) {
m_bound -= coeff0 - std::max(0, coeff1);
}
else if (coeff0 < 0 && inc > 0) {
m_bound -= std::min(0, coeff1) - coeff0;
}
}
\brief attempt a cut and simplification of constraints.
*/
void theory_pb::cut() {
unsigned g = 0;
for (unsigned i = 0; g != 1 && i < m_active_vars.size(); ++i) {
bool_var v = m_active_vars[i];
int coeff = get_abs_coeff(v);
if (coeff == 0) {
continue;
}
if (m_bound < coeff) {
if (get_coeff(v) > 0) {
m_coeffs[v] = m_bound;
}
else {
m_coeffs[v] = -m_bound;
}
coeff = m_bound;
}
SASSERT(0 < coeff && coeff <= m_bound);
if (g == 0) {
g = static_cast<unsigned>(coeff);
}
else {
g = u_gcd(g, static_cast<unsigned>(coeff));
}
}
if (g >= 2) {
normalize_active_coeffs();
for (auto v : m_active_vars) {
m_coeffs[v] /= static_cast<int>(g);
}
m_bound = (m_bound + g - 1) / g;
TRACE("pb", display_resolved_lemma(tout << "cut\n"););
}
}
bool theory_pb::can_propagate() { return false; }
void theory_pb::propagate() { }
bool theory_pb::resolve_conflict(card& c, literal_vector const& confl) {
TRACE("pb", display(tout, c, true); );
bool_var v;
m_conflict_lvl = 0;
for (literal lit : confl) {
SASSERT(ctx.get_assignment(lit) == l_false);
m_conflict_lvl = std::max(m_conflict_lvl, ctx.get_assign_level(lit));
}
if (m_conflict_lvl < ctx.get_assign_level(c.lit()) || m_conflict_lvl == ctx.get_base_level()) {
return false;
}
reset_coeffs();
m_num_marks = 0;
m_bound = c.k();
m_antecedents.reset();
m_resolved.reset();
literal_vector ante;
process_card(c, 1);
app_ref A(m), B(m), C(m);
DEBUG_CODE(A = c.to_expr(*this););
literal_vector const& lits = ctx.assigned_literals();
SASSERT(!lits.empty());
unsigned idx = lits.size()-1;
b_justification js;
literal conseq = ~confl[2];
int bound = 1;
while (m_num_marks > 0) {
v = conseq.var();
int offset = get_abs_coeff(v);
if (offset == 0) {
goto process_next_resolvent;
}
SASSERT(validate_lemma());
if (offset > 1000) {
while (m_num_marks > 0 && idx > 0) {
v = lits[idx].var();
if (ctx.is_marked(v)) {
ctx.unset_mark(v);
}
--idx;
}
return false;
}
SASSERT(offset > 0);
js = ctx.get_justification(v);
TRACE("pb",
display_resolved_lemma(tout << conseq << "\n");
ctx.display(tout, js););
m_resolved.push_back(conseq);
bound = 1;
switch(js.get_kind()) {
case b_justification::CLAUSE: {
inc_coeff(conseq, offset);
clause& cls = *js.get_clause();
justification* cjs = cls.get_justification();
if (cjs && !is_proof_justification(*cjs)) {
TRACE("pb", tout << "skipping justification for clause over: " << conseq << " "
<< typeid(*cjs).name() << "\n";);
break;
}
unsigned num_lits = cls.get_num_literals();
if (cls.get_literal(0) == conseq) {
process_antecedent(cls.get_literal(1), offset);
}
else {
SASSERT(cls.get_literal(1) == conseq);
process_antecedent(cls.get_literal(0), offset);
}
for (unsigned i = 2; i < num_lits; ++i) {
process_antecedent(cls.get_literal(i), offset);
}
TRACE("pb", tout << literal_vector(cls.get_num_literals(), cls.begin()) << "\n";);
break;
}
case b_justification::BIN_CLAUSE:
inc_coeff(conseq, offset);
process_antecedent(~js.get_literal(), offset);
break;
case b_justification::AXIOM:
bound = 0;
break;
case b_justification::JUSTIFICATION: {
justification* j = js.get_justification();
card_justification* pbj = nullptr;
if (j->get_from_theory() == get_id()) {
pbj = dynamic_cast<card_justification*>(j);
}
if (pbj == nullptr) {
TRACE("pb", tout << "skip justification for " << conseq << "\n";);
bound = 0;
}
else {
card& c2 = pbj->get_card();
process_card(c2, offset);
bound = c2.k();
}
break;
}
default:
UNREACHABLE();
}
m_bound += offset * bound;
DEBUG_CODE(
B = justification2expr(js, conseq);
C = active2expr();
B = m.mk_and(A, B);
validate_implies(B, C);
A = C;);
cut();
process_next_resolvent:
while (true) {
conseq = lits[idx];
v = conseq.var();
if (ctx.is_marked(v)) break;
SASSERT(idx > 0);
--idx;
}
SASSERT(ctx.get_assign_level(v) == m_conflict_lvl);
ctx.unset_mark(v);
--idx;
--m_num_marks;
}
SASSERT(validate_lemma());
TRACE("pb", display_resolved_lemma(tout << "done\n"););
normalize_active_coeffs();
if (m_bound > 0 && m_active_vars.empty()) {
return false;
}
int slack = -m_bound;
for (bool_var v : m_active_vars) {
slack += get_abs_coeff(v);
}
unsigned i = 0;
literal_vector const& alits = ctx.assigned_literals();
literal alit = get_asserting_literal(~conseq);
slack -= get_abs_coeff(alit.var());
for (i = alits.size(); 0 <= slack && i-- > 0; ) {
literal lit = alits[i];
bool_var v = lit.var();
if (m_active_var_set.contains(v) && v != alit.var()) {
int coeff = get_coeff(v);
if (coeff < 0 && !lit.sign()) {
slack += coeff;
m_antecedents.push_back(lit);
}
else if (coeff > 0 && lit.sign()) {
slack -= coeff;
m_antecedents.push_back(lit);
}
}
}
SASSERT(slack < 0);
++m_stats.m_num_resolves;
SASSERT(validate_antecedents(m_antecedents));
TRACE("pb", tout << "assign " << m_antecedents << " ==> " << alit << "\n";);
ctx.assign(alit, ctx.mk_justification(theory_propagation_justification(get_id(), ctx.get_region(), m_antecedents.size(), m_antecedents.data(), alit, 0, nullptr)));
DEBUG_CODE(
m_antecedents.push_back(~alit);
expr_ref_vector args(m);
for (literal lit : m_antecedents) {
args.push_back(literal2expr(lit));
}
B = m.mk_not(m.mk_and(args.size(), args.data()));
validate_implies(A, B); );
return true;
}
bool theory_pb::is_proof_justification(justification const& j) const {
return typeid(smt::justification_proof_wrapper) == typeid(j) || m.proofs_enabled();
}
justification* theory_pb::justify(literal l1, literal l2) {
literal lits[2] = { l1, l2 };
justification* js = nullptr;
if (proofs_enabled()) {
js = ctx.mk_justification(theory_axiom_justification(get_id(), ctx.get_region(), 2, lits));
}
return js;
}
justification* theory_pb::justify(literal_vector const& lits) {
justification* js = nullptr;
if (proofs_enabled()) {
js = ctx.mk_justification(theory_axiom_justification(get_id(), ctx.get_region(), lits.size(), lits.data()));
}
return js;
}
void theory_pb::validate_watch(ineq const& c) const {
scoped_mpz sum(m_mpz_mgr), max(m_mpz_mgr);
for (unsigned i = 0; i < c.watch_size(); ++i) {
sum += c.ncoeff(i);
if (max < c.ncoeff(i)) {
max = c.ncoeff(i);
}
}
SASSERT(c.watch_sum() == sum);
SASSERT(sum >= c.mpz_k());
SASSERT(max == c.max_watch());
}
void theory_pb::validate_assign(ineq const& c, literal_vector const& lits, literal l) const {
uint_set nlits;
for (literal lit : lits) {
SASSERT(ctx.get_assignment(lit) == l_true);
nlits.insert((~lit).index());
}
SASSERT(ctx.get_assignment(l) == l_undef);
SASSERT(ctx.get_assignment(c.lit()) == l_true);
nlits.insert(l.index());
numeral sum = numeral::zero();
for (unsigned i = 0; i < c.size(); ++i) {
literal lit = c.lit(i);
if (!nlits.contains(lit.index())) {
sum += c.coeff(i);
}
}
CTRACE("pb", (sum >= c.k()),
display(tout << "invalid assign" , c, true);
for (literal lit : lits) tout << lit << " ";
tout << " => " << l << "\n";);
SASSERT(sum < c.k());
}
void theory_pb::validate_final_check() {
TRACE("pb", tout << "validate " << m_var_infos.size() << "\n";);
for (auto & vi : m_var_infos) {
if (vi.m_ineq) {
validate_final_check(*vi.m_ineq);
}
if (vi.m_card) {
validate_final_check(*vi.m_card);
}
}
}
void theory_pb::validate_final_check(card& c) {
if (ctx.get_assignment(c.lit()) == l_undef) {
TRACE("pb", display(tout << "is undef ", c, true););
return;
}
if (!ctx.is_relevant(c.lit())) {
TRACE("pb", display(tout << "not relevant ", c, true););
return;
}
unsigned sum = 0, maxsum = 0;
for (unsigned i = 0; i < c.size(); ++i) {
switch(ctx.get_assignment(c.lit(i))) {
case l_true:
++sum;
case l_undef:
++maxsum;
break;
case l_false:
break;
}
}
TRACE("pb_verbose", display(tout << "validate: ", c, true);
tout << "sum: " << sum << " " << maxsum << " ";
tout << ctx.get_assignment(c.lit()) << "\n";);
SASSERT(sum <= maxsum);
SASSERT((sum >= c.k()) == (ctx.get_assignment(c.lit()) == l_true));
SASSERT((maxsum < c.k()) == (ctx.get_assignment(c.lit()) == l_false));
}
void theory_pb::validate_final_check(ineq& c) {
if (ctx.get_assignment(c.lit()) == l_undef) {
TRACE("pb", tout << c.lit() << " is undef\n";);
return;
}
if (!ctx.is_relevant(c.lit())) {
TRACE("pb", tout << c.lit() << " is not relevant\n";);
return;
}
numeral sum = numeral::zero(), maxsum = numeral::zero();
for (unsigned i = 0; i < c.size(); ++i) {
switch(ctx.get_assignment(c.lit(i))) {
case l_true:
sum += c.coeff(i);
case l_undef:
maxsum += c.coeff(i);
break;
case l_false:
break;
}
}
TRACE("pb", display(tout << "validate: ", c, true);
tout << "sum: " << sum << " " << maxsum << " ";
tout << ctx.get_assignment(c.lit()) << "\n";
ctx.display(tout);
);
SASSERT(sum <= maxsum);
SASSERT(!c.is_ge() || (sum >= c.k()) == (ctx.get_assignment(c.lit()) == l_true));
SASSERT(!c.is_ge() || (maxsum < c.k()) == (ctx.get_assignment(c.lit()) == l_false));
SASSERT(!c.is_eq() || (sum == c.k()) == (ctx.get_assignment(c.lit()) == l_true));
}
bool theory_pb::validate_antecedents(literal_vector const& lits) {
for (literal lit : lits) {
if (ctx.get_assignment(lit) != l_true) {
return false;
}
}
return true;
}
bool theory_pb::validate_unit_propagation(card const& c) {
for (unsigned i = c.k(); i < c.size(); ++i) {
if (ctx.get_assignment(c.lit(i)) != l_false) {
return false;
}
}
return true;
}
app_ref theory_pb::literal2expr(literal lit) {
app_ref arg(m.mk_const(symbol((unsigned)lit.var()), m.mk_bool_sort()), m);
return app_ref(lit.sign() ? m.mk_not(arg) : arg, m);
}
app_ref theory_pb::active2expr() {
expr_ref_vector args(m);
vector<rational> coeffs;
normalize_active_coeffs();
for (unsigned i = 0; i < m_active_vars.size(); ++i) {
bool_var v = m_active_vars[i];
literal lit(v, get_coeff(v) < 0);
args.push_back(literal2expr(lit));
coeffs.push_back(rational(get_abs_coeff(v)));
}
rational k(m_bound);
return app_ref(pb.mk_ge(args.size(), coeffs.data(), args.data(), k), m);
}
void theory_pb::display_resolved_lemma(std::ostream& out) const {
out << "num marks: " << m_num_marks << "\n";
out << "conflict level: " << m_conflict_lvl << "\n";
for (literal r : m_resolved) {
out << ctx.get_assign_level(r) << ": " << r << " ";
ctx.display(out, ctx.get_justification(r.var()));
}
if (!m_antecedents.empty()) {
out << m_antecedents << " ==> ";
}
uint_set seen;
bool first = true;
for (bool_var v: m_active_vars) {
if (seen.contains(v)) {
continue;
}
seen.insert(v);
int coeff = get_coeff(v);
if (coeff == 0) {
continue;
}
if (!first) {
out << " + ";
}
literal lit(v, coeff < 0);
if (coeff > 1) {
out << coeff << " * ";
}
else if (coeff < -1) {
out << (-coeff) << " * ";
}
out << lit << "(" << ctx.get_assignment(lit) << "@" << ctx.get_assign_level(lit) << ")";
first = false;
}
out << " >= " << m_bound << "\n";
}
std::ostream& theory_pb::display(std::ostream& out, arg_t const& c, bool values) const {
return c.display(ctx, out, values);
}
std::ostream& theory_pb::display(std::ostream& out, ineq const& c, bool values) const {
out << c.lit();
if (c.lit() != null_literal) {
if (values) {
out << "@(" << ctx.get_assignment(c.lit());
if (ctx.get_assignment(c.lit()) != l_undef) {
out << ":" << ctx.get_assign_level(c.lit());
}
out << ")";
}
expr_ref tmp(m);
ctx.literal2expr(c.lit(), tmp);
out << " " << tmp << "\n";
}
else {
out << " ";
}
for (unsigned i = 0; i < c.size(); ++i) {
literal l(c.lit(i));
if (!c.coeff(i).is_one()) {
out << c.coeff(i) << "*";
}
out << l;
if (values) {
out << "@(" << ctx.get_assignment(l);
if (ctx.get_assignment(l) != l_undef) {
out << ":" << ctx.get_assign_level(l);
}
out << ")";
}
if (i + 1 == c.watch_size()) {
out << " .w ";
}
if (i + 1 < c.size()) {
out << " + ";
}
}
out << (c.is_ge()?" >= ":" = ") << c.k() << "\n";
if (c.m_num_propagations) out << "propagations: " << c.m_num_propagations << " ";
if (c.m_max_watch.is_pos()) out << "max_watch: " << c.max_watch() << " ";
if (c.watch_size()) out << "watch size: " << c.watch_size() << " ";
if (c.m_watch_sum.is_pos()) out << "watch-sum: " << c.watch_sum() << " ";
if (!c.m_max_sum.is_zero()) out << "sum: [" << c.min_sum() << ":" << c.max_sum() << "] ";
if (c.m_num_propagations || c.m_max_watch.is_pos() || c.watch_size() ||
c.m_watch_sum.is_pos() || !c.m_max_sum.is_zero()) out << "\n";
return out;
}
class theory_pb::pb_model_value_proc : public model_value_proc {
app* m_app;
svector<model_value_dependency> m_dependencies;
public:
pb_model_value_proc(app* a):
m_app(a) {}
void add(enode* n) {
m_dependencies.push_back(model_value_dependency(n));
}
void get_dependencies(buffer<model_value_dependency> & result) override {
result.append(m_dependencies.size(), m_dependencies.data());
}
app * mk_value(model_generator & mg, expr_ref_vector const& values) override {
ast_manager& m = mg.get_manager();
SASSERT(values.size() == m_dependencies.size());
SASSERT(values.size() == m_app->get_num_args());
pb_util u(m);
rational sum(0);
for (unsigned i = 0; i < m_app->get_num_args(); ++i) {
if (!m.is_true(values[i]) && !m.is_false(values[i])) {
return m_app;
}
if (m.is_true(values[i])) {
sum += u.get_coeff(m_app, i);
}
}
rational k = u.get_k(m_app);
switch(m_app->get_decl_kind()) {
case OP_AT_MOST_K:
return (sum <= k)?m.mk_true():m.mk_false();
case OP_AT_LEAST_K:
return (sum >= k)?m.mk_true():m.mk_false();
case OP_PB_LE:
return (sum <= k)?m.mk_true():m.mk_false();
case OP_PB_GE:
return (sum >= k)?m.mk_true():m.mk_false();
default:
UNREACHABLE();
return nullptr;
}
return nullptr;
}
};
class pb_factory : public value_factory {
public:
pb_factory(ast_manager& m, family_id fid):
value_factory(m, fid) {}
expr * get_some_value(sort * s) override {
return m_manager.mk_true();
}
bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) override {
v1 = m_manager.mk_true();
v2 = m_manager.mk_false();
return true;
}
expr * get_fresh_value(sort * s) override {
return nullptr;
}
void register_value(expr * n) override { }
};
void theory_pb::init_model(model_generator & m) {
m.register_factory(alloc(pb_factory, get_manager(), get_id()));
}
model_value_proc * theory_pb::mk_value(enode * n, model_generator & mg) {
app* a = n->get_expr();
pb_model_value_proc* p = alloc(pb_model_value_proc, a);
for (unsigned i = 0; i < a->get_num_args(); ++i) {
p->add(ctx.get_enode(a->get_arg(i)));
}
return p;
}
void theory_pb::display_watch(std::ostream& out, bool_var v, bool sign) const {
ineq_watch const* w = m_var_infos[v].m_lit_watch[sign];
if (!w) return;
ineq_watch const& wl = *w;
out << "watch: " << literal(v, sign) << " |-> ";
for (unsigned i = 0; i < wl.size(); ++i) {
out << wl[i]->lit() << " ";
}
out << "\n";
}
void theory_pb::display(std::ostream& out) const {
for (unsigned vi = 0; vi < m_var_infos.size(); ++vi) {
display_watch(out, vi, false);
display_watch(out, vi, true);
}
for (unsigned vi = 0; vi < m_var_infos.size(); ++vi) {
ineq* c = m_var_infos[vi].m_ineq;
if (c) {
display(out, *c, true);
}
}
for (unsigned vi = 0; vi < m_var_infos.size(); ++vi) {
card* c = m_var_infos[vi].m_card;
if (c) {
display(out, *c, true);
}
}
}
}