From 9fd31080c53777ebfc7601d8fd8088a0b4a311ec Mon Sep 17 00:00:00 2001 From: Kim Morrison Date: Sat, 11 Jul 2026 18:58:35 +0000 Subject: [PATCH 1/2] feat(hex-roots): retune Phase-4 wall models and record inconclusive scientific run Retune the 13 parametric bench registrations in bench/HexRoots/Bench.lean from pure exact-dyadic op-counts to wall-time models (SPEC op-count contract times the family's working-bit-length B growth where that growth is asymptotically significant on the schedule), and record the scientific run, comparators, profile, and time budgets in reports/hexroots-performance.md. Cost-model derivation for the changed complexity declarations. The whole-polynomial drivers runIsolateAll and runIsolate and the three compare-group targets (runIsolateNk, runIsolatePellet, runIsolateNkThenPellet) change from n^3 to n^5. The SPEC complexity contract is O(n^3 * B^2) bit operations: n^3 is the op count (up to O(n) components each driven through O(n) subdivision levels of O(n^2)-per-witness work), and the working bit-length reaches B = Theta(n) (target precision plus the seeded n*log||p|| term plus the Taylor coefficients' Theta(prec*n) denominator growth), entering the growing-precision dyadic arithmetic (notably the invAtPrec reciprocal) as a schoolbook O(B^2) per-op factor. O(n^3 * B^2) with B = Theta(n) gives the n^5 wall model; the bit-growth is folded into the exponent because it is asymptotically significant here, unlike the flat allocation-dominated band of the primitives (runTaylor, runWitnessCheck, runNkWitnessCheck, runNewtonSquare, runRefine1, runCertify, runMahlerPrec), whose operands stay sub-word on their schedules so their op-count models (n, n^2) are unchanged, and runRefineTo, whose fixed-degree t-bit schoolbook multiply keeps t^2. Phase 4 is NOT claimed: the scientific run (commit b08a66cce522, chungus2) returned inconclusive verdicts for 9 of 13 registrations and two SPEC time budgets fail (degree 50 @ prec 64: 495.85 s vs < 10 s; degree 100 @ prec 128: did not complete). libraries.yml keeps HexRoots.done_through: 3. The nine inconclusive verdicts are benchmark-family / schedule / budget findings (non-integer Taylor centre transition band, startup-dominated microsecond witness benches, seeded-family non-monotonicity, Newton-doubling precision quantisation, narrow-range compare group), diagnosed per registration in the report's Concerns section; none is a wrong-asymptotic implementation bug. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01SpYSH4swVcLJbN2Bn6NfSP --- HexRoots/SPEC/hex-roots.md | 11 + bench/HexRoots/Bench.lean | 265 +- libraries.yml | 21 + .../bench-results/hex-roots-b08a66cce522.json | 2028 ++++++++++++++ .../hex-roots-flint-b08a66cce522.json | 125 + .../hex-roots-comparator-seeded-dense.svg | 2321 +++++++++++++++++ reports/hexroots-performance.md | 353 +++ scripts/bench/hexroots_flint_compare.py | 103 + scripts/check_phase4.py | 9 +- scripts/plots/hex-roots-comparator.py | 80 + 10 files changed, 5236 insertions(+), 80 deletions(-) create mode 100644 reports/bench-results/hex-roots-b08a66cce522.json create mode 100644 reports/bench-results/hex-roots-flint-b08a66cce522.json create mode 100644 reports/figures/hex-roots-comparator-seeded-dense.svg create mode 100644 reports/hexroots-performance.md create mode 100644 scripts/bench/hexroots_flint_compare.py create mode 100644 scripts/plots/hex-roots-comparator.py diff --git a/HexRoots/SPEC/hex-roots.md b/HexRoots/SPEC/hex-roots.md index 62351b5a3..7b71f17de 100644 --- a/HexRoots/SPEC/hex-roots.md +++ b/HexRoots/SPEC/hex-roots.md @@ -750,6 +750,17 @@ and the Phase-4 external performance comparator; it is not wired into merge-facing CI. SageMath is not used (per SPEC/testing.md's Sage policy). +For Phase 4 both named comparators are classified `informational` (per +[SPEC/benchmarking.md §Comparator classification](../../SPEC/benchmarking.md#comparator-classification-gating-vs-informational)): +python-flint `fmpz_poly.complex_roots` and MPSolve are multiprecision +ball/float engines computing approximate root inclusions, structurally +different from this library's decidable exact-integer Pellet / +Newton-Kantorovich certificates, so their ratios orient but do not gate, and +the yardstick is the "Time budgets" targets above rather than a constant-factor +goal. Neither is `gating`; MPSolve is additionally scheduled-only. The +classifications and rationales are mirrored in +`libraries.yml: HexRoots.phase4.comparators`. + ## Complexity contract Write `n = deg p` and `B = prec + n · log ‖p‖∞` for the working diff --git a/bench/HexRoots/Bench.lean b/bench/HexRoots/Bench.lean index abc253244..0a5d4702b 100644 --- a/bench/HexRoots/Bench.lean +++ b/bench/HexRoots/Bench.lean @@ -39,31 +39,75 @@ parameter (its degree): Newton-step registrations, whose squares are centred on the integer root `1` of the family. -The declared complexity models are the textbook *exact-dyadic-operation counts* -from `HexRoots/SPEC/hex-roots.md § Complexity contract`. They do not fold in the -working bit-length `B = prec + n·log‖p‖∞`, which grows with the parameter; the -scientific wall-time verdicts and their reconciliation against these op-count -models are a scheduled-hardware concern (as for the other Phase 4 benches), -not a merge-gating one. The `verify` smoke gate only exercises each +The declared complexity models are *wall-time* models: the textbook +exact-dyadic-operation count from `HexRoots/SPEC/hex-roots.md § Complexity +contract` multiplied by the family's working-bit-length growth +`B = prec + n·log‖p‖∞` where that growth is asymptotically significant on the +registered schedule (the reconciliation each derivation comment records). A +family whose operands stay in the flat, allocation-dominated GMP band across +its schedule keeps the op-count model unchanged (the bit-growth sits in the +constant); a family whose precision or coefficient bit-length grows with the +parameter folds the growth into the exponent. Each registration's comment +states which case it is and why. The `verify` smoke gate only exercises each registration at parameters `0` and `1`. +The scientific verdicts and their op-count-vs-wall reconciliation are recorded +in `reports/hexroots-performance.md`; several registrations return +*inconclusive* on their reachable schedules for reasons the report's Concerns +section documents (the fixed non-integer Taylor centre's `Θ(n)` denominator +growth, the seeded family's degree-dependent root geometry, the `refineTo` +Newton-doubling precision quantisation, and the startup-dominated microsecond +band of the small-degree witness benches). + Registrations (each with an adjacent cost-model derivation comment): -* `runTaylor` — exact Taylor shift, `O(n²)`. +* `runTaylor` — exact Taylor shift, `O(n²)` exact-dyadic operations (the + fixed non-integer centre's `Θ(n)`-bit denominator growth stays in the + allocation-dominated band on the schedule). * `runWitnessCheck`, `runNkWitnessCheck` — Pellet and Newton-Kantorovich atom - witnesses, `O(n²)` (Taylor shift dominates). -* `runMahlerPrec` — separation precision, `O(n · log‖p‖∞)`. -* `runNewtonSquare` — speculative Newton step, `O(n²)`. + witnesses, `O(n²)` (Taylor shift dominates; integer centre, sub-word + operands, flat band). +* `runMahlerPrec` — separation precision, `O(n · log‖p‖∞)`, word-size integer + arithmetic (no bit-growth). +* `runNewtonSquare` — speculative Newton step, `O(n²)` (integer-centre Taylor + shift dominates, flat band). * `runRefine1`, `runCertify` — one subdivision round and one certification - attempt on a mid-refinement component, `O(n²)`. -* `runIsolateAll` — `isolateAll?` at target `32`, `O(n³)`. -* `runIsolate` — `isolate` to the `separationDepth` floor, `O(n³)`. + attempt on a mid-refinement component, `O(n²)` (low-precision centres, + sub-word operands, flat band). +* `runIsolateAll` — `isolateAll?` at target `32`, `O(n³·B²) = O(n⁵)` with the + working bit-length `B = Θ(n)`. +* `runIsolate` — `isolate` to the `separationDepth` floor, `O(n³·B²) = O(n⁵)`. * `runRefineTo` — `DyadicRootIsolation.refineTo?` from `≈32` to a parametric - target `t`, `O(t²)`. + target precision `t`, `O(t²)` schoolbook bit-cost at fixed degree. * `runSameRoot` — `RefinedIsolation.sameRoot`, a single dyadic comparison (fixed benchmark, microseconds). * `runIsolateNk`, `runIsolatePellet`, `runIsolateNkThenPellet` — the `compare` - group over `linProdPoly`; all three must agree on the strategy-invariant hash. + group over `linProdPoly`, `O(n³·B²) = O(n⁵)`; all three must agree on the + strategy-invariant hash. + +External comparators (both `informational`, per +`libraries.yml: HexRoots.phase4.comparators`): + +* **python-flint** `fmpz_poly.complex_roots()` (the SPEC's ci-tier oracle, + which returns certified Arb balls with multiplicities) is timed as a + process-call comparator on the same seeded degree ladder the + whole-polynomial drivers use; the ratio `hex isolateAll?@32 / flint` per + degree is recorded in `reports/hexroots-performance.md`. It is + `informational`, not gating: FLINT's `complex_roots` is a multiprecision + ball-arithmetic engine, structurally different from this library's + decidable exact-integer Pellet / Newton-Kantorovich certificates, so the + SPEC's time budgets — not a constant-factor `1×` goal — are the yardstick. + Reproduce with `scripts/bench/hexroots_flint_compare.py` under a + `python-flint ≥ 0.9.0` virtualenv (subprocess, wall clock, per-call + overhead measured on a trivial input). +* **MPSolve** (Bini–Fiorentino `mpsolve`, the SPEC's local-tier and Phase-4 + external performance comparator) is classified `informational` and + **scheduled-only**: it is not wired in this PR. Required environment: the + `mpsolve` CLI (`unisa-cs/mpsolve`, built with GMP) on `PATH`, driven on the + same seeded ladder via its `-au -Gi` isolate-mode output. Rationale for the + informational class: MPSolve is a multiprecision-float C library computing + approximate root inclusions, structurally different from this library's + integer-certified Lean witnesses, so its ratio orients but does not gate. -/ namespace Hex.RootsBench @@ -320,10 +364,23 @@ def prepRefineTo (target : Nat) : Option (DyadicRootIsolation refinePoly) × Int /-! ### `taylor` / `mahlerPrec` : dense seeded family -/ /- -`taylor` produces `p(X + z) = Σ cₖ Xᵏ` by repeated synthetic division: the -`k`-indexed outer pass runs an inner Horner sweep of length `n − 1 − k`, so the -total is `Σ_k (n − 1 − k) = O(n²)` exact Gaussian-dyadic multiply/adds. Degree -`n` is the benchmark parameter, so the op-count model is `n²`. +Cost model. `taylor` produces `p(X + z) = Σ cₖ Xᵏ` by repeated synthetic +division: the `k`-indexed outer pass runs an inner Horner sweep of length +`n − 1 − k`, so the total is `Σ_k (n − 1 − k) = O(n²)` exact Gaussian-dyadic +multiply/adds (degree `n` is the parameter). Bit-growth: the fixed centre +`z = 1/4 + i/8 = 2^{−3}(2 + i)` is a *non-integer* dyadic, so `z^{j−k}` carries +denominator `2^{3(j−k)}` and the coefficient `cₖ` reaches working bit-length +`B = Θ(n)` (the `2^{3n}` denominator dominates the `binomial ~ 2^n` numerator). +Each inner op multiplies a `B`-bit operand by the fixed `3`-bit centre, a +schoolbook `O(B)` cost, so the wall bit-cost is `O(n²·B) = O(n³)` in the +multiplication-bound limit. On the registered `16..256` schedule the operands +are `≤ 12` GMP words, still in the allocation-dominated transition where wall +tracks the `O(n²)` operation count with a sub-linear residual (measured +`~n^{2.25}`, short of the `n³` asymptote); the op-count `n²` is the declared +wall model per the SPEC contract, with the transition residual reported as a +Concern. This is the same Taylor-shift shape as hex-real-roots' +`runMobiusTransform` but with a non-integer centre, whose denominator growth +is what keeps it out of the flat band that made that one consistent at `n²`. -/ setup_benchmark runTaylor n => n * n with prep := seededPoly @@ -337,11 +394,14 @@ setup_benchmark runTaylor n => n * n } /- -`mahlerPrec` evaluates the closed-form Mahler/Landau separation bound: a fixed -number of `ceilLog2` calls plus integer multiplies over the degree `n` and the -coefficient-norm `log‖p‖∞`. The SPEC contract is `O(n · log‖p‖∞)`; the seeded -family holds `‖p‖∞ ≤ 10` fixed, so the `log‖p‖∞` factor is a constant over this -schedule and the op-count model reduces to linear in `n`. +Cost model. `mahlerPrec` evaluates the closed-form Mahler/Landau separation +bound: the `coeffAbsMax` scan is `O(n)` `Nat.max` steps, plus a fixed number +of `ceilLog2` calls and word-size integer multiplies forming `t`. The SPEC +contract is `O(n · log‖p‖∞)`; the seeded family holds `‖p‖∞ ≤ 10` fixed, so +`log‖p‖∞` is constant and the op count reduces to linear in `n`. Bit-growth: +every operand (the coefficients `≤ 10`, the degree `n`, the `ceilLog2` results, +the sum `t = O(n)`) fits in a single machine word across `16..256`, so `B` is +constant and the wall model equals the op count `n`. -/ setup_benchmark runMahlerPrec n => n with prep := seededPoly @@ -357,11 +417,16 @@ setup_benchmark runMahlerPrec n => n /-! ### witness checks / Newton step : root-centred `linProdPoly` -/ /- -`witnessCheck` computes the exact Taylor coefficients at the square's centre -(the `O(n²)` shift, which dominates) and then, for each of the three test -radii, a single `O(n)` fold over the coefficients. The op-count model is `n²`. -The square is centred on the integer root `1` of `linProdPoly`, so the witness -actually fires at every scheduled degree. +Cost model. `witnessCheck` computes the exact Taylor coefficients at the +square's centre (the `O(n²)` shift, which dominates) and then, for each of the +three test radii, a single `O(n)` fold over the coefficients, so the op count +is `n²`. Bit-growth: the square is centred on the *integer* root `1` of +`linProdPoly`, so the centre `z = (1, 0)` has precision `0` and `z^{j−k}` +introduces no denominator; the Taylor coefficients are the (integer) +elementary-symmetric coefficients of `p(X+1)`, of bit-length `~log(n!) = +O(n log n)`, which stays under one GMP word (`≤ 44` bits at `n = 16`) across +the `2..16` schedule. Operands are sub-word, per-op cost flat, so the wall +model is the op count `n²`. -/ setup_benchmark runWitnessCheck n => n * n with prep := linProdPoly @@ -375,9 +440,12 @@ setup_benchmark runWitnessCheck n => n * n } /- -`nkWitnessCheck` has the same `O(n²)` Taylor-shift-dominated shape as -`witnessCheck`, plus one `Dyadic.invAtPrec` reciprocal and a single `O(n)` -radial-Lipschitz fold. Op-count model `n²`, root-centred on `linProdPoly`. +Cost model. `nkWitnessCheck` has the same `O(n²)` Taylor-shift-dominated shape +as `witnessCheck`, plus one `invFloor` reciprocal and a single `O(n)` +radial-Lipschitz fold, so the op count is `n²`. Bit-growth: identical to +`witnessCheck` — integer centre `(1, 0)`, no denominator, integer Taylor +coefficients of `O(n log n)` bits staying sub-word (`≤ 44` bits) across +`2..16`, so operands are flat and the wall model is the op count `n²`. -/ setup_benchmark runNkWitnessCheck n => n * n with prep := linProdPoly @@ -391,10 +459,13 @@ setup_benchmark runNkWitnessCheck n => n * n } /- -`newtonSquare` computes the Taylor coefficients at the centre (the `O(n²)` -shift), reads `c₀, c₁`, and does one `Dyadic.invAtPrec` reciprocal plus a -constant amount of Gaussian-dyadic arithmetic. The Taylor shift dominates, so -the op-count model is `n²`. +Cost model. `newtonSquare` computes the Taylor coefficients at the centre (the +`O(n²)` shift), reads `c₀, c₁`, and does one `Dyadic.invAtPrec` reciprocal plus +a constant amount of Gaussian-dyadic arithmetic. The Taylor shift dominates, so +the op count is `n²`. Bit-growth: integer centre `(1, 0)` on `linProdPoly`, no +denominator, integer Taylor coefficients of `O(n log n)` bits staying sub-word +across `2..16`; the reciprocal is at precision `~2·prec = 24` bits, also +sub-word. Operands flat, so the wall model is the op count `n²`. -/ setup_benchmark runNewtonSquare n => n * n with prep := linProdPoly @@ -410,11 +481,16 @@ setup_benchmark runNewtonSquare n => n * n /-! ### refinement primitives : mid-refinement component of the seeded family -/ /- -`refine1` subdivides each square of the component four ways and runs the `T₀` -`rootFree` exclusion — one Taylor shift, `O(n²)` — on each child, then glues the -survivors. For a component of a bounded number of squares this is a bounded -number of `O(n²)` shifts, so the op-count model is `n²` in the polynomial -degree `n`. +Cost model. `refine1` subdivides each square of the component four ways and +runs the `T₀` `rootFree` exclusion — one Taylor shift, `O(n²)` — on each child, +then glues the survivors. For a component of a bounded number of squares this +is a bounded number of `O(n²)` shifts, so the op count is `n²` in the degree +`n`. Bit-growth: the mid-refinement component sits two levels below `cauchy`, +so its square centres have low precision (`~cauchy.prec + 2`, a few bits) and +the seeded coefficients are `≤ 10`; the Taylor coefficients reach only `O(n)` +bits (dominated by `|z|^n` with `|z|` near the root bound `~11`), which stays +sub-word (`≤ 42` bits at `n = 12`) across the `4..12` schedule. Operands flat, +so the wall model is the op count `n²`. -/ setup_benchmark runRefine1 n => n * n with prep := midComponent @@ -428,13 +504,15 @@ setup_benchmark runRefine1 n => n * n } /- -`certify?` under the default `nkThenPellet` strategy first tries the -Newton-Kantorovich witness on the doubled enclosing square: one +Cost model. `certify?` under the default `nkThenPellet` strategy first tries +the Newton-Kantorovich witness on the doubled enclosing square: one `nkWitnessCheck` (`O(n²)`) and one speculative `newtonSquare` (`O(n²)`). On a -mid-refinement component localised near a root this NK path fires, so the -op-count model is `n²`. (The Pellet fallback, taken only when NK does not fire, -loops over `k ≤ deg p` and is `O(n³)`; it is not the path this fixture -measures.) +mid-refinement component localised near a root this NK path fires, so the op +count is `n²`. (The Pellet fallback, taken only when NK does not fire, loops +over `k ≤ deg p` and is `O(n³)`; it is not the path this fixture measures.) +Bit-growth: same low-precision seeded mid-refinement component as `refine1` — +`O(n)`-bit Taylor coefficients staying sub-word across `4..12` — so operands +are flat and the wall model is the op count `n²`. -/ setup_benchmark runCertify n => n * n with prep := midComponent @@ -450,16 +528,21 @@ setup_benchmark runCertify n => n * n /-! ### whole-polynomial drivers : dense seeded family -/ /- -`isolateAll?` refines the Cauchy component to disjoint certified atoms at target -precision `32`. The SPEC heuristic is `O(n³ · B²)` bit operations for degree `n` -with well-separated roots; the op-count model (dropping the working bit-length -`B`) is `n³`: up to `O(n)` components, each driven through `O(n)` subdivision -levels of `O(n²)`-per-witness work amortised by the speculative Newton jumps. -The seeded family's distinct irrational roots force genuine subdivision, so the -driver exercises the separation machinery rather than a rational-root -short-circuit. +Cost model. `isolateAll?` refines the Cauchy component to disjoint certified +atoms at target precision `32`. The op count is `n³`: up to `O(n)` components, +each driven through `O(n)` subdivision levels of `O(n²)`-per-witness work +amortised by the speculative Newton jumps. Bit-growth is asymptotically +significant here: the working bit-length reaches `B = prec + n·log‖p‖∞ = Θ(n)` +(precision `~32` at the certifying level, plus the seeded `n·log 10` term, and +the Taylor coefficients' `Θ(prec·n)` denominator growth), and the +growing-precision dyadic arithmetic (notably the `invAtPrec` reciprocal) is +schoolbook `O(B²)`. The SPEC heuristic `O(n³·B²)` with `B = Θ(n)` gives the +wall model `n⁵`. The seeded family's distinct irrational roots force genuine +subdivision, but their *degree-dependent* root geometry (some degrees have +much closer roots than neighbours) makes the wall time non-monotonic in `n`, +which the report's Concerns section flags as a fit-quality limitation. -/ -setup_benchmark runIsolateAll n => n * n * n +setup_benchmark runIsolateAll n => n * n * n * n * n with prep := seededPoly where { paramFloor := 4 @@ -471,14 +554,18 @@ setup_benchmark runIsolateAll n => n * n * n } /- -`isolate` runs `isolateAll?` from the Cauchy component to +Cost model. `isolate` runs `isolateAll?` from the Cauchy component to `max atom_prec (separationDepth p)` and requires every result to be an atom. -With `atom_prec = 0` the target is the `separationDepth` floor, which grows with -the degree, so this is the deeper of the two whole-polynomial drivers. Same -`O(n³)` op-count model as `isolateAll?`. The schedule caps lower (degree `16`) +With `atom_prec = 0` the target is the `separationDepth` floor, which grows +with the degree, so this is the deeper of the two whole-polynomial drivers. +Same `n³` op count as `isolateAll?`. Bit-growth: the target precision is now +`separationDepth p = mahlerPrec p + O(log n) = O(n·log‖p‖∞)`, so `B = Θ(n)` is +even more firmly in the multiplication-bound regime; `O(n³·B²)` with +`B = Θ(n)` gives the wall model `n⁵`. The schedule caps lower (degree `16`) because the separation-depth target keeps one full-ladder pass practical there. +Same seeded-family non-monotonicity caveat as `isolateAll?`. -/ -setup_benchmark runIsolate n => n * n * n +setup_benchmark runIsolate n => n * n * n * n * n with prep := seededPoly where { paramFloor := 4 @@ -490,11 +577,16 @@ setup_benchmark runIsolate n => n * n * n } /- -`refineTo?` sharpens a fixed degree-3 atom from precision `≈32` to the parametric -target `t`. Speculative Newton doubles the precision per accepted jump, so the -final witness at precision `t` dominates: one `O(n²)`-op Taylor shift on -`B ≈ t`-bit dyadics, whose schoolbook bit-cost is `O(t²)` at fixed degree. The -parameter is the target precision, so the model is `t²`. +Cost model. `refineTo?` sharpens a fixed degree-3 atom from precision `≈32` to +the parametric target `t`. Speculative Newton doubles the precision per +accepted jump, so the final witness at precision `t` dominates: a fixed number +(`O(deg²) = O(1)` at degree `3`) of Taylor multiplies on `B ≈ t`-bit dyadics, +each a schoolbook `t × t` product costing `O(t²)`, so the wall model is `t²` +in the target precision. Caveat: Newton doubling reaches a *discrete* precision +ladder, so the per-call work is a step function of `t` rather than smooth in it +(targets in the same doubling interval do equal work); the report's Concerns +section flags this quantisation as the reason the verdict is inconclusive on +the `64..256` schedule. -/ setup_benchmark runRefineTo t => t * t with prep := prepRefineTo @@ -514,12 +606,17 @@ same `linProdPoly` inputs under the three `AtomStrategy` values and digest the output with the strategy-invariant `rootsDigest`. On this integer-root family all three agree, so `compare runIsolateNk runIsolatePellet runIsolateNkThenPellet` reports `allAgreed`; a divergence would be a cross-strategy conformance failure. -The op-count model is `n³` (each is an `isolate` run), matching the drivers. -/ +The wall model is `n⁵` (each is an `isolate` run), matching the drivers. -/ --- Cost model: one `isolate` run over `linProdPoly n`; n subdivision/adoption --- rounds of O(n) witness checks at O(n) ops each (fixed-height family), so --- n^3 exact-dyadic operations; the NK-only strategy certifies each atom on its doubled square. -setup_benchmark runIsolateNk n => n * n * n +/- +Cost model: one `isolate` run over `linProdPoly n`; `n³` op count (n +subdivision/adoption rounds of O(n) witness checks at O(n) ops each), and the +separation target's working bit-length `B = Θ(n log n)` enters the +growing-precision arithmetic as a schoolbook `O(B²)` per-op factor, so +`O(n³·B²)` gives the `n⁵` wall model; the NK-only strategy certifies each atom +on its doubled square. +-/ +setup_benchmark runIsolateNk n => n * n * n * n * n with prep := linProdPoly where { paramFloor := 2 @@ -530,10 +627,15 @@ setup_benchmark runIsolateNk n => n * n * n signalFloorMultiplier := 1.0 } --- Cost model: one `isolate` run over `linProdPoly n`; n subdivision/adoption --- rounds of O(n) witness checks at O(n) ops each (fixed-height family), so --- n^3 exact-dyadic operations; the Pellet-only strategy runs the three-radius test per k candidate. -setup_benchmark runIsolatePellet n => n * n * n +/- +Cost model: one `isolate` run over `linProdPoly n`; `n³` op count (n +subdivision/adoption rounds of O(n) witness checks at O(n) ops each), and the +separation target's working bit-length `B = Θ(n log n)` enters the +growing-precision arithmetic as a schoolbook `O(B²)` per-op factor, so +`O(n³·B²)` gives the `n⁵` wall model; the Pellet-only strategy runs the +three-radius test per k candidate. +-/ +setup_benchmark runIsolatePellet n => n * n * n * n * n with prep := linProdPoly where { paramFloor := 2 @@ -544,10 +646,15 @@ setup_benchmark runIsolatePellet n => n * n * n signalFloorMultiplier := 1.0 } --- Cost model: one `isolate` run over `linProdPoly n`; n subdivision/adoption --- rounds of O(n) witness checks at O(n) ops each (fixed-height family), so --- n^3 exact-dyadic operations; the default strategy tries NK first, Pellet as fallback. -setup_benchmark runIsolateNkThenPellet n => n * n * n +/- +Cost model: one `isolate` run over `linProdPoly n`; `n³` op count (n +subdivision/adoption rounds of O(n) witness checks at O(n) ops each), and the +separation target's working bit-length `B = Θ(n log n)` enters the +growing-precision arithmetic as a schoolbook `O(B²)` per-op factor, so +`O(n³·B²)` gives the `n⁵` wall model; the default strategy tries NK first, +Pellet as fallback. +-/ +setup_benchmark runIsolateNkThenPellet n => n * n * n * n * n with prep := linProdPoly where { paramFloor := 2 diff --git a/libraries.yml b/libraries.yml index 16fd49d3d..038df8fef 100644 --- a/libraries.yml +++ b/libraries.yml @@ -453,8 +453,29 @@ libraries: HexRoots: deps: [HexPolyZ] mathlib: false + # done_through stays 3: the Phase-4 scientific run (commit b08a66cce522, + # chungus2) returned inconclusive verdicts for 9 of 13 parametric + # registrations and two SPEC time budgets fail, so Phase 4 is NOT claimed. + # See reports/hexroots-performance.md §Concerns. The phase4 block below + # records the comparators and input families the run used; it does not + # assert completion. done_through: 3 status: active + phase4: + comparators: + - tool: "python-flint fmpz_poly.complex_roots" + class: informational + rationale: "The SPEC's ci-tier oracle (python-flint fmpz_poly.complex_roots, returning certified Arb balls with multiplicities) is a multiprecision ball-arithmetic engine, structurally different from this library's decidable exact-integer Pellet / Newton-Kantorovich certificates. Timed on the same seed-0xC0FFEE dense integer ladder the whole-polynomial drivers use (scripts/bench/hexroots_flint_compare.py); the ratio hex isolateAll?@32 / flint per degree is recorded in reports/hexroots-performance.md. Informational, not gating: the SPEC's time budgets — not a constant-factor 1x goal — are the yardstick for an integer-certified isolator against a float engine." + - tool: "MPSolve" + class: informational + rationale: "MPSolve (Bini-Fiorentino, the SPEC's local-tier and Phase-4 external performance comparator) is a multiprecision-float C library computing approximate root inclusions, structurally different from this library's integer-certified Lean witnesses, so its ratio orients but does not gate. Scheduled-only: not wired in this PR. Required environment stated in bench/HexRoots/Bench.lean's module docstring (the mpsolve CLI built with GMP, driven on the seeded ladder via -au -Gi isolate mode)." + input_families: + - name: seeded-dense + description: Seed-0xC0FFEE dense integer polynomials with coefficients in [-10, 10] (constant term first, leading forced nonzero), generically distinct irrational roots; drives taylor, mahlerPrec, refine1, certify, and the whole-polynomial isolateAll?/isolate drivers. + - name: wilkinson-linprod + description: Wilkinson-shaped products ∏(X−j), j=1..n, with distinct integer roots 1..n; Newton recentres exactly onto integer roots, exercised by the witness-check, Newton-step, and dual-route compare-group registrations. + - name: refine-fixed + description: The fixed degree-3 polynomial (x−1)(x−2)(x+3) whose isolated atom the refineTo? and sameRoot registrations sharpen to a parametric target precision. HexRootsMathlib: deps: [HexRoots, HexPolyZMathlib] mathlib: true diff --git a/reports/bench-results/hex-roots-b08a66cce522.json b/reports/bench-results/hex-roots-b08a66cce522.json new file mode 100644 index 000000000..d070b0967 --- /dev/null +++ b/reports/bench-results/hex-roots-b08a66cce522.json @@ -0,0 +1,2028 @@ +{"results": + [{"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 16, + "ok_count": 1, + "min_per_call_nanos": 52903.728027, + "median_per_call_nanos": 52903.728027, + "max_per_call_nanos": 52903.728027}, + {"relative_spread": 0, + "param": 32, + "ok_count": 1, + "min_per_call_nanos": 466444.96875, + "median_per_call_nanos": 466444.96875, + "max_per_call_nanos": 466444.96875}, + {"relative_spread": 0, + "param": 64, + "ok_count": 1, + "min_per_call_nanos": 2834366.78125, + "median_per_call_nanos": 2834366.78125, + "max_per_call_nanos": 2834366.78125}, + {"relative_spread": 0, + "param": 128, + "ok_count": 1, + "min_per_call_nanos": 15331126.5, + "median_per_call_nanos": 15331126.5, + "max_per_call_nanos": 15331126.5}, + {"relative_spread": 0, + "param": 256, + "ok_count": 1, + "min_per_call_nanos": 70384976, + "median_per_call_nanos": 70384976, + "max_per_call_nanos": 70384976}], + "spawn_floor_nanos": 23576386, + "slope": 0.414762, + "ratios": + [[16, 206.655188], + [32, 455.512665], + [64, 691.984077], + [128, 935.737701], + [256, 1073.989502]], + "points": + [{"trial_index": 0, + "total_nanos": 108346835, + "status": "ok", + "result_hash": "0xc944db6223447de2", + "per_call_nanos": 52903.728027, + "peak_rss_kb": 60528, + "part_of_verdict": true, + "param": 16, + "inner_repeats": 2048, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 59704956, + "status": "ok", + "result_hash": "0x7de19fad1eb7e38b", + "per_call_nanos": 466444.96875, + "peak_rss_kb": 59448, + "part_of_verdict": true, + "param": 32, + "inner_repeats": 128, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 90699737, + "status": "ok", + "result_hash": "0xd617f3bd0b0ea721", + "per_call_nanos": 2834366.78125, + "peak_rss_kb": 60620, + "part_of_verdict": true, + "param": 64, + "inner_repeats": 32, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 61324506, + "status": "ok", + "result_hash": "0x391f76a4f4851795", + "per_call_nanos": 15331126.5, + "peak_rss_kb": 60848, + "part_of_verdict": true, + "param": 128, + "inner_repeats": 4, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 70384976, + "status": "ok", + "result_hash": "0x1242736d713af35b", + "per_call_nanos": 70384976, + "peak_rss_kb": 59932, + "part_of_verdict": true, + "param": 256, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runTaylor", + "env": + {"timestamp_unix_ms": 1783794950864, + "timestamp_iso": "2026-07-11T18:35:50Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [16, 32, 64, 128, 256], "kind": "custom"}, + "param_floor": 16, + "param_ceiling": 256, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 4, + "cache_mode": "warm"}, + "complexity_formula": "n * n", + "c_min": 455.512665, + "c_max": 1073.989502, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "consistent_with_declared_complexity", + "trial_summaries": + [{"relative_spread": 0, + "param": 16, + "ok_count": 1, + "min_per_call_nanos": 153.305408, + "median_per_call_nanos": 153.305408, + "max_per_call_nanos": 153.305408}, + {"relative_spread": 0, + "param": 32, + "ok_count": 1, + "min_per_call_nanos": 280.41304, + "median_per_call_nanos": 280.41304, + "max_per_call_nanos": 280.41304}, + {"relative_spread": 0, + "param": 64, + "ok_count": 1, + "min_per_call_nanos": 484.174156, + "median_per_call_nanos": 484.174156, + "max_per_call_nanos": 484.174156}, + {"relative_spread": 0, + "param": 128, + "ok_count": 1, + "min_per_call_nanos": 806.362137, + "median_per_call_nanos": 806.362137, + "max_per_call_nanos": 806.362137}, + {"relative_spread": 0, + "param": 256, + "ok_count": 1, + "min_per_call_nanos": 1695.441193, + "median_per_call_nanos": 1695.441193, + "max_per_call_nanos": 1695.441193}], + "spawn_floor_nanos": 23564278, + "slope": -0.147599, + "ratios": + [[16, 9.581588], + [32, 8.762908], + [64, 7.565221], + [128, 6.299704], + [256, 6.622817]], + "points": + [{"trial_index": 0, + "total_nanos": 80376186, + "status": "ok", + "result_hash": "0x89", + "per_call_nanos": 153.305408, + "peak_rss_kb": 60284, + "part_of_verdict": true, + "param": 16, + "inner_repeats": 524288, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 73508596, + "status": "ok", + "result_hash": "0x131", + "per_call_nanos": 280.41304, + "peak_rss_kb": 60456, + "part_of_verdict": true, + "param": 32, + "inner_repeats": 262144, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 63461675, + "status": "ok", + "result_hash": "0x2a2", + "per_call_nanos": 484.174156, + "peak_rss_kb": 59440, + "part_of_verdict": true, + "param": 64, + "inner_repeats": 131072, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 52845749, + "status": "ok", + "result_hash": "0x5c2", + "per_call_nanos": 806.362137, + "peak_rss_kb": 59816, + "part_of_verdict": true, + "param": 128, + "inner_repeats": 65536, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 55556217, + "status": "ok", + "result_hash": "0xc83", + "per_call_nanos": 1695.441193, + "peak_rss_kb": 59788, + "part_of_verdict": true, + "param": 256, + "inner_repeats": 32768, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runMahlerPrec", + "env": + {"timestamp_unix_ms": 1783794951426, + "timestamp_iso": "2026-07-11T18:35:51Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [16, 32, 64, 128, 256], "kind": "custom"}, + "param_floor": 16, + "param_ceiling": 256, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 2, + "cache_mode": "warm"}, + "complexity_formula": "n", + "c_min": 6.299704, + "c_max": 8.762908, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 2, + "ok_count": 1, + "min_per_call_nanos": 1130.775024, + "median_per_call_nanos": 1130.775024, + "max_per_call_nanos": 1130.775024}, + {"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 3169.275848, + "median_per_call_nanos": 3169.275848, + "max_per_call_nanos": 3169.275848}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 5507.034729, + "median_per_call_nanos": 5507.034729, + "max_per_call_nanos": 5507.034729}, + {"relative_spread": 0, + "param": 8, + "ok_count": 1, + "min_per_call_nanos": 8641.646729, + "median_per_call_nanos": 8641.646729, + "max_per_call_nanos": 8641.646729}, + {"relative_spread": 0, + "param": 10, + "ok_count": 1, + "min_per_call_nanos": 11552.318481, + "median_per_call_nanos": 11552.318481, + "max_per_call_nanos": 11552.318481}, + {"relative_spread": 0, + "param": 12, + "ok_count": 1, + "min_per_call_nanos": 15053.983154, + "median_per_call_nanos": 15053.983154, + "max_per_call_nanos": 15053.983154}, + {"relative_spread": 0, + "param": 16, + "ok_count": 1, + "min_per_call_nanos": 44720.152344, + "median_per_call_nanos": 44720.152344, + "max_per_call_nanos": 44720.152344}], + "spawn_floor_nanos": 23878744, + "slope": -0.227425, + "ratios": + [[2, 282.693756], + [4, 198.079741], + [6, 152.973187], + [8, 135.02573], + [10, 115.523185], + [12, 104.54155], + [16, 174.688095]], + "points": + [{"trial_index": 0, + "total_nanos": 74106472, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 1130.775024, + "peak_rss_kb": 60280, + "part_of_verdict": true, + "param": 2, + "inner_repeats": 65536, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 103850831, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 3169.275848, + "peak_rss_kb": 60760, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 32768, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 90227257, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 5507.034729, + "peak_rss_kb": 60252, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 16384, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 70792370, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 8641.646729, + "peak_rss_kb": 60944, + "part_of_verdict": true, + "param": 8, + "inner_repeats": 8192, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 94636593, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 11552.318481, + "peak_rss_kb": 60960, + "part_of_verdict": true, + "param": 10, + "inner_repeats": 8192, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 61661115, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 15053.983154, + "peak_rss_kb": 59768, + "part_of_verdict": true, + "param": 12, + "inner_repeats": 4096, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 91586872, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 44720.152344, + "peak_rss_kb": 60880, + "part_of_verdict": true, + "param": 16, + "inner_repeats": 2048, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runWitnessCheck", + "env": + {"timestamp_unix_ms": 1783794951906, + "timestamp_iso": "2026-07-11T18:35:51Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [2, 4, 6, 8, 10, 12, 16], "kind": "custom"}, + "param_floor": 2, + "param_ceiling": 16, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 4, + "cache_mode": "warm"}, + "complexity_formula": "n * n", + "c_min": 104.54155, + "c_max": 198.079741, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "consistent_with_declared_complexity", + "trial_summaries": + [{"relative_spread": 0, + "param": 2, + "ok_count": 1, + "min_per_call_nanos": 826.542053, + "median_per_call_nanos": 826.542053, + "max_per_call_nanos": 826.542053}, + {"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 2354.16568, + "median_per_call_nanos": 2354.16568, + "max_per_call_nanos": 2354.16568}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 3745.424011, + "median_per_call_nanos": 3745.424011, + "max_per_call_nanos": 3745.424011}, + {"relative_spread": 0, + "param": 8, + "ok_count": 1, + "min_per_call_nanos": 5793.193115, + "median_per_call_nanos": 5793.193115, + "max_per_call_nanos": 5793.193115}, + {"relative_spread": 0, + "param": 10, + "ok_count": 1, + "min_per_call_nanos": 8491.290894, + "median_per_call_nanos": 8491.290894, + "max_per_call_nanos": 8491.290894}, + {"relative_spread": 0, + "param": 12, + "ok_count": 1, + "min_per_call_nanos": 12025.950195, + "median_per_call_nanos": 12025.950195, + "max_per_call_nanos": 12025.950195}, + {"relative_spread": 0, + "param": 16, + "ok_count": 1, + "min_per_call_nanos": 39156.393066, + "median_per_call_nanos": 39156.393066, + "max_per_call_nanos": 39156.393066}], + "spawn_floor_nanos": 24449570, + "slope": -0.094631, + "ratios": + [[2, 206.635513], + [4, 147.135355], + [6, 104.039556], + [8, 90.518642], + [10, 84.912909], + [12, 83.513543], + [16, 152.95466]], + "points": + [{"trial_index": 0, + "total_nanos": 108336520, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 826.542053, + "peak_rss_kb": 60792, + "part_of_verdict": true, + "param": 2, + "inner_repeats": 131072, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 77141301, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 2354.16568, + "peak_rss_kb": 60816, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 32768, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 61365027, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 3745.424011, + "peak_rss_kb": 59784, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 16384, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 94915676, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 5793.193115, + "peak_rss_kb": 60512, + "part_of_verdict": true, + "param": 8, + "inner_repeats": 16384, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 69560655, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 8491.290894, + "peak_rss_kb": 60372, + "part_of_verdict": true, + "param": 10, + "inner_repeats": 8192, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 98516584, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 12025.950195, + "peak_rss_kb": 60080, + "part_of_verdict": true, + "param": 12, + "inner_repeats": 8192, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 80192293, + "status": "ok", + "result_hash": "0xb", + "per_call_nanos": 39156.393066, + "peak_rss_kb": 59836, + "part_of_verdict": true, + "param": 16, + "inner_repeats": 2048, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runNkWitnessCheck", + "env": + {"timestamp_unix_ms": 1783794952697, + "timestamp_iso": "2026-07-11T18:35:52Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [2, 4, 6, 8, 10, 12, 16], "kind": "custom"}, + "param_floor": 2, + "param_ceiling": 16, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 4, + "cache_mode": "warm"}, + "complexity_formula": "n * n", + "c_min": 83.513543, + "c_max": 152.95466, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 2, + "ok_count": 1, + "min_per_call_nanos": 676.23243, + "median_per_call_nanos": 676.23243, + "max_per_call_nanos": 676.23243}, + {"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 1117.922882, + "median_per_call_nanos": 1117.922882, + "max_per_call_nanos": 1117.922882}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 2141.073944, + "median_per_call_nanos": 2141.073944, + "max_per_call_nanos": 2141.073944}, + {"relative_spread": 0, + "param": 8, + "ok_count": 1, + "min_per_call_nanos": 3353.481659, + "median_per_call_nanos": 3353.481659, + "max_per_call_nanos": 3353.481659}, + {"relative_spread": 0, + "param": 10, + "ok_count": 1, + "min_per_call_nanos": 4681.702515, + "median_per_call_nanos": 4681.702515, + "max_per_call_nanos": 4681.702515}, + {"relative_spread": 0, + "param": 12, + "ok_count": 1, + "min_per_call_nanos": 6960.588135, + "median_per_call_nanos": 6960.588135, + "max_per_call_nanos": 6960.588135}, + {"relative_spread": 0, + "param": 16, + "ok_count": 1, + "min_per_call_nanos": 31888.599121, + "median_per_call_nanos": 31888.599121, + "max_per_call_nanos": 31888.599121}], + "spawn_floor_nanos": 23372262, + "slope": 0.197594, + "ratios": + [[2, 169.058108], + [4, 69.87018], + [6, 59.474276], + [8, 52.398151], + [10, 46.817025], + [12, 48.337418], + [16, 124.56484]], + "points": + [{"trial_index": 0, + "total_nanos": 88635137, + "status": "ok", + "result_hash": "0x450307c7dcbe905c", + "per_call_nanos": 676.23243, + "peak_rss_kb": 60532, + "part_of_verdict": true, + "param": 2, + "inner_repeats": 131072, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 73264194, + "status": "ok", + "result_hash": "0x450307c7dcbe905c", + "per_call_nanos": 1117.922882, + "peak_rss_kb": 60780, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 65536, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 70158711, + "status": "ok", + "result_hash": "0x450307c7dcbe905c", + "per_call_nanos": 2141.073944, + "peak_rss_kb": 59780, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 32768, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 109886887, + "status": "ok", + "result_hash": "0x450307c7dcbe905c", + "per_call_nanos": 3353.481659, + "peak_rss_kb": 60512, + "part_of_verdict": true, + "param": 8, + "inner_repeats": 32768, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 76705014, + "status": "ok", + "result_hash": "0x450307c7dcbe905c", + "per_call_nanos": 4681.702515, + "peak_rss_kb": 60280, + "part_of_verdict": true, + "param": 10, + "inner_repeats": 16384, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 57021138, + "status": "ok", + "result_hash": "0x450307c7dcbe905c", + "per_call_nanos": 6960.588135, + "peak_rss_kb": 59948, + "part_of_verdict": true, + "param": 12, + "inner_repeats": 8192, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 65307851, + "status": "ok", + "result_hash": "0x450307c7dcbe905c", + "per_call_nanos": 31888.599121, + "peak_rss_kb": 60576, + "part_of_verdict": true, + "param": 16, + "inner_repeats": 2048, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runNewtonSquare", + "env": + {"timestamp_unix_ms": 1783794953494, + "timestamp_iso": "2026-07-11T18:35:53Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [2, 4, 6, 8, 10, 12, 16], "kind": "custom"}, + "param_floor": 2, + "param_ceiling": 16, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 4, + "cache_mode": "warm"}, + "complexity_formula": "n * n", + "c_min": 46.817025, + "c_max": 124.56484, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "consistent_with_declared_complexity", + "trial_summaries": + [{"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 302199.492188, + "median_per_call_nanos": 302199.492188, + "max_per_call_nanos": 302199.492188}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 472436.742188, + "median_per_call_nanos": 472436.742188, + "max_per_call_nanos": 472436.742188}, + {"relative_spread": 0, + "param": 8, + "ok_count": 1, + "min_per_call_nanos": 742243.84375, + "median_per_call_nanos": 742243.84375, + "max_per_call_nanos": 742243.84375}, + {"relative_spread": 0, + "param": 10, + "ok_count": 1, + "min_per_call_nanos": 994128.375, + "median_per_call_nanos": 994128.375, + "max_per_call_nanos": 994128.375}, + {"relative_spread": 0, + "param": 12, + "ok_count": 1, + "min_per_call_nanos": 1419906.953125, + "median_per_call_nanos": 1419906.953125, + "max_per_call_nanos": 1419906.953125}], + "spawn_floor_nanos": 22816820, + "slope": null, + "ratios": + [[4, 18887.468262], + [6, 13123.242839], + [8, 11597.560059], + [10, 9941.28375], + [12, 9860.464952]], + "points": + [{"trial_index": 0, + "total_nanos": 77363070, + "status": "ok", + "result_hash": "0xd125cbcffcdbb3a1", + "per_call_nanos": 302199.492188, + "peak_rss_kb": 60832, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 256, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 60471903, + "status": "ok", + "result_hash": "0xb674b30b3e491ead", + "per_call_nanos": 472436.742188, + "peak_rss_kb": 60836, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 128, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 95007212, + "status": "ok", + "result_hash": "0xe207bbfd1454f77", + "per_call_nanos": 742243.84375, + "peak_rss_kb": 60112, + "part_of_verdict": true, + "param": 8, + "inner_repeats": 128, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 63624216, + "status": "ok", + "result_hash": "0xbe23835dfeb75738", + "per_call_nanos": 994128.375, + "peak_rss_kb": 60232, + "part_of_verdict": true, + "param": 10, + "inner_repeats": 64, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 90874045, + "status": "ok", + "result_hash": "0x67a4e6fe2931f85d", + "per_call_nanos": 1419906.953125, + "peak_rss_kb": 60884, + "part_of_verdict": true, + "param": 12, + "inner_repeats": 64, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runRefine1", + "env": + {"timestamp_unix_ms": 1783794954237, + "timestamp_iso": "2026-07-11T18:35:54Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [4, 6, 8, 10, 12], "kind": "custom"}, + "param_floor": 4, + "param_ceiling": 12, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 4, + "cache_mode": "warm"}, + "complexity_formula": "n * n", + "c_min": 9860.464952, + "c_max": 13123.242839, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 13150.027222, + "median_per_call_nanos": 13150.027222, + "max_per_call_nanos": 13150.027222}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 17187.675781, + "median_per_call_nanos": 17187.675781, + "max_per_call_nanos": 17187.675781}, + {"relative_spread": 0, + "param": 8, + "ok_count": 1, + "min_per_call_nanos": 21050.085449, + "median_per_call_nanos": 21050.085449, + "max_per_call_nanos": 21050.085449}, + {"relative_spread": 0, + "param": 10, + "ok_count": 1, + "min_per_call_nanos": 24386.145752, + "median_per_call_nanos": 24386.145752, + "max_per_call_nanos": 24386.145752}, + {"relative_spread": 0, + "param": 12, + "ok_count": 1, + "min_per_call_nanos": 28842.26709, + "median_per_call_nanos": 28842.26709, + "max_per_call_nanos": 28842.26709}], + "spawn_floor_nanos": 22847394, + "slope": null, + "ratios": + [[4, 821.876701], + [6, 477.435438], + [8, 328.907585], + [10, 243.861458], + [12, 200.293521]], + "points": + [{"trial_index": 0, + "total_nanos": 107725023, + "status": "ok", + "result_hash": "0x99bc4f2495506336", + "per_call_nanos": 13150.027222, + "peak_rss_kb": 59816, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 8192, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 70400720, + "status": "ok", + "result_hash": "0xc0bf9a4ff6d6ca0", + "per_call_nanos": 17187.675781, + "peak_rss_kb": 59840, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 4096, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 86221150, + "status": "ok", + "result_hash": "0x9026af8b39c631d2", + "per_call_nanos": 21050.085449, + "peak_rss_kb": 60752, + "part_of_verdict": true, + "param": 8, + "inner_repeats": 4096, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 99885653, + "status": "ok", + "result_hash": "0xf6cb7feddb68424d", + "per_call_nanos": 24386.145752, + "peak_rss_kb": 60824, + "part_of_verdict": true, + "param": 10, + "inner_repeats": 4096, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 59068963, + "status": "ok", + "result_hash": "0x7c47cfcdaf58dbfb", + "per_call_nanos": 28842.26709, + "peak_rss_kb": 60668, + "part_of_verdict": true, + "param": 12, + "inner_repeats": 2048, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runCertify", + "env": + {"timestamp_unix_ms": 1783794954779, + "timestamp_iso": "2026-07-11T18:35:54Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [4, 6, 8, 10, 12], "kind": "custom"}, + "param_floor": 4, + "param_ceiling": 12, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 6, + "cache_mode": "warm"}, + "complexity_formula": "n * n", + "c_min": 200.293521, + "c_max": 477.435438, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 3772561.1875, + "median_per_call_nanos": 3772561.1875, + "max_per_call_nanos": 3772561.1875}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 11906305.25, + "median_per_call_nanos": 11906305.25, + "max_per_call_nanos": 11906305.25}, + {"relative_spread": 0, + "param": 8, + "ok_count": 1, + "min_per_call_nanos": 58009423, + "median_per_call_nanos": 58009423, + "max_per_call_nanos": 58009423}, + {"relative_spread": 0, + "param": 10, + "ok_count": 1, + "min_per_call_nanos": 138416144, + "median_per_call_nanos": 138416144, + "max_per_call_nanos": 138416144}, + {"relative_spread": 0, + "param": 12, + "ok_count": 1, + "min_per_call_nanos": 481762762, + "median_per_call_nanos": 481762762, + "max_per_call_nanos": 481762762}, + {"relative_spread": 0, + "param": 14, + "ok_count": 1, + "min_per_call_nanos": 1094785951, + "median_per_call_nanos": 1094785951, + "max_per_call_nanos": 1094785951}, + {"relative_spread": 0, + "param": 16, + "ok_count": 1, + "min_per_call_nanos": 2310390073, + "median_per_call_nanos": 2310390073, + "max_per_call_nanos": 2310390073}, + {"relative_spread": 0, + "param": 18, + "ok_count": 1, + "min_per_call_nanos": 6519449742, + "median_per_call_nanos": 6519449742, + "max_per_call_nanos": 6519449742}, + {"relative_spread": 0, + "param": 20, + "ok_count": 1, + "min_per_call_nanos": 4018060104, + "median_per_call_nanos": 4018060104, + "max_per_call_nanos": 4018060104}], + "spawn_floor_nanos": 23111676, + "slope": 0.265752, + "ratios": + [[4, 3684.141785], + [6, 1531.160655], + [8, 1770.307098], + [10, 1384.16144], + [12, 1936.096491], + [14, 2035.584041], + [16, 2203.359674], + [18, 3450.232933], + [20, 1255.643783]], + "points": + [{"trial_index": 0, + "total_nanos": 60360979, + "status": "ok", + "result_hash": "0x803bdbcf79747a0b", + "per_call_nanos": 3772561.1875, + "peak_rss_kb": 60756, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 16, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 95250442, + "status": "ok", + "result_hash": "0x43746460fa07ed9c", + "per_call_nanos": 11906305.25, + "peak_rss_kb": 60772, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 8, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 58009423, + "status": "ok", + "result_hash": "0x17d9b84ae6e53a4b", + "per_call_nanos": 58009423, + "peak_rss_kb": 61088, + "part_of_verdict": true, + "param": 8, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 138416144, + "status": "ok", + "result_hash": "0xd9e6d701d18ba198", + "per_call_nanos": 138416144, + "peak_rss_kb": 60024, + "part_of_verdict": true, + "param": 10, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 481762762, + "status": "ok", + "result_hash": "0x8c7900c32041027", + "per_call_nanos": 481762762, + "peak_rss_kb": 61212, + "part_of_verdict": true, + "param": 12, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 1094785951, + "status": "ok", + "result_hash": "0xf0fba15f6d07106e", + "per_call_nanos": 1094785951, + "peak_rss_kb": 61340, + "part_of_verdict": true, + "param": 14, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 2310390073, + "status": "ok", + "result_hash": "0xb2247c474d679abb", + "per_call_nanos": 2310390073, + "peak_rss_kb": 61308, + "part_of_verdict": true, + "param": 16, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 6519449742, + "status": "ok", + "result_hash": "0xe352a49462e400c3", + "per_call_nanos": 6519449742, + "peak_rss_kb": 59296, + "part_of_verdict": true, + "param": 18, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 4018060104, + "status": "ok", + "result_hash": "0xe04d9a38cc8e2885", + "per_call_nanos": 4018060104, + "peak_rss_kb": 60744, + "part_of_verdict": true, + "param": 20, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runIsolateAll", + "env": + {"timestamp_unix_ms": 1783794955358, + "timestamp_iso": "2026-07-11T18:35:55Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": + {"params": [4, 6, 8, 10, 12, 14, 16, 18, 20], "kind": "custom"}, + "param_floor": 4, + "param_ceiling": 20, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 20, + "cache_mode": "warm"}, + "complexity_formula": "n * n * n * n * n", + "c_min": 1255.643783, + "c_max": 3450.232933, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 3740418.5625, + "median_per_call_nanos": 3740418.5625, + "max_per_call_nanos": 3740418.5625}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 13037584.625, + "median_per_call_nanos": 13037584.625, + "max_per_call_nanos": 13037584.625}, + {"relative_spread": 0, + "param": 8, + "ok_count": 1, + "min_per_call_nanos": 60507236, + "median_per_call_nanos": 60507236, + "max_per_call_nanos": 60507236}, + {"relative_spread": 0, + "param": 10, + "ok_count": 1, + "min_per_call_nanos": 143896888, + "median_per_call_nanos": 143896888, + "max_per_call_nanos": 143896888}, + {"relative_spread": 0, + "param": 12, + "ok_count": 1, + "min_per_call_nanos": 493499879, + "median_per_call_nanos": 493499879, + "max_per_call_nanos": 493499879}, + {"relative_spread": 0, + "param": 14, + "ok_count": 1, + "min_per_call_nanos": 1121755315, + "median_per_call_nanos": 1121755315, + "max_per_call_nanos": 1121755315}, + {"relative_spread": 0, + "param": 16, + "ok_count": 1, + "min_per_call_nanos": 2364352425, + "median_per_call_nanos": 2364352425, + "max_per_call_nanos": 2364352425}], + "spawn_floor_nanos": 24320428, + "slope": null, + "ratios": + [[4, 3652.752502], + [6, 1676.644113], + [8, 1846.534302], + [10, 1438.96888], + [12, 1983.265332], + [14, 2085.729374], + [16, 2254.822183]], + "points": + [{"trial_index": 0, + "total_nanos": 59846697, + "status": "ok", + "result_hash": "0x35fcf0a24e2ce595", + "per_call_nanos": 3740418.5625, + "peak_rss_kb": 60580, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 16, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 104300677, + "status": "ok", + "result_hash": "0x4669ffb741a00196", + "per_call_nanos": 13037584.625, + "peak_rss_kb": 61120, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 8, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 60507236, + "status": "ok", + "result_hash": "0x944973d09f8fa1e4", + "per_call_nanos": 60507236, + "peak_rss_kb": 61424, + "part_of_verdict": true, + "param": 8, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 143896888, + "status": "ok", + "result_hash": "0x2176d56c90a7de2", + "per_call_nanos": 143896888, + "peak_rss_kb": 61236, + "part_of_verdict": true, + "param": 10, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 493499879, + "status": "ok", + "result_hash": "0x5c30f93bcc334745", + "per_call_nanos": 493499879, + "peak_rss_kb": 61120, + "part_of_verdict": true, + "param": 12, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 1121755315, + "status": "ok", + "result_hash": "0x27a0c649f1e08c1e", + "per_call_nanos": 1121755315, + "peak_rss_kb": 60288, + "part_of_verdict": true, + "param": 14, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 2364352425, + "status": "ok", + "result_hash": "0x83846a2a71bf4090", + "per_call_nanos": 2364352425, + "peak_rss_kb": 61000, + "part_of_verdict": true, + "param": 16, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runIsolate", + "env": + {"timestamp_unix_ms": 1783794970401, + "timestamp_iso": "2026-07-11T18:36:10Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [4, 6, 8, 10, 12, 14, 16], "kind": "custom"}, + "param_floor": 4, + "param_ceiling": 16, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 30, + "cache_mode": "warm"}, + "complexity_formula": "n * n * n * n * n", + "c_min": 1438.96888, + "c_max": 2254.822183, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 64, + "ok_count": 1, + "min_per_call_nanos": 6643.0979, + "median_per_call_nanos": 6643.0979, + "max_per_call_nanos": 6643.0979}, + {"relative_spread": 0, + "param": 96, + "ok_count": 1, + "min_per_call_nanos": 19493.402832, + "median_per_call_nanos": 19493.402832, + "max_per_call_nanos": 19493.402832}, + {"relative_spread": 0, + "param": 128, + "ok_count": 1, + "min_per_call_nanos": 19529.457275, + "median_per_call_nanos": 19529.457275, + "max_per_call_nanos": 19529.457275}, + {"relative_spread": 0, + "param": 192, + "ok_count": 1, + "min_per_call_nanos": 46874.458984, + "median_per_call_nanos": 46874.458984, + "max_per_call_nanos": 46874.458984}, + {"relative_spread": 0, + "param": 256, + "ok_count": 1, + "min_per_call_nanos": 46790.026855, + "median_per_call_nanos": 46790.026855, + "max_per_call_nanos": 46790.026855}], + "spawn_floor_nanos": 24691809, + "slope": null, + "ratios": + [[64, 1.62185], + [96, 2.11517], + [128, 1.191983], + [192, 1.271551], + [256, 0.713959]], + "points": + [{"trial_index": 0, + "total_nanos": 108840516, + "status": "ok", + "result_hash": "0x58287f338ded7c4a", + "per_call_nanos": 6643.0979, + "peak_rss_kb": 60192, + "part_of_verdict": true, + "param": 64, + "inner_repeats": 16384, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 79844978, + "status": "ok", + "result_hash": "0x30d5bbf2a1731352", + "per_call_nanos": 19493.402832, + "peak_rss_kb": 60724, + "part_of_verdict": true, + "param": 96, + "inner_repeats": 4096, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 79992657, + "status": "ok", + "result_hash": "0x30d5bbf2a1731352", + "per_call_nanos": 19529.457275, + "peak_rss_kb": 60300, + "part_of_verdict": true, + "param": 128, + "inner_repeats": 4096, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 95998892, + "status": "ok", + "result_hash": "0x9de10954e8ec1aa1", + "per_call_nanos": 46874.458984, + "peak_rss_kb": 61172, + "part_of_verdict": true, + "param": 192, + "inner_repeats": 2048, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 95825975, + "status": "ok", + "result_hash": "0x9de10954e8ec1aa1", + "per_call_nanos": 46790.026855, + "peak_rss_kb": 61200, + "part_of_verdict": true, + "param": 256, + "inner_repeats": 2048, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runRefineTo", + "env": + {"timestamp_unix_ms": 1783794974968, + "timestamp_iso": "2026-07-11T18:36:14Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [64, 96, 128, 192, 256], "kind": "custom"}, + "param_floor": 64, + "param_ceiling": 256, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 4, + "cache_mode": "warm"}, + "complexity_formula": "t * t", + "c_min": 0.713959, + "c_max": 2.11517, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "consistent_with_declared_complexity", + "trial_summaries": + [{"relative_spread": 0, + "param": 2, + "ok_count": 1, + "min_per_call_nanos": 414004.039062, + "median_per_call_nanos": 414004.039062, + "max_per_call_nanos": 414004.039062}, + {"relative_spread": 0, + "param": 3, + "ok_count": 1, + "min_per_call_nanos": 2202618.53125, + "median_per_call_nanos": 2202618.53125, + "max_per_call_nanos": 2202618.53125}, + {"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 8226581.125, + "median_per_call_nanos": 8226581.125, + "max_per_call_nanos": 8226581.125}, + {"relative_spread": 0, + "param": 5, + "ok_count": 1, + "min_per_call_nanos": 27713973.5, + "median_per_call_nanos": 27713973.5, + "max_per_call_nanos": 27713973.5}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 78346357, + "median_per_call_nanos": 78346357, + "max_per_call_nanos": 78346357}], + "spawn_floor_nanos": 24492234, + "slope": null, + "ratios": + [[2, 12937.626221], + [3, 9064.273791], + [4, 8033.77063], + [5, 8868.47152], + [6, 10075.405993]], + "points": + [{"trial_index": 0, + "total_nanos": 52992517, + "status": "ok", + "result_hash": "0xd8ffc7ced4231e2b", + "per_call_nanos": 414004.039062, + "peak_rss_kb": 60980, + "part_of_verdict": true, + "param": 2, + "inner_repeats": 128, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 70483793, + "status": "ok", + "result_hash": "0x7dd1aefc55b8dae7", + "per_call_nanos": 2202618.53125, + "peak_rss_kb": 59524, + "part_of_verdict": true, + "param": 3, + "inner_repeats": 32, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 65812649, + "status": "ok", + "result_hash": "0xe0b4f8d60c0a264e", + "per_call_nanos": 8226581.125, + "peak_rss_kb": 60652, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 8, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 55427947, + "status": "ok", + "result_hash": "0xbc18efaec363654c", + "per_call_nanos": 27713973.5, + "peak_rss_kb": 61008, + "part_of_verdict": true, + "param": 5, + "inner_repeats": 2, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 78346357, + "status": "ok", + "result_hash": "0x6519358031d0ea70", + "per_call_nanos": 78346357, + "peak_rss_kb": 60588, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 1, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runIsolateNk", + "env": + {"timestamp_unix_ms": 1783794975594, + "timestamp_iso": "2026-07-11T18:36:15Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [2, 3, 4, 5, 6], "kind": "custom"}, + "param_floor": 2, + "param_ceiling": 6, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 8, + "cache_mode": "warm"}, + "complexity_formula": "n * n * n * n * n", + "c_min": 8033.77063, + "c_max": 10075.405993, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 2, + "ok_count": 1, + "min_per_call_nanos": 272850.675781, + "median_per_call_nanos": 272850.675781, + "max_per_call_nanos": 272850.675781}, + {"relative_spread": 0, + "param": 3, + "ok_count": 1, + "min_per_call_nanos": 1831705.3125, + "median_per_call_nanos": 1831705.3125, + "max_per_call_nanos": 1831705.3125}, + {"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 5781410, + "median_per_call_nanos": 5781410, + "max_per_call_nanos": 5781410}, + {"relative_spread": 0, + "param": 5, + "ok_count": 1, + "min_per_call_nanos": 14711062.5, + "median_per_call_nanos": 14711062.5, + "max_per_call_nanos": 14711062.5}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 34966203, + "median_per_call_nanos": 34966203, + "max_per_call_nanos": 34966203}], + "spawn_floor_nanos": 24116385, + "slope": null, + "ratios": + [[2, 8526.583618], + [3, 7537.881944], + [4, 5645.908203], + [5, 4707.54], + [6, 4496.682485]], + "points": + [{"trial_index": 0, + "total_nanos": 69849773, + "status": "ok", + "result_hash": "0xd8ffc7ced4231e2b", + "per_call_nanos": 272850.675781, + "peak_rss_kb": 59844, + "part_of_verdict": true, + "param": 2, + "inner_repeats": 256, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 58614570, + "status": "ok", + "result_hash": "0x7dd1aefc55b8dae7", + "per_call_nanos": 1831705.3125, + "peak_rss_kb": 59880, + "part_of_verdict": true, + "param": 3, + "inner_repeats": 32, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 92502560, + "status": "ok", + "result_hash": "0xe0b4f8d60c0a264e", + "per_call_nanos": 5781410, + "peak_rss_kb": 60660, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 16, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 58844250, + "status": "ok", + "result_hash": "0xbc18efaec363654c", + "per_call_nanos": 14711062.5, + "peak_rss_kb": 60324, + "part_of_verdict": true, + "param": 5, + "inner_repeats": 4, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 69932406, + "status": "ok", + "result_hash": "0x6519358031d0ea70", + "per_call_nanos": 34966203, + "peak_rss_kb": 60292, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 2, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runIsolatePellet", + "env": + {"timestamp_unix_ms": 1783794976114, + "timestamp_iso": "2026-07-11T18:36:16Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [2, 3, 4, 5, 6], "kind": "custom"}, + "param_floor": 2, + "param_ceiling": 6, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 8, + "cache_mode": "warm"}, + "complexity_formula": "n * n * n * n * n", + "c_min": 4496.682485, + "c_max": 7537.881944, + "budget_truncated": false, + "advisories": []}, + {"verdict_dropped_leading": 1, + "verdict": "inconclusive", + "trial_summaries": + [{"relative_spread": 0, + "param": 2, + "ok_count": 1, + "min_per_call_nanos": 288364.375, + "median_per_call_nanos": 288364.375, + "max_per_call_nanos": 288364.375}, + {"relative_spread": 0, + "param": 3, + "ok_count": 1, + "min_per_call_nanos": 2049771.0625, + "median_per_call_nanos": 2049771.0625, + "max_per_call_nanos": 2049771.0625}, + {"relative_spread": 0, + "param": 4, + "ok_count": 1, + "min_per_call_nanos": 6220360.625, + "median_per_call_nanos": 6220360.625, + "max_per_call_nanos": 6220360.625}, + {"relative_spread": 0, + "param": 5, + "ok_count": 1, + "min_per_call_nanos": 15184484.5, + "median_per_call_nanos": 15184484.5, + "max_per_call_nanos": 15184484.5}, + {"relative_spread": 0, + "param": 6, + "ok_count": 1, + "min_per_call_nanos": 36157814, + "median_per_call_nanos": 36157814, + "max_per_call_nanos": 36157814}], + "spawn_floor_nanos": 23207569, + "slope": null, + "ratios": + [[2, 9011.386719], + [3, 8435.271862], + [4, 6074.570923], + [5, 4859.03504], + [6, 4649.92464]], + "points": + [{"trial_index": 0, + "total_nanos": 73821280, + "status": "ok", + "result_hash": "0xd8ffc7ced4231e2b", + "per_call_nanos": 288364.375, + "peak_rss_kb": 59844, + "part_of_verdict": true, + "param": 2, + "inner_repeats": 256, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 65592674, + "status": "ok", + "result_hash": "0x7dd1aefc55b8dae7", + "per_call_nanos": 2049771.0625, + "peak_rss_kb": 61088, + "part_of_verdict": true, + "param": 3, + "inner_repeats": 32, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 99525770, + "status": "ok", + "result_hash": "0xe0b4f8d60c0a264e", + "per_call_nanos": 6220360.625, + "peak_rss_kb": 60400, + "part_of_verdict": true, + "param": 4, + "inner_repeats": 16, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 60737938, + "status": "ok", + "result_hash": "0xbc18efaec363654c", + "per_call_nanos": 15184484.5, + "peak_rss_kb": 60692, + "part_of_verdict": true, + "param": 5, + "inner_repeats": 4, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}, + {"trial_index": 0, + "total_nanos": 72315628, + "status": "ok", + "result_hash": "0x6519358031d0ea70", + "per_call_nanos": 36157814, + "peak_rss_kb": 60748, + "part_of_verdict": true, + "param": 6, + "inner_repeats": 2, + "error": null, + "below_signal_floor": false, + "alloc_bytes": null}], + "kind": "parametric", + "hashable": true, + "function": "Hex.RootsBench.runIsolateNkThenPellet", + "env": + {"timestamp_unix_ms": 1783794976676, + "timestamp_iso": "2026-07-11T18:36:16Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}, + "config": + {"verdict_warmup_fraction": 0.2, + "target_inner_nanos": 100000000, + "slope_tolerance": 0.15, + "signal_floor_multiplier": 1, + "param_schedule": {"params": [2, 3, 4, 5, 6], "kind": "custom"}, + "param_floor": 2, + "param_ceiling": 6, + "outer_trials": 1, + "narrow_range_noise_floor": 1.5, + "max_seconds_per_call": 8, + "cache_mode": "warm"}, + "complexity_formula": "n * n * n * n * n", + "c_min": 4649.92464, + "c_max": 8435.271862, + "budget_truncated": false, + "advisories": []}], + "lean_bench_version": "0.1.0", + "export_schema_version": 1, + "env": + {"timestamp_unix_ms": 1783794950864, + "timestamp_iso": "2026-07-11T18:35:50Z", + "platform_target": "x86_64-unknown-linux-gnu", + "os": "linux", + "lean_version": "4.32.0-rc1", + "lean_toolchain": "leanprover/lean4:4.32.0-rc1", + "lean_bench_version": "0.1.0", + "hostname": "chungus2", + "git_dirty": true, + "git_commit": "b08a66cce522b2434022a5493e71ce41ee206e3b", + "exe_name": "hexroots_bench", + "cpu_model": "AMD EPYC 9455 48-Core Processor", + "cpu_cores": 96, + "arch": "x86_64"}} diff --git a/reports/bench-results/hex-roots-flint-b08a66cce522.json b/reports/bench-results/hex-roots-flint-b08a66cce522.json new file mode 100644 index 000000000..d637d568e --- /dev/null +++ b/reports/bench-results/hex-roots-flint-b08a66cce522.json @@ -0,0 +1,125 @@ +{ + "comparator": "python-flint fmpz_poly.complex_roots", + "python_flint_version": "0.9.0", + "prec_bits": 32, + "overhead_s": 1.0820222087204456e-06, + "rows": [ + { + "degree": 4, + "median_s": 1.1988013284280896e-05, + "n_roots": 4, + "repeats": 50, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 6, + "median_s": 1.770147355273366e-05, + "n_roots": 6, + "repeats": 50, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 8, + "median_s": 2.6308989617973566e-05, + "n_roots": 8, + "repeats": 50, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 10, + "median_s": 3.578397445380688e-05, + "n_roots": 10, + "repeats": 50, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 12, + "median_s": 7.655352237634361e-05, + "n_roots": 12, + "repeats": 50, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 14, + "median_s": 0.00010304249008186162, + "n_roots": 14, + "repeats": 20, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 16, + "median_s": 7.710899808444083e-05, + "n_roots": 16, + "repeats": 20, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 18, + "median_s": 9.669348946772516e-05, + "n_roots": 18, + "repeats": 8, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + }, + { + "degree": 20, + "median_s": 0.00016736300312913954, + "n_roots": 20, + "repeats": 8, + "coeffs_head": [ + -7, + -2, + -3, + -5, + 3 + ] + } + ] +} diff --git a/reports/figures/hex-roots-comparator-seeded-dense.svg b/reports/figures/hex-roots-comparator-seeded-dense.svg new file mode 100644 index 000000000..6eee23fbb --- /dev/null +++ b/reports/figures/hex-roots-comparator-seeded-dense.svg @@ -0,0 +1,2321 @@ + + + + + + + + 2026-07-11T18:53:49.646252 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/reports/hexroots-performance.md b/reports/hexroots-performance.md new file mode 100644 index 000000000..e75255af4 --- /dev/null +++ b/reports/hexroots-performance.md @@ -0,0 +1,353 @@ +# HexRoots Performance Report + +**Phase 4 is not claimed for HexRoots.** The scientific run below returns +*inconclusive* for 9 of the 13 parametric registrations, and two of the three +SPEC time budgets fail, so `libraries.yml` keeps `HexRoots.done_through: 3`. +The §Concerns subsection is therefore non-empty; each entry names the finding +and its root cause. This report is the record of the Phase-4 measurement work +and of why the phase is not yet exitable, per +[PLAN/Phase4.md](../PLAN/Phase4.md) and +[SPEC/benchmarking.md §Headline reports](../SPEC/benchmarking.md#headline-reports). + +All numbers come from commit `b08a66cce522` on `chungus2` (AMD EPYC 9455 +48-Core, linux `x86_64`, kernel 6.12.95), Lean `4.32.0-rc1`, lean-bench +`0.1.0`, `python-flint 0.9.0`. The harness recorded `git_dirty: true` because +the retuned bench module and this report were staged but uncommitted at run +time (the same convention as hex-real-roots); the recorded commit is the branch +point `b08a66cce522`. + +## Bench Targets + +Declared complexities copied verbatim from the `setup_benchmark` registration +sites in `bench/HexRoots/Bench.lean`. These are *wall-time* models: the SPEC +op-count contract multiplied by the family's working-bit-length growth `B` +where that growth is asymptotically significant on the schedule (each +registration's comment records the reconciliation). + +- `Hex.RootsBench.runTaylor`: `n * n` +- `Hex.RootsBench.runMahlerPrec`: `n` +- `Hex.RootsBench.runWitnessCheck`: `n * n` +- `Hex.RootsBench.runNkWitnessCheck`: `n * n` +- `Hex.RootsBench.runNewtonSquare`: `n * n` +- `Hex.RootsBench.runRefine1`: `n * n` +- `Hex.RootsBench.runCertify`: `n * n` +- `Hex.RootsBench.runIsolateAll`: `n * n * n * n * n` +- `Hex.RootsBench.runIsolate`: `n * n * n * n * n` +- `Hex.RootsBench.runRefineTo`: `t * t` +- `Hex.RootsBench.runIsolateNk`: `n * n * n * n * n` +- `Hex.RootsBench.runIsolatePellet`: `n * n * n * n * n` +- `Hex.RootsBench.runIsolateNkThenPellet`: `n * n * n * n * n` +- `Hex.RootsBench.runSameRoot`: fixed benchmark, `repeats = 5` + +Three input families (`libraries.yml: HexRoots.phase4.input_families`): +`seeded-dense` (seed-`0xC0FFEE` dense integer polynomials, coefficients in +`[-10, 10]`, generically distinct irrational roots — the whole-polynomial +drivers, `taylor`, `mahlerPrec`, `refine1`, `certify`), `wilkinson-linprod` +(`∏(X−j)`, `j = 1..n`, integer roots — the witness/Newton primitives and the +dual-route compare group), and `refine-fixed` (the degree-3 `(x−1)(x−2)(x+3)` +whose atom `refineTo?`/`sameRoot` sharpen). + +### Op-count-vs-wall reconciliation (Phase4.md performance rationale) + +The registration comments were retuned from pure exact-dyadic op-counts to +wall-time models. Which op-count models changed, and why: + +- **`runTaylor` stays `n²`.** The SPEC op-count is `n²` synthetic-division + multiply/adds. The fixed centre `1/4 + i/8 = 2^{−3}(2+i)` is *non-integer*, + so `z^{j−k}` carries denominator `2^{3(j−k)}` and the coefficients reach + `B = Θ(n)` bits; each inner op multiplies a `B`-bit operand by the fixed + `3`-bit centre (`O(B)`), giving an `n³` bit-cost asymptote. On the reachable + `16..256` (probed to `1024`) schedule the operands stay `≤ 48` GMP words — + the allocation-dominated transition band — so wall tracks `~n^{2.25}`, short + of the `n³` asymptote. `n²` is declared as the SPEC op-count; the residual is + a Concern (below). +- **`runMahlerPrec`, `runWitnessCheck`, `runNkWitnessCheck`, `runNewtonSquare`, + `runRefine1`, `runCertify` stay at their op-counts (`n`, `n²`).** `mahlerPrec` + is word-size integer arithmetic (`B` constant). The witness/Newton/refine + primitives run on integer-centred `wilkinson-linprod` (no denominator growth, + integer Taylor coefficients of `O(n log n)` bits staying sub-word `≤ 44` bits + through `n = 16`) or on low-precision seeded mid-refinement components + (`O(n)`-bit coefficients staying sub-word through `n = 12`), so the operands + are flat and wall tracks the op count. The bit-growth sits in the constant. +- **`runIsolateAll`, `runIsolate`, and the three compare-group targets changed + `n³ → n⁵`.** The SPEC contract is `O(n³·B²)` bit operations; the + whole-polynomial drivers reach a working bit-length `B = Θ(n)` (target + precision plus the seeded `n·log‖p‖∞` term plus the Taylor coefficients' + `Θ(prec·n)` denominator growth), and the growing-precision dyadic arithmetic + — notably the `invAtPrec` reciprocal — is schoolbook `O(B²)`. `O(n³·B²)` with + `B = Θ(n)` gives the `n⁵` wall model, folding the bit-growth into the + exponent because it is asymptotically significant here (unlike the flat-band + primitives above). +- **`runRefineTo` stays `t²`.** At fixed degree the parameter is the target + precision `t`; the dominant final witness does a fixed number of `t × t` + schoolbook multiplies, `O(t²)`. + +## Verdicts + +Scientific run, one command, exporting +`reports/bench-results/hex-roots-b08a66cce522.json`: + +```sh +lake exe hexroots_bench run \ + Hex.RootsBench.runTaylor Hex.RootsBench.runMahlerPrec \ + Hex.RootsBench.runWitnessCheck Hex.RootsBench.runNkWitnessCheck \ + Hex.RootsBench.runNewtonSquare Hex.RootsBench.runRefine1 \ + Hex.RootsBench.runCertify Hex.RootsBench.runIsolateAll \ + Hex.RootsBench.runIsolate Hex.RootsBench.runRefineTo \ + Hex.RootsBench.runIsolateNk Hex.RootsBench.runIsolatePellet \ + Hex.RootsBench.runIsolateNkThenPellet \ + --export-file reports/bench-results/hex-roots-b08a66cce522.json +``` + +Inputs are deterministic; no random seeds are involved. Every verdict verbatim +(`β` is the residual log-log slope of `C = per-call / model`; the harness calls +a run *consistent* iff `|β| ≤ 0.15`, or, on a log-x range too narrow to fit a +slope, iff `cMax/cMin ≤ max(1.5, exp(0.15·xRange))`): + +| registration | model | verdict | β / range | final-param hash | +|---|---|---|---|---| +| `runTaylor` | `n²` | **inconclusive** | `β=+0.415` (slower by `~n^0.42`) | `0x1242736d713af35b` @256 | +| `runMahlerPrec` | `n` | consistent | `β=−0.148` | `0xc83…` @256 | +| `runWitnessCheck` | `n²` | **inconclusive** | `β=−0.227` (faster by `~n^0.23`) | `0xb` @16 | +| `runNkWitnessCheck` | `n²` | consistent | `β=−0.095` | `0xb` @16 | +| `runNewtonSquare` | `n²` | **inconclusive** | `β=+0.198` (slower by `~n^0.20`) | `0x450307c7dcbe905c` @16 | +| `runRefine1` | `n²` | consistent | range check (`cMax/cMin=1.33`) | `0x67a4e6fe2931f85d` @12 | +| `runCertify` | `n²` | **inconclusive** | range check (`cMax/cMin=2.38`) | `0x7c47cfcdaf58dbfb` @12 | +| `runIsolateAll` | `n⁵` | **inconclusive** | `β=+0.266` (slower by `~n^0.27`) | `0xe04d9a38cc8e2885` @20 | +| `runIsolate` | `n⁵` | **inconclusive** | range check (`cMax/cMin=1.57`) | `0x83846a2a71bf4090` @16 | +| `runRefineTo` | `t²` | **inconclusive** | range check (`cMax/cMin=2.96`) | `0x9de10954e8ec1aa1` @256 | +| `runIsolateNk` | `n⁵` | consistent | range check (`cMax/cMin=1.25`) | `0x6519358031d0ea70` @6 | +| `runIsolatePellet` | `n⁵` | **inconclusive** | range check (`cMax/cMin=1.68`) | `0x6519358031d0ea70` @6 | +| `runIsolateNkThenPellet` | `n⁵` | **inconclusive** | range check (`cMax/cMin=1.81`) | `0x6519358031d0ea70` @6 | + +**4 consistent, 9 inconclusive.** The fixed `runSameRoot` benchmark: +median `132 ns` (min `130`, max `134`, `×2^13` inner repeats), all repeats +agree on hash `0xb`, matching the registered `expectedHash`. + +The nine inconclusive verdicts are analysed in §Concerns. They fall into four +root causes, none of which is a wrong-asymptotic *implementation* bug that a +`done_through` rollback of the `def` would fix: the fixed non-integer Taylor +centre's transition-band growth, the startup-dominated microsecond band of the +small-degree witness benches, the seeded family's degree-dependent (hence +non-power-law) root geometry, and `refineTo`'s Newton-doubling precision +quantisation. They are benchmark-family / schedule findings. + +## Comparator Ratios + +`HexRoots/SPEC/hex-roots.md` names python-flint (`fmpz_poly.complex_roots`, the +ci-tier oracle) and MPSolve (the local-tier / Phase-4 external comparator). +Both are classified `informational` in +`libraries.yml: HexRoots.phase4.comparators`; neither gates Phase 4. The +per-library yardstick is the SPEC's time budgets, not a constant-factor `1×` +goal, because both comparators are multiprecision-float/ball engines +structurally different from this library's decidable exact-integer +certificates. + +### python-flint (`informational`, run) + +`scripts/bench/hexroots_flint_compare.py` times `fmpz_poly.complex_roots()` at +`ctx.prec = 32` on the *same* seed-`0xC0FFEE` dense integer ladder the +whole-polynomial drivers use (the LCG replication is byte-verified against the +Lean `seededCoeffs`: `seededCoeffs 4 = [-7,-2,-3,-5,3]`, +`seededCoeffs 8 = [-7,-2,-3,-5,3,1,-7,-3,-4]` in both). All degrees run in one +warm process; the measured per-call overhead (`complex_roots` on the trivial +`x²−2`) is `1.08 µs`, exceeding 5 % of flint wall time only at `n = 4, 6`, +where both raw and overhead-adjusted ratios are shown. Data: +`reports/bench-results/hex-roots-flint-b08a66cce522.json`; hex per-call from +the `runIsolateAll` rows of `hex-roots-b08a66cce522.json`. + +| degree | hex `isolateAll?@32` | flint | flint adj. | ratio hex/flint | adj. ratio | +|---:|---:|---:|---:|---:|---:| +| 4 | 3.77 ms | 12.0 µs | 10.9 µs | 315 | 346 | +| 6 | 11.9 ms | 17.7 µs | 16.6 µs | 673 | 716 | +| 8 | 58.0 ms | 26.3 µs | 25.2 µs | 2205 | 2300 | +| 10 | 138.4 ms | 35.8 µs | 34.7 µs | 3868 | 3989 | +| 12 | 481.8 ms | 76.6 µs | 75.5 µs | 6293 | 6383 | +| 14 | 1.095 s | 103.0 µs | 102.0 µs | 10625 | 10737 | +| 16 | 2.310 s | 77.1 µs | 76.0 µs | 29963 | 30389 | +| 18 | 6.519 s | 96.7 µs | 95.6 µs | 67424 | 68187 | +| 20 | 4.018 s | 167.4 µs | 166.3 µs | 24008 | 24164 | + +**Trend.** The ratio *diverges* as degree grows (315× at `n=4` to `~30000×` at +`n=16`; the `n=18/20` scatter tracks the seeded family's non-monotonicity, not +the trend). flint stays sub-`200 µs` across the whole ladder — its +ball-arithmetic root finder is nearly flat here — while hex is `~n⁵` certified +exact arithmetic, so the gap widens by roughly `n⁴` per the models. This is the +expected shape for an integer-certified isolator against a float engine and is +not a Concern under the informational classification; the yardstick is the SPEC +time budgets (§below), which is where hex's absolute cost is judged. Plot: +`reports/figures/hex-roots-comparator-seeded-dense.svg` +(`scripts/plots/hex-roots-comparator.py --family seeded-dense`); the +`wilkinson-linprod` and `refine-fixed` families carry no external-comparator +series and MPSolve is scheduled-only, so no second curve is drawn for them. + +### MPSolve (`informational`, scheduled-only) + +Not wired in this PR. Required environment (stated in +`bench/HexRoots/Bench.lean`): the `mpsolve` CLI (`unisa-cs/mpsolve`, built with +GMP) on `PATH`, driven on the seeded ladder via `-au -Gi` isolate mode. +Rationale for the informational class: MPSolve is a multiprecision-float C +library computing approximate root inclusions, structurally different from this +library's integer-certified Lean witnesses. + +### Cross-strategy compare group (internal, `allAgreed`) + +```sh +lake exe hexroots_bench compare \ + Hex.RootsBench.runIsolateNk Hex.RootsBench.runIsolatePellet \ + Hex.RootsBench.runIsolateNkThenPellet +``` + +reports `agreement: all functions agree on common params` over the shared +`wilkinson-linprod` domain (`n = 2, 3, 4, 5, 6`): the strategy-invariant +`rootsDigest` (atom count + integer-grid centre buckets) is identical across +`.nk`, `.pellet`, and `.nkThenPellet` (final hash `0x6519358031d0ea70` at +`n = 6` for all three), the cross-implementation conformance check the compare +group exists for. This is the dual-route experiment's measurement record. The +per-degree strategy timings and the `pellet/nk` ratio: + +| degree | `.nk` | `.pellet` | `.nkThenPellet` | pellet/nk | +|---:|---:|---:|---:|---:| +| 2 | 0.414 ms | 0.273 ms | 0.288 ms | 0.659 | +| 3 | 2.203 ms | 1.832 ms | 2.050 ms | 0.832 | +| 4 | 8.227 ms | 5.781 ms | 6.220 ms | 0.703 | +| 5 | 27.71 ms | 14.71 ms | 15.18 ms | 0.531 | +| 6 | 78.35 ms | 34.97 ms | 36.16 ms | 0.446 | + +On this integer-root family the **Pellet-only route is the faster one, and +increasingly so with degree** (ratio `0.66 → 0.45`): Pellet certifies at a +coarser precision than the sup-norm Newton-Kantorovich witness needs, so the +`.nk` route subdivides more levels. `.nkThenPellet` tracks `.pellet` closely +(NK does not fire early on these coarse squares, so the default falls through to +Pellet after one NK attempt). This is a genuine dual-route finding for the +companion's eventual route-retirement decision: on well-separated integer roots +Pellet wins; the NK route's advantage (exact first-order bounds, no `√2`) is not +visible on this family at these degrees. + +## Profile + +`perf record -g -F 999` on the in-process `_child` batch runner +(`hexroots_bench _child --bench --param --target-nanos 3000000000`), +one representative case per `phase4.input_families` entry, same commit and host +as the scientific run. Leaf self-time is categorised across +{own code, GMP, allocation, Lean runtime}; own code = `l_Hex_*`, `lp_Hex_*`, +`l_Dyadic_*`, `l_GaussDyadic_*`, and the dyadic-mantissa integer leaves +(`l_Int_*`). `perf.data` artefacts are developer-local under `/tmp` and are not +committed. + +### `seeded-dense` — `runIsolateAll` at `n = 16` (2447 samples) + +Leaf self-time: GMP 32.9 % (`__gmpz_init_set` 7.6 %, `__gmpz_cmp_si` 2.6 %, +`__gmpz_add` 2.1 %, `__gmpz_realloc`/`__gmpn_*`), allocation 21.5 % +(`malloc` 7.5 %, `cfree` 7.1 %, `realloc`, `mi_*`), Lean runtime 22.7 % +(`lean_dec_ref_cold`, `lean::mpz_to_int`, `lean::mpz::~mpz`), own code 19.0 % +(`l_Dyadic_add` 3.8 %, `l_Dyadic_mul` 1.9 %, `l_Int_trailingZeros_aux`, +`lp_Hex_…taylor`); 96.2 % classified. This is the growing-precision regime the +`n⁵` model predicts: the working bit-length reaches multiple GMP words, so GMP +big-integer arithmetic and its allocation/box-unbox traffic dominate, flowing +inclusively through the registered `isolateAll?` → `taylor`/`witnessCheck` +path. + +### `wilkinson-linprod` — `runIsolateNkThenPellet` at `n = 6` (2489 samples) + +Leaf self-time: **own code 44.9 %** (`l_Int_trailingZeros_aux` 9.3 %, +`l_Dyadic_add` 7.4 %, `l_Dyadic_mul` 5.3 %, `l_Dyadic_ofIntWithPrec` 3.0 %, +`l_Int_shiftLeft`), GMP 19.3 % (`__gmpz_mul_2exp` 3.2 %), allocation 15.1 %, +Lean runtime 17.6 %; 97.0 % classified. The own-code share is more than double +the seeded family's, exactly the flat, sub-word band the `n²`/`n⁵`-op-count +derivations claim for the integer-centred `linProdPoly`: operands stay small, +so the actual `Dyadic` arithmetic (own code) dominates rather than GMP. The +inclusive path is the registered `isolate`/compare-group driver. + +### `refine-fixed` — `runRefineTo` at `t = 256` (3180 samples) + +Leaf self-time: GMP 30.0 % (`__gmpz_init_set` 4.9 %, +`__gmpn_divrem_1_x86_64` 2.6 % — the `t`-bit reciprocal — `__gmp_default_*`), +allocation 24.7 % (`cfree` 7.2 %, `malloc` 6.3 %, `_int_free_chunk`, +`realloc`), Lean runtime 19.9 % (`lean_dec_ref_cold`, `lean_nat_big_sub`), own +code 18.3 % (`l_Int_trailingZeros_aux` 4.3 %, `l_Dyadic_add`); 92.9 % +classified. The `t`-bit division/multiplication and its allocation churn +dominate, consistent with the `t²` schoolbook model; inclusive cost is the +registered `refineTo?`. + +**Attribution rule.** Every dominant inclusive path terminates in a registered +bench target (`isolateAll?`/`isolate`, `taylor`, `witnessCheck`/ +`nkWitnessCheck`, `newtonSquare`, `refine1`/`certify`, `refineTo?`), so no +unregistered helper dominates and no new target is required. (Lean's +closure-call unwinding fragments some inclusive attribution into an unresolved +`0x1` frame ~6 %, a `perf`/RTS artefact, not an unregistered hot path.) + +## Concerns + +Phase 4 is blocked; `done_through` stays `3`. Each Concern is a +benchmark-family / schedule / budget finding, with the diagnosis that closes it. +None is a wrong-asymptotic implementation bug that rolling back a `def` would +fix; the resolutions are Phase-4 benchmark re-scaffolding (new schedules, a +smooth driver family, an integer Taylor centre) plus a SPEC time-budget +re-appraisal. + +1. **`runIsolate`, `runIsolateAll` inconclusive — seeded-family + non-monotonicity.** At `n⁵` the residual is small (`β=+0.266` for + `isolateAll`), but the seeded polynomials' degree-dependent root geometry + makes wall time non-monotonic in `n` (e.g. `isolateAll` at `n=18` is + `6.52 s` but `n=20` is `4.02 s`, because `seededPoly 18` happens to have a + closer root pair), so no power-law fit is clean. Resolution: replace the + per-degree-varying seed with a family whose isolation difficulty is smooth + in `n` (e.g. a fixed root-separation product), then re-measure `n⁵`. + +2. **`runIsolatePellet`, `runIsolateNkThenPellet` inconclusive — narrow-range + range check.** `n⁵` is the right model (`runIsolateNk` on the same domain is + *consistent*), but the 5-rung `n=2..6` schedule is too narrow for a slope + fit, so the verdict falls to the multiplicative range check, which the + slightly-sub-`n⁵` Pellet growth (`cMax/cMin = 1.68`, `1.81`) fails against + the `1.5` noise floor. Resolution: widen the compare-group schedule + (larger degrees, or in-fill rungs) so a slope fit governs the verdict. + +3. **`runTaylor` inconclusive — non-integer-centre transition band.** The fixed + centre `1/4 + i/8` gives `Θ(n)` denominator growth, so wall scales + `~n^{2.25}` (probed to `n=1024`), between the `n²` op-count and the `n³` + bit-op asymptote; neither clean power is consistent (`n²` gives `β=+0.42`). + The analogous hex-real-roots `runMobiusTransform` is consistent at `n²` + precisely because it uses an *integer* interval endpoint. Resolution: an + integer Taylor centre for the benchmark, or declare and reach the `n³` + multiplication-bound regime (operand word counts an order of magnitude past + the wallclock cap). + +4. **`runWitnessCheck`, `runNewtonSquare`, `runCertify` inconclusive — + startup-dominated microsecond band.** These run in `1–45 µs` on the small + `n ≤ 12/16` schedules, where the fixed per-call overhead (array allocation, + checksum) makes the `C` curve U-shaped (`witnessCheck` `β=−0.23`, + `newtonSquare` `β=+0.20`, `certify` monotone-decreasing), so `n²` does not + fit cleanly even though the operands are provably flat. `runRefine1` and + `runNkWitnessCheck` on the same band happen to pass. Resolution: raise the + schedules into a signal band clear of startup (larger degrees, keeping + operands sub-word), or hoist more of the per-call fixed cost out of the + timed body. + +5. **`runRefineTo` inconclusive — Newton-doubling precision quantisation.** + Speculative Newton doubles precision per jump, so `refineTo?` reaches a + *discrete* precision ladder and the per-call work is a step function of the + target `t` (`t=96` and `t=128` do equal work; likewise `t=192`, `t=256`), + not smooth in `t`. `t²` cannot fit a staircase. Resolution: parametrise the + benchmark by the *number of Newton jumps* (monotone in work) rather than the + raw target precision. + +6. **SPEC time budget: degree 50 @ prec 64 FAILS.** SPEC target `< 10 s`. + Measured with `isolateAll? (seededPoly 50) 64` (used because + `separationDepth(deg 50) ≫ 64`, per the SPEC note): single call + **495.85 s** (`chk = 4218`, 50 atoms), **`49.6×` over budget**. Resolution: + this is a rough-first-guess budget the implementation does not meet at `n⁵` + scaling; either the budget is re-appraised against MPSolve (its stated + purpose) or the driver is optimised (Graeffe iteration, deferred in the + SPEC, would cut the `ceilLog2(deg)` separation-depth factor). + +7. **SPEC time budget: degree 100 @ prec 128 FAILS.** SPEC target `< 1 min`. + Measured with `isolateAll? (seededPoly 100) 128`: the single call did **not + complete within a 19-minute window** (`> 1143 s`, already `> 19×` the budget, + then stopped; extrapolating the `n⁵·B²` model from the degree-50 point puts + the true time in the hours). Same resolution as Concern 6. + +For reference, the one budget that is met: **degree 10 @ prec 32** runs in +`0.137 s` (`isolateAll? (seededPoly 10) 32`, compiled, calibrated against the +`runIsolateAll` `n=10` bench row of `138 ms`), comfortably under the `< 1 s` +target. diff --git a/scripts/bench/hexroots_flint_compare.py b/scripts/bench/hexroots_flint_compare.py new file mode 100644 index 000000000..998eab8d9 --- /dev/null +++ b/scripts/bench/hexroots_flint_compare.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Informational python-flint comparator for hex-roots complex root isolation. + +Times `flint.fmpz_poly.complex_roots()` on the same seed-`0xC0FFEE` dense +integer polynomial ladder that `bench/HexRoots/Bench.lean`'s whole-polynomial +drivers use (`seededPoly`), so the ratio `hex isolateAll?@32 / flint` per degree +in `reports/hexroots-performance.md` is apples-to-apples on the input. + +This is an `informational` process-call comparator (SPEC/benchmarking.md +§External comparators): FLINT's `complex_roots` is a multiprecision Arb +ball-arithmetic engine, structurally different from hex-roots' decidable +exact-integer Pellet / Newton-Kantorovich certificates, so it orients but does +not gate Phase 4. All degrees run in one warm process; the process-startup and +per-call floors are measured separately and reported. + +Reproduce under a `python-flint >= 0.9.0` virtualenv: + + python3 -m venv /tmp/rootsvenv && /tmp/rootsvenv/bin/pip install python-flint + /tmp/rootsvenv/bin/python scripts/bench/hexroots_flint_compare.py + +Emits one JSON object on stdout: {"prec_bits", "overhead_s", per-degree rows}. +""" +from __future__ import annotations + +import json +import statistics +import sys +import time + +MASK64 = (1 << 64) - 1 + + +def lcg_next(s: int) -> int: + """The LCG step from conformance/HexRoots/EmitFixtures.lean, UInt64 wraparound.""" + return (6364136223846793005 * s + 1442695040888963407) & MASK64 + + +def seeded_coeffs(degree: int) -> list[int]: + """Replicates `Hex.RootsBench.seededCoeffs`: degree+1 coefficients in + [-10, 10] (constant term first), leading coefficient forced nonzero.""" + s = 0xC0FFEE + out: list[int] = [] + for _ in range(degree + 1): + s = lcg_next(s) + out.append((s % 21) - 10) + if out[degree] == 0: + out[degree] = 1 + return out + + +def time_call(poly, repeats: int) -> float: + """Median wall time (seconds) of `poly.complex_roots()` over `repeats`.""" + ts = [] + for _ in range(repeats): + t0 = time.perf_counter() + poly.complex_roots() + ts.append(time.perf_counter() - t0) + return statistics.median(ts) + + +def main() -> int: + import flint + from flint import ctx, fmpz_poly + + prec_bits = 32 # match hex isolateAll?@32 (half-width 2^-32) + ctx.prec = prec_bits + + ladder = [int(a) for a in sys.argv[1:]] or [4, 6, 8, 10, 12, 14, 16, 18, 20] + + # Per-call floor: a trivial sub-millisecond input (x^2 - 2). + trivial = fmpz_poly([-2, 0, 1]) + trivial.complex_roots() # warm + overhead_s = time_call(trivial, 200) + + rows = [] + for d in ladder: + coeffs = seeded_coeffs(d) + p = fmpz_poly(coeffs) + p.complex_roots() # warm + # Fewer repeats at high degree to keep total time bounded. + repeats = 50 if d <= 12 else (20 if d <= 16 else 8) + med = time_call(p, repeats) + nroots = sum(m for _, m in p.complex_roots()) + rows.append({ + "degree": d, + "median_s": med, + "n_roots": nroots, + "repeats": repeats, + "coeffs_head": coeffs[:5], + }) + + print(json.dumps({ + "comparator": "python-flint fmpz_poly.complex_roots", + "python_flint_version": flint.__version__, + "prec_bits": prec_bits, + "overhead_s": overhead_s, + "rows": rows, + }, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check_phase4.py b/scripts/check_phase4.py index f6a218ed0..86e482961 100644 --- a/scripts/check_phase4.py +++ b/scripts/check_phase4.py @@ -133,7 +133,14 @@ def changed_setup_lines(root: Path, base: str) -> list[tuple[Path, int, str]]: if SETUP_RE.match(text): changed.append((current_file, new_line, text)) new_line += 1 - elif line.startswith("-") and not line.startswith("---"): + elif line.startswith("-"): + # A deleted line: never advances the new-file cursor. The guard is + # unconditional on `-` because a deleted `--`-comment line renders as + # `--- …` in the diff and must not be mistaken for the `--- a/` + # header (that header precedes `+++ b/`, so it is already + # skipped by the `current_file is None` check and reset at the next + # `@@`). Counting a deleted `--` comment as context inflated the + # new-file line number of the following `setup_benchmark`. continue else: new_line += 1 diff --git a/scripts/plots/hex-roots-comparator.py b/scripts/plots/hex-roots-comparator.py new file mode 100644 index 000000000..9edf6ec0e --- /dev/null +++ b/scripts/plots/hex-roots-comparator.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Generate the HexRoots comparator-runtime plot from committed exports. + +Draws the Lean `isolateAll?@32` wall-time curve (from the lean-bench export) +alongside the python-flint `fmpz_poly.complex_roots` curve (from the flint +comparator export) for one `phase4.input_families` entry, log-y wall time per +call across the shared seeded degree ladder. Reads the same JSONL/JSON the +Comparator-ratios numbers in reports/hexroots-performance.md cite. + +Only the `seeded-dense` family has python-flint data (the process-call +comparator was run on the whole-polynomial driver ladder); MPSolve is +scheduled-only (no data points), and the `wilkinson-linprod` / `refine-fixed` +families carry no external-comparator series, so `--family` accepts only +`seeded-dense`. + +Usage: + python3 scripts/plots/hex-roots-comparator.py --family seeded-dense +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +matplotlib.rcParams["svg.hashsalt"] = "hex-roots-comparator" +import matplotlib.pyplot as plt + +ROOT = Path(__file__).resolve().parents[2] +LEAN_EXPORT = ROOT / "reports/bench-results/hex-roots-b08a66cce522.json" +FLINT_EXPORT = ROOT / "reports/bench-results/hex-roots-flint-b08a66cce522.json" + + +def lean_isolateall_by_degree() -> dict[int, float]: + d = json.loads(LEAN_EXPORT.read_text()) + out: dict[int, float] = {} + for r in d["results"]: + if r["function"].endswith("runIsolateAll"): + for t in r["trial_summaries"]: + out[t["param"]] = t["median_per_call_nanos"] / 1e9 + return out + + +def flint_by_degree() -> tuple[dict[int, float], float]: + f = json.loads(FLINT_EXPORT.read_text()) + return {row["degree"]: row["median_s"] for row in f["rows"]}, f["overhead_s"] + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--family", default="seeded-dense", choices=["seeded-dense"]) + ap.add_argument("--out", default=None) + args = ap.parse_args() + + hexd = lean_isolateall_by_degree() + flintd, overhead = flint_by_degree() + degrees = sorted(set(hexd) & set(flintd)) + + fig, ax = plt.subplots(figsize=(7, 4.5)) + ax.plot(degrees, [hexd[d] for d in degrees], "o-", label="hex isolateAll?@32 (Lean, certified)") + ax.plot(degrees, [max(flintd[d] - overhead, 1e-9) for d in degrees], "s-", + label="python-flint complex_roots (overhead-adjusted)") + ax.set_yscale("log") + ax.set_xlabel("degree n (seed-0xC0FFEE dense integer polynomial)") + ax.set_ylabel("wall time per call (s)") + ax.set_title("HexRoots vs python-flint — seeded-dense family (chungus2)") + ax.legend() + ax.grid(True, which="both", alpha=0.3) + out = Path(args.out) if args.out else ROOT / f"reports/figures/hex-roots-comparator-{args.family}.svg" + out.parent.mkdir(parents=True, exist_ok=True) + fig.tight_layout() + fig.savefig(out) + print(f"wrote {out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From f9126b08bd916cd31a00ce4a953c010d3b47e61f Mon Sep 17 00:00:00 2001 From: Kim Morrison Date: Sat, 11 Jul 2026 19:14:35 +0000 Subject: [PATCH 2/2] bench(hex-roots): polylog-suppression policy notes and flint LCG tether The five n^5 driver declared models suppress polylog factors (a strict B = Theta(n log n) reading gives n^5 log^2 n); the cost-model comments now state that policy explicitly, matching the house convention the sibling hex-real-roots Phase-4 report uses. The flint comparator script now verifies at startup that the Lean bench family's LCG literals are still present in bench/HexRoots/Bench.lean, failing loudly if the seeded family drifts. Both from the Codex review. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01SpYSH4swVcLJbN2Bn6NfSP --- bench/HexRoots/Bench.lean | 20 ++++++++++++++++++++ scripts/bench/hexroots_flint_compare.py | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/bench/HexRoots/Bench.lean b/bench/HexRoots/Bench.lean index 0a5d4702b..ced78f9ec 100644 --- a/bench/HexRoots/Bench.lean +++ b/bench/HexRoots/Bench.lean @@ -542,6 +542,10 @@ subdivision, but their *degree-dependent* root geometry (some degrees have much closer roots than neighbours) makes the wall time non-monotonic in `n`, which the report's Concerns section flags as a fit-quality limitation. -/ +-- Polylog factors (a strict B = Θ(n log n) reading gives n^5·log²n) are +-- suppressed in the declared model per house convention: the harness fits a +-- log-log slope, and the sibling Phase-4 reports (hex-real-roots) declare +-- plain powers for the same reason. setup_benchmark runIsolateAll n => n * n * n * n * n with prep := seededPoly where { @@ -565,6 +569,10 @@ even more firmly in the multiplication-bound regime; `O(n³·B²)` with because the separation-depth target keeps one full-ladder pass practical there. Same seeded-family non-monotonicity caveat as `isolateAll?`. -/ +-- Polylog factors (a strict B = Θ(n log n) reading gives n^5·log²n) are +-- suppressed in the declared model per house convention: the harness fits a +-- log-log slope, and the sibling Phase-4 reports (hex-real-roots) declare +-- plain powers for the same reason. setup_benchmark runIsolate n => n * n * n * n * n with prep := seededPoly where { @@ -616,6 +624,10 @@ growing-precision arithmetic as a schoolbook `O(B²)` per-op factor, so `O(n³·B²)` gives the `n⁵` wall model; the NK-only strategy certifies each atom on its doubled square. -/ +-- Polylog factors (a strict B = Θ(n log n) reading gives n^5·log²n) are +-- suppressed in the declared model per house convention: the harness fits a +-- log-log slope, and the sibling Phase-4 reports (hex-real-roots) declare +-- plain powers for the same reason. setup_benchmark runIsolateNk n => n * n * n * n * n with prep := linProdPoly where { @@ -635,6 +647,10 @@ growing-precision arithmetic as a schoolbook `O(B²)` per-op factor, so `O(n³·B²)` gives the `n⁵` wall model; the Pellet-only strategy runs the three-radius test per k candidate. -/ +-- Polylog factors (a strict B = Θ(n log n) reading gives n^5·log²n) are +-- suppressed in the declared model per house convention: the harness fits a +-- log-log slope, and the sibling Phase-4 reports (hex-real-roots) declare +-- plain powers for the same reason. setup_benchmark runIsolatePellet n => n * n * n * n * n with prep := linProdPoly where { @@ -654,6 +670,10 @@ growing-precision arithmetic as a schoolbook `O(B²)` per-op factor, so `O(n³·B²)` gives the `n⁵` wall model; the default strategy tries NK first, Pellet as fallback. -/ +-- Polylog factors (a strict B = Θ(n log n) reading gives n^5·log²n) are +-- suppressed in the declared model per house convention: the harness fits a +-- log-log slope, and the sibling Phase-4 reports (hex-real-roots) declare +-- plain powers for the same reason. setup_benchmark runIsolateNkThenPellet n => n * n * n * n * n with prep := linProdPoly where { diff --git a/scripts/bench/hexroots_flint_compare.py b/scripts/bench/hexroots_flint_compare.py index 998eab8d9..c2801eb2b 100644 --- a/scripts/bench/hexroots_flint_compare.py +++ b/scripts/bench/hexroots_flint_compare.py @@ -30,6 +30,27 @@ MASK64 = (1 << 64) - 1 +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def verify_lcg_matches_lean() -> None: + """Tether this reimplementation to the Lean bench family: the LCG + multiplier and increment literals must appear in + bench/HexRoots/Bench.lean, or the comparator is no longer + apples-to-apples and must fail loudly rather than time a divergent + input family.""" + src = (REPO_ROOT / "bench" / "HexRoots" / "Bench.lean").read_text() + for lit in ("6364136223846793005", "1442695040888963407"): + if lit not in src: + raise SystemExit( + f"hexroots_flint_compare: LCG literal {lit} not found in " + "bench/HexRoots/Bench.lean; the Lean seeded family has " + "changed and this script must be updated to match" + ) + + def lcg_next(s: int) -> int: """The LCG step from conformance/HexRoots/EmitFixtures.lean, UInt64 wraparound.""" return (6364136223846793005 * s + 1442695040888963407) & MASK64 @@ -59,6 +80,7 @@ def time_call(poly, repeats: int) -> float: def main() -> int: + verify_lcg_matches_lean() import flint from flint import ctx, fmpz_poly