Multivariate gcd and factorization
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/mpoly/src/gcd.rs, factor.rs, hensel.rs, factor_p.rs,
rdense.rs. Papers: Brown 1971; Wang 1978 (EEZ); Yun 1976; the textbooks
of von zur Gathen and Gerhard (Modern Computer Algebra) and of Geddes,
Czapor and Labahn (Algorithms for Computer Algebra).
GCD: Brown's modular algorithm, with cofactors
Over , the gcd is computed modulo large primes and the images are combined by CRT. Modulo a prime:
- evaluate the last variable at points of ;
- find the gcds of the images recursively, down to univariate gcds;
- interpolate back (Newton).
Leading coefficients are normalized to at each level. Unlucky primes and points show up as leading monomials that are too large, and are discarded.
What worked: interpolate the cofactors too. and go through the same evaluations and interpolation as . The result is then checked by two products, and , rather than two trial divisions. Multiplication is cheaper and simpler to make fast than division. The Fateman gcd () takes 0.075 s, against Singular's 0.077 s.
Factorization over : Wang's EEZ
- Contents.
- Yun's square-free decomposition, which reuses the gcd's cofactors.
- For each square-free part:
- evaluate all variables but one;
- factor the univariate image (Zassenhaus, in
sagebrush-poly); - impose the leading coefficients;
- Hensel-lift one variable at a time, by multivariate diophantine equations.
Things that mattered:
Leading coefficients by distinctive primes. Wang's trick:
- factor the leading coefficient;
- evaluate its factors at the point;
- attribute each one to the image factors through primes that divide exactly one evaluated factor.
This avoids the coefficient blow-up of imposing on everything.
Choose the point and the main variable well. Prefer zeros (sparse images), and the variable with the simplest leading coefficient.
Lift modulo a 62-bit prime, not . Coefficients smaller than half the prime come out as symmetric residues. Every factor is checked by exact division, and the rare failure falls back to -adic lifting with a rigorous bound.
Prefix products split by degree in the lifting. The error term of each step comes from products kept per power of the new variable, so a variable costs a few products, not one per degree.
with factors in 0.66 s; Singular takes 0.58 s. 216 random factorizations matched Singular.
Over finite fields: images split
Hilbert irreducibility fails over : the univariate image of an irreducible polynomial usually splits. So Wang's method cannot assume the image's factors correspond to true factors. The fix is a bivariate stage:
- Lift the factors of the univariate image -adically, with the polynomial made monic as a power series, far enough that any true factor is determined.
- Recombine by trying subsets.
- Lift the true bivariate factors to all variables by the same multivariate Hensel lifting, with the leading coefficient imposed on every factor. Over a field that costs degree, not coefficient size.
Characteristic square-free parts are different too. A derivative can vanish without the polynomial being constant. Take a variable with :
- is the product of the irreducible factors that are separable in and of multiplicity prime to ;
- factor those, divide them out (repeated divisions give the multiplicities), and treat the rest again;
- when every derivative vanishes, is a -th power: divide the exponents by .
GCDs without evaluation points. Over there are no good
points, so Brown's algorithm cannot run. rdense.rs stores polynomials
recursively and densely, and computes the subresultant PRS gcd. The first
version used a naive PRS and was far too slow; the subresultant
(prem multiplies by exactly ) plus square-free
tests on univariate and bivariate images fixed it.
Tiny fields: other main variables, then automorphisms , within a budget. Otherwise the engine says "not in the engine" and the caller falls back. Evaluation in extension fields is the proper fix and is still to do (some cases).
Printing like Sage took real work:
- factors are made monic in invlex;
- they are sorted by degree, then exponent, then Sage's own comparison of polynomials, which is not transitive.
503 of 506 random factorizations print exactly as Sage 10.10 does. All 506 are correct; the three differ only in the order of ties.
Bugs worth remembering
- Detecting the level of a recursive dense polynomial by its first coefficient fails when that coefficient is zero. Use the last.
- Constant factors in square-free loops can loop forever. Guard every "divide out and repeat" loop with a degree check.
What would have saved time
- Fix the output conventions (monic in which order, sorting) against the oracle before optimizing. Matching Sage's printing reached into the normalization code after it was written.
- Build the PRS gcd for tiny fields first, then Brown. Brown is the fast path; the PRS gcd is the one that always works, and tests on and find bugs early.
- Interpolate cofactors from the start. Checking a gcd by products instead of divisions removed a whole class of slow paths.