mirror of
https://github.com/Z3Prover/z3
synced 2025-04-06 17:44:08 +00:00
928 lines
30 KiB
C++
928 lines
30 KiB
C++
/*++
|
|
Copyright (c) 2007 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
nnf.cpp
|
|
|
|
Abstract:
|
|
|
|
Negation Normal Form & Skolemization
|
|
|
|
Author:
|
|
|
|
Leonardo (leonardo) 2008-01-11
|
|
|
|
Notes:
|
|
Major revision on 2011-10-06
|
|
|
|
--*/
|
|
#include"nnf.h"
|
|
#include"warning.h"
|
|
#include"used_vars.h"
|
|
#include"well_sorted.h"
|
|
#include"var_subst.h"
|
|
|
|
#include"name_exprs.h"
|
|
#include"act_cache.h"
|
|
#include"cooperate.h"
|
|
|
|
#include"ast_smt2_pp.h"
|
|
|
|
class skolemizer {
|
|
typedef act_cache cache;
|
|
|
|
ast_manager & m_manager;
|
|
symbol m_sk_hack;
|
|
bool m_sk_hack_enabled;
|
|
cache m_cache;
|
|
cache m_cache_pr;
|
|
|
|
void process(quantifier * q, expr_ref & r, proof_ref & p) {
|
|
used_vars uv;
|
|
uv(q);
|
|
SASSERT(is_well_sorted(m(), q));
|
|
unsigned sz = uv.get_max_found_var_idx_plus_1();
|
|
ptr_buffer<sort> sorts;
|
|
expr_ref_vector args(m());
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
sort * s = uv.get(i);
|
|
if (s != 0) {
|
|
sorts.push_back(s);
|
|
args.push_back(m().mk_var(i, s));
|
|
}
|
|
}
|
|
|
|
TRACE("skolemizer", tout << "skid: " << q->get_skid() << "\n";);
|
|
|
|
expr_ref_vector substitution(m());
|
|
unsigned num_decls = q->get_num_decls();
|
|
for (unsigned i = num_decls; i > 0; ) {
|
|
--i;
|
|
sort * r = q->get_decl_sort(i);
|
|
func_decl * sk_decl = m().mk_fresh_func_decl(q->get_decl_name(i), q->get_skid(), sorts.size(), sorts.c_ptr(), r);
|
|
app * sk = m().mk_app(sk_decl, args.size(), args.c_ptr());
|
|
substitution.push_back(sk);
|
|
}
|
|
//
|
|
// (VAR 0) is in the first position of substitution.
|
|
// (VAR num_decls-1) is in the last position.
|
|
//
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
sort * s = uv.get(i);
|
|
if (s != 0)
|
|
substitution.push_back(m().mk_var(i, s));
|
|
else
|
|
substitution.push_back(0);
|
|
}
|
|
//
|
|
// (VAR num_decls) ... (VAR num_decls+sz-1)
|
|
// are in positions num_decls .. num_decls+sz-1
|
|
//
|
|
std::reverse(substitution.c_ptr(), substitution.c_ptr() + substitution.size());
|
|
//
|
|
// (VAR 0) should be in the last position of substitution.
|
|
//
|
|
var_subst s(m());
|
|
SASSERT(is_well_sorted(m(), q->get_expr()));
|
|
expr_ref tmp(m());
|
|
expr * body = q->get_expr();
|
|
if (m_sk_hack_enabled) {
|
|
unsigned num_patterns = q->get_num_patterns();
|
|
for (unsigned i = 0; i < num_patterns; ++i) {
|
|
expr * p = q->get_pattern(i);
|
|
if (is_sk_hack(p)) {
|
|
expr * sk_hack = to_app(p)->get_arg(0);
|
|
if (q->is_forall()) // check whether is in negative/positive context.
|
|
tmp = m().mk_or(body, m().mk_not(sk_hack)); // negative context
|
|
else
|
|
tmp = m().mk_and(body, sk_hack); // positive context
|
|
body = tmp;
|
|
}
|
|
}
|
|
}
|
|
s(body, substitution.size(), substitution.c_ptr(), r);
|
|
SASSERT(is_well_sorted(m(), r));
|
|
p = 0;
|
|
if (m().proofs_enabled()) {
|
|
if (q->is_forall())
|
|
p = m().mk_skolemization(m().mk_not(q), m().mk_not(r));
|
|
else
|
|
p = m().mk_skolemization(q, r);
|
|
}
|
|
}
|
|
|
|
public:
|
|
skolemizer(ast_manager & m, params_ref const & p):
|
|
m_manager(m),
|
|
m_sk_hack("sk_hack"),
|
|
m_cache(m),
|
|
m_cache_pr(m) {
|
|
updt_params(p);
|
|
}
|
|
|
|
void updt_params(params_ref const & p) {
|
|
m_sk_hack_enabled = p.get_bool("sk_hack", false);
|
|
}
|
|
|
|
static void get_param_descrs(param_descrs & r) {
|
|
r.insert("sk_hack", CPK_BOOL, "(default: false) hack for VCC");
|
|
}
|
|
|
|
ast_manager & m() const { return m_manager; }
|
|
|
|
void operator()(quantifier * q, expr_ref & r, proof_ref & p) {
|
|
r = m_cache.find(q);
|
|
if (r.get() != 0) {
|
|
p = 0;
|
|
if (m().proofs_enabled())
|
|
p = static_cast<proof*>(m_cache_pr.find(q));
|
|
}
|
|
else {
|
|
process(q, r, p);
|
|
m_cache.insert(q, r);
|
|
if (m().proofs_enabled())
|
|
m_cache_pr.insert(q, p);
|
|
}
|
|
}
|
|
|
|
bool is_sk_hack(expr * p) const {
|
|
SASSERT(m().is_pattern(p));
|
|
if (to_app(p)->get_num_args() != 1)
|
|
return false;
|
|
expr * body = to_app(p)->get_arg(0);
|
|
if (!is_app(body))
|
|
return false;
|
|
func_decl * f = to_app(body)->get_decl();
|
|
if (!(f->get_name() == m_sk_hack && f->get_arity() == 1))
|
|
return false;
|
|
if (!m().is_bool(body)) {
|
|
warning_msg("sk_hack constant must return a Boolean");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
typedef default_exception nnf_params_exception;
|
|
typedef default_exception nnf_exception;
|
|
|
|
struct nnf::imp {
|
|
|
|
struct frame {
|
|
expr * m_curr;
|
|
unsigned m_i:28;
|
|
unsigned m_pol:1; // pos/neg polarity
|
|
unsigned m_in_q:1; // true if m_curr is nested in a quantifier
|
|
unsigned m_new_child:1;
|
|
unsigned m_cache_result:1;
|
|
unsigned m_spos; // top of the result stack, when the frame was created.
|
|
frame(expr * n, bool pol, bool in_q, bool cache_res, unsigned spos):
|
|
m_curr(n),
|
|
m_i(0),
|
|
m_pol(pol),
|
|
m_in_q(in_q),
|
|
m_new_child(false),
|
|
m_cache_result(cache_res),
|
|
m_spos(spos) {
|
|
}
|
|
};
|
|
|
|
// There are four caches:
|
|
#define NEG_NQ_CIDX 0 // negative polarity and not nested in a quantifier
|
|
#define POS_NQ_CIDX 1 // positive polarity and not nested in a quantifier
|
|
#define NEG_Q_CIDX 2 // negative polarity and nested in a quantifier
|
|
#define POS_Q_CIDX 3 // positive polarity and nested in a quantifier
|
|
|
|
ast_manager & m_manager;
|
|
svector<frame> m_frame_stack;
|
|
expr_ref_vector m_result_stack;
|
|
|
|
typedef act_cache cache;
|
|
cache * m_cache[4];
|
|
|
|
expr_ref_vector m_todo_defs;
|
|
proof_ref_vector m_todo_proofs;
|
|
|
|
// proof generation goodness ----
|
|
proof_ref_vector m_result_pr_stack;
|
|
cache * m_cache_pr[4];
|
|
// ------------------------------
|
|
|
|
skolemizer m_skolemizer;
|
|
|
|
// configuration ----------------
|
|
nnf_mode m_mode;
|
|
bool m_ignore_labels;
|
|
bool m_skolemize;
|
|
// ------------------------------
|
|
|
|
name_exprs * m_name_nested_formulas;
|
|
name_exprs * m_name_quant;
|
|
|
|
symbol m_skolem;
|
|
|
|
volatile bool m_cancel;
|
|
unsigned long long m_max_memory; // in bytes
|
|
|
|
imp(ast_manager & m, defined_names & n, params_ref const & p):
|
|
m_manager(m),
|
|
m_result_stack(m),
|
|
m_todo_defs(m),
|
|
m_todo_proofs(m),
|
|
m_result_pr_stack(m),
|
|
m_skolemizer(m, p),
|
|
m_skolem("skolem"),
|
|
m_cancel(false) {
|
|
updt_local_params(p);
|
|
for (unsigned i = 0; i < 4; i++) {
|
|
m_cache[i] = alloc(act_cache, m);
|
|
if (m.proofs_enabled())
|
|
m_cache_pr[i] = alloc(act_cache, m);
|
|
}
|
|
m_name_nested_formulas = mk_nested_formula_namer(m, n);
|
|
m_name_quant = mk_quantifier_label_namer(m, n);
|
|
}
|
|
|
|
ast_manager & m() const { return m_manager; }
|
|
|
|
bool proofs_enabled() const { return m().proofs_enabled(); }
|
|
|
|
~imp() {
|
|
for (unsigned i = 0; i < 4; i++) {
|
|
dealloc(m_cache[i]);
|
|
if (proofs_enabled())
|
|
dealloc(m_cache_pr[i]);
|
|
}
|
|
del_name_exprs(m_name_nested_formulas);
|
|
del_name_exprs(m_name_quant);
|
|
}
|
|
|
|
void updt_params(params_ref const & p) {
|
|
updt_local_params(p);
|
|
m_skolemizer.updt_params(p);
|
|
}
|
|
|
|
void updt_local_params(params_ref const & p) {
|
|
symbol mode_sym = p.get_sym("mode", m_skolem);
|
|
if (mode_sym == m_skolem)
|
|
m_mode = NNF_SKOLEM;
|
|
else if (mode_sym == "full")
|
|
m_mode = NNF_FULL;
|
|
else if (mode_sym == "quantifiers")
|
|
m_mode = NNF_QUANT;
|
|
else
|
|
throw nnf_params_exception("invalid NNF mode");
|
|
|
|
TRACE("nnf", tout << "nnf-mode: " << m_mode << " " << mode_sym << "\n" << p << "\n";);
|
|
|
|
m_ignore_labels = p.get_bool("ignore_labels", false);
|
|
m_skolemize = p.get_bool("skolemize", true);
|
|
m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX));
|
|
}
|
|
|
|
static void get_param_descrs(param_descrs & r) {
|
|
insert_max_memory(r);
|
|
r.insert("mode", CPK_SYMBOL,
|
|
"(default: skolem) NNF translation mode: skolem (skolem normal form), quantifiers (skolem normal form + quantifiers in NNF), full");
|
|
r.insert("ignore_labels", CPK_BOOL,
|
|
"(default: false) remove/ignore labels in the input formula, this option is ignored if proofs are enabled");
|
|
r.insert("skolemize", CPK_BOOL,
|
|
"(default: true) skolemize (existential force) quantifiers");
|
|
skolemizer::get_param_descrs(r);
|
|
}
|
|
|
|
void reset() {
|
|
m_frame_stack.reset();
|
|
m_result_stack.reset();
|
|
m_result_pr_stack.reset();
|
|
m_todo_defs.reset();
|
|
m_todo_proofs.reset();
|
|
}
|
|
|
|
void reset_cache() {
|
|
for (unsigned i = 0; i < 4; i++) {
|
|
m_cache[i]->reset();
|
|
if (proofs_enabled())
|
|
m_cache_pr[i]->reset();
|
|
}
|
|
}
|
|
|
|
void push_frame(expr * t, bool pol, bool in_q, bool cache_res) {
|
|
m_frame_stack.push_back(frame(t, pol, in_q, cache_res, m_result_stack.size()));
|
|
}
|
|
|
|
static unsigned get_cache_idx(bool pol, bool in_q) {
|
|
return static_cast<unsigned>(in_q) * 2 + static_cast<unsigned>(pol);
|
|
}
|
|
|
|
void cache_result(expr * t, bool pol, bool in_q, expr * v, proof * pr) {
|
|
unsigned idx = get_cache_idx(pol, in_q);
|
|
m_cache[idx]->insert(t, v);
|
|
if (proofs_enabled())
|
|
m_cache_pr[idx]->insert(t, pr);
|
|
}
|
|
|
|
expr * get_cached(expr * t, bool pol, bool in_q) const {
|
|
return m_cache[get_cache_idx(pol, in_q)]->find(t);
|
|
}
|
|
|
|
proof * get_cached_pr(expr * t, bool pol, bool in_q) const {
|
|
SASSERT(proofs_enabled());
|
|
return static_cast<proof*>(m_cache_pr[get_cache_idx(pol, in_q)]->find(t));
|
|
}
|
|
|
|
/**
|
|
\brief Return true if the result for (t, pol, in_q) is already cached,
|
|
and store the result on the stack.
|
|
*/
|
|
bool process_cached(expr * t, bool pol, bool in_q) {
|
|
expr * r = get_cached(t, pol, in_q);
|
|
if (r) {
|
|
m_result_stack.push_back(r);
|
|
if (proofs_enabled()) {
|
|
proof * pr = get_cached_pr(t, pol, in_q);
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
m_frame_stack.pop_back();
|
|
set_new_child_flag(t, r);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void set_cancel(bool f) {
|
|
m_cancel = f;
|
|
}
|
|
|
|
void checkpoint() {
|
|
cooperate("nnf");
|
|
if (memory::get_allocation_size() > m_max_memory)
|
|
throw nnf_exception(Z3_MAX_MEMORY_MSG);
|
|
if (m_cancel)
|
|
throw nnf_exception(Z3_CANCELED_MSG);
|
|
}
|
|
|
|
void set_new_child_flag() {
|
|
if (!m_frame_stack.empty())
|
|
m_frame_stack.back().m_new_child = true;
|
|
}
|
|
|
|
void set_new_child_flag(expr * old_t, expr * new_t) {
|
|
if (old_t != new_t)
|
|
set_new_child_flag();
|
|
}
|
|
|
|
void skip(expr * t, bool pol) {
|
|
expr * r = pol ? t : m().mk_not(t);
|
|
m_result_stack.push_back(r);
|
|
if (proofs_enabled()) {
|
|
m_result_pr_stack.push_back(m().mk_oeq_reflexivity(r));
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
}
|
|
|
|
bool visit(expr * t, bool pol, bool in_q) {
|
|
SASSERT(m().is_bool(t));
|
|
|
|
if (m_mode == NNF_SKOLEM || (m_mode == NNF_QUANT && !in_q)) {
|
|
if (!has_quantifiers(t) && !has_labels(t)) {
|
|
skip(t, pol);
|
|
return true; // t does not need to be processed
|
|
}
|
|
}
|
|
|
|
bool cache_res = t->get_ref_count() > 1;
|
|
|
|
if (cache_res) {
|
|
expr * r = get_cached(t, pol, in_q);
|
|
if (r) {
|
|
m_result_stack.push_back(r);
|
|
set_new_child_flag(t, r);
|
|
if (proofs_enabled()) {
|
|
proof * pr = get_cached_pr(t, pol, in_q);
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
return true; // t was already processed
|
|
}
|
|
}
|
|
|
|
switch (t->get_kind()) {
|
|
case AST_APP:
|
|
if (to_app(t)->get_num_args() == 0) {
|
|
skip(t, pol);
|
|
return true;
|
|
}
|
|
else {
|
|
push_frame(t, pol, in_q, cache_res);
|
|
return false;
|
|
}
|
|
case AST_QUANTIFIER:
|
|
push_frame(t, pol, in_q, cache_res);
|
|
return false;
|
|
case AST_VAR:
|
|
skip(t, pol);
|
|
return true;
|
|
default:
|
|
UNREACHABLE();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
proof * mk_proof(bool pol, unsigned num_parents, proof * const * parents, app * old_e, app * new_e) {
|
|
if (pol) {
|
|
if (old_e->get_decl() == new_e->get_decl())
|
|
return m().mk_oeq_congruence(old_e, new_e, num_parents, parents);
|
|
else
|
|
return m().mk_nnf_pos(old_e, new_e, num_parents, parents);
|
|
}
|
|
else
|
|
return m().mk_nnf_neg(old_e, new_e, num_parents, parents);
|
|
}
|
|
|
|
bool process_and_or(app * t, frame & fr) {
|
|
unsigned num_args = t->get_num_args();
|
|
while (fr.m_i < num_args) {
|
|
expr * arg = t->get_arg(fr.m_i);
|
|
fr.m_i++;
|
|
if (!visit(arg, fr.m_pol, fr.m_in_q))
|
|
return false;
|
|
}
|
|
app * r;
|
|
if (m().is_and(t) == fr.m_pol)
|
|
r = m().mk_and(t->get_num_args(), m_result_stack.c_ptr() + fr.m_spos);
|
|
else
|
|
r = m().mk_or(t->get_num_args(), m_result_stack.c_ptr() + fr.m_spos);
|
|
|
|
m_result_stack.shrink(fr.m_spos);
|
|
m_result_stack.push_back(r);
|
|
if (proofs_enabled()) {
|
|
proof * pr = mk_proof(fr.m_pol, t->get_num_args(), m_result_pr_stack.c_ptr() + fr.m_spos, t, r);
|
|
m_result_pr_stack.shrink(fr.m_spos);
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool process_not(app * t, frame & fr) {
|
|
if (fr.m_i == 0) {
|
|
fr.m_i = 1;
|
|
if (!visit(t->get_arg(0), !fr.m_pol, fr.m_in_q))
|
|
return false;
|
|
}
|
|
expr * r = m_result_stack.back();
|
|
proof * pr = 0;
|
|
if (proofs_enabled()) {
|
|
pr = m_result_pr_stack.back();
|
|
if (!fr.m_pol) {
|
|
pr = m().mk_nnf_neg(t, r, 1, &pr);
|
|
m_result_pr_stack.pop_back();
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool process_implies(app * t, frame & fr) {
|
|
SASSERT(t->get_num_args() == 2);
|
|
switch (fr.m_i) {
|
|
case 0:
|
|
fr.m_i = 1;
|
|
if (!visit(t->get_arg(0), !fr.m_pol, fr.m_in_q))
|
|
return false;
|
|
case 1:
|
|
fr.m_i = 2;
|
|
if (!visit(t->get_arg(1), fr.m_pol, fr.m_in_q))
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
app * r;
|
|
if (fr.m_pol)
|
|
r = m().mk_or(2, m_result_stack.c_ptr() + fr.m_spos);
|
|
else
|
|
r = m().mk_and(2, m_result_stack.c_ptr() + fr.m_spos);
|
|
|
|
m_result_stack.shrink(fr.m_spos);
|
|
m_result_stack.push_back(r);
|
|
if (proofs_enabled()) {
|
|
proof * pr = mk_proof(fr.m_pol, 2, m_result_pr_stack.c_ptr() + fr.m_spos, t, r);
|
|
m_result_pr_stack.shrink(fr.m_spos);
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool process_ite(app * t, frame & fr) {
|
|
SASSERT(t->get_num_args() == 3);
|
|
switch (fr.m_i) {
|
|
case 0:
|
|
fr.m_i = 1;
|
|
if (!visit(t->get_arg(0), true, fr.m_in_q))
|
|
return false;
|
|
case 1:
|
|
fr.m_i = 2;
|
|
if (!visit(t->get_arg(0), false, fr.m_in_q))
|
|
return false;
|
|
case 2:
|
|
fr.m_i = 3;
|
|
if (!visit(t->get_arg(1), fr.m_pol, fr.m_in_q))
|
|
return false;
|
|
case 3:
|
|
fr.m_i = 4;
|
|
if (!visit(t->get_arg(2), fr.m_pol, fr.m_in_q))
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
expr * const * rs = m_result_stack.c_ptr() + fr.m_spos;
|
|
expr * _cond = rs[0];
|
|
expr * _not_cond = rs[1];
|
|
expr * _then = rs[2];
|
|
expr * _else = rs[3];
|
|
|
|
app * r = m().mk_and(m().mk_or(_not_cond, _then), m().mk_or(_cond, _else));
|
|
m_result_stack.shrink(fr.m_spos);
|
|
m_result_stack.push_back(r);
|
|
if (proofs_enabled()) {
|
|
proof * pr = mk_proof(fr.m_pol, 4, m_result_pr_stack.c_ptr() + fr.m_spos, t, r);
|
|
m_result_pr_stack.shrink(fr.m_spos);
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool is_eq(app * t) const { return m().is_eq(t) || m().is_iff(t); }
|
|
|
|
bool process_iff_xor(app * t, frame & fr) {
|
|
SASSERT(t->get_num_args() == 2);
|
|
switch (fr.m_i) {
|
|
case 0:
|
|
fr.m_i = 1;
|
|
if (!visit(t->get_arg(0), true, fr.m_in_q))
|
|
return false;
|
|
case 1:
|
|
fr.m_i = 2;
|
|
if (!visit(t->get_arg(0), false, fr.m_in_q))
|
|
return false;
|
|
case 2:
|
|
fr.m_i = 3;
|
|
if (!visit(t->get_arg(1), true, fr.m_in_q))
|
|
return false;
|
|
case 3:
|
|
fr.m_i = 4;
|
|
if (!visit(t->get_arg(1), false, fr.m_in_q))
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
expr * const * rs = m_result_stack.c_ptr() + fr.m_spos;
|
|
expr * lhs = rs[0];
|
|
expr * not_lhs = rs[1];
|
|
expr * rhs = rs[2];
|
|
expr * not_rhs = rs[3];
|
|
|
|
app * r;
|
|
if (is_eq(t) == fr.m_pol)
|
|
r = m().mk_and(m().mk_or(not_lhs, rhs), m().mk_or(lhs, not_rhs));
|
|
else
|
|
r = m().mk_and(m().mk_or(lhs, rhs), m().mk_or(not_lhs, not_rhs));
|
|
m_result_stack.shrink(fr.m_spos);
|
|
m_result_stack.push_back(r);
|
|
if (proofs_enabled()) {
|
|
proof * pr = mk_proof(fr.m_pol, 4, m_result_pr_stack.c_ptr() + fr.m_spos, t, r);
|
|
m_result_pr_stack.shrink(fr.m_spos);
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool process_eq(app * t, frame & fr) {
|
|
if (m().is_bool(t->get_arg(0)))
|
|
return process_iff_xor(t, fr);
|
|
else
|
|
return process_default(t, fr);
|
|
}
|
|
|
|
bool process_default(app * t, frame & fr) {
|
|
SASSERT(fr.m_i == 0);
|
|
if (m_mode == NNF_FULL || t->has_quantifiers() || t->has_labels()) {
|
|
expr_ref n2(m());
|
|
proof_ref pr2(m());
|
|
if (m_mode == NNF_FULL || (m_mode != NNF_SKOLEM && fr.m_in_q))
|
|
m_name_nested_formulas->operator()(t, m_todo_defs, m_todo_proofs, n2, pr2);
|
|
else
|
|
m_name_quant->operator()(t, m_todo_defs, m_todo_proofs, n2, pr2);
|
|
|
|
if (!fr.m_pol)
|
|
n2 = m().mk_not(n2);
|
|
|
|
m_result_stack.push_back(n2);
|
|
if (proofs_enabled()) {
|
|
if (!fr.m_pol) {
|
|
proof * prs[1] = { pr2 };
|
|
pr2 = m().mk_oeq_congruence(m().mk_not(t), static_cast<app*>(n2.get()), 1, prs);
|
|
}
|
|
m_result_pr_stack.push_back(pr2);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
}
|
|
else {
|
|
skip(t, fr.m_pol);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool process_label(app * t, frame & fr) {
|
|
if (fr.m_i == 0) {
|
|
fr.m_i = 1;
|
|
if (!visit(t->get_arg(0), fr.m_pol, fr.m_in_q))
|
|
return false;
|
|
}
|
|
|
|
expr * arg = m_result_stack.back();
|
|
proof * arg_pr = proofs_enabled() ? m_result_pr_stack.back() : 0;
|
|
|
|
if (m_ignore_labels && !proofs_enabled())
|
|
return true; // the result is already on the stack
|
|
|
|
|
|
buffer<symbol> names;
|
|
bool pos;
|
|
m().is_label(t, pos, names);
|
|
expr_ref r(m());
|
|
proof_ref pr(m());
|
|
if (fr.m_pol == pos) {
|
|
expr * lbl_lit = m().mk_label_lit(names.size(), names.c_ptr());
|
|
r = m().mk_and(arg, lbl_lit);
|
|
if (proofs_enabled()) {
|
|
expr_ref aux(m_manager);
|
|
aux = m().mk_label(true, names.size(), names.c_ptr(), arg);
|
|
pr = m().mk_transitivity(mk_proof(fr.m_pol, 1, &arg_pr, t, to_app(aux)),
|
|
m().mk_iff_oeq(m().mk_rewrite(aux, r)));
|
|
}
|
|
}
|
|
else {
|
|
r = arg;
|
|
if (proofs_enabled()) {
|
|
proof * p1 = m().mk_iff_oeq(m().mk_rewrite(t, t->get_arg(0)));
|
|
pr = m().mk_transitivity(p1, arg_pr);
|
|
}
|
|
}
|
|
|
|
m_result_stack.pop_back();
|
|
m_result_stack.push_back(r);
|
|
if (proofs_enabled()) {
|
|
m_result_pr_stack.pop_back();
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool process_app(app * t, frame & fr) {
|
|
SASSERT(m().is_bool(t));
|
|
if (t->get_family_id() == m().get_basic_family_id()) {
|
|
switch (static_cast<basic_op_kind>(t->get_decl_kind())) {
|
|
case OP_AND: case OP_OR:
|
|
return process_and_or(t, fr);
|
|
case OP_NOT:
|
|
return process_not(t, fr);
|
|
case OP_IMPLIES:
|
|
return process_implies(t, fr);
|
|
case OP_ITE:
|
|
return process_ite(t, fr);
|
|
case OP_IFF:
|
|
case OP_XOR:
|
|
return process_iff_xor(t, fr);
|
|
case OP_EQ:
|
|
return process_eq(t, fr);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m().is_label(t)) {
|
|
return process_label(t, fr);
|
|
}
|
|
|
|
return process_default(t, fr);
|
|
}
|
|
|
|
bool process_var(var * v, frame & fr) {
|
|
skip(v, fr.m_pol);
|
|
return true;
|
|
}
|
|
|
|
bool process_quantifier(quantifier * q, frame & fr) {
|
|
expr_ref r(m());
|
|
proof_ref pr(m());
|
|
if (fr.m_i == 0) {
|
|
fr.m_i = 1;
|
|
if (q->is_forall() == fr.m_pol || !m_skolemize) {
|
|
if (!visit(q->get_expr(), fr.m_pol, true))
|
|
return false;
|
|
}
|
|
else {
|
|
m_skolemizer(q, r, pr);
|
|
if (!visit(r, !q->is_forall(), fr.m_in_q))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (q->is_forall() == fr.m_pol || !m_skolemize) {
|
|
expr * new_expr = m_result_stack.back();
|
|
proof * new_expr_pr = proofs_enabled() ? m_result_pr_stack.back() : 0;
|
|
|
|
ptr_buffer<expr> new_patterns;
|
|
|
|
if (q->is_forall() == fr.m_pol) {
|
|
// collect non sk_hack patterns
|
|
unsigned num_patterns = q->get_num_patterns();
|
|
for (unsigned i = 0; i < num_patterns; i++) {
|
|
expr * pat = q->get_pattern(i);
|
|
if (!m_skolemizer.is_sk_hack(pat))
|
|
new_patterns.push_back(pat);
|
|
}
|
|
}
|
|
else {
|
|
// New quantifier has existential force.
|
|
// So, ignore patterns
|
|
}
|
|
|
|
quantifier * new_q = 0;
|
|
proof * new_q_pr = 0;
|
|
if (fr.m_pol) {
|
|
new_q = m().update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), new_expr);
|
|
if (proofs_enabled())
|
|
new_q_pr = m().mk_nnf_pos(q, new_q, 1, &new_expr_pr);
|
|
}
|
|
else {
|
|
new_q = m().update_quantifier(q, !q->is_forall(), new_patterns.size(), new_patterns.c_ptr(), new_expr);
|
|
if (proofs_enabled())
|
|
new_q_pr = m().mk_nnf_neg(q, new_q, 1, &new_expr_pr);
|
|
}
|
|
|
|
m_result_stack.pop_back();
|
|
m_result_stack.push_back(new_q);
|
|
if (proofs_enabled()) {
|
|
m_result_pr_stack.pop_back();
|
|
m_result_pr_stack.push_back(new_q_pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
}
|
|
else {
|
|
// Quantifier was skolemized.
|
|
// The result is already on the stack.
|
|
// However, the proof must be updated
|
|
if (proofs_enabled()) {
|
|
m_skolemizer(q, r, pr); // retrieve the proof
|
|
pr = m().mk_transitivity(pr, m_result_pr_stack.back());
|
|
m_result_pr_stack.pop_back();
|
|
m_result_pr_stack.push_back(pr);
|
|
SASSERT(m_result_stack.size() == m_result_pr_stack.size());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void recover_result(expr * t, expr_ref & result, proof_ref & result_pr) {
|
|
// recover result from the top of the stack.
|
|
result = m_result_stack.back();
|
|
m_result_stack.pop_back();
|
|
SASSERT(m_result_stack.empty());
|
|
if (proofs_enabled()) {
|
|
result_pr = m_result_pr_stack.back();
|
|
m_result_pr_stack.pop_back();
|
|
if (result_pr.get() == 0)
|
|
result_pr = m().mk_reflexivity(t);
|
|
SASSERT(m_result_pr_stack.empty());
|
|
}
|
|
}
|
|
|
|
void process(expr * t, expr_ref & result, proof_ref & result_pr) {
|
|
TRACE("nnf", tout << "processing:\n" << mk_ismt2_pp(t, m()) << "\n";);
|
|
SASSERT(m().is_bool(t));
|
|
|
|
if (visit(t, true /* positive polarity */, false /* not nested in quantifier */)) {
|
|
recover_result(t, result, result_pr);
|
|
return;
|
|
}
|
|
SASSERT(!m_frame_stack.empty());
|
|
while (!m_frame_stack.empty()) {
|
|
checkpoint();
|
|
frame & fr = m_frame_stack.back();
|
|
expr * t = fr.m_curr;
|
|
|
|
if (fr.m_i == 0 && t->get_ref_count() > 1 && process_cached(t, fr.m_pol, fr.m_in_q))
|
|
continue;
|
|
|
|
bool status;
|
|
|
|
switch (t->get_kind()) {
|
|
case AST_APP:
|
|
status = process_app(to_app(t), fr);
|
|
break;
|
|
case AST_QUANTIFIER:
|
|
status = process_quantifier(to_quantifier(t), fr);
|
|
break;
|
|
case AST_VAR:
|
|
status = process_var(to_var(t), fr);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
status = true;
|
|
break;
|
|
}
|
|
|
|
if (status) {
|
|
if (fr.m_cache_result)
|
|
cache_result(fr.m_curr, fr.m_pol, fr.m_in_q, m_result_stack.back(), proofs_enabled() ? m_result_pr_stack.back() : 0);
|
|
m_frame_stack.pop_back();
|
|
}
|
|
}
|
|
recover_result(t, result, result_pr);
|
|
}
|
|
|
|
void operator()(expr * n, expr_ref_vector & new_defs, proof_ref_vector & new_def_proofs, expr_ref & r, proof_ref & pr) {
|
|
reset();
|
|
process(n, r, pr);
|
|
unsigned old_sz1 = new_defs.size();
|
|
unsigned old_sz2 = new_def_proofs.size();
|
|
|
|
for (unsigned i = 0; i < m_todo_defs.size(); i++) {
|
|
expr_ref dr(m());
|
|
proof_ref dpr(m());
|
|
process(m_todo_defs.get(i), dr, dpr);
|
|
new_defs.push_back(dr);
|
|
if (proofs_enabled()) {
|
|
proof * new_pr = m().mk_modus_ponens(m_todo_proofs.get(i), dpr);
|
|
new_def_proofs.push_back(new_pr);
|
|
}
|
|
}
|
|
std::reverse(new_defs.c_ptr() + old_sz1, new_defs.c_ptr() + new_defs.size());
|
|
std::reverse(new_def_proofs.c_ptr() + old_sz2, new_def_proofs.c_ptr() + new_def_proofs.size());
|
|
}
|
|
};
|
|
|
|
|
|
nnf::nnf(ast_manager & m, defined_names & n, params_ref const & p) {
|
|
TRACE("nnf", tout << "nnf constructor: " << p << "\n";);
|
|
m_imp = alloc(imp, m, n, p);
|
|
}
|
|
|
|
nnf::nnf(ast_manager & m, defined_names & n, nnf_params & np) {
|
|
params_ref p;
|
|
if (np.m_nnf_mode == NNF_FULL)
|
|
p.set_sym("mode", symbol("full"));
|
|
else if (np.m_nnf_mode == NNF_QUANT)
|
|
p.set_sym("mode", symbol("quantifiers"));
|
|
|
|
if (np.m_nnf_ignore_labels)
|
|
p.set_bool("ignore_labels", true);
|
|
|
|
if (np.m_nnf_sk_hack)
|
|
p.set_bool("sk_hack", true);
|
|
m_imp = alloc(imp, m, n, p);
|
|
}
|
|
|
|
nnf::~nnf() {
|
|
dealloc(m_imp);
|
|
}
|
|
|
|
void nnf::operator()(expr * n, expr_ref_vector & new_defs, proof_ref_vector & new_def_proofs, expr_ref & r, proof_ref & p) {
|
|
m_imp->operator()(n, new_defs, new_def_proofs, r, p);
|
|
TRACE("nnf_result", tout << mk_ismt2_pp(n, m_imp->m()) << "\nNNF result:\n" << mk_ismt2_pp(r, m_imp->m()) << "\n";);
|
|
}
|
|
|
|
void nnf::updt_params(params_ref const & p) {
|
|
m_imp->updt_params(p);
|
|
}
|
|
|
|
void nnf::get_param_descrs(param_descrs & r) {
|
|
imp::get_param_descrs(r);
|
|
}
|
|
|
|
void nnf::set_cancel(bool f) {
|
|
m_imp->set_cancel(f);
|
|
}
|
|
|
|
void nnf::reset() {
|
|
m_imp->reset();
|
|
}
|
|
|
|
void nnf::reset_cache() {
|
|
m_imp->reset_cache();
|
|
}
|
|
|