diff --git a/ERRATA.md b/ERRATA.md index 4c9edfa..624c5ff 100644 --- a/ERRATA.md +++ b/ERRATA.md @@ -1062,3 +1062,113 @@ the two versions cannot drift. Magma now passes all three. basis-aware either, so a posReduced run wrote over the negReduced run's log. Worse than untidy: `--inherit-from` reads those logs, so a negReduced arb could have been handed posReduced cases. Found by noticing a POS log where a NEG one belonged. + +## E23: the gates mutate formula files, so they cannot be run concurrently + +**Found 2026-08-26 during C4, by making the mistake.** `verification/selftest.py` +provokes its own guards by editing formula files in place and restoring them: +`blocks` drops a live `ExactQuotient` from `arb_splitG3_ADD.mag`, `dominance` +deletes a live `k3` assignment, `adjugate` removes a ledger comment. Each run +restores what it touched, and each is correct on its own. + +**Two runs at once are not.** A second `selftest` restored a file while the first +still held it mutated, and the restore was lost. The tree was left with +`g3/ramifiedModel/g3Formulas/arb_ramifiedG3_ADD.mag` missing the six-line +`// top: 15m 0s 9a` ledger comment that `adjugate.py` anchors on -- a file +neither run was testing and no one had edited. + +**It surfaced as a gate failure naming an untouched file:** `adjugate` reporting +`arb_ramifiedG3_ADD.mag no longer carries its 'top' op-count comment, so +shipped_7 is measured against nothing`, while a standalone `adjugate` run passed. +That is a confusing signal, and the natural first hypothesis -- that the edit +under test broke something -- is wrong. + +**The same hazard applies to Magma.** `./test_all.sh` loads formula files as each +tester starts, so a `selftest` running alongside it can feed a deliberately +broken file to a tester, and the suite's verdict then describes neither the +committed code nor the code under test. + +**Not fixed, and the fix is not obvious.** Restoring via a temporary copy rather +than in place would remove the hazard, but the provocations must edit the real +path because the gates locate their targets by content in the real file. A lock +file would serialise them at the cost of silent waiting. Recorded so the next +person recognises the symptom rather than debugging a phantom formula defect. + +**Meanwhile: run the gates serially, and never alongside Magma.** If a gate fails +naming a file the current work did not touch, check `git status` before believing +it. + +## E24: a proved saving in the split genus-3 addition that cannot yet be applied + +**Registered 2026-08-26, when C5 was dropped from the C4 pull request.** Not a +defect in the formulas -- a saving that is mathematically established and whose +implementation is blocked on a question about the code, recorded per the standing +rule that a result proved before its oracle exists is written down rather than +applied. + +**The setting.** In `F_q[x]`, one Euclidean division step on `(up, dw2)` gives +`up = q*dw2 + dw3` with `deg dw3 < deg dw2`. That is the first step of the +extended Euclidean algorithm on the pair, and the extended algorithm maintains +Bezout cofactors alongside the remainders: with `s_i*up + t_i*dw2 = r_i` and the +recurrences `s_{i+1} = s_{i-1} - q_i*s_i`, `t_{i+1} = t_{i-1} - q_i*t_i`, after +one step `t_1 = -q`. So the cofactor of `dw2` is, up to sign, exactly the +quotient the division already computed. Reducing modulo `up` gives +`t_1*dw2 = dw3 (mod up)`, hence `dw2^{-1} = t_1*dw3^{-1} (mod up)` whenever +`dw3` is a unit there. + +**The saving.** In the leaf of `Deg3ADD` where the guards force `d = 0`, +`t7 = 0` and `t4 = 0`, the formulas need `b2 = S*(dw2^{-1} mod up)` with `S` the +monic `dw3`, and spend **11M 6A** on a `2x2` Cramer solve, its own comment naming +the method: `//b2 := S*R!((Q!a)^-1) mod up; //2x2 system`. By the above the +polynomial part is free, and the intermediate it needs is already in hand: +`t0 := w2*up2 - w1` is computed twelve lines earlier as part of +`//dw3 := up mod dw2;`. + +**What is proved.** Constructing inputs that satisfy the leaf's precondition -- +`S1 = x + a`, `dw2 = S1*(bx + c)`, `q = (1/b)x + g`, `up = q*dw2 + lam*S1`, so +`S1 | up` and `up mod dw2 = lam*S1` -- and computing the true +`S*dw2^{-1} mod unp` independently in the quotient ring: the closed form +`(-w2, -t0)` is proportional to it in **400 of 400** trials over GF(1000003). +Constructed rather than sampled because rejection sampling reaches this leaf +about once in ten thousand random inputs. + +**What blocks it.** The file's `(b1, b0)` is **not** that quantity up to any +scalar, 0 of 400. So the substitution is not `b1 := -w2; b0 := -t0` at some +weight, and the file's normalisation of `b2` is unknown. Two leads: +`b1 := m1 + w4*m2` and `b0 := m3 + w4*m4` form `M*(1,w4)^T` for +`M = [[m1,m2],[m3,m4]]`, which is not a Cramer solve for an inverse; and the +comment `//a := dw2 mod up;` does not say whether the modulus is the original +degree-3 `up` or the degree-2 quotient built two lines above, with the file's +`up` still holding the degree-3 one at that point. + +**Do not settle this by re-implementing the block in Python.** That was tried. +The transcription is precisely what is in doubt, so the experiment cannot +distinguish its own error from a real difference. Observe the real execution +instead: a recursive copy of `maginterp`'s statement loop in the manner of +`detect.py`, snapshotting the environment where `b0` lands, over the whitebox +cases reaching `ADD281`, `ADD282` and `ADD283`. Note `opcount.py` cannot verify +such an edit -- the leaf is not a priced row -- so `whitebox.py` and Magma would +carry it alone. The corpus **can** see the leaf: breaking `b1` deliberately gives +9 whitebox mismatches, checked 2026-08-26. + +**Worth little in expectation, which is why it was dropped rather than finished.** +Three zero-tests deep and reached about once in ten thousand inputs, so the +11M 6A is a raw count on a path almost never taken, and no published cell moves. + +**Nine sibling sites are unexamined**, each a `b2` computation of the same family: +`arb_splitG3_ADD.mag:8113/8249/9312`, `nch2_splitG3_ADD.mag:7834/7970/9031`, +`ch2_splitG3_ADD.mag:7696/7832/8871` (numbering predates C4; locate by content). +Each needs a division producing the remainder just above it and needs `u = up`. +Genus-2 split has no `b2` site at all, and no doubling has one. + +**The transferable rule, which outlives this leaf.** Wherever an explicit formula +computes a remainder `r = a mod b` and later requires the Bezout cofactor of `b` +modulo `a` -- equivalently an inverse of `b` in `F_q[x]/(a)` -- the quotient +discarded by that division already is that cofactor, up to the scalar `lc(r)`. +The ramified model exploits this and the split model does not, and the reason is +inversion scheduling rather than oversight: ramified inverts early, so `S1` +becomes an exact scalar and everything downstream is unweighted, while split +inverts late because its `f` is non-monic of degree `2g+2` and `upp` must be +normalised after `upp` is known, which is how it holds to a single inversion. +Late inversion means every upstream quantity is carried with a weight, and a +weighted cofactor is not a drop-in for an exact one. diff --git a/NEW_WORK.md b/NEW_WORK.md index 2f0d471..c222405 100644 --- a/NEW_WORK.md +++ b/NEW_WORK.md @@ -2958,3 +2958,117 @@ Two standing rules, both learned the hard way here: alternative is that the next person re-runs the experiment. - **State honest limits in the entry, not in a footnote.** E-T5 is not measured and says so; the doubling composite is approximate and says so. + +## N33 — The adjugate is nearly free, and a discarded quotient is a Bezout cofactor + +**Status** — established, C4. **Where** — `g3/splitModel/negReduced/g3Formulas/`, +all six `Deg3ADD` and `Deg3DBL` files; the published tables are +`Thesis/chapter6.tex` `tab:g3splitfcosts{ADD,DBL}`, corrected as `E-T10`. + +**What was there.** Composing two degree-3 divisors needs `s = vt*q mod up`, +where `q = d/w mod up` is a quadratic whose coefficient vector is the first +column of the adjugate of the `3x3` matrix `T`. The split formulas built exactly +that column -- three `2x2` minors `m1`, `m4`, `m7` -- took the determinant by +expanding along `T`'s first row, and then reduced the product `vt*q` modulo `up` +using Karatsuba twice. + +**The finding, and it is not the one the plan predicted.** Carrying the *whole* +adjugate and applying it as a matrix-vector product is `+1M -12A` on the generic +path of all six operations. The plan expected the extra multiplication to be paid +at the `T` block, on the reasoning that nine entries must cost more than three. +Measured, the block costs **`15M 0S 9A` either way**. + +The reason is structural rather than a happy accident, and it is the part worth +publishing. Column 3 of `T` is `x` times column 2, reduced modulo `up`. `adj(T)` +is itself a multiplication matrix -- by `q`, up to `det(T)` -- so it inherits that +shift structure. Concretely, the bottom row `(m7, m8, m9)` is the cross product +of columns 1 and 2 of `T` and needs no third column at all, and the remaining six +entries are then **shifts of that row costing one multiplication each** instead +of a `2x2` minor costing two: + + m5 = m9 + w2*m8 m2 = -w0*m7 m1 = m5 + w1*m7 + m4 = m8 + w2*m7 m6 = m2 - w1*m8 m3 = -w0*m8 + +with `(w0, w1, w2)` the modulus coefficients. Three minors at `2M` and six shifts +at `1M` is `12M`, exactly what three minors at `2M` plus the three now-unneeded +`T` entries `t3`, `t6`, `t9` at `1M` used to cost. The nine-entry adjugate is +free relative to the three-entry column. + +A further multiplication falls out of `t7*m3 = (-w0*t7)*m8 = t2*m8`, the signs +cancelling because `t2` and `m3` carry the same `-w0` factor. So the determinant +can be expanded along `T`'s first *column* reading `m8` in place of `m3`, and +`m3` is left wanted only by the generic path, which computes it there -- no +degenerate leaf pays for an entry it never reads. + +**Where the cost actually moves.** Downstream. Applying the matrix is +`9M 6A` plus the three deferred entries at `3M 2A`, so `12M 8A`, against +Karatsuba's `11M 20A`. One multiplication on, twelve additions off, and no +reduction step because applying the multiplication matrix *is* the reduction. + +**Measured, six sites, `+1M -12A` at every one:** + +| | 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** | + +`+6M -72A` in total, accepted comfortably by the thesis's own `1M : 3A` rule. No +other shape moved in any of the twelve families, which is the acceptance test: +the trade is confined to the generic path it targets. + +**Scope, and why it is exactly six.** The `3x3` multiplication matrix exists only +when reducing modulo a degree-3 modulus, so the lower-degree additions and +doublings carry a smaller system with nothing to trade, and genus-2 split has no +such matrix at all -- `m7` occurs zero times in all six of its formula files. + +**For the paper.** State the shift structure as the result, not the `+1M -12A`. +The operation count is an artefact of one model at one genus; the statement that +*the adjugate of a multiplication matrix inherits the shift structure of the +matrix, so all nine entries cost barely more than one column* is what transfers, +and it is what makes the matrix-vector form cheaper than forming the polynomial +and reducing. + +### The refutation this replaced + +The plan carried a hand count saying the conversion **loses `2M 1A`** at the +block and that the `+1M` is paid there. That count is wrong, and it was wrong in +a way no amount of re-reading would have caught: it priced the ramified route's +seven entries against split's three without noticing both arrangements spend the +same fifteen multiplications. `verification/adjugate.py` settles it by executing +both from their real `.mag` text -- `split_q_col1` at `15M 0S 9A` for four +entries, `split_q` at `15M 0S 9A` for seven -- and the pre-C4 route is kept as a +candidate rather than deleted so the comparison keeps scoring it. + +**The methodological point, which this project keeps relearning:** state what a +change *removes*, and measure the total. A hand count of two arrangements is a +prediction, not a result, and here the prediction had the right bottom line for +the wrong reason. My own independent hand count also said `0M` before the +measurement said `+1M`. + +### A related result, proved and deliberately not applied + +The same leaf structure exposed that split spends `11M 6A` reconstructing a +Bezout cofactor it has already computed. The mathematics is settled -- `400 of +400` constructed trials against ground truth in the quotient ring -- and the +implementation is blocked on the file's normalisation of `b2`, so it is recorded +in `ERRATA.md` **E24** rather than applied. The general statement belongs here +because it outlives the leaf: + +**Wherever an explicit formula computes a remainder `r = a mod b` and later needs +the Bezout cofactor of `b` modulo `a`, equivalently an inverse of `b` in +`F_q[x]/(a)`, the quotient discarded by that division already is that cofactor, +up to the scalar `lc(r)`.** One Euclidean step gives `t_1 = -q` in the extended +algorithm's recurrence `t_{i+1} = t_{i-1} - q_i*t_i`, and reducing +`s_1*a + t_1*b = r` modulo `a` gives `b^{-1} = t_1*r^{-1} (mod a)`. The cost of +the modular inverse collapses to the cost of inverting a leading coefficient. + +The ramified model exploits this; the split model does not. The reason is +**inversion scheduling**, and that is the publishable observation: ramified +inverts early, so the monic-making scalar is exact and everything downstream is +unweighted, while split must invert late because its `f` is non-monic of degree +`2g+2` and `upp` needs normalising only after `upp` is known -- batching that +normalisation with weight removal is precisely how the split formulas hold to a +single inversion. Late inversion means every upstream quantity is carried +projectively, and a weighted cofactor is not a drop-in for an exact one. The +omission is a consequence of a deliberate design choice, not an oversight. diff --git a/README.md b/README.md index 6dee0b4..8190eca 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,15 @@ The frequent case of the highest-degree operation in each family. "Frequent" me g3 ramified arb533711544804 g3 ramified nch2533590535610 g3 ramified ch2513620514552 -g3 split arb653871273310119 -g3 split nch2653850724970 -g3 split ch2653800714861 +g3 split arb66375127438919 +g3 split nch2663730734850 +g3 split ch2663680724741 Every figure is measured. `python3 verification/opcount.py --family ` executes the formulas over a real field, identifies the frequent case by observing which branch is taken, and cross-checks each contributing call against the Cantor reference implementation. All fifteen families measure, the twelve above plus genus-2 split negative reduced. -Where the thesis publishes a cell, measurement reproduces it exactly: the genus-2 ramified rows against `tab:ramfcosts`, and 168 split shapes against `tab:splitfcosts` and `tab:g3splitfcosts{ADD,DBL}`, every one at exactly one inversion. One systematic divergence turned up during that check and was the tool's rather than the thesis's, a flat `+2A` on every split row, because a divisor's balancing weight is a small integer and `n := n1 + n2 - 2` is bookkeeping rather than field arithmetic. See [NEW_WORK.md](NEW_WORK.md) N31. +Where the thesis publishes a cell, measurement reproduces it exactly: the genus-2 ramified rows against `tab:ramfcosts`, and 168 split shapes against `tab:splitfcosts` and `tab:g3splitfcosts{ADD,DBL}`, every one at exactly one inversion. Two of those cells have since moved by design: the genus-3 split `33ADD n=0,0` and `3DBL n=0` each trade one multiplication for twelve additions, so measurement and the published table differ there deliberately, recorded in [Thesis/ERRATA.md](Thesis/ERRATA.md). One systematic divergence turned up during the original check and was the tool's rather than the thesis's, a flat `+2A` on every split row, because a divisor's balancing weight is a small integer and `n := n1 + n2 - 2` is bookkeeping rather than field arithmetic. See [NEW_WORK.md](NEW_WORK.md) N31. The genus-2 split figures are positive reduced, the basis of record. Negative reduced is a different algorithm and differs by an operation or two on several rows. @@ -119,10 +119,10 @@ Against Rezai Rad et al. 2019 and Sutherland 2019. MSACMSACMSAC -3DBL odd85216307481270724970 -3ADD odd75213807361270653850 -3DBL char 28911160n/a714861 -3ADD char 28101180n/a653800 +3DBL odd85216307481270734850 +3ADD odd75213807361270663730 +3DBL char 28911160n/a724741 +3ADD char 28101180n/a663680 @@ -544,12 +544,12 @@ Operation costs for every non-degenerate function, measured as in [Typical Case 22ADD n=1,1371567362570334520 23ADD n=0,07538918725783724767 23ADD n=1,0411593411570411550 -33ADD n=0,06538712653850653800 +33ADD n=0,06637512663730663680 1DBL n=042566274065773874410 1DBL n=1711967115371143 1DBL n=2143258143243123194 2DBL n=06259531607894607807 2DBL n=13705613352543361483 -3DBL n=073310119724970714861 +3DBL n=07438919734850724741 diff --git a/Thesis/ERRATA.md b/Thesis/ERRATA.md index f9f9500..598b7e5 100644 --- a/Thesis/ERRATA.md +++ b/Thesis/ERRATA.md @@ -343,3 +343,41 @@ from the `f4`-free code, which is why the contradiction is with the sentence and the numbers. --- + +## E-T10 — the genus-3 split Degree-3 rows trade one multiplication for twelve additions + +**`chapter6.tex:2389` in `tab:g3splitfcostsDBL` and `:2491` in `tab:g3splitfcostsADD`.** +**Measured, and hand-counted independently.** + +**This is not an error in the thesis.** The published counts were correct for the +formulas as published; the formulas have since changed under them. The entry +exists because `Thesis/` must not quote a cost the code no longer has. + + :2389 &73&3&101&19 &72&4&97&0 &71&4&86&1 becomes + &74&3& 89&19 &73&4&85&0 &72&4&74&1 + + :2491 &65&3& 87&12 &65&3&85&0 &65&3&80&0 becomes + &66&3& 75&12 &66&3&73&0 &66&3&68&0 + +Six cells, `+1M -12A` in each, across the `arb`, `nch2` and `ch2` columns of both +tables. S and C are unchanged everywhere, and no other row in either table moves. + +**What changed.** The addition and doubling now carry the full adjugate of the +`3x3` matrix `T` and apply it as a matrix-vector product, where they previously +built only its first column and reduced `vt*q mod up` by Karatsuba twice. The +trade is `+1M` for `-12A`, which the thesis's own `1M : 3A` rule accepts +comfortably. + +**Where the cost actually moves, which the derivation should state.** Not at the +`T` block: that costs `15M 0S 9A` either way, yielding three adjugate entries in +the old arrangement and seven in the new, because column 3 of `T` is `x` times +column 2 reduced modulo the modulus, so six of the nine entries are one +multiplication each rather than a `2x2` minor. The whole `+1M` and the whole +`-12A` are downstream, where applying the matrix costs `12M 8A` against +Karatsuba's `11M 20A`. + +**Adjudicated per the standing rule.** `verification/opcount.py` measures the new +figures by execution and a hand count of the changed region reproduces the same +`+1M -12A`, two methods sharing no code. `verification/selftest.py`'s published +pins for `33ADD n=0,0` and `3DBL n=0` are updated with the reason recorded +inline, and every other pinned cell still matches its published value. diff --git a/Thesis/chapter6.tex b/Thesis/chapter6.tex index ad8e4f3..2323845 100644 --- a/Thesis/chapter6.tex +++ b/Thesis/chapter6.tex @@ -2386,7 +2386,7 @@ \subsection{Field Operation Costs and Comparisons} &62&5&95&31 &60&7&89&4 &60&7&80&7\TS\\ \hline Degree 3 -&73&3&101&19 &72&4&97&0 &71&4&86&1\TS\\ +&74&3&89&19 &73&4&85&0 &72&4&74&1\TS\\ \hline \end{tabular} \end{table} @@ -2488,7 +2488,7 @@ \subsection{Field Operation Costs and Comparisons} &75&3&89&18 &72&5&78&3 &72&4&76&7\TS\\ \hline Degree 3 -&65&3&87&12 &65&3&85&0 &65&3&80&0\TS\\ +&66&3&75&12 &66&3&73&0 &66&3&68&0\TS\\ \hline \end{tabular} \end{table} diff --git a/g3/splitModel/negReduced/g3Formulas/arb_splitG3_ADD.mag b/g3/splitModel/negReduced/g3Formulas/arb_splitG3_ADD.mag index 1bbd95e..5ea92fc 100644 --- a/g3/splitModel/negReduced/g3Formulas/arb_splitG3_ADD.mag +++ b/g3/splitModel/negReduced/g3Formulas/arb_splitG3_ADD.mag @@ -8391,14 +8391,26 @@ Deg3ADD:= function(u2,u1,u0,v2,v1,v0,up2,up1,up0,vp2,vp1,vp0,ccs) t2 := -up0*t7; t5 := t1 - up1*t7; t8 := t4 - up2*t7; - t3 := -up0*t8; - t6 := t2 - up1*t8; - t9 := t5 - up2*t8; + //t3 := -up0*t8; + //t6 := t2 - up1*t8; + //t9 := t5 - up2*t8; - m1 := t5*t9 - t6*t8; - m4 := t6*t7 - t4*t9; + // Bottom row of the adjugate needs only columns 1 and 2 of T. + m9 := t1*t5 - t2*t4; + m8 := t2*t7 - t1*t8; m7 := t4*t8 - t5*t7; - d := t1*m1 + t2*m4 + t3*m7; //Determinant of T = t1*m1 + t2*m4 + t3*m7 + + // Column 3 of T is x*(column 2) reduced mod up, so the remaining six + // adjugate entries are shifts of the bottom row with one multiplication + // each rather than a 2x2 minor. m3, m4 and m6 are the three the + // determinant does not read, so they wait until the generic path below. + m5 := m9 + up2*m8; + m2 := -up0*m7; + m1 := m5 + up1*m7; + + // t7*m3 = t7*(-up0*m8) = (-up0*t7)*m8 = t2*m8, the signs cancelling because + // t2 and m3 carry the same -up0 factor. So the determinant needs m8, not m3. + d := t1*m1 + t4*m2 + t2*m8; //Determinant of T if IsZero(d) then //dw1 := u mod up; // = (a1^-1)*S for S,a1,b1 = XGCD(u,up); @@ -10042,16 +10054,17 @@ Deg3ADD:= function(u2,u1,u0,v2,v1,v0,up2,up1,up0,vp2,vp1,vp0,ccs) vt1:= vp1 - v1; vt0:= vp0 - v0; - //Compute s = vt*q mod u2 using Karatsuba twice - t0 := vt0*m1; - t1 := vt1*m4; - t2 := vt2*m7; - t3 := (m4 + m7)*(vt1 + vt2) - t2 - t1 - up2*t2; - t4 := up1*t2; - t5 := t4 - t1; - sp0 := t0 - up0*t3; - sp1 := (m1 + m4)*(vt0 + vt1) - sp0 - (up0 + up1)*(t2 + t3) + t5; - sp2 := (m1 + m7)*(vt0 + vt2) - t0 - t2 - t5 - up2*t3; + // the three adjugate entries the determinant did not need + m4 := m8 + up2*m7; + m6 := m2 - up1*m8; + m3 := -up0*m8; + + //Compute s = vt*q mod up as the matrix-vector product sp = M*vt. M is + //multiplication by q = d/u mod up, so this is the same map as vt*q mod up, + //and applying the matrix needs no reduction step. + sp0 := vt0*m1 + vt1*m2 + vt2*m3; + sp1 := vt0*m4 + vt1*m5 + vt2*m6; + sp2 := vt0*m7 + vt1*m8 + vt2*m9; if IsZero(sp2) then if IsZero(sp1) then diff --git a/g3/splitModel/negReduced/g3Formulas/arb_splitG3_DBL.mag b/g3/splitModel/negReduced/g3Formulas/arb_splitG3_DBL.mag index dce8d44..bdb2d82 100644 --- a/g3/splitModel/negReduced/g3Formulas/arb_splitG3_DBL.mag +++ b/g3/splitModel/negReduced/g3Formulas/arb_splitG3_DBL.mag @@ -1163,16 +1163,26 @@ Deg3DBL:= function(u2,u1,u0,v2,v1,v0,ccs) t2 := -u0*t7; t5 := t1 - u1*t7; t8 := t4 - u2*t7; - t3 := -u0*t8; - t6 := t2 - u1*t8; - t9 := t5 - u2*t8; - - //Determinant of T = t1*m1 + t2*m4 + t3*m7 - //First column of M corresponds to polynomial q = m7x^2 + m4x + m1 = d/u1 mod u2. - m1 := t5*t9 - t6*t8; - m4 := t6*t7 - t4*t9; + //t3 := -u0*t8; + //t6 := t2 - u1*t8; + //t9 := t5 - u2*t8; + + // Bottom row of the adjugate needs only columns 1 and 2 of T. + m9 := t1*t5 - t2*t4; + m8 := t2*t7 - t1*t8; m7 := t4*t8 - t5*t7; - d := t1*m1 + t2*m4 + t3*m7; + + // Column 3 of T is x*(column 2) reduced mod u, so the remaining six + // adjugate entries are shifts of the bottom row with one multiplication + // each rather than a 2x2 minor. m3, m4 and m6 are the three the + // determinant does not read, so they wait until the generic path below. + m5 := m9 + u2*m8; + m2 := -u0*m7; + m1 := m5 + u1*m7; + + // t7*m3 = t7*(-u0*m8) = (-u0*t7)*m8 = t2*m8, the signs cancelling because + // t2 and m3 carry the same -u0 factor. So the determinant needs m8, not m3. + d := t1*m1 + t4*m2 + t2*m8; if IsZero(d) then //gcd(h + 2*v1, u1) = (x + xp1) @@ -1418,16 +1428,17 @@ Deg3DBL:= function(u2,u1,u0,v2,v1,v0,ccs) kp0 := dn5 + c3*v0 - h2*v1 - v2*vh1 - u1*k2 - u2*k1 - t12 - t12; - //Compute s = k*q mod u using Karatsuba twice - t0 := kp0*m1; - t1 := kp1*m4; - t2 := kp2*m7; - t3 := (m4 + m7)*(kp1 + kp2) - t2 - t1 - u2*t2; - t4 := u1*t2; - t5 := t4 - t1; - sp0 := t0 - u0*t3; - sp1 := (m1 + m4)*(kp0 + kp1) - sp0 - (u0 + u1)*(t2 + t3) + t5; - sp2 := (m1 + m7)*(kp0 + kp2) - t0 - t2 - t5 - u2*t3; + // the three adjugate entries the determinant did not need + m4 := m8 + u2*m7; + m6 := m2 - u1*m8; + m3 := -u0*m8; + + //Compute s = k*q mod u as the matrix-vector product sp = M*kp. M is + //multiplication by q = d/w mod u, so this is the same map as kp*q mod u, + //and applying the matrix needs no reduction step. + sp0 := kp0*m1 + kp1*m2 + kp2*m3; + sp1 := kp0*m4 + kp1*m5 + kp2*m6; + sp2 := kp0*m7 + kp1*m8 + kp2*m9; if IsZero(sp2) then if IsZero(sp1) then diff --git a/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_ADD.mag b/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_ADD.mag index 46f20cf..83feb5c 100644 --- a/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_ADD.mag +++ b/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_ADD.mag @@ -7964,14 +7964,27 @@ Deg3ADD:= function(u2,u1,u0,v2,v1,v0,up2,up1,up0,vp2,vp1,vp0,ccs) t2 := up0*t7; t5 := t1 + up1*t7; t8 := t4 + up2*t7; - //t3 := +u20*t8; - t6 := t2 + up1*t8; - t9 := t5 + up2*t8; + //t3 := up0*t8; + //t6 := t2 + up1*t8; + //t9 := t5 + up2*t8; - m1 := t5*t9 + t6*t8; - m4 := t6*t7 + t4*t9; + // Bottom row of the adjugate needs only columns 1 and 2 of T. + m9 := t1*t5 + t2*t4; + m8 := t2*t7 + t1*t8; m7 := t4*t8 + t5*t7; - d := t1*m1 + t2*m4 + up0*t8*m7; //Determinant of T = t1*m1 + t2*m4 + t3*m7 + + // Column 3 of T is x*(column 2) reduced mod up, so the remaining six + // adjugate entries are shifts of the bottom row with one multiplication + // each rather than a 2x2 minor. m3, m4 and m6 are the three the + // determinant does not read, so they wait until the generic path below. + m5 := m9 + up2*m8; + m2 := up0*m7; + m1 := m5 + up1*m7; + + // t7*m3 = t7*(up0*m8) = (up0*t7)*m8 = t2*m8. So the determinant needs m8, + // not m3, and the inlined up0*t8*m7 it used to spend two multiplications + // on is gone. + d := t1*m1 + t4*m2 + t2*m8; //Determinant of T if IsZero(d) then //dw1 := u mod up; // = (a1^-1)*S for S,a1,b1 = XGCD(u,up); @@ -9584,16 +9597,17 @@ Deg3ADD:= function(u2,u1,u0,v2,v1,v0,up2,up1,up0,vp2,vp1,vp0,ccs) vt1:= vp1 + v1; vt0:= vp0 + v0; - //Compute s = vt*q mod u2 using Karatsuba twice - t0 := vt0*m1; - t1 := vt1*m4; - t2 := vt2*m7; - t3 := (m4 + m7)*(vt1 + vt2) + t2 + t1 + up2*t2; - t4 := up1*t2; - t5 := t4 + t1; - sp0 := t0 + up0*t3; - sp1 := (m1 + m4)*(vt0 + vt1) + sp0 + (up0 + up1)*(t2 + t3) + t5; - sp2 := (m1 + m7)*(vt0 + vt2) + t0 + t2 + t5 + up2*t3; + // the three adjugate entries the determinant did not need + m4 := m8 + up2*m7; + m6 := m2 + up1*m8; + m3 := up0*m8; + + //Compute s = vt*q mod up as the matrix-vector product sp = M*vt. M is + //multiplication by q = d/u mod up, so this is the same map as vt*q mod up, + //and applying the matrix needs no reduction step. + sp0 := vt0*m1 + vt1*m2 + vt2*m3; + sp1 := vt0*m4 + vt1*m5 + vt2*m6; + sp2 := vt0*m7 + vt1*m8 + vt2*m9; if IsZero(sp2) then if IsZero(sp1) then diff --git a/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_DBL.mag b/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_DBL.mag index f9022c2..3a51868 100644 --- a/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_DBL.mag +++ b/g3/splitModel/negReduced/g3Formulas/ch2_splitG3_DBL.mag @@ -1090,16 +1090,26 @@ Deg3DBL:= function(u2,u1,u0,v2,v1,v0,ccs) t2 := u0*t7; t5 := t1 + u1*t7; t8 := t4 + u2*t7; - t3 := u0*t8; - t6 := t2 + u1*t8; - t9 := t5 + u2*t8; - - //Determinant of T = t1*m1 + t2*m4 + t3*m7 - //First column of M corresponds to polynomial q = m7x^2 + m4x + m1 = d/u1 mod u2. - m1 := t5*t9 + t6*t8; - m4 := t6*t7 + t4*t9; + //t3 := u0*t8; + //t6 := t2 + u1*t8; + //t9 := t5 + u2*t8; + + // Bottom row of the adjugate needs only columns 1 and 2 of T. + m9 := t1*t5 + t2*t4; + m8 := t2*t7 + t1*t8; m7 := t4*t8 + t5*t7; - d := t1*m1 + t2*m4 + t3*m7; + + // Column 3 of T is x*(column 2) reduced mod u, so the remaining six + // adjugate entries are shifts of the bottom row with one multiplication + // each rather than a 2x2 minor. m3, m4 and m6 are the three the + // determinant does not read, so they wait until the generic path below. + m5 := m9 + u2*m8; + m2 := u0*m7; + m1 := m5 + u1*m7; + + // t7*m3 = t7*(u0*m8) = (u0*t7)*m8 = t2*m8. So the determinant needs m8, + // not m3. + d := t1*m1 + t4*m2 + t2*m8; if IsZero(d) then //gcd(h + 2*v1, u1) = (x + xp1) @@ -1327,16 +1337,17 @@ Deg3DBL:= function(u2,u1,u0,v2,v1,v0,ccs) kp1 := v0 + yn0 + v2*vn2 + yy2 + u2*k2; kp0 := f3 + h2*v1 + v2*vh1 + u1*kp2 + u2*kp1; - //Compute s = k*q mod u using Karatsuba twice - t0 := kp0*m1; - t1 := kp1*m4; - t2 := kp2*m7; - t3 := (m4 + m7)*(kp1 + kp2) + t2 + t1 + u2*t2; - t4 := u1*t2; - t5 := t4 + t1; - sp0 := t0 + u0*t3; - sp1 := (m1 + m4)*(kp0 + kp1) + sp0 + (u0 + u1)*(t2 + t3) + t5; - sp2 := (m1 + m7)*(kp0 + kp2) + t0 + t2 + t5 + u2*t3; + // the three adjugate entries the determinant did not need + m4 := m8 + u2*m7; + m6 := m2 + u1*m8; + m3 := u0*m8; + + //Compute s = k*q mod u as the matrix-vector product sp = M*kp. M is + //multiplication by q = d/w mod u, so this is the same map as kp*q mod u, + //and applying the matrix needs no reduction step. + sp0 := kp0*m1 + kp1*m2 + kp2*m3; + sp1 := kp0*m4 + kp1*m5 + kp2*m6; + sp2 := kp0*m7 + kp1*m8 + kp2*m9; if IsZero(sp2) then diff --git a/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_ADD.mag b/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_ADD.mag index 3e5118a..a99554f 100644 --- a/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_ADD.mag +++ b/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_ADD.mag @@ -8099,13 +8099,26 @@ Deg3ADD:= function(u2,u1,u0,v2,v1,v0,up2,up1,up0,vp2,vp1,vp0,ccs) t5 := t1 - up1*t7; t8 := t4 - up2*t7; //t3 := -up0*t8; - t6 := t2 - up1*t8; - t9 := t5 - up2*t8; + //t6 := t2 - up1*t8; + //t9 := t5 - up2*t8; - m1 := t5*t9 - t6*t8; - m4 := t6*t7 - t4*t9; + // Bottom row of the adjugate needs only columns 1 and 2 of T. + m9 := t1*t5 - t2*t4; + m8 := t2*t7 - t1*t8; m7 := t4*t8 - t5*t7; - d := t1*m1 + t2*m4 - up0*t8*m7; //Determinant of T = t1*m1 + t2*m4 + t3*m7 + + // Column 3 of T is x*(column 2) reduced mod up, so the remaining six + // adjugate entries are shifts of the bottom row with one multiplication + // each rather than a 2x2 minor. m3, m4 and m6 are the three the + // determinant does not read, so they wait until the generic path below. + m5 := m9 + up2*m8; + m2 := -up0*m7; + m1 := m5 + up1*m7; + + // t7*m3 = t7*(-up0*m8) = (-up0*t7)*m8 = t2*m8, the signs cancelling because + // t2 and m3 carry the same -up0 factor. So the determinant needs m8, not m3, + // and the inlined -up0*t8*m7 it used to spend two multiplications on is gone. + d := t1*m1 + t4*m2 + t2*m8; //Determinant of T if IsZero(d) then //dw1 := u mod up; // = (a1^-1)*S for S,a1,b1 = XGCD(u,up); @@ -9769,16 +9782,17 @@ Deg3ADD:= function(u2,u1,u0,v2,v1,v0,up2,up1,up0,vp2,vp1,vp0,ccs) vt1:= vp1 - v1; vt0:= vp0 - v0; - //Compute s = vt*q mod u2 using Karatsuba twice - t0 := vt0*m1; - t1 := vt1*m4; - t2 := vt2*m7; - t3 := (m4 + m7)*(vt1 + vt2) - t2 - t1 - up2*t2; - t4 := up1*t2; - t5 := t4 - t1; - sp0 := t0 - up0*t3; - sp1 := (m1 + m4)*(vt0 + vt1) - sp0 - (up0 + up1)*(t2 + t3) + t5; - sp2 := (m1 + m7)*(vt0 + vt2) - t0 - t2 - t5 - up2*t3; + // the three adjugate entries the determinant did not need + m4 := m8 + up2*m7; + m6 := m2 - up1*m8; + m3 := -up0*m8; + + //Compute s = vt*q mod up as the matrix-vector product sp = M*vt. M is + //multiplication by q = d/u mod up, so this is the same map as vt*q mod up, + //and applying the matrix needs no reduction step. + sp0 := vt0*m1 + vt1*m2 + vt2*m3; + sp1 := vt0*m4 + vt1*m5 + vt2*m6; + sp2 := vt0*m7 + vt1*m8 + vt2*m9; if IsZero(sp2) then if IsZero(sp1) then diff --git a/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_DBL.mag b/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_DBL.mag index 6caf2ec..72748e4 100644 --- a/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_DBL.mag +++ b/g3/splitModel/negReduced/g3Formulas/nch2_splitG3_DBL.mag @@ -1111,16 +1111,26 @@ Deg3DBL:= function(u2,u1,u0,v2,v1,v0,ccs) t2 := -u0*t7; t5 := t1 - u1*t7; t8 := t4 - u2*t7; - t3 := -u0*t8; - t6 := t2 - u1*t8; - t9 := t5 - u2*t8; - - //Determinant of T = t1*m1 + t2*m4 + t3*m7 - //First column of M corresponds to polynomial q = m7x^2 + m4x + m1 = d/u1 mod u2. - m1 := t5*t9 - t6*t8; - m4 := t6*t7 - t4*t9; + //t3 := -u0*t8; + //t6 := t2 - u1*t8; + //t9 := t5 - u2*t8; + + // Bottom row of the adjugate needs only columns 1 and 2 of T. + m9 := t1*t5 - t2*t4; + m8 := t2*t7 - t1*t8; m7 := t4*t8 - t5*t7; - d := t1*m1 + t2*m4 + t3*m7; + + // Column 3 of T is x*(column 2) reduced mod u, so the remaining six + // adjugate entries are shifts of the bottom row with one multiplication + // each rather than a 2x2 minor. m3, m4 and m6 are the three the + // determinant does not read, so they wait until the generic path below. + m5 := m9 + u2*m8; + m2 := -u0*m7; + m1 := m5 + u1*m7; + + // t7*m3 = t7*(-u0*m8) = (-u0*t7)*m8 = t2*m8, the signs cancelling because + // t2 and m3 carry the same -u0 factor. So the determinant needs m8, not m3. + d := t1*m1 + t4*m2 + t2*m8; if IsZero(d) then //gcd(h + 2*v1, u1) = (x + xp1) @@ -1368,16 +1378,17 @@ Deg3DBL:= function(u2,u1,u0,v2,v1,v0,ccs) kp0 := f3 - v2*vh1 - u1*k2 - u2*k1 - t12 - t12; - //Compute s = k*q mod u using Karatsuba twice - t0 := kp0*m1; - t1 := kp1*m4; - t2 := kp2*m7; - t3 := (m4 + m7)*(kp1 + kp2) - t2 - t1 - u2*t2; - t4 := u1*t2; - t5 := t4 - t1; - sp0 := t0 - u0*t3; - sp1 := (m1 + m4)*(kp0 + kp1) - sp0 - (u0 + u1)*(t2 + t3) + t5; - sp2 := (m1 + m7)*(kp0 + kp2) - t0 - t2 - t5 - u2*t3; + // the three adjugate entries the determinant did not need + m4 := m8 + u2*m7; + m6 := m2 - u1*m8; + m3 := -u0*m8; + + //Compute s = k*q mod u as the matrix-vector product sp = M*kp. M is + //multiplication by q = d/w mod u, so this is the same map as kp*q mod u, + //and applying the matrix needs no reduction step. + sp0 := kp0*m1 + kp1*m2 + kp2*m3; + sp1 := kp0*m4 + kp1*m5 + kp2*m6; + sp2 := kp0*m7 + kp1*m8 + kp2*m9; if IsZero(sp2) then k3 := T4 + T4; diff --git a/verification/adjugate.py b/verification/adjugate.py index aefb89d..ab85d0b 100644 --- a/verification/adjugate.py +++ b/verification/adjugate.py @@ -1032,13 +1032,17 @@ def shipped_9(t1, t4, t7, up0, up1, up2): m9=m9, d=d) -def split_q(t1, t4, t7, up0, up1, up2): - """arb_splitG3_ADD.mag, `t1 := u0 - up0;` .. `d := t1*m1 + t2*m4 + t3*m7;`. +def split_q_col1(t1, t4, t7, up0, up1, up2): + """The split route as shipped BEFORE C4: `d := t1*m1 + t2*m4 + t3*m7;`. The whole first column, and d. - The split model wants only column 1 of the adjugate (it is the coefficient - vector of q = d/w mod up), and pays for all nine T entries to get it. + It wants only column 1 of the adjugate (the coefficient vector of + q = d/w mod up) and pays for all nine T entries to get it. Kept as a + candidate rather than deleted: it is a valid route and the comparison should + keep scoring it, and it is the thing C4 measured itself against. Note it + costs the SAME 15M 0S 9A as the shift route below -- the block conversion + alone wins nothing, which is why C4's unit of work is the generic path. """ t2 = -(up0 * t7) t5 = t1 - up1 * t7 @@ -1053,6 +1057,32 @@ def split_q(t1, t4, t7, up0, up1, up2): return dict(m1=m1, m4=m4, m7=m7, d=d) +def split_q(t1, t4, t7, up0, up1, up2): + """arb_splitG3_ADD.mag, `t2 := -up0*t7;` .. `d := t1*m1 + t4*m2 + t2*m8;`. + + The split route as shipped SINCE C4, and textually the ramified block with + `up` for `u`: bottom row of the adjugate from columns 1 and 2 of T, then six + shifts at one multiplication each, then the column expansion that reads m8 + rather than m3 because t7*m3 = t2*m8. + + Column 3 of T is x*(column 2) reduced mod up, which is what makes the six + remaining entries shifts rather than 2x2 minors. m3, m4 and m6 are the three + the determinant does not read, so the file defers them to the generic path + and no degenerate leaf pays for them. + """ + t2 = -(up0 * t7) + t5 = t1 - up1 * t7 + t8 = t4 - up2 * t7 + m9 = t1 * t5 - t2 * t4 + m8 = t2 * t7 - t1 * t8 + m7 = t4 * t8 - t5 * t7 + m5 = m9 + up2 * m8 + m2 = -(up0 * m7) + m1 = m5 + up1 * m7 + d = t1 * m1 + t4 * m2 + t2 * m8 + return dict(m1=m1, m2=m2, m5=m5, m7=m7, m8=m8, m9=m9, d=d) + + def _rank5_row3(t1, t4, t7, up0, up1, up2): """The five-product bottom row, and column 2 of T. Shared by the variants. @@ -1175,6 +1205,7 @@ def __init__(self, name, fn, nargs, expect=None, annot=None, group="entries"): ("arb_ramifiedG3_DBL.mag", "block")), Candidate("shipped_9", shipped_9, 6, (18, 0, 11)), Candidate("split_q", split_q, 6, (15, 0, 9)), + Candidate("split_q_col1", split_q_col1, 6, (15, 0, 9)), Candidate("rank5_7_d", rank5_7_d, 6, (15, 0, 14)), Candidate("rank5_7", rank5_7, 6, (12, 0, 12)), Candidate("rank5_9_d", rank5_9_d, 6, (17, 0, 16)), @@ -1932,7 +1963,7 @@ def section_bound(primes=(2, 3, 5, 7, 11), verbose=False): "t2 := -u0*t7;", "d := t1*m1 + t4*m2 + t2*m8;", {"u0": "up0", "u1": "up1", "u2": "up2"}), ("split_q", "g3/splitModel/negReduced/g3Formulas/arb_splitG3_ADD.mag", - "t2 := -up0*t7;", "d := t1*m1 + t2*m4 + t3*m7;", {}), + "t2 := -up0*t7;", "d := t1*m1 + t4*m2 + t2*m8;", {}), ) # The three `t1 = u0 - up0` differences are deliberately outside every anchor diff --git a/verification/selftest.py b/verification/selftest.py index 11a12e5..3ae6d33 100644 --- a/verification/selftest.py +++ b/verification/selftest.py @@ -1874,9 +1874,20 @@ def section_split_counts(rep, quick): # every tuple a published cell and every delta measured. PINS = [ ("splitneg/g3/arb", 31, { - "33ADD n=0,0": ((65, 3, 87, 12, 1), 2), # Degree 3 + # SUPERSEDED BY C4, not a disagreement with the thesis. The published + # cell is (65, 3, 87, 12, 1); the formula changed under it, trading one + # multiplication for twelve additions by carrying the full adjugate and + # applying it as a matrix-vector product. Hand-counted and measured + # independently, both giving +1M -12A. `Thesis/ERRATA.md` records the + # divergence; nothing here says the thesis was wrong. + "33ADD n=0,0": ((66, 3, 75, 12, 1), 2), # Degree 3, post-C4 "13ADD n=1,0": ((22, 2, 43, 7, 1), 2), # Degree 1 and 3, Up Adjust - "3DBL n=0": ((73, 3, 101, 19, 1), 2), # Degree 3 + # SUPERSEDED BY C4, same trade as 33ADD above and for the same + # reason. Published cell (73, 3, 101, 19, 1). Hand-counted and + # measured independently, both giving +1M -12A: the generic path's + # s computation goes 11M 20A -> 12M 8A while the T-block holds at + # 15M 9A. `Thesis/ERRATA.md` records the divergence. + "3DBL n=0": ((74, 3, 89, 19, 1), 2), # Degree 3, post-C4 "1DBL n=1": ((7, 1, 19, 6, 1), 2), # Degree 1 "1DBL n=2": ((14, 3, 25, 8, 1), 2), # Degree 1 with Down Adjust }),