opcount: measure the split model — all fifteen families, and 168 published cells confirmed - #36
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
verification/opcount.py— the counter of record — measured 6 of 15 families. It now measures allfifteen, 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
mainselected[f for f in families if f.model == "ramified"]before the skip machinery, so thenine split families were never attempted rather than attempted and refused.
--jsonreported"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
arbextracts 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
fnorh. They takeccs, the constantsPrecomputederives.Most of the machinery already existed.
driver.split_specderives the domain fromPrecompute'sown source,
split_curve_in_domainvalidates the places at infinity, andbuild_args_splitmaps the(u1,v1,n1,u2,v2,n2,ccs)signature — the refusal message literally named the function that does thejob. All three are used verbatim.
Precomputeruns once per curve outside the measured call, beingper-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 —
33ADD89 against 87,3DBL103 against 101, both two-Up-Adjust doublings likewise, with
M,SandCexact everywhere. Asystematic 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 afield element. Every addition runs
n := n1 + n2 - 2and every genus-3 doublingnp := n + n - 2.Charged as field additions that is a flat
+2Aon 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
maginterpgainsINT_ARITH_FREE, an inert flag on the PR35 precedent, testing operand type —sound because a field element is an
FFElementin every field this repository builds and never a Pythonint. Testing by syntax, the form already used for integer-literal products, cannot work here:n1 + n2contains no literal.The convention is not uniform, and assuming it was is recorded as a refutation. The guard was first
written to assert
+2Aeverywhere and failed on three cells. Measured per shape: every addition atboth genera and both bases moves
+2A, every genus-3 doubling moves+2A, and the genus-2 doublingsmove nothing at all — they carry no weight addition on the counted path. Nothing but
Aever 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-2posReducedfamiliesagainst
tab:splitfcosts. Zero unmatched, and no published row left unreproduced. Both tables are parsedout of the
.texrather than retyped, because a table retyped by hand is a third place for a number tobe 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.
posReducedmatches thepublished table 14 of 14 in all three characteristic classes;
negReduceddiffers on three to fiverows, each by one or two operations. A different algorithm, not a defect — and an independent
confirmation of
posReducedas 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 independentCantor arithmetic. It never was —
measure_callreturned 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
--verifyflag that has never existed.Verification
selftest18 passed, 0 failed, 0 skipped — the newsplit_countssection pins eight publishedcells and is shown to fail with either half reverted: dropping the split dispatch gives
not measurable: split family: see split_spec, dropping the convention gives13ADD n=1,0 measured (22,2,45,7,1), published (22,2,43,7,1)whiteboxPASS ·driver --strict13,746 / 13,746 ·dominanceclean on 39 files ·blockcheck0 wrong ·check_paths --strict·check_readme_paths·ruffgit diff --name-only master -- '*.mag'is empty, but run anywayDocumentation
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.mdN31 records the work at publicationdepth, with three honest limits inline: the genus-2
negReducedfamilies are measured but checkedagainst nothing, no published table pricing that basis; the two genus-3
posReducedfamilies do notexist 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 summarisedrather than listed, since no published row prices them.
What this unblocks
C4 (the adjugate trade,
+1M −12Aacross six sites) and C5 (the redundant 2×2 system,11M 6Adeletable) both recorded this counter as their sole outstanding prerequisite. Their starting points are
now confirmed rather than assumed — genus-3 split
Deg3ADDandDeg3DBLmeasure 65M 3S 87A 12C and73M 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.