3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-04 22:36:10 +00:00

nlsat/anum: share mutation-aware merge sort in one helper (#10006)

## Summary

Follow-up to #10001 addressing @NikolajBjorner's review comment:

> isn't this nearly identical AI generated code to the other file? There
has to be some modular approach to deal with sorting vectors?

#10001 introduced two nearly-identical copies of a bounds-safe,
mutation-aware index-permutation merge sort:
- `algebraic_numbers.cpp::merge_sort_roots_perm`
- `nlsat/levelwise.cpp::merge_sort_perm`

Both exist because the comparator (`anum_manager::compare`/`lt`) is
**not pure**: it mutates the algebraic numbers it compares (refining
isolating intervals) and may throw on the resource limit, which makes
`std::sort` undefined behavior (the original SIGSEGV).

## Change

Extract the algorithm into a single shared helper
`util/index_sort_with_mutations.h` (`stable_index_merge_sort`). The long
rationale for why `std::sort` is unsafe and merge sort is safe now lives
in exactly one place. Both call sites become thin wrappers that build
the scratch buffer and forward their local comparator.

No behavioral change: same stable O(n log n) merge sort over an index
permutation.

## Verification

CMake/Ninja Release build:
- `test-z3 /seq algebraic_numbers` — PASS
- `test-z3 /seq algebraic` — PASS
- NRA/NIA smoke solves with `nlsat.lws=true` return expected sat/unsat.

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Lev Nachmanson 2026-06-30 08:40:33 -07:00 committed by GitHub
parent 32d806d500
commit 2490e86d3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 98 additions and 76 deletions

View file

@ -22,6 +22,7 @@ Notes:
#include "util/mpbqi.h"
#include "util/timeit.h"
#include "util/common_msgs.h"
#include "util/index_sort_with_mutations.h"
#include "math/polynomial/algebraic_numbers.h"
#include "math/polynomial/upolynomial.h"
#include "math/polynomial/sexpr2upolynomial.h"
@ -593,49 +594,24 @@ namespace algebraic_numbers {
}
}
// Bounds-safe, mutation-aware merge sort of an index permutation.
//
// We deliberately avoid std::sort: the comparator (lt -> compare) is NOT pure
// -- it MUTATES the algebraic numbers it compares by refining their isolating
// intervals (possibly collapsing a root to a rational), and can hit the
// resource limit and throw. That refinement is monotone toward the one true
// real order (a decided sign is permanent), but a comparison can transiently
// strengthen from "uncertain" to "decided". std::sort (introsort) relies on a
// comparator-derived sentinel and re-compares a pivot repeatedly; a
// strengthening invalidates the sentinel and its *unguarded* insertion pass
// walks off the array -> out-of-bounds read -> SIGSEGV (a try/catch could not
// help). Merge sort is safe because it never re-compares a pair and uses no
// comparator-derived sentinel: every loop bound is arithmetic, so an
// inconsistent comparator can only yield a wrong order, never an OOB access or
// a hang. Runs are ordered by decided signs that later refinement cannot
// un-decide, so deeper merges stay correct and inherit cheaper intervals.
// O(n log n) comparisons, O(n) scratch. See also nlsat/levelwise.cpp.
// Sort an index permutation with a bounds-safe, mutation-aware merge
// sort. The comparator (compare/lt) is NOT pure: it MUTATES the
// algebraic numbers it compares (refining their isolating intervals) and
// may throw on the resource limit, so std::sort would be undefined
// behavior here. See util/index_sort_with_mutations.h for the rationale.
void merge_sort_roots_perm(numeral_vector & r, unsigned_vector & perm) {
unsigned n = perm.size();
if (n < 2)
return;
unsigned_vector tmp;
tmp.resize(n, 0);
unsigned_vector scratch;
scratch.resize(n, 0);
// Strict, total, stable index comparator: decided sign first, then index
// tiebreak (covers the equal/limit case so the order stays deterministic).
auto idx_lt = [&](unsigned x, unsigned y) {
::sign s = compare(r[x], r[y]);
return s != sign_zero ? s == sign_neg : x < y;
};
for (unsigned width = 1; width < n; width <<= 1) {
for (unsigned lo = 0; lo < n; lo += (width << 1)) {
unsigned mid = std::min(lo + width, n);
unsigned hi = std::min(lo + (width << 1), n);
unsigned i = lo, j = mid, k = lo;
while (i < mid && j < hi)
tmp[k++] = idx_lt(perm[j], perm[i]) ? perm[j++] : perm[i++];
while (i < mid)
tmp[k++] = perm[i++];
while (j < hi)
tmp[k++] = perm[j++];
}
perm.swap(tmp);
}
stable_index_merge_sort(perm.data(), scratch.data(), n, idx_lt);
}
void sort_roots(numeral_vector & r) {