Sagebrush · Articles · Number theory engines, MIT-licensed: how they were built

Modular symbols and newforms

Written by Claude Opus 5.5, an AI model made by Anthropic, from the code, commit history and result notes (results/engine*.md, newforms.md, newspaces.md, general*.md) of the Sagebrush project, led by William Stein (SageMath, Inc.).

Code: engine/modsym. Sources: Cremona, "Algorithms for Modular Elliptic Curves"; Stein, "Modular Forms: A Computational Approach"; Merel's Hecke matrices; Cohen–Oesterlé dimension formulas; Atkin–Lehner–Li theory.

What is computed

The engine works with modular symbols for , in the sign quotient. It computes:

The presentation

The lesson of the dense coordinate table

The first engine stored every generator's coordinates in the quotient basis: numbers. Measurement showed that after sparse elimination in creation order, the relations keep about 2.0 nonzeros per row at every level tried. The dense table was 154× (at ) to 1764× (at ) larger than the information in it.

The engine now stores only the sparse relations. A functional extends to all generators in , and Hecke matrices are assembled 64 columns at a time. Peak memory at fell from 235 to 120 MB, and the split from 23 s to 3 s.

Characteristic polynomials over , proven

The characteristic polynomial is computed by CRT over primes below , under three rules:

The kernel. The Hessenberg reduction mod did a 64-bit % in every inner step. It now uses u32 entries and Shoup multiplication (a precomputed ), so the inner loops have no division and vectorize. AVX2 is chosen at run time, at the leaf kernels, because closures run by rayon do not inherit #[target_feature]. One prime at : 2501 ms became 276 ms.

Against Sage, one thread (before the sharper bound):

, dim Sage default Sage, matrix + LinBox Sagebrush, proven
2310, 13 592 44.0 s 12.6 s 11.2 s
5000, 3 754 113.4 s 14.5 s
10007, 2 835 162.1 s 121.9 s 25.8 s

LinBox stops early (heuristically), while ours proves its bound. With 8 threads and the sharper bound, takes 2.56 s.

What did not work. Interleaving 8 primes in one matrix of [u32; 8] lanes was correct and used half the memory. It was also 1.5× slower below dimension 1000, and only equal at 1668. The single-prime kernel was already 8-wide across columns, so lanes did not reduce the bytes moved per prime. Large dimensions are memory-bound (3.6 ns per element out of L3): the fix there is a blocked charpoly, not more SIMD.

Rational newforms: Cremona's tables from scratch

A rational newform has integer with . So the space is split only by those finitely many eigenvalues:

  1. take the charpoly's integer roots for the first , and kernels only for them;
  2. refine prime by prime;
  3. drop any subspace that the known old part explains: each newform of level occurs with multiplicity , memoized across levels;
  4. a 1-dimensional joint eigenspace is new.

Then for one functional , one sum over Heilbronn matrices per prime.

Results:

Against Magma 2.18 (NewformDecomposition of the cuspidal new subspace):

workload Magma Sagebrush
3.82 s 0.10 s
(dim 2336) 855 s 92 s on one thread, 18 s on 16
all levels 11–2000, one core 1039 s 76.7 s (13.5×)

Magma's route also decomposes the non-rational part, so this is not a like-for-like comparison of algorithms. eclib (Cremona's own program) is the honest benchmark, and has not been run yet.

Any weight, any character: LMFDB's newspaces

Work modulo primes , so every character takes values in . For each level between the conductor of and , and each conjugate character :

  1. compute the charpoly of a combination ;
  2. remove the Eisenstein eigenvalues , which are enumerated, and whose count is checked against the formula for ;
  3. divide out the old forms .

What remains is the new part. The product over conjugates, lifted by CRT, is the charpoly over on the newspace. Its irreducible factors are the Galois orbits.

The choice of has to beat two things:

Candidates escalate from two primes, to primes generating , to 8–16 primes; 26 spaces needed the later stages.

Results:

A convention the tests caught. The character relation's direction matters. With replaced by , 664 of 1452 validation spaces disagree with Sage. Testing against the oracle is what pinned the convention.

What would have saved time

Updated 2026-10-09 · source on GitHub