FLINT3.7.0-dev

Computing an LU decomposition

Synopsis#include <flint/nmod_mat.h>
slong nmod_mat_lu(slong * P, nmod_mat_t A, int rank_check)source
slong nmod_mat_lu_classical(slong * P, nmod_mat_t A, int rank_check)source
slong nmod_mat_lu_classical_delayed(slong * P, nmod_mat_t A, int rank_check)source
slong nmod_mat_lu_recursive(slong * P, nmod_mat_t A, int rank_check)source

Computes an LU decomposition with row pivoting of the m×nm \times n matrix AA modulo a prime, overwriting AA with LL and UU, and returns the rank rr. On return, row ii of LULU is row P[i] of the original AA, where P is an array of length mm (it need not be initialised). UU is in row echelon form with rr nonzero rows, and LL is m×rm \times r unit lower triangular. If rank_check is nonzero, the function may stop as soon as it finds that the rank is less than nn, and then returns 0 and leaves AA undefined.

All four functions compute the same output. nmod_mat_lu chooses the algorithm: nmod_mat_lu_classical for min⁡(m,n)≤3\min(m, n) \le 3 (or ≤7\le 7 when n≥228n \ge 2^{28}), and otherwise nmod_mat_lu_classical_delayed below a crossover that depends on the bit size of nn and nmod_mat_lu_recursive above it. _classical is Gaussian elimination with a reduction after each update, _classical_delayed accumulates the updates of each entry in one to three words and reduces an entry only when it is about to be used as a pivot or multiplier, and _recursive splits the columns in half and reduces the work to triangular solving and matrix multiplication. See Computing an LU decomposition for the storage format.

slong nmod_mat_lu_with_pivots(slong * P, slong * pivots_nonpivots, nmod_mat_t A)source

The same as nmod_mat_lu with rank_check 0, and in addition writes to pivots_nonpivots, an array of length nn, the indices of the rr pivot columns of UU in increasing order followed by the indices of the n−rn - r other columns in increasing order.

An LU decomposition with row pivoting writes an m×nm \times n matrix AA over a field Z/pZ\mathbb{Z}/p\mathbb{Z} as a row permutation of a product LULU, with LL lower triangular and UU in row echelon form. It is the basis of nmod_mat_solve, nmod_mat_inv, nmod_mat_det, nmod_mat_rank, nmod_mat_rref and nmod_mat_nullspace, and it is useful directly when several systems with the same matrix must be solved. The functions overwrite AA with both factors and return the rank.

AlgorithmGaussian elimination and LU decompositionLU decomposition with row pivoting over Z/pZ\mathbb{Z}/p\mathbb{Z} in its classical, delayed-reduction and recursive forms, and how FLINT derives triangular solving, linear system solving, inverses, determinants, ranks, echelon forms and nullspaces from it; plus division-free determinants and the Howell form over Z/nZ\mathbb{Z}/n\mathbb{Z}.

Output#

Let rr be the returned rank. On return:

  • P[i] is the index of the row of the original AA that ended up in row ii. P must have room for mm entries and need not be initialised.
  • UU is the r×nr \times n matrix formed by the first rr rows of the output, keeping the entries in columns j≥ij \ge i of row ii and replacing those in columns j<ij < i by zero. It is in row echelon form: the pivot (first nonzero entry) of row ii is in column cic_i, with c0<c1<⋯<cr−1c_0 < c_1 < \cdots < c_{r-1}.
  • LL is the m×rm \times r unit lower triangular matrix whose entry (i,j)(i, j) for j<min⁡(i,r)j < \min(i, r) is entry (i,j)(i, j) of the output; its diagonal entries are 1 and are not stored. The rows i≥ri \ge r of the output contain LL in their first rr columns and zeros elsewhere.

Then row ii of LULU equals row P[i] of the original AA. In matrix terms, A=ΠLUA = \Pi L U where Π\Pi is the permutation matrix with a 1 in row P[i] and column ii. nmod_mat_permute_rows(A, P, NULL) applies the same permutation to another copy of AA, as in the example below.

The pivots are chosen column by column: the pivot of column cc is the first row, among those not yet used, with a nonzero entry in column cc after elimination. The output therefore does not depend on the algorithm, and c0,…,cr−1c_0, \ldots, c_{r-1} is the column rank profile of AA, the lexicographically smallest set of rr linearly independent columns. nmod_mat_lu_with_pivots returns these indices, followed by the indices of the other columns.

Checking the rank#

With a nonzero rank_check, the decomposition stops as soon as a column without a pivot is found, and the function returns 0, leaving AA in an undefined state. This is what nmod_mat_solve and nmod_mat_inv use to reject singular matrices early. If m≥nm \ge n, the return value is then 0 exactly when the rank is less than nn. If m<nm < n the result depends on the algorithm: for example, nmod_mat_lu_recursive returns 0 for the 2×62 \times 6 matrix (I2∣0)(I_2 \mid 0), which has full rank 2. Use rank_check only with m≥nm \ge n.

Moduli#

The algorithms divide by pivots, so the modulus should be prime. For a composite modulus the functions still return a decomposition if every pivot they meet happens to be invertible, but they raise a FLINT_IMPINV exception when they meet a nonzero pivot that is not, and the returned "rank" has no meaning. Use nmod_mat_howell_form or nmod_mat_det for composite moduli.

Algorithms#

nmod_mat_lu_classical is Gaussian elimination, with one reduction for each update of an entry. nmod_mat_lu_classical_delayed performs the same operations but accumulates the updates of each entry of the active submatrix unreduced in one, two or three words, as in a dot product, and reduces an entry only when its column becomes the pivot column or its row the pivot row. nmod_mat_lu_recursive splits the columns into halves of ⌊n/2⌋\lfloor n/2 \rfloor and ⌈n/2⌉\lceil n/2 \rceil, decomposes the left half recursively, updates the right half with a triangular solve (nmod_mat_solve_tril) and a matrix product (nmod_mat_submul), and decomposes the remaining block recursively. Its cost is dominated by matrix multiplication, O(mnrω−2)O(m n r^{\omega - 2}) operations with the exponent ω\omega of the multiplication algorithm used.

nmod_mat_lu uses the classical algorithm when min⁡(m,n)≤3\min(m, n) \le 3, or ≤7\le 7 for p≥228p \ge 2^{28}. Otherwise it uses the delayed algorithm when min⁡(m,n)\min(m, n) is below a crossover that depends on the bit size bb of pp, and the recursive algorithm above it. The recursive algorithm calls nmod_mat_lu for the halves, so the crossover also sets its base case. The values were tuned on an AMD Zen 3 processor:

bbcrossover
1–264
3–7244–332
8–11792–800
12–24400–424
25, 26, 27, 28484, 1352, 1032, 260
2968
30–3256
33–60112–168
61, 6268, 64
63–64104

The details, with proofs and complexity bounds, are in Gaussian elimination and LU decomposition.

Example#

The decomposition of a 4×54 \times 5 matrix of rank 3 modulo 17, and a check that LULU is AA with its rows permuted by P:

Examplenmod_mat_lu_unpack.cchecked by make checkdoc
#include <flint/flint.h>
#include <flint/nmod_mat.h>

int main(void)
{
    /* a 4 x 5 matrix of rank 3 modulo 17: row 3 = row 0 + row 1 */
    ulong a[4][5] = { { 0, 0, 3, 1, 4 },
                      { 2, 4, 1, 0, 5 },
                      { 1, 2, 7, 7, 1 },
                      { 2, 4, 4, 1, 9 } };
    nmod_mat_t A, LU, L, U, T;
    slong P[4], piv[5], i, j, r;

    nmod_mat_init(A, 4, 5, 17);
    for (i = 0; i < 4; i++)
        for (j = 0; j < 5; j++)
            nmod_mat_entry(A, i, j) = a[i][j];

    nmod_mat_init_set(LU, A);
    r = nmod_mat_lu_with_pivots(P, piv, LU);

    flint_printf("rank %wd, P = [%wd %wd %wd %wd]\n", r, P[0], P[1], P[2], P[3]);
    flint_printf("pivot columns %wd %wd %wd, others %wd %wd\n",
                 piv[0], piv[1], piv[2], piv[3], piv[4]);
    nmod_mat_print_pretty(LU);

    /* unpack L (4 x r, unit diagonal) and U (r x 5) */
    nmod_mat_init(L, 4, r, 17);
    nmod_mat_init(U, r, 5, 17);
    for (i = 0; i < 4; i++)
        for (j = 0; j < r; j++)
            nmod_mat_entry(L, i, j) = (i == j) ? 1 :
                                      (j < i) ? nmod_mat_entry(LU, i, j) : 0;
    for (i = 0; i < r; i++)
        for (j = i; j < 5; j++)
            nmod_mat_entry(U, i, j) = nmod_mat_entry(LU, i, j);

    /* row i of L*U is row P[i] of A */
    nmod_mat_init(T, 4, 5, 17);
    nmod_mat_mul(T, L, U);
    nmod_mat_permute_rows(A, P, NULL);
    flint_printf("L*U == rows of A permuted by P: %d\n", nmod_mat_equal(T, A));

    nmod_mat_clear(A);
    nmod_mat_clear(LU);
    nmod_mat_clear(L);
    nmod_mat_clear(U);
    nmod_mat_clear(T);
    return 0;
}
Output
rank 3, P = [1 0 2 3]
pivot columns 0 2 3, others 1 4
<4 x 5 integer matrix mod 17>
[ 2  4  1  0  5]
[ 0  0  3  1  4]
[ 9  5  0  2  4]
[ 1  1  0  0  0]
L*U == rows of A permuted by P: 1

Row 0 of AA has a zero in column 0, so rows 0 and 1 are swapped. Column 1 has no pivot because it is twice column 0. In row 2 of the output, the entries 9 and 5 belong to LL and the entries 0,2,40, 2, 4 in columns 2 to 4 to UU, whose pivot in this row is the 2 in column 3. Row 3 lies in the span of the rows above, so it holds only its row of LL, which says that it is the sum of the first two rows of UU.

Guides#

Cite this page

The FLINT team, “Computing an LU decomposition (nmod_mat.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/nmod_mat/lu.html (generated 2026-11-02).