Sparse and dense multiplication
- Cost
- Heap method monomial comparisons and coefficient multiplications with extra memory besides the output; array method for an array of size ; Kronecker substitution for the dense box of size ; FPS powering for .
Let and be polynomials with and terms in the distributed representation of Monomial orderings and exponent packing. Their product has at most terms, and in the sparse case about that many; in the dense case far fewer, because many products share a monomial. The classical algorithm forms all products and sorts or merges them. Done naively (adding to an accumulator one row at a time) it costs monomial comparisons in the worst case. FLINT's algorithms avoid this in different ways: a heap (sparse inputs), a dense array (moderately dense inputs) or a reduction to univariate multiplication (dense inputs).
The heap method#
Johnson's algorithm [Joh1974] merges the sorted sequences with a priority queue holding the next unused product of each sequence. Monagan and Pearce [MonPea2007] made it practical with three refinements: packed monomials (so that the heap keys are words compared with one instruction), chaining (products with equal monomials are linked in one heap node, so the heap only holds distinct monomials) and a rule that keeps the heap small. We describe the algorithm as implemented, with rows indexed by and columns by .
Input: and , sorted. Output: , sorted.
- Insert with key into an empty heap. Mark row 0 as being in the heap at column 0.
While the heap is not empty:
- Let be the largest key. Pop all nodes with key (each heap entry is a chain of such nodes), add to an accumulator and record the popped pairs.
- For each popped : insert if and row has used exactly the columns before and has no node in the heap; and insert if and either or row has already used column .
- If the accumulator is nonzero, append the term with monomial .
Lemma 1 (Heap invariant). In Algorithm 1, the heap always contains the largest product not yet used, each row has at most one node in the heap, and every product is used exactly once.
Proof. For , because the ordering is compatible with multiplication; similarly . So the products form a matrix decreasing along rows and columns, and the unused product with largest monomial must have both and used (when they exist). The insertion rule inserts exactly when the second of these two neighbours is popped, so every such frontier product is in the heap, and in particular the largest unused one. A row gets a new node only when its previous node is popped, or when its first node is inserted from row ; hence each row has at most one node and every product is inserted exactly once.
Theorem 2 (Cost of the heap method). Algorithm 1 computes with coefficient multiplications, at most monomial comparisons in the heap, and memory besides the inputs and output. FLINT exchanges and if necessary so that .
Proof. By Lemma 1 the heap has at most entries, so each insertion and extraction costs comparisons, and there are of each (chained products cost less). The output is produced in decreasing order because the popped key is always the largest unused monomial.
In the dense case, chaining makes the cost per product close to constant: many products are popped together and the heap stays much smaller than . The packed monomial of each candidate product is a word sum () or an -word sum, and the comparisons use the packed comparison.
Coefficient accumulation#
Over , if all coefficients of and are small fmpz (absolute value below on a 64-bit machine), each product is a signed two-word integer, and a sum of at most of them has absolute value below ; it is accumulated in three words with smul_ppmm and three-word additions and converted to an fmpz once per output term. Otherwise fmpz_addmul is used. Over (nmod_mpoly), products are accumulated unreduced in up to three words and reduced once per output term, the delayed reduction of Choosing a method for modular arithmetic. This accounts for much of the speed of the specialised versions compared with the generic one.
Parallel heap multiplication#
Monagan and Pearce [MonPea2009] parallelise by splitting the output into ranges of monomials, each computed by a heap restricted to the products in the range. FLINT (fmpz_mpoly_mul_heap_threaded) cuts the monomials at boundaries for threads: the -th boundary is a monomial such that about of the products are at most (found by mpoly_search_monomials, a binary search along the rows for each candidate). The quadratic spacing makes the ranges for the largest monomials, which are processed first, contain more products, so that no thread ends long before the others. For each range and each row of the first and last columns of in the range are known, and the restricted heap algorithm is run on these segments. The pieces are concatenated in order at the end.
Dense methods#
Array accumulation#
If the product is dense in a box of moderate size, it is faster to add the products into a dense array indexed by the exponents than to sort them. FLINT's array method (fmpz_mpoly_mul_array) slices the product:
- for
ORD_LEX, by the exponent of : with , the product of the slices of and of degrees and in contributes to slice , and each slice is a dense array of entries, with ; - for the degree orderings, by total degree: within a slice the exponent of is determined by the others, and the array is indexed by the exponents of , each below a bound on the total degree of the product, so it has entries.
Each output slice is computed by adding all contributing products of input slices into a zeroed array and then reading off the nonzero entries in decreasing order.
Lemma 3 (Accumulator size). If the products contributing to an output slice come from pairs of input slices , fixed, then every coefficient of the slice is bounded in absolute value by
Proof. A coefficient of is over pairs with a fixed monomial; each appears at most once in the sum, and so does each , so its absolute value is at most both and . Sum over the pairs.
FLINT evaluates this bound in bits for each slice and accumulates in one, two or three words if it fits (with one bit for the sign), and in fmpz otherwise. Slices are distributed over threads in fmpz_mpoly_mul_array_threaded. The method declines (returns 0) when the array would be too large or the exponents do not fit one word.
Kronecker substitution#
For dense inputs of moderate degree the fastest method is to substitute a power of a single variable for each variable and multiply univariate polynomials [Kro1882].
Lemma 4 (Kronecker substitution). Let for , and let be the substitution , , , . Then is injective on the monomials of , so is recovered from by reading the exponents of in mixed radix .
Proof. with . For the monomials of , for , so determines by division with remainder. Since is a ring homomorphism, , and no two terms of are mapped to the same power of .
fmpz_mpoly_mul_dense converts and to dense arrays in this order (no padding is needed for ), multiplies them with fmpz_poly_mul, which uses the univariate algorithms of Polynomial arithmetic over generic rings and its specialisations (Kronecker substitution into integers, multimodular and Schönhage–Strassen methods), and converts back. The cost is that of a univariate product of length , so the method only pays when is not much larger than the number of terms of the product. For nmod_mpoly the same is done with nmod_poly_mul.
Squaring and powering#
Squaring with the heap method can use symmetry: needs only about half the products (gr_mpoly_sqr_commutative_heap). For higher powers, repeated multiplication computes with a cost dominated by the last product, and binary powering does not help much for sparse polynomials because the last squaring of a dense intermediate result is as expensive as all the rest. Monagan and Pearce [MonPea2012] instead compute directly by the recurrence for the coefficients of a power of a power series (FPS, after the classical method of Euler; see [Knu1997, §4.7]).
Lemma 5 (FPS recurrence). Let be a derivation of over a ring with for a group homomorphism (for example ). If and , then and .
Proof. For a derivation, . Multiply by .
Write and compare the coefficients of a monomial on both sides of :
where . The term with is . So if , the coefficient of in is
in terms of coefficients of at larger monomials. Generating the products with a heap in decreasing order of , the algorithm computes, for each monomial , the coefficient of and the coefficient of at the same time; the latter is needed only when divides , which is tested with the packed divisibility test.
Theorem 6 (FPS powering). With injective on the monomials that occur (for example , the packed exponent read as an integer) and for all monomials of , the algorithm computes and with coefficient operations and monomial comparisons, all divisions being exact over .
Proof. The monomials of are at most , and those of at most ; the recurrence determines the coefficients of in decreasing order, starting from . Each product enters the heap once, so the cost is that of a heap multiplication of by . The quotients are coefficients of , so the divisions are exact. If , injectivity holds by construction, and for the orderings with zero cmpmask it is also order preserving, so for . For ORD_DEGREVLEX, injectivity alone gives .
Since is the number of terms of , this is about the cost of the final multiplication in repeated multiplication, while avoiding the intermediate products. fmpz_mpoly_pow_ui uses FPS for (after checking that the output length cannot overflow); the divisions by make it specific to characteristic zero. Over , nmod_mpoly_pow_ui multiplies repeatedly (nmod_mpoly_pow_rmul), and for prime uses (the Frobenius map, since in ) to handle the base- digits of .
Generic and specialised implementations#
The generic implementation is gr_mpoly_mul, for any coefficient ring given by a gr context: it uses gr_mpoly_mul_monomial for a monomial factor, the heap method gr_mpoly_mul_heap (and gr_mpoly_mul_heap_threaded when the product has more than term products and threads are available), and the symmetric squaring gr_mpoly_sqr_commutative_heap for in commutative rings. It has no array or Kronecker methods, and accumulates coefficients with the ring's own operations. The specialised modules add:
| Module | Heap | Threaded heap | Array | Kronecker | Powering |
|---|---|---|---|---|---|
fmpz_mpoly | fmpz_mpoly_mul_johnson | fmpz_mpoly_mul_heap_threaded | fmpz_mpoly_mul_array (and threaded) | fmpz_mpoly_mul_dense | FPS, fmpz_mpoly_pow_fps |
fmpq_mpoly | via fmpz_mpoly | via fmpz_mpoly | via fmpz_mpoly | via fmpz_mpoly | via fmpz_mpoly |
nmod_mpoly | nmod_mpoly_mul_johnson | nmod_mpoly_mul_heap_threaded | nmod_mpoly_mul_array (and threaded) | nmod_mpoly_mul_dense | repeated multiplication, Frobenius |
fmpz_mod_mpoly | fmpz_mod_mpoly_mul_johnson | fmpz_mod_mpoly_mul_dense | repeated multiplication | ||
fq_nmod_mpoly | fq_nmod_mpoly_mul_johnson | repeated multiplication |
The choice of method in fmpz_mpoly_mul, with its thresholds, is described in Multiplication; nmod_mpoly_mul follows the same pattern.
- Heap: Algorithm 1 in
_fmpz_mpoly_mul_johnson(one-word exponents:_fmpz_mpoly_mul_johnson1), with the accumulation of Coefficient accumulation; parallel splitting infmpz_mpoly_mul_heap_threadedwithmpoly_search_monomials. - Array:
fmpz_mpoly_mul_array,_fmpz_mpoly_mul_array_chunked_LEXand_fmpz_mpoly_mul_array_chunked_DEG, the accumulators_fmpz_mpoly_addmul_array1_slong1,_fmpz_mpoly_addmul_array1_slong2,_fmpz_mpoly_addmul_array1_slongand_fmpz_mpoly_addmul_array1_fmpz(Lemma 3). - Kronecker:
fmpz_mpoly_mul_dense(Lemma 4). - Powering:
fmpz_mpoly_pow_fps,fmpz_mpoly_pow_ui,fmpz_mpoly_pow_fmpz(Theorem 6).
void fmpz_mpoly_mul(fmpz_mpoly_t A, const fmpz_mpoly_t B, const fmpz_mpoly_t C, const fmpz_mpoly_ctx_t ctx)void fmpz_mpoly_mul_johnson(fmpz_mpoly_t A, const fmpz_mpoly_t B, const fmpz_mpoly_t C, const fmpz_mpoly_ctx_t ctx)void fmpz_mpoly_mul_heap_threaded(fmpz_mpoly_t A, const fmpz_mpoly_t B, const fmpz_mpoly_t C, const fmpz_mpoly_ctx_t ctx)int fmpz_mpoly_mul_array(fmpz_mpoly_t A, const fmpz_mpoly_t B, const fmpz_mpoly_t C, const fmpz_mpoly_ctx_t ctx)int fmpz_mpoly_mul_dense(fmpz_mpoly_t A, const fmpz_mpoly_t B, const fmpz_mpoly_t C, const fmpz_mpoly_ctx_t ctx)void fmpz_mpoly_pow_fps(fmpz_mpoly_t A, const fmpz_mpoly_t B, ulong k, const fmpz_mpoly_ctx_t ctx)int gr_mpoly_mul(gr_mpoly_t A, const gr_mpoly_t B, const gr_mpoly_t C, gr_mpoly_ctx_t ctx)Referenced from#
- Division, exact division and square roots (algorithm)
- Multivariate polynomials: representations and algorithms (overview)
- Monomial orderings and exponent packing (algorithm)
- Sparse interpolation (algorithm)
- fmpz_mod_mpoly.h (module)
fmpz_mpoly_pow_fps- Multiplication
- fq_nmod_mpoly.h (module)
- fq_zech_mpoly.h (module)
- gr_mpoly.h (module)
- mpoly.h (module)
- nmod_mpoly.h (module)
References#
- [Joh1974] S. C. Johnson. Sparse polynomial arithmetic. ACM SIGSAM Bulletin 8:3 (1974), 63–71. https://doi.org/10.1145/1086837.1086847
- [MonPea2007] M. Monagan and R. Pearce. Polynomial division using dynamic arrays, heaps, and packed exponent vectors. Computer Algebra in Scientific Computing (CASC 2007), Lecture Notes in Computer Science 4770, Springer, 2007, 295–315. https://doi.org/10.1007/978-3-540-75187-8_23
- [MonPea2009] M. Monagan and R. Pearce. Parallel sparse polynomial multiplication using heaps. Proceedings of ISSAC 2009, ACM, 263–270. https://doi.org/10.1145/1576702.1576739
- [Kro1882] L. Kronecker, "Grundzüge einer arithmetischen Theorie der algebraischen Grössen", Journal für die reine und angewandte Mathematik 92 (1882), 1–122
- [MonPea2012] M. Monagan and R. Pearce. Sparse polynomial powering using heaps. Computer Algebra in Scientific Computing (CASC 2012), Lecture Notes in Computer Science 7442, Springer, 2012, 236–247. https://doi.org/10.1007/978-3-642-32973-9_20
- [Knu1997] Knuth, D. E. The Art of Computer Programming, volume 2: Seminumerical algorithms, 1997
The FLINT team, “Sparse and dense multiplication,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/multivariate/multiplication.html (generated 2026-11-02).
@misc{flint-doc-algorithms-multivariate-multiplication,
author = {{The FLINT team}},
title = {{Sparse and dense multiplication}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/algorithms/multivariate/multiplication.html},
note = {Generated 2026-11-02}
}