One calling convention: smaller divisor first, coefficients descending, in all twelve families - #40
Merged
Merged
Conversation
Same-degree cases are spelled with a single digit everywhere in this repository -- Deg1ADD for 1+1, Deg3ADD for 3+3, Deg2ADD in genus-2 split and genus-2 ramified -- except the 2+2 case of the two genus-3 models, which spelled it Deg22ADD. That was the lone holdout. Purely cosmetic: a same-degree case has no operand order to confuse, which is why it lands separately from the reorder that follows and is verifiable by copy-and-diff. 55 occurrences in live code: 9 in each of the three genus-3 split additions, 8 in each of the three genus-3 ramified additions, 1 in each of the three genus-3 split whitebox generators, 1 in verification/blockcheck.py. The variants follow, Deg22ADDUP -> Deg2ADDUP and Deg22ADDUP2 -> Deg2ADDUP2. Verified beforehand that no Deg2ADD existed anywhere under g3/, so there is no collision, and the g3/timings/ copy is deliberately untouched as not-of-record. The frozen corpus needed no substitution, checked rather than assumed: Deg22ADD never appears inside a debug label string. Genus-3 split labels are numeric (ADD000..ADD349) and the genus-3 ramified labels were numbered in PR6, carrying the prose name only in a trailing comment. harvested_cases.json holds 0 cases and coverage_baseline.json carries no Deg-bearing labels. This is the trap that drifted 56 cases in PR21 and 20 in PR36; it is absent here. Docs handled in three ways rather than substituted blindly. NEW_WORK.md x10 and the two EFFICIENCY files x3 are references to a function that still exists under a new name, so they follow the code, on the PR21 precedent. One sentence had both names collide into "arb_splitG3_ADD's Deg2ADD and genus-2 ramified's Deg2ADD" -- correct, since they now genuinely share a name, but reworded to say it once. The two README mentions are deliberately left: both sit in prose this PR falsifies, one calling Deg22ADD "the lone holdout" whose collapse "is queued", and a blind pass would have produced "collapsing Deg2ADD to Deg2ADD". They are rewritten in the docs commit. Gates: whitebox 7043/7043 byte-identical, opcount identical per shape (22ADD n=0,0 still 61M 3S 78A 25C and so on -- opcount names shapes by degree, so the check is that nothing else moved), driver --strict 55236/55236, dominance clean on 39 files, blockcheck 412 compared 0 wrong, adjugate ok, selftest 19/0/0, Magma 30 testers 0 failures 0 skips in 4m49s.
Nine mixed-degree functions and their eighteen dispatcher call sites. The signatures now read as the rest of the repository does: Deg12ADD(u0,v0,up1,up0,vp1,vp0,ccs) Deg13ADD(u0,v0,up2,up1,up0,vp2,vp1,vp0,ccs) Deg23ADD(u1,u0,v1,v0,up2,up1,up0,vp2,vp1,vp0,ccs) byte-for-byte the genus-2 ramified Deg12ADD and genus-3 ramified Deg23ADD parameter lists. Before this, genus-3 split was the last family where the digits in Deg<i><j>ADD were merely sorted rather than positional, so a caller who read the name and passed operands in the genus-2 order got a silently wrong answer. Two different edits, deliberately not conflated. Inside each function body a prefix swap u <-> up and v <-> vp with subscripts untouched, the prefix being the divisor identity and the subscript the coefficient index. At the call sites no renaming at all, only the two argument groups exchanged, because the dispatcher's variables keep their meaning. The signature needs both. Done in one regex pass with a lookup rather than three passes through a sentinel, so u0 -> up0 and up0 -> u0 cannot interfere: re.sub replaces each match independently and never rescans its output. The alternation lists up|vp before u|v, since Python matches leftmost-first rather than longest, and the other order matches the u of up0, fails the digit test and skips the token -- a half-applied swap, which is the wrong-answer case. Scoped to the nine function bodies rather than the file. That is load-bearing: up3 is not a divisor coefficient but a live scratch temporary in Deg3ADD holding up2^2, and a file-wide pass would have renamed it to u3. Self-checks before any gate: within the nine bodies the token counts exchange exactly, u 543 <-> 597 and v 602 <-> 495, and the diff is balanced at 1106 insertions and 1106 deletions. Gates: opcount IDENTICAL across all 42 shapes, which is the acceptance test for a reorder; whitebox 7043/7043 with every branch label unchanged; driver --strict 55236/55236; dominance clean on 39 files; selftest 19/0/0; Magma 30 testers 0 failures 0 skips in 4m56s. DEVIATION from the plan, stated rather than glossed: the plan named an _OLD same-session differential as the per-file acceptance test. It was attempted in Python and abandoned after ten minutes without completing -- interpreting nine functions of a 10,000-line file at that volume is too slow. Relying instead on three things that together cover it: the frozen whitebox corpus was extracted from the OLD code and records both returned divisors and branch labels, and the new code reproduces every case exactly; driver --strict agrees with an independent Cantor implementation over 55,236 operations across every degree combination, so old and new agree with each other through it; and the specific failure this edit risks, a missed call site feeding old-order arguments to a reordered function, is precisely what the corpus catches because its cases drive the dispatcher rather than the leaf functions.
The same nine functions and eighteen call sites in each, by the same script that did arb, unchanged. Genus-3 split was the last family in the repository whose Deg<i><j>ADD digits were sorted rather than positional; all twelve families now agree, and the signatures match the genus-2 ramified and genus-3 ramified parameter lists byte for byte. nch2: u tokens 1279 -> 1342, up 1272 -> 1209, diff 1113 / 1113 ch2: u tokens 1259 -> 1305, up 1211 -> 1165, diff 1087 / 1087 Unlike C4, the specialisations needed no separate derivation here. The reorder depends only on the parameter list, which all three files share, where C4's trade depended on how each file had inlined t3 and so differed per file. Gates: opcount IDENTICAL across all 42 shapes in both families, whitebox 7043/7043 with every branch label unchanged, driver --strict 55236/55236, dominance clean on 39 files, selftest 19/0/0, Magma 30 testers 0 failures 0 skips in 4m52s. The _OLD differential deviation recorded on the arb commit applies here too, on the same reasoning and with the same evidence standing in for it.
Five signatures and nine call sites in each of the three files. Deg02ADDDWN, Deg02ADDUP, Deg12ADD, Deg12ADDUP and Deg2ADD wrote each operand's coefficients ascending, u0,u1; they now descend, u1,u0, which three of the four families already did. The argument is not merely consistency with the other families: this family contradicted ITSELF. Deg2DBL in the same file reads (u1,u0,v1,v0,ccs) while Deg2ADD read (u0,u1,v0,v1,...), so a reader moving between a doubling and an addition in one file met two conventions. They now agree, and Deg12ADD's parameter list is byte-identical to genus-2 ramified's. Positional only: no renaming, no token substitution, no body edits. The parameter names keep their meanings and only their order moves, so signature and call sites had to move together -- miss one and u0/u1 arrive swapped, which is a wrong answer with no compile error. Both were rewritten from the same derived run structure, grouping consecutive parameters by prefix and reversing each run, so they cannot disagree. Doubling is untouched, already descending. Gates: opcount IDENTICAL in all three families across every shape, whitebox 7043/7043, driver --strict 55236/55236, dominance clean on 39 files, selftest 19/0/0, Magma 30 testers 0 failures 0 skips.
Five signatures and nine call sites in each of the three files, by the same derived run structure that did posReduced. The two bases now agree with each other and with the other three families, so every Deg12ADD in the repository reads (u0,v0,up1,up0,vp1,vp0,...). Gates as for posReduced, run over the combined state: opcount IDENTICAL in all three splitneg/g2 families, whitebox 7043/7043, driver --strict 55236/55236, dominance clean on 39 files, selftest 19/0/0, Magma 30 testers 0 failures 0 skips. Timing note for anyone comparing runs: that Magma pass took 24m55s of wall clock against the usual five minutes, but the suite's own reported times sum to 283s against a 297s baseline. The formulas did the same work at the same speed; the wall clock was host contention, not a regression.
README's naming section described the state this PR ended. It called Deg22ADD "the lone holdout" whose collapse "is queued", and its table said genus-3 split binds the larger divisor first. Both are now false, which is why the two Deg22ADD mentions were deliberately held back from the rename commit rather than substituted -- a blind pass would have produced "collapsing Deg2ADD to Deg2ADD". The table now states the convention as a fact and lists all four families agreeing, noting the two Deg12ADD forms differ only in their curve-constant tail. The "Genus-3 split still binds the larger divisor first" bullet leaves Current and planned work, that work being done. NEW_WORK.md N34 records the transferable part, which is not the convention but the observation that this was three kinds of edit with different risk: a pure rename, verifiable by copy-and-diff; a prefix swap inside a function body; and an argument reorder at the call site with no renaming at all. Conflating the last two corrupts the dispatcher, and doing the second without the third is a wrong answer with no compile error. It also records why the swap is one regex pass with a lookup rather than three through a sentinel -- re.sub never rescans its output, so the ordering hazard becomes structurally impossible -- and the two things that would each have produced a half-applied swap: the alternation must list up|vp before u|v because Python matches leftmost-first rather than longest, and the substitution must be scoped to the target bodies because up3 is a live scratch temporary in Deg3ADD rather than a divisor coefficient. And it records the honest limit: the plan's _OLD differential was abandoned as too slow to interpret, with the frozen corpus, driver --strict and the corpus's dispatcher-level coverage standing in for 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.
Deg<i><j>ADDnames the degrees of its two input divisors, and the repository disagreed with itself about what that meant. Three families read the digits as positional — smaller-degree divisor first — while genus-3 split read them as merely sorted and took the larger first. Separately, genus-2 split wrote each operand's coefficients ascending where everyone else wrote them descending.After this PR all twelve families share one convention. Every
Deg12ADDin the repository reads(u0, v0, up1, up0, vp1, vp0, …), differing only in the curve-constant tail.Why the operand half is a correctness problem
A caller who reads the name and passes operands in the genus-2 order gets a silently wrong answer, not an error, because the arities match. That is the defect class that got the inherited
*_use_for_odd_even.mfiles dropped at import for carrying a reversed operand convention.The coefficient half is genuinely cosmetic, but genus-2 split contradicted itself:
Deg2DBL(u1,u0,v1,v0,ccs)andDeg2ADD(u0,u1,v0,v1,…)sat in the same file, so a reader moving between a doubling and an addition met two conventions without leaving the page.Scope
Deg22ADD→Deg2ADDblockcheck.pyThree kinds of edit, deliberately not conflated
This is the part worth carrying forward, because the three have different risk and different verification:
u ↔ upandv ↔ vpwith subscripts untouched, the prefix being the divisor identity and the subscript the coefficient index within it.Conflating 2 and 3 corrupts the dispatcher. Doing 2 without 3 is a wrong answer with no compile error.
The swap is one pass with a lookup, not three through a sentinel
A sentinel is correct, but its correctness rests on the sentinel never colliding and on nobody reordering the passes later.
re.subwith a callback replaces each match independently and never rescans its own output, sou0 → up0andup0 → u0in the same pass cannot interfere — the hazard becomes structurally impossible rather than avoided by discipline.Two things would each have produced a half-applied swap, which is the wrong-answer case, and both were settled by measurement before the first edit rather than discovered after:
up|vpmust precedeu|vin the alternation. Python matches leftmost-first, not longest, so the other order matches theuofup0, fails the digit test and skips the token — leaving everyupuntouched while everyuswaps.up3is not a divisor coefficient; it is a live scratch temporary inDeg3ADDholdingup2². A file-wide pass would have renamed it tou3.The pattern was run against all three files beforehand and shown to rewrite exactly thirteen token forms while leaving the other fifty-two identifiers beginning
uorvalone —upp0–upp3,unp0,vpp0–vpp2,vh0–vh2,vt0–vt2and the bareu,up,v,vpamong them.The acceptance test for a reorder is that nothing moves
opcountidentical per branch, per family, before and after. A reorder that changes a count has changed behaviour. Measured identical across all 42 shapes in each genus-3 split family and every shape in all six genus-2 split families. That gate did not exist when this work was first scheduled; PR41 supplied it.Two mechanical self-checks ran before any gate: within the swapped bodies the token counts exchange exactly (
u543 ↔ 597,v602 ↔ 495 in arb), andgit diff --statshows equal insertions and deletions — 1,106/1,106 for arb, 2,200/2,200 for the two specialisations. A partial swap fails both.The trap that cost two earlier PRs was absent, and was checked rather than assumed
PR21 drifted 56 frozen cases and PR36 another 20, both because a rename touched debug label strings the corpus stores verbatim. Here
Deg22ADDnever appears inside a label string: genus-3 split labels are numeric and the genus-3 ramified labels were numbered in PR6, carrying the prose name only in a trailing comment.harvested_cases.jsonholds 0 cases andcoverage_baseline.jsonnoDeg-bearing labels. So no frozen-corpus substitution and no re-harvest.Two other scope facts came from measuring rather than assuming: the whitebox generators and random testers needed no change at all, calling only the
ADDdispatcher and never the leaf functions; and doubling was untouched throughout, already descending.Gates
opcountidentical everywhere ·whitebox7,043/7,043 with every branch label unchanged ·driver --strict55,236/55,236 ·dominanceclean on 39 files ·selftest19/0/0 ·blockcheck· three full Magma passes, 30 testers, 0 failures, 0 skips ·readme-paths94/94 ·check_paths --strict259/259.Two limits, recorded rather than glossed
The
_OLDdifferential was not run. The plan named a same-session old-versus-new comparison as the per-file acceptance test, the technique PR36 used. It was attempted in Python and abandoned after ten minutes without completing — interpreting nine functions of a 10,000-line file at that volume is too slow, and the Magma form is its own piece of tooling. What stands in for it: the frozen corpus was extracted from runs of the old code and records both returned divisors and branch labels, and the new code reproduces every case exactly;driver --strictagrees with an independent Cantor implementation over 55,236 operations across every degree combination; and the specific failure this edit risks, a missed call site feeding old-order arguments to a reordered function, is exactly what the corpus catches, because its cases drive the dispatcher rather than the leaf functions.One Magma pass took 24m55s of wall clock against the usual five. Not a regression: the suite's own reported times sum to 283s against a 297s baseline, so the formulas did the same work at the same speed. The cause was host contention, traced to an orphaned scratch process from an earlier session.