3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-15 03:25:43 +00:00
Commit graph

22473 commits

Author SHA1 Message Date
Simon Scatton
25e0e6f780
fix(bazel): pin CMake library installs to lib (#10126)
rules_foreign_cc expects libraries under its default lib output
directory. GNUInstallDirs may instead select lib64, causing Bazel to
reject an otherwise successful CMake build because its declared output
is missing.

Share the default CMake arguments between the static and dynamic targets
and set CMAKE_INSTALL_LIBDIR to lib.
2026-07-14 08:30:48 -07:00
Copilot
98c8f2935e
Nightly: prevent test.PyPI publish failure by rewriting unsupported macOS wheel tags (#10122)
The nightly workflow’s `Publish to test.PyPI` job fails because
test.PyPI rejects uploaded macOS wheels tagged `macosx_13_3_*`. This
change keeps the validation publish path working by rewriting
unsupported macOS wheel tags to a supported form during that specific
upload step.

- **Root cause reflected in workflow behavior**
- `publish-test-pypi` currently uploads all artifacts from
`PythonPackages`, including macOS wheels that test.PyPI does not accept.

- **Workflow change (surgical)**
- In `.github/workflows/nightly.yml`, added a pre-upload rewrite step in
`publish-test-pypi` to rename wheel tags from `macosx_13_3_*` to
`macosx_13_*` in `dist/`.
- Left artifact production unchanged; only the filenames used for the
test.PyPI upload are adjusted.

- **Effect on release flow**
- test.PyPI upload continues for sdist + Linux/Windows wheels and now
includes rewritten macOS wheels.
- Nightly macOS artifacts remain built and available through existing
artifact/release paths.

```yaml
- name: Rewrite macOS wheel tags unsupported by test.PyPI
  run: |
    for whl in dist/*-macosx_13_3_*.whl; do
      [ -e "$whl" ] || continue
      mv "$whl" "${whl/macosx_13_3_/macosx_13_}"
    done
    ls -l dist
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-14 08:21:20 -07:00
Nikolaj Bjorner
eae0530675 update version to 4.17.1
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-13 18:56:19 -07:00
Copilot
627e19197c
Update RELEASE_NOTES.md for v4.17.0 (PRs #9798–#10116) (#10121)
Appends ~35 release note entries to the `Version 4.17.0` section,
covering the substantial changes since PR #9700 as catalogued in
discussion #10117.

## New features
- `z3regex` Python module bridging Python `re` syntax to Z3 regex ASTs
- `OP_RE_XOR` + bisimulation-based ground regex equivalence (Margus
Veanes)
- `bv_divrem_bounds_tactic` for bounded BV div/rem constraints
- Linear divisibility closure lemma for lp/nla solver
- Pyodide (WASM) wheel support; Bazel versioned shared objects
- rlimit support in fixedpoint/Horn parameters
- HO matching improvements: curry-order, variable shift, lazy MAM
deferral, throttle configs
- Go bindings: concurrent `dec_ref` for GC finalizers

## Bug fixes
- Three optimization soundness fixes (strict optima with delta-rational
/ infinitesimal bounds, unvalidated LP bound)
- Horn clause solver segfault on unused quantified variables
- .NET API memory leak (NativeContext finalizer, delegate lifetime, GC
pressure)
- Parallel solver unsigned overflow in conflict budget escalation
- `psmt`/`smt_parallel` infinite loops on theory-incomplete cubes
- `seq_rewriter` `re.range` empty-language and soundness fixes
- Mod rewriter non-termination on symbolic modulus
- `elim_uncnstr` disabled by `has_type_vars()` flag (issue #6260)
- MBQI timeout regression in HO term enumeration
- `bv2int_translator` assertion violation with `smt.bv.solver=2`

## API fixes
- Java `Sort.create()` returns `EnumSort` (not `DatatypeSort`) for enum
sorts

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 18:43:58 -07:00
Copilot
5c0443591d
Align release macOS build targets with nightly’s 13.3 settings (#10118)
The `Release Build` workflow still targeted macOS 13.0 for the x64/arm64
packaging jobs, while the codebase now relies on libc++ functionality
that is only available with a 13.3 deployment target. This updates the
release workflow to use the same macOS target configuration already
applied in `nightly.yml`.

- **Release workflow**
- Raise `MACOSX_DEPLOYMENT_TARGET` from `13.0` to `13.3` for both
`mac-build-x64` and `mac-build-arm64`
- Update the packaging target passed to `mk_unix_dist.py` from
`--os=osx-13.0` to `--os=osx-13.3`

- **Config alignment**
- Bring `release.yml` in sync with the existing nightly macOS fix so
both workflows build against the same minimum macOS version

```yaml
env:
  MACOSX_DEPLOYMENT_TARGET: "13.3"

run: python scripts/mk_unix_dist.py --arch=x64 --os=osx-13.3
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 17:51:57 -07:00
Copilot
26ad30bb76
Fix invalid sequence models for seq.foldl results observed through seq.nth (#10111)
`seq.foldl` could produce a concrete sequence model while related
`seq.nth` constraints were still validated against stale or
underconstrained length information, leading to invalid models. In the
reported case, `all` was modeled as `(seq.++ (seq.unit 7) (seq.unit 0))`
while `final = (seq.nth all 0)` remained inconsistent with `final = 6`.

- **Root cause**
- Sequence solutions were propagated as equalities, but parent `seq.len`
terms were not updated when a sequence term was solved.
- As a result, `seq.nth` guard reasoning could miss that a solved
sequence had known in-bounds length.

- **Solver change**
- Extend `theory_seq::add_solution` to collect parent `seq.len`
expressions of a solved term when the solved result is sequence-typed.
- After propagating the solved sequence equality, also propagate the
rewritten length equality for those parent length terms.
- Keep this propagation guarded to sequence results so scalar
`seq.foldl`/`seq.foldli` solutions do not regress from `sat` to
`unknown` under model validation.

- **Regression coverage**
  - Add a focused test for the reported SMT-LIB pattern:
    - `all = seq.foldl(...)`
    - `final = seq.nth all 0`
    - `initial = 0`
    - `final = 6`
- Add focused scalar `seq.foldl`/`seq.foldli` model-validation coverage
for the existing benchmark shapes that must continue returning `sat`.
- The regressions check both that model validation no longer reports an
invalid model for the `seq.nth` case and that scalar fold/foldi cases do
not regress to `unknown`.

- **Effect**
- Solved sequence terms now push enough derived length information for
dependent `seq.nth` constraints to validate against the actual modeled
sequence.
  - Existing scalar fold/foldi solving behavior is preserved.

```smt2
(define-fun all_sums ((prev_sums (Seq Int)) (elem Int)) (Seq Int)
  (seq.++ (seq.unit (+ (seq.nth prev_sums 0) elem)) prev_sums)
)

(assert (= all (seq.foldl all_sums (seq.unit initial) elements)))
(assert (= final (seq.nth all 0)))
(assert (= initial 0))
(assert (= final 6))
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 17:33:39 -07:00
Copilot
424bcc545e
Fix release-notes-updater: add copilot-requests: write permission (#10116)
The `release-notes-updater` agent job was failing with HTTP 401 on every
Copilot API inference request because the GitHub Actions token lacked
`copilot-requests: write`.

## Changes

- **`release-notes-updater.md`**: Replace `permissions: read-all`
shorthand with an explicit permissions object that includes
`copilot-requests: write`
- **`release-notes-updater.lock.yml`**: Recompiled via `gh aw compile`

```yaml
# Before
permissions: read-all

# After
permissions:
  contents: read
  issues: read
  pull-requests: read
  discussions: read
  copilot-requests: write
```

`read-all` expands all scopes to read but does not grant
`copilot-requests: write`, which is the permission required for the
GitHub Actions token to authenticate with the Copilot API proxy.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 17:13:31 -07:00
Copilot
710b4082d1
Add constant-bound contradiction shortcut for string < constraints in theory_seq (#10112)
The reported case showed expensive reasoning for lexicographic string
comparisons under the default sequence solver, and incorrect handling
expectations with `z3str3` (which does not interpret these comparisons).
This change targets the default solver path by short-circuiting
contradictory constant-bound `<` constraints earlier.

- **Theory shortcut for constant lexical bounds**
- In `theory_seq::assign_eh`, detect asserted `str.<` constraints of the
form `c < x` or `x < c` where `c` is a string constant.
- When a complementary bound on the same equivalence class is already
true, check bound consistency immediately.
- If bounds are contradictory (`!(lower < upper)`), emit a direct theory
conflict from the two active literals instead of waiting for deeper
axiom propagation.

- **Preserve existing comparison reasoning**
  - Existing `check_lts` transitivity/axiom flow is retained.
- The new logic is a narrow fast path for contradictory constant bounds
and does not alter general string-order semantics.

- **Regression coverage**
- Added a solver-level regression in `src/test/seq_rewriter.cpp` for
contradictory date-like lexical bounds to ensure this class of
constraints is rejected as `unsat`.

```cpp
ctx.assert_expr(su.str.mk_lex_lt(su.str.mk_string("2024-01-01"), x));
ctx.assert_expr(su.str.mk_lex_lt(x, su.str.mk_string("2024-12-31")));
ctx.assert_expr(su.str.mk_lex_lt(x, su.str.mk_string("2023-01-01")));
ENSURE(ctx.check() == l_false);
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 12:56:57 -07:00
Copilot
038b367d68
Fix non-termination in mod rewriter for symbolic modulus (#10105)
Combining `mod0`/`div0` quantifier axioms with a mod-idempotency
quantifier caused Z3 to loop forever. The core issue was that
`mk_mod_core` in `arith_rewriter.cpp` only handled rewrite rules for
*numeral* moduli, leaving two gaps for symbolic `y`:

1. `mod(a + k*y, y)` was not reduced to `mod(a, y)`, so `(not (= (mod (+
a b) b) (mod a b)))` stayed unreduced and caused the nlsat solver to
spin.
2. The E-matching pattern `(mod (mod x y) y)` fired on every new term it
produced, creating an unbounded chain of nested `mod` expressions.

```lisp
; Previously non-terminating, now returns unsat immediately
(assert (forall ((x Int)) (! (= (mod0 x 0) 0) :pattern ((mod0 x 0)))))
(assert (forall ((x Int)) (! (= (div0 x 0) 0) :pattern ((div0 x 0)))))
(assert (forall ((x Int) (y Int))
  (! (= (mod (mod x y) y) (mod x y)) :pattern ((mod (mod x y) y)))))
(assert (not (= (mod (+ a b) b) (mod a b))))
(check-sat)
```

## Changes

- **`src/ast/rewriter/arith_rewriter.cpp` — symbolic summand
elimination**: In `mk_mod_core`, when the modulus is a non-numeral
integer and the dividend is an `add`, strip any summand equal to the
modulus or an integer multiple of it. Soundness: `k*0 = 0` for all `k`,
so the rule holds even at `y = 0`. This immediately collapses the
reported formula to `false`.

- **`src/ast/rewriter/arith_rewriter.cpp` — symbolic idempotency via
ite**: Extend the existing `mod(mod(x,y), y) → mod(x,y)` rule
(previously numeral-only) to symbolic `y` by rewriting to `ite(y=0,
mod(mod(x,0),0), mod(x,y))`. The `y=0` branch uses a numeral divisor,
which is excluded by the `!v2.is_zero()` guard, halting the E-matching
chain.

- **`src/test/arith_rewriter.cpp`**: Regression tests for `mod(a+y, y) =
mod(a,y)`, `mod(a+2y, y) = mod(a,y)`, and `mod(mod(a,3),3) = mod(a,3)`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-13 09:20:03 -07:00
Copilot
98e1f5ca2d
Fix assertion violation in bv2int_translator with smt.bv.solver=2 (#10109)
`ASSERTION VIOLATION` at `bv2int_translator.h:72` when using
`smt.bv.solver=2` with formulas containing `abs` (or other arith
expressions that rewrite to ITE with arith predicates).

**Root cause**

`ensure_translated` skips adding sub-expressions of boolean non-BV nodes
to the `todo` list — correct, since the base theory owns them in plugin
mode. However, `translate_expr`'s early-return only covered
`basic_family_id` booleans, not non-basic ones (e.g.,
`arith_family_id`).

When `(abs f)` is rewritten by the arith rewriter to `(ite (>= f 0) f (-
f))`, the predicate `(>= f 0)` ends up in `todo` (as a child of the
ITE), but its own children (e.g., the integer literal `0`) are not
added. `translate_expr` then calls `translated(0)` on an unmapped
expression, firing `SASSERT(r)`.

Reproducer:
```smt2
(declare-const f Int)
(assert (= 0 (mod 0 (bv2nat ((_ int_to_bv 1) (abs f))))))
(check-sat)
; z3 test.smt2 smt.bv.solver=2  →  ASSERTION VIOLATION (before fix)
```

**Fix**

Extend the early-return in `translate_expr` to match
`ensure_translated`'s skip condition — all boolean non-BV expressions in
plugin mode map to themselves:

```cpp
// before
if (m_is_plugin && ap->get_family_id() == basic_family_id && m.is_bool(ap)) {

// after
if (m_is_plugin && m.is_bool(ap) && ap->get_family_id() != bv.get_family_id()) {
```

BV boolean predicates (`bvule`, etc.) are unaffected — they still route
through `translate_bv`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 21:56:12 -07:00
Copilot
965942be79
Java API: return EnumSort instead of DatatypeSort from Sort.create() (#10104)
`Expr.getSort()` on a constant with an `EnumSort` sort returns a
`DatatypeSort`, causing `ClassCastException` when enum-specific methods
are called.

```java
Context ctx = new Context();
EnumSort<Foo> enumSort = ctx.mkEnumSort("my-enum", "e1", "e2");
Expr<EnumSort<Foo>> c = ctx.mkConst("my-const", enumSort);
c.getSort().getName();  // ClassCastException — getSort() returns DatatypeSort, not EnumSort
```

### Changes

- **`Sort.java`**: In `Sort.create()`, detect enum sorts at the
`Z3_DATATYPE_SORT` case by checking whether all constructors have arity
0 — matching Z3's own `util::is_enum_sort` logic in
`datatype_decl_plugin.cpp`. Return `EnumSort<>` when true,
`DatatypeSort<>` otherwise.
- **`EnumSort.java`**: Add package-private `EnumSort(Context ctx, long
obj)` constructor so an `EnumSort` can be instantiated from an existing
native sort handle (analogous to `DatatypeSort(Context ctx, long obj)`).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 21:46:46 -07:00
Nikolaj Bjorner
7b26fe135a
Add linear divisibility closure lemma for lp/nla solver (#7464) (#10107)
## Summary

Fixes the divergence in issue #7464: formulas involving `mod`/`div` by a
**variable** divisor could send `smt.arith.solver=6` into a
non-terminating nonlinear search.

Minimal reproducer (UNSAT, previously timed out; now solved in <0.5s):

```smt2
(declare-fun V () Int)
(declare-fun n () Int)
(declare-fun l () Int)
(assert (and (> V 0) (= 0 (mod n 2)) (= (div n 2) (div n l)) (= 0 (mod (div n l) V))))
(assert (distinct 0 (mod n V)))
(check-sat)
```

## Root cause

A variable-divisor `mod n V` is axiomatized by the Euclidean identity
`n = V*(n div V) + (n mod V)`. The `V*(n div V)` term is nonlinear, so
arith.solver=6
hands the problem to the nlsat/Gröbner branch, which branches on values
of `V` with no
termination bound and diverges.

## Fix

Add a **linear divisibility closure** lemma in `nla_divisions`:

> `mod(a, y) = 0 & x = c*a` (c an integer constant) ⟹ `mod(x, y) = 0`.

The emitted clause

```
(x - c*a != 0)  \/  (mod(a, y) != 0)  \/  (mod(x, y) = 0)
```

is a **tautology for every integer `c`**, so mining a candidate `c =
val(x)/val(a)` from
the current model can never be unsound. It is only emitted when all
three literals are
false in the current model, so the clause is a genuine
conflict/propagation and always
makes progress. This lets the theory refute the instance directly
instead of entering the
divergent nonlinear branch.

Variable-divisor `mod` terms were previously **not registered** in nla
at all; they are now
registered into a new `m_divisibility` list in `theory_lra`, so the
reasoner can pair a
violated `mod(x, y)` with a satisfied `mod(a, y)` of the same divisor.

## Changes

- `src/math/lp/nla_divisions.{h,cpp}` — new `m_divisibility` list
`{r=mod, x=dividend, y=divisor}`, `add_divisibility(...)`, and
`check_linear_divisibility()`; invoked from `divisions::check()`.
- `src/math/lp/nla_core.h`, `src/math/lp/nla_solver.{h,cpp}` —
forwarding of `add_divisibility`.
- `src/smt/theory_lra.cpp` — register variable-divisor `mod` into the
divisibility list.

## Validation

- `min.smt2` → `unsat` in 0.46s, minimized core → 0.15s (were timeouts).
- Soundness: 350 differential fuzz formulas (arith.solver=6 vs
arith.solver=2), **0 mismatches**.
- Spot checks correct (divisor-3 variant → unsat; non-divisible variants
→ sat).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-12 21:20:50 -07:00
Copilot
634b2886ba
fix: declare type variables before use in solver display output (#10103)
`decl_collector::visit_sort` did not collect sorts with `poly_family_id`
(type variables created via `mk_type_var` / `declare-type-var`), so
`solver::display` and `ast_pp_util::display_decls` never emitted type
variable declarations before referencing them — producing invalid
SMT-LIB2 output.

## Changes

- **`src/ast/decl_collector.h`**: added a dedicated `lim_svector<sort*>
m_type_vars` field (separate from `m_sorts`) with a `get_type_vars()`
getter; `reset()` clears it; `push()`/`pop()` maintain its scope.

- **`src/ast/decl_collector.cpp` — `visit_sort`**: sorts with
`poly_family_id` are now pushed to `m_type_vars` instead of `m_sorts`,
keeping type variables distinct from uninterpreted sorts:

```cpp
if (m.is_uninterp(n))
    m_sorts.push_back(n);
else if (fid == poly_family_id)
    m_type_vars.push_back(n);
```

- **`src/ast/ast_pp_util.h`**: added a `stacked_value<unsigned>
m_type_vars` cursor to track which type variables have already been
printed.

- **`src/ast/ast_pp_util.cpp` — `display_decls`**: emits
`(declare-type-var <name>)` for each collected type variable before
other sort declarations; `reset()`/`push()`/`pop()` maintain the new
cursor.

**Example** — given `(declare-type-var A)(declare-fun f (A) A)`, the
dump now correctly produces:

```smt2
(declare-type-var A)
(declare-fun f (A) A)
(assert ...)
```

The output round-trips cleanly through the Z3 parser.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 20:56:00 -07:00
Copilot
087eeaf33c
Fix api_datalog test: reuse of Z3_context across set-logic calls (#10101)
The `api_datalog` unit test was failing in CI with `"the logic has
already been set"`. Two consecutive regression tests shared a single
`Z3_context`, but both called `Z3_eval_smtlib2_string` with `(set-logic
HORN)` — the second call always fails because context logic state is
permanent.

## Changes

- **`src/test/api_datalog.cpp`**: Give each
`Z3_eval_smtlib2_string`-based regression test its own
`Z3_config`/`Z3_context`, destroyed immediately after the test block.
The outer context is retained only for the two tests that don't invoke
the SMT-LIB evaluator.

```cpp
// Before: both blocks shared `ctx`, second (set-logic HORN) always errored
Z3_string response = Z3_eval_smtlib2_string(ctx, chc1);  // sets HORN logic
Z3_string response = Z3_eval_smtlib2_string(ctx, chc2);  // ERROR: logic already set

// After: each block owns its context
Z3_config cfg2 = Z3_mk_config();
Z3_context ctx2 = Z3_mk_context(cfg2);
Z3_del_config(cfg2);
Z3_string response = Z3_eval_smtlib2_string(ctx2, chc2);
Z3_del_context(ctx2);
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 20:32:02 -07:00
Copilot
136a653ade
cmake: prevent install_tactic.deps from triggering spurious rebuilds on reconfigure (#10102)
`z3_add_install_tactic_rule` unconditionally called `file(WRITE)` during
CMake configure, updating `install_tactic.deps`'s mtime on every
reconfiguration — even when the content was identical. Since
`install_tactic.cpp` lists `install_tactic.deps` as a dependency, any
CMake reconfigure (e.g. touching `src/api/ml/CMakeLists.txt`) caused a
full rebuild of the tactic installation target.

## Change

- **`cmake/z3_add_component.cmake` — `z3_add_install_tactic_rule`**:
Replace unconditional `file(WRITE)` with a read-and-compare guard; the
deps file is only rewritten when its content actually changes.

```cmake
# Before
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/install_tactic.deps" ${_tactic_header_files})

# After
set(_install_tactic_deps_file "${CMAKE_CURRENT_BINARY_DIR}/install_tactic.deps")
if (EXISTS "${_install_tactic_deps_file}")
  file(READ "${_install_tactic_deps_file}" _install_tactic_deps_old)
else()
  set(_install_tactic_deps_old "")
endif()
if (NOT _install_tactic_deps_old STREQUAL "${_tactic_header_files}")
  file(WRITE "${_install_tactic_deps_file}" "${_tactic_header_files}")
endif()
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 20:07:41 -07:00
Copilot
0b8335e776
Set pi.avoid_skolems=false for TPTP solver runs (#10100)
The TPTP frontend was not forcing `pi.avoid_skolems=false`, so TPTP
problems could be solved with the default pattern-inference behavior
instead of the intended frontend-specific setting. This change applies
the override directly to the solver used by TPTP runs.

- **What changed**
- After constructing the TPTP solver, the frontend now sets
`pi.avoid_skolems=false` via solver parameters before `check_sat`.
- The override is scoped to the TPTP solver instance instead of mutating
process-global parameter state.

- **Why this shape**
- Keeps the TPTP behavior explicit at the point where the solver is
created.
  - Avoids leaking the parameter change into unrelated solver contexts.

- **Code sketch**
  ```c++
  ctx.set_solver_factory(mk_smt_strategic_solver_factory());
  params_ref solver_params;
  solver_params.set_bool("pi.avoid_skolems", false);
  ctx.get_solver()->updt_params(solver_params);
  ```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 19:21:25 -07:00
Nikolaj Bjorner
0000e16851 expose avoid-skolems parameter to deal with TPTP problems 2026-07-12 18:54:31 -07:00
Copilot
7d67dbfcfc
Add QE regression for unsound check-sat-using qe result on quantified reals (#10097)
`check-sat-using qe` was reported to return `unsat` on a satisfiable
quantified-real formula, while a subsequent `check-sat` on the same
assertion returned `sat`. This PR adds focused regression coverage for
that shape to prevent reintroduction.

- **Regression coverage for the reported formula**
  - Added `test_qe_regression_4175()` in `src/test/quant_solve.cpp`.
  - Parses and quantifier-eliminates:
    ```smt2
    (forall ((b Real)) (= (= r1 b) (= b 0)))
    ```

- **Behavioral oracle encoded in the test**
  - Verifies the QE result is satisfiable under `r1 = 0`.
  - Verifies the QE result is unsatisfiable under `r1 != 0`.
- This captures the intended semantics of the original formula and
guards against the unsound `unsat` outcome from the QE path.

- **Integration**
- Wires the new regression into `tst_quant_solve()` so it runs with
existing quantifier-solver test coverage.

Example snippet from the new test logic:

```cpp
solver.assert_expr(result);
solver.assert_expr(m.mk_eq(r1, zero));
VERIFY(l_true == solver.check());

solver.assert_expr(result);
solver.assert_expr(m.mk_not(m.mk_eq(r1, zero)));
VERIFY(l_false == solver.check());
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 18:53:15 -07:00
Copilot
4862a10ffd
Spacer QE: avoid assertion on extended arithmetic model values and add #3845 regression (#10096)
Spacer crashed in quantifier-elimination projection on certain HORN
inputs when model evaluation produced arithmetic expressions that were
not plain numerals. The failure was an assertion in
`spacer_qe_project.cpp` during sign/offset computation for projected
literals.

- **Projection robustness in Spacer arithmetic QE**
- Updated numeral extraction in `src/muz/spacer/spacer_qe_project.cpp`
from `is_numeral` to `is_extended_numeral` at all model-evaluation sites
used by projection.
- This covers evaluated arithmetic forms (e.g., normalized arithmetic
expressions) that are semantically numeric but not syntactic numerals,
preventing assertion failures in disequality/equality handling and bound
selection.

- **Regression coverage for the crashing HORN shape**
- Added a focused regression in `src/test/api_datalog.cpp` that
evaluates the reported Spacer/HORN input pattern through
`Z3_eval_smtlib2_string`.
- The test exercises the exact QE/projection path that previously
triggered the assertion.

```cpp
// Before
VERIFY(a.is_numeral(val, r));

// After
VERIFY(a.is_extended_numeral(val, r));
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 18:52:44 -07:00
Copilot
d99b6e7b08
Regenerate gh-aw lock workflows to remove stale model-multiplier runtime hook (#10099)
Multiple agentic workflows were failing at runtime with
`MODULE_NOT_FOUND` for `merge_awf_model_multipliers.cjs` under
`${RUNNER_TEMP}/gh-aw/actions`. The lock workflows had stale generated
runtime steps that no longer matched the current `gh-aw` actions bundle.

- **Root cause**
  - Generated `.lock.yml` workflows referenced a removed script:
- `node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs"`

- **Change**
- Recompiled all agentic workflow sources (`.github/workflows/*.md`)
with current `gh aw` tooling.
  - Checked in regenerated lock artifacts:
    - `.github/workflows/*.lock.yml`
    - `.github/aw/actions-lock.json`
- Result: stale `merge_awf_model_multipliers.cjs` invocations were
eliminated from generated workflows.

- **Representative diff shape**
  ```yaml
  # removed from generated lock workflows
  - GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" \
    node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs"
  ```

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 18:30:19 -07:00
Copilot
b1aa1e6ddc
Add z3regex Python module to translate Python regex syntax into Z3 regular expressions (#10095)
This PR adds a first-class Python API helper for expressing regex
constraints in familiar Python regex syntax and translating them into Z3
regex terms. The translator is implemented as a separate module
(`z3regex.py`) as requested, keeping regex conversion logic isolated
from core API files.

- **New Python regex translator module**
  - Adds `src/api/python/z3/z3regex.py`.
- Introduces `regex_to_re(pattern, flags=0, ctx=None)` to convert parsed
Python regex constructs into Z3 regex expressions.
- Supports core regular constructs (literals, classes/ranges,
alternation, grouping, quantifiers, categories, wildcard).
- Raises `NotImplementedError` for unsupported non-regular constructs
(e.g., features outside regular languages).

- **API/package integration**
  - Exposes the module via `src/api/python/z3/__init__.py`.
- Includes `z3/z3regex.py` in Python binding file copy/install flow in
`src/api/python/CMakeLists.txt`.

- **Doctest entrypoint support**
- Extends `src/api/python/z3test.py` with `z3regex` mode so translator
doctests can be run consistently with existing Python API doctest flows.

```python
from z3 import *
from z3.z3regex import regex_to_re

x = String("x")
r = regex_to_re(r"(ab|cd)+\d{2}")

s = Solver()
s.add(InRe(x, r))
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 18:26:59 -07:00
Copilot
d9ad23a188
Ensure Nightly release source archive always matches the workflow commit (#10094)
The Nightly release occasionally exposed a mismatch between the commit
shown on the release page and the downloaded “Source code” archive. Root
cause was non-deterministic `Nightly` tag/release state during publish.

- **Deterministic Nightly tag lifecycle**
- Serialize Nightly workflow runs with a dedicated concurrency group to
prevent overlapping tag/release mutations.
- Replace best-effort cleanup with explicit release-exists checks and
failure-on-cleanup-error behavior.
  - Remove orphan `Nightly` tags even when no release exists.

- **Pin release to the exact workflow commit**
- Force-update `Nightly` to `${{ github.sha }}` and push it before
release creation.
- Verify remote tag SHA (including annotated-tag dereference shape)
matches `${{ github.sha }}`.
- Create release with `--verify-tag` so source archives are generated
from the validated tag, not an implicit/stale target.

- **Workflow behavior change (deploy section)**
  ```yaml
  concurrency:
    group: nightly-release
    cancel-in-progress: false
  ```

  ```bash
  git tag -f Nightly "${{ github.sha }}"
  git push --force origin refs/tags/Nightly
  gh release create Nightly --verify-tag ...
  ```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 17:32:45 -07:00
Nikolaj Bjorner
606a06fa5f fix warnings
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-12 16:45:24 -07:00
Nikolaj Bjorner
f7afe8e025 tout -> out
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-12 16:41:05 -07:00
Nikolaj Bjorner
bcc176fc47 prepare ground for general projection 2026-07-12 15:59:56 -07:00
Nikolaj Bjorner
eaceded5f1
Issue 438 (#10085)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-12 13:35:57 -07:00
Copilot
ba7b12c18c
Fix .NET API memory leak: NativeContext finalizer, delegate lifetime, GC memory pressure (#10090)
Creating and disposing `Context` instances causes unbounded native
memory growth (~12 GB for 100k contexts) because `NativeContext` had no
finalizer — if `Dispose()` was never called, the native Z3 context
leaked permanently. Additionally, both `Context` and `NativeContext` had
delegate lifetime and thread-safety issues in their disposal paths.

## `NativeContext.cs`

- **Add missing finalizer** `~NativeContext() { Dispose(); }` — the root
cause of permanent leaks when callers don't explicitly dispose
- **Atomic disposal** via `Interlocked.Exchange(ref m_ctx, IntPtr.Zero)`
— prevents double-free when `Dispose()` is called concurrently (e.g.
user code + finalizer race)
- **Delegate lifetime** — capture `errHandler` locally +
`GC.KeepAlive(errHandler)` after `Z3_del_context`; the GC could
otherwise collect the error handler callback before the native
destructor finishes
- **Remove dead code** — `GC.SuppressFinalize` in `InitContext()` and
`GC.ReRegisterForFinalize` in `Dispose()` were both no-ops (no finalizer
existed); the latter would have caused infinite finalization with the
new finalizer
- **GC memory pressure** — `GC.AddMemoryPressure(8MB)` on init /
`GC.RemoveMemoryPressure(8MB)` on dispose, guarded by
`m_memPressureAdded` flag, so the GC schedules finalizers promptly when
contexts accumulate

## `Context.cs`

- **Thread-safe disposal** — capture `ctx` and `errHandler` inside the
existing `lock(this)` block; previously both were read outside the lock,
allowing two concurrent callers to both capture the same non-zero `ctx`
and double-free it
- **Delegate lifetime** — same `errHandler` + `GC.KeepAlive` pattern as
`NativeContext`
- **`GC.SuppressFinalize` placement** — moved inside the `if (m_ctx !=
IntPtr.Zero)` block, before cleanup, per .NET best practice
- **GC memory pressure** — same add/remove pattern, conditioned on
`!is_external` via `m_memPressureAdded` flag

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-11 21:15:36 -07:00
Copilot
c4b0fe33bc
test: replace SASSERT with ENSURE, remove Windows-only guards (#10086)
Unit tests relied on `SASSERT()` which is a no-op in release builds
(`DEBUG_CODE` wrapper), silently skipping all assertions outside debug
mode. Several test files were also gated behind `#ifdef _WINDOWS`,
making them dead code on Linux/macOS CI.

## Changes

- **`SASSERT` → `ENSURE` in 20 test files (200 occurrences)**: `ENSURE`
maps to `VERIFY` and always executes regardless of build type, ensuring
test assertions are active in both debug and release builds.

- **`src/test/diff_logic.cpp`**: Removed `#ifdef _WINDOWS` wrapping the
entire file. No Windows-specific APIs were used; the guard only
prevented compilation on non-Windows platforms.

- **`src/test/dl_product_relation.cpp`**: Removed `#ifdef _WINDOWS`
guard around `tst_dl_product_relation()`. The function body has no
platform dependencies.

- **`src/test/sat_local_search.cpp`**: Replaced `sscanf_s`
(MSVC-specific) with portable `sscanf`; added return-value check to
detect malformed input. Previously, `build_instance()` unconditionally
returned `false` on non-Windows, making the SAT local search test a
no-op on Linux/macOS.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-11 21:14:59 -07:00
Copilot
b0a77d2a58
[CMake] Guard Z3_API_LOG_SYNC against Z3_SINGLE_THREADED (#10088)
`Z3_API_LOG_SYNC` uses `std::mutex` to serialize API log writes across
threads. When combined with `Z3_SINGLE_THREADED`, all mutex operations
become no-ops (`SINGLE_THREAD` define strips them), silently defeating
the synchronization.

## Changes

- **`CMakeLists.txt`**: Emit `FATAL_ERROR` when both
`Z3_API_LOG_SYNC=ON` and `Z3_SINGLE_THREADED=ON` are set:
  ```
CMake Error: Z3_API_LOG_SYNC requires threading support and cannot be
combined with Z3_SINGLE_THREADED
  ```
- **`README-CMake.md`**: Expand `Z3_API_LOG_SYNC` description to clarify
its purpose and document the incompatibility with `Z3_SINGLE_THREADED`.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-11 19:20:15 -07:00
Nikolaj Bjorner
14e1dc9896
Fix segfault in horn on formulas with unused quantified variables (#6… (#10091)
…158)

rule_manager::mk_query eliminates gaps in de Bruijn indices caused by
unused quantified variables via a substitution that renumbers the
remaining variables contiguously. This was done in a single pass, but
var_subst applies the rewriter, which can simplify away further variable
occurrences (e.g. collapsing ite terms such as (ite true a b) or (ite c
x x)), introducing new gaps. The leftover null sort was then
dereferenced, asserting in debug and segfaulting in release.

Iterate the gap-elimination until the free variables are contiguous.
Each iteration either compacts the indices or strictly reduces the set
of used variables, so it terminates.

Fixes the crash reported for:
(assert (forall ((a Bool)(b Bool)(d (_ BitVec 1))(e (_ BitVec 1))(f (_
BitVec 1))(g Bool))
(= (= f (ite a (_ bv0 1) (ite true (_ bv0 1) (ite b e e)))) g)))
  (check-sat-using horn)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-11 19:17:18 -07:00
Copilot
dfd2f328f6
fix: update nightly.yml mac-build-x64 to macOS 13.3 for C++20 compatibility (#10082)
`std::format` (C++20 `<format>`) pulls in `std::to_chars` for
floating-point formatting, which is only available on macOS 13.3+. The
Mac x64 CI job was targeting macOS 13.0, causing build errors in
`src/ast/`.

## Changes

Updated `nightly.yml` to raise the macOS deployment target for the x64
build:

- **`MACOSX_DEPLOYMENT_TARGET`**: `"13.0"` → `"13.3"` in the
`mac-build-x64` job
- **`--os` flag**: `osx-13.0` → `osx-13.3` in the `mk_unix_dist.py`
invocation

This matches the existing `mac-build-arm64` job, which already targets
macOS 13.3, and allows `std::format` to be used freely in `src/ast/`
without workarounds.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-11 08:26:26 -07:00
Copilot
efe5e946f1
Fix unsigned overflow in parallel solver conflict budget escalation (#10076)
Z3 4.16.0 introduced a cube-and-conquer parallel solver that regressed
easy QF_LIRA problems from <1s to hanging indefinitely. Workers start
with a 1000-conflict budget and multiply by 1.5× on each timeout, but
after ~38 escalations the `unsigned` cast overflows, causing the budget
to oscillate chaotically (e.g. 3.27B → 618M → 927M → … never reaching a
stable large value). For sub-cubes that require more conflicts than any
value in the oscillation window, the worker loops forever.

## Changes

- **`src/smt/smt_parallel.h`** – `update_max_thread_conflicts()`:
replace raw `(unsigned)(mul * val)` cast with saturating arithmetic that
caps at `UINT_MAX`, eliminating the UB and the oscillation:
  ```cpp
// Before – UB when product > UINT_MAX, budget oscillates after ~38
escalations
  m_config.m_threads_max_conflicts =
(unsigned)(m_config.m_max_conflict_mul *
m_config.m_threads_max_conflicts);

  // After – saturates at UINT_MAX
double next = m_config.m_max_conflict_mul *
m_config.m_threads_max_conflicts;
m_config.m_threads_max_conflicts = (next >= (double)UINT_MAX) ? UINT_MAX
: (unsigned)next;
  ```

- **`src/solver/parallel_tactical.cpp`** – Identical
saturating-arithmetic fix in the `parallel_tactical2` worker's
`update_max_thread_conflicts()`, which is the code path actually
exercised for QF_LIRA problems.

- **`src/smt/smt_parallel.cpp`** – Worker's initial per-cube conflict
budget is now sourced from `m_smt_params.m_threads_max_conflicts` (the
user-visible `smt.threads.max_conflicts` parameter) instead of being
hardcoded to 1000, so users who leave the parameter at its default get
an unlimited initial budget matching Z3 4.12.x portfolio behaviour.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-10 15:25:57 -07:00
Nikolaj Bjorner
af0fa8ecf3 change bit-vector AC simplification n to true 2026-07-10 15:00:32 -07:00
Nikolaj Bjorner
c3170108e6 remove ad-hoc disabling skolem shadowing in pattern inference
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-10 14:25:20 -07:00
Nikolaj Bjorner
382abb786a disable term enumeration by default
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-10 14:23:40 -07:00
Nikolaj Bjorner
1d425e55cd bug fixes to ho_matching - offset alignment inv_var_shift, callback scopes should not be nested, allow bindings that are not ground
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-09 19:16:12 -07:00
Lev Nachmanson
ed6e2a241d
opt: validate strict optimization optima faithfully with delta-rational bounds (#10059)
## Problem

Maximizing/minimizing under a **strict** inequality has a delta-rational
optimum. For

```smt2
(declare-const r Real)
(assert (< r 1))
(maximize r)
(check-sat)
(get-objectives)
```

the optimum is the supremum `1 - epsilon`, but z3 reported `r = 0`.

The same defect makes shared-symbol objectives report a value matching
**neither the model nor the true optimum** (issue #10028 follow-up).
Minimal reproducer — a 6-mark Golomb ruler (a `>32`-arg `distinct`, so
the objective is coupled to EUF) with a strict real objective `obj >
x5`, whose true optimum is `17 + epsilon`:

| case | before | after |
|---|---|---|
| `maximize r`, `r < 1` | `0`  | `1 - epsilon`  |
| `minimize r`, `r > 1` | `0`  | `1 + epsilon`  |
| Golomb `minimize obj`, `obj > x5` | `35/2` / `7+eps`  | `17 +
epsilon`  |

## Root cause

`check_bound` validates the LP hint by asserting `objective >= optimum`.
For a supremum `1 - epsilon` this is a **lower** bound whose value
carries a **negative** infinitesimal `(1, -1)`.

No `lconstraint_kind` can express that. The kind->infinitesimal map only
yields the *matching-sign* cases — `GT` -> lower `(r, +1)`, `LT` ->
upper `(r, -1)` — or zero (`GE`/`LE`). The opposite-sign lower bound
`(r, -1)` (i.e. `r >= r0 - delta`) is a *relaxation* that no strict
inequality produces. `opt_solver::mk_ge` therefore projected the
`-epsilon` away, turning `r >= 1 - epsilon` into the over-strong,
unsatisfiable `r >= 1`; validation failed and the strictly smaller
current model value was reported instead.

## Fix — carry the infinitesimal faithfully through the bound pipeline

- **`lp_api::bound`** gains an `eps` component so `get_value` returns
the true delta value (no spurious rational fixed-variable equality is
propagated to EUF).
- **`lar_base_constraint`** stores its right-hand side as a
delta-rational `impq` pair; `rhs()` returns the rational component,
`bound_eps()` the infinitesimal one.
- **`lar_solver`** bound activation/update threads the whole `impq`
bound, so a lower bound `(r, -1)` can be asserted. `constraint_holds`
accounts for it using the **same** strict-bounds delta that flattens the
model, computed **once per model**.
- **`theory_lra::mk_ge`** builds a *fresh* predicate for the `(r, -1)`
lower bound (to avoid colliding with an already-internalized `v >= r`
literal) and attaches `eps = -1`. **`opt_solver::mk_ge`** passes the
unprojected value to `theory_lra` / `theory_mi_arith` /
`theory_inf_arith` (whose bounds are already `inf_rational`).

The pair machinery is what makes the supremum both representable
(optimum `1 - epsilon`) and validatable; the reported witness model
remains the flattened rational (`find_delta_for_strict_bounds`),
consistent with the existing epsilon semantics.

## Validation

- Strict optima correct: `1-eps`, `1+eps`, bounded `2<r<5 -> 5-eps`, and
lex/box variants.
- Integer optima and the #10028 shared-symbol cases unchanged (Golomb
n=6/7/8 -> 17/25/34, consistent with the model).
- Unit tests **92/92** (release); no new debug-suite failures.
- Opt regression corpus (73 files, `model_validate=true`)
**byte-identical** to baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-09 10:39:23 -07:00
Copilot
5a55ed7cfb
Nightly: fix Mac ARM64 build by targeting macOS 13.3 for std::format (#10075)
The Nightly **Mac ARM64 Build** job fails on current Xcode/libc++
because `std::format` pulls floating-point formatting paths that require
`std::to_chars` availability from macOS 13.3+. The job was still
compiling with a 13.0 deployment target.

- **Root cause**
  - ARM64 nightly job used:
    - `MACOSX_DEPLOYMENT_TARGET=13.0`
    - `mk_unix_dist.py --os=osx-13.0`
- This mismatched the effective libc++ requirements in the runner
toolchain.

- **Workflow change**
  - Updated only `.github/workflows/nightly.yml` for `mac-build-arm64`:
    - `MACOSX_DEPLOYMENT_TARGET: "13.0" -> "13.3"`
    - `--os=osx-13.0 -> --os=osx-13.3`

- **Resulting behavior**
- ARM64 nightly build target now aligns with the minimum OS level
required by the formatting code paths used by current compilation units.

```yaml
# .github/workflows/nightly.yml (mac-build-arm64)
env:
  MACOSX_DEPLOYMENT_TARGET: "13.3"

- name: Build
  run: python scripts/mk_unix_dist.py --dotnet-key=$GITHUB_WORKSPACE/resources/z3.snk --arch=arm64 --os=osx-13.3
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-09 08:45:51 -07:00
Lev Nachmanson
2c16f44c0a
[coz3-deepperf-fix] lp: hoist loop-invariant pivot reads in HNF pivot_column_non_fractional (#10073)
## Summary

Hoists loop-invariant matrix reads out of the hot inner loop of
`pivot_column_non_fractional` in the Hermite Normal Form (HNF)
computation used by z3's linear-arithmetic integer solver. The
arithmetic is unchanged; the patch only removes repeated
permutation-indexed `mpq` accesses from an O(n2) elimination loop.

## Hotspot

`lp::hnf_calc::pivot_column_non_fractional<M>` (`src/math/lp/hnf.h:130`)
performs the Bareiss-style fraction-free Gaussian elimination step over
a matrix `m`:

```cpp
for (unsigned j = r + 1; j < m.column_count(); ++j)
    for (unsigned i = r + 1; i < m.row_count(); ++i)
        m[i][j] = (r > 0) ? (m[r][r]*m[i][j] - m[i][r]*m[r][j]) / m[r-1][r-1]
                          : (m[r][r]*m[i][j] - m[i][r]*m[r][j]);
```

For `general_matrix`, every `m[a][b]` builds a temporary `ref_row` and
performs two permutation-array indirections (row and column permutation
lookups in `src/math/lp/general_matrix.h`) before the underlying vector
access. Inside this double loop the terms `m[r][r]`, `m[r-1][r-1]` and
`m[r][j]` are re-read on every iteration even though they are invariant,
so those redundant indexed reads dominate the loop cost.

## Change and complexity argument

Rows `<= r` are never written by this loop — it only assigns `m[i][j]`
for `i > r`, `j > r` — so the three pivot entries in rows `<= r` are
loop-invariant:

- `m[r][r]` and `m[r-1][r-1]` are invariant across **both** loops → bind
`m[r][r]` to a reference and take a pointer to `m[r-1][r-1]` once before
the outer loop. The pointer is `nullptr` when `r == 0`, which also
encodes the existing "no division" case with a single branch.
- `m[r][j]` is invariant across the inner `i` loop → hoist it to a
reference at the top of the outer `j` loop.
- `m[i][j]` is bound to a reference so it is indexed once per iteration
instead of three times (two reads + one write).

This turns **O(n2)** repeated permutation-indexed `mpq` reads into
**O(1)/O(n)** hoisted reads. The operands, operation order, and division
are identical to the original, so the computed matrix is bit-for-bit the
same.

## Measurements

Profiled with callgrind (z3 built with the same configuration,
`model_validate=true`) on a representative integer-arithmetic problem
that exercises the HNF cut generator:

- Target function instructions: **6,793,150,548 → 6,241,093,226**
(0.9187×; its share of total drops 15.2% → 14.5%).
- Total program instructions: **44,727,385,309 → 43,036,726,143**
(0.9622×).
- Wall-clock: **5.53s → 4.89s (~11.6% faster)**.
- Differential correctness preserved: identical solver output before and
after the change.

## Logic class

Integer linear arithmetic — the HNF-based cut generation path in the
`lp` int-solver.

<!-- gh-aw-workflow-id: coz3-deepperf-fix -->

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-09 07:02:47 -07:00
Nikolaj Bjorner
aa6dddbdf0 update pattern inference to allow patterns with variables outside of scope
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-08 19:48:10 -07:00
Nikolaj Bjorner
30a4a84c79 move verbose out to where it is used 2026-07-08 18:54:03 -07:00
Nikolaj Bjorner
3f6a57e8a8
Improve generation accounting (#10008) (#10009)
Details in original PR: https://github.com/Z3Prover/z3/pull/10007

---------

---------

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
Co-authored-by: Can Cebeci <can.cebeci99@gmail.com>
Co-authored-by: Can Cebeci <t-cancebeci@microsoft.com>
2026-07-08 15:43:41 -07:00
Nikolaj Bjorner
c56b2cbaa4 fix pattern inference to deal with binders properly, pin sorts in tptp_frontend 2026-07-08 15:29:05 -07:00
Nikolaj Bjorner
965db51b5c use patterns 2026-07-07 15:00:32 -07:00
Nikolaj Bjorner
7040a74d1a defer ho-matching to lazy mam
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-07 15:00:02 -07:00
Copilot
165a4a42bc
Fix debug-only well-sorted traversal freeing temporary assertions (#10067)
The Ubuntu `python make - MT` job was failing in unit tests because the
debug-time well-sorted check could invalidate freshly constructed
assertion expressions during solver entry. This surfaced as crashes in
`theory_dl` and `seq_rewriter`, not as logic bugs in those tests.

- **Root cause**
- `is_well_sorted` traversed the input through a temporary
`expr_ref`/`subterms` wrapper.
- For freshly built assertions passed directly into `assert_expr`, that
temporary ownership could drop the last refcount during validation and
free the AST before the solver used it.

- **Change**
- Reworked the traversal in `src/ast/well_sorted.cpp` to walk raw
`expr*` nodes explicitly.
- The checker now validates subterms without taking transient ownership
of the asserted expression.

- **Effect**
  - Debug validation remains intact.
- Temporary formulas survive the well-sorted check, so assertion-time
validation no longer corrupts the caller’s AST.

- **Representative change**
  ```cpp
  ptr_vector<expr> todo;
  expr_mark visited;
  todo.push_back(e);
  while (!todo.empty()) {
      expr* term = todo.back();
      todo.pop_back();
      if (visited.is_marked(term))
          continue;
      visited.mark(term, true);
      if (is_app(term)) {
          for (expr* arg : *to_app(term))
              if (!visited.is_marked(arg))
                  todo.push_back(arg);
          check_app(to_app(term));
      }
      else if (is_var(term)) {
          check_var(to_var(term));
      }
      else if (is_quantifier(term)) {
          check_quantifier(to_quantifier(term));
      }
  }
  ```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-07 13:26:44 -07:00
Lev Nachmanson
22c779c77c
[snapshot-regression-fix] Fix elim_uncnstr disabled by manager-wide has_type_vars() flag (iss-6260/small-2) (#10063)
## Summary

Fixes a completeness regression where `elim_uncnstr` was silently
disabled for ordinary (non-polymorphic) goals, detected by the
`snapshot-regression` corpus.

- **Originating discussion:**
https://github.com/Z3Prover/bench/discussions/3054
- **Benchmark:** `iss-6260/small-2.smt2` (corpus `Z3Prover/bench`,
`inputs/issues/iss-6260/`)
- **Divergence:** recorded oracle `sat` → current z3 produces `unknown`

### Divergence diff

```diff
--- small-2.expected.out (expected)
+++ produced (current z3)
@@ -1,3 +1,3 @@
-sat
+unknown
 (error "line 17 column 0: unexpected character")
 (error "line 17 column 1: unexpected character")
```

(The `(error ...)` lines are expected: the benchmark contains a stray
```` ``` ```` fence on line 17. Only the `sat` → `unknown` change is the
regression.)

## Root cause

`git bisect` over the regression window pins the flip to commit
`208cc5686` ("fix build"), which added `|| m().has_type_vars()` to the
`elim_uncnstr_tactic` guard and an equivalent `if (m.has_type_vars())
return;` to the `elim_unconstrained` simplifier.

`ast_manager::has_type_vars()` is a **manager-wide, sticky** flag: it is
set to `true` as soon as *any* type variable is created, and is never
reset. In particular `finite_set_decl_plugin::init()` creates type
variables `A`/`B` to define its polymorphic signatures. Those type
variables never occur in the user's assertions, but once the finite_set
plugin is initialized — which happens while processing this benchmark —
the flag is globally `true` (confirmed by instrumenting `mk_type_var`:
the only type vars created for this benchmark are the finite_set
signature vars `A` and `B`).

As a result `elim_uncnstr` bails out for goals that contain **no**
polymorphic terms at all, i.e. it is effectively disabled. Unconstrained
subterms that used to be eliminated now reach the theory solvers.

For this benchmark the (single) assertion is `(not (xor (>= x 0) (>= x1
0) (>= x 0) x4 (str.contains ...)))`. The duplicated `(>= x 0)` cancels
(`a xor a = false`), and the free Boolean `x4` can fix the parity
regardless of the value of the `str.contains` term, so the goal is
trivially `sat`. That `str.contains`/`str.replace_re` subterm is
unconstrained and was previously removed by `elim_uncnstr`; without that
elimination it reaches `theory_seq`, which marks `str.replace_re` as
unhandled and gives up in `final_check` → `unknown` (`incomplete (theory
seq)`).

## Fix

Make the guard precise. Keep `has_type_vars()` as a cheap pre-filter
(matching existing usage in `ast_translation.cpp` and
`ast_manager::has_type_var`), but only bail out when the goal / asserted
formulas **actually** contain type-variable typed terms, using the
existing `polymorphism::util::has_type_vars(expr*)`.

This preserves the polymorphism crash-protection (goals with genuine
type-variable terms still skip `elim_uncnstr`) while restoring
`elim_uncnstr` for the vast majority of goals that merely triggered a
polymorphic-plugin initialization. Both twin guards (the `elim_uncnstr`
tactic and the `elim_unconstrained` simplifier) are fixed consistently.

## Validation

Built this checkout and re-ran the benchmark (step 5 of the fixer
workflow):

- `./configure && make -C build -j$(nproc)` — Z3 version 4.17.0.
- Unpatched master reproduced the divergence: `z3 -T:20
iss-6260/small-2.smt2` → `unknown`.
- After the fix, `z3 -T:20 inputs/issues/iss-6260/small-2.smt2` produces
**exactly** the recorded oracle:

```
sat
(error "line 17 column 0: unexpected character")
(error "line 17 column 1: unexpected character")
(error "line 17 column 2: unexpected character")
```

- Regression sanity checks: sibling `iss-6260/small.smt2` unchanged
(`sat`); plain arithmetic unconstrained goals unchanged; polymorphic
goals with genuine type-variable terms (including `declare-type-var` and
forcing `(check-sat-using (then elim-uncnstr smt))`) still solve without
crashing — the guard still fires for those.

Opened as a **draft** for human review.




> Generated by [Fix a Z3 snapshot-regression
divergence](https://github.com/Z3Prover/bench/actions/runs/28844840657)
· 861.2 AIC · ⌖ 45.8 AIC · ⊞ 8.9K ·
[◷](https://github.com/search?q=repo%3AZ3Prover%2Fz3+%22gh-aw-workflow-id%3A+snapshot-regression-fixer%22&type=pullrequests)

<!-- gh-aw-agentic-workflow: Fix a Z3 snapshot-regression divergence,
engine: copilot, version: 1.0.63, model: claude-opus-4.8, id:
28844840657, workflow_id: snapshot-regression-fixer, run:
https://github.com/Z3Prover/bench/actions/runs/28844840657 -->

<!-- gh-aw-workflow-id: snapshot-regression-fixer -->
<!-- gh-aw-workflow-call-id: Z3Prover/bench/snapshot-regression-fixer
-->

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-07 13:02:37 -07:00
Can Cebeci
5fc2b04dea
Mark quantifier instances that lead to conflicts as relevant (#10064)
This patch fixes a corner case where quantifier conflicts can create
fresh terms that aren't marked as relevant.
I couldn't easily produce a minimal query that this patch turns stable,
nor did the patch stabilize the query I have been working on, but I
think the example below still illustrates the problem:

```
(set-option :auto_config false)
(set-option :type_check true)
(set-option :smt.case_split 3)
(set-option :smt.mbqi false)

(declare-fun R (Int) Bool)
(declare-fun S (Int) Bool)
(declare-fun dummy (Int) Bool)

(assert (or (R 0) (dummy 0)))

(assert (forall ((x Int)) (! 
    (and (not (R x)) (not (S x)))
    :pattern ((R x))
    :qid not_r_not_s
)))

(assert (forall ((x Int)) (! 
    (S x)
    :pattern ((S x))
    :qid s_true
)))
(check-sat)
```

The query is unstable (due to the same interaction between relevancy and
triggers in https://github.com/Z3Prover/z3/issues/7444): if the solver
assigns `(dummy 0)` first, it returns `unknown`.

If the solver assigns `(R 0)` first, we would expect an `unsat`. The
current implementation returns `unknown` because `not_r_not_s` leads to
a quantifier conflict, which creates `S(x)` without marking it (or any
of its ancestors) as relevant.

---------

Co-authored-by: Can Cebeci <t-cancebeci@microsoft.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-07 13:01:44 -07:00
Copilot
5f70ba10a9
Remove temporary LP batching parameter and make batched explanation unconditional (#10066)
This removes the temporary `lp.batch_explain_fixed_in_row` knob added
with the recent LP changes. The batched fixed-column explanation path is
kept as the only implementation, matching the follow-up review comments.

- **Problem**
- The new LP setting exposed a temporary fallback path that is no longer
needed.
- Keeping both paths added parameter surface area and settings plumbing
without a lasting behavioral distinction.

- **Changes**
  - **Remove parameter definition**
- Delete `lp.batch_explain_fixed_in_row` from LP parameter generation.
  - **Remove settings plumbing**
- Drop the stored field, accessor methods, and parameter update wiring
from `lp_settings`.
  - **Keep batched explanation as default behavior**
    - Remove the runtime branch in `lar_solver::explain_fixed_in_row`.
- Always linearize fixed-column witnesses together in a single
dependency pass.

- **Resulting simplification**
- The solver no longer carries a dead configuration toggle for fixed-row
explanation.
- The batched dependency-linearization path remains intact and is now
the sole code path.

```c++
void lar_solver::explain_fixed_in_row(unsigned row, explanation& ex) {
    auto& witnesses = m_imp->m_tmp_witnesses;
    witnesses.reset();
    for (auto const& c : get_row(row)) {
        if (!column_is_fixed(c.var()))
            continue;
        const column& ul = m_imp->m_columns[c.var()];
        witnesses.push_back(ul.lower_bound_witness());
        witnesses.push_back(ul.upper_bound_witness());
    }
    m_imp->m_tmp_dependencies.reset();
    m_imp->m_dependencies.linearize(witnesses, m_imp->m_tmp_dependencies);
    for (auto ci : m_imp->m_tmp_dependencies)
        ex.push_back(ci);
}
```

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2026-07-07 13:01:10 -07:00
Nikolaj Bjorner
334f4fa32b reduce leaks for sorts 2026-07-07 12:04:14 -07:00