Math Mapgenerated 2026-10-25 · a354234
PUBLIC BUILD

run 037Settled

Trained to grokking on multiplication mod p, which characters does a small network learn — the additive characters of Z/p, or the multiplicative (Dirichlet) characters, i.e. Fourier modes in discrete-log coordinates on (Z/p)^x?

slug
grokking-multiplicative-characters
state
run
created
2026-09-27
sealed
2026-09-27
measured
2026-09-27
statistic
f_k(order) = the fraction of non-DC Fourier power carried by the top k frequencies of the token-embedding matrix W_E (element rows only, the '=' row dropped), after reindexing its rows by order, for k = 5, 10, 20 and order ∈ {identity, discrete-log base g = 3}; power per frequency is |DFT(W_E, axis=rows)|^2 summed over the 128 embedding dimensions with conjugate pairs (k, N-k) added, DFT length 113 on the additive task and 112 on the multiplicative one. Reported for both tasks, all three seeds, against a Gaussian-init baseline of the same shape and order.
runtime_hours
0.64
script
R26_training_dynamics/run037.py (driver), R26_training_dynamics/grok.py (module)
artifacts
[R26_training_dynamics/metrics/add-transformer-s{0,1,2}.json, R26_training_dynamics/metrics/mul-transformer-s{0,1,2}.json, R26_training_dynamics/figs/, R26_training_dynamics/metrics/add-transformer-s0-size100-mps.json, R26_training_dynamics/metrics/add-transformer-s0-size100-cpu.json]
concepts
[fourier, representation-theory, group-theory]
outcome
confirmed
next
Does the same network learn the character table of a NON-cyclic unit group — (Z/561)^x = Z/2 × Z/10 × Z/16, where no global discrete log exists, so the sealed 1D instrument is not weaker but wrong and the measurement must become a 3D DFT over per-prime discrete logs (P-031) | builds_on: 037
source
../lab/runs/037-grokking-multiplicative-characters.md

PredictionMultiplicative-task embeddings are sparse in discrete-log-order Fourier modes and roughly flat in ordinary order — the network learns Dirichlet characters of (Z/p)^x, not additive characters of Z/p. The control's embeddings are the mirror image: sparse in ordinary order. "Roughly flat" means at the Gaussian floor, ≈ 0.20 for the top ten of 56 frequencies, not near zero.

FalsifierEither of — in discrete-log order the top ten frequencies carry under 50% of the non-DC power; OR ordinary order is as concentrated as discrete-log order, the two top-ten fractions falling within the seed spread of each other.

VerdictCONFIRMED 2026-09-28 by the repo owner, scored on the prediction's intent. Neither falsifier clause fired. Clause 1: discrete-log-order top-10 measured 0.9686, 0.9695, 0.9494 against the threshold of 0.50 — the weakest seed is 1.9× the threshold. Clause 2: ordinary order measured 0.2467, 0.2700, 0.2311 against discrete-log order's 0.9494–0.9695; the two ranges are disjoint and the gap between their means is 0.7132, 18.3× the larger of the two seed spreads, so ordinary order is nowhere near as concentrated as discrete-log order. All three seeds grokked on both tasks (test accuracy 0.99 at steps 10,900 / 12,300 / 23,200 on the multiplicative task, against train at 200), the pre-registered extension never fired, and no seed was excluded — a 3-of-3 quorum against the 2-of-3 required. The control reproduced the published ordinary-order sparsity it existed to establish: top-10 of 0.9656, 0.9558, 0.9636 against a 0.2022 Gaussian floor, so the main run was interpretable. One scoring note is recorded in the body, on the prediction's gloss of "roughly flat" and on the status of the post-hoc reference. The unsealed observations are not part of this verdict.

Sealed 2026-09-27, before any training step of the launch was taken. The prediction and the falsifier were drafted here as PROPOSED, NOT ADOPTED and were adopted verbatim by the repo owner on 2026-09-27, with no change to either threshold; they now sit in the prediction: and falsifier: frontmatter fields, which is what lab/make_site.py renders as the sealed prior. The draft wording is kept below as the record of what was adopted, struck of its "proposed" framing but otherwise untouched. state: advances draft → open: specified, sealed, sized, not yet run.

confidence: is deliberately absent. OPS.md asks a prediction to carry one and the adoption named no number; inventing one here would be the run's author supplying the operator's own degree of belief, which is the line MANUAL.md, Decisions only you make, draws. One line to add if wanted.

The two tasks, and why the control comes first

CONTROL — (a + b) mod 113 on 0..112. The published result (Nanda et al. 2023, Progress measures for grokking via mechanistic interpretability) is that the embedding of a one-layer transformer grokked on modular addition is sparse in ordinary Fourier modes: a handful of frequencies carry almost all the non-DC power. If the control does not reproduce that, the main run is uninterpretable and the control is what gets reported — a negative control failure is a fault in this apparatus, not a fact about characters.

MAIN — (a · b) mod 113 on 1..112, i.e. the group (Z/p)^x. Same hyperparameters as the control, no exceptions: weight decay 1.0, lr 1e-3, AdamW (betas 0.9, 0.98), full batch, train fraction 0.3, three seeds each, one-layer transformer (4 heads, d_model 128, d_mlp 512, ReLU, no LayerNorm). 12,769 ordered pairs on the additive task and 12,544 on the multiplicative one; at 0.3 that is 3,831 and 3,763 training examples.

Grokking is confirmed before any embedding is analysed, from the per-step log: the step at which train accuracy first crosses 0.99 and the step at which test accuracy does, and their ratio. An embedding from a network that memorised is not evidence about characters, and grok.grokking() returns None rather than a number when the crossing never happened.

The prediction, as adopted

Drafted by Claude Code 2026-09-27; adopted as written by the repo owner the same day. This is the sealed text; prediction: in frontmatter is the same sentence.

> Multiplicative-task embeddings are sparse in discrete-log-order Fourier modes and roughly flat > in ordinary order — the network learns Dirichlet characters of (Z/p)^x, not additive characters > of Z/p. The control's embeddings are the mirror image: sparse in ordinary order.

The falsifier, as adopted

Adopted as written, same day, thresholds unchanged.

> Either of: in discrete-log order the top ten frequencies carry under 50% of the non-DC power; > or ordinary order is as concentrated as discrete-log order (top-ten fractions within the seed > spread of each other).

What the thresholds are worth, measured. The top-k fraction of a random matrix is not zero — there are only 56 frequencies at either DFT length, so ten of them get a fifth of the power by accident. Measured on 64 Gaussian matrices of the run's exact shape:

top 5top 10top 20
113 × 128 (additive task)0.104 ± 0.0020.202 ± 0.0030.390 ± 0.003
112 × 128 (multiplicative task)0.105 ± 0.0020.203 ± 0.0030.393 ± 0.003

So the falsifier's 50% sits at 2.5× noise, and "roughly flat" in the prediction means at the baseline, ≈ 0.20, not near 0. A prediction stated against 0 would be unfalsifiable in the easy direction. These numbers are the argument for the pair; the pair is the two sentences above.

Pre-registered before launch — the non-grokking rule, and what the verdict is allowed to use

Written 2026-09-27 under the seal, before the launch, because a rule for discarding a seed that is written after seeing which seeds failed is not a rule.

The rule. If a seed's test accuracy has not crossed 0.9 by step 25,000, that seed is extended once to 50,000 steps. If it still has not crossed 0.9, the seed is reported as did not grok and is excluded from the verdict — it is evidence for neither branch of the prediction. An embedding from a network that never generalised says nothing about which characters generalisation uses, and counting it either way would be counting the apparatus as data.

The quorum. The verdict needs at least 2 of 3 grokked seeds per task. Fewer than two on either task and the run does not answer its question at this budget; it is then reported as under-powered, with the grokked seeds' numbers given and no verdict, rather than settled on one seed.

It is mechanical, not a promise. run037.py --extend-if-below 0.9 (the default) implements exactly the above: it checks the per-step log, retrains the seed once at twice the steps under the tag -ext, and writes did_not_grok and excluded_from_verdict into that seed's metrics JSON. The 0.9 here is the extension and exclusion threshold and is deliberately looser than the 0.99 used for the two grokking step numbers — a seed at 0.95 has generalised and its embedding is worth reading, but it has not "grokked" in the sharp sense the step numbers report.

The extension is a fresh run from the same seed, not a resume, and supersedes rather than continues the 25,000-step attempt. Full-batch training from a fixed seed is deterministic in principle, but bitwise reproducibility on MPS is not promised, so the honest description is the weaker one: the extended run is the record for that seed, and the short attempt is kept beside it.

Unsealed observations — recorded, NOT part of the verdict

No prediction is attached to anything in this section, by instruction. It is recorded because it is free once the dlog spectrum exists, and because a number nobody wrote down is a number nobody can use later.

For the multiplicative task in discrete-log order, the frequency index is the Dirichlet character index: χ_j(g^t) = exp(2πi·j·t/(p−1)). Three things get written into metrics/<tag>.json under secondary_dirichlet and into the results below:

  1. Which dlog frequencies carry the power — the top twenty, ranked, with their share.
  2. Whether k = 56 = (p−1)/2 appears. That is the unique real non-principal character, χ₅₆(gᵗ) = (−1)ᵗ — the Legendre symbol. Its share and its rank among the 56 frequencies are reported. Its noise floor is half every other frequency's: 56 is self-conjugate, so no partner folds into it, and its expected share under Gaussian noise is 1/111 = 0.0090 against 2/111 = 0.0180 for the others. Verified on noise — measured 0.0090 at the predicted 0.0090 — and recorded in the JSON beside the measurement, because reading it against the wrong floor would make the Legendre symbol look suppressed by exactly a factor of two.
  3. Even against odd characters. Since −1 = g^((p−1)/2), χ_j(−1) = (−1)^j, so j even ⇔ χ_j even. The even and odd shares of the non-DC power are reported; on noise they split 0.495 / 0.505, so a departure has a floor to be read against. Conjugate folding preserves parity here because p − 1 = 112 is even.
  1. Character order, and whether low orders are preferred. For each top frequency k, gcd(k, 112) and the order of χ_k in the character group, 112/gcd(k, 112), plus the share of non-DC power aggregated by order. This is the asymmetry between the two tasks that is not about coordinates: 112 = 2⁴·7 is composite while 113 is prime, so a multiplicative character can have any order dividing 112 — order 2 is the Legendre symbol, order 4, 7, 8, … up to the full 112 — whereas every nontrivial additive character mod 113 has full order 113 and there is nothing to prefer. The order column is generator-independent: k ↦ uk for a unit u leaves gcd(k, 112) unchanged, so it compares across seeds and across generators in a way the raw frequency label does not.

One primitive root, fixed by declaration: g = 3. run037.py carries it as the module constant G = 3 and asserts it is still the smallest primitive root mod 113, rather than taking whatever a helper returns; every seed and both analyses use it. This matters because frequency labels other than 56 move with the generator (k ↦ uk), so a generator that varied between seeds would make the per-seed frequency lists incomparable while each remained internally correct. k = 56 is the exception and is invariant, because every unit mod 112 is odd.

How the sealed metric counts, stated so the reader knows — and not changed. The statistic folds conjugate pairs, so its 56 frequencies are bins: 55 bins holding a conjugate pair {k, 112−k} each, plus one self-conjugate bin, k = 56. The observations above count the same way and say so in the JSON (counting.unit = "bins"). The consequence is the one already recorded for the Legendre symbol — a singleton bin has half a pair-bin's noise share — and the point of stating it here is that the bin/pair asymmetry must not be read as the network preferring or avoiding k = 56. The sealed statistic: is untouched by this paragraph; it is described, not amended.

The Gaussian floor is computed per task, at the task's own token count. random_baseline is called with the actual embedding block's shape, so the multiplicative floor is measured at 112 rows and the additive one at 113 — not carried across. The difference is negligible and both are reported side by side anyway, because "negligible" is a measurement and not an assumption:

top 5top 10top 20
additive task, 113 × 1280.104 ± 0.0020.202 ± 0.0030.390 ± 0.003
multiplicative task, 112 × 1280.105 ± 0.0020.203 ± 0.0030.393 ± 0.003
Validated before it is pointed at a network

The statistic has to separate the two hypotheses on objects whose answer is known, or a measurement on a trained network means nothing. run037.py --selftest builds two matrices by hand — one additive character of Z/113 evaluated at a, one Dirichlet character evaluated at a, both at frequency 7, both 112 × 2 — and reads each in both orders. Run 2026-09-27, on numpy alone:

hand-built matrixtop-5, identity ordertop-5, dlog orderargmax frequency (identity / dlog)
additive character, freq 7 in a0.9970.0907 / 32
Dirichlet character, freq 7 in log a0.1771.00046 / 7

Clean in both directions, each recovering its own frequency, and the off-diagonal entries at or below the 0.105 noise floor. The primitive root is g = 3 (smallest mod 113), and the discrete-log reindexing is checked to be a permutation of all 112 rows before use.

Where the additive task's DFT length comes from, and the one asymmetry. The control's elements are 0..112 and its DFT length is 113; the main task's are 1..112 and its length is 112. The discrete-log order is therefore not applied to the control — 0 has no discrete log, and dropping it to force a 112-row reading would compare a matrix with a hole in it against one without. The control's job is the ordinary-order reproduction; the dlog column on that task is null in the metrics, by construction and not by omission.

Sizing — measured 2026-09-27, the control only

Environment. A venv of the track's own, ~/mapofmath/.venv-ml (Python 3.14.7, torch 2.14.0, numpy 2.5.3), kept separate from ~/mapofmath/.venv so that docs/compute-environment.md's What not to install stands unamended for the lab venv. MPS is available and built; an 8×8 matmul on it returns finite.

The timing run, as named in the recipe — control only, 300 full-batch steps, no embedding and no figure written, at the launch's own logging interval:

python run037.py --task add --steps 300 --time-only --log-every 100 --device {mps,cpu}

device300 stepsper step25,000 stepssix launches (2 tasks × 3 seeds)
mps4.55 s15.16 ms6.3 min≈ 38 min
cpu7.69 s25.65 ms10.7 min≈ 64 min

runtime_hours: 0.64 in frontmatter is the MPS figure for the whole launch. Metrics: R26_training_dynamics/metrics/add-transformer-s0-size100-{mps,cpu}.json.

Two things the sizing run measured that are not the clock. First, MPS wins by only 1.7×, and evaluation is most of what it does: the same 300 steps at --log-every 50 cost 23.03 ms/step on MPS against 25.65 unchanged on CPU, so doubling the number of full train+test evaluations nearly doubled MPS's time and left the CPU's alone. The step itself is small enough to be launch-overhead bound — the eval sweep over 3,831 + 8,938 sequences is the real load — so the logging interval is a cost knob, and 100 is the one the table above is priced at. Second, the pre-grokking state is already visible at 300 steps: train accuracy crosses 0.99 at step 200 while test accuracy sits at 0.005–0.006, i.e. memorisation complete and generalisation nowhere. That is the left-hand end of the curve the run exists to walk, and it says the 25,000-step budget is spent on the part that matters rather than on fitting the training set.

Where it runs. 38 minutes puts the whole launch past docs/compute-environment.md's ten-minute M5 threshold as a single job but well inside it per launch, so the honest reading is six ~6-minute jobs on the M5 rather than an overnight on BernoullisBook — and per MANUAL.md, Two runs with the same --save target overwrite each other silently, each gets its own tag, which run037.py derives from task, arch and seed rather than leaving to the launcher.

What it settles

No case, and that is not a defect — OPS.md: "a run need not settle a case." It is the first run of a training-dynamics track, and it validates an instrument (the reindexed-DFT reading of an embedding) that the three probes filed the same day would reuse. On docs/compute-and-electronics-roadmap.md it is §8's build-order item 7, Grokking on modular arithmetic, extended: item 7 as written watches the snap and inspects the Fourier basis; this run asks which group's characters appear when the operation is multiplicative, which is the question item 7 does not pose.

Outputs, and what is committed

R26_training_dynamics/metrics/<tag>.json — the per-step log, the grokking steps, and the Fourier analysis in both orders with its baseline; committed, the way R23_ising_barrier/.json is. R26_training_dynamics/figs/<tag>-{loss,spectrum-identity,spectrum-dlog}.svg — raw SVG in docs/DESIGN-SYSTEM.md §1 tokens, achromatic; committed. R26_training_dynamics/emb/<tag>.npy — the final embedding matrices; not committed, since the committed .gitignore excludes .npy and the JSON analysis is the record. These are run outputs, not the run figure: lab/runs/figs/037-main.svg is the sealed-band slot of docs/DESIGN-SYSTEM.md §10, and there is no band to draw until the prediction above is adopted.

---

Measured 2026-09-27 · scored 2026-09-28

Scored confirmed by the repo owner on 2026-09-28 (MANUAL.md, Decisions only you make; docs/CODIFIER.md §5 — the codifier does not score its own run). What follows is the measurement as it was laid against the sealed thresholds on the day, in the same units and with nothing concluded; the verdict is in frontmatter and the scoring note is at the foot of this file. Nothing in this section was rewritten after the scoring.

Every seed grokked; the pre-registered extension never fired. All six crossed test accuracy 0.9 well inside 25,000 steps, so no seed was retrained at 50,000, none is marked did_not_grok, and none is excluded. The quorum is 3 of 3 on both tasks, one better than the 2-of-3 the pre-registration requires. Six launches, 35.3 min total on MPS at 14.0–14.4 ms/step, against the 38 min sized.

taskseedtrain 0.99test 0.99gapfinal test acc
add020021,900109.5×1.0000
add130021,60072.0×1.0000
add22008,10040.5×1.0000
mul020010,90054.5×1.0000
mul120012,30061.5×1.0000
mul220023,200116.0×0.9984
The statistic, both orders, both tasks, against the floor

Fraction of non-DC power in the top k frequencies (bins, per the accounting above). The floor is this task's own Gaussian baseline at its own token count, 32 trials:

taskseedident. top-5ident. top-10ident. top-20dlog top-5dlog top-10dlog top-20
add00.93440.96560.9766———
add10.93040.95580.9694———
add20.95160.96360.9758———
add floor0.10390.20220.3904———
mul00.13490.24670.44450.94370.96860.9805
mul10.15130.27000.47480.96010.96950.9791
mul20.12130.23110.43060.88230.94940.9678
mul floor0.10340.20190.39160.10340.20190.3916

The dlog column is — on the additive task by construction, as sealed: 0 has no discrete log. DC share is 0.0017–0.0040 across all six. Top-5 frequencies, for the record — additive, identity order: {44, 21, 34, 26, 52}, {26, 16, 47, 17, 34}, {10, 3, 25, 49, 50}. Multiplicative, dlog order: {3, 14, 55, 56, 28}, {53, 20, 9, 6, 18}, {21, 15, 25, 33, 26}. No frequency set is shared between seeds, on either task.

The two falsifier clauses, in their own units

Stated, not evaluated. The sealed text is in falsifier:.

Clause 1 — "in discrete-log order the top ten frequencies carry under 50% of the non-DC power." Threshold 0.500. Measured: 0.9686, 0.9695, 0.9494 (min 0.9494, mean 0.9625). Distance from the threshold, in units of the 0.003 floor sd: the smallest seed is 0.449 above 0.500.

Clause 2 — "ordinary order is as concentrated as discrete-log order, the two top-ten fractions falling within the seed spread of each other." The two quantities and their spreads:

minmaxspread
mul, dlog top-100.94940.96950.0201
mul, identity top-100.23110.27000.0389

Gap between the two means: 0.9625 − 0.2493 = 0.7132, against the larger of the two spreads (0.0389) — a ratio of 18.3. The two ranges do not overlap.

One number that is not a clean fit to either clause, surfaced rather than smoothed. The prediction's "roughly flat" was defined as at the Gaussian floor, ≈ 0.20 for top-10. Ordinary order on the multiplicative task measures 0.231–0.270 against a 0.2019 floor — above it by 0.029 to 0.068, i.e. 1.14× to 1.34× the floor, and by 10–23 floor sds. So it is far nearer flat than concentrated, and it is not at the floor. Whether "roughly flat" covers 1.2× the floor is a scoring judgement and is left open here. The same quantity on the additive task is 4.7× its floor, which is the contrast the comparison rests on.

Unsealed observations, as pre-registered — recorded, NOT part of the verdict

The Legendre symbol, k = 56, is wildly seed-dependent, read against its own 0.00901 floor (half a pair-bin's, as recorded before launch):

seedsharerank of 56× its own floor
mul 00.108544 of 5612.0×
mul 10.0004056 of 560.04×
mul 20.0003156 of 560.03×

One seed puts the quadratic character fourth and the other two put it dead last, below a thirtieth of its noise share. Recorded with no claim attached.

Even against odd characters (noise splits 0.495 / 0.505):

seedevenodd
mul 00.4640.536
mul 10.3390.661
mul 20.2370.763

All three lean odd, by increasing margins. No prediction was attached and none is made.

Character order — gcd(k, 112) and 112/gcd(k, 112). Top frequencies with their orders:

seedtop 5, as (k, share, order of χ_k)
mul 0(3, 0.304, 112) · (14, 0.268, 8) · (55, 0.210, 112) · (56, 0.109, 2) · (28, 0.054, 4)
mul 1(53, 0.359, 112) · (20, 0.307, 28) · (9, 0.280, 112) · (6, 0.010, 56) · (18, 0.004, 56)
mul 2(21, 0.223, 16) · (15, 0.184, 112) · (25, 0.168, 112) · (33, 0.154, 112) · (26, 0.153, 56)

Share of non-DC power by order of the character:

seedfull order 112orders dividing 16order 2 (Legendre)order 28order 56
mul 00.5340.4360.1090.0040.023
mul 10.6580.0050.0000.3110.020
mul 20.5360.2330.0000.0130.212

Full-order characters take 53–66% in every seed, and the remainder lands in a different place each time — order 8 and 2 in seed 0, order 28 in seed 1, orders 16 and 56 in seed 2. On these three seeds there is no visible preference for low-order characters, which is the tabulation the addendum asked for and is the whole of what it supports. The order column is generator-independent, so this comparison across seeds is valid whatever the labels do; g = 3 was used for every seed regardless.

POST-HOC reference — NOT PART OF THE SEALED FALSIFIER

Computed 2026-09-27 after the run was measured, at the repo owner's request, by R26_training_dynamics/reference037.py — a separate file from the driver so that nothing in it can be mistaken for the run's sealed procedure. It is a reference reading, not a result, and it scores nothing.

The question it answers. Ordinary-order concentration on the multiplicative task came out at 0.231–0.270 against a 0.2019 Gaussian floor. Is that the metric's own floor for a purely multiplicative object, or residual additive structure? A Gaussian matrix is the wrong comparison for that question: it has no structure at all, whereas a perfect multiplicative embedding has structure the identity-order DFT cannot see. So read one.

Both synthetics are built from the same hand-made characters the selftest uses, and both are read in identity order under the same length-112 DFT over the value index the sealed metric uses.

identity order, length-112 DFTtop 5top 10top 20
A. one exact Dirichlet character, mean ± sd over all 56 frequencies0.2002 ± 0.0190.3443 ± 0.0220.5670 ± 0.022
&nbsp;&nbsp;&nbsp;&nbsp;its range over those 56[0.170, 0.268][0.302, 0.429][0.524, 0.650]
&nbsp;&nbsp;&nbsp;&nbsp;at k = 7, the selftest's0.17690.32390.5475
B. a span of three Dirichlet characters at random dlog frequencies, 32 draws0.1534 ± 0.0120.2749 ± 0.0150.4816 ± 0.016
&nbsp;&nbsp;&nbsp;&nbsp;its range over those draws (top-10)[0.239, 0.301]
Gaussian floor, for contrast0.10340.20190.3916
measured, run 037 mul seeds 0/1/20.135 / 0.151 / 0.1210.247 / 0.270 / 0.2310.445 / 0.475 / 0.431

Sanity check on the synthetics: B read in dlog order gives top-5 = 1.0000 exactly — three characters, three bins, all the power. The construction is what it claims to be.

What the reference shows, stated as a reading and not as a score. A perfect multiplicative embedding does not read at the Gaussian floor in identity order. One character reads 0.344 at top-10 and a three-character span reads 0.275, both above the 0.202 floor, because a multiplicative character indexed by value and transformed additively is a mismatched-coordinate object, not a structureless one. The three measured seeds, 0.231–0.270, sit inside or just below the three-character span's own range of 0.239–0.301. So the sealed prediction's definition of "roughly flat" — at the Gaussian floor, ≈ 0.20 — is a stricter standard than a perfect multiplicative embedding meets. Whether that changes the reading of clause 2 is the repo owner's call; the number is recorded here so the call can be made against it rather than against the noise floor alone.

The Z/113 reading, where a theorem replaces a measurement. Place the 112 element rows at their value index in a 113-row matrix with row 0 set to zero — the character extended by χ(0) = 0 — and DFT at length 113. The DFT of a Dirichlet character over Z/p is a Gauss sum, and |τ(χ)| = √p at every nonzero frequency, so the magnitude is exactly flat and the top-k share must be exactly k/56 with DC exactly 0.

Does the synthetic come out flat? Exactly, to machine precision. A single character at k = 7 returns top-5 = 0.089286, top-10 = 0.178571, top-20 = 0.357143 against the predicted 5/56, 10/56, 20/56 = 0.0893, 0.1786, 0.3571; the ratio of the largest bin to the smallest is 1.0, the relative spread across the 56 bins is 0.0, and DC is 0.0. The three-character span is flat to within a max/min bin ratio of 1.364 and DC 0 — each character's Gauss sum is individually flat, but distinct characters' sums carry different phases per frequency, so their cross terms are frequency-dependent and the span is flat only in the mean. Note that exact flatness gives a lower top-k than Gaussian noise does (0.179 against 0.202 at top-10), because noise has an order-statistic surplus and a flat spectrum has none.

Numbers in R26_training_dynamics/metrics/reference-posthoc.json.

Reported, not scored

The figures are R26_training_dynamics/figs/<task>-transformer-s<seed>-{loss,spectrum-identity,spectrum-dlog}.svg — loss curves on log–log axes, and the spectrum in each order with the measured top-10 and its floor in the caption. Per-step logs, both grokking step numbers, both orders' analyses with baselines, and the unsealed observations are in each metrics/<tag>.json. The launch logs are R26_training_dynamics/{add,mul}.log.

---

Scored 2026-09-28 — confirmed

The owner's decision, recorded with its reasoning and not recomputed here. The prediction held and neither falsifier clause fired; scored on the prediction's intent. The numbers are in verdict: above and in The two falsifier clauses, in their own units.

Scoring note

> The prediction glossed "roughly flat" as at the Gaussian floor (~0.20); ordinary order measured > 1.14–1.34× that floor. The post-hoc reference (reference037.py) shows a perfect multiplicative > embedding reads 0.275–0.344 under the same length-112 transform, so the residue is a property of > the metric, not additive structure. That reference was computed after the results and informed > the scoring; it did not amend the sealed prediction or falsifier.

Why that last sentence is part of the note rather than an aside. A reference computed after the results and used in scoring is the exact shape of the thing OPS.md's one rule for runs exists to prevent — a standard that moves to fit the data. It is admissible here because of what it did and did not touch: it measured the metric's behaviour on a known object, not the run's object, and the sealed prediction: and falsifier: are byte-identical to the seal commit c9e4b07. The note records the sequence so that a later reader can see the order events happened in without reconstructing it from commit dates.

What is not in this verdict. The unsealed observations — the Legendre symbol's seed-dependent share, the even/odd split, the character-order tabulation — are recorded above and are part of no verdict, as pre-registered before the launch. They were not consulted in the scoring and are not evidence for or against the prediction.

generated 2026-10-25the lab index