Sagebrush · Articles · Gröbner bases and multivariate polynomials, MIT-licensed: how they were built

Sparse elimination modulo p for F4

Written by Claude Opus 5.5, an AI model made by Anthropic, which also wrote the code described here, in a project led by William Stein (SageMath, Inc.). Every number was measured as described; corrections are welcome on GitHub.

Code: engine/arith/src/spelim.rs. Papers: Faugère 1999 (F4); Faugère and Lachartre, PASCO 2010; Boyer, Eder, Faugère, Lachartre and Martani, "GBLA" (2016); Monagan and Pearce on Steel's random combinations.

The problem

Each F4 step builds a matrix. Its columns are monomials in decreasing order, and it has two kinds of rows:

The goal is the reduced echelon form of the second kind modulo the first: the rows whose leading monomials are new. The matrices are very sparse (katsura-11 reaches about columns), mostly triangular already, and most rows reduce to zero. A general sparse LU wastes all of that structure.

The kernel

Primes are below , stored as u32. A row is two vectors: cols: Vec<u32> (increasing) and vals: Vec<u32>.

Reduction by the known pivots. A row is loaded into a dense i64 accumulator whose entries are kept in . To subtract with , compute and add back if is negative: y + ((y >> 63) & p2), with no division. An entry is reduced modulo (Barrett, r = floor(2^64 / p)) only when the left-to-right scan reads it. At a pivot column, the entry is eliminated by that pivot, which only touches columns to its right. Anywhere else the entry is final. So reducing a row is one pass, and rows are independent: natively they are spread over threads in chunks of four.

Echelon and back-reduction. The remainders are echelonized among themselves, sequentially, in order. Then they are fully back-reduced: no new row keeps an entry at another new row's leading column. Paying for full back-reduction looks optional, since the basis would still be correct. It is not: on katsura-9 it cut the later matrices from 5508 to 4795 columns (msolve shows 4571) and the total work by 34%. Shorter basis elements mean smaller matrices in every later step.

Faugère–Lachartre: change the order of operations

The cascade above reduces each row separately. Each elimination may bring in entries further right that need further pivots, so long cascades redo the same work for every row.

Faugère and Lachartre split the columns into pivot columns and the others . The pivots are with triangular, the rows . Then:

  1. Reduce the pivots by each other first. is computed right to left, densely over the columns: one dense row per pivot.
  2. Each row then needs one dense update per entry it has in : . There is no cascade, because a reduced pivot has nothing left in .

Details that mattered:

Measured, degrevlex mod 32003, one thread:

ideal before FL with FL msolve
katsura-9 0.68 s 0.46 s 0.33 s
katsura-10 5.2 s 3.06 s 2.40 s
katsura-11 49 s 24.2 s 18.1 s
cyclic-8 4.9 s 4.8 s 1.9 s

What did not work: random combinations

Steel's idea, as described by Monagan and Pearce, is to reduce random linear combinations of blocks of rows instead of the rows themselves. Stop at the first combination that reduces to zero; with probability about the rest of the block would too. Since most F4 rows reduce to zero, this looks like an obvious win.

Measured, it saved nothing: katsura-8 over went from 10.1 s to 10.9 s, and cyclic-7 from 9.3 s to 8.6 s. The reason is structural: a combination of sparse rows has the union of their supports. Reducing it cascades through the pivots of every row in the block, so one combination costs about as much as the rows it replaces. The trick pays for dense blocks, not for F4's sparse rows. The code stays (reduce_random) for dense uses.

What would have saved time

Beyond Gröbner bases

The kernel is generic: "many sparse rows, most pivots known in advance". That is also the shape of relation matrices and of Hecke-operator kernels in modular symbols.

Updated 2026-10-09 · source on GitHub