FLINT3.7.0-dev

Multiplying matrices over generic rings

Synopsis#include <flint/gr_mat.h>
int gr_mat_mul(gr_mat_t res, const gr_mat_t mat1, const gr_mat_t mat2, gr_ctx_t ctx)source
int gr_mat_mul_generic(gr_mat_t C, const gr_mat_t A, const gr_mat_t B, gr_ctx_t ctx)source

Set res to the product AB. gr_mat_mul calls the ring method GR_METHOD_MAT_MUL; the default gr_mat_mul_generic is classical multiplication. Most rings with a dedicated matrix module override the method. Returns GR_DOMAIN if the shapes are incompatible: gr_mat_mul itself does not check them, so every ring method must (all methods in FLINT do). See Multiplying matrices over generic rings.

int gr_mat_mul_classical(gr_mat_t res, const gr_mat_t mat1, const gr_mat_t mat2, gr_ctx_t ctx)source

Computes the m \times n by n \times p product with the classical algorithm, as mp dot products of length n (_gr_vec_dot), with the right factor transposed shallowly so that its columns are contiguous.

int gr_mat_mul_strassen(gr_mat_t C, const gr_mat_t A, const gr_mat_t B, gr_ctx_t ctx)source

One level of Strassen's algorithm [Str1969] with the 7-multiplication, 15-addition sequence of Winograd [Win1971] in the order given by Bodrato [Bodrato2010], which saves further operations when squaring. The seven products of half size are computed with gr_mat_mul, so the recursion continues only if the ring method dispatches back to this function above a cutoff; then the complexity is O(n^{\log_2 7}) = O(n^{2.81}). See Strassen–Winograd multiplication.

int gr_mat_mul_waksman(gr_mat_t C, const gr_mat_t A, const gr_mat_t B, gr_ctx_t ctx)source
int gr_mat_mul_rosowski(gr_mat_t C, const gr_mat_t A, const gr_mat_t B, gr_ctx_t ctx)source

Compute AB with about half the multiplications of the classical algorithm over a commutative ring, at the cost of more additions, which pays off when multiplications are much more expensive than additions (large integers, polynomials). Waksman's algorithm [Waksman1970] (refining Winograd's inner product trick [Win1968]) needs exact division by 2, implemented as gr_mul_2exp_si by -1. Rosowski's algorithm [Rosowski2023] needs no division and uses fewer multiplications than Waksman's when the inner dimension is odd.

gr_mat_mul multiplies two matrices over any ring by calling the ring's matrix multiplication method. The other functions below implement one algorithm each: the classical algorithm with dot products, one level of Strassen-Winograd, and the commutative algorithms of Waksman and Rosowski, which trade multiplications for additions. Since block LU decomposition, triangular solving, rank, nullspace, inverse and characteristic polynomial algorithms all call gr_mat_mul, the method chosen here determines their speed as well.

OverviewLinear algebra over generic rings and its specialisationsHow the generic matrix module gr_mat organises multiplication, LU and fraction-free LU decomposition, solving, determinants, ranks, echelon forms, characteristic polynomials and eigenvalues over any ring, how it handles rings that are not fields and inexact rings, and how nmod_mat, fmpz_mat, mpn_mod, arb_mat, acb_mat and fq_mat specialise or reuse these algorithms.

Algorithm selection#

The default method gr_mat_mul_generic is classical multiplication, which is all that can be assumed of an arbitrary ring. The rings below override the method GR_METHOD_MAT_MUL:

RingMethod
nmodnmod_mat_mul (Multiplying matrices over Z/nZ)
nmod8nmod_mat_mul_u8 from dimension 8, classical below
nmod32the integer SIMD kernel of nmod_mat from dimension 8, with one level of Strassen-Winograd on top of it (single-threaded) from an architecture-dependent dimension; without the kernel, Strassen-Winograd from dimension 256 and classical below
fmpz_modfmpz_mat_mul followed by a reduction of the entries
mpn_modclassical, Waksman, then multimodular reduction to nmod_mat products, with cutoffs tabulated by modulus size
fmpz, fmpqfmpz_mat_mul (Multiplying matrices over the integers), fmpq_mat_mul
fq, fq_nmod, fq_zechfq_mat_mul and its variants (Kronecker substitution into a matrix of polynomials or integers)
arb, acbarb_mat_mul, acb_mat_mul (block algorithm with exact integer matrix products and error bounds, Ball matrix multiplication)
arf, acfclassical below dimension 40 to 120 (depending on the precision), the ball algorithm above
nfloat, nfloat_complexclassical, fixed-point and block algorithms tuned by precision
caca_mat_mul

Rings without an override, such as polynomial rings, number fields and qqbar, use the classical algorithm. To use Strassen or Waksman multiplication recursively for such a ring, its context must be given a method that calls gr_mat_mul_strassen (or the other algorithm) above a cutoff, since these functions perform one level of recursion and compute their sub-products with gr_mat_mul.

The algorithms#

gr_mat_mul_classical computes each of the mp entries of an m \times n by n \times p product as one dot product of length n with _gr_vec_dot, after transposing the right factor shallowly (GR_MAT_TMP_INIT_SHALLOW_TRANSPOSE) so that its columns are contiguous. Rings with fast dot products (delayed modular reduction, exact accumulation of integer products, a single rounding for floating point) therefore make the classical algorithm fast as well.

gr_mat_mul_strassen splits each factor into 2 \times 2 blocks and computes the product with 7 block products and 15 block additions in Bodrato's ordering of Winograd's variant [Win1971; Bodrato2010]; odd dimensions are handled by peeling off the last row or column. One level saves an eighth of the multiplications; a full recursion costs O(n^{\log_2 7}) operations [Str1969]. See Strassen–Winograd multiplication.

gr_mat_mul_waksman [Waksman1970] computes \sum_k a_{ik} b_{kj} in pairs, as \sum_k (a_{i,2k} + b_{2k+1,j})(a_{i,2k+1} + b_{2k,j}) minus correction terms that depend only on i or only on j, in the style of Winograd's inner product algorithm [Win1968]. This uses about mnp/2 multiplications instead of mnp, but more additions and a division by 2 (gr_mul_2exp_si); it requires a commutative ring. It pays off when multiplications are much more expensive than additions: integers and polynomials with large entries, multi-word modular integers (as in mpn_mod). gr_mat_mul_rosowski [Rosowski2023] achieves the same saving without division by 2, and saves more when the inner dimension n is odd.

All these functions return GR_DOMAIN when the shapes do not match. gr_mat_mul passes its arguments to the ring method without checking them, so the check is part of the contract of every GR_METHOD_MAT_MUL implementation; the ring-specific methods in FLINT make it before calling the type's own multiplication. (In FLINT 3.6 and earlier most of them did not, and incompatible shapes gave wrong results or corrupted memory.)

Example#

All algorithms give the same product. The program multiplies random integer matrices of shapes 30 \times 40 and 40 \times 35 with the ring method (here fmpz_mat_mul) and with each generic algorithm.

Examplegr_mat_mul_algorithms.cchecked by make checkdoc
#include <flint/flint.h>
#include <flint/gr.h>
#include <flint/gr_mat.h>

int main(void)
{
    gr_ctx_t ZZ;
    gr_mat_t A, B, C, D;
    flint_rand_t state;

    flint_rand_init(state);
    gr_ctx_init_fmpz(ZZ);
    gr_mat_init(A, 30, 40, ZZ);
    gr_mat_init(B, 40, 35, ZZ);
    gr_mat_init(C, 30, 35, ZZ);
    gr_mat_init(D, 30, 35, ZZ);

    GR_MUST_SUCCEED(gr_mat_randtest(A, state, ZZ));
    GR_MUST_SUCCEED(gr_mat_randtest(B, state, ZZ));

    /* the ring's method: fmpz_mat_mul */
    GR_MUST_SUCCEED(gr_mat_mul(C, A, B, ZZ));

    GR_MUST_SUCCEED(gr_mat_mul_classical(D, A, B, ZZ));
    flint_printf("classical: %d\n", gr_mat_equal(C, D, ZZ) == T_TRUE);
    GR_MUST_SUCCEED(gr_mat_mul_strassen(D, A, B, ZZ));
    flint_printf("strassen:  %d\n", gr_mat_equal(C, D, ZZ) == T_TRUE);
    GR_MUST_SUCCEED(gr_mat_mul_waksman(D, A, B, ZZ));
    flint_printf("waksman:   %d\n", gr_mat_equal(C, D, ZZ) == T_TRUE);
    GR_MUST_SUCCEED(gr_mat_mul_rosowski(D, A, B, ZZ));
    flint_printf("rosowski:  %d\n", gr_mat_equal(C, D, ZZ) == T_TRUE);

    /* the generic algorithms check the shapes */
    flint_printf("A*A:       status %d\n", gr_mat_mul_classical(D, A, A, ZZ));

    gr_mat_clear(A, ZZ);
    gr_mat_clear(B, ZZ);
    gr_mat_clear(C, ZZ);
    gr_mat_clear(D, ZZ);
    gr_ctx_clear(ZZ);
    flint_rand_clear(state);
    return 0;
}
Output
classical: 1
strassen:  1
waksman:   1
rosowski:  1
A*A:       status 1

References#

  • [Str1969] V. Strassen, "Gaussian elimination is not optimal", Numerische Mathematik 13 (1969), 354-356, https://doi.org/10.1007/BF02165411
  • [Win1971] S. Winograd, "On multiplication of 2 × 2 matrices", Linear Algebra and its Applications 4 (1971), 381-388, https://doi.org/10.1016/0024-3795(71)90009-7
  • [Bodrato2010] Bodrato, Marco : A Strassen-like Matrix Multiplication Suited for Squaring and Higher Power Computation. Proceedings of the ISSAC 2010 München, Germany, 25-28 July, 2010
  • [Waksman1970] Waksman, Abraham, "On Winograd's algorithm for inner products", IEEE Transactions on Computers 19.04 (1970): 360-361.
  • [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
  • [Rosowski2023] Rosowski, Andreas. "Fast commutative matrix algorithms". Journal of Symbolic Computation 114 (2023): 302–321.
Cite this page

The FLINT team, “Multiplying matrices over generic rings (gr_mat.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/gr_mat/mul.html (generated 2026-10-20).