Skip to content

opcount: measure the split model — all fifteen families, and 168 published cells confirmed - #36

Merged
salindne merged 4 commits into
masterfrom
pr41-opcount-split
Aug 25, 2026
Merged

opcount: measure the split model — all fifteen families, and 168 published cells confirmed#36
salindne merged 4 commits into
masterfrom
pr41-opcount-split

Conversation

@salindne

Copy link
Copy Markdown
Owner

verification/opcount.py — the counter of record — measured 6 of 15 families. It now measures all
fifteen, and every operation count in the repository is reproducible from the sources by execution.

Two separable defects, landed separately.

1. A run covering six of fifteen families said so about none of it

main selected [f for f in families if f.model == "ramified"] before the skip machinery, so the
nine split families were never attempted rather than attempted and refused. --json reported
"skipped": [], and the human output listed six results with nothing to say the other nine existed.

The refusal itself was already honest — naming a split family explicitly has always printed
skipped: split family: see split_spec. Only the default path bypassed it, which is the worst version:
the default path is the one everybody runs and the one a reader quotes from. Nothing is silently capped
in this repository, and the counter of record is the last place that rule should lapse.

2. Teaching it the split model

The refusal was for a real reason, not an unfinished one. The ramified domain is derived by contrast
what arb extracts from the curve and a specialisation does not is what the specialisation assumes away
— and that argument has nothing to work with in the split model, whose dispatchers read neither f nor
h. They take ccs, the constants Precompute derives.

Most of the machinery already existed. driver.split_spec derives the domain from Precompute's
own source, split_curve_in_domain validates the places at infinity, and build_args_split maps the
(u1,v1,n1,u2,v2,n2,ccs) signature — the refusal message literally named the function that does the
job. All three are used verbatim. Precompute runs once per curve outside the measured call, being
per-curve setup and not part of an operation's cost.

What was genuinely new: a split operation's shape is not its degree. The published tables price
"Degree 1", "Degree 1 with Down Adjust" and "Degree 1 with two Up Adjusts" separately, at 7M, 14M and
42M — and measured, the input balancing weight is exactly what selects between them. The six
(degree, weight) pairs a genus-3 divisor admits reproduce the six published doubling rows one for one,
so the weight is the tables' own row index rather than a nuisance parameter. Keying on degree alone
pooled rows the thesis prices apart and reported whichever the sampler happened to favour — under which
a degree-1 doubling reports as 42M. Weights are driven rather than drawn, so no published row goes
unsampled because its weight never came up.

The disagreement, and it was the tool's

First measurement put every split row +2A above its published cell — 33ADD 89 against 87, 3DBL
103 against 101, both two-Up-Adjust doublings likewise, with M, S and C exact everywhere. A
systematic offset is one cause, not four errata, and the standing rule is to presume the published
count correct and hand-count the divergence.

A split divisor carries a balancing weight, and that weight is a small integer in [0, g], not a
field element. Every addition runs n := n1 + n2 - 2 and every genus-3 doubling np := n + n - 2.
Charged as field additions that is a flat +2A on every split row in the thesis. They are bookkeeping,
of the same kind as a loop index, and the published tables are right not to count them.

So maginterp gains INT_ARITH_FREE, an inert flag on the PR35 precedent, testing operand type
sound because a field element is an FFElement in every field this repository builds and never a Python
int. Testing by syntax, the form already used for integer-literal products, cannot work here:
n1 + n2 contains no literal.

The convention is not uniform, and assuming it was is recorded as a refutation. The guard was first
written to assert +2A everywhere and failed on three cells. Measured per shape: every addition at
both genera and both bases moves +2A, every genus-3 doubling moves +2A, and the genus-2 doublings
move nothing at all — they carry no weight addition on the counted path. Nothing but A ever moves.

Acceptance, met by agreement rather than adjudication

The criterion this work was given: every split figure it newly produces is hand-counted against the
published thesis cell before it is believed
, because a counter producing split figures nobody has
checked is worse than one that refuses.

168 measured shapes reproduce their published cell exactly — 42 in each of the three genus-3 split
families against tab:g3splitfcosts{ADD,DBL}, and 14 in each of the three genus-2 posReduced families
against tab:splitfcosts. Zero unmatched, and no published row left unreproduced. Both tables are parsed
out of the .tex rather than retyped, because a table retyped by hand is a third place for a number to
be wrong.

Every priced row comes out at exactly one inversion — which chapter 6 claims in prose and nothing had
ever checked for the split model.

A side result: the genus-2 basis question, settled from the other direction. posReduced matches the
published table 14 of 14 in all three characteristic classes; negReduced differs on three to five
rows, each by one or two operations. A different algorithm, not a defect — and an independent
confirmation of posReduced as the genus-2 basis of record, previously decided by other means.

A third defect, found while working

The module header claimed every contributing execution was compared against reference.py's independent
Cantor arithmetic. It never wasmeasure_call returned the value and the histogram discarded it,
so a call that left the domain and returned a wrong answer was histogrammed as a legitimate count. That
is the failure mode yielding a plausible wrong number rather than an obvious one, and precisely the
risk the split work adds. Now performed; a disagreeing sample is dropped rather than counted. Turning it
on moved no ramified figure, so nothing was being masked — but nothing was guarding it either.

The header also advertised a --verify flag that has never existed.

Verification

  • ramified byte-identical across all six families, for both the convention and the new cross-check
  • selftest 18 passed, 0 failed, 0 skipped — the new split_counts section pins eight published
    cells and is shown to fail with either half reverted: dropping the split dispatch gives
    not measurable: split family: see split_spec, dropping the convention gives
    13ADD n=1,0 measured (22,2,45,7,1), published (22,2,43,7,1)
  • whitebox PASS · driver --strict 13,746 / 13,746 · dominance clean on 39 files ·
    blockcheck 0 wrong · check_paths --strict · check_readme_paths · ruff
  • Magma suite: 30 testers, 0 failures, 0 skips, 3m44s — not a gate here, since
    git diff --name-only master -- '*.mag' is empty, but run anyway

Documentation

The README's op-count table had half its rows reading published rather than measured, with the reason
inline. All twelve split figures were verified against their published cell before the label changed,
so the caveat is discharged rather than deleted. NEW_WORK.md N31 records the work at publication
depth, with three honest limits inline: the genus-2 negReduced families are measured but checked
against nothing, no published table pricing that basis; the two genus-3 posReduced families do not
exist to measure; and thirteen genus-3 shapes per family cost nothing at all, being inputs the dispatcher
answers without arithmetic — verified like every other sample and carried in --json, but summarised
rather than listed, since no published row prices them.

What this unblocks

C4 (the adjugate trade, +1M −12A across six sites) and C5 (the redundant 2×2 system, 11M 6A
deletable) both recorded this counter as their sole outstanding prerequisite. Their starting points are
now confirmed rather than assumed — genus-3 split Deg3ADD and Deg3DBL measure 65M 3S 87A 12C and
73M 3S 101A 19C, matching the published table exactly. They still owe a hand count for any cell they
move, being published formulas.

PR10's genus-3 split reorder also gains the per-branch op-count gate its plan section records as
unavailable — the same acceptance test the ramified renames used.

The default run selected `[f for f in families if f.model == "ramified"]` before
the skip machinery, so the nine split families were never attempted rather than
attempted and refused. The effect was a complete-looking answer over 6 of 15
families: `--json` reported `"skipped": []`, and the human output listed six
results with nothing to say the other nine existed.

The refusal itself was already there and already honest -- naming a split family
explicitly has always printed `skipped: split family: see split_spec`. Only the
default path bypassed it, which is the worst version of the bug, because the
default path is the one everybody runs and the one a reader quotes from.

Nothing is silently capped in this repository, and the counter of record is the
last place that rule should lapse. Every family is now attempted, every refusal is
printed, and the run ends with `6 of 15 families measured, 9 skipped` so the
figure cannot be read as coverage it does not have.

This is the reporting half of teaching opcount the split model, split out
deliberately: it is independent of whether a split domain can be derived at all,
and it makes the other half's progress visible -- the skipped count is what will
fall as families become measurable.

Verified as a reporting change only: the six ramified families' output is
byte-identical to master's, selftest 17/0 with the specialisation section still
comparing nine shapes per pair, ruff clean.
…g as arithmetic

The counter of record measured six of fifteen families. The nine split ones were
refused for a real reason rather than an unfinished one: the ramified domain is
derived by contrast -- what `arb` extracts from the curve and a specialisation
does not is what the specialisation assumes away -- and that argument has nothing
to work with in the split model, whose dispatchers read neither f nor h. They take
`ccs`, the constants Precompute derives.

Most of what was needed already existed and is used verbatim. `split_spec` derives
the domain from Precompute's own source, `split_curve_in_domain` validates the
places at infinity, and `build_args_split` maps the (u1,v1,n1,u2,v2,n2,ccs)
signature. Precompute runs once per curve OUTSIDE the measured call, being
per-curve setup and not part of an operation's cost.

What was new is that a split operation's shape is not its degree. The published
tables price "Degree 1", "Degree 1 with Down Adjust" and "Degree 1 with two Up
Adjusts" separately, at 7M, 14M and 42M, and measured it is the input balancing
weight that selects between them -- the six (degree, weight) pairs a genus-3
divisor admits reproduce the six published doubling rows one for one. Keying on
degree alone pooled rows the thesis prices apart and reported whichever the
sampler happened to favour, under which a degree-1 doubling reports as 42M.
Weights are driven rather than drawn, so no published row can go unsampled
because its weight never came up.

THE CONVENTION, which is the part worth reading

First measurement put every split row +2A above its published cell: 33ADD 89
against 87, 3DBL 103 against 101, both two-Up-Adjust doublings likewise, with M, S
and C exact everywhere. A systematic offset is one cause, not four errata.

A split divisor carries a balancing weight, and that weight is a small integer in
[0, g], not a field element. Every addition runs `n := n1 + n2 - 2` and every
genus-3 doubling `np := n + n - 2`. Charged as field additions that is a flat +2A
on every split row in the thesis. The published tables are right not to count
them; they are bookkeeping, of the same kind as a loop index.

So `maginterp` gains INT_ARITH_FREE, an inert flag on the PR35 precedent, testing
operand TYPE -- sound because a field element is an FFElement in every field this
repository builds and never a Python int. Testing by syntax, the form already used
for integer-literal products, cannot work: `n1 + n2` contains no literal.

The convention is NOT uniform and assuming it was is recorded as a refutation:
every addition at both genera and both bases moves +2A, every genus-3 doubling
moves +2A, and the genus-2 doublings move nothing at all, carrying no weight
addition on the counted path. Nothing but A ever moves, in any family.

Also fixed here, and it is the E12 class in the counter itself: the module header
claimed every contributing execution was compared against reference.py's
independent Cantor arithmetic. It never was. `measure_call` returned the value and
the histogram discarded it, so a call that left the domain and returned a wrong
answer was histogrammed as a legitimate count -- the failure mode that yields a
plausible wrong number rather than an obvious one, and precisely the risk the split
work adds. The check is now performed and a disagreeing sample is dropped. The
header also advertised a --verify flag that has never existed.

Verified: 168 measured shapes reproduce their published cell exactly -- 42 in each
of the three genus-3 split families against tab:g3splitfcosts, 14 in each of the
three genus-2 posReduced families against tab:splitfcosts -- with no unmatched
figure and no published row left unreproduced, both tables parsed out of the .tex
rather than retyped. Every priced row comes out at exactly one inversion, which
chapter 6 claims in prose and nothing had checked for the split model. posReduced
matches 14 of 14 where negReduced differs on three to five rows by an operation or
two, independently confirming posReduced as the genus-2 basis of record.

All six ramified families are byte-identical to master, across both the convention
and the new cross-check. No .mag file is touched.
A guard never seen to fire is not known to be a guard, and a counter that
produces split figures nobody has checked is worse than one that refuses. So this
section pins the answer, not merely the fact of an answer: eight cells quoted from
tab:splitfcosts and tab:g3splitfcosts, reproduced by measurement from the .mag
sources alone. The two share no code and no input -- one is a LaTeX table typed in
2020, the other is the formulas being executed over GF(31).

Both halves are shown to fail without their fix. Reverting the split dispatch
gives "splitneg/g3/arb not measurable: split family: see split_spec"; reverting
the integer-bookkeeping convention gives "13ADD n=1,0 measured (22,2,45,7,1),
published (22,2,43,7,1)".

The provocation disables the convention at its real decision site and requires
every pinned cell to move by exactly its recorded delta. Those deltas are pinned
per cell rather than assumed uniform because measured they are not: additions move
+2A at both genera and both bases, genus-3 doublings move +2A, genus-2 doublings
move nothing. A uniform "+2A everywhere" assertion was written first and failed on
three cells -- kept in the docstring, since the next person will reach for it too.

Note which basis genus 2 pins. The published table prices posReduced, and
negReduced legitimately differs from it by an operation or two on several rows.
Pinning a negReduced row against that table would assert the wrong thing.

18 sections, 0 failed, 0 skipped.
Half the README's operation-count table read *published* rather than *measured*,
with the reason stated inline: the counter could not measure split families and
refused rather than guessing, so no split figure was independently checked by this
repository. All twelve are now measured, and each was verified against its
published cell before the label was changed -- the caveat is discharged rather
than deleted.

N31 records the work at publication depth: why the split domain genuinely resists
the arb-contrast, why the balancing weight turns out to BE the published tables'
row index rather than a nuisance parameter, and the systematic +2A that the
adjudication rule resolved in the thesis's favour and the tool's disfavour.

It carries three honest limits inline, per the standing rule. The genus-2
negReduced families are measured but checked against nothing, no published table
pricing that basis. Both genus-3 posReduced families do not exist to measure.
Thirteen genus-3 shapes per family cost nothing at all -- inputs the dispatcher
answers without arithmetic, verified like every other sample and carried in
--json, but summarised rather than listed since no published row prices them.

And it records the refutation, per the other standing rule: the convention is not
uniform, and the guard that assumed it was failed on three cells.

Ground rule 7 deviation, the PR16 precedent: N31 is written here rather than
reconstructed later, but sits in its own commit rather than being split across the
two before it, because it is one narrative entry and splitting it would leave an
intermediate commit holding half of it.
@salindne
salindne merged commit 4477dca into master Aug 25, 2026
6 checks passed
@salindne
salindne deleted the pr41-opcount-split branch August 25, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant