Computing a PLE decomposition revealing the rank profile matrix
#include <flint/nmod_mat.h>slong nmod_mat_ple(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)sourceslong nmod_mat_ple_classical(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)sourceslong nmod_mat_ple_classical_delayed(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)sourceslong nmod_mat_ple_crout(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)sourceslong nmod_mat_ple_recursive(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)sourceComputes the PLE decomposition of the matrix modulo a prime that reveals its rank profile matrix, overwrites with and in the storage of nmod_mat_lu, and returns the rank . Row of is row P[i] of the original (P has length ). The array pivots_nonpivots, of length , receives the pivot columns of followed by the other columns, both in increasing order; it may be NULL. The rows P[r], ..., P[m-1] are also in increasing order, and the rank profile matrix of has its nonzero entries at (P[k], pivots_nonpivots[k]) for . If flags contains NMOD_MAT_PLE_RANK_CHECK and the rank is less than , the function returns 0, possibly before the end, and leaves A, P and pivots_nonpivots undefined.
These conditions determine the output, so all the algorithms return the same; see Computing a PLE decomposition revealing the rank profile matrix. nmod_mat_ple_classical is Gaussian elimination with a reduction after each update, and nmod_mat_ple_classical_delayed accumulates the updates of each entry unreduced in one to three words, as nmod_mat_lu_classical_delayed. nmod_mat_ple_crout computes each entry when it is needed, with dot products and vector-matrix products that delay their reductions. nmod_mat_ple_recursive splits the columns in half and reduces the work to triangular solving and matrix multiplication, as nmod_mat_lu_recursive. nmod_mat_ple chooses the algorithm.
void nmod_mat_ple_nullspace(nmod_mat_t X, const nmod_mat_t LU, const slong * pivots_nonpivots, slong rank)sourcevoid nmod_mat_ple_left_nullspace(nmod_mat_t X, const nmod_mat_t LU, const slong * P, slong rank)sourceGiven the output LU, P, pivots_nonpivots and rank of nmod_mat_ple for an matrix , sets the matrix to the basis of the right nullspace computed by nmod_mat_nullspace, respectively the matrix to the basis of the left nullspace computed by nmod_mat_left_nullspace. must have exactly these dimensions.
Let be the pivot columns and the other columns, as in pivots_nonpivots. Vector of the right nullspace has a 1 in position , zeros at the other , and entry of in position , where and are the columns and of ; this costs a triangular solve, about operations. Vector of the left nullspace has a 1 in position P[r+k], zeros at the other P[r+j], and entry of in position P[j], where and are the first and the last rows of ; this costs a triangular solve on the right, about operations. These are the only bases with an identity matrix in these positions: the columns for the right nullspace, and for the left nullspace the rows that are not in the row rank profile, in increasing order.
void nmod_mat_ple_nullspace_compact(nmod_mat_t X, const nmod_mat_t LU, const slong * pivots_nonpivots, slong rank)sourcevoid nmod_mat_ple_left_nullspace_compact(nmod_mat_t X, slong * rows, const nmod_mat_t LU, const slong * P, slong rank)sourceThe same bases without their identity matrix. For the right nullspace, is and receives : its row gives the entries in position pivots_nonpivots[j] and its column corresponds to the vector with a 1 in position pivots_nonpivots[r+k]. For the left nullspace, is , rows (length ) receives the row rank profile of in increasing order followed by the other rows in increasing order, and receives the columns of sorted accordingly: its column gives the entries in position rows[j] and its row corresponds to the vector with a 1 in position rows[r+k].
NMOD_MAT_PLE_RANK_CHECKsourceFlag for nmod_mat_ple: return 0 as soon as the rank is known to be less than .
nmod_mat_ple computes an LU decomposition with row pivoting, in the format of nmod_mat_lu, that reveals the rank profile matrix of : from its row permutation and pivot columns one reads the first linearly independent rows and columns of , and those of every leading submatrix of . Its output is unique, so it does not depend on the algorithm or on the machine. It has the same pivot columns as nmod_mat_lu, but the rows come in a different order, and therefore the factors and may differ too.
Rank profile matrix#
Let be an matrix of rank over . Its row rank profile is the lexicographically smallest set of linearly independent rows, that is, the rows that are not linear combinations of the rows above them; the column rank profile is defined in the same way. The rank profile matrix is the unique matrix with entries equal to 1, no two in the same row or column, and zeros elsewhere, such that for all and the submatrices formed by the first rows and columns of and of have the same rank [DPS2017]. Its rows and columns with a nonzero entry are the row and column rank profiles of , and the same holds for the leading submatrices of with the entries of that lie in them.
Output#
Let be the returned rank. On return, as for nmod_mat_lu (Output):
- row of is row
P[i]of the original , where is the unit lower triangular matrix stored below the diagonal and the matrix in row echelon form stored on and above it, whose pivot in row is in column , with ; - the rows of the output contain in their first columns and zeros elsewhere;
- if
pivots_nonpivotsis notNULL, it receives followed by the indices of the other columns in increasing order.
In addition, the rows without pivot are in increasing order, P[r] P[r+1] P[m-1], and the nonzero entries of are at the positions (P[k], ) for . In particular is the column rank profile of and P[0], ..., P[r-1] sorted is its row rank profile.
These conditions determine P and the pivot columns, and then and , since an LU decomposition with unit is unique once the order of the rows and the pivot columns are fixed. All the algorithms therefore compute the same output.
nmod_mat_lu chooses the same pivot columns, but it brings each pivot row into place by a swap with the row in that position, which changes the order of the rows passed over: its pivot rows need not be the row rank profile, as in the example below. nmod_mat_ple uses an upward rotation instead: the pivot row moves up, and the rows passed over move down by one position each, keeping their order, so that the rows without pivot remain in increasing order of their indices in . The algorithms use this to avoid moving rows of the matrix too often. The classical algorithm keeps the rows in place in memory and records their order in P only, so that an upward rotation past rows costs operations on indices, and permutes the rows once at the end. The delayed and Crout algorithms swap the pivot row with the row in its position, as nmod_mat_lu does, so that the eliminations run through consecutive rows; the pivot of a column is then the row with a nonzero entry of smallest index in among the rows without pivot, and these rows are put back in increasing order at the end. The recursive algorithm puts the rows in order once for each half at each level of the recursion.
Kernels#
From , the right nullspace of is that of and the left nullspace is that of with the columns permuted by . Writing and for the pivot and the other columns of , and , for the first and the other rows of , the matrices and give the bases with an identity matrix in the columns without pivot and in the rows P[r], ..., P[m-1]; the latter are the rows of that are not in its row rank profile, since the decomposition reveals it. nmod_mat_ple_nullspace and nmod_mat_ple_left_nullspace compute these bases from the output of nmod_mat_ple with one triangular solve, and their _compact versions only the blocks and (with the columns of the latter sorted by row index). These are the bases computed by nmod_mat_nullspace and nmod_mat_left_nullspace, which use these functions.
Checking the rank#
With NMOD_MAT_PLE_RANK_CHECK in flags, the function returns the rank if it is , with the same output as without the flag, and returns 0 otherwise, for all shapes of ; this differs from the rank_check argument of nmod_mat_lu, which is only reliable for . The decomposition stops as soon as more than columns without pivot have been met, and then leaves A, P and pivots_nonpivots undefined.
Moduli#
The modulus should be prime. For a composite modulus the functions raise a FLINT_IMPINV exception when they meet a nonzero pivot that is not invertible, as nmod_mat_lu does.
Algorithms#
nmod_mat_ple_classical is Gaussian elimination column by column, with one reduction for each update of an entry; rows whose multiplier is zero are not updated. The pivot of a column is the first row, in the order recorded in P, among the rows that are not pivot rows yet, with a nonzero entry in that column. Why this pivoting reveals the rank profile matrix is explained in Rank profile matrix. For small matrices and moduli of at least 16 bits, where inverting each pivot costs about as much as updating the entries ( columns, rows up to 24 bits, up to 32 bits, at most 16 rows for ), it avoids these inversions: the eliminations replace a row by , where is the pivot row and its pivot, which multiplies all the rows that are not pivot rows yet by the same factor; one inversion at the end gives the inverses of these factors and of the pivots, and the scaled multipliers and pivot rows are normalized. The result is the same.
nmod_mat_ple_classical_delayed performs the same operations, but accumulates the updates of each entry unreduced, in the matrix itself if and otherwise in a buffer of two or three words per entry, and reduces an entry only when it is about to be used, as nmod_mat_lu_classical_delayed (Delayed reduction).
nmod_mat_ple_crout performs the same operations in the order of the Crout schedule (Crout elimination): the entries of a column in the rows without pivot are computed when the column is processed, by dot products of the multipliers of these rows with the entries of above them, and the new pivot row by a vector-matrix product of its multipliers with the rows of . These products delay their reductions, with SIMD code for all moduli, and no buffer is needed. They skip the zero multipliers before the first and after the last nonzero one of each row, but not the others.
nmod_mat_ple_recursive is the recursion of nmod_mat_lu_recursive: it decomposes the left half of the columns, puts the rows of the right half in the same order, updates them with a triangular solve and a matrix multiplication, and decomposes the remaining block, calling nmod_mat_ple for the two halves. Its cost is dominated by matrix multiplication.
nmod_mat_ple chooses the algorithm as follows, with ; the thresholds depend on the SIMD kernels compiled in, with one set of values for AVX512-IFMA (whose dot products and vector-matrix products make the Crout algorithm faster up to ) and one for the other targets.
- The classical algorithm when , or and the delayed algorithm would need two or three words per entry, except for with one word per entry, , and or (IFMA) or (other). For the other targets, also when , , , or , and or (the inversion-free variant below).
The recursive algorithm when and :
Modulus (IFMA) (other) 17 17 16 16 17 16 (14 without AVX2) 18 16 21 19 - For on x86-64 with AVX2 and , the classical algorithm.
The Crout algorithm when the delayed algorithm would need two or three words per entry,
nmod_mat_nmod_vec_mulhas a vector code path for (not for on NEON), and or ( and ):Modulus (IFMA) (other) (32, 16) (64, 256), or (128, 256) without AVX2 (32, 16) (128, 64) (32, 16) (128, 128) (64, 128) (128, 128) (64, 128) (64, 128) - Otherwise the delayed algorithm.
These choices were tuned with uniformly random matrices on Zen 4 and Ice Lake SP (AVX512-IFMA), Cascade Lake (AVX-512 without IFMA) and Apple M4 (NEON), and may not be the best on other machines (in particular with AVX2 but not AVX-512). On matrices with many zero multipliers, the classical and delayed algorithms, which skip all of them, can be faster than the others beyond these crossovers.
Example#
The decomposition of a matrix of rank 3 modulo 17 whose first two rows are proportional, compared with nmod_mat_lu:
#include <flint/flint.h>
#include <flint/nmod_mat.h>
int main(void)
{
/* rows 0 and 1 are proportional; row 2 has the only nonzero entry
of column 0 */
ulong a[4][5] = { { 0, 3, 1, 0, 2 },
{ 0, 6, 2, 0, 4 },
{ 5, 1, 0, 2, 3 },
{ 0, 0, 0, 4, 1 } };
nmod_mat_t A, LU;
slong P[4], Q[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_ple(P, Q, LU, 0);
flint_printf("rank %wd, P = [%wd %wd %wd %wd]\n", r, P[0], P[1], P[2], P[3]);
flint_printf("pivots_nonpivots = [%wd %wd %wd %wd %wd]\n",
Q[0], Q[1], Q[2], Q[3], Q[4]);
nmod_mat_print_pretty(LU);
flint_printf("rank profile matrix:");
for (i = 0; i < r; i++)
flint_printf(" (%wd, %wd)", P[i], Q[i]);
flint_printf("\n");
/* nmod_mat_lu swaps rows instead and selects row 1, not row 0 */
nmod_mat_set(LU, A);
r = nmod_mat_lu(P, LU, 0);
flint_printf("nmod_mat_lu: P = [%wd %wd %wd %wd]\n", P[0], P[1], P[2], P[3]);
nmod_mat_clear(A);
nmod_mat_clear(LU);
return 0;
}rank 3, P = [2 0 3 1] pivots_nonpivots = [0 1 3 2 4] <4 x 5 integer matrix mod 17> [ 5 1 0 2 3] [ 0 3 1 0 2] [ 0 0 0 4 1] [ 0 2 0 0 0] rank profile matrix: (2, 0) (0, 1) (3, 3) nmod_mat_lu: P = [2 1 3 0]
Column 0 has its only nonzero entry in row 2, which becomes the first pivot row by an upward rotation: rows 0 and 1 move down one position each and keep their order, so that row 0 is the pivot row of column 1. Row 1, twice row 0, then becomes zero; its row of , in the last row of the output, holds the multiplier 2. Columns 2 and 4 have no pivot. The pivot rows 2, 0, 3 form the row rank profile , and the leading submatrix of , of rank 1, contains one entry of the rank profile matrix, at . nmod_mat_lu swaps rows 0 and 2 instead, which puts row 1 before row 0, and its pivot rows 2, 1, 3 are not the row rank profile.
References#
- [DPS2017] J.-G. Dumas, C. Pernet and Z. Sultan, "Fast computation of the rank profile matrix and the generalized Bruhat decomposition", Journal of Symbolic Computation 83 (2017), 187-210
The FLINT team, “Computing a PLE decomposition revealing the rank profile matrix (nmod_mat.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/nmod_mat/ple.html (generated 2026-11-02).
@misc{flint-doc-nmod-mat-ple,
author = {{The FLINT team}},
title = {{Computing a PLE decomposition revealing the rank profile matrix (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/ple.html},
note = {Generated 2026-11-02}
}