#include "ast/expr_functors.h"
#include "muz/spacer/spacer_context.h"
using namespace spacer;
namespace {
class contains_array_op_proc : public i_expr_pred {
ast_manager &m;
family_id m_array_fid;
public:
contains_array_op_proc(ast_manager &manager)
: m(manager), m_array_fid(array_util(m).get_family_id()) {}
bool operator()(expr *e) override {
return is_app(e) && to_app(e)->get_family_id() == m_array_fid;
}
};
class lemma_inductive_generalizer : public lemma_generalizer {
struct stats {
unsigned count;
unsigned weaken_success;
unsigned weaken_fail;
stopwatch watch;
stats() { reset(); }
void reset() {
count = 0;
weaken_success = 0;
weaken_fail = 0;
watch.reset();
}
};
ast_manager &m;
expr_ref m_true;
stats m_st;
bool m_only_array_eligible;
bool m_enable_litweak;
contains_array_op_proc m_contains_array_op;
check_pred m_contains_array_pred;
expr_ref_vector m_pinned;
lemma *m_lemma = nullptr;
spacer::pred_transformer *m_pt = nullptr;
unsigned m_weakness = 0;
unsigned m_level = 0;
ptr_vector<expr> m_cube;
expr_ref_vector m_core;
public:
lemma_inductive_generalizer(spacer::context &ctx,
bool only_array_eligible = false,
bool enable_literal_weakening = true)
: lemma_generalizer(ctx), m(ctx.get_ast_manager()),
m_true(m.mk_true(), m), m_only_array_eligible(only_array_eligible),
m_enable_litweak(enable_literal_weakening), m_contains_array_op(m),
m_contains_array_pred(m_contains_array_op, m),
m_pinned(m), m_core(m) {}
private:
bool is_eligible(expr *lit) {
return !m_only_array_eligible || has_arrays(lit);
}
bool has_arrays(expr *lit) { return m_contains_array_op(lit); }
void reset() {
m_cube.reset();
m_weakness = 0;
m_level = 0;
m_pt = nullptr;
m_pinned.reset();
m_core.reset();
}
void setup(lemma_ref &lemma) {
SASSERT(m_pt == nullptr);
m_lemma = lemma.get();
m_pt = &lemma->get_pob()->pt();
m_weakness = lemma->weakness();
m_level = lemma->level();
auto &cube = lemma->get_cube();
m_cube.reset();
for (auto *lit : cube) { m_cube.push_back(lit); }
}
void load_cube_to_core() {
m_core.reset();
for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) {
auto *lit = m_cube.get(i);
if (lit == m_true) continue;
m_core.push_back(lit);
}
}
bool is_cube_inductive() {
load_cube_to_core();
if (m_core.empty()) return false;
unsigned used_level;
if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) {
m_level = std::max(m_level, used_level);
return true;
}
return false;
}
unsigned update_cube_by_core(unsigned from = 0) {
unsigned success = 0;
ast_fast_mark2 marked_core;
for (auto *v : m_core) { marked_core.mark(v); }
for (unsigned i = from, sz = m_cube.size(); i < sz; ++i) {
auto *lit = m_cube.get(i);
if (lit == m_true) continue;
if (!marked_core.is_marked(lit)) {
m_cube[i] = m_true;
success++;
}
}
return success;
}
unsigned generalize_core(unsigned from = 0) {
unsigned success = 0;
unsigned used_level;
if (m_core.empty()) return 0;
if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) {
success += update_cube_by_core(from);
m_level = std::max(m_level, used_level);
}
return success;
}
unsigned generalize1(unsigned lit_idx) {
if (!is_eligible(m_cube.get(lit_idx))) return 0;
m_core.reset();
for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) {
auto *lit = m_cube.get(i);
if (lit == m_true || i == lit_idx) continue;
m_core.push_back(lit);
}
return generalize_core(lit_idx);
}
unsigned generalize_range(unsigned from, unsigned to) {
unsigned success = 0;
for (unsigned i = from; i < to; ++i) { success += generalize1(i); }
return success;
}
unsigned weaken1(unsigned lit_idx) {
if (!is_eligible(m_cube.get(lit_idx))) return 0;
if (m_cube.get(lit_idx) == m_true) return 0;
unsigned success = 0;
unsigned cube_sz = m_cube.size();
expr *saved_lit = m_cube.get(lit_idx);
m_cube[lit_idx] = m_true;
expr_ref_vector weakening(m);
weakening.push_back(saved_lit);
expand_literals(m, weakening);
if (weakening.get(0) != saved_lit) {
for (auto *lit : weakening) {
m_cube.push_back(lit);
m_pinned.push_back(lit);
}
if (m_cube.size() - cube_sz >= 2) {
success += generalize_range(cube_sz, m_cube.size());
} else {
success += (is_cube_inductive() ? 1 : 0);
}
}
if (success == 0) {
m_cube[lit_idx] = saved_lit;
m_cube.shrink(cube_sz);
m_st.weaken_fail++;
} else {
m_st.weaken_success++;
}
return success;
}
unsigned weaken_range(unsigned from, unsigned to) {
unsigned success = 0;
for (unsigned i = from; i < to; ++i) { success += weaken1(i); }
return success;
}
public:
void operator()(lemma_ref &lemma) override {
if (lemma->get_cube().empty()) return;
m_st.count++;
scoped_watch _w_(m_st.watch);
setup(lemma);
unsigned num_gens = 0;
num_gens += generalize_range(0, m_cube.size());
if (m_enable_litweak) {
unsigned cube_sz = m_cube.size();
num_gens += weaken_range(0, cube_sz);
if (cube_sz < m_cube.size())
num_gens += weaken_range(cube_sz, m_cube.size());
}
if (num_gens > 0) {
TRACE(indgen,
tout << "Generalized " << num_gens << " literals\n";);
m_core.reset();
for (auto *lit : m_cube) {
if (lit != m_true) m_core.push_back(lit);
}
TRACE(indgen, tout << "Original: " << lemma->get_cube() << "\n"
<< "Generalized: " << m_core << "\n";);
lemma->update_cube(lemma->get_pob(), m_core);
lemma->set_level(m_level);
}
reset();
return;
}
void collect_statistics(statistics &st) const override {
st.update("time.spacer.solve.reach.gen.ind", m_st.watch.get_seconds());
st.update("SPACER inductive gen", m_st.count);
st.update("SPACER inductive gen weaken success", m_st.weaken_success);
st.update("SPACER inductive gen weaken fail", m_st.weaken_fail);
}
void reset_statistics() override { m_st.reset(); }
};
}
namespace spacer {
lemma_generalizer *
alloc_lemma_inductive_generalizer(spacer::context &ctx,
bool only_array_eligible,
bool enable_literal_weakening) {
return alloc(lemma_inductive_generalizer, ctx, only_array_eligible,
enable_literal_weakening);
}
}