While working on https://github.com/Z3Prover/z3/pull/9405, I noticed
that euf_mam.cpp code was slightly out of sync with mam.cpp and did some
redundant work.
Co-authored-by: Can Cebeci <t-cancebeci@microsoft.com>
https://github.com/Z3Prover/z3/pull/9405 made the trace report
used_enodes incorrectly, since the previous code relied on
update_max_generation to maintain the relevant data structure. This
should fix it.
Co-authored-by: Can Cebeci <t-cancebeci@microsoft.com>
Cleans up dead code left by the "remove side definitions" refactoring
(a0a3047).
- **`smt_model_checker.cpp`** — Remove `defined_names dn(m)` variable
that was declared but never used
- **`smt_model_checker.h`** — Drop the now-unnecessary `#include
"ast/normal_forms/defined_names.h"`
- **`fingerprints.cpp`** — Collapse redundant tail in
`fingerprint_set::contains`:
```cpp
// Before
if (m_set.contains(d))
return true;
return false;
// After
return m_set.contains(d);
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Z3's -T measures wall clock time, whereas `ulimit -t` measures CPU time.
Currently, an expired ulimit timeout crashes Z3 without printing
statistics; this patch makes it react cleanly (just as if it has
encountered a regular timeout) to SIGXCPU, the signal that ulimit sends
before sending SIGKILL.
Two fixes for mbp_dt_tg::apply() when encountering an accessor whose
argument has a different constructor in the model:
1. Don't call rm_accessor (which would assert a contradictory
recognizer, making the formula false). This prevents the original bug
where QEL returned 'false' for satisfiable formulas.
2. Branch on the model-assigned constructor for the accessor's argument.
The correct output should include the literal introduced in (2).
However, this fix does not produce it. Spacer is sound with this
over-approximation, as long as the counter example does not depend on
value of mismatched accessors (e.g. (tl nil)).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This simplifies the recent `choice` axiom path in the SMT array solver
for consistency with the SAT-side implementation. The change is purely
structural: align local naming with the quantifier body it represents,
inline a single-use literal, and remove stray whitespace in the array
decl header.
- **Choice axiom cleanup**
- Rename the local implication term in
`theory_array_full::instantiate_choice_axiom` from `ax` to `body`
- Match the naming already used in
`sat/smt/array_axioms.cpp::assert_choice_axiom`
- **Single-use literal inlining**
- Replace the temporary `literal l = mk_literal(q); assert_axiom(l);`
with a direct call
- Reduce noise without changing behavior
- **Header whitespace cleanup**
- Remove trailing whitespace in `src/ast/array_decl_plugin.h`
```c++
expr_ref body(m.mk_implies(px, pc), m);
expr_ref q(m.mk_forall(1, &x_sort, &x_name, body), m);
ctx.get_rewriter()(q);
assert_axiom(mk_literal(q));
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Bumps [github/gh-aw-actions](https://github.com/github/gh-aw-actions)
from 0.76.1 to 0.77.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/gh-aw-actions/releases">github/gh-aw-actions's
releases</a>.</em></p>
<blockquote>
<h2>v0.77.0</h2>
<p>Sync of actions from <a
href="https://github.com/github/gh-aw">gh-aw</a> at
<code>v0.77.0</code>.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b11be78086"><code>b11be78</code></a>
chore: sync actions from gh-aw@v0.77.0 (<a
href="https://redirect.github.com/github/gh-aw-actions/issues/122">#122</a>)</li>
<li>See full diff in <a
href="https://github.com/github/gh-aw-actions/compare/v0.76.1...v0.77.0">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
- 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
A `root-obj`-driven unsat case was exiting with a leaked `mpz_manager`
allocation even though solver output was correct. The leak came from
temporary rational bounds created during algebraic-number comparison and
not released before shutdown.
- **Root cause**
- `algebraic_numbers::compare_core()` materialized interval bounds as
raw `mpq` temporaries.
- Those temporaries could allocate backing `mpz` storage, but their
lifetime was not tied to the manager, so the allocator retained leaked
cells at process exit.
- **Change**
- Replace the raw `mpq` temporaries with `scoped_mpq` in
`/src/math/polynomial/algebraic_numbers.cpp`.
- This keeps the comparison logic unchanged while making temporary bound
conversion use RAII-managed cleanup.
- **Effect**
- `root-obj` comparisons no longer leave `mpz_manager` allocations
behind.
- Solver behavior is unchanged; the fix is limited to temporary numeral
lifetime management.
```c++
- mpq l_a, u_a, l_b, u_b;
+ scoped_mpq l_a(qm()), u_a(qm()), l_b(qm()), u_b(qm());
```
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
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 updates the compare-stats anomaly reporter to retrieve benchmark
data from `http://mtzguido.tplinkdns.com:8081/z3/` instead of the old
`compare_stats.html` endpoint. The workflow prompt and generated lock
file now consistently reference the root benchmark stats URL.
- **Workflow source**
- Repoint the benchmark source URL from `.../compare_stats.html` to
`.../z3/`
- Update prompt text to describe the source as benchmark statistics
rather than a specific HTML file
- Rename the temporary fetched artifact from `compare_stats.html` to
`benchmark_stats.html` for consistency
- **Generated workflow**
- Regenerate `compare-stats-anomaly-reporter.lock.yml` so the compiled
workflow matches the markdown source
- Refresh the embedded workflow description and prompt payload to
reference the new endpoint
- **Report output**
- Update the discussion template’s source link to point at the root
benchmark stats page
```md
Source URL:
`http://mtzguido.tplinkdns.com:8081/z3/`
curl -fsSL --max-time 60 "http://mtzguido.tplinkdns.com:8081/z3/" \
-o /tmp/gh-aw/agent/benchmark_stats.html
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This updates a formatting regression introduced in the `pop_app_frame`
non-`expr_head` path, where block indentation made control flow harder
to read. The patch is whitespace-only and keeps parser behavior
unchanged.
- **What changed**
- Reindented the `else` body in
`src/parsers/smt2/smt2parser.cpp::pop_app_frame` so nested `if/else`
structure is visually unambiguous.
- Removed trailing spaces on the `m_ctx.mk_app(symbol("select"), ...)`
lines in the same block.
- **Scope**
- No control-flow, data-flow, or API changes.
- No changes outside `pop_app_frame`.
```cpp
// Before
else {
local l;
if (m_env.find(fr->m_f, l)) {
...
}
else {
...
}
}
// After
else {
local l;
if (m_env.find(fr->m_f, l)) {
...
}
else {
...
}
}
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This adds an agentic workflow that analyzes `compare_stats.html` over a
rolling 30-hour window and publishes a GitHub Discussion summarizing
bugs, crashes, and anomalies. It explicitly captures unknown-outlier
patterns where a benchmark is `unknown` while peers in the same set are
mostly `sat`/`unsat`/`timeout`.
- **Workflow added**
- Introduces `.github/workflows/compare-stats-anomaly-reporter.md` (plus
compiled `.lock.yml`).
- Supports `workflow_dispatch` and scheduled execution.
- Uses safe discussion output with auto-close of older reports for the
same stream.
- **Data acquisition + robustness**
- Fetches `http://mtzguido.tplinkdns.com:8081/z3/compare_stats.html`
with `curl` and `wget` fallback.
- Adds integrity checks (non-empty HTML/table presence) and explicit
incomplete-report behavior on fetch/parse failures.
- **30-hour analysis semantics**
- Filters rows by timestamp candidates (`time`, `timestamp`, `date`,
`run`, etc.) using UTC.
- Falls back to full-table analysis when timestamps are unavailable, and
marks the report accordingly.
- **Classification logic**
- Detects bug/crash signals from status/details (`crash`, `segfault`,
`assert`, `abort`, `exception`, `error`, `failed`, `bug`).
- Detects:
- unknown-outlier anomalies (thresholded minority `unknown` in otherwise
decisive SAT-family outcomes),
- status divergences (conflicting non-timeout outcomes for same
benchmark),
- repeated hard-failure anomalies.
- **Discussion output shape**
- Produces a compact report with executive counts, bug/crash table,
anomaly subsections, and raw extraction summary/limitations.
```yaml
safe-outputs:
create-discussion:
title-prefix: "[Compare Stats] "
category: "agentic workflows"
close-older-discussions: true
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
So far, `algebraic_numbers compare_core ` handles an edge case
incorrectly:
- If the two compared numbers (`a`, `b`) are different,
- the intervals still overlap after refinements, and
- both a and b are a root of the second polynomial (`cell_b->m_p`), e.g.
they are the first and second root
then the method would return `sign_zero` (i.e. "equal"). This behavior
can be replicated with the provided test case (before the fix). This
requires `algebraic.factor=false`, though i first encountered it during
solver runs on QF_NRA instances with the default
`algebraic.factor=true`, which apparently means that the polynomials for
anums are still not always factored.
The fix is to compare the interval bounds of b to a and vice versa. Then
the Sturm-Tarski check is only run if `a` and `b` both lie in the
intersection of the intervals, because only then is it guaranteed to be
correct.
The SMT2 front-end rejected valid higher-order inputs using `HO_ALL` and
failed on curried applications where the function position is itself an
expression (e.g., `((transfer top) 0)`).
This update adds `HO_ALL` support and makes curried parsing consistently
lower to implicit `select` chains.
- **Logic recognition**
- Treat `HO_ALL` as an `ALL`-class logic in SMT logic classification.
- This unblocks `(set-logic HO_ALL)` in the standard SMT2 command path.
- **Curried application parsing**
- Extend application-frame handling to support parenthesized expression
heads, not only symbol heads.
- When the head is an expression, parse application arguments normally
and construct nested implicit selects:
- `(f a b)` → `(select (select f a) b)`
- Preserve existing behavior for symbol-based applications, qualified
identifiers, and lambda-led forms.
- **Regression coverage**
- Add a focused parser/eval regression using the reported higher-order
case to lock in behavior.
```smt2
(set-logic HO_ALL)
(declare-fun transfer () (-> (-> Int Bool) (-> Int Bool)))
(assert (forall ((P (-> Int Bool))) (=> (P 0) ((transfer P) 0))))
(declare-fun top () (-> Int Bool))
(assert (forall ((x Int)) (top x)))
(assert (not ((transfer top) 0)))
(check-sat)
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Adds a new lemma pattern to nla_grobner::propagate_quotients that
derives a modular-residue constraint from polynomial divisibility,
filling a gap between quotient1-5 (model-value-driven case splits) and
the polynomials Grobner actually produces on Skolem-encoded mod
arithmetic.
Pattern
-------
For a polynomial p with all-integer free variables and a linear monomial
c_v * v (single integer var), the pattern computes M = gcd(|c_i/c_v|)
over the other monomials and K = c0/c_v for the constant term. When both
are integers, dividing p by c_v gives
v + M*Q + K = 0 with Q an integer
so v ≡ -K (mod M). The pattern emits the sound disjunctive lemma
(v < 0) ∨ (v ≥ M) ∨ (v = target)
where target = (-K) mod M ∈ [0, M-1]. This encodes "v ∈ target + M·Z" in
a form the LP / SAT layer can refute against current bounds.
Motivation
----------
QF_UFNIA verification benchmarks over fixed-prime modular arithmetic
(e.g. zk applications using the BabyBear prime 2013265921) regularly
produce basis polynomials of the form
-p*v_div + p*(v_a * v_b) - v_mod = 0
where v_mod is the result of (mod (* v_a v_b) p). The polynomial sits in
the Grobner basis but none of quotient1-5 fires: they all require
specific model-value alignments (r_value == 0, |v_value| > |r_value|,
etc.) that don't hold when all variables in scope are similarly sized
integers in [0, p). The proof spins on interval-tightening lemmas
without ever extracting the modular conclusion.
The author of propagate_quotients flagged this gap with the comment
\"other division lemmas are possible\" preceding the fall-through \"no
lemmas found\" CTRACE. This patch supplies one.
Soundness
---------
The lemma is sound regardless of v's LP bounds — the bound-negation
disjuncts (v < 0) and (v ≥ M) make the disjunction unconditionally true
under the polynomial identity, with v = target as the canonical residue
in [0, M-1]. M is derived from the polynomial's coefficient gcd, not
from any LP-side bound.
Validated under smt.arith.validate=true on the mod-factor-propagation
reproducers (PR #9235 follow-up), zk verifier benchmarks, and a broader
QF_UFNIA sample — 50+ files total, zero validate_conflict() assertion
violations.
Performance
-----------
A model-value gate (skip emission when v's current value already
satisfies one of the disjuncts) prevents the pattern from
short-circuiting the propagate_quotients || propagate_gcd_test ||
propagate_eqs || propagate_factorization || propagate_linear_equations
chain with redundant emissions. Without the gate, a single (v, M,
target) triple can re-emit each Grobner round and starve the downstream
propagators — observed in regression testing as thousands of identical
emissions on a small benchmark, turning a sub-second closure into a
timeout.
On six small mod-factor-propagation reproducers, the patch closes four
cases that previously timed out at 30 s (~1 s typical under the
Grobner-ramped config: smt.arith.nl.gr_q=50,
smt.arith.nl.grobner_eqs_growth=50,
smt.arith.nl.grobner_exp_delay=false, smt.arith.nl.grobner_frequency=1).
The two remaining timeouts in that set are attributable to different
gaps (Boolean-disjunction propagation, and the multi-bounded-mod-result
polynomial shape that needs Grobner over Z/pZ), not to mod_residue
itself.
Diagnostics
-----------
TRACE under the existing 'grobner' tag emits one line per lemma
emission, recording v, M, c_v, c0, and target.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Monthly agentic workflow that discovers community-contributed SMT-LIB
benchmark repositories on GitHub, excluding official distributions from
smtlib.org and Zenodo, and posts a categorised summary as a GitHub
Discussion.
## Workflow steps
- **Exclusion list** — scrapes `smtlib.cs.uiowa.edu` + paginates the
[Zenodo SMT-LIB community](https://zenodo.org/communities/smt-lib/) (up
to 300 records) to extract official repo URLs; hard-excludes `SMT-LIB/*`
and `SMT-Competition/*` orgs
- **GitHub search** — 7 search strategies (topic tags
`smtlib`/`smt-lib`/`smt2`, filename/path patterns, README mentions)
scoped to repos pushed since the last run (90-day window on first run)
- **Filter + classify** — removes official sets and noise (homework
repos, empty repos); categorises survivors into: solver evaluation,
verification, security/CTF, theory/logic, tool output, education, other
- **Discussion report** — posts `[SMT-LIB Benchmarks] Community
Benchmark Repository Survey — [Month YYYY]`; older discussions
auto-closed after 90 days
- **Cache** — stores classified repo sets and run date so subsequent
runs are incremental
## Files
- `.github/workflows/smtlib-benchmark-finder.md` — workflow definition
- `.github/workflows/smtlib-benchmark-finder.lock.yml` — compiled GitHub
Actions YAML (`gh aw compile`)
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
The JS bindings currently assume the wasm can be located via the default
Emscripten path resolution, which can force Deno users into
`--allow-read`. This change lets callers provide a custom wasm load path
through `init(...)`, so Deno can resolve the packaged wasm asset without
filesystem reads.
- **Public init API**
- Extend the JS entrypoints (`node` and `browser`) so `init(...)`
accepts optional Emscripten module overrides.
- Surface a typed `Z3ModuleOverrides` shape with explicit support for
`locateFile(path, prefix)`.
- **Low-level initialization**
- Thread module overrides through the generated low-level wrapper
instead of always calling the Emscripten module factory with no
arguments.
- Keep the default behavior unchanged when no overrides are provided.
- **Docs**
- Document the Deno usage pattern in the published JS README.
- Clarify the `locateFile` signature and show the intended Deno 2.1+
`import.meta.resolve(...)` flow.
- **Focused coverage**
- Add unit tests for `node` and `browser` init to verify module
overrides are forwarded correctly.
Example:
```ts
import { init } from 'npm:z3-solver';
const api = await init({
locateFile: (file, _prefix): string =>
import.meta.resolve(`npm:z3-solver/build/${file}`),
});
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>