Computing an LU decomposition
#include <flint/nmod_mat.h>slong nmod_mat_lu(slong * P, nmod_mat_t A, int rank_check)sourceslong nmod_mat_lu_classical(slong * P, nmod_mat_t A, int rank_check)sourceslong nmod_mat_lu_classical_delayed(slong * P, nmod_mat_t A, int rank_check)sourceslong nmod_mat_lu_recursive(slong * P, nmod_mat_t A, int rank_check)sourceComputes an LU decomposition with row pivoting of the matrix modulo a prime, overwriting with and , and returns the rank . On return, row of is row P[i] of the original , where P is an array of length (it need not be initialised). is in row echelon form with nonzero rows, and is unit lower triangular. If rank_check is nonzero, the function may stop as soon as it finds that the rank is less than , and then returns 0 and leaves undefined.
All four functions compute the same output. nmod_mat_lu chooses the algorithm: nmod_mat_lu_classical for (or when ), and otherwise nmod_mat_lu_classical_delayed below a crossover that depends on the bit size of 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)sourceThe same as nmod_mat_lu with rank_check 0, and in addition writes to pivots_nonpivots, an array of length , the indices of the pivot columns of in increasing order followed by the indices of the other columns in increasing order.
An LU decomposition with row pivoting writes an matrix over a field as a row permutation of a product , with lower triangular and 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 with both factors and return the rank.
Output#
Let be the returned rank. On return:
P[i]is the index of the row of the original that ended up in row .Pmust have room for entries and need not be initialised.- is the matrix formed by the first rows of the output, keeping the entries in columns of row and replacing those in columns by zero. It is in row echelon form: the pivot (first nonzero entry) of row is in column , with .
- is the unit lower triangular matrix whose entry for is entry of the output; its diagonal entries are 1 and are not stored. The rows of the output contain in their first columns and zeros elsewhere.
Then row of equals row P[i] of the original . In matrix terms, where is the permutation matrix with a 1 in row P[i] and column . nmod_mat_permute_rows(A, P, NULL) applies the same permutation to another copy of , as in the example below.
The pivots are chosen column by column: the pivot of column is the first row, among those not yet used, with a nonzero entry in column after elimination. The output therefore does not depend on the algorithm, and is the column rank profile of , the lexicographically smallest set of 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 in an undefined state. This is what nmod_mat_solve and nmod_mat_inv use to reject singular matrices early. If , the return value is then 0 exactly when the rank is less than . If the result depends on the algorithm: for example, nmod_mat_lu_recursive returns 0 for the matrix , which has full rank 2. Use rank_check only with .
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 and , 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, operations with the exponent of the multiplication algorithm used.
nmod_mat_lu uses the classical algorithm when , or for . Otherwise it uses the delayed algorithm when is below a crossover that depends on the bit size of , 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:
| crossover | |
|---|---|
| 1–2 | 64 |
| 3–7 | 244–332 |
| 8–11 | 792–800 |
| 12–24 | 400–424 |
| 25, 26, 27, 28 | 484, 1352, 1032, 260 |
| 29 | 68 |
| 30–32 | 56 |
| 33–60 | 112–168 |
| 61, 62 | 68, 64 |
| 63–64 | 104 |
The details, with proofs and complexity bounds, are in Gaussian elimination and LU decomposition.
Example#
The decomposition of a matrix of rank 3 modulo 17, and a check that is with its rows permuted by P:
#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;
}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 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 and the entries in columns 2 to 4 to , 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 , which says that it is the sum of the first two rows of .
Guides#
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).
@misc{flint-doc-nmod-mat-lu,
author = {{The FLINT team}},
title = {{Computing an LU decomposition (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/lu.html},
note = {Generated 2026-11-02}
}