3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-05-25 19:36:20 +00:00

prepare for lambda unfolding in ho-matcher and selectively enable ho matching

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2026-05-22 13:24:19 -07:00
parent f40e4759e4
commit 19166bd0b5
7 changed files with 75 additions and 5 deletions

View file

@ -242,17 +242,72 @@ namespace euf {
// We assume that m_rewriter should produce // We assume that m_rewriter should produce
// something amounting to weak-head normal form WHNF // something amounting to weak-head normal form WHNF
// Unfold a lambda-def application f(args) to the corresponding lambda expression.
// For a func_decl f with arity n and lambda-def quantifier (lambda (x1..xk) body),
// f(a1,...,an) is unfolded to (lambda (x1..xk) body[params := a1..an]).
// For a constant f (arity 0) that is a lambda-def, returns the lambda directly.
expr_ref ho_matcher::unfold_lambda_def(expr* e) const {
if (!is_app(e))
return expr_ref(e, m);
app* a = to_app(e);
func_decl* f = a->get_decl();
quantifier* lam = m.is_lambda_def(f);
if (!lam)
return expr_ref(e, m);
unsigned arity = f->get_arity();
SASSERT(is_lambda(lam));
if (arity == 0) {
// Constant lambda-def: just return the lambda expression
return expr_ref(lam, m);
}
// f(a1,...,an) where lam = lambda (x1..xk) body
// The lambda body uses var(0)..var(k-1) for the lambda-bound vars
// and var(k)..var(k+n-1) for the function parameters.
// We substitute the function parameters with the actual arguments.
unsigned num_lambda_decls = lam->get_num_decls();
expr_ref body(lam->get_expr(), m);
// Build substitution: var(num_lambda_decls + i) -> a->get_arg(arity - 1 - i) for i in [0, arity)
// var_subst replaces var(i) with subst[i]
expr_ref_vector subst(m);
subst.resize(num_lambda_decls + arity);
for (unsigned i = 0; i < num_lambda_decls; ++i)
subst[i] = m.mk_var(i, lam->get_decl_sort(num_lambda_decls - 1 - i));
for (unsigned i = 0; i < arity; ++i)
subst[num_lambda_decls + i] = a->get_arg(arity - 1 - i);
var_subst vs(m, false);
body = vs(lam->get_expr(), subst);
// Rebuild the lambda with the new body
ptr_buffer<sort> sorts;
vector<symbol> names;
for (unsigned i = 0; i < num_lambda_decls; ++i) {
sorts.push_back(lam->get_decl_sort(i));
names.push_back(lam->get_decl_name(i));
}
return expr_ref(m.mk_lambda(num_lambda_decls, sorts.data(), names.data(), body), m);
}
void ho_matcher::reduce(match_goal& wi) { void ho_matcher::reduce(match_goal& wi) {
while (true) { while (true) {
expr_ref r = whnf(wi.pat, wi.pat_offset()); expr_ref r = whnf(wi.pat, wi.pat_offset());
if (r == wi.pat)
r = unfold_lambda_def(wi.pat);
if (r == wi.pat) if (r == wi.pat)
break; break;
IF_VERBOSE(3, verbose_stream() << "ho_matcher::reduce: " << wi.pat << " -> " << r << "\n";); IF_VERBOSE(3, verbose_stream() << "ho_matcher::reduce: " << wi.pat << " -> " << r << "\n";);
wi.pat = r; wi.pat = r;
} }
while (true) { while (true) {
expr_ref r = whnf(wi.t, wi.term_offset()); expr_ref r = whnf(wi.t, wi.term_offset());
if (r == wi.t)
r = unfold_lambda_def(wi.t);
if (r == wi.t) if (r == wi.t)
break; break;
IF_VERBOSE(3, verbose_stream() << "ho_matcher::reduce: " << wi.t << " -> " << r << "\n";); IF_VERBOSE(3, verbose_stream() << "ho_matcher::reduce: " << wi.t << " -> " << r << "\n";);
@ -656,16 +711,17 @@ namespace euf {
todo.pop_back(); todo.pop_back();
continue; continue;
} }
if (m_unitary.is_flex(0, t)) { if (m_unitary.is_flex(0, t) || (is_app(t) && m.is_lambda_def(to_app(t)->get_decl())) || is_lambda(t)) {
m_pat2abs.insert_if_not_there(p, svector<std::pair<unsigned, expr*>>()).push_back({ nb, t }); 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()); auto v = m.mk_var(nb++, t->get_sort());
bound.push_back(v); bound.push_back(v);
cache.setx(t->get_id(), v); cache.setx(t->get_id(), v);
todo.pop_back(); todo.pop_back();
continue; continue;
} }
if (is_app(t)) { if (is_app(t)) {
auto a = to_app(t); auto a = to_app(t);
unsigned sz = a->get_num_args(); unsigned sz = a->get_num_args();
ptr_buffer<expr> args; ptr_buffer<expr> args;
for (auto arg : *a) { for (auto arg : *a) {
@ -737,7 +793,7 @@ namespace euf {
trail().push(insert_map(m_pat2abs, p)); trail().push(insert_map(m_pat2abs, p));
trail().push(insert_map(m_q2hoq, q)); trail().push(insert_map(m_q2hoq, q));
trail().push(insert_map(m_hoq2q, q1)); trail().push(insert_map(m_hoq2q, q1));
trail().push(insert_map(m_hopat2free_vars, p1)); trail().push(insert_map(m_hopat2free_vars, p1));
return { q1, p1 }; return { q1, p1 };
} }

View file

@ -350,6 +350,8 @@ namespace euf {
void reduce(match_goal& wi); void reduce(match_goal& wi);
expr_ref unfold_lambda_def(expr* e) const;
trail_stack& trail() { return m_trail; } trail_stack& trail() { return m_trail; }
std::ostream& display(std::ostream& out) const; std::ostream& display(std::ostream& out) const;
@ -395,5 +397,13 @@ namespace euf {
quantifier* hoq2q(quantifier* q) const { return m_hoq2q[q]; } 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;
}
}; };
} }

View file

@ -546,13 +546,13 @@ void pattern_inference_cfg::reset_pre_patterns() {
bool pattern_inference_cfg::is_forbidden(app * n) const { 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)) if (is_ground(n))
return false; return false;
// Remark: skolem constants should not be used in patterns, since they do not // Remark: skolem constants should not be used in patterns, since they do not
// occur outside of the quantifier. That is, Z3 will never match this kind of // occur outside of the quantifier. That is, Z3 will never match this kind of
// pattern. // pattern.
if (m_params.m_pi_avoid_skolems && decl->is_skolem()) { if (m_params.m_pi_avoid_skolems && decl->is_skolem() && !m.is_lambda_def(decl)) {
CTRACE(pattern_inference_skolem, decl->is_skolem(), tout << "ignoring: " << mk_pp(n, m) << "\n";); CTRACE(pattern_inference_skolem, decl->is_skolem(), tout << "ignoring: " << mk_pp(n, m) << "\n";);
return true; return true;
} }

View file

@ -27,6 +27,7 @@ void smt_params::updt_local_params(params_ref const & _p) {
m_random_seed = p.random_seed(); m_random_seed = p.random_seed();
m_relevancy_lvl = p.relevancy(); m_relevancy_lvl = p.relevancy();
m_ematching = p.ematching(); m_ematching = p.ematching();
m_ho_matching = p.ho_matching();
m_induction = p.induction(); m_induction = p.induction();
m_clause_proof = p.clause_proof(); m_clause_proof = p.clause_proof();
m_phase_selection = static_cast<phase_selection>(p.phase_selection()); m_phase_selection = static_cast<phase_selection>(p.phase_selection());

View file

@ -109,6 +109,7 @@ struct smt_params : public preprocessor_params,
bool m_display_features = false; bool m_display_features = false;
bool m_new_core2th_eq = true; bool m_new_core2th_eq = true;
bool m_ematching = true; bool m_ematching = true;
bool m_ho_matching = false;
bool m_induction = false; bool m_induction = false;
bool m_clause_proof = false; bool m_clause_proof = false;
symbol m_proof_log; symbol m_proof_log;

View file

@ -10,6 +10,7 @@ def_module_params(module_name='smt',
('quasi_macros', BOOL, False, 'try to find universally quantified formulas that are quasi-macros'), ('quasi_macros', BOOL, False, 'try to find universally quantified formulas that are quasi-macros'),
('restricted_quasi_macros', BOOL, False, 'try to find universally quantified formulas that are restricted quasi-macros'), ('restricted_quasi_macros', BOOL, False, 'try to find universally quantified formulas that are restricted quasi-macros'),
('ematching', BOOL, True, 'E-Matching based quantifier instantiation'), ('ematching', BOOL, True, 'E-Matching based quantifier instantiation'),
('ho_matching', BOOL, False, 'higher-order matching for quantifier instantiation'),
('phase_selection', UINT, 3, 'phase selection heuristic: 0 - always false, 1 - always true, 2 - phase caching, 3 - phase caching conservative, 4 - phase caching conservative 2, 5 - random, 6 - number of occurrences, 7 - theory'), ('phase_selection', UINT, 3, 'phase selection heuristic: 0 - always false, 1 - always true, 2 - phase caching, 3 - phase caching conservative, 4 - phase caching conservative 2, 5 - random, 6 - number of occurrences, 7 - theory'),
('phase_caching_on', UINT, 400, 'number of conflicts while phase caching is on'), ('phase_caching_on', UINT, 400, 'number of conflicts while phase caching is on'),
('phase_caching_off', UINT, 100, 'number of conflicts while phase caching is off'), ('phase_caching_off', UINT, 100, 'number of conflicts while phase caching is off'),

View file

@ -56,6 +56,7 @@ X(ctx_propagate_assertions, assert_eq_bug, "assert eq bug")
X(ctx_solver_simplify_tactic, ctx_solver_simplify_tactic, "ctx solver simplify tactic") X(ctx_solver_simplify_tactic, ctx_solver_simplify_tactic, "ctx solver simplify tactic")
X(default_qm_plugin, default_qm_plugin, "default qm plugin") X(default_qm_plugin, default_qm_plugin, "default qm plugin")
X(default_qm_plugin, ho_matching, "ho matching")
X(default_qm_plugin, mam_stats, "mam stats") X(default_qm_plugin, mam_stats, "mam stats")
X(default_qm_plugin, quantifier, "quantifier") X(default_qm_plugin, quantifier, "quantifier")