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

Add SMT-LIB choice support via array OP_CHOICE and instantiate choice axioms in array solvers (#9649)

This change wires SMT-LIB Hilbert choice parsing to a concrete
array-theory operator and ensures both array backends enforce the
expected semantic axiom. Previously, `(choice ((x T)) phi)` parsed as
NYI and had no solver-side instantiation path.

- **Parser: lower `choice_k` into array `OP_CHOICE`**
- `pop_quant_frame(choice_k)` now builds `(choice p)` instead of
throwing.
- Added parser include/use of array utilities to construct the term
directly from the generated lambda predicate.

- **Array decl plugin: add `OP_CHOICE` typing + surface syntax**
  - Added declaration support for `choice` with signature:
- `(Array T Bool) -> T` (encoded as `('a -> Bool) -> 'a` in HO view).
- Added recognizer/util helpers (`is_choice`, `mk_choice`) and exposed
`"choice"` in op names.

- **SMT array theory (`theory_array_full`): instantiate choice axiom**
  - Added instantiation for each encountered `choice(p)`:
    - `forall x . p(x) => p(choice(p))`
  - Integrated into internalization/relevancy paths and statistics.

- **SAT/SMT array backend (`sat/smt/array_*`): instantiate choice
axiom**
- Added new axiom record kind for choice, internalization hook,
assertion routine, and diagnostics/stat tracking.
  - Uses the same quantified implication schema as above.

- **Regression coverage**
- Extended SMT2 parser regression with an HO `choice` example to ensure
parser/eval pipeline accepts and processes choice terms.

Example of the now-supported input:

```smt2
(set-logic HO_ALL)
(declare-sort U 0)
(declare-fun P () (-> U Bool))
(assert (exists ((x U)) (P x)))
(assert (= witness (choice ((x U)) (P x))))
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot 2026-05-27 10:05:06 -07:00 committed by GitHub
parent 690cdd3f25
commit 51da9db615
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 124 additions and 17 deletions

View file

@ -28,7 +28,7 @@ namespace smt {
unsigned m_num_axiom1, m_num_axiom2a, m_num_axiom2b, m_num_extensionality, m_num_eq_splits;
unsigned m_num_map_axiom, m_num_default_map_axiom;
unsigned m_num_select_const_axiom, m_num_default_store_axiom, m_num_default_const_axiom, m_num_default_as_array_axiom;
unsigned m_num_select_as_array_axiom, m_num_default_lambda_axiom;
unsigned m_num_select_as_array_axiom, m_num_default_lambda_axiom, m_num_choice_axiom;
void reset() { memset(this, 0, sizeof(theory_array_stats)); }
theory_array_stats() { reset(); }
};
@ -115,4 +115,3 @@ namespace smt {
};

View file

@ -43,6 +43,7 @@ namespace smt {
bool is_const(app const* n) const { return n->is_app_of(get_id(), OP_CONST_ARRAY); }
bool is_array_ext(app const * n) const { return n->is_app_of(get_id(), OP_ARRAY_EXT); }
bool is_as_array(app const * n) const { return n->is_app_of(get_id(), OP_AS_ARRAY); }
bool is_choice(app const* n) const { return 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(app const* n) const { return is_array_sort(n->get_sort()); }
@ -51,6 +52,7 @@ namespace smt {
bool is_select(enode const* n) const { return is_select(n->get_expr()); }
bool is_const(enode const* n) const { return is_const(n->get_expr()); }
bool is_as_array(enode const * n) const { return is_as_array(n->get_expr()); }
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_expr()); }
bool is_select_arg(enode* r);
@ -208,4 +210,3 @@ namespace smt {
};

View file

@ -271,7 +271,7 @@ namespace smt {
return theory_array::internalize_term(n);
}
if (!is_const(n) && !is_default(n) && !is_map(n) && !is_as_array(n)) {
if (!is_const(n) && !is_default(n) && !is_map(n) && !is_as_array(n) && !is_choice(n)) {
if (!is_array_ext(n))
found_unsupported_op(n);
return false;
@ -329,6 +329,9 @@ 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)));
@ -406,7 +409,7 @@ namespace smt {
void theory_array_full::relevant_eh(app* n) {
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)){
if (!is_default(n) && !is_select(n) && !is_map(n) && !is_const(n) && !is_as_array(n) && !is_choice(n)){
return;
}
ctx.ensure_internalized(n);
@ -442,6 +445,9 @@ namespace smt {
else if (is_as_array(n)) {
instantiate_default_as_array_axiom(node);
}
else if (is_choice(n)) {
instantiate_choice_axiom(node);
}
}
bool theory_array_full::should_research(expr_ref_vector & unsat_core) {
@ -596,6 +602,32 @@ namespace smt {
return try_assign_eq(val.get(), def);
}
bool theory_array_full::instantiate_choice_axiom(enode* ch) {
if (!ctx.add_fingerprint(this, m_choice_fingerprint, 1, &ch))
return false;
++m_stats.m_num_choice_axiom;
SASSERT(is_choice(ch));
app* choice_term = ch->get_expr();
expr* pred = choice_term->get_arg(0);
sort* pred_sort = pred->get_sort();
SASSERT(is_array_sort(pred_sort));
SASSERT(get_array_arity(pred_sort) == 1);
SASSERT(m.is_bool(get_array_range(pred_sort)));
sort* x_sort = get_array_domain(pred_sort, 0);
expr_ref x(m.mk_var(0, x_sort), m);
expr* args1[2] = { pred, x };
expr_ref px(mk_select(2, args1), m);
expr* args2[2] = { pred, choice_term };
expr_ref pc(mk_select(2, args2), m);
expr_ref ax(m.mk_implies(px, pc), m);
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);
return true;
}
//
// Assert axiom:
// select(const v, i_1, ..., i_n) = v
@ -839,5 +871,6 @@ namespace smt {
st.update("array def as-array", m_stats.m_num_default_as_array_axiom);
st.update("array sel as-array", m_stats.m_num_select_as_array_axiom);
st.update("array def lambda", m_stats.m_num_default_lambda_axiom);
st.update("array choice ax", m_stats.m_num_choice_axiom);
}
}

View file

@ -43,6 +43,7 @@ namespace smt {
static unsigned const m_default_const_fingerprint = UINT_MAX - 115;
static unsigned const m_default_as_array_fingerprint = UINT_MAX - 116;
static unsigned const m_default_lambda_fingerprint = UINT_MAX - 117;
static unsigned const m_choice_fingerprint = UINT_MAX - 118;
protected:
@ -80,6 +81,7 @@ namespace smt {
bool instantiate_default_map_axiom(enode* map);
bool instantiate_default_as_array_axiom(enode* arr);
bool instantiate_default_lambda_def_axiom(enode* arr);
bool instantiate_choice_axiom(enode* ch);
bool instantiate_parent_stores_default(theory_var v);
@ -112,4 +114,3 @@ namespace smt {
};