Module 12 — The Spectral Test
Source: The Art of Computer Programming, Vol. 2, 3rd ed., §3.3.4 (with the lattice preliminaries of §3.3.4A–B and Table 1). Lab:
labs/module-12-spectral· Grade it:./grade 12Self-contained: you can complete the module without the book. If you own Vol. 2, read §3.3.4 up to Algorithm S first; we build the same theory but stop our exact computations at (Knuth's algorithm reaches ).
In 1968 George Marsaglia published a paper with the unimprovable title "Random numbers fall mainly in the planes." Every linear congruential generator — no matter how carefully tuned — produces points in dimensions that lie on a family of parallel hyperplanes. The only question is how many planes, and the spectral test answers it exactly. It is, in Knuth's words, by far the most powerful test known for linear congruential generators: the bad ones it condemns include IBM's infamous RANDU, which silently corrupted a decade of published simulations. By the end of this module you will compute the test exactly — integer arithmetic, no approximations — and issue the same verdicts Knuth's Table 1 does.
Companion exhibit — The Illusion of Chaos. Before you compute the test, see it: the Museum's Loom of Chaos plots an LCG's pairs so the lattice of parallel lines is visible to the eye — few coarse stripes for a bad multiplier, dense white noise for a good one. Load its RANDU preset to meet the generator this module condemns.
1. Random numbers fall mainly in the planes
A linear congruential generator (LCG) produces
To study how random the sequence looks in dimensions, form the overlapping -tuples
Overlapping, because that is what consumers experience: a simulation that draws three coordinates in a row gets exactly these triples, and the serial tests of §3.3.2 measure exactly this point set. A truly random source would scatter the uniformly through the cube. An LCG cannot: the whole point set has a rigid crystalline structure, and we are about to compute its worst defect.
The increment is irrelevant
We will work with throughout, and here is why nothing is lost. Run the same multiplier with increment from the same and compare with the sequence: by induction, the term with increment is
so the tuple starting at position is the tuple plus the fixed vector — the same shift for every . The entire point set is translated; translations change neither spacings nor plane counts, so every quantity in this module is independent of . (This is Knuth's observation at the start of §3.3.4: the spectral test depends only on and .)
2. The lattice theorem
A lattice in is the set of all integer combinations of some basis vectors. The structure theorem for LCG tuples is almost embarrassingly short:
Theorem (lattice structure). Every overlapping -tuple of the sequence lies in the lattice generated by
Proof (two lines). Coordinate of the tuple starting at is . So the tuple equals minus a suitable multiple of in each coordinate after the first (the first needs none: is already in ) — and those corrections are exactly what supply. ∎
Note that is also in : . So contains all of plus the distinct multiples of ; its determinant (volume of a fundamental cell) is . The cube has volume and the sequence visits at most points of it — one point per cell, a perfect crystal.
Honest caveat: the theorem says the tuples lie in ; over a full period they typically fill it (that is Knuth's setting), while a short run or a restricted cycle (e.g. odd seeds for ) fills a subset. The defects we compute are properties of , hence upper bounds on the quality of any run.
3. Dual vectors, families of planes, and
Call a dual vector () if
Why "dual": by definition, and trivially, so for every . In particular, again in two lines: every tuple satisfies
because coordinate of is congruent to . So the entire point set lies on the family of parallel hyperplanes
How many planes meet the cube? For , split the dual vector into its positive and negative parts: and . Then
so ranges over the integers of ; counting them gives exactly planes when has entries of both signs (and when it has one sign, where the plane touches only the origin). Either way, the family has at most
Small dual vector few planes bad generator.
How far apart are the planes? Adjacent hyperplanes and are at distance (the standard point–plane distance). Short dual vector wide empty slabs.
Both defects are governed by the same quantity, and that is the test:
Definition (spectral test). For ,
is the length of the shortest nonzero dual vector. The maximum gap between adjacent covering planes, over all families, is ; and the family cut out by that shortest has at most planes (Cauchy–Schwarz). Large is good: every direction's dual vectors are then long, so no direction sees wide empty slabs or few planes.
The RANDU catastrophe, exactly
RANDU is , , (odd seeds). The multiplier was chosen so that could be computed with two shifts and two adds. But
so is a dual vector: . Every triple the generator will ever produce satisfies
Plane count: , , so 15 planes (at most ) carry all triples in a cube with cells per side. In stage 3 you will prove nothing shorter exists: . A good multiplier for could have achieved (the Hermite bound of §6), i.e. thousands of tightly packed plane families instead of 15 gaping slabs. Marsaglia's title was not hyperbole.
4. Two dimensions, exactly: Gauss–Lagrange reduction
For the dual lattice is . It has an obvious basis:
Lemma. is generated by and .
Proof. Both satisfy the congruence. Conversely if , then for some integer , so . ∎
is the squared length of the shortest nonzero vector of , and in rank 2 there is an exact, fast, ancient algorithm for that — Lagrange's reduction, usually credited to Gauss. It is Euclid's algorithm acting on vectors: repeatedly subtract the best integer multiple of the shorter vector from the longer one.
Algorithm G (Gauss–Lagrange reduction)
Given a basis v_1, v_2 of a rank-2 lattice, find a shortest nonzero vector.
G1. [Initialize.] v1 <- (m, 0), v2 <- (-a, 1).
G2. [Order.] If |v1| < |v2|, interchange v1 and v2.
G3. [Reduce.] Let q be the nearest integer to (v1·v2)/(v2·v2);
set v1 <- v1 - q·v2.
G4. [Done?] If |v1| >= |v2|, terminate; v2 is a shortest nonzero
vector and ν_2² = v2·v2. Otherwise return to G2.
Hand-trace it on the module's toy generator , (do this on paper — it is the module's Fibonacci-trace moment):
| pass | new | new | ||||||
|---|---|---|---|---|---|---|---|---|
| 1 | 65536 | 18770 | 328 | |||||
| 2 | 18770 | 328 | 2468 | 8 | 274 | |||
| 3 | 328 | 274 | : stop |
Answer: shortest vector , so . Sanity: is dual? The congruence for : ✓ (a lattice contains iff it contains ). The pair point set of this generator lies on lines, spaced apart.
Termination. Steps G2–G3 leave the lattice unchanged (swaps and shears are unimodular). If G4 does not stop, the new is strictly shorter than , which was the shorter of the previous pair — so , a positive integer, strictly decreases on every continuing pass. A decreasing sequence of positive integers is finite. (And if the vector is unchanged, but then from G2, so G4 stops: no infinite idling.) This is module 01's proof pattern — invariant plus a decreasing quantity — operating on norms instead of remainders.
Optimality. At termination, two facts hold: , and — the property of rounding to the nearest integer — . Now take any nonzero lattice vector ().
-
If : .
-
If : .
-
Otherwise,
because for nonzero integers. So is genuinely shortest. ∎ Note where integrality did the work: for real the quadratic form dips to . The final basis is called reduced; this is the 2-dimensional case of what LLL approximates in higher rank.
5. Three dimensions: a certified search
For the dual condition is . Choose freely; then is pinned down modulo , and the best representative is obvious:
Lemma (centered residue). Fix and let be the residue of . Among all valid , the one of smallest absolute value is the centered residue — if , else — and it satisfies . Any other member of the class differs by a nonzero multiple of and is at least as far from 0. ∎
So the 3-D minimum is a search over the plane. The trouble: the plane is infinite. The fix: a bound that certifies itself.
Algorithm C (certified bounded search)
C1. [Initialize.] best <- m² (the class (u2,u3) = (0,0) contributes
(m, 0, 0); u = 0 itself is excluded). B <- 1.
C2. [Scan.] For all (u2, u3) in [-B, B]² except (0,0): set u1 to the
centered residue of -(a·u2 + a²·u3) mod m, and
best <- min(best, u1² + u2² + u3²).
C3. [Certified?] If B² >= best, terminate: ν_3² = best.
C4. [Grow.] B <- 2B; return to C2.
Why termination at C3 is a proof, not a hope. Suppose best and some dual vector is shorter than best. Where is it? If or , then best — contradiction. So lies in the scanned square; but there we already took the best possible for its class (the centered-residue lemma), so cannot beat best either. Hence best exactly. The search bound is justified by the answer it found — the same trick as branch-and-bound, in its simplest possible clothing.
Why C4 terminates. Hermite's theorem (next section) gives for , so once we have best-after-scan and C3 fires. Total work: the doubling scans sum to at most cheap iterations — the geometric series is dominated by its last term, a bound you proved for table doubling in earlier modules. (In practice RANDU certifies at and the minimal standard at .)
Why not extend Gauss reduction instead? Rank-3 reduction (or LLL) gives a nearly shortest vector with more delicate proofs; Knuth's Algorithm S does exact enumeration with Cholesky-style pruning. Algorithm C is the honest kernel of the latter: exact, certified, and slow only in ways we can afford at .
6. Figures of merit: how good could possibly be?
Raw values are incomparable across moduli — is meaningless until you know how large could be for that . Two normalizations:
Hermite's constants. For any rank- lattice of determinant , the shortest nonzero vector satisfies , where
Our dual lattice has index in (one congruence mod ), hence determinant . Therefore
These bounds are load-bearing twice in the lab: they justify the brute-force cutoff in stage 2 ( suffices) and the termination of Algorithm C in stage 3.
Knuth's (§3.3.4, Eq. (37)) compares to the ideal by volume:
so
(the half-integer factorial: ). Interpretation: the dual lattice has one point per volume , so is the expected number of lattice points in a ball whose radius is the actual shortest vector — if is tiny, that vector is anomalously short and the generator is bad in dimensions. Hermite caps the scale: and . Knuth's rule of thumb: passes, is excellent.
The verdicts you will compute in the lab (compare Knuth's Table 1):
| generator | verdict | |||||
|---|---|---|---|---|---|---|
| toy, | 256 | 274 | 30 | 3.36 | 2.69 | great for its size |
| RANDU, | 2 147 221 514 | 118 | 3.14 | catastrophic in 3-D | ||
| minimal standard, | 408 197 | 0.41 | 0.51 | passes; unspectacular | ||
(std::minstd_rand) | 1 990 735 345 | 1 433 881 | 2.91 | 3.35 | excellent for this |
Two morals. First, the test is per dimension: RANDU's pairs are nearly perfect (, close to the Hermite ceiling!) while its triples are ruinous — you must test every your application consumes. Second, multipliers are not commodities: 16807 and 48271 cost the same to evaluate, and one is an order of magnitude better. That is why the revision happened.
Scope note (honesty about )
Knuth's Algorithm S carries this program to : it maintains dual and primal bases simultaneously, transforms them as t grows, and enumerates candidates inside an ellipsoid with Cholesky-factorization bounds instead of our square. Every idea you implement here — dual lattice, exact integer arithmetic, a search bound certified by the best value found — reappears there, industrial-strength. We stop at because rank-2 reduction and one certified 2-parameter search teach the entire method without the bookkeeping; if you want the rest, exercise W6 and §3.3.4 are waiting.
7. Why it's done this way
- Exact integer arithmetic,
i128. is a minimum over a lattice; a single rounding error can report a vector that does not exist or miss one that does, and a "certification" computed in floating point certifies nothing. Norms reach ;i128holds them with room to spare. Floating point enters exactly once, in , after the integer minimum is frozen. - The dual lattice, not the primal. The failure mode users care about is planes-and-slabs, and plane families correspond one-to-one to dual vectors, with gap . Minimizing over the dual is maximizing the worst slab — and the dual basis is available by inspection, no inversion needed.
- Reduction in 2-D, enumeration in 3-D. Rank 2 is the one case where a greedy reduction provably finds the optimum (§4's proof), so use it. At rank 3 greedy is no longer provably optimal, but enumeration with a self-certifying bound is still cheap — so we buy the proof with cycles. This mirrors real practice: LLL/BKZ reduce, then an exact enumeration finishes.
- c = 0 without loss. The increment translates the point set (§1); carrying it through every formula would buy nothing and obscure the lattice. Knuth drops it in the first paragraph of §3.3.4 for the same reason.
- Overlapping tuples. Non-overlapping tuples of a full-period LCG form essentially the same lattice (a sub-sequence of an LCG is an LCG with multiplier ), but overlapping tuples are what serial correlation actually sees, and they make the lattice theorem a one-liner.
8. Stage-by-stage lab guide
Open labs/module-12-spectral/src/lab.rs. Run ./grade 12; stages run in
order and stop at the first failure.
Stage 1 — tuples and is_dual_vector
Mechanics first. tuples(a, m, t, x0, count) generates terms
of and windows them; multiply in i128 before
reducing. is_dual_vector(u, a, m) checks with
incrementally computed powers (never form unreduced!) and rejects
. Coordinates may be negative: rem_euclid gives the non-negative
residue Rust's % does not. The tests make you verify RANDU's
— and that every generated triple obeys the planes equation,
which is the lattice theorem made executable.
Stage 2 — nu2_squared
Algorithm G on the basis , , all in i128. The one subtlety
is nearest integer to with : (2*d + n).div_euclid(2*n)
does it (check it against the hand trace: , must give
). The tests cross-check against a brute force whose cutoff is the
Hermite bound — read that helper; its comment is the argument you should be
able to reproduce cold — and pin the hand-traced .
Stage 3 — nu3_squared_certified
Algorithm C verbatim. Keep the running residue incremental if you like speed, but correctness only needs the centered residue chosen right: vs , smaller absolute value wins. Do not forget to skip inside the scan while still seeding best . The tests demand RANDU's 118 on the nose, the frozen 408 197 for the minimal standard, agreement with a full brute force on tiny moduli, and a witness dual vector achieving your minimum.
Stage 4 — mu2 and mu3
Ten lines: , with in f64. The tests hold you to the reference values to 1e-9 and — the point of the whole module — to the ordering: on , with five orders of magnitude between the ends.
9. In the real world
- RANDU's corrupted simulations. RANDU shipped in IBM's System/360 Scientific Subroutine Package and was the de facto random source of the late 1960s and 1970s. Any Monte Carlo computation that consumed triples — particle transport, numerical integration in 3-D, generation of random directions — was sampling 15 planes instead of a volume. Results built on it had to be re-examined for years afterwards; Knuth's verdict in §3.3.4 is that its very name deserves contempt, and textbooks still cite it as the canonical warning that a generator can pass 1-D tests perfectly while being unusable. The deeper lesson: the flaw was provable in advance — is one line of algebra — but nobody ran the test before shipping.
- Why PCG and xoshiro replaced plain LCGs. The lattice structure you
computed is not a bug of bad multipliers; it is a theorem about all
LCGs. Even the best multiplier leaves , so a
32-bit LCG is structurally weak in high dimensions, and power-of-two
moduli additionally make the low bits cycle with period . Modern
designs keep a linear engine but destroy the lattice at the output:
PCG (O'Neill, 2014) advances a 64/128-bit LCG state — with multipliers
chosen via exactly this spectral theory, and its default multiplier
6364136223846793005 is the one Knuth ships in MMIX (and the one this
course's test files use for deterministic data) — then applies a
state-dependent permutation (xorshift + rotate) so that output tuples no
longer lie on the state lattice. The xoshiro/xoroshiro family (Blackman &
Vigna) uses linear recurrences over GF(2) plus a nonlinear scrambler
(
**= multiply–rotate–multiply) for the same reason. Both pass TestU01's BigCrush, where every raw LCG of comparable size fails — largely on lattice-derived tests. - Where LCGs are still fine. As the state transition inside PCG and
in
java.util.Random-style engines (weak, but understood); as combined generators — L'Ecuyer's MRG32k3a, built from two multiple-recursive congruential generators chosen by spectral-type criteria, remains a workhorse of simulation libraries and GPU stream packages (MATLAB, SSJ, cuRAND); and anywhere reproducible, cheap, statistically undemanding streams are needed: test-data generation (this repo!), procedural jitter in graphics, load-balancing salts. The rule: fine when nobody consumes -tuples as geometry, or when an output scrambler stands between the lattice and the user. And when someone proposes a new multiplier, the spectral test — Fishman–Moore/L'Ecuyer-style exhaustive searches, Knuth's Algorithm S — is still exactly how it is vetted.
10. Check your understanding
Answer before moving on (hints inverted at the end of each item):
- Adding an increment changes every — why does it change no ? Hint: write the -tuple as ( tuple) for a fixed ; what do translations do to plane families?
- Show for without multiplying out . Hint: it is .
- RANDU's planes: derive the count 15 from . Hint: bound over and count multiples of strictly inside; with , .
- In Algorithm C, why may the scan fix to the centered residue and ignore every other member of the class — could a longer ever pair with the same to give a shorter vector? Hint: the other coordinates don't change, and only grows.
- The optimality proof of Algorithm G needs . Where does it fail if range over reals, and what does that tell you about why lattice problems are hard while linear algebra is easy? Hint: try ; shortest-vector is discrete optimization.
11. Exercises
Ratings use Knuth's scale: 00 immediate · 10 a minute · 20 fifteen minutes
to an hour · 30 hours · 40 term project. M = mathematically oriented. Log
attempts in course/module-12-spectral/exercises.md. (These are curated
for our build; if you own Vol. 2, the exercises of §3.3.4 continue
the story — several assume the full Algorithm S.)
| Ex. | Rating | Statement |
|---|---|---|
| ▶W1 | M10 | Prove for every . (Pad a dual vector with a zero.) So generators only get worse as grows. |
| W2 | M18 | Write out the translation proof of §1 in full: exhibit the fixed vector for general and verify (mod per coordinate). |
| W3 | M22 | Prove the plane-count formula: a dual with both signs present yields exactly planes meeting , assuming each candidate plane contains at least one integer point of the cube. Where is that assumption used? |
| ▶W4 | M24 | Fill in every inequality of the Gauss–Lagrange optimality proof, and show the reduced basis also achieves the second minimum: is a shortest vector independent of . |
| W5 | 22 | Instrument Algorithm C: report the final and total iterations for RANDU, 16807, 48271. Verify the doubling schedule's cost is of the final scan. |
| ▶W6 | 30 | Extend the certified search to (scan , centered ; same certification). Estimate the cost from the Hermite bound , then measure. Reproduce for RANDU. |
| W7 | M28 | For , the multiplier 48271 was found by exhaustive spectral search (Fishman–Moore style). Search all in a range of your choice maximizing and see how close to 48271-quality you get. |
12. Proof techniques you practiced
- Reduction to a canonical case — dropping by exhibiting the point sets as translates (§1); one line of algebra deleted a parameter from the whole theory.
- Proof by generators — the lattice theorem and dual-vector annihilation were verified on basis vectors only, and linearity did the rest (§2, §3).
- Counting by bounding a linear form — the plane count came from trapping in an interval and counting multiples of inside it (§3); the same move produced Lamé-style bounds in module 01.
- Invariant + strictly decreasing integer quantity — termination of Gauss–Lagrange reduction (§4), the course's oldest pattern, here with unimodularity as the invariant and the squared norm as the descent.
- Optimality via a quadratic-form inequality — the exchange argument turned "locally reduced" into "globally shortest" (§4), with integrality load-bearing.
- Certified enumeration — a search bound justified a posteriori by the best value found, plus an a priori Hermite bound to guarantee the loop halts (§5, §6). This is the skeleton of every branch-and-bound correctness argument you will ever write.
13. Where this leads
- Module 04 gave you the LCG and its period theory (§3.2.1); this module is the quality theory that decides which full-period generator to ship. Together they are Knuth's answer to "what multiplier?"
- The reduction/enumeration pair (Gauss–Lagrange, certified search) is the entry point to lattice algorithms at large: LLL, BKZ, and the shortest-vector problems underlying post-quantum cryptography — where §4's optimality argument reappears as the base case of LLL's analysis.
- Knuth's Algorithm S () adds simultaneous primal/dual basis updates and ellipsoid enumeration — a finished branch-and-bound built on stage 3's certification idea.
- The statistical companion is §3.3.2's serial test and the empirical batteries (TestU01); the spectral test is the theoretical member of that family — it examines the whole period at once, no sampling.