Matrix multiplication over Z/nZ for multiword n
#include <flint/mpn_mod.h>Set . mpn_mod_mat_mul chooses between classical multiplication with delayed reduction (gr_mat_mul_classical with the dot product above), Waksman's algorithm with delayed reduction and multimodular multiplication via nmod_mat_mul. See Matrix multiplication over Z/nZ for multiword n.
mpn_mod_mat_mul is the matrix multiplication method of the mpn_mod ring: gr_mat_mul over the ring calls it, and so do the generic block algorithms built on matrix multiplication (recursive LU decomposition, triangular solving, inversion, determinants). It chooses between three algorithms, all of which return the exact product modulo :
| Algorithm | Function | Multiplications for times | Reductions |
|---|---|---|---|
| Classical | gr_mat_mul_classical with _mpn_mod_vec_dot | products of limbs | one per entry |
| Waksman | mpn_mod_mat_mul_waksman | about products of or limbs | one per entry |
| Multimodular | mpn_mod_mat_mul_multi_mod | products over nmod_mat with | Chinese remaindering, one reduction per entry |
Here is the bit size of and its number of limbs.
Choice of algorithm#
Matrices with at most 3 rows use classical multiplication. Otherwise two cutoffs are read from a table indexed by in steps of 16 bits, tuned on x86-64:
- if , or is less than , the classical algorithm is used;
- otherwise, if one of them is less than , Waksman's algorithm;
- otherwise the multimodular algorithm.
For moduli of at most 128 bits and of 177 to 192 bits the two cutoffs are equal, so Waksman's algorithm is not used: the classical algorithm runs up to dimension 88 to 230 (for ) or 74 (for ). For other sizes Waksman's algorithm starts at dimension 4 to 17 and the multimodular algorithm takes over between dimensions 39 and 93; at 256 bits the cutoffs are 17 and 63, at 1024 bits 4 and 53. Strassen's algorithm is not used: at these sizes the multimodular algorithm wins first.
Classical multiplication#
Each entry of is a dot product of a row of and a column of , computed by _mpn_mod_vec_dot_strided without intermediate reductions: the full products are added in limbs, and the sum is reduced once (Dot products).
Waksman's algorithm#
Winograd [Win1968] observed that for two vectors of even length ,
In a matrix product, is a row of and a column of , so the last two sums depend only on the row or only on the column and can be computed once for each: the product costs multiplications instead of . Waksman [Waksman1970] noticed that the correction terms can be obtained from the same kind of products. Writing and , the identity
gives for every column from the products for row 0, and for every row from those for column 0; then . The extra products are only needed in the first row and column. For odd the last term is added with ordinary products.
mpn_mod_mat_mul_waksman evaluates all sums exactly on integers and reduces each entry of once. Accumulators have limbs and hold signed values. The sums of two residues fit in limbs unless the top bit of is set, in which case they get an extra limb and the products are -limb products; the differences are formed as sign and magnitude. The halvings are exact divisions by 2 of signed multi-limb integers. Since a multiplication of -limb numbers costs about word operations and an addition about , halving the number of multiplications at the price of additions pays off from small dimensions on.
Multimodular multiplication#
mpn_mod_mat_mul_multi_mod computes the integer product of the matrices of residues, whose entries are less than with , modulo word-size moduli whose product exceeds , and reconstructs it by the Chinese remainder theorem:
- The moduli are and the smallest primes above , where =
NMOD_MAT_OPTIMAL_MODULUS_BITS(59 on 64-bit machines), with . Using a power of two as the first modulus lets the fixed-length code offlint_mpn_multi_crtapply for small products. - Each row of and of is reduced modulo all moduli at once with
flint_mpn_multi_mod_vec. - The products are computed with
nmod_mat_mul, which itself uses delayed reduction, Strassen–Winograd, SIMD or BLAS kernels (Multiplying matrices over Z/nZ). - Each row of is reconstructed with
flint_mpn_multi_crt_vecand each entry reduced modulo withmpn_mod_set_mpn.
The reductions and the reconstruction are split over threads (by rows, when the dimensions are in the hundreds or larger), and nmod_mat_mul is multithreaded itself. The code follows the multimodular multiplication of fmpz_mat (Multimodular and p-adic linear algebra over the integers).
Example#
The program below multiplies two matrices modulo with the three algorithms and checks that they agree.
#include <flint/fmpz.h>
#include <flint/gr.h>
#include <flint/gr_mat.h>
#include <flint/mpn_mod.h>
int main(void)
{
gr_ctx_t ctx;
fmpz_t p;
gr_mat_t A, B, C1, C2, C3;
slong i, j, d = 30;
int status = GR_SUCCESS;
/* p = 2^255 - 19 */
fmpz_init(p);
fmpz_ui_pow_ui(p, 2, 255);
fmpz_sub_ui(p, p, 19);
GR_MUST_SUCCEED(gr_ctx_init_mpn_mod(ctx, p));
gr_mat_init(A, d, d, ctx);
gr_mat_init(B, d, d, ctx);
gr_mat_init(C1, d, d, ctx);
gr_mat_init(C2, d, d, ctx);
gr_mat_init(C3, d, d, ctx);
/* A[i][j] = ij - 7, B[i][j] = A[i][j]^40 */
for (i = 0; i < d; i++)
for (j = 0; j < d; j++)
{
status |= gr_set_si(gr_mat_entry_ptr(A, i, j, ctx), i * j - 7, ctx);
status |= gr_pow_ui(gr_mat_entry_ptr(B, i, j, ctx),
gr_mat_entry_ptr(A, i, j, ctx), 40, ctx);
}
status |= gr_mat_mul_classical(C1, A, B, ctx);
status |= mpn_mod_mat_mul_waksman(C2, A, B, ctx);
status |= mpn_mod_mat_mul_multi_mod(C3, A, B, ctx);
printf("waksman: %d, multi_mod: %d\n",
gr_mat_equal(C1, C2, ctx) == T_TRUE, gr_mat_equal(C1, C3, ctx) == T_TRUE);
status |= gr_println(gr_mat_entry_ptr(C1, 1, 2, ctx), ctx);
gr_mat_clear(A, ctx);
gr_mat_clear(B, ctx);
gr_mat_clear(C1, ctx);
gr_mat_clear(C2, ctx);
gr_mat_clear(C3, ctx);
gr_ctx_clear(ctx);
fmpz_clear(p);
printf("status %d\n", status);
return 0;
}waksman: 1, multi_mod: 1 5452489965307334725493791908297774450834361894610887641359429602394561 status 0
mpn_mod evaluates sums of products modulo an -limb exactly in to limbs and reduces once, in dot products, polynomial multiplication and division, Euclid's algorithm, Gaussian elimination and matrix multiplication, and how this plugs into the generic algorithms.References#
- [Win1968] S. Winograd. A new algorithm for inner product. IEEE Transactions on Computers C-17 (1968), 693–694. https://doi.org/10.1109/TC.1968.227420
- [Waksman1970] Waksman, Abraham, "On Winograd's algorithm for inner products", IEEE Transactions on Computers 19.04 (1970): 360-361.
The FLINT team, “Matrix multiplication over Z/nZ for multiword n (mpn_mod.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/mpn_mod/mat-mul.html (generated 2026-11-02).
@misc{flint-doc-mpn-mod-mat-mul,
author = {{The FLINT team}},
title = {{Matrix multiplication over Z/nZ for multiword n (mpn\_mod.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/mpn_mod/mat-mul.html},
note = {Generated 2026-11-02}
}