Skip to content

Split genus-3: carry the full adjugate, +1M -12A at all six sites - #39

Merged
salindne merged 4 commits into
masterfrom
c4c5-split-adjugate
Aug 26, 2026
Merged

Split genus-3: carry the full adjugate, +1M -12A at all six sites#39
salindne merged 4 commits into
masterfrom
c4c5-split-adjugate

Conversation

@salindne

Copy link
Copy Markdown
Owner

Carries the full adjugate of the 3x3 matrix T in the genus-3 split addition and doubling and applies it as a matrix-vector product, where the formulas previously built only its first column and reduced vt*q mod up by Karatsuba twice.

+1M -12A at every one of the six sites, +6M -72A in total, accepted comfortably by the thesis's own 1M : 3A rule.

addition doubling
arb 65/3/87/12 → 66/3/75/12 73/3/101/19 → 74/3/89/19
nch2 65/3/85/0 → 66/3/73/0 72/4/97/0 → 73/4/85/0
ch2 65/3/80/0 → 66/3/68/0 71/4/86/1 → 72/4/74/1

No other shape moved in any of the twelve families. That is the acceptance test, not a remark: the trade is confined to the generic path it targets.

The finding is that the adjugate is free, which is not what was predicted

The plan expected the extra multiplication to be paid at the T block, on the reasoning that nine adjugate entries must cost more than three, and hand-counted the block conversion as losing 2M 1A. My own independent hand count said 0M. The measurement says the block costs 15M 0S 9A either way.

The reason is structural. Column 3 of T is x times column 2, reduced modulo the modulus, and adj(T) is itself a multiplication matrix, so it inherits that shift structure. The bottom row is the cross product of columns 1 and 2 and needs no third column at all; the remaining six entries are then shifts of that row at one multiplication each rather than 2x2 minors at two:

m5 = m9 + w2*m8      m2 = -w0*m7      m1 = m5 + w1*m7
m4 = m8 + w2*m7      m6 = m2 - w1*m8  m3 = -w0*m8

Three minors plus six shifts is exactly what three minors plus the now-unneeded t3, t6, t9 used to cost. A further multiplication falls out of t7*m3 = t2*m8, so the determinant expands along T's first column reading m8 in place of m3, and m3 is left to the generic path that alone consumes it — no degenerate leaf pays for it.

So the whole trade is downstream, where applying the matrix costs 12M 8A against Karatsuba's 11M 20A, and there is no reduction step because applying the multiplication matrix is the reduction.

verification/adjugate.py settles this by executing both routes from their real .mag text: split_q_col1 at 15M 0S 9A for four entries, split_q at 15M 0S 9A for seven. The pre-C4 route is kept as a candidate rather than deleted, so the comparison keeps scoring what this work measured itself against.

Established before the edit, not after

  • Six shift identities and both determinant forms verified over 5,000 trials on GF(10007), with t1, t4, t7 and the modulus free, so one check covers the additions (modulus up) and the doublings (modulus u) rather than assuming one transfers to the other.
  • The shared prefix feeds a 1,600-line degenerate block, so before deleting anything: t9 shown never to appear below the prefix, t3 and t6 shown to be reused temps whose every read is preceded by its own local assignment, m5/m6/m9 shown to be unused names, and every Karatsuba temp t0t5 shown reassigned before being read. Only m7's prefix value is consumed downstream, and the new arrangement still computes it.
  • Negative control: breaking the target gave 117 whitebox mismatches, so the corpus can see this edit. That check exists because a green gate on an edit to a published formula carried no information until PR38 — which is exactly what reverted the ADD29/ADD33 saving the first time.

Six published cells move, adjudicated

33ADD n=0,0 and 3DBL n=0 across all three families. Per the standing rule, opcount measures the new figures by execution and a hand count of the changed region reproduces the same +1M -12A — two methods sharing no code.

Thesis/ERRATA.md E-T10 records it and the cells are updated in Thesis/chapter6.tex. This is not an error in the thesis: the published counts were correct for the formulas as published, and the formulas moved under them. ThesisPublished/ is untouched, verified.

verification/selftest.py's published pins are superseded with the reason inline; every other pinned cell still matches its published value.

C5 is dropped, and its result is recorded rather than lost

The redundant 2x2 system was scoped into this PR and taken out of it. Not refuted — unfinished, and worth little: the leaf sits three zero-tests deep and is reached about once in ten thousand inputs, so its 11M 6A is a raw count on a path almost never taken, and it moves no published cell.

The mathematics was settled on the way out and is in ERRATA.md E24. The closed form is proportional to the true S*dw2^-1 mod unp in 400 of 400 constructed trials, which is stronger than the finding originally carried. What blocks it is that the file's (b1, b0) is not that quantity up to any scalar, 0 of 400, so its normalisation is unknown. The entry carries the two leads, the nine unexamined sibling sites, the measured fact that the corpus can see the leaf, and a warning not to re-attempt the Python reproduction whose transcription is precisely what is in doubt.

NEW_WORK.md N33 states the general rule, which outlives the leaf: wherever a formula computes r = a mod b and later needs the Bezout cofactor of b modulo a, the quotient discarded by that division already is that cofactor, up to lc(r). The ramified model exploits this and split does not, and the reason is inversion scheduling rather than oversight — split must invert late because its f is non-monic of degree 2g+2, which is how it holds to a single inversion, and late inversion means every upstream quantity is weighted.

A gate hazard found by tripping over it

ERRATA.md E23. The gates provoke their own guards by editing formula files in place and restoring them. Two runs at once are not safe: a second selftest restored a file while the first held it mutated, leaving the ramified ADD missing the ledger comment adjugate.py anchors on — a file neither run was testing. It surfaced as a gate failure naming a file nothing had edited, which is a confusing signal whose natural first hypothesis is wrong.

The same applies to Magma, which loads those files as each tester starts. Not fixed, because the provocations must edit the real path to locate their targets by content. Recorded so the symptom is recognised rather than debugged as a phantom formula defect, with the rule: run the gates serially, never alongside Magma, and check git status before believing a failure that names a file you did not touch.

Gates

Per formula commit: opcount as tabled, hand count, whitebox 7,043/7,043, driver --strict 55,236/55,236, dominance clean on 39 files, adjugate, selftest 19/0/0, and Magma 30 testers, 0 failures, 0 skips — three separate full suite runs, 5m13s, 5m22s and 4m51s.

Docs commit touches zero .mag files: readme-paths 94/94, check_paths --strict 259/259, ruff clean on CI's ruleset, opcount re-confirming all six README figures as written.

…ic path

The T-block already cost 15M 0S 9A for three adjugate entries. Computing the
bottom row from columns 1 and 2 of T and shifting it costs the SAME 15M 0S 9A
and yields seven, because column 3 of T is x*(column 2) reduced mod up, so the
six remaining entries are one multiplication each rather than a 2x2 minor. The
determinant then reads m8 rather than m3, since t7*m3 = (-up0*t7)*m8 = t2*m8.

So the block conversion is free and the saving is downstream. With the full
adjugate in hand the generic path applies it as a matrix-vector product instead
of forming q = m7x^2 + m4x + m1 and reducing vt*q mod up by Karatsuba twice:
12M 11A against 11M 23A.

Deg3ADD generic 65M 3S 87A 12C -> 66M 3S 75A 12C. No other shape moves. The
1M:3A trade rule accepts +1M -12A comfortably.

Established before the edit rather than after: the six shift identities and both
determinant forms verified over 4000 trials on GF(10007); t9 shown never to
appear below the prefix, t3 and t6 shown to be reused temps whose every read is
preceded by its own local assignment, and every Karatsuba temp t0-t5 reassigned
before being read in the rest of the generic path.

adjugate.py keeps the pre-C4 route as split_q_col1, which measures the same
15M 0S 9A for four entries against the new block's seven, and its .mag anchor
moves to the new determinant line. selftest.py's published pin for this cell is
superseded with the reason recorded: the thesis is not wrong, the formula moved
under it, and Thesis/ERRATA.md carries the divergence.

Gates: hand count agreeing at +1M -12A independently of opcount, whitebox
7043/7043, driver --strict 55236/55236, dominance clean on 39 files, adjugate
ok, selftest 19/0/0, Magma 30 testers 0 failures 0 skips in 5m13s.
Deg3ADD generic:
  nch2  65M 3S 85A 0C -> 66M 3S 73A 0C
  ch2   65M 3S 80A 0C -> 66M 3S 68A 0C

Not a copy of the arb edit, because both files had already inlined t3 as
-up0*t8*m7 and +up0*t8*m7. That spends two multiplications where arb's t3*m7
spends one, so their determinants cost 4M against arb's 3M and their prefixes
reached the same 15M by a different route. The new determinant reads t2*m8 and
wants neither t3 nor the inlined pair, so in these two files the conversion
drops two multiplications at d and adds three shifts, arriving at the same +1M
overall from the other side.

No other shape moves in either family. The identities were re-verified with
t1, t4, t7 and the modulus all free, 5000 trials over GF(10007), which covers
the doublings as well since only the t-recurrence shape matters.

Gates: opcount as above, whitebox 7043/7043, driver --strict 55236/55236,
dominance clean on 39 files, adjugate ok, selftest 19/0/0, Magma 30 testers
0 failures 0 skips in 5m22s.

Deviation from the plan's one-file-per-commit: both specialisations are in this
one commit. opcount isolates each family, but whitebox, dominance and Magma
cover them jointly, and splitting the commit would imply per-file evidence that
was not gathered.
Deg3DBL typical:
  arb   73M 3S 101A 19C -> 74M 3S 89A 19C
  nch2  72M 4S  97A  0C -> 73M 4S 85A  0C
  ch2   71M 4S  86A  1C -> 72M 4S 74A  1C

Same rewrite as the additions at the other modulus: the doubling reduces mod u
where the addition reduces mod up, and its vector is kp rather than vt. The
shift identities were re-verified with t1, t4 and t7 free and the modulus free,
5000 trials over GF(10007), so one check covers both operations rather than
assuming the addition's result transfers.

That completes C4 at all six sites, +6M -72A in total, with no unintended shape
moving in any of the twelve families.

The block conversion is free, which is worth stating because the plan predicted
otherwise. adjugate.py measures both routes from their real .mag text and each
costs 15M 0S 9A -- the old one yielding four adjugate entries, the new one
seven, because column 3 of T is x*(column 2) reduced mod the modulus and six
entries are therefore one multiplication each. The whole +1M is downstream,
where applying the matrix costs 12M against Karatsuba's 11M, and the -12A is
downstream too: 20A becomes 8A.

selftest's published pin for 3DBL n=0 is superseded alongside 33ADD n=0,0, both
hand-counted independently of opcount and both agreeing at +1M -12A.

Gates: opcount as above, whitebox 7043/7043, driver --strict 55236/55236,
dominance clean on 39 files, adjugate ok, selftest 19/0/0, Magma 30 testers
0 failures 0 skips in 4m51s.
Thesis/ERRATA.md E-T10 and the cells themselves in Thesis/chapter6.tex: the
genus-3 split Degree-3 rows of tab:g3splitfcostsADD and tab:g3splitfcostsDBL,
six cells, +1M -12A each. Not an error in the thesis -- the published counts were
right for the formulas as published and the formulas moved under them, which is
why Thesis/ must stop quoting a cost the code no longer has. ThesisPublished/ is
untouched, verified.

NEW_WORK.md N33 states the result as mathematics rather than an operation count.
The adjugate of a multiplication matrix inherits the shift structure of the
matrix, so with column 3 of T equal to x times column 2 reduced mod the modulus,
the bottom row is a cross product needing no third column and the other six
entries are shifts at one multiplication each. All nine entries therefore cost
the same 15M 0S 9A as the three the old arrangement built, and the whole trade is
downstream: 12M 8A to apply the matrix against Karatsuba's 11M 20A.

It also records the refutation. The plan hand-counted the block conversion as
losing 2M 1A and my own independent hand count said 0M; the measurement says the
block is free and the total is +1M. adjugate.py settles it by executing both
routes from their real .mag text, and keeps the pre-C4 route as split_q_col1 so
the comparison keeps scoring what C4 measured itself against.

ERRATA.md E23: the gates mutate formula files in place to provoke their own
guards, so they cannot be run concurrently, nor alongside Magma which loads those
files. Found by making the mistake: two selftest runs raced and left the ramified
ADD missing the ledger comment adjugate anchors on, surfacing as a gate failure
naming a file nothing had edited. Not fixed; the symptom is recorded so the next
person does not debug a phantom formula defect.

ERRATA.md E24: C5, dropped from this PR. The Bezout cofactor identity is proved
-- the closed form is proportional to the true S*dw2^-1 mod unp in 400 of 400
constructed trials -- but the file's (b1,b0) is not that quantity up to scalar in
any of them, so its normalisation is unknown and the substitution cannot be
written. Recorded rather than applied, per the rule for a result proved before
its oracle exists, with the two leads, the nine unexamined sibling sites, and a
warning not to re-attempt the Python reproduction whose transcription is the
thing in doubt.

README: the six genus-3 split cells in Typical Case Operation Counts, the four in
the Related Work genus-3 split table, and the two appendix rows, each re-measured
by opcount rather than copied. The exactness claim now names its two deliberate
exceptions instead of overstating.

Gates: readme-paths 94/94, check_paths --strict 259/259, ruff clean on CI's
ruleset, opcount confirming all six figures as written.
@salindne
salindne merged commit fe4c87e into master Aug 26, 2026
6 checks passed
@salindne
salindne deleted the c4c5-split-adjugate branch August 27, 2026 10:33
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