3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-06-10 19:07:18 +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

@ -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);
}

View file

@ -80,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 +107,8 @@ class array_decl_plugin : public decl_plugin {
func_decl * mk_set_subset(unsigned arity, sort * const * domain);
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:
@ -165,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); }
@ -173,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); }
@ -309,6 +314,10 @@ public:
return m_manager.mk_app(m_fid, OP_AS_ARRAY, 1, &param, 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);
@ -319,4 +328,3 @@ public:
};