mirror of
https://github.com/Z3Prover/z3
synced 2025-04-08 18:31:49 +00:00
950 lines
32 KiB
C++
950 lines
32 KiB
C++
/*++
|
|
Copyright (c) 2007 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
simplifier.cpp
|
|
|
|
Abstract:
|
|
|
|
Expression simplifier.
|
|
|
|
Author:
|
|
|
|
Leonardo (leonardo) 2008-01-03
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
#include"simplifier.h"
|
|
#include"var_subst.h"
|
|
#include"ast_ll_pp.h"
|
|
#include"ast_pp.h"
|
|
#include"well_sorted.h"
|
|
#include"ast_smt_pp.h"
|
|
|
|
simplifier::simplifier(ast_manager & m):
|
|
base_simplifier(m),
|
|
m_proofs(m),
|
|
m_subst_proofs(m),
|
|
m_need_reset(false),
|
|
m_use_oeq(false),
|
|
m_visited_quantifier(false),
|
|
m_ac_support(true) {
|
|
}
|
|
|
|
void simplifier::register_plugin(plugin * p) {
|
|
m_plugins.register_plugin(p);
|
|
}
|
|
|
|
simplifier::~simplifier() {
|
|
flush_cache();
|
|
}
|
|
|
|
void simplifier::enable_ac_support(bool flag) {
|
|
m_ac_support = flag;
|
|
ptr_vector<plugin>::const_iterator it = m_plugins.begin();
|
|
ptr_vector<plugin>::const_iterator end = m_plugins.end();
|
|
for (; it != end; ++it) {
|
|
if (*it != 0)
|
|
(*it)->enable_ac_support(flag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief External interface for the simplifier.
|
|
A client will invoke operator()(s, r, p) to simplify s.
|
|
The result is stored in r.
|
|
When proof generation is enabled, a proof for the equivalence (or equisatisfiability)
|
|
of s and r is stored in p.
|
|
When proof generation is disabled, this method stores the "undefined proof" object in p.
|
|
*/
|
|
void simplifier::operator()(expr * s, expr_ref & r, proof_ref & p) {
|
|
m_need_reset = true;
|
|
expr * old_s;
|
|
expr * result;
|
|
proof * result_proof;
|
|
switch (m_manager.proof_mode()) {
|
|
case PGM_DISABLED: // proof generation is disabled.
|
|
reduce_core(s);
|
|
// after executing reduce_core, the result of the simplification is in the cache
|
|
get_cached(s, result, result_proof);
|
|
r = result;
|
|
p = m_manager.mk_undef_proof();
|
|
break;
|
|
case PGM_COARSE: // coarse proofs... in this case, we do not produce a step by step (fine grain) proof to show the equivalence (or equisatisfiability) of s an r.
|
|
m_subst_proofs.reset(); // m_subst_proofs is an auxiliary vector that is used to justify substitutions. See comment on method get_subst.
|
|
reduce_core(s);
|
|
get_cached(s, result, result_proof);
|
|
r = result;
|
|
if (result == s)
|
|
p = m_manager.mk_reflexivity(s);
|
|
else {
|
|
remove_duplicates(m_subst_proofs);
|
|
p = m_manager.mk_rewrite_star(s, result, m_subst_proofs.size(), m_subst_proofs.c_ptr());
|
|
}
|
|
break;
|
|
case PGM_FINE: // fine grain proofs... in this mode, every proof step (or most of them) is described.
|
|
m_proofs.reset();
|
|
old_s = 0;
|
|
// keep simplyfing until no further simplifications are possible.
|
|
while (s != old_s) {
|
|
TRACE("simplifier", tout << "simplification pass... " << s->get_id() << "\n";);
|
|
TRACE("simplifier_loop", tout << mk_ll_pp(s, m_manager) << "\n";);
|
|
reduce_core(s);
|
|
get_cached(s, result, result_proof);
|
|
if (result_proof != 0)
|
|
m_proofs.push_back(result_proof);
|
|
old_s = s;
|
|
s = result;
|
|
}
|
|
SASSERT(s != 0);
|
|
r = s;
|
|
p = m_proofs.empty() ? m_manager.mk_reflexivity(s) : m_manager.mk_transitivity(m_proofs.size(), m_proofs.c_ptr());
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
void simplifier::flush_cache() {
|
|
m_cache.flush();
|
|
ptr_vector<plugin>::const_iterator it = m_plugins.begin();
|
|
ptr_vector<plugin>::const_iterator end = m_plugins.end();
|
|
for (; it != end; ++it) {
|
|
if (*it != 0) {
|
|
(*it)->flush_caches();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool simplifier::get_subst(expr * n, expr_ref & r, proof_ref & p) {
|
|
return false;
|
|
}
|
|
|
|
void simplifier::reduce_core(expr * n) {
|
|
if (!is_cached(n)) {
|
|
// We do not assume m_todo is empty... So, we store the current size of the todo-stack.
|
|
unsigned sz = m_todo.size();
|
|
m_todo.push_back(n);
|
|
while (m_todo.size() != sz) {
|
|
expr * n = m_todo.back();
|
|
if (is_cached(n))
|
|
m_todo.pop_back();
|
|
else if (visit_children(n)) {
|
|
// if all children were already simplified, then remove n from the todo stack and apply a
|
|
// simplification step to it.
|
|
m_todo.pop_back();
|
|
reduce1(n);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Return true if all children of n have been already simplified.
|
|
*/
|
|
bool simplifier::visit_children(expr * n) {
|
|
switch(n->get_kind()) {
|
|
case AST_VAR:
|
|
return true;
|
|
case AST_APP:
|
|
// The simplifier has support for flattening AC (associative-commutative) operators.
|
|
// The method ast_manager::mk_app is used to create the flat version of an AC operator.
|
|
// In Z3 1.x, we used multi-ary operators. This creates problems for the superposition engine.
|
|
// So, starting at Z3 2.x, only boolean operators can be multi-ary.
|
|
// Example:
|
|
// (and (and a b) (and c d)) --> (and a b c d)
|
|
// (+ (+ a b) (+ c d)) --> (+ a (+ b (+ c d)))
|
|
// Remark: The flattening is only applied if m_ac_support is true.
|
|
if (m_ac_support && to_app(n)->get_decl()->is_associative() && to_app(n)->get_decl()->is_commutative())
|
|
return visit_ac(to_app(n));
|
|
else {
|
|
bool visited = true;
|
|
unsigned j = to_app(n)->get_num_args();
|
|
while (j > 0) {
|
|
--j;
|
|
visit(to_app(n)->get_arg(j), visited);
|
|
}
|
|
return visited;
|
|
}
|
|
case AST_QUANTIFIER:
|
|
return visit_quantifier(to_quantifier(n));
|
|
default:
|
|
UNREACHABLE();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Visit the children of n assuming it is an AC (associative-commutative) operator.
|
|
|
|
For example, if n is of the form (+ (+ a b) (+ c d)), this method
|
|
will return true if the nodes a, b, c and d have been already simplified.
|
|
The nodes (+ a b) and (+ c d) are not really checked.
|
|
*/
|
|
bool simplifier::visit_ac(app * n) {
|
|
bool visited = true;
|
|
func_decl * decl = n->get_decl();
|
|
SASSERT(m_ac_support);
|
|
SASSERT(decl->is_associative());
|
|
SASSERT(decl->is_commutative());
|
|
m_ac_marked.reset();
|
|
ptr_buffer<app> todo;
|
|
todo.push_back(n);
|
|
while (!todo.empty()) {
|
|
app * n = todo.back();
|
|
todo.pop_back();
|
|
if (m_ac_mark.is_marked(n))
|
|
continue;
|
|
m_ac_mark.mark(n, true);
|
|
m_ac_marked.push_back(n);
|
|
SASSERT(n->get_decl() == decl);
|
|
unsigned i = n->get_num_args();
|
|
while (i > 0) {
|
|
--i;
|
|
expr * arg = n->get_arg(i);
|
|
if (is_app_of(arg, decl))
|
|
todo.push_back(to_app(arg));
|
|
else
|
|
visit(arg, visited);
|
|
}
|
|
}
|
|
ptr_vector<expr>::const_iterator it = m_ac_marked.begin();
|
|
ptr_vector<expr>::const_iterator end = m_ac_marked.end();
|
|
for (; it != end; ++it)
|
|
m_ac_mark.mark(*it, false);
|
|
return visited;
|
|
}
|
|
|
|
bool simplifier::visit_quantifier(quantifier * n) {
|
|
m_visited_quantifier = true;
|
|
bool visited = true;
|
|
unsigned j = to_quantifier(n)->get_num_patterns();
|
|
while (j > 0) {
|
|
--j;
|
|
visit(to_quantifier(n)->get_pattern(j), visited);
|
|
}
|
|
j = to_quantifier(n)->get_num_no_patterns();
|
|
while (j > 0) {
|
|
--j;
|
|
visit(to_quantifier(n)->get_no_pattern(j), visited);
|
|
}
|
|
visit(to_quantifier(n)->get_expr(), visited);
|
|
return visited;
|
|
}
|
|
|
|
/**
|
|
\brief Simplify n and store the result in the cache.
|
|
*/
|
|
void simplifier::reduce1(expr * n) {
|
|
switch (n->get_kind()) {
|
|
case AST_VAR:
|
|
cache_result(n, n, 0);
|
|
break;
|
|
case AST_APP:
|
|
reduce1_app(to_app(n));
|
|
break;
|
|
case AST_QUANTIFIER:
|
|
reduce1_quantifier(to_quantifier(n));
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Simplify the given application using the cached values,
|
|
associativity flattening, the given substitution, and family/theory
|
|
specific simplifications via plugins.
|
|
*/
|
|
void simplifier::reduce1_app(app * n) {
|
|
expr_ref r(m_manager);
|
|
proof_ref p(m_manager);
|
|
TRACE("reduce", tout << "reducing...\n" << mk_pp(n, m_manager) << "\n";);
|
|
if (get_subst(n, r, p)) {
|
|
TRACE("reduce", tout << "applying substitution...\n";);
|
|
cache_result(n, r, p);
|
|
return;
|
|
}
|
|
|
|
func_decl * decl = n->get_decl();
|
|
if (m_ac_support && decl->is_associative() && decl->is_commutative())
|
|
reduce1_ac_app_core(n);
|
|
else
|
|
reduce1_app_core(n);
|
|
}
|
|
|
|
|
|
void simplifier::reduce1_app_core(app * n) {
|
|
m_args.reset();
|
|
func_decl * decl = n->get_decl();
|
|
proof_ref p1(m_manager);
|
|
// Stores the new arguments of n in m_args.
|
|
// Let n be of the form
|
|
// (decl arg_0 ... arg_{n-1})
|
|
// then
|
|
// m_args contains [arg_0', ..., arg_{n-1}'],
|
|
// where arg_i' is the simplification of arg_i
|
|
// and
|
|
// p1 is a proof for
|
|
// (decl arg_0 ... arg_{n-1}) is equivalente/equisatisfiable to (decl arg_0' ... arg_{n-1}')
|
|
// p1 is built using the congruence proof step and the proofs for arg_0' ... arg_{n-1}'.
|
|
// Of course, p1 is 0 if proofs are not enabled or coarse grain proofs are used.
|
|
bool has_new_args = get_args(n, m_args, p1);
|
|
// The following if implements a simple trick.
|
|
// If none of the arguments have been simplified, and n is not a theory symbol,
|
|
// Then no simplification is possible, and we can cache the result of the simplification of n as n.
|
|
if (has_new_args || decl->get_family_id() != null_family_id) {
|
|
expr_ref r(m_manager);
|
|
TRACE("reduce", tout << "reduce1_app\n"; for(unsigned i = 0; i < m_args.size(); i++) tout << mk_ll_pp(m_args[i], m_manager););
|
|
// the method mk_app invokes get_subst and plugins to simplify
|
|
// (decl arg_0' ... arg_{n-1}')
|
|
mk_app(decl, m_args.size(), m_args.c_ptr(), r);
|
|
if (!m_manager.fine_grain_proofs()) {
|
|
cache_result(n, r, 0);
|
|
}
|
|
else {
|
|
expr * s = m_manager.mk_app(decl, m_args.size(), m_args.c_ptr());
|
|
proof * p;
|
|
if (n == r)
|
|
p = 0;
|
|
else if (r != s)
|
|
// we use a "theory rewrite generic proof" to justify the step
|
|
// s = (decl arg_0' ... arg_{n-1}') --> r
|
|
p = m_manager.mk_transitivity(p1, m_manager.mk_rewrite(s, r));
|
|
else
|
|
p = p1;
|
|
cache_result(n, r, p);
|
|
}
|
|
}
|
|
else {
|
|
cache_result(n, n, 0);
|
|
}
|
|
}
|
|
|
|
bool is_ac_list(app * n, ptr_vector<expr> & args) {
|
|
args.reset();
|
|
func_decl * f = n->get_decl();
|
|
app * curr = n;
|
|
while (true) {
|
|
if (curr->get_num_args() != 2)
|
|
return false;
|
|
expr * arg1 = curr->get_arg(0);
|
|
if (is_app_of(arg1, f))
|
|
return false;
|
|
args.push_back(arg1);
|
|
expr * arg2 = curr->get_arg(1);
|
|
if (!is_app_of(arg2, f)) {
|
|
args.push_back(arg2);
|
|
return true;
|
|
}
|
|
curr = to_app(arg2);
|
|
}
|
|
}
|
|
|
|
bool is_ac_vector(app * n) {
|
|
func_decl * f = n->get_decl();
|
|
unsigned num_args = n->get_num_args();
|
|
for (unsigned i = 0; i < num_args; i++) {
|
|
if (is_app_of(n->get_arg(i), f))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void simplifier::reduce1_ac_app_core(app * n) {
|
|
app_ref n_c(m_manager);
|
|
proof_ref p1(m_manager);
|
|
mk_ac_congruent_term(n, n_c, p1);
|
|
TRACE("ac", tout << "expr:\n" << mk_pp(n, m_manager) << "\ncongruent term:\n" << mk_pp(n_c, m_manager) << "\n";);
|
|
expr_ref r(m_manager);
|
|
func_decl * decl = n->get_decl();
|
|
family_id fid = decl->get_family_id();
|
|
plugin * p = get_plugin(fid);
|
|
if (is_ac_vector(n_c)) {
|
|
if (p != 0 && p->reduce(decl, n_c->get_num_args(), n_c->get_args(), r)) {
|
|
// done...
|
|
}
|
|
else {
|
|
r = n_c;
|
|
}
|
|
}
|
|
else if (is_ac_list(n_c, m_args)) {
|
|
// m_args contains the arguments...
|
|
if (p != 0 && p->reduce(decl, m_args.size(), m_args.c_ptr(), r)) {
|
|
// done...
|
|
}
|
|
else {
|
|
r = m_manager.mk_app(decl, m_args.size(), m_args.c_ptr());
|
|
}
|
|
}
|
|
else {
|
|
m_args.reset();
|
|
m_mults.reset();
|
|
get_ac_args(n_c, m_args, m_mults);
|
|
TRACE("ac", tout << "AC args:\n";
|
|
for (unsigned i = 0; i < m_args.size(); i++) {
|
|
tout << mk_pp(m_args[i], m_manager) << " * " << m_mults[i] << "\n";
|
|
});
|
|
if (p != 0 && p->reduce(decl, m_args.size(), m_mults.c_ptr(), m_args.c_ptr(), r)) {
|
|
// done...
|
|
}
|
|
else {
|
|
ptr_buffer<expr> new_args;
|
|
expand_args(m_args.size(), m_mults.c_ptr(), m_args.c_ptr(), new_args);
|
|
r = m_manager.mk_app(decl, new_args.size(), new_args.c_ptr());
|
|
}
|
|
}
|
|
TRACE("ac", tout << "AC result:\n" << mk_pp(r, m_manager) << "\n";);
|
|
|
|
if (!m_manager.fine_grain_proofs()) {
|
|
cache_result(n, r, 0);
|
|
}
|
|
else {
|
|
proof * p;
|
|
if (n == r.get())
|
|
p = 0;
|
|
else if (r.get() != n_c.get())
|
|
p = m_manager.mk_transitivity(p1, m_manager.mk_rewrite(n_c, r));
|
|
else
|
|
p = p1;
|
|
cache_result(n, r, p);
|
|
}
|
|
}
|
|
|
|
static unsigned g_rewrite_lemma_id = 0;
|
|
|
|
void simplifier::dump_rewrite_lemma(func_decl * decl, unsigned num_args, expr * const * args, expr* result) {
|
|
expr_ref arg(m_manager);
|
|
arg = m_manager.mk_app(decl, num_args, args);
|
|
if (arg.get() != result) {
|
|
char buffer[128];
|
|
#ifdef _WINDOWS
|
|
sprintf_s(buffer, ARRAYSIZE(buffer), "lemma_%d.smt", g_rewrite_lemma_id);
|
|
#else
|
|
sprintf(buffer, "rewrite_lemma_%d.smt", g_rewrite_lemma_id);
|
|
#endif
|
|
ast_smt_pp pp(m_manager);
|
|
pp.set_benchmark_name("rewrite_lemma");
|
|
pp.set_status("unsat");
|
|
expr_ref n(m_manager);
|
|
n = m_manager.mk_not(m_manager.mk_eq(arg.get(), result));
|
|
std::ofstream out(buffer);
|
|
pp.display(out, n);
|
|
out.close();
|
|
++g_rewrite_lemma_id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Return in \c result an expression \c e equivalent to <tt>(f args[0] ... args[num_args - 1])</tt>, and
|
|
store in \c pr a proof for <tt>(= (f args[0] ... args[num_args - 1]) e)</tt>
|
|
|
|
If e is identical to (f args[0] ... args[num_args - 1]), then pr is set to 0.
|
|
*/
|
|
void simplifier::mk_app(func_decl * decl, unsigned num_args, expr * const * args, expr_ref & result) {
|
|
m_need_reset = true;
|
|
if (m_manager.is_eq(decl)) {
|
|
sort * s = m_manager.get_sort(args[0]);
|
|
plugin * p = get_plugin(s->get_family_id());
|
|
if (p != 0 && p->reduce_eq(args[0], args[1], result))
|
|
return;
|
|
}
|
|
else if (m_manager.is_distinct(decl)) {
|
|
sort * s = m_manager.get_sort(args[0]);
|
|
plugin * p = get_plugin(s->get_family_id());
|
|
if (p != 0 && p->reduce_distinct(num_args, args, result))
|
|
return;
|
|
}
|
|
family_id fid = decl->get_family_id();
|
|
plugin * p = get_plugin(fid);
|
|
if (p != 0 && p->reduce(decl, num_args, args, result)) {
|
|
//uncomment this line if you want to trace rewrites as lemmas:
|
|
//dump_rewrite_lemma(decl, num_args, args, result.get());
|
|
return;
|
|
}
|
|
result = m_manager.mk_app(decl, num_args, args);
|
|
}
|
|
|
|
/**
|
|
\brief Create a term congruence to n (f a[0] ... a[num_args-1]) using the
|
|
cached values for the a[i]'s. Store the result in r, and the proof for (= n r) in p.
|
|
If n and r are identical, then set p to 0.
|
|
*/
|
|
void simplifier::mk_congruent_term(app * n, app_ref & r, proof_ref & p) {
|
|
bool has_new_args = false;
|
|
ptr_vector<expr> args;
|
|
ptr_vector<proof> proofs;
|
|
unsigned num = n->get_num_args();
|
|
for (unsigned j = 0; j < num; j++) {
|
|
expr * arg = n->get_arg(j);
|
|
expr * new_arg;
|
|
proof * arg_proof;
|
|
get_cached(arg, new_arg, arg_proof);
|
|
|
|
CTRACE("simplifier_bug", (arg != new_arg) != (arg_proof != 0),
|
|
tout << mk_ll_pp(arg, m_manager) << "\n---->\n" << mk_ll_pp(new_arg, m_manager) << "\n";
|
|
tout << "#" << arg->get_id() << " #" << new_arg->get_id() << "\n";
|
|
tout << arg << " " << new_arg << "\n";);
|
|
|
|
|
|
if (arg != new_arg) {
|
|
has_new_args = true;
|
|
proofs.push_back(arg_proof);
|
|
SASSERT(arg_proof);
|
|
}
|
|
else {
|
|
SASSERT(arg_proof == 0);
|
|
}
|
|
args.push_back(new_arg);
|
|
}
|
|
if (has_new_args) {
|
|
r = m_manager.mk_app(n->get_decl(), args.size(), args.c_ptr());
|
|
if (m_use_oeq)
|
|
p = m_manager.mk_oeq_congruence(n, r, proofs.size(), proofs.c_ptr());
|
|
else
|
|
p = m_manager.mk_congruence(n, r, proofs.size(), proofs.c_ptr());
|
|
}
|
|
else {
|
|
r = n;
|
|
p = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Store the new arguments of \c n in result. Store in p a proof for
|
|
(= n (f result[0] ... result[num_args - 1])), where f is the function symbol of n.
|
|
|
|
If there are no new arguments or fine grain proofs are disabled, then p is set to 0.
|
|
|
|
Return true there are new arguments.
|
|
*/
|
|
bool simplifier::get_args(app * n, ptr_vector<expr> & result, proof_ref & p) {
|
|
bool has_new_args = false;
|
|
unsigned num = n->get_num_args();
|
|
if (m_manager.fine_grain_proofs()) {
|
|
app_ref r(m_manager);
|
|
mk_congruent_term(n, r, p);
|
|
result.append(r->get_num_args(), r->get_args());
|
|
SASSERT(n->get_num_args() == result.size());
|
|
has_new_args = r != n;
|
|
}
|
|
else {
|
|
p = 0;
|
|
for (unsigned j = 0; j < num; j++) {
|
|
expr * arg = n->get_arg(j);
|
|
expr * new_arg;
|
|
proof * arg_proof;
|
|
get_cached(arg, new_arg, arg_proof);
|
|
if (arg != new_arg) {
|
|
has_new_args = true;
|
|
}
|
|
result.push_back(new_arg);
|
|
}
|
|
}
|
|
return has_new_args;
|
|
}
|
|
|
|
/**
|
|
\brief Create a term congruence to n (where n is an expression such as: (f (f a_1 a_2) (f a_3 (f a_4 a_5))) using the
|
|
cached values for the a_i's. Store the result in r, and the proof for (= n r) in p.
|
|
If n and r are identical, then set p to 0.
|
|
*/
|
|
void simplifier::mk_ac_congruent_term(app * n, app_ref & r, proof_ref & p) {
|
|
SASSERT(m_ac_support);
|
|
func_decl * f = n->get_decl();
|
|
|
|
m_ac_cache.reset();
|
|
m_ac_pr_cache.reset();
|
|
|
|
ptr_buffer<app> todo;
|
|
ptr_buffer<expr> new_args;
|
|
ptr_buffer<proof> new_arg_prs;
|
|
todo.push_back(n);
|
|
while (!todo.empty()) {
|
|
app * curr = todo.back();
|
|
if (m_ac_cache.contains(curr)) {
|
|
todo.pop_back();
|
|
continue;
|
|
}
|
|
bool visited = true;
|
|
bool has_new_arg = false;
|
|
new_args.reset();
|
|
new_arg_prs.reset();
|
|
unsigned num_args = curr->get_num_args();
|
|
for (unsigned j = 0; j < num_args; j ++) {
|
|
expr * arg = curr->get_arg(j);
|
|
if (is_app_of(arg, f)) {
|
|
app * new_arg = 0;
|
|
if (m_ac_cache.find(to_app(arg), new_arg)) {
|
|
SASSERT(new_arg != 0);
|
|
new_args.push_back(new_arg);
|
|
if (arg != new_arg)
|
|
has_new_arg = true;
|
|
if (m_manager.fine_grain_proofs()) {
|
|
proof * pr = 0;
|
|
m_ac_pr_cache.find(to_app(arg), pr);
|
|
if (pr != 0)
|
|
new_arg_prs.push_back(pr);
|
|
}
|
|
}
|
|
else {
|
|
visited = false;
|
|
todo.push_back(to_app(arg));
|
|
}
|
|
}
|
|
else {
|
|
expr * new_arg = 0;
|
|
proof * pr;
|
|
get_cached(arg, new_arg, pr);
|
|
new_args.push_back(new_arg);
|
|
if (arg != new_arg)
|
|
has_new_arg = true;
|
|
if (m_manager.fine_grain_proofs() && pr != 0)
|
|
new_arg_prs.push_back(pr);
|
|
}
|
|
}
|
|
if (visited) {
|
|
SASSERT(new_args.size() == curr->get_num_args());
|
|
todo.pop_back();
|
|
if (!has_new_arg) {
|
|
m_ac_cache.insert(curr, curr);
|
|
if (m_manager.fine_grain_proofs())
|
|
m_ac_pr_cache.insert(curr, 0);
|
|
}
|
|
else {
|
|
app * new_curr = m_manager.mk_app(f, new_args.size(), new_args.c_ptr());
|
|
m_ac_cache.insert(curr, new_curr);
|
|
if (m_manager.fine_grain_proofs()) {
|
|
proof * p = m_manager.mk_congruence(curr, new_curr, new_arg_prs.size(), new_arg_prs.c_ptr());
|
|
m_ac_pr_cache.insert(curr, p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SASSERT(m_ac_cache.contains(n));
|
|
app * new_n = 0;
|
|
m_ac_cache.find(n, new_n);
|
|
r = new_n;
|
|
if (m_manager.fine_grain_proofs()) {
|
|
proof * new_pr = 0;
|
|
m_ac_pr_cache.find(n, new_pr);
|
|
p = new_pr;
|
|
}
|
|
}
|
|
|
|
#define White 0
|
|
#define Grey 1
|
|
#define Black 2
|
|
|
|
static int get_color(obj_map<expr, int> & colors, expr * n) {
|
|
obj_map<expr, int>::obj_map_entry * entry = colors.insert_if_not_there2(n, White);
|
|
return entry->get_data().m_value;
|
|
}
|
|
|
|
static bool visit_ac_children(func_decl * f, expr * n, obj_map<expr, int> & colors, ptr_buffer<expr> & todo, ptr_buffer<expr> & result) {
|
|
if (is_app_of(n, f)) {
|
|
unsigned num_args = to_app(n)->get_num_args();
|
|
bool visited = true;
|
|
// Put the arguments in 'result' in reverse order.
|
|
// Reason: preserve the original order of the arguments in the final result.
|
|
// Remark: get_ac_args will traverse 'result' backwards.
|
|
for (unsigned i = 0; i < num_args; i++) {
|
|
expr * arg = to_app(n)->get_arg(i);
|
|
obj_map<expr, int>::obj_map_entry * entry = colors.insert_if_not_there2(arg, White);
|
|
if (entry->get_data().m_value == White) {
|
|
todo.push_back(arg);
|
|
visited = false;
|
|
}
|
|
}
|
|
return visited;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void simplifier::ac_top_sort(app * n, ptr_buffer<expr> & result) {
|
|
ptr_buffer<expr> todo;
|
|
func_decl * f = n->get_decl();
|
|
obj_map<expr, int> & colors = m_colors;
|
|
colors.reset();
|
|
todo.push_back(n);
|
|
while (!todo.empty()) {
|
|
expr * curr = todo.back();
|
|
int color;
|
|
obj_map<expr, int>::obj_map_entry * entry = colors.insert_if_not_there2(curr, White);
|
|
SASSERT(entry);
|
|
color = entry->get_data().m_value;
|
|
switch (color) {
|
|
case White:
|
|
// Remark: entry becomes invalid if an element is inserted into the hashtable.
|
|
// So, I must set Grey before executing visit_ac_children.
|
|
entry->get_data().m_value = Grey;
|
|
SASSERT(get_color(colors, curr) == Grey);
|
|
if (visit_ac_children(f, curr, colors, todo, result)) {
|
|
// If visit_ac_children succeeded, then the hashtable was not modified,
|
|
// and entry is still valid.
|
|
SASSERT(todo.back() == curr);
|
|
entry->get_data().m_value = Black;
|
|
SASSERT(get_color(colors, curr) == Black);
|
|
result.push_back(curr);
|
|
todo.pop_back();
|
|
}
|
|
break;
|
|
case Grey:
|
|
SASSERT(visit_ac_children(f, curr, colors, todo, result));
|
|
SASSERT(entry);
|
|
entry->get_data().m_value = Black;
|
|
SASSERT(get_color(colors, curr) == Black);
|
|
result.push_back(curr);
|
|
SASSERT(todo.back() == curr);
|
|
todo.pop_back();
|
|
break;
|
|
case Black:
|
|
todo.pop_back();
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
|
|
void simplifier::get_ac_args(app * n, ptr_vector<expr> & args, vector<rational> & mults) {
|
|
SASSERT(m_ac_support);
|
|
ptr_buffer<expr> sorted_exprs;
|
|
ac_top_sort(n, sorted_exprs);
|
|
SASSERT(!sorted_exprs.empty());
|
|
SASSERT(sorted_exprs[sorted_exprs.size()-1] == n);
|
|
|
|
TRACE("ac", tout << mk_ll_pp(n, m_manager, true, false) << "#" << n->get_id() << "\nsorted expressions...\n";
|
|
for (unsigned i = 0; i < sorted_exprs.size(); i++) {
|
|
tout << "#" << sorted_exprs[i]->get_id() << " ";
|
|
}
|
|
tout << "\n";);
|
|
|
|
m_ac_mults.reset();
|
|
m_ac_mults.insert(n, rational(1));
|
|
func_decl * decl = n->get_decl();
|
|
unsigned j = sorted_exprs.size();
|
|
while (j > 0) {
|
|
--j;
|
|
expr * curr = sorted_exprs[j];
|
|
rational mult;
|
|
m_ac_mults.find(curr, mult);
|
|
SASSERT(!mult.is_zero());
|
|
if (is_app_of(curr, decl)) {
|
|
unsigned num_args = to_app(curr)->get_num_args();
|
|
for (unsigned i = 0; i < num_args; i++) {
|
|
expr * arg = to_app(curr)->get_arg(i);
|
|
rational zero;
|
|
obj_map<expr, rational>::obj_map_entry * entry = m_ac_mults.insert_if_not_there2(arg, zero);
|
|
entry->get_data().m_value += mult;
|
|
}
|
|
}
|
|
else {
|
|
args.push_back(curr);
|
|
mults.push_back(mult);
|
|
}
|
|
}
|
|
}
|
|
|
|
void simplifier::reduce1_quantifier(quantifier * q) {
|
|
expr * new_body;
|
|
proof * new_body_pr;
|
|
SASSERT(is_well_sorted(m_manager, q));
|
|
get_cached(q->get_expr(), new_body, new_body_pr);
|
|
|
|
quantifier_ref q1(m_manager);
|
|
proof * p1 = 0;
|
|
|
|
if (is_quantifier(new_body) &&
|
|
to_quantifier(new_body)->is_forall() == q->is_forall() &&
|
|
!to_quantifier(q)->has_patterns() &&
|
|
!to_quantifier(new_body)->has_patterns()) {
|
|
|
|
quantifier * nested_q = to_quantifier(new_body);
|
|
|
|
ptr_buffer<sort> sorts;
|
|
buffer<symbol> names;
|
|
sorts.append(q->get_num_decls(), q->get_decl_sorts());
|
|
names.append(q->get_num_decls(), q->get_decl_names());
|
|
sorts.append(nested_q->get_num_decls(), nested_q->get_decl_sorts());
|
|
names.append(nested_q->get_num_decls(), nested_q->get_decl_names());
|
|
|
|
q1 = m_manager.mk_quantifier(q->is_forall(),
|
|
sorts.size(),
|
|
sorts.c_ptr(),
|
|
names.c_ptr(),
|
|
nested_q->get_expr(),
|
|
std::min(q->get_weight(), nested_q->get_weight()),
|
|
q->get_qid(),
|
|
q->get_skid(),
|
|
0, 0, 0, 0);
|
|
SASSERT(is_well_sorted(m_manager, q1));
|
|
|
|
if (m_manager.fine_grain_proofs()) {
|
|
quantifier * q0 = m_manager.update_quantifier(q, new_body);
|
|
proof * p0 = q == q0 ? 0 : m_manager.mk_quant_intro(q, q0, new_body_pr);
|
|
p1 = m_manager.mk_pull_quant(q0, q1);
|
|
p1 = m_manager.mk_transitivity(p0, p1);
|
|
}
|
|
}
|
|
else {
|
|
ptr_buffer<expr> new_patterns;
|
|
ptr_buffer<expr> new_no_patterns;
|
|
expr * new_pattern;
|
|
proof * new_pattern_pr;
|
|
|
|
// Remark: we can ignore the proofs for the patterns.
|
|
unsigned num = q->get_num_patterns();
|
|
for (unsigned i = 0; i < num; i++) {
|
|
get_cached(q->get_pattern(i), new_pattern, new_pattern_pr);
|
|
if (m_manager.is_pattern(new_pattern)) {
|
|
new_patterns.push_back(new_pattern);
|
|
}
|
|
}
|
|
num = q->get_num_no_patterns();
|
|
for (unsigned i = 0; i < num; i++) {
|
|
get_cached(q->get_no_pattern(i), new_pattern, new_pattern_pr);
|
|
new_no_patterns.push_back(new_pattern);
|
|
}
|
|
|
|
remove_duplicates(new_patterns);
|
|
remove_duplicates(new_no_patterns);
|
|
|
|
q1 = m_manager.mk_quantifier(q->is_forall(),
|
|
q->get_num_decls(),
|
|
q->get_decl_sorts(),
|
|
q->get_decl_names(),
|
|
new_body,
|
|
q->get_weight(),
|
|
q->get_qid(),
|
|
q->get_skid(),
|
|
new_patterns.size(),
|
|
new_patterns.c_ptr(),
|
|
new_no_patterns.size(),
|
|
new_no_patterns.c_ptr());
|
|
SASSERT(is_well_sorted(m_manager, q1));
|
|
|
|
TRACE("simplifier", tout << mk_pp(q, m_manager) << "\n" << mk_pp(q1, m_manager) << "\n";);
|
|
if (m_manager.fine_grain_proofs()) {
|
|
if (q != q1 && !new_body_pr) {
|
|
new_body_pr = m_manager.mk_rewrite(q->get_expr(), new_body);
|
|
}
|
|
p1 = q == q1 ? 0 : m_manager.mk_quant_intro(q, q1, new_body_pr);
|
|
}
|
|
}
|
|
|
|
expr_ref r(m_manager);
|
|
elim_unused_vars(m_manager, q1, r);
|
|
|
|
proof * pr = 0;
|
|
if (m_manager.fine_grain_proofs()) {
|
|
proof * p2 = 0;
|
|
if (q1.get() != r.get())
|
|
p2 = m_manager.mk_elim_unused_vars(q1, r);
|
|
pr = m_manager.mk_transitivity(p1, p2);
|
|
}
|
|
|
|
cache_result(q, r, pr);
|
|
}
|
|
|
|
/**
|
|
\see release_plugins
|
|
*/
|
|
void simplifier::borrow_plugins(simplifier const & s) {
|
|
ptr_vector<plugin>::const_iterator it = s.begin_plugins();
|
|
ptr_vector<plugin>::const_iterator end = s.end_plugins();
|
|
for (; it != end; ++it)
|
|
register_plugin(*it);
|
|
}
|
|
|
|
/**
|
|
\brief Make the simplifier behave as a pre-simplifier: No AC, and plugins are marked in pre-simplification mode.
|
|
*/
|
|
void simplifier::enable_presimp() {
|
|
enable_ac_support(false);
|
|
ptr_vector<plugin>::const_iterator it = begin_plugins();
|
|
ptr_vector<plugin>::const_iterator end = end_plugins();
|
|
for (; it != end; ++it)
|
|
(*it)->enable_presimp(true);
|
|
}
|
|
|
|
/**
|
|
\brief This method should be invoked if the plugins of this simplifier were borrowed from a different simplifier.
|
|
*/
|
|
void simplifier::release_plugins() {
|
|
m_plugins.release();
|
|
}
|
|
|
|
void subst_simplifier::set_subst_map(expr_map * s) {
|
|
flush_cache();
|
|
m_subst_map = s;
|
|
}
|
|
|
|
bool subst_simplifier::get_subst(expr * n, expr_ref & r, proof_ref & p) {
|
|
if (m_subst_map && m_subst_map->contains(n)) {
|
|
expr * _r;
|
|
proof * _p = 0;
|
|
m_subst_map->get(n, _r, _p);
|
|
r = _r;
|
|
p = _p;
|
|
if (m_manager.coarse_grain_proofs())
|
|
m_subst_proofs.push_back(p);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void push_core(ast_manager & m, expr * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) {
|
|
SASSERT(pr == 0 || m.is_undef_proof(pr) || e == m.get_fact(pr));
|
|
TRACE("preprocessor",
|
|
tout << mk_pp(e, m) << "\n";
|
|
if (pr) tout << mk_ll_pp(pr, m) << "\n\n";);
|
|
if (m.is_true(e))
|
|
return;
|
|
result.push_back(e);
|
|
if (m.proofs_enabled())
|
|
result_prs.push_back(pr);
|
|
}
|
|
|
|
static void push_and(ast_manager & m, app * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) {
|
|
unsigned num = e->get_num_args();
|
|
TRACE("push_and", tout << mk_pp(e, m) << "\n";);
|
|
for (unsigned i = 0; i < num; i++)
|
|
push_assertion(m, e->get_arg(i), m.mk_and_elim(pr, i), result, result_prs);
|
|
}
|
|
|
|
static void push_not_or(ast_manager & m, app * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) {
|
|
unsigned num = e->get_num_args();
|
|
TRACE("push_not_or", tout << mk_pp(e, m) << "\n";);
|
|
for (unsigned i = 0; i < num; i++) {
|
|
expr * child = e->get_arg(i);
|
|
if (m.is_not(child)) {
|
|
expr * not_child = to_app(child)->get_arg(0);
|
|
push_assertion(m, not_child, m.mk_not_or_elim(pr, i), result, result_prs);
|
|
}
|
|
else {
|
|
expr_ref not_child(m);
|
|
not_child = m.mk_not(child);
|
|
push_assertion(m, not_child, m.mk_not_or_elim(pr, i), result, result_prs);
|
|
}
|
|
}
|
|
}
|
|
|
|
void push_assertion(ast_manager & m, expr * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) {
|
|
CTRACE("push_assertion", !(pr == 0 || m.is_undef_proof(pr) || m.get_fact(pr) == e),
|
|
tout << mk_pp(e, m) << "\n" << mk_pp(m.get_fact(pr), m) << "\n";);
|
|
SASSERT(pr == 0 || m.is_undef_proof(pr) || m.get_fact(pr) == e);
|
|
if (m.is_and(e))
|
|
push_and(m, to_app(e), pr, result, result_prs);
|
|
else if (m.is_not(e) && m.is_or(to_app(e)->get_arg(0)))
|
|
push_not_or(m, to_app(to_app(e)->get_arg(0)), pr, result, result_prs);
|
|
else
|
|
push_core(m, e, pr, result, result_prs);
|
|
}
|
|
|