mirror of
https://github.com/Z3Prover/z3
synced 2026-06-19 23:26:30 +00:00
Merge branch 'master' into c3
This commit is contained in:
commit
043c6c0ad1
259 changed files with 18907 additions and 3725 deletions
|
|
@ -36,7 +36,8 @@ array_decl_plugin::array_decl_plugin():
|
|||
m_set_complement_sym("complement"),
|
||||
m_set_subset_sym("subset"),
|
||||
m_array_ext_sym("array-ext"),
|
||||
m_as_array_sym("as-array") {
|
||||
m_as_array_sym("as-array"),
|
||||
m_choice_sym("choice") {
|
||||
}
|
||||
|
||||
#define ARRAY_SORT_STR "Array"
|
||||
|
|
@ -433,6 +434,20 @@ func_decl * array_decl_plugin::mk_as_array(func_decl * f) {
|
|||
return m_manager->mk_const_decl(m_as_array_sym, s, info);
|
||||
}
|
||||
|
||||
func_decl* array_decl_plugin::mk_choice(unsigned arity, sort* const* domain) {
|
||||
if (arity != 1) {
|
||||
m_manager->raise_exception("choice takes one argument");
|
||||
return nullptr;
|
||||
}
|
||||
sort* s = domain[0];
|
||||
if (!is_array_sort(s) || get_array_arity(s) != 1 || !m_manager->is_bool(get_array_range(s))) {
|
||||
m_manager->raise_exception("choice expects an argument with sort (Array T Bool)");
|
||||
return nullptr;
|
||||
}
|
||||
return m_manager->mk_func_decl(m_choice_sym, arity, domain, get_array_domain(s, 0),
|
||||
func_decl_info(m_family_id, OP_CHOICE));
|
||||
}
|
||||
|
||||
|
||||
func_decl * array_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters,
|
||||
unsigned arity, sort * const * domain, sort * range) {
|
||||
|
|
@ -501,6 +516,8 @@ func_decl * array_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters
|
|||
func_decl * f = to_func_decl(parameters[0].get_ast());
|
||||
return mk_as_array(f);
|
||||
}
|
||||
case OP_CHOICE:
|
||||
return mk_choice(arity, domain);
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
@ -529,6 +546,7 @@ void array_decl_plugin::get_op_names(svector<builtin_name>& op_names, symbol con
|
|||
op_names.push_back(builtin_name("complement",OP_SET_COMPLEMENT));
|
||||
op_names.push_back(builtin_name("subset",OP_SET_SUBSET));
|
||||
op_names.push_back(builtin_name("as-array", OP_AS_ARRAY));
|
||||
op_names.push_back(builtin_name("choice", OP_CHOICE));
|
||||
op_names.push_back(builtin_name("array-ext", OP_ARRAY_EXT));
|
||||
|
||||
#if 0
|
||||
|
|
@ -655,4 +673,3 @@ func_decl* array_util::mk_array_ext(sort *domain, unsigned i) {
|
|||
parameter p(i);
|
||||
return m_manager.mk_func_decl(m_fid, OP_ARRAY_EXT, 1, &p, 2, domains);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ enum array_op_kind {
|
|||
OP_SET_COMPLEMENT,
|
||||
OP_SET_SUBSET,
|
||||
OP_AS_ARRAY, // used for model construction
|
||||
OP_CHOICE,
|
||||
LAST_ARRAY_OP
|
||||
};
|
||||
|
||||
|
|
@ -79,6 +80,7 @@ class array_decl_plugin : public decl_plugin {
|
|||
symbol m_set_subset_sym;
|
||||
symbol m_array_ext_sym;
|
||||
symbol m_as_array_sym;
|
||||
symbol m_choice_sym;
|
||||
|
||||
bool check_set_arguments(unsigned arity, sort * const * domain);
|
||||
|
||||
|
|
@ -106,6 +108,8 @@ class array_decl_plugin : public decl_plugin {
|
|||
|
||||
func_decl * mk_as_array(func_decl * f);
|
||||
|
||||
func_decl * mk_choice(unsigned arity, sort* const* domain);
|
||||
|
||||
bool is_array_sort(sort* s) const;
|
||||
public:
|
||||
array_decl_plugin();
|
||||
|
|
@ -164,6 +168,7 @@ public:
|
|||
bool is_difference(expr* n) const { return is_app_of(n, m_fid, OP_SET_DIFFERENCE); }
|
||||
bool is_complement(expr* n) const { return is_app_of(n, m_fid, OP_SET_COMPLEMENT); }
|
||||
bool is_as_array(expr * n) const { return is_app_of(n, m_fid, OP_AS_ARRAY); }
|
||||
bool is_choice(expr* n) const { return is_app_of(n, m_fid, OP_CHOICE); }
|
||||
bool is_as_array(expr * n, func_decl*& f) const { return is_as_array(n) && (f = get_as_array_func_decl(n), true); }
|
||||
bool is_select(func_decl* f) const { return is_decl_of(f, m_fid, OP_SELECT); }
|
||||
bool is_store(func_decl* f) const { return is_decl_of(f, m_fid, OP_STORE); }
|
||||
|
|
@ -172,6 +177,7 @@ public:
|
|||
bool is_union(func_decl* f) const { return is_decl_of(f, m_fid, OP_SET_UNION); }
|
||||
bool is_intersect(func_decl* f) const { return is_decl_of(f, m_fid, OP_SET_INTERSECT); }
|
||||
bool is_as_array(func_decl* f) const { return is_decl_of(f, m_fid, OP_AS_ARRAY); }
|
||||
bool is_choice(func_decl* f) const { return is_decl_of(f, m_fid, OP_CHOICE); }
|
||||
bool is_default(func_decl* f) const { return is_decl_of(f, m_fid, OP_ARRAY_DEFAULT); }
|
||||
bool is_default(expr* n) const { return is_app_of(n, m_fid, OP_ARRAY_DEFAULT); }
|
||||
bool is_subset(expr const* n) const { return is_app_of(n, m_fid, OP_SET_SUBSET); }
|
||||
|
|
@ -308,6 +314,10 @@ public:
|
|||
return m_manager.mk_app(m_fid, OP_AS_ARRAY, 1, ¶m, 0, nullptr, nullptr);
|
||||
}
|
||||
|
||||
app* mk_choice(expr* p) const {
|
||||
return m_manager.mk_app(m_fid, OP_CHOICE, p);
|
||||
}
|
||||
|
||||
sort* get_array_range_rec(sort* s) {
|
||||
while (is_array(s)) {
|
||||
s = get_array_range(s);
|
||||
|
|
@ -317,5 +327,3 @@ public:
|
|||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -242,21 +242,14 @@ func_decl_info::func_decl_info(family_id family_id, decl_kind k, unsigned num_pa
|
|||
m_injective(false),
|
||||
m_idempotent(false),
|
||||
m_skolem(false),
|
||||
m_lambda(false),
|
||||
m_polymorphic(false) {
|
||||
}
|
||||
|
||||
bool func_decl_info::operator==(func_decl_info const & info) const {
|
||||
return decl_info::operator==(info) &&
|
||||
m_left_assoc == info.m_left_assoc &&
|
||||
m_right_assoc == info.m_right_assoc &&
|
||||
m_flat_associative == info.m_flat_associative &&
|
||||
m_commutative == info.m_commutative &&
|
||||
m_chainable == info.m_chainable &&
|
||||
m_pairwise == info.m_pairwise &&
|
||||
m_injective == info.m_injective &&
|
||||
m_skolem == info.m_skolem &&
|
||||
m_lambda == info.m_lambda;
|
||||
return decl_info::operator==(info) && m_left_assoc == info.m_left_assoc && m_right_assoc == info.m_right_assoc &&
|
||||
m_flat_associative == info.m_flat_associative && m_commutative == info.m_commutative &&
|
||||
m_chainable == info.m_chainable && m_pairwise == info.m_pairwise && m_injective == info.m_injective &&
|
||||
m_skolem == info.m_skolem;
|
||||
}
|
||||
|
||||
std::ostream & operator<<(std::ostream & out, func_decl_info const & info) {
|
||||
|
|
@ -270,7 +263,6 @@ std::ostream & operator<<(std::ostream & out, func_decl_info const & info) {
|
|||
if (info.is_injective()) out << " :injective ";
|
||||
if (info.is_idempotent()) out << " :idempotent ";
|
||||
if (info.is_skolem()) out << " :skolem ";
|
||||
if (info.is_lambda()) out << " :lambda ";
|
||||
if (info.is_polymorphic()) out << " :polymorphic ";
|
||||
return out;
|
||||
}
|
||||
|
|
@ -1625,19 +1617,6 @@ bool ast_manager::are_distinct(expr* a, expr* b) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void ast_manager::add_lambda_def(func_decl* f, quantifier* q) {
|
||||
TRACE(model, tout << "add lambda def " << mk_pp(q, *this) << "\n");
|
||||
m_lambda_defs.insert(f, q);
|
||||
f->get_info()->set_lambda(true);
|
||||
inc_ref(q);
|
||||
}
|
||||
|
||||
quantifier* ast_manager::is_lambda_def(func_decl* f) {
|
||||
if (f->get_info() && f->get_info()->is_lambda())
|
||||
return m_lambda_defs[f];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void ast_manager::register_plugin(family_id id, decl_plugin * plugin) {
|
||||
SASSERT(m_plugins.get(id, 0) == 0);
|
||||
|
|
@ -1670,7 +1649,7 @@ bool ast_manager::slow_not_contains(ast const * n) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
#if 0
|
||||
static unsigned s_count = 0;
|
||||
|
||||
static void track_id(ast_manager& m, ast* n, unsigned id) {
|
||||
|
|
@ -1832,10 +1811,6 @@ void ast_manager::delete_node(ast * n) {
|
|||
m_poly_roots.erase(f);
|
||||
if (f->m_info != nullptr) {
|
||||
func_decl_info * info = f->get_info();
|
||||
if (info->is_lambda()) {
|
||||
push_dec_ref(m_lambda_defs[f]);
|
||||
m_lambda_defs.remove(f);
|
||||
}
|
||||
info->del_eh(*this);
|
||||
dealloc(info);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -404,7 +404,6 @@ struct func_decl_info : public decl_info {
|
|||
bool m_injective:1;
|
||||
bool m_idempotent:1;
|
||||
bool m_skolem:1;
|
||||
bool m_lambda:1;
|
||||
bool m_polymorphic:1;
|
||||
|
||||
func_decl_info(family_id family_id = null_family_id, decl_kind k = null_decl_kind, unsigned num_parameters = 0, parameter const * parameters = nullptr);
|
||||
|
|
@ -419,7 +418,6 @@ struct func_decl_info : public decl_info {
|
|||
bool is_injective() const { return m_injective; }
|
||||
bool is_idempotent() const { return m_idempotent; }
|
||||
bool is_skolem() const { return m_skolem; }
|
||||
bool is_lambda() const { return m_lambda; }
|
||||
bool is_polymorphic() const { return m_polymorphic; }
|
||||
|
||||
void set_associative(bool flag = true) { m_left_assoc = flag; m_right_assoc = flag; }
|
||||
|
|
@ -432,7 +430,6 @@ struct func_decl_info : public decl_info {
|
|||
void set_injective(bool flag = true) { m_injective = flag; }
|
||||
void set_idempotent(bool flag = true) { m_idempotent = flag; }
|
||||
void set_skolem(bool flag = true) { m_skolem = flag; }
|
||||
void set_lambda(bool flag = true) { m_lambda = flag; }
|
||||
void set_polymorphic(bool flag = true) { m_polymorphic = flag; }
|
||||
|
||||
bool operator==(func_decl_info const & info) const;
|
||||
|
|
@ -661,7 +658,6 @@ public:
|
|||
bool is_pairwise() const { return get_info() != nullptr && get_info()->is_pairwise(); }
|
||||
bool is_injective() const { return get_info() != nullptr && get_info()->is_injective(); }
|
||||
bool is_skolem() const { return get_info() != nullptr && get_info()->is_skolem(); }
|
||||
bool is_lambda() const { return get_info() != nullptr && get_info()->is_lambda(); }
|
||||
bool is_idempotent() const { return get_info() != nullptr && get_info()->is_idempotent(); }
|
||||
bool is_polymorphic() const { return get_info() != nullptr && get_info()->is_polymorphic(); }
|
||||
unsigned get_arity() const { return m_arity; }
|
||||
|
|
@ -857,7 +853,8 @@ public:
|
|||
enum quantifier_kind {
|
||||
forall_k,
|
||||
exists_k,
|
||||
lambda_k
|
||||
lambda_k,
|
||||
choice_k
|
||||
};
|
||||
|
||||
class quantifier : public expr {
|
||||
|
|
@ -1512,7 +1509,6 @@ protected:
|
|||
proof_gen_mode m_proof_mode;
|
||||
bool m_int_real_coercions; // If true, use hack that automatically introduces to_int/to_real when needed.
|
||||
ast_table m_ast_table;
|
||||
obj_map<func_decl, quantifier*> m_lambda_defs;
|
||||
id_gen m_expr_id_gen;
|
||||
id_gen m_decl_id_gen;
|
||||
sort * m_bool_sort;
|
||||
|
|
@ -1642,15 +1638,7 @@ public:
|
|||
bool are_distinct(expr * a, expr * b) const;
|
||||
|
||||
bool contains(ast * a) const { return m_ast_table.contains(a); }
|
||||
|
||||
bool is_lambda_def(quantifier* q) const { return q->get_qid() == m_lambda_def; }
|
||||
void add_lambda_def(func_decl* f, quantifier* q);
|
||||
quantifier* is_lambda_def(func_decl* f);
|
||||
quantifier* is_lambda_def(app* e) { return is_lambda_def(e->get_decl()); }
|
||||
obj_map<func_decl, quantifier*> const& lambda_defs() const { return m_lambda_defs; }
|
||||
|
||||
symbol const& lambda_def_qid() const { return m_lambda_def; }
|
||||
|
||||
|
||||
unsigned get_num_asts() const { return m_ast_table.size(); }
|
||||
|
||||
void debug_ref_count() { m_debug_ref_count = true; }
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ Revision History:
|
|||
--*/
|
||||
#include "ast/ast.h"
|
||||
|
||||
#define check_symbol(S1,S2) if (S1 != S2) return lt(S1,S2)
|
||||
#define check_value(V1,V2) if (V1 != V2) return V1 < V2
|
||||
#define check_bool(B1,B2) if (B1 != B2) return !B1 && B2
|
||||
#define check_ptr(P1,P2) if (!P1 && P2) return true; if (P1 && !P2) return false
|
||||
#define check_ast(T1,T2) if (T1 != T2) { n1 = T1; n2 = T2; goto start; }
|
||||
#define check_zstring(S1, S2) if (S1 != S2) return S1 < S2
|
||||
#define check_symbol(S1,S2) if ((S1) != (S2)) return lt((S1),(S2))
|
||||
#define check_value(V1,V2) if ((V1) != (V2)) return (V1) < (V2)
|
||||
#define check_bool(B1,B2) if ((B1) != (B2)) return !(B1) && (B2)
|
||||
#define check_ptr(P1,P2) if (!(P1) && (P2)) return true; if ((P1) && !(P2)) return false
|
||||
#define check_ast(T1,T2) if ((T1) != (T2)) { n1 = (T1); n2 = (T2); goto start; }
|
||||
#define check_zstring(S1, S2) if ((S1) != (S2)) return (S1) < (S2)
|
||||
|
||||
#define check_parameter(p1, p2) { \
|
||||
check_value(p1.get_kind(), p2.get_kind()); \
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ private:
|
|||
|
||||
void pp_atomic_step(const expr * e) {
|
||||
unsigned id = get_id(e);
|
||||
m_out << "node_" << id << " [shape=box,color=\"yellow\",style=\"filled\",label=\"" << label_of_expr(e) << "\"] ;" << std::endl;
|
||||
m_out << "node_" << id << " [shape=box,color=\"yellow\",style=\"filled\",label=\"" << label_of_expr(e) << "\"] ;" << '\n';
|
||||
}
|
||||
|
||||
void pp_step(const proof * p) {
|
||||
|
|
@ -91,7 +91,7 @@ private:
|
|||
m_first ? (m_first=false,"color=\"red\"") : num_parents==0 ? "color=\"yellow\"": "";
|
||||
m_out << "node_" << id <<
|
||||
" [shape=box,style=\"filled\",label=\"" << label_of_expr(p_res) << "\""
|
||||
<< color << "]" << std::endl;
|
||||
<< color << "]" << '\n';
|
||||
// now print edges to parents (except last one, which is the result)
|
||||
std::string label = p->get_decl()->get_name().str();
|
||||
for (unsigned i = 0 ; i < num_parents; ++i) {
|
||||
|
|
@ -99,7 +99,7 @@ private:
|
|||
// explore parent, also print a link to it
|
||||
push_term(to_app(parent));
|
||||
m_out << "node_" << id << " -> " << "node_" << get_id((expr*)parent)
|
||||
<< "[label=\"" << label << "\"];" << std::endl;;
|
||||
<< "[label=\"" << label << "\"];" << '\n';
|
||||
}
|
||||
} else {
|
||||
pp_atomic_step(p);
|
||||
|
|
@ -120,11 +120,11 @@ private:
|
|||
|
||||
// main printer
|
||||
std::ostream & ast_pp_dot::pp(std::ostream & out) const {
|
||||
out << "digraph proof { " << std::endl;
|
||||
out << "digraph proof { " << '\n';
|
||||
ast_pp_dot_st pp_st(this, out);
|
||||
pp_st.push_term(m_pr);
|
||||
pp_st.pp_loop();
|
||||
out << std::endl << " } " << std::endl << std::flush;
|
||||
out << '\n' << " } " << '\n' << std::flush;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -733,7 +733,8 @@ public:
|
|||
m_AUFLIRA("AUFLIRA"),
|
||||
// It's much easier to read those testcases with that.
|
||||
m_no_lets(no_lets),
|
||||
m_simplify_implies(simplify_implies)
|
||||
m_simplify_implies(simplify_implies),
|
||||
m_top(nullptr)
|
||||
{
|
||||
m_basic_fid = m.get_basic_family_id();
|
||||
m_label_fid = m.mk_family_id("label");
|
||||
|
|
|
|||
|
|
@ -181,20 +181,12 @@ void ast_translation::mk_func_decl(func_decl * f, frame & fr) {
|
|||
new_fi.set_injective(fi->is_injective());
|
||||
new_fi.set_skolem(fi->is_skolem());
|
||||
new_fi.set_idempotent(fi->is_idempotent());
|
||||
new_fi.set_lambda(fi->is_lambda());
|
||||
|
||||
new_f = m_to_manager.mk_func_decl(f->get_name(),
|
||||
f->get_arity(),
|
||||
new_domain,
|
||||
new_range,
|
||||
new_fi);
|
||||
|
||||
if (new_fi.is_lambda()) {
|
||||
quantifier* q = from().is_lambda_def(f);
|
||||
ast_translation tr(from(), to());
|
||||
quantifier* new_q = tr(q);
|
||||
to().add_lambda_def(new_f, new_q);
|
||||
}
|
||||
}
|
||||
TRACE(ast_translation,
|
||||
tout << f->get_name() << " "; if (fi) tout << *fi; tout << "\n";
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ expr * mk_and(ast_manager & m, unsigned num_args, expr * const * args) {
|
|||
}
|
||||
|
||||
app* mk_and(ast_manager & m, unsigned num_args, app * const * args) {
|
||||
return to_app(mk_and(m, num_args, (expr* const*) args));
|
||||
return to_app(mk_and(m, num_args, reinterpret_cast<expr* const*>(args)));
|
||||
}
|
||||
|
||||
expr * mk_or(ast_manager & m, unsigned num_args, expr * const * args) {
|
||||
|
|
|
|||
|
|
@ -783,6 +783,9 @@ void bv_decl_plugin::get_op_names(svector<builtin_name> & op_names, symbol const
|
|||
op_names.push_back(builtin_name("rotate_left",OP_ROTATE_LEFT));
|
||||
op_names.push_back(builtin_name("rotate_right",OP_ROTATE_RIGHT));
|
||||
op_names.push_back(builtin_name("bit2bool", OP_BIT2BOOL));
|
||||
op_names.push_back(builtin_name("ubv_to_int", OP_UBV2INT));
|
||||
op_names.push_back(builtin_name("sbv_to_int", OP_SBV2INT));
|
||||
op_names.push_back(builtin_name("int_to_bv", OP_INT2BV));
|
||||
|
||||
if (logic == symbol::null || logic == symbol("ALL") || logic == "QF_FD" || logic == "HORN") {
|
||||
op_names.push_back(builtin_name("bvumul_noovfl",OP_BUMUL_NO_OVFL));
|
||||
|
|
@ -804,11 +807,10 @@ void bv_decl_plugin::get_op_names(svector<builtin_name> & op_names, symbol const
|
|||
op_names.push_back(builtin_name("ext_rotate_left",OP_EXT_ROTATE_LEFT));
|
||||
op_names.push_back(builtin_name("ext_rotate_right",OP_EXT_ROTATE_RIGHT));
|
||||
op_names.push_back(builtin_name("int2bv",OP_INT2BV));
|
||||
op_names.push_back(builtin_name("int_to_bv",OP_INT2BV));
|
||||
|
||||
op_names.push_back(builtin_name("bv2int",OP_UBV2INT));
|
||||
op_names.push_back(builtin_name("bv2nat",OP_UBV2INT));
|
||||
op_names.push_back(builtin_name("ubv_to_int",OP_UBV2INT));
|
||||
op_names.push_back(builtin_name("sbv_to_int",OP_SBV2INT));
|
||||
|
||||
op_names.push_back(builtin_name("mkbv",OP_MKBV));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ namespace euf {
|
|||
// Instructions
|
||||
//
|
||||
// ------------------------------------
|
||||
typedef enum {
|
||||
typedef enum : uint8_t {
|
||||
INIT1=0, INIT2, INIT3, INIT4, INIT5, INIT6, INITN, INITAC,
|
||||
BIND1, BIND2, BIND3, BIND4, BIND5, BIND6, BINDN,
|
||||
YIELD1, YIELD2, YIELD3, YIELD4, YIELD5, YIELD6, YIELDN,
|
||||
|
|
@ -239,6 +239,7 @@ namespace euf {
|
|||
unsigned short m_num_args;
|
||||
unsigned m_ireg;
|
||||
unsigned m_oreg;
|
||||
unsigned m_curr_generation;
|
||||
};
|
||||
|
||||
struct get_cgr : public instruction {
|
||||
|
|
@ -1926,28 +1927,38 @@ namespace euf {
|
|||
m_max_generation = std::max(m_max_generation, n->generation());
|
||||
}
|
||||
|
||||
void get_f_app(func_decl* lbl, unsigned num_expected_args, enode* curr, enode*& matching_cgr, enode*& min_gen_match) {
|
||||
if (curr->get_decl() == lbl && curr->num_args() == num_expected_args) {
|
||||
if (curr->is_cgr() && !matching_cgr)
|
||||
matching_cgr = curr;
|
||||
if (!min_gen_match || min_gen_match->generation() > curr->generation())
|
||||
min_gen_match = curr;
|
||||
}
|
||||
}
|
||||
|
||||
// We have to provide the number of expected arguments because we have flat-assoc applications such as +.
|
||||
// Flat-assoc applications may have arbitrary number of arguments.
|
||||
enode * get_first_f_app(func_decl * lbl, unsigned num_expected_args, enode * first) {
|
||||
enode *matching_cgr = nullptr, *min_gen_match = nullptr;
|
||||
for (enode* curr : euf::enode_class(first)) {
|
||||
if (curr->get_decl() == lbl && curr->is_cgr() && curr->num_args() == num_expected_args) {
|
||||
update_max_generation(curr, first);
|
||||
return curr;
|
||||
}
|
||||
get_f_app(lbl, num_expected_args, curr, matching_cgr, min_gen_match);
|
||||
curr = curr->get_next();
|
||||
}
|
||||
return nullptr;
|
||||
if (matching_cgr)
|
||||
update_max_generation(min_gen_match, first);
|
||||
return matching_cgr;
|
||||
}
|
||||
|
||||
enode * get_next_f_app(func_decl * lbl, unsigned num_expected_args, enode * first, enode * curr) {
|
||||
curr = curr->get_next();
|
||||
enode *matching_cgr = nullptr, *min_gen_match = nullptr;
|
||||
while (curr != first) {
|
||||
if (curr->get_decl() == lbl && curr->is_cgr() && curr->num_args() == num_expected_args) {
|
||||
update_max_generation(curr, first);
|
||||
return curr;
|
||||
}
|
||||
get_f_app(lbl, num_expected_args, curr, matching_cgr, min_gen_match);
|
||||
curr = curr->get_next();
|
||||
}
|
||||
return nullptr;
|
||||
if (matching_cgr)
|
||||
update_max_generation(min_gen_match, first);
|
||||
return matching_cgr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2563,6 +2574,7 @@ namespace euf {
|
|||
m_backtrack_stack[m_top].m_instr = m_pc; \
|
||||
m_backtrack_stack[m_top].m_old_max_generation = m_curr_max_generation; \
|
||||
m_backtrack_stack[m_top].m_curr = m_app; \
|
||||
const_cast<bind*>(static_cast<bind const*>(m_pc))->m_curr_generation = m_max_generation; \
|
||||
m_top++;
|
||||
|
||||
BIND_COMMON();
|
||||
|
|
@ -2829,7 +2841,8 @@ namespace euf {
|
|||
goto backtrack; \
|
||||
} \
|
||||
bp.m_curr = m_app; \
|
||||
TRACE(mam_int, tout << "bind next candidate:\n" << mk_ll_pp(m_app->get_expr(), m);); \
|
||||
m_max_generation = m_b->m_curr_generation; \
|
||||
TRACE(mam_int, tout << "bind next candidate:\n" << mk_ll_pp(m_app->get_expr(), m);); \
|
||||
m_oreg = m_b->m_oreg
|
||||
|
||||
BBIND_COMMON();
|
||||
|
|
@ -4059,4 +4072,4 @@ void euf::mam::ground_subterms(expr* e, ptr_vector<app>& ground) {
|
|||
|
||||
euf::mam* euf::mam::mk(euf::mam_solver& ctx, euf::on_binding_callback& em) {
|
||||
return alloc(mam_impl, ctx, em, true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace euf {
|
|||
}
|
||||
|
||||
void ho_matcher::search() {
|
||||
IF_VERBOSE(1, display(verbose_stream()));
|
||||
IF_VERBOSE(10, display(verbose_stream()));
|
||||
|
||||
while (m.inc()) {
|
||||
// Q, B -> Q', B'. Push work on the backtrack stack and new work items
|
||||
|
|
@ -77,7 +77,7 @@ namespace euf {
|
|||
break;
|
||||
}
|
||||
|
||||
IF_VERBOSE(1, display(verbose_stream() << "ho_matcher: done\n"));
|
||||
IF_VERBOSE(10, display(verbose_stream() << "ho_matcher: done\n"));
|
||||
}
|
||||
|
||||
void ho_matcher::backtrack() {
|
||||
|
|
@ -92,7 +92,7 @@ namespace euf {
|
|||
while (!m_backtrack.empty()) {
|
||||
auto& wi = *m_backtrack.back();
|
||||
bool st = consume_work(wi);
|
||||
IF_VERBOSE(3, display(verbose_stream() << "ho_matcher::consume_work: " << wi.pat << " =?= " << wi.t << " -> " << (st?"true":"false") << "\n"););
|
||||
TRACE(ho_matching, display(tout << "ho_matcher::consume_work: " << mk_bounded_pp(wi.pat, m) << " =?= " << mk_bounded_pp(wi.t, m) << " -> " << (st?"true":"false") << "\n"););
|
||||
if (st) {
|
||||
if (m_goals.empty())
|
||||
m_on_match(m_subst);
|
||||
|
|
@ -110,7 +110,11 @@ namespace euf {
|
|||
}
|
||||
|
||||
lbool ho_matcher::are_equal(unsigned o1, expr* p, unsigned o2, expr* t) const {
|
||||
SASSERT(p->get_sort() == t->get_sort());
|
||||
if (p->get_sort() != t->get_sort()) {
|
||||
TRACE(ho_matching, tout << "sort mismatch: " << mk_pp(p, m) << " : " << mk_pp(p->get_sort(), m)
|
||||
<< " vs " << mk_pp(t, m) << " : " << mk_pp(t->get_sort(), m) << "\n";);
|
||||
return l_false;
|
||||
}
|
||||
if (o1 == o2 && p == t)
|
||||
return l_true;
|
||||
|
||||
|
|
@ -239,25 +243,19 @@ namespace euf {
|
|||
return r;
|
||||
}
|
||||
|
||||
// We assume that m_rewriter should produce
|
||||
// something amounting to weak-head normal form WHNF
|
||||
expr_ref ho_matcher::whnf_star(expr *e, unsigned offset) const {
|
||||
expr_ref r(e, m);
|
||||
while (true) {
|
||||
auto q = whnf(r, offset);
|
||||
if (q == r)
|
||||
return r;
|
||||
r = q;
|
||||
}
|
||||
}
|
||||
|
||||
void ho_matcher::reduce(match_goal& wi) {
|
||||
while (true) {
|
||||
expr_ref r = whnf(wi.pat, wi.pat_offset());
|
||||
if (r == wi.pat)
|
||||
break;
|
||||
IF_VERBOSE(3, verbose_stream() << "ho_matcher::reduce: " << wi.pat << " -> " << r << "\n";);
|
||||
wi.pat = r;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
expr_ref r = whnf(wi.t, wi.term_offset());
|
||||
if (r == wi.t)
|
||||
break;
|
||||
IF_VERBOSE(3, verbose_stream() << "ho_matcher::reduce: " << wi.t << " -> " << r << "\n";);
|
||||
wi.t = r;
|
||||
}
|
||||
wi.pat = whnf_star(wi.pat, wi.pat_offset());
|
||||
wi.t = whnf_star(wi.t, wi.term_offset());
|
||||
}
|
||||
|
||||
bool ho_matcher::consume_work(match_goal &wi) {
|
||||
|
|
@ -288,7 +286,6 @@ namespace euf {
|
|||
break;
|
||||
}
|
||||
|
||||
|
||||
// v >= offset
|
||||
// v - offset |-> t
|
||||
if (is_meta_var(p, wi.pat_offset()) && is_closed(t, 0, wi.term_offset())) {
|
||||
|
|
@ -299,7 +296,6 @@ namespace euf {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
// N = \ x. T => ((shift1 N) x) = T
|
||||
if (is_lambda(t) && !is_lambda(p)) {
|
||||
auto q = to_quantifier(t);
|
||||
|
|
@ -318,6 +314,43 @@ namespace euf {
|
|||
return true;
|
||||
}
|
||||
|
||||
// \x . N = T => N = ((shift1 T) x)
|
||||
if (is_lambda(p) && !is_lambda(t)) {
|
||||
auto q = to_quantifier(p);
|
||||
auto p_body = q->get_expr();
|
||||
auto nd = q->get_num_decls();
|
||||
var_shifter vs(m);
|
||||
expr_ref r(m);
|
||||
vs(t, nd, r);
|
||||
expr_ref_vector args(m);
|
||||
args.push_back(r);
|
||||
for (unsigned i = 0; i < nd; ++i)
|
||||
args.push_back(m.mk_var(nd - 1 - i, q->get_decl_sort(i)));
|
||||
r = m_array.mk_select(args);
|
||||
m_goals.push(wi.level, wi.term_offset() + nd, p_body, r);
|
||||
wi.set_done();
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// lambda x . p == lambda x . t
|
||||
//
|
||||
if (is_quantifier(p) && is_quantifier(t)) {
|
||||
auto qp = to_quantifier(p);
|
||||
auto qt = to_quantifier(t);
|
||||
unsigned pd = qp->get_num_decls();
|
||||
unsigned td = qt->get_num_decls();
|
||||
if (qp->get_kind() != qt->get_kind())
|
||||
return false;
|
||||
if (pd != td)
|
||||
return false;
|
||||
for (unsigned i = 0; i < pd; ++i)
|
||||
if (qp->get_decl_sort(i) != qt->get_decl_sort(i))
|
||||
return false;
|
||||
m_goals.push(wi.level, wi.term_offset() + td, qp->get_expr(), qt->get_expr());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flex head unitary
|
||||
// H(pat) = t
|
||||
|
||||
|
|
@ -457,25 +490,7 @@ namespace euf {
|
|||
m_goals.push(wi.level, wi.term_offset(), tp->get_arg(i), ta->get_arg(i));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// lambda x . p == lambda x . t
|
||||
//
|
||||
if (is_quantifier(p) && is_quantifier(t)) {
|
||||
auto qp = to_quantifier(p);
|
||||
auto qt = to_quantifier(t);
|
||||
unsigned pd = qp->get_num_decls();
|
||||
unsigned td = qt->get_num_decls();
|
||||
if (qp->get_kind() != qt->get_kind())
|
||||
return false;
|
||||
if (pd != td)
|
||||
return false;
|
||||
for (unsigned i = 0; i < pd; ++i)
|
||||
if (qp->get_decl_sort(i) != qt->get_decl_sort(i))
|
||||
return false;
|
||||
m_goals.push(wi.level, wi.term_offset() + td, qp->get_expr(), qt->get_expr());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -488,8 +503,7 @@ namespace euf {
|
|||
uint_set vars;
|
||||
while (m_array.is_select(p)) {
|
||||
auto a = to_app(p);
|
||||
for (unsigned i = 1; i < a->get_num_args(); ++i) {
|
||||
auto arg = a->get_arg(i);
|
||||
for (auto arg : *a) {
|
||||
if (!is_bound_var(arg, offset))
|
||||
return false;
|
||||
auto idx = to_var(arg)->get_idx();
|
||||
|
|
@ -549,15 +563,12 @@ namespace euf {
|
|||
}
|
||||
expr_ref_vector pat2bound(m);
|
||||
for (auto a : pats) {
|
||||
unsigned sz = a->get_num_args();
|
||||
for (unsigned i = 1; i < sz; ++i) {
|
||||
auto arg = a->get_arg(i);
|
||||
for (auto arg : *a) {
|
||||
SASSERT(is_bound_var(arg, offset));
|
||||
auto idx = to_var(arg)->get_idx();
|
||||
pat2bound.reserve(idx + 1);
|
||||
pat2bound[idx] = m.mk_var(--num_bound, arg->get_sort());
|
||||
}
|
||||
p1 = a->get_arg(0);
|
||||
}
|
||||
}
|
||||
var_subst sub(m, false);
|
||||
expr_ref lam = sub(t, pat2bound);
|
||||
|
|
@ -575,7 +586,7 @@ namespace euf {
|
|||
|
||||
//
|
||||
// keep track of number of internal scopes and offset to non-capture variables.
|
||||
// a variable is captured if it's index is in the interval [scopes, offset[.
|
||||
// a variable is captured if its index is in the interval [scopes, offset[.
|
||||
//
|
||||
bool ho_matcher::is_closed(expr* v, unsigned scopes, unsigned offset) const {
|
||||
if (is_ground(v))
|
||||
|
|
@ -630,49 +641,59 @@ namespace euf {
|
|||
void ho_matcher::add_binding(var* v, unsigned offset, expr* t) {
|
||||
SASSERT(v->get_idx() >= offset);
|
||||
m_subst.set(v->get_idx() - offset, t);
|
||||
IF_VERBOSE(1, verbose_stream() << "ho_matcher::add_binding: v" << v->get_idx() - offset << " -> " << mk_pp(t, m) << "\n";);
|
||||
SASSERT(v->get_sort() == t->get_sort());
|
||||
TRACE(ho_matching, tout << "ho_matcher::add_binding: v" << v->get_idx() - offset << " -> " << mk_pp(t, m) << "\n";);
|
||||
m_trail.push(undo_set(m_subst, v->get_idx() - offset));
|
||||
}
|
||||
|
||||
|
||||
std::pair<quantifier*, app*> ho_matcher::compile_ho_pattern(quantifier* q, app* p) {
|
||||
app* p1 = nullptr;
|
||||
if (m_pat2hopat.find(p, p)) {
|
||||
q = m_q2hoq[q];
|
||||
return { q, p };
|
||||
quantifier *q1 = nullptr;
|
||||
if (m_pat2hopat.find(p, p1) && m_q2hoq.find(q, q1)) {
|
||||
return { q1, p1 };
|
||||
}
|
||||
auto is_ho = any_of(subterms::all(expr_ref(p, m)), [&](expr* t) { return m_unitary.is_flex(0, t); });
|
||||
auto is_ho = any_of(subterms::all(expr_ref(p, m)), [&](expr* t) {
|
||||
return m_unitary.is_flex(0, t) ||
|
||||
// m.is_lambda_def(t) ||
|
||||
is_lambda(t);
|
||||
});
|
||||
if (!is_ho)
|
||||
return { q, p };
|
||||
ptr_vector<expr> todo;
|
||||
vector<std::pair<expr*, unsigned>> todo;
|
||||
ptr_buffer<var> bound;
|
||||
expr_ref_vector cache(m);
|
||||
unsigned nb = q->get_num_decls();
|
||||
todo.push_back(p);
|
||||
bool contains_pat2abs = m_pat2abs.contains(p);
|
||||
SASSERT(m.is_pattern(p));
|
||||
todo.push_back({p, 0});
|
||||
while (!todo.empty()) {
|
||||
auto t = todo.back();
|
||||
auto [t, lvl] = todo.back();
|
||||
if (is_var(t)) {
|
||||
cache.setx(t->get_id(), t);
|
||||
todo.pop_back();
|
||||
continue;
|
||||
}
|
||||
if (m_unitary.is_flex(0, t)) {
|
||||
m_pat2abs.insert_if_not_there(p, svector<std::pair<unsigned, expr*>>()).push_back({ nb, t });
|
||||
if ((m_unitary.is_flex(0, t) && lvl > 1) || // m.is_lambda_def(t) ||
|
||||
is_lambda(t)) {
|
||||
if (!contains_pat2abs)
|
||||
m_pat2abs.insert_if_not_there(p, svector<std::pair<unsigned, expr*>>()).push_back({ nb, t });
|
||||
auto v = m.mk_var(nb++, t->get_sort());
|
||||
bound.push_back(v);
|
||||
cache.setx(t->get_id(), v);
|
||||
todo.pop_back();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (is_app(t)) {
|
||||
auto a = to_app(t);
|
||||
|
||||
unsigned sz = a->get_num_args();
|
||||
ptr_buffer<expr> args;
|
||||
for (auto arg : *a) {
|
||||
cache.reserve(arg->get_id() + 1);
|
||||
expr* arg1 = cache.get(arg->get_id());
|
||||
if (!arg1)
|
||||
todo.push_back(arg);
|
||||
todo.push_back({arg, lvl + 1});
|
||||
else
|
||||
args.push_back(arg1);
|
||||
}
|
||||
|
|
@ -682,11 +703,15 @@ namespace euf {
|
|||
cache.setx(t->get_id(), m.mk_app(a->get_decl(), args.size(), args.data()));
|
||||
}
|
||||
if (is_quantifier(t)) {
|
||||
m_pat2abs.remove(p);
|
||||
if (!contains_pat2abs)
|
||||
m_pat2abs.remove(p);
|
||||
return { q, p };
|
||||
}
|
||||
}
|
||||
p1 = to_app(cache.get(p->get_id()));
|
||||
|
||||
if (p1 == p)
|
||||
return {q, p};
|
||||
expr_free_vars free_vars;
|
||||
free_vars(p1);
|
||||
app_ref_vector new_ground(m);
|
||||
|
|
@ -713,6 +738,8 @@ namespace euf {
|
|||
auto body = q->get_expr();
|
||||
if (!new_patterns.empty()) {
|
||||
ptr_vector<app> pats;
|
||||
CTRACE(ho_matching, !m.is_pattern(p1),
|
||||
tout << mk_pp(p, m) << "\n" << mk_pp(p1, m) << "\n";);
|
||||
VERIFY(m.is_pattern(p1, pats));
|
||||
for (auto p : new_patterns) // patterns for variables that are not free in new pattern
|
||||
pats.push_back(p);
|
||||
|
|
@ -721,23 +748,40 @@ namespace euf {
|
|||
p1 = m.mk_pattern(pats.size(), pats.data());
|
||||
}
|
||||
|
||||
quantifier* q1 = m.mk_forall(sorts.size(), sorts.data(), names.data(), body);
|
||||
q1 = m.mk_forall(sorts.size(), sorts.data(), names.data(), body);
|
||||
|
||||
m_pat2hopat.insert(p, p1);
|
||||
m_hopat2pat.insert(p1, p);
|
||||
m_q2hoq.insert(q, q1);
|
||||
m_hoq2q.insert(q1, q);
|
||||
m_hopat2free_vars.insert(p1, std::move(free_vars));
|
||||
m_ho_patterns.push_back(p1);
|
||||
m_ho_qs.push_back(q1);
|
||||
trail().push(push_back_vector(m_ho_patterns));
|
||||
trail().push(push_back_vector(m_ho_qs));
|
||||
trail().push(insert_map(m_pat2hopat, p));
|
||||
trail().push(insert_map(m_hopat2pat, p1));
|
||||
trail().push(insert_map(m_pat2abs, p));
|
||||
trail().push(insert_map(m_q2hoq, q));
|
||||
trail().push(insert_map(m_hoq2q, q1));
|
||||
trail().push(insert_map(m_hopat2free_vars, p1));
|
||||
|
||||
if (!m_pat2hopat.contains(p)) {
|
||||
m_pat2hopat.insert(p, p1);
|
||||
trail().push(insert_map(m_pat2hopat, p));
|
||||
}
|
||||
if (!m_hopat2pat.contains(p1)) {
|
||||
m_hopat2pat.insert(p1, p);
|
||||
trail().push(insert_map(m_hopat2pat, p1));
|
||||
}
|
||||
if (!m_q2hoq.contains(q)) {
|
||||
m_q2hoq.insert(q, q1);
|
||||
trail().push(insert_map(m_q2hoq, q));
|
||||
}
|
||||
if (!m_hoq2q.contains(q1)) {
|
||||
m_hoq2q.insert(q1, q);
|
||||
trail().push(insert_map(m_hoq2q, q1));
|
||||
}
|
||||
if (!m_hopat2free_vars.contains(p1)) {
|
||||
m_hopat2free_vars.insert(p1, std::move(free_vars));
|
||||
trail().push(insert_map(m_hopat2free_vars, p1));
|
||||
}
|
||||
if (!contains_pat2abs)
|
||||
trail().push(insert_map(m_pat2abs, p));
|
||||
|
||||
TRACE(ho_matching, tout << mk_pp(q, m) << "\n"
|
||||
<< mk_pp(p, m) << "\n->\n"
|
||||
<< mk_pp(q1, m) << "\n"
|
||||
<< mk_pp(p1, m) << "\n");
|
||||
return { q1, p1 };
|
||||
}
|
||||
|
||||
|
|
@ -745,28 +789,46 @@ namespace euf {
|
|||
return m_hopat2pat.contains(p);
|
||||
}
|
||||
|
||||
void ho_matcher::register_ho_pattern(app* alias_p, app* full_p) {
|
||||
if (alias_p == full_p) return;
|
||||
auto orig_p = m_hopat2pat[full_p];
|
||||
m_hopat2pat.insert(alias_p, orig_p);
|
||||
m_hopat2free_vars.insert(alias_p, m_hopat2free_vars[full_p]);
|
||||
m_ho_patterns.push_back(alias_p);
|
||||
trail().push(push_back_vector(m_ho_patterns));
|
||||
trail().push(insert_map(m_hopat2pat, alias_p));
|
||||
trail().push(insert_map(m_hopat2free_vars, alias_p));
|
||||
}
|
||||
|
||||
void ho_matcher::refine_ho_match(app* p, expr_ref_vector& s) {
|
||||
auto fo_pat = m_hopat2pat[p];
|
||||
IF_VERBOSE(10, verbose_stream() << "refine_ho_match: p=" << mk_pp(p, m) << "\n fo_pat=" << mk_pp(fo_pat, m) << "\n";
|
||||
verbose_stream() << " m_pat2abs has fo_pat: " << m_pat2abs.contains(fo_pat) << "\n";
|
||||
auto& abs = m_pat2abs[fo_pat];
|
||||
verbose_stream() << " m_pat2abs size: " << abs.size() << "\n";
|
||||
for (auto [v, pat] : abs) verbose_stream() << " v=" << v << " pat=" << mk_pp(pat, m) << "\n";);
|
||||
m_trail.push_scope();
|
||||
m_subst.resize(0);
|
||||
m_subst.resize(s.size());
|
||||
m_goals.reset();
|
||||
// MAM bindings are reversed: s[i] = binding for var idx = s.size()-1-i
|
||||
// m_subst is indexed by var index directly
|
||||
for (unsigned i = 0; i < s.size(); ++i) {
|
||||
auto idx = s.size() - i - 1;
|
||||
if (!m_hopat2free_vars[p].contains(idx))
|
||||
s[i] = m.mk_var(idx, s[i]->get_sort());
|
||||
else if (s.get(i))
|
||||
m_subst.set(i, s.get(i));
|
||||
m_subst.set(idx, s.get(i));
|
||||
}
|
||||
|
||||
IF_VERBOSE(1, verbose_stream() << "refine " << mk_pp(p, m) << "\n" << s << "\n");
|
||||
TRACE(ho_matching, tout << "refine " << mk_pp(p, m) << "\n" << s << "\n");
|
||||
|
||||
unsigned num_bound = 0, level = 0;
|
||||
for (auto [v, pat] : m_pat2abs[fo_pat]) {
|
||||
var_subst sub(m, true);
|
||||
auto pat_refined = sub(pat, s);
|
||||
IF_VERBOSE(1, verbose_stream() << mk_pp(pat, m) << " -> " << pat_refined << "\n");
|
||||
m_goals.push(level, num_bound, pat_refined, s.get(s.size() - v - 1));
|
||||
TRACE(ho_matching, tout << mk_pp(pat, m) << " -> " << pat_refined << "\n");
|
||||
m_goals.push(level, num_bound, pat_refined, m_subst.get(v));
|
||||
}
|
||||
|
||||
search();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ Author:
|
|||
#include "ast/for_each_expr.h"
|
||||
#include "ast/reg_decl_plugins.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/rewriter/array_rewriter.h"
|
||||
#include "ast/rewriter/var_subst.h"
|
||||
|
||||
|
|
@ -88,13 +89,15 @@ namespace euf {
|
|||
}
|
||||
|
||||
match_goal(unsigned level, unsigned offset, expr_ref const& pat, expr_ref const& t) noexcept :
|
||||
base_offset(offset), pat(pat), t(t), level(level) {}
|
||||
base_offset(offset), pat(pat), t(t), level(level) {
|
||||
SASSERT(pat->get_sort() == t->get_sort());
|
||||
}
|
||||
|
||||
unsigned term_offset() const { return base_offset + delta_offset; }
|
||||
unsigned pat_offset() const { return base_offset + delta_offset; }
|
||||
|
||||
std::ostream& display(std::ostream& out) const {
|
||||
return out << "[" << level << ":" << base_offset + delta_offset << "] " << pat << " ~ " << t << "\n";
|
||||
return out << "[" << level << ":" << base_offset + delta_offset << "] " << mk_bounded_pp(pat, pat.m()) << " ~ " << mk_bounded_pp(t, t.m()) << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -329,6 +332,8 @@ namespace euf {
|
|||
bool consume_work(match_goal& wi);
|
||||
|
||||
expr_ref whnf(expr* e, unsigned offset) const;
|
||||
|
||||
expr_ref whnf_star(expr *e, unsigned offset) const;
|
||||
|
||||
bool is_bound_var(expr* v, unsigned offset) const { return is_var(v) && to_var(v)->get_idx() < offset; }
|
||||
|
||||
|
|
@ -389,11 +394,23 @@ namespace euf {
|
|||
|
||||
bool is_ho_pattern(app* p);
|
||||
|
||||
// Register an alias pattern (e.g., after stripping ground elements)
|
||||
// that maps to the same original pattern as full_p
|
||||
void register_ho_pattern(app* alias_p, app* full_p);
|
||||
|
||||
void refine_ho_match(app* p, expr_ref_vector& s);
|
||||
|
||||
bool is_free(app* p, unsigned i) const { return m_hopat2free_vars[p].contains(i); }
|
||||
|
||||
quantifier* hoq2q(quantifier* q) const { return m_hoq2q[q]; }
|
||||
|
||||
|
||||
svector<std::pair<unsigned, expr*>> const* get_flex_subterms(app* p) const {
|
||||
auto orig_p = m_hopat2pat.find_core(p);
|
||||
if (!orig_p) return nullptr;
|
||||
auto abs = m_pat2abs.find_core(orig_p->get_data().get_value());
|
||||
return abs ? &abs->get_data().get_value() : nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2976,13 +2976,12 @@ void fpa2bv_converter::mk_to_real(func_decl * f, unsigned num, expr * const * ar
|
|||
prev_bit = bit;
|
||||
}
|
||||
|
||||
expr_ref one_div_exp2(m);
|
||||
one_div_exp2 = m_arith_util.mk_div(one, exp2);
|
||||
exp2 = m.mk_ite(exp_is_neg, one_div_exp2, exp2);
|
||||
dbg_decouple("fpa2bv_to_real_exp2", exp2);
|
||||
|
||||
expr_ref res(m), two_exp2(m), minus_res(m), sgn_is_1(m);
|
||||
expr_ref two_exp2(m), one_div_two_exp2(m);
|
||||
two_exp2 = m_arith_util.mk_power(two, exp2);
|
||||
one_div_two_exp2 = m_arith_util.mk_div(one, two_exp2);
|
||||
two_exp2 = m.mk_ite(exp_is_neg, one_div_two_exp2, two_exp2);
|
||||
dbg_decouple("fpa2bv_to_real_exp2", two_exp2);
|
||||
expr_ref res(m), minus_res(m), sgn_is_1(m);
|
||||
res = m_arith_util.mk_mul(rsig, two_exp2);
|
||||
minus_res = m_arith_util.mk_uminus(res);
|
||||
sgn_is_1 = m.mk_eq(sgn, bv1);
|
||||
|
|
@ -2990,7 +2989,7 @@ void fpa2bv_converter::mk_to_real(func_decl * f, unsigned num, expr * const * ar
|
|||
dbg_decouple("fpa2bv_to_real_sig_times_exp2", res);
|
||||
|
||||
TRACE(fpa2bv_to_real, tout << "rsig = " << mk_ismt2_pp(rsig, m) << std::endl;
|
||||
tout << "exp2 = " << mk_ismt2_pp(exp2, m) << std::endl;);
|
||||
tout << "two_exp2 = " << mk_ismt2_pp(two_exp2, m) << std::endl;);
|
||||
|
||||
expr_ref unspec(m);
|
||||
mk_to_real_unspecified(f, num, args, unspec);
|
||||
|
|
|
|||
|
|
@ -121,9 +121,6 @@ app * defined_names::impl::gen_name(expr * e, sort_ref_buffer & var_sorts, buffe
|
|||
sort * range = e->get_sort();
|
||||
func_decl * new_skolem_decl = m.mk_fresh_func_decl(m_z3name, symbol::null, domain.size(), domain.data(), range);
|
||||
app * n = m.mk_app(new_skolem_decl, new_args.size(), new_args.data());
|
||||
if (is_lambda(e)) {
|
||||
m.add_lambda_def(new_skolem_decl, to_quantifier(e));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
@ -193,43 +190,7 @@ void defined_names::impl::mk_definition(expr * e, app * n, sort_ref_buffer & var
|
|||
else if (m.is_term_ite(e)) {
|
||||
bound_vars(var_sorts, var_names, MK_OR(MK_NOT(to_app(e)->get_arg(0)), MK_EQ(n, to_app(e)->get_arg(1))), n, defs);
|
||||
bound_vars(var_sorts, var_names, MK_OR(to_app(e)->get_arg(0), MK_EQ(n, to_app(e)->get_arg(2))), n, defs);
|
||||
}
|
||||
else if (is_lambda(e)) {
|
||||
// n(y) = \x . M[x,y]
|
||||
// =>
|
||||
// n(y)[x] = M, forall x y
|
||||
//
|
||||
// NB. The pattern is incomplete.
|
||||
// consider store(a, i, v) == \lambda j . if i = j then v else a[j]
|
||||
// the instantiation rules for store(a, i, v) are:
|
||||
// store(a, i, v)[j] = if i = j then v else a[j] with patterns {a[j], store(a, i, v)} { store(a, i, v)[j] }
|
||||
// The first pattern is not included.
|
||||
// TBD use a model-based scheme for extracting instantiations instead of
|
||||
// using multi-patterns.
|
||||
//
|
||||
|
||||
quantifier* q = to_quantifier(e);
|
||||
expr_ref_vector args(m);
|
||||
expr_ref n2(m), n3(m);
|
||||
var_shifter vs(m);
|
||||
vs(n, q->get_num_decls(), n2);
|
||||
args.push_back(n2);
|
||||
var_sorts.append(q->get_num_decls(), q->get_decl_sorts());
|
||||
var_names.append(q->get_num_decls(), q->get_decl_names());
|
||||
for (unsigned i = 0; i < q->get_num_decls(); ++i) {
|
||||
args.push_back(m.mk_var(q->get_num_decls() - i - 1, q->get_decl_sort(i)));
|
||||
}
|
||||
array_util autil(m);
|
||||
func_decl * f = nullptr;
|
||||
if (autil.is_as_array(n2, f)) {
|
||||
n3 = m.mk_app(f, args.size()-1, args.data() + 1);
|
||||
}
|
||||
else {
|
||||
n3 = autil.mk_select(args.size(), args.data());
|
||||
}
|
||||
bound_vars(var_sorts, var_names, MK_EQ(q->get_expr(), n3), to_app(n3), defs, m.lambda_def_qid());
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
bound_vars(var_sorts, var_names, MK_EQ(e, n), n, defs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class name_quantifier_labels : public name_exprs_core {
|
|||
public:
|
||||
pred(ast_manager & m):m(m) {}
|
||||
bool operator()(expr * t) override {
|
||||
return is_quantifier(t) || m.is_label(t);
|
||||
return (is_quantifier(t) && !is_lambda(t)) || m.is_label(t);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class name_nested_formulas : public name_exprs_core {
|
|||
TRACE(name_exprs, tout << "name_nested_formulas::pred:\n" << mk_ismt2_pp(t, m) << "\n";);
|
||||
if (is_app(t))
|
||||
return to_app(t)->get_family_id() == m.get_basic_family_id() && to_app(t)->get_num_args() > 0 && t != m_root;
|
||||
return m.is_label(t) || is_quantifier(t);
|
||||
return m.is_label(t) || (is_quantifier(t) && !is_lambda(t));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ struct pull_quant::imp {
|
|||
var_names.data(),
|
||||
nested_q->get_expr(),
|
||||
std::min(q->get_weight(), nested_q->get_weight()),
|
||||
m.is_lambda_def(q) ? symbol("pulled-lambda") : q->get_qid());
|
||||
q->get_qid());
|
||||
}
|
||||
|
||||
void pull_quant1(quantifier * q, expr * new_expr, expr_ref & result) {
|
||||
|
|
|
|||
|
|
@ -254,6 +254,27 @@ void pattern_inference_cfg::collect::save_candidate(expr * n, unsigned delta) {
|
|||
}
|
||||
return;
|
||||
}
|
||||
case AST_QUANTIFIER: {
|
||||
quantifier * q = to_quantifier(n);
|
||||
unsigned num_decls = q->get_num_decls();
|
||||
info * body_info = nullptr;
|
||||
m_cache.find(entry(q->get_expr(), delta + num_decls), body_info);
|
||||
if (body_info == nullptr) {
|
||||
save(n, delta, nullptr);
|
||||
return;
|
||||
}
|
||||
// The lambda/quantifier itself is a valid sub-term in a pattern.
|
||||
// Propagate the free variables from the body (they already refer
|
||||
// to the outer quantifier's bindings) and keep the node as-is.
|
||||
expr * new_body = body_info->m_node.get();
|
||||
quantifier_ref new_q(m);
|
||||
if (new_body != q->get_expr())
|
||||
new_q = m.update_quantifier(q, new_body);
|
||||
else
|
||||
new_q = q;
|
||||
save(n, delta, alloc(info, m, new_q, body_info->m_free_vars, body_info->m_size + 1));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
save(n, delta, nullptr);
|
||||
return;
|
||||
|
|
@ -363,6 +384,8 @@ bool pattern_inference_cfg::contains_subpattern::operator()(expr * n) {
|
|||
break;
|
||||
case AST_VAR:
|
||||
break;
|
||||
case AST_QUANTIFIER:
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
@ -525,7 +548,7 @@ void pattern_inference_cfg::reset_pre_patterns() {
|
|||
|
||||
|
||||
bool pattern_inference_cfg::is_forbidden(app * n) const {
|
||||
func_decl const * decl = n->get_decl();
|
||||
func_decl * decl = n->get_decl();
|
||||
if (is_ground(n))
|
||||
return false;
|
||||
// Remark: skolem constants should not be used in patterns, since they do not
|
||||
|
|
|
|||
|
|
@ -444,7 +444,8 @@ namespace recfun {
|
|||
|
||||
promise_def plugin::mk_def(symbol const& name, unsigned n, sort *const * params, sort * range, bool is_generated) {
|
||||
def* d = u().decl_fun(name, n, params, range, is_generated);
|
||||
SASSERT(!m_defs.contains(d->get_decl()));
|
||||
if (m_defs.contains(d->get_decl()))
|
||||
throw default_exception(std::string("recursive function ") + name.str() + " already defined");
|
||||
m_defs.insert(d->get_decl(), d);
|
||||
return promise_def(&u(), d);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -750,7 +750,10 @@ bool array_rewriter::add_store(expr_ref_vector& args, unsigned num_idxs, expr* e
|
|||
}
|
||||
if (is_var(e1) && is_ground(e2)) {
|
||||
unsigned idx = to_var(e1)->get_idx();
|
||||
args[num_idxs - idx - 1] = e2;
|
||||
unsigned nidx = num_idxs - idx - 1;
|
||||
if (args.get(nidx) && args.get(nidx) != e2)
|
||||
return false;
|
||||
args[nidx] = e2;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
|
|
@ -858,19 +861,45 @@ br_status array_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result)
|
|||
return false;
|
||||
};
|
||||
|
||||
auto domain_is_larger_than = [&](sort* s, unsigned num_stores) {
|
||||
unsigned sz = get_array_arity(s);
|
||||
rational dsz(1);
|
||||
for (unsigned i = 0; i < sz; ++i) {
|
||||
sort* d = get_array_domain(s, i);
|
||||
if (d->is_infinite())
|
||||
return true;
|
||||
if (d->is_very_big())
|
||||
return false;
|
||||
dsz *= rational(d->get_num_elements().size(), rational::ui64());
|
||||
if (dsz > rational(num_stores, rational::ui64()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
expr* lhs1 = lhs;
|
||||
expr* rhs1 = rhs;
|
||||
unsigned num_lhs = 0, num_rhs = 0;
|
||||
while (m_util.is_store(lhs1)) {
|
||||
lhs1 = to_app(lhs1)->get_arg(0);
|
||||
++num_lhs;
|
||||
}
|
||||
while (m_util.is_store(rhs1)) {
|
||||
rhs1 = to_app(rhs1)->get_arg(0);
|
||||
++num_rhs;
|
||||
}
|
||||
|
||||
if (m_util.is_const(lhs1, v) && m_util.is_const(rhs1, w) &&
|
||||
domain_is_larger_than(lhs->get_sort(), num_lhs + num_rhs)) {
|
||||
mk_eq(lhs, lhs, rhs, fmls);
|
||||
mk_eq(rhs, lhs, rhs, fmls);
|
||||
fmls.push_back(m().mk_eq(v, w));
|
||||
result = m().mk_and(fmls);
|
||||
return BR_REWRITE_FULL;
|
||||
}
|
||||
|
||||
|
||||
if (m_expand_store_eq) {
|
||||
expr* lhs1 = lhs;
|
||||
expr* rhs1 = rhs;
|
||||
unsigned num_lhs = 0, num_rhs = 0;
|
||||
while (m_util.is_store(lhs1)) {
|
||||
lhs1 = to_app(lhs1)->get_arg(0);
|
||||
++num_lhs;
|
||||
}
|
||||
while (m_util.is_store(rhs1)) {
|
||||
rhs1 = to_app(rhs1)->get_arg(0);
|
||||
++num_rhs;
|
||||
}
|
||||
if (lhs1 == rhs1) {
|
||||
mk_eq(lhs, lhs, rhs, fmls);
|
||||
mk_eq(rhs, lhs, rhs, fmls);
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ struct enum2bv_rewriter::imp {
|
|||
new_body_ref = mk_and(bounds);
|
||||
break;
|
||||
case lambda_k:
|
||||
case choice_k:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -561,9 +561,13 @@ void rewriter_tpl<Config>::process_quantifier(quantifier * q, frame & fr) {
|
|||
expr * const * np = it + 1;
|
||||
expr * const * nnp = np + num_pats;
|
||||
unsigned j = 0;
|
||||
for (unsigned i = 0; i < num_pats; ++i)
|
||||
for (unsigned i = 0; i < num_pats; ++i) {
|
||||
if (m_manager.is_pattern(np[i]))
|
||||
new_pats[j++] = np[i];
|
||||
else {
|
||||
IF_VERBOSE(10, verbose_stream() << "[rewriter] dropping pattern (is_pattern check failed) for qid=" << q->get_qid() << " pattern[" << i << "]: " << mk_ismt2_pp(np[i], m_manager, 3) << "\n";);
|
||||
}
|
||||
}
|
||||
new_pats.shrink(j);
|
||||
num_pats = j;
|
||||
j = 0;
|
||||
|
|
@ -664,7 +668,7 @@ template<typename Config>
|
|||
void rewriter_tpl<Config>::display_bindings(std::ostream& out) {
|
||||
for (unsigned i = 0; i < m_bindings.size(); ++i) {
|
||||
if (m_bindings[i])
|
||||
out << i << ": " << mk_ismt2_pp(m_bindings[i], m()) << ";\n";
|
||||
out << i << ": " << mk_ismt2_pp(m_bindings[i], m()) << " : " << mk_pp(m_bindings[i]->get_sort(), m()) << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ z3_add_component(simplifiers
|
|||
euf_completion.cpp
|
||||
extract_eqs.cpp
|
||||
factor_simplifier.cpp
|
||||
fold_unfold.cpp
|
||||
linear_equation.cpp
|
||||
max_bv_sharing.cpp
|
||||
model_reconstruction_trail.cpp
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ bool bound_propagator::relevant_bound(var x, double new_k) const {
|
|||
if (b == nullptr)
|
||||
return true; // variable did not have a bound
|
||||
|
||||
double interval_size;
|
||||
double interval_size = 0.0;
|
||||
bool bounded = get_interval_size(x, interval_size);
|
||||
|
||||
if (!is_int(x)) {
|
||||
|
|
@ -939,4 +939,3 @@ void bound_propagator::display(std::ostream & out) const {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -88,22 +88,6 @@ void dependent_expr_state::freeze_recfun() {
|
|||
m_num_recfun = sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze all functions used in lambda defined declarations
|
||||
*/
|
||||
void dependent_expr_state::freeze_lambda() {
|
||||
auto& m = m_frozen_trail.get_manager();
|
||||
unsigned sz = m.lambda_defs().size();
|
||||
if (m_num_lambdas >= sz)
|
||||
return;
|
||||
|
||||
ast_mark visited;
|
||||
for (auto const& [f, body] : m.lambda_defs())
|
||||
freeze_terms(body, false, visited);
|
||||
m_trail.push(value_trail(m_num_lambdas));
|
||||
m_num_lambdas = sz;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The current qhead is to be updated to qtail.
|
||||
|
|
@ -122,8 +106,7 @@ void dependent_expr_state::freeze_suffix() {
|
|||
if (m_suffix_frozen)
|
||||
return;
|
||||
m_suffix_frozen = true;
|
||||
freeze_recfun();
|
||||
freeze_lambda();
|
||||
freeze_recfun();
|
||||
auto& m = m_frozen_trail.get_manager();
|
||||
ast_mark visited;
|
||||
ptr_vector<expr> es;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ class dependent_expr_state {
|
|||
func_decl_ref_vector m_frozen_trail;
|
||||
void freeze_prefix();
|
||||
void freeze_recfun();
|
||||
void freeze_lambda();
|
||||
void freeze_terms(expr* term, bool only_as_array, ast_mark& visited);
|
||||
void freeze(func_decl* f);
|
||||
struct thaw : public trail {
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ eliminate:
|
|||
elim_unconstrained::elim_unconstrained(ast_manager& m, dependent_expr_state& fmls) :
|
||||
dependent_expr_simplifier(m, fmls), m_inverter(m), m_lt(*this), m_heap(1024, m_lt), m_trail(m), m_args(m) {
|
||||
std::function<bool(expr*)> is_var = [&](expr* e) {
|
||||
return is_uninterp_const(e) && !m_fmls.frozen(e) && get_node(e).is_root() && get_node(e).num_parents() <= 1;
|
||||
return is_uninterp_const(e) && !m_fmls.frozen(e) && !m_disabled.is_marked(e) && get_node(e).is_root() && get_node(e).num_parents() <= 1;
|
||||
};
|
||||
m_inverter.set_is_var(is_var);
|
||||
}
|
||||
|
|
@ -247,10 +247,12 @@ elim_unconstrained::node& elim_unconstrained::get_node(expr* t) {
|
|||
m_heap.increased(arg->get_id());
|
||||
}
|
||||
}
|
||||
else if (is_quantifier(t)) {
|
||||
node& ch = get_node(to_quantifier(t)->get_expr());
|
||||
else if (is_quantifier(t)) {
|
||||
auto body = to_quantifier(t)->get_expr();
|
||||
node& ch = get_node(body);
|
||||
SASSERT(ch.is_root());
|
||||
ch.add_parent(*n);
|
||||
disable(body);
|
||||
}
|
||||
}
|
||||
return *n;
|
||||
|
|
@ -411,10 +413,9 @@ void elim_unconstrained::update_model_trail(generic_model_converter& mc, vector<
|
|||
case generic_model_converter::instruction::HIDE:
|
||||
break;
|
||||
case generic_model_converter::instruction::ADD:
|
||||
// new_def = entry.m_def;
|
||||
// (*rp)(new_def);
|
||||
new_def = m.mk_const(entry.m_f);
|
||||
sub->insert(new_def, new_def, nullptr, nullptr);
|
||||
new_def = entry.m_def;
|
||||
(*rp)(new_def);
|
||||
sub->insert(m.mk_const(entry.m_f), new_def, nullptr, nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -436,6 +437,7 @@ void elim_unconstrained::reduce() {
|
|||
assert_normalized(old_fmls);
|
||||
update_model_trail(*mc, old_fmls);
|
||||
mc->reset();
|
||||
m_disabled.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,3 +445,21 @@ void elim_unconstrained::updt_params(params_ref const& p) {
|
|||
smt_params_helper sp(p);
|
||||
m_config.m_enabled = sp.elim_unconstrained();
|
||||
}
|
||||
|
||||
void elim_unconstrained::disable(expr* e) {
|
||||
if (m_disabled.is_marked(e))
|
||||
return;
|
||||
|
||||
ptr_buffer<expr> todo;
|
||||
todo.push_back(e);
|
||||
while (!todo.empty()) {
|
||||
e = todo.back();
|
||||
todo.pop_back();
|
||||
if (m_disabled.is_marked(e))
|
||||
continue;
|
||||
m_disabled.mark(e);
|
||||
if (is_app(e))
|
||||
for (auto arg : *to_app(e))
|
||||
todo.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ class elim_unconstrained : public dependent_expr_simplifier {
|
|||
stats m_stats;
|
||||
config m_config;
|
||||
bool m_created_compound = false;
|
||||
expr_mark m_disabled;
|
||||
|
||||
bool is_var_lt(int v1, int v2) const;
|
||||
node& get_node(unsigned n) const { return *m_nodes[n]; }
|
||||
|
|
@ -108,6 +109,7 @@ class elim_unconstrained : public dependent_expr_simplifier {
|
|||
expr* reconstruct_term(node& n);
|
||||
void assert_normalized(vector<dependent_expr>& old_fmls);
|
||||
void update_model_trail(generic_model_converter& mc, vector<dependent_expr> const& old_fmls);
|
||||
void disable(expr *e);
|
||||
|
||||
|
||||
public:
|
||||
|
|
|
|||
396
src/ast/simplifiers/fold_unfold.cpp
Normal file
396
src/ast/simplifiers/fold_unfold.cpp
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
/*++
|
||||
Copyright (c) 2022 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
fold_unfold.h
|
||||
|
||||
Abstract:
|
||||
|
||||
fold-unfold simplifier
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2025-11-5.
|
||||
|
||||
- remove alias x = y
|
||||
- remove alias with const x = k
|
||||
- fold-unfold simplification x = f(y), y = g(z), f(g(z)) = u -> x |-> u
|
||||
|
||||
- assign levels to E-nodes:
|
||||
- dfs over roots.
|
||||
- visit children, assign level
|
||||
-
|
||||
- remove alias with linear x = f(y) -> x |-> f(y) if level y < level x
|
||||
--*/
|
||||
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/simplifiers/fold_unfold.h"
|
||||
#include "ast/rewriter/expr_replacer.h"
|
||||
#include "util/union_find.h"
|
||||
#include "params/smt_params_helper.hpp"
|
||||
|
||||
namespace euf {
|
||||
|
||||
fold_unfold::fold_unfold(ast_manager& m, dependent_expr_state& fmls)
|
||||
: dependent_expr_simplifier(m, fmls),
|
||||
m_rewriter(m),
|
||||
m_egraph(m) {
|
||||
register_extract_eqs(m, m_extract_plugins);
|
||||
m_rewriter.set_flat_and_or(false);
|
||||
// flat sum/prod := false
|
||||
}
|
||||
|
||||
void fold_unfold::reduce() {
|
||||
if (!m_config.m_enabled)
|
||||
return;
|
||||
|
||||
m_fmls.freeze_suffix();
|
||||
|
||||
for (extract_eq* ex : m_extract_plugins)
|
||||
ex->pre_process(m_fmls);
|
||||
|
||||
reduce_alias(true);
|
||||
reduce_linear();
|
||||
reduce_alias(false);
|
||||
}
|
||||
|
||||
void fold_unfold::reduce_alias(bool fuf) {
|
||||
m_subst = nullptr;
|
||||
dep_eq_vector eqs;
|
||||
get_eqs(eqs);
|
||||
extract_subst(fuf, eqs);
|
||||
vector<dependent_expr> old_fmls;
|
||||
apply_subst(old_fmls);
|
||||
}
|
||||
|
||||
void fold_unfold::get_eqs(dep_eq_vector& eqs) {
|
||||
for (extract_eq* ex : m_extract_plugins)
|
||||
for (unsigned i : indices())
|
||||
ex->get_eqs(m_fmls[i], eqs);
|
||||
}
|
||||
|
||||
void fold_unfold::extract_subst(bool fuf, dep_eq_vector const& eqs) {
|
||||
m_find.reset();
|
||||
for (auto const& [orig, v, t, d] : eqs) {
|
||||
auto a = mk_enode(v);
|
||||
auto b = mk_enode(t);
|
||||
// verbose_stream() << mk_bounded_pp(v, m) << " == " << mk_bounded_pp(t, m) << "\n";
|
||||
proof_ref pr(m);
|
||||
auto j = to_ptr(push_pr_dep(pr, d));
|
||||
m_egraph.merge(a, b, j);
|
||||
}
|
||||
|
||||
// choose uninterpreted or value representative
|
||||
auto find_rep = [&](enode *a, ptr_buffer<enode>& vars) {
|
||||
enode *rep = nullptr;
|
||||
for (auto b : euf::enode_class(a)) {
|
||||
expr *t = b->get_expr();
|
||||
if (is_uninterp_const(t))
|
||||
vars.push_back(b);
|
||||
if (m.is_value(t))
|
||||
rep = b;
|
||||
}
|
||||
if (!rep) {
|
||||
for (auto v : vars)
|
||||
if (!rep || v->get_id() < rep->get_id())
|
||||
rep = v;
|
||||
}
|
||||
return rep;
|
||||
};
|
||||
|
||||
for (auto a : m_egraph.nodes()) {
|
||||
if (!a->is_root())
|
||||
continue;
|
||||
ptr_buffer<enode> vars;
|
||||
enode *rep = find_rep(a, vars);
|
||||
if (!rep)
|
||||
continue;
|
||||
for (auto w : vars) {
|
||||
if (w != rep)
|
||||
m_find.setx(w->get_id(), rep, nullptr);
|
||||
}
|
||||
}
|
||||
if (fuf) {
|
||||
// find new equalities by performing fold-unfold
|
||||
vector<std::tuple<enode *, expr_ref, proof_ref, expr_dependency *>> new_eqs;
|
||||
for (auto n : m_egraph.nodes()) {
|
||||
if (!n->is_root())
|
||||
continue;
|
||||
auto ne = n->get_expr();
|
||||
unsigned depth = 3;
|
||||
vector<std::pair<expr_ref, expr_dependency *>> es;
|
||||
unfold(depth, n, nullptr, es);
|
||||
// verbose_stream() << "unfolds " << es.size() << "\n";
|
||||
for (auto [e, d] : es) {
|
||||
expr_ref r(m);
|
||||
proof_ref pr(m);
|
||||
fold(e, r, pr);
|
||||
if (ne == r)
|
||||
continue;
|
||||
new_eqs.push_back({n, r, pr, d});
|
||||
}
|
||||
}
|
||||
for (auto const &[a, t, pr, d] : new_eqs) {
|
||||
auto b = mk_enode(t);
|
||||
auto j = to_ptr(push_pr_dep(pr, d));
|
||||
m_egraph.merge(a, b, j);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto a : m_egraph.nodes()) {
|
||||
if (!a->is_root())
|
||||
continue;
|
||||
ptr_buffer<enode> vars;
|
||||
enode *rep = find_rep(a, vars);
|
||||
if (!rep)
|
||||
continue;
|
||||
for (auto v : vars) {
|
||||
if (v == rep)
|
||||
continue;
|
||||
m_find.setx(v->get_id(), rep, nullptr);
|
||||
// verbose_stream() << "insert " << mk_pp(v->get_expr(), m) << " " << mk_pp(rep->get_expr(), m) << "\n";
|
||||
insert_subst(v->get_expr(), rep->get_expr(), explain_eq(v, rep));
|
||||
m_stats.m_num_elim_vars++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expr_dependency *fold_unfold::explain_eq(enode *a, enode *b) {
|
||||
if (a == b)
|
||||
return nullptr;
|
||||
ptr_vector<size_t> just;
|
||||
m_egraph.begin_explain();
|
||||
m_egraph.explain_eq(just, nullptr, a, b);
|
||||
m_egraph.end_explain();
|
||||
expr_dependency *d = nullptr;
|
||||
for (size_t *j : just)
|
||||
d = m.mk_join(d, m_pr_dep[from_ptr(j)].second);
|
||||
return d;
|
||||
}
|
||||
|
||||
unsigned fold_unfold::push_pr_dep(proof *pr, expr_dependency *d) {
|
||||
unsigned sz = m_pr_dep.size();
|
||||
SASSERT(!m.proofs_enabled() || pr);
|
||||
m_pr_dep.push_back({proof_ref(pr, m), d});
|
||||
m_trail.push(push_back_vector(m_pr_dep));
|
||||
return sz;
|
||||
}
|
||||
|
||||
enode *fold_unfold::mk_enode(expr *e) {
|
||||
m_todo.push_back(e);
|
||||
enode *n;
|
||||
while (!m_todo.empty()) {
|
||||
e = m_todo.back();
|
||||
if (m_egraph.find(e)) {
|
||||
m_todo.pop_back();
|
||||
continue;
|
||||
}
|
||||
if (!is_app(e)) {
|
||||
m_egraph.mk(e, m_generation, 0, nullptr);
|
||||
m_todo.pop_back();
|
||||
continue;
|
||||
}
|
||||
m_args.reset();
|
||||
unsigned sz = m_todo.size();
|
||||
for (expr *arg : *to_app(e)) {
|
||||
n = m_egraph.find(arg);
|
||||
if (n)
|
||||
m_args.push_back(n);
|
||||
else
|
||||
m_todo.push_back(arg);
|
||||
}
|
||||
if (sz == m_todo.size()) {
|
||||
n = m_egraph.mk(e, m_generation, m_args.size(), m_args.data());
|
||||
if (m_egraph.get_plugin(e->get_sort()->get_family_id()))
|
||||
m_egraph.add_th_var(n, m_th_var++, e->get_sort()->get_family_id());
|
||||
if (!m.is_eq(e)) {
|
||||
for (auto ch : m_args)
|
||||
for (auto idv : euf::enode_th_vars(*ch))
|
||||
m_egraph.register_shared(n, idv.get_id());
|
||||
}
|
||||
m_todo.pop_back();
|
||||
}
|
||||
}
|
||||
return m_egraph.find(e);
|
||||
}
|
||||
|
||||
|
||||
void fold_unfold::fold(expr *e, expr_ref &result, proof_ref &pr) {
|
||||
m_rewriter(e, result, pr);
|
||||
}
|
||||
|
||||
void fold_unfold::unfold(unsigned n, enode *e, expr_dependency* d, vector<std::pair<expr_ref, expr_dependency*>>& es) {
|
||||
if (n == 0) {
|
||||
es.push_back({expr_ref(e->get_expr(), m), d});
|
||||
return;
|
||||
}
|
||||
if (es.size() > 10)
|
||||
return;
|
||||
unsigned count = 0;
|
||||
for (auto sib : euf::enode_class(e)) {
|
||||
auto sib_e = sib->get_expr();
|
||||
if (!is_app(sib_e))
|
||||
continue;
|
||||
if (is_uninterp_const(sib_e)) {
|
||||
auto f = m_find.get(sib->get_id(), nullptr);
|
||||
if (f && f != sib)
|
||||
continue;
|
||||
}
|
||||
++count;
|
||||
expr_ref_vector args(m);
|
||||
expr_dependency *d1 = m.mk_join(d, explain_eq(sib, e));
|
||||
unfold_arg(n, 0, sib, args, d1, es);
|
||||
if (count > 2)
|
||||
break;
|
||||
}
|
||||
// verbose_stream() << "count " << count << "\n";
|
||||
}
|
||||
|
||||
void fold_unfold::unfold_arg(unsigned n, unsigned i, enode* e, expr_ref_vector& args, expr_dependency* d,
|
||||
vector<std::pair<expr_ref, expr_dependency*>>& es) {
|
||||
if (i == e->num_args()) {
|
||||
es.push_back({expr_ref(m.mk_app(e->get_decl(), args), m), d});
|
||||
return;
|
||||
}
|
||||
vector<std::pair<expr_ref, expr_dependency *>> es_arg;
|
||||
unfold(n - 1, e->get_arg(i), d, es_arg);
|
||||
for (auto [arg, dep] : es_arg) {
|
||||
args.push_back(arg);
|
||||
unfold_arg(n, i + 1, e, args, dep, es);
|
||||
args.pop_back();
|
||||
if (es.size() > 10)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void fold_unfold::insert_subst(expr * v, expr * t, expr_dependency* d) {
|
||||
if (!m_subst)
|
||||
m_subst = alloc(expr_substitution, m, true, false);
|
||||
m_subst->insert(v, t, d);
|
||||
}
|
||||
|
||||
void fold_unfold::apply_subst(vector<dependent_expr> &old_fmls) {
|
||||
if (!m.inc())
|
||||
return;
|
||||
if (!m_subst)
|
||||
return;
|
||||
|
||||
scoped_ptr<expr_replacer> rp = mk_default_expr_replacer(m, false);
|
||||
rp->set_substitution(m_subst.get());
|
||||
|
||||
for (unsigned i : indices()) {
|
||||
auto [f, p, d] = m_fmls[i]();
|
||||
auto [new_f, new_dep] = rp->replace_with_dep(f);
|
||||
proof_ref new_pr(m);
|
||||
expr_ref tmp(m);
|
||||
m_rewriter(new_f, tmp, new_pr);
|
||||
if (tmp == f)
|
||||
continue;
|
||||
new_dep = m.mk_join(d, new_dep);
|
||||
old_fmls.push_back(m_fmls[i]);
|
||||
m_fmls.update(i, dependent_expr(m, tmp, mp(p, new_pr), new_dep));
|
||||
}
|
||||
m_fmls.model_trail().push(m_subst.detach(), old_fmls, false);
|
||||
}
|
||||
|
||||
void fold_unfold::set_levels() {
|
||||
m_node2level.reset();
|
||||
m_level2node.reset();
|
||||
m_level_count = 0;
|
||||
for (auto n : m_egraph.nodes())
|
||||
if (n->is_root())
|
||||
set_level(n);
|
||||
for (auto n : m_egraph.nodes())
|
||||
if (n->is_root())
|
||||
n->unmark1();
|
||||
}
|
||||
|
||||
void fold_unfold::set_level(enode* n) {
|
||||
SASSERT(n->is_root());
|
||||
|
||||
if (m_node2level.get(n->get_id(), UINT_MAX) != UINT_MAX)
|
||||
return;
|
||||
|
||||
if (!n->is_marked1()) {
|
||||
n->mark1();
|
||||
for (auto b : enode_class(n)) {
|
||||
for (auto arg : enode_args(b))
|
||||
set_level(arg->get_root());
|
||||
}
|
||||
}
|
||||
if (m_node2level.get(n->get_id(), UINT_MAX) != UINT_MAX)
|
||||
return;
|
||||
for (auto a : enode_class(n)) {
|
||||
m_node2level.setx(a->get_id(), m_level_count, UINT_MAX);
|
||||
m_level2node.setx(m_level_count, a, nullptr);
|
||||
}
|
||||
++m_level_count;
|
||||
}
|
||||
|
||||
void fold_unfold::reduce_linear() {
|
||||
set_levels();
|
||||
m_subst = alloc(expr_substitution, m, true, false);
|
||||
scoped_ptr<expr_replacer> rp = mk_default_expr_replacer(m, false);
|
||||
rp->set_substitution(m_subst.get());
|
||||
for (auto n : m_level2node) {
|
||||
SASSERT(n);
|
||||
SASSERT(n->is_root());
|
||||
// if a is uninterpreted and is not eliminated,
|
||||
// n is equal to a linear term with lower level argument
|
||||
// back-substitute the linear term using existing subst.
|
||||
// update subst with a -> linear term
|
||||
enode *var = nullptr;
|
||||
enode *term = nullptr;
|
||||
for (auto a : enode_class(n)) {
|
||||
if (m_find.get(a->get_id(), nullptr) != nullptr) // already substituted
|
||||
continue;
|
||||
if (is_uninterp_const(a->get_expr()))
|
||||
var = a;
|
||||
else if (is_linear_term(a))
|
||||
term = a;
|
||||
}
|
||||
if (var && term) {
|
||||
m_find.setx(var->get_id(), term, nullptr); // record that var was replaced
|
||||
auto dep = explain_eq(var, term);
|
||||
auto [new_term, new_dep] = rp->replace_with_dep(term->get_expr());
|
||||
expr_ref r(m);
|
||||
proof_ref pr(m);
|
||||
m_rewriter(new_term, r, pr);
|
||||
m_subst->insert(var->get_expr(), r, m.mk_join(dep, new_dep));
|
||||
}
|
||||
}
|
||||
vector<dependent_expr> old_fmls;
|
||||
apply_subst(old_fmls);
|
||||
}
|
||||
|
||||
bool fold_unfold::is_linear_term(enode *n) {
|
||||
unsigned num_vars = 0;
|
||||
unsigned level = m_node2level[n->get_root_id()];
|
||||
for (auto arg : enode_args(n))
|
||||
if (!m.is_value(arg->get_expr())) {
|
||||
if (m_node2level[arg->get_root_id()] >= level)
|
||||
return false;
|
||||
++num_vars;
|
||||
}
|
||||
return num_vars <= 1;
|
||||
}
|
||||
|
||||
void fold_unfold::updt_params(params_ref const &p) {
|
||||
m_config.m_enabled = true;
|
||||
params_ref p1;
|
||||
p1.set_bool("eliminate_mod", false);
|
||||
for (auto ex : m_extract_plugins) {
|
||||
ex->updt_params(p);
|
||||
ex->updt_params(p1);
|
||||
}
|
||||
}
|
||||
|
||||
void fold_unfold::collect_param_descrs(param_descrs &r) {}
|
||||
|
||||
void fold_unfold::collect_statistics(statistics &st) const {
|
||||
st.update("fold-unfold-steps", m_stats.m_num_steps);
|
||||
st.update("fold-unfold-elim-vars", m_stats.m_num_elim_vars);
|
||||
}
|
||||
|
||||
}
|
||||
108
src/ast/simplifiers/fold_unfold.h
Normal file
108
src/ast/simplifiers/fold_unfold.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
|
||||
/*++
|
||||
Copyright (c) 2022 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
fold_unfold.h
|
||||
|
||||
Abstract:
|
||||
|
||||
fold-unfold simplifier
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2025-11-5.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/scoped_ptr_vector.h"
|
||||
#include "ast/expr_substitution.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include "ast/simplifiers/extract_eqs.h"
|
||||
#include "ast/euf/euf_egraph.h"
|
||||
|
||||
namespace euf {
|
||||
|
||||
class fold_unfold : public dependent_expr_simplifier {
|
||||
friend class solve_context_eqs;
|
||||
|
||||
struct stats {
|
||||
unsigned m_num_steps = 0;
|
||||
unsigned m_num_elim_vars = 0;
|
||||
void reset() {
|
||||
m_num_steps = 0;
|
||||
m_num_elim_vars = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct config {
|
||||
bool m_enabled = true;
|
||||
};
|
||||
|
||||
stats m_stats;
|
||||
config m_config;
|
||||
th_rewriter m_rewriter;
|
||||
egraph m_egraph;
|
||||
scoped_ptr_vector<extract_eq> m_extract_plugins;
|
||||
unsigned_vector m_var2id; // app->get_id() |-> small numeral
|
||||
scoped_ptr<expr_substitution> m_subst; // current substitution
|
||||
vector<std::pair<proof_ref, expr_dependency *>> m_pr_dep;
|
||||
|
||||
void get_eqs(dep_eq_vector &eqs);
|
||||
void extract_subst(bool fuf, dep_eq_vector const &eqs);
|
||||
void insert_subst(expr *v, expr *t, expr_dependency* d);
|
||||
void apply_subst(vector<dependent_expr> &old_fmls);
|
||||
void reduce_alias(bool fuf);
|
||||
void reduce_linear();
|
||||
|
||||
size_t *to_ptr(size_t i) const {
|
||||
return reinterpret_cast<size_t *>(i);
|
||||
}
|
||||
unsigned from_ptr(size_t *s) const {
|
||||
return (unsigned)reinterpret_cast<size_t>(s);
|
||||
}
|
||||
unsigned push_pr_dep(proof *pr, expr_dependency *d);
|
||||
expr_dependency *explain_eq(enode *a, enode *b);
|
||||
|
||||
ptr_vector<expr> m_todo;
|
||||
enode_vector m_args, m_find;
|
||||
unsigned_vector m_node2level;
|
||||
enode_vector m_level2node;
|
||||
unsigned m_level_count = 0;
|
||||
|
||||
void set_levels();
|
||||
void set_level(enode *n);
|
||||
bool is_linear_term(enode *n);
|
||||
|
||||
unsigned m_generation = 0;
|
||||
unsigned m_th_var = 0;
|
||||
enode *mk_enode(expr *e);
|
||||
|
||||
void fold(expr *e, expr_ref &result, proof_ref &pr);
|
||||
void unfold(unsigned n, enode *e, expr_dependency* d, vector<std::pair<expr_ref, expr_dependency *>> &es);
|
||||
void unfold_arg(unsigned n, unsigned i, enode *e, expr_ref_vector &args, expr_dependency *d,
|
||||
vector<std::pair<expr_ref, expr_dependency *>> &es);
|
||||
|
||||
public:
|
||||
fold_unfold(ast_manager &m, dependent_expr_state &fmls);
|
||||
|
||||
char const *name() const override {
|
||||
return "fold-unfold";
|
||||
}
|
||||
|
||||
void reduce() override;
|
||||
|
||||
void updt_params(params_ref const &p) override;
|
||||
|
||||
void collect_param_descrs(param_descrs &r) override;
|
||||
|
||||
void collect_statistics(statistics &st) const override;
|
||||
|
||||
void reset_statistics() override {
|
||||
m_stats.reset();
|
||||
}
|
||||
};
|
||||
} // namespace euf
|
||||
|
|
@ -121,7 +121,10 @@ namespace euf {
|
|||
continue;
|
||||
|
||||
if (!m_config.m_enable_non_ground && has_quantifiers(t))
|
||||
continue;
|
||||
continue;
|
||||
|
||||
if (!m_config.m_enable_non_linear && !is_linear(t))
|
||||
continue;
|
||||
|
||||
bool is_safe = true;
|
||||
unsigned todo_sz = todo.size();
|
||||
|
|
@ -241,10 +244,12 @@ namespace euf {
|
|||
unsigned count = 0;
|
||||
vector<dependent_expr> old_fmls;
|
||||
dep_eq_vector eqs;
|
||||
auto _reset_unsafe = on_scope_exit([&]() { m_unsafe_vars.reset(); });
|
||||
do {
|
||||
old_fmls.reset();
|
||||
m_subst_ids.reset();
|
||||
eqs.reset();
|
||||
filter_unsafe_vars();
|
||||
get_eqs(eqs);
|
||||
extract_dep_graph(eqs);
|
||||
extract_subst();
|
||||
|
|
@ -262,6 +267,7 @@ namespace euf {
|
|||
old_fmls.reset();
|
||||
m_subst_ids.reset();
|
||||
eqs.reset();
|
||||
filter_unsafe_vars();
|
||||
solve_context_eqs context_solve(*this);
|
||||
context_solve.collect_nested_equalities(eqs);
|
||||
extract_dep_graph(eqs);
|
||||
|
|
@ -313,6 +319,15 @@ namespace euf {
|
|||
return num <= m_config.m_max_occs;
|
||||
}
|
||||
|
||||
bool solve_eqs::is_linear(expr* t) const {
|
||||
unsigned num_values = 0;
|
||||
if (!is_app(t))
|
||||
return false;
|
||||
for (auto arg : *to_app(t))
|
||||
num_values += m.is_value(arg) ? 0 : 1;
|
||||
return num_values <= 1;
|
||||
}
|
||||
|
||||
void solve_eqs::save_subst(vector<dependent_expr> const& old_fmls) {
|
||||
if (!m_subst->empty())
|
||||
m_fmls.model_trail().push(m_subst.detach(), old_fmls, false);
|
||||
|
|
@ -322,7 +337,7 @@ namespace euf {
|
|||
m_unsafe_vars.reset();
|
||||
recfun::util rec(m);
|
||||
for (func_decl* f : rec.get_rec_funs())
|
||||
for (expr* term : subterms::all(expr_ref(rec.get_def(f).get_rhs(), m), &m_todo, &m_visited))
|
||||
for (expr* term : subterms::all(expr_ref(rec.get_def(f).get_rhs(), m)))
|
||||
m_unsafe_vars.mark(term);
|
||||
}
|
||||
|
||||
|
|
@ -342,6 +357,7 @@ namespace euf {
|
|||
smt_params_helper sp(p);
|
||||
m_config.m_enabled = sp.solve_eqs();
|
||||
m_config.m_enable_non_ground = sp.solve_eqs_non_ground();
|
||||
m_config.m_enable_non_linear = !sp.solve_eqs_linear();
|
||||
}
|
||||
|
||||
void solve_eqs::collect_param_descrs(param_descrs& r) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ namespace euf {
|
|||
unsigned m_max_occs = UINT_MAX;
|
||||
bool m_enabled = true;
|
||||
bool m_enable_non_ground = true;
|
||||
bool m_enable_non_linear = true;
|
||||
};
|
||||
|
||||
stats m_stats;
|
||||
|
|
@ -74,6 +75,7 @@ namespace euf {
|
|||
void collect_num_occs(expr * t, expr_fast_mark1 & visited);
|
||||
void collect_num_occs();
|
||||
bool check_occs(expr* t) const;
|
||||
bool is_linear(expr *t) const;
|
||||
|
||||
public:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue