Multiplying matrices
#include <flint/nmod_mat.h>void nmod_mat_mul(nmod_mat_t C, const nmod_mat_t A, const nmod_mat_t B)sourceSets , where is , is and is . may be aliased with or .
void nmod_mat_addmul(nmod_mat_t D, const nmod_mat_t C, const nmod_mat_t A, const nmod_mat_t B)sourcevoid nmod_mat_submul(nmod_mat_t D, const nmod_mat_t C, const nmod_mat_t A, const nmod_mat_t B)sourceSets , respectively . may be aliased with but not with or . The product is either computed by classical multiplication fused with the addition, or with nmod_mat_mul and then added; see Adding a product.
nmod_mat_mul computes for an matrix and a matrix over (here is the modulus, since is a dimension). It is the function to call: it picks one of about ten methods from the dimensions, the size of , the number of threads and the instruction set, and it accepts any modulus, prime or not. nmod_mat_addmul and nmod_mat_submul add the product to, or subtract it from, another matrix. Almost all of the linear algebra in this module, from LU decomposition to triangular solving, reduces to these three functions.
nmod_mat_mul chooses among them.
Usage#
- must be initialised as an matrix with the same modulus as and . The dimensions are checked only by
FLINT_ASSERT. nmod_mat_mulallows to be the same matrix as or , for examplenmod_mat_mul(A, A, A)to square in place; the product is then computed into a temporary matrix. Windows are allowed, but must not partially overlap or .nmod_mat_addmul(D, C, A, B)andnmod_mat_submul(D, C, A, B)allow to be , which is the usual way to update a matrix in place, but not or .- With
flint_set_num_threads, the large products and most kernels use several threads.
All methods compute the exact product modulo , so the result never depends on the method chosen.
How the method is chosen#
On 64-bit machines nmod_mat_mul tests the following cases in order, where .
| Case | Method |
|---|---|
| each entry is a dot product accumulated in one to three words and reduced once | |
_nmod_vec_scalar_mul_nmod | |
| and | nmod_mat_mul_u8: bit-packed for , byte table lookups for , flint_sgemm above |
| , , one thread | nmod_mat_mul_nmod_vec |
| at most 4 rows in (8 with AVX512-IFMA), , a vector code path for (on x86-64 with AVX2 every has one), not aliased, and not too narrow for it (for instance, the path of , of with IFMA, and of without FMA, needs , or and for ; the next case is preferred for 2 columns and ) | the rows of are streamed once for all rows of , as in nmod_mat_nmod_vec_mul |
| 2 to 4 columns in (8 with AVX512-IFMA), one thread, a vector code path for , not aliased | each row of is multiplied by all columns of at once |
| , and (or and ) | a SIMD kernel, see below, with Strassen–Winograd on top for FLINT_NMOD_MAT_MUL_SIMD_STRASSEN_CUTOFF and one thread |
| and above a cutoff | nmod_mat_mul_blas |
| several threads | nmod_mat_mul_classical_threaded |
| () or | nmod_mat_mul_classical |
| otherwise | nmod_mat_mul_strassen |
The SIMD kernel depends on the bit size of :
- :
nmod_mat_mul_u32, ornmod_mat_mul_fp50for a tiny inner dimension (by default with AVX2 and otherwise), ornmod_mat_mul_blaswhen a single floating-point product is exact () and is at leastFLINT_NMOD_MAT_MUL_BLAS_1PASS_CUTOFF(256 by default on x86-64, between 128 and 800 on the tuned x86-64 targets, and 0, meaning never, on Zen 4, Tiger Lake, Sapphire Rapids and AArch64). - :
nmod_mat_mul_fp50on x86-64,nmod_mat_mul_k52on AArch64. - :
nmod_mat_mul_k52. - With AVX512-IFMA,
nmod_mat_mul_u52replaces these for (and on some processors for ) when , and for whatever . - With an external BLAS,
nmod_mat_mul_blasreplacesnmod_mat_mul_k52andnmod_mat_mul_fp50from dimensionFLINT_NMOD_MAT_MUL_K52_BLAS_CUTOFFon, where this parameter is nonzero (384 on Apple M processors, 512 on Broadwell, 3000 on Skylake).
With several threads, the streaming of for few rows in (third case of the table) is used for the shapes that the threaded SIMD kernels below do not take, and for all the shapes of that case for , which has no SIMD kernel (the classical algorithm is then serial for so few rows).
For and large matrices, nmod_mat_mul_blas reduces the product to several floating-point products and a Chinese remainder reconstruction. It is used from with one thread, where it competes with Strassen–Winograd, and from with threads. Below, the classical algorithm forms each entry as a dot product with a single reduction (Dot products modulo a word-size integer), and Strassen–Winograd is used from .
The cutoffs are the FLINT_NMOD_MAT_MUL_* parameters of the flint-mparam.h file selected for the build machine, measured with src/nmod_mat/profile/p-mul_tune.c. On 32-bit machines only nmod_mat_mul_u8 (without bit packing), the classical algorithm and Strassen–Winograd are available.
Adding a product#
nmod_mat_addmul and nmod_mat_submul compute . They compute with nmod_mat_mul into a temporary matrix and add it, except with one thread, a dimension below 400 () or 200, and a modulus for which nmod_mat_mul has no faster kernel than the classical algorithm. In that case they use the classical algorithm and add each dot product to the entry of before the final reduction.
On 64-bit machines the kernels of nmod_mat_mul are preferred for , and for when the AVX512-IFMA or the floating-point kernels are available (on 32-bit machines only for ). The fused classical branch is kept for small products (fewer than 4 columns of , or ), for which the temporary matrix and the extra pass cost as much as they save, and for when and are at least 8 but one of them is below 16, where nmod_mat_mul_u8 does not pay for its conversions.
Examples#
Products of and matrices for moduli of several sizes, which go to different methods, checked against the classical algorithm (64-bit only, since some moduli exceed ):
#include <flint/flint.h>
#include <flint/nmod.h>
#include <flint/nmod_mat.h>
/* a simple deterministic pseudo-random generator (64-bit) */
static ulong next(ulong * x)
{
*x = *x * UWORD(6364136223846793005) + UWORD(1442695040888963407);
return *x >> 1;
}
int main(void)
{
/* moduli of 7, 31, 45, 52 and 64 bits */
ulong ns[5] = { 101, 2147483647, UWORD(35184372088777),
UWORD(4503599627370449), UWORD(18446744073709551557) };
slong m = 120, k = 90, n = 110, i, j, t;
ulong x = 1;
for (t = 0; t < 5; t++)
{
nmod_mat_t A, B, C, D;
nmod_mat_init(A, m, k, ns[t]);
nmod_mat_init(B, k, n, ns[t]);
nmod_mat_init(C, m, n, ns[t]);
nmod_mat_init(D, m, n, ns[t]);
for (i = 0; i < m; i++)
for (j = 0; j < k; j++)
nmod_mat_entry(A, i, j) = next(&x) % ns[t];
for (i = 0; i < k; i++)
for (j = 0; j < n; j++)
nmod_mat_entry(B, i, j) = next(&x) % ns[t];
nmod_mat_mul(C, A, B); /* chooses the method */
nmod_mat_mul_classical(D, A, B); /* reference */
flint_printf("%2wu bits: C[5][7] = %wu, agrees with classical: %d\n",
NMOD_BITS(nmod_mat_mod(C)), nmod_mat_entry(C, 5, 7),
nmod_mat_equal(C, D));
nmod_mat_clear(A);
nmod_mat_clear(B);
nmod_mat_clear(C);
nmod_mat_clear(D);
}
return 0;
}7 bits: C[5][7] = 90, agrees with classical: 1 31 bits: C[5][7] = 696258119, agrees with classical: 1 45 bits: C[5][7] = 21009516881923, agrees with classical: 1 52 bits: C[5][7] = 3687877138422982, agrees with classical: 1 64 bits: C[5][7] = 12025067871927492558, agrees with classical: 1
Updating a matrix in place with nmod_mat_submul and nmod_mat_addmul, and squaring in place with nmod_mat_mul:
#include <flint/flint.h>
#include <flint/nmod_mat.h>
int main(void)
{
nmod_mat_t A, B, S;
slong i, j;
nmod_mat_init(A, 3, 3, 97);
nmod_mat_init(B, 3, 3, 97);
nmod_mat_init(S, 3, 3, 97);
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
{
nmod_mat_entry(A, i, j) = (i + 2 * j + 1) % 97;
nmod_mat_entry(B, i, j) = (i == j) ? 2 : 1;
}
/* S = A*B, then S = S - A*B in place: the result is zero */
nmod_mat_mul(S, A, B);
nmod_mat_submul(S, S, A, B);
flint_printf("A*B - A*B is zero: %d\n", nmod_mat_is_zero(S));
/* S = B + A*A, then square A in place */
nmod_mat_addmul(S, B, A, A);
nmod_mat_mul(A, A, A);
nmod_mat_add(A, A, B);
flint_printf("addmul agrees: %d\n", nmod_mat_equal(S, A));
nmod_mat_print_pretty(S);
nmod_mat_clear(A);
nmod_mat_clear(B);
nmod_mat_clear(S);
return 0;
}A*B - A*B is zero: 1 addmul agrees: 1 <3 x 3 integer matrix mod 97> [24 41 59] [29 54 77] [35 65 96]
Guides#
The FLINT team, “Multiplying matrices (nmod_mat.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/nmod_mat/mul.html (generated 2026-11-02).
@misc{flint-doc-nmod-mat-mul,
author = {{The FLINT team}},
title = {{Multiplying matrices (nmod\_mat.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/nmod_mat/mul.html},
note = {Generated 2026-11-02}
}