3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-06-05 08:30:50 +00:00

move closure conversion to solver internalization

- only the internalizer performs closure conversion
- theory_array treats propagation of lambdas similar to stores
- ho_matcher treats top-level flex patterns as first-order
- pattern-inference fix to handle quantifiers (lambdas) in patterns that are computed
This commit is contained in:
Nikolaj Bjorner 2026-05-30 18:41:37 -07:00
parent 2cc4422018
commit dbe986fdf7
10 changed files with 97 additions and 87 deletions

View file

@ -598,19 +598,7 @@ namespace smt {
if (e_internalized(q))
return;
app_ref lam_name(m.mk_fresh_const("lambda", q->get_sort()), m);
app_ref eq(m), lam_app(m);
expr_ref_vector vars(m);
vars.push_back(lam_name);
unsigned sz = q->get_num_decls();
for (unsigned i = 0; i < sz; ++i)
vars.push_back(m.mk_var(sz - i - 1, q->get_decl_sort(i)));
array_util autil(m);
lam_app = autil.mk_select(vars.size(), vars.data());
eq = m.mk_eq(lam_app, q->get_expr());
quantifier_ref fa(m);
expr * patterns[1] = { m.mk_pattern(lam_app) };
fa = m.mk_forall(sz, q->get_decl_sorts(), q->get_decl_names(), eq, 0, m.lambda_def_qid(), symbol::null, 1, patterns);
internalize_quantifier(fa, true);
m.add_lambda_def(lam_name->get_decl(), q);
if (!e_internalized(lam_name))
internalize_uninterpreted(lam_name);
enode* lam_node = get_enode(lam_name);
@ -619,9 +607,6 @@ namespace smt {
m_app2enode.setx(q->get_id(), lam_node, nullptr);
m_l_internalized_stack.push_back(q);
m_trail_stack.push_ptr(&m_mk_lambda_trail);
bool_var bv = get_bool_var(fa);
assign(literal(bv, false), nullptr);
mark_as_relevant(bv);
}
bool context::has_lambda() {

View file

@ -68,12 +68,12 @@ namespace smt {
m_var_data.push_back(alloc(var_data));
var_data * d = m_var_data[r];
TRACE(array, tout << mk_bounded_pp(n->get_expr(), m) << "\nis_array: " << is_array_sort(n) << ", is_select: " << is_select(n) <<
", is_store: " << is_store(n) << "\n";);
", is_store: " << is_store(n) << ", is_lambda: " << is_lambda(n) << "\n";);
d->m_is_array = is_array_sort(n);
if (d->m_is_array)
register_sort(n->get_expr()->get_sort());
d->m_is_select = is_select(n);
if (is_store(n))
if (is_store(n) || is_lambda(n))
d->m_stores.push_back(n);
ctx.attach_th_var(n, this, r);
if (laziness() <= 1 && is_store(n))
@ -95,7 +95,7 @@ namespace smt {
if (!m_params.m_array_delay_exp_axiom && d->m_prop_upward) {
for (enode* store : d->m_parent_stores) {
SASSERT(is_store(store));
SASSERT(is_store(store) || is_lambda(store));
if (!m_params.m_array_cg || store->is_cgr()) {
instantiate_axiom2b(s, store);
}
@ -106,7 +106,7 @@ namespace smt {
void theory_array::add_parent_store(theory_var v, enode * s) {
if (m_params.m_array_cg && !s->is_cgr())
return;
SASSERT(is_store(s));
SASSERT(is_store(s) || is_lambda(s));
v = find(v);
var_data * d = m_var_data[v];
d->m_parent_stores.push_back(s);
@ -177,7 +177,7 @@ namespace smt {
void theory_array::add_store(theory_var v, enode * s) {
if (m_params.m_array_cg && !s->is_cgr())
return;
SASSERT(is_store(s));
SASSERT(is_store(s) || is_lambda(s));
v = find(v);
var_data * d = m_var_data[v];
unsigned lambda_equiv_class_size = get_lambda_equiv_size(v, d);
@ -204,7 +204,7 @@ namespace smt {
void theory_array::instantiate_axiom2a(enode * select, enode * store) {
TRACE(array, tout << "axiom 2a: #" << select->get_owner_id() << " #" << store->get_owner_id() << "\n";);
SASSERT(is_select(select));
SASSERT(is_store(store));
SASSERT(is_store(store) || is_lambda(store));
if (assert_store_axiom2(store, select))
m_stats.m_num_axiom2a++;
}
@ -212,7 +212,7 @@ namespace smt {
bool theory_array::instantiate_axiom2b(enode * select, enode * store) {
TRACE(array_axiom2b, tout << "axiom 2b: #" << select->get_owner_id() << " #" << store->get_owner_id() << "\n";);
SASSERT(is_select(select));
SASSERT(is_store(store));
SASSERT(is_store(store) || is_lambda(store));
if (assert_store_axiom2(store, select)) {
m_stats.m_num_axiom2b++;
return true;
@ -261,7 +261,7 @@ namespace smt {
}
bool theory_array::internalize_term(app * n) {
if (!is_store(n) && !is_select(n)) {
if (!is_store(n) && !is_select(n) && !is_lambda(n)) {
if (!is_array_ext(n))
found_unsupported_op(n);
return false;
@ -282,7 +282,7 @@ namespace smt {
if (is_select(n)) {
add_parent_select(v_arg, ctx.get_enode(n));
}
else if (is_store(n)) {
else if (is_store(n) || is_lambda(n)) {
add_parent_store(v_arg, ctx.get_enode(n));
}
}
@ -325,9 +325,11 @@ namespace smt {
if (m.is_ite(n)) {
TRACE(array, tout << "relevant ite " << mk_pp(n, m) << "\n";);
}
if (!is_store(n) && !is_select(n))
if (!is_store(n) && !is_select(n) && !is_lambda(n))
return;
if (!ctx.e_internalized(n)) ctx.internalize(n, false);
if (is_lambda(n))
return;
enode * arg = ctx.get_enode(to_app(n)->get_arg(0));
theory_var v_arg = arg->get_th_var(get_id());
SASSERT(v_arg != null_theory_var);

View file

@ -217,18 +217,41 @@ namespace smt {
if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n";
}
}
void theory_array_base::assert_lambda_axiom_core(enode* n, enode* select) {
SASSERT(is_lambda(n));
SASSERT(is_select(select));
expr *e = n->get_expr();
app *s = select->get_app();
auto q = is_quantifier(e) ? to_quantifier(e) : m.is_lambda_def(e);
SASSERT(q);
SASSERT(::is_lambda(q));
SASSERT(q->get_num_decls() == s->get_num_args() - 1);
// do the same thing as in sat/smt/array_axioms:
ptr_vector<expr> args(s->get_num_args(), s->get_args());
args[0] = q;
array_util a(m);
expr_ref alpha(a.mk_select(args), m);
expr_ref beta(alpha);
ctx.get_rewriter()(beta);
TRACE(array, tout << alpha << " == " << beta << "\n";);
auto alpha_n = ensure_enode(alpha);
auto beta_n = ensure_enode(beta);
ctx.assign_eq(alpha_n, beta_n, eq_justification::mk_axiom());
}
bool theory_array_base::assert_store_axiom2(enode * store, enode * select) {
SASSERT(is_store(store) || is_lambda(store));
unsigned num_args = select->get_num_args();
unsigned i = 1;
for (; i < num_args; ++i)
if (store->get_arg(i)->get_root() != select->get_arg(i)->get_root())
if (is_store(store) && store->get_arg(i)->get_root() != select->get_arg(i)->get_root())
break;
if (i == num_args)
return false;
if (ctx.add_fingerprint(store, store->get_owner_id(), select->get_num_args() - 1, select->get_args() + 1)) {
TRACE(array, tout << "adding axiom2 to todo queue\n";);
m_axiom2_todo.push_back(std::make_pair(store, select));
m_axiom2_todo.push_back({store, select});
return true;
}
TRACE(array, tout << "axiom already instantiated: #" << store->get_owner_id() << " #" << select->get_owner_id() << "\n";);
@ -426,7 +449,10 @@ namespace smt {
m_axiom1_todo.reset();
for (unsigned i = 0; i < m_axiom2_todo.size(); ++i) {
auto [store, select] = m_axiom2_todo[i];
assert_store_axiom2_core(store, select);
if (is_store(store))
assert_store_axiom2_core(store, select);
else
assert_lambda_axiom_core(store, select);
}
m_axiom2_todo.reset();
for (unsigned i = 0; i < m_extensionality_todo.size(); ++i) {

View file

@ -46,6 +46,10 @@ namespace smt {
bool is_choice(expr const* n) const { return is_app(n) && to_app(n)->is_app_of(get_id(), OP_CHOICE); }
bool is_array_sort(sort const* s) const { return s->is_sort_of(get_id(), ARRAY_SORT); }
bool is_array_sort(expr const* n) const { return is_array_sort(n->get_sort()); }
bool is_lambda(expr *n) const {
return m.is_lambda_def(n) || ::is_lambda(n);
}
bool is_store(enode const * n) const { return is_store(n->get_expr()); }
bool is_map(enode const* n) const { return is_map(n->get_expr()); }
@ -55,6 +59,12 @@ namespace smt {
bool is_choice(enode const* n) const { return is_choice(n->get_expr()); }
bool is_default(enode const* n) const { return is_default(n->get_expr()); }
bool is_array_sort(enode const* n) const { return is_array_sort(n->get_sort()); }
bool is_lambda(enode const *n) const {
return is_lambda(n->get_expr());
}
bool is_select_arg(enode* r);
app * mk_select(unsigned num_args, expr * const * args);
@ -76,6 +86,7 @@ namespace smt {
void assert_axiom(literal l);
void assert_store_axiom1_core(enode * n);
void assert_store_axiom2_core(enode * store, enode * select);
void assert_lambda_axiom_core(enode *lambda, enode *select);
void assert_store_axiom1(enode * n) { m_axiom1_todo.push_back(n); }
bool assert_store_axiom2(enode * store, enode * select);

View file

@ -248,7 +248,7 @@ namespace smt {
instantiate_default_as_array_axiom(n);
d->m_as_arrays.push_back(n);
}
else if (m.is_lambda_def(n->get_expr())) {
else if (is_lambda(n)) {
instantiate_default_lambda_def_axiom(n);
d->m_lambdas.push_back(n);
m_lambdas.push_back(n);
@ -329,9 +329,6 @@ namespace smt {
ctx.push_trail(push_back_vector(m_as_array));
instantiate_default_as_array_axiom(node);
}
else if (is_choice(n)) {
instantiate_choice_axiom(node);
}
else if (is_array_ext(n)) {
SASSERT(n->get_num_args() == 2);
instantiate_extensionality(ctx.get_enode(n->get_arg(0)), ctx.get_enode(n->get_arg(1)));
@ -371,8 +368,8 @@ namespace smt {
TRACE(array, tout << "v" << v << " " << pp(get_enode(v), m) << " "
<< d->m_prop_upward << " " << m_params.m_array_delay_exp_axiom << "\n";);
for (enode * store : d->m_stores) {
SASSERT(is_store(store));
instantiate_default_store_axiom(store);
if (is_store(store))
instantiate_default_store_axiom(store);
}
if (!m_params.m_array_delay_exp_axiom && d->m_prop_upward) {
@ -410,7 +407,7 @@ namespace smt {
TRACE(array, tout << mk_pp(n, m) << "\n";);
theory_array::relevant_eh(n);
if (!is_default(n) && !is_select(n) && !is_map(n) &&
!is_const(n) && !is_as_array(n) && !is_choice(n) && !is_lambda(n)){
!is_const(n) && !is_as_array(n) && !is_choice(n)) {
return;
}
ctx.ensure_internalized(n);
@ -447,10 +444,8 @@ namespace smt {
instantiate_default_as_array_axiom(node);
}
else if (is_choice(n)) {
instantiate_choice_axiom(node);
}
else if (is_lambda(n)) {
NOT_IMPLEMENTED_YET();
m_choice_terms.push_back(node);
ctx.push_trail(push_back_vector(m_choice_terms));
}
}
@ -585,7 +580,7 @@ namespace smt {
m_stats.m_num_default_lambda_axiom++;
expr* e = arr->get_expr();
expr_ref def(mk_default(e), m);
quantifier* lam = m.is_lambda_def(e);
quantifier* lam = is_quantifier(e) ? to_quantifier(e) : m.is_lambda_def(e);
TRACE(array, tout << mk_pp(lam, m) << "\n" << mk_pp(e, m) << "\n");
expr_ref_vector args(m);
var_subst subst(m, false);
@ -627,8 +622,9 @@ namespace smt {
symbol x_name("x");
expr_ref q(m.mk_forall(1, &x_sort, &x_name, ax), m);
ctx.get_rewriter()(q);
literal l = mk_literal(q);
assert_axiom(l);
TRACE(array, tout << "choice " << q << "\n");
ctx.assert_expr(q);
ctx.internalize_assertions();
return true;
}
@ -783,6 +779,16 @@ namespace smt {
return {eps, diag};
}
void theory_array_full::propagate() {
if (m_choice_qhead == m_choice_terms.size())
return;
ctx.push_trail(value_trail(m_choice_qhead));
for (; m_choice_qhead < m_choice_terms.size(); ++m_choice_qhead) {
enode *choice = m_choice_terms[m_choice_qhead];
instantiate_choice_axiom(choice);
}
}
final_check_status theory_array_full::assert_delayed_axioms() {
final_check_status r = FC_DONE;
if (!m_params.m_array_delay_exp_axiom) {

View file

@ -37,6 +37,8 @@ namespace smt {
ast2ast_trailmap<sort, app> m_sort2epsilon;
ast2ast_trailmap<sort, func_decl> m_sort2diag;
obj_pair_map<expr, expr, bool> m_eqs;
enode_vector m_choice_terms;
unsigned m_choice_qhead = 0;
static unsigned const m_default_map_fingerprint = UINT_MAX - 112;
static unsigned const m_default_store_fingerprint = UINT_MAX - 113;
@ -111,6 +113,8 @@ namespace smt {
void merge_eh(theory_var v1, theory_var v2, theory_var, theory_var) override;
void display_var(std::ostream & out, theory_var v) const override;
void collect_statistics(::statistics & st) const override;
bool can_propagate() override { return theory_array::can_propagate() || m_choice_qhead < m_choice_terms.size(); }
void propagate() override;
};
};