FLINT3.7.0-dev

Computing a PLE decomposition revealing the rank profile matrix

Synopsis#include <flint/nmod_mat.h>
slong nmod_mat_ple(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)source
slong nmod_mat_ple_classical(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)source
slong nmod_mat_ple_classical_delayed(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)source
slong nmod_mat_ple_crout(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)source
slong nmod_mat_ple_recursive(slong * P, slong * pivots_nonpivots, nmod_mat_t A, int flags)source

Computes the PLE decomposition of the m×nm \times n matrix AA modulo a prime that reveals its rank profile matrix, overwrites AA with LL and EE in the storage of nmod_mat_lu, and returns the rank rr. Row ii of LELE is row P[i] of the original AA (P has length mm). The array pivots_nonpivots, of length nn, receives the pivot columns of EE 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 AA has its nonzero entries at (P[k], pivots_nonpivots[k]) for k<rk < r. If flags contains NMOD_MAT_PLE_RANK_CHECK and the rank is less than min⁡(m,n)\min(m, n), 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)source
void nmod_mat_ple_left_nullspace(nmod_mat_t X, const nmod_mat_t LU, const slong * P, slong rank)source

Given the output LU, P, pivots_nonpivots and rank =r= r of nmod_mat_ple for an m×nm \times n matrix AA, sets the n×(n−r)n \times (n - r) matrix XX to the basis of the right nullspace {x:Ax=0}\{x : Ax = 0\} computed by nmod_mat_nullspace, respectively the (m−r)×m(m - r) \times m matrix XX to the basis of the left nullspace {x:xA=0}\{x : xA = 0\} computed by nmod_mat_left_nullspace. XX must have exactly these dimensions.

Let c0<⋯<cr−1c_0 < \cdots < c_{r-1} be the pivot columns and f0<⋯<fn−r−1f_0 < \cdots < f_{n-r-1} the other columns, as in pivots_nonpivots. Vector kk of the right nullspace has a 1 in position fkf_k, zeros at the other fjf_j, and entry (j,k)(j, k) of C=−E1−1E2C = -E_1^{-1} E_2 in position cjc_j, where E1E_1 and E2E_2 are the columns cjc_j and fjf_j of EE; this costs a triangular solve, about r2(n−r)r^2 (n - r) operations. Vector kk of the left nullspace has a 1 in position P[r+k], zeros at the other P[r+j], and entry (k,j)(k, j) of D=−L2L1−1D = -L_2 L_1^{-1} in position P[j], where L1L_1 and L2L_2 are the first rr and the last m−rm - r rows of LL; this costs a triangular solve on the right, about (m−r)r2(m - r) r^2 operations. These are the only bases with an identity matrix in these positions: the columns fjf_j 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)source
void nmod_mat_ple_left_nullspace_compact(nmod_mat_t X, slong * rows, const nmod_mat_t LU, const slong * P, slong rank)source

The same bases without their identity matrix. For the right nullspace, XX is r×(n−r)r \times (n - r) and receives CC: its row jj gives the entries in position pivots_nonpivots[j] and its column kk corresponds to the vector with a 1 in position pivots_nonpivots[r+k]. For the left nullspace, XX is (m−r)×r(m - r) \times r, rows (length mm) receives the row rank profile of AA in increasing order followed by the other rows in increasing order, and XX receives the columns of DD sorted accordingly: its column jj gives the entries in position rows[j] and its row kk corresponds to the vector with a 1 in position rows[r+k].

NMOD_MAT_PLE_RANK_CHECKsource

Flag for nmod_mat_ple: return 0 as soon as the rank is known to be less than min⁡(m,n)\min(m, n).

nmod_mat_ple computes an LU decomposition with row pivoting, in the format of nmod_mat_lu, that reveals the rank profile matrix of AA: from its row permutation and pivot columns one reads the first linearly independent rows and columns of AA, and those of every leading submatrix of AA. 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 LL and EE may differ too.

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}.

Rank profile matrix#

Let AA be an m×nm \times n matrix of rank rr over Z/pZ\mathbb{Z}/p\mathbb{Z}. Its row rank profile is the lexicographically smallest set of rr 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 RA\mathcal{R}_A is the unique m×nm \times n matrix with rr entries equal to 1, no two in the same row or column, and zeros elsewhere, such that for all i≤mi \le m and j≤nj \le n the submatrices formed by the first ii rows and jj columns of AA and of RA\mathcal{R}_A have the same rank [DPS2017]. Its rows and columns with a nonzero entry are the row and column rank profiles of AA, and the same holds for the leading submatrices of AA with the entries of RA\mathcal{R}_A that lie in them.

Output#

Let rr be the returned rank. On return, as for nmod_mat_lu (Output):

  • row ii of LELE is row P[i] of the original AA, where LL is the m×rm \times r unit lower triangular matrix stored below the diagonal and EE the r×nr \times n matrix in row echelon form stored on and above it, whose pivot in row kk is in column ckc_k, with c0<c1<⋯<cr−1c_0 < c_1 < \cdots < c_{r-1};
  • the rows i≥ri \ge r of the output contain LL in their first rr columns and zeros elsewhere;
  • if pivots_nonpivots is not NULL, it receives c0,…,cr−1c_0, \ldots, c_{r-1} 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] <⋯<< \cdots < P[m-1], and the nonzero entries of RA\mathcal{R}_A are at the positions (P[k], ckc_k) for k<rk < r. In particular c0,…,cr−1c_0, \ldots, c_{r-1} is the column rank profile of AA and P[0], ..., P[r-1] sorted is its row rank profile.

These conditions determine P and the pivot columns, and then LL and EE, since an LU decomposition with unit LL 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 AA. 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 kk rows costs O(k)O(k) 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 AA 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 A=ΠLEA = \Pi L E, the right nullspace of AA is that of EE and the left nullspace is that of LL with the columns permuted by PP. Writing E1E_1 and E2E_2 for the pivot and the other columns of EE, and L1L_1, L2L_2 for the first rr and the other rows of LL, the matrices −E1−1E2-E_1^{-1} E_2 and −L2L1−1-L_2 L_1^{-1} 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 AA 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 −E1−1E2-E_1^{-1} E_2 and −L2L1−1-L_2 L_1^{-1} (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 min⁡(m,n)\min(m, n), with the same output as without the flag, and returns 0 otherwise, for all shapes of AA; this differs from the rank_check argument of nmod_mat_lu, which is only reliable for m≥nm \ge n. The decomposition stops as soon as more than n−min⁡(m,n)n - \min(m, n) 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 (n≤8n \le 8 columns, m≥nm \ge n rows up to 24 bits, m≥n/2m \ge n/2 up to 32 bits, at most 16 rows for p>2FLINT_BITS−1p > 2^{\mathtt{FLINT\_BITS} - 1}), it avoids these inversions: the eliminations replace a row RR by dR−eR′d R - e R', where R′R' is the pivot row and dd 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 min⁡(m,n)(p−1)2<2FLINT_BITS\min(m, n) (p - 1)^2 < 2^{\mathtt{FLINT\_BITS}} 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 EE above them, and the new pivot row by a vector-matrix product of its multipliers with the rows of EE. 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 s=min⁡(m,n)s = \min(m, n); 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 2522^{52}) and one for the other targets.

  • The classical algorithm when s≤3s \le 3, or s≤7s \le 7 and the delayed algorithm would need two or three words per entry, except for s=2,3s = 2, 3 with one word per entry, p>2p > 2, and n>4mn > 4m or m>2nm > 2n (IFMA) or m>8nm > 8n (other). For the other targets, also when n≤8n \le 8, n/2≤m≤2nn/2 \le m \le 2n, p≥220p \ge 2^{20}, m≥nm \ge n or p≥224p \ge 2^{24}, and m≤8m \le 8 or p≥232p \ge 2^{32} (the inversion-free variant below).
  • The recursive algorithm when s≥96s \ge 96 and mn≥2emn \ge 2^e:

    Modulusee (IFMA)ee (other)
    p<4p < 41717
    4≤p<2294 \le p < 2^{29}1616
    229≤p<2322^{29} \le p < 2^{32}1716 (14 without AVX2)
    232≤p<2522^{32} \le p < 2^{52}1816
    p≥252p \ge 2^{52}2119
  • For p=2p = 2 on x86-64 with AVX2 and n≥4mn \ge 4m, the classical algorithm.
  • The Crout algorithm when the delayed algorithm would need two or three words per entry, nmod_mat_nmod_vec_mul has a vector code path for pp (not for p≥250p \ge 2^{50} on NEON), and s≥Ss \ge S or (n≥2mn \ge 2m and n≥N0n \ge N_0):

    Modulus(S,N0)(S, N_0) (IFMA)(S,N0)(S, N_0) (other)
    p<232p < 2^{32}(32, 16)(64, 256), or (128, 256) without AVX2
    232≤p<2502^{32} \le p < 2^{50}(32, 16)(128, 64)
    250≤p<2522^{50} \le p < 2^{52}(32, 16)(128, 128)
    252≤p<2622^{52} \le p < 2^{62}(64, 128)(128, 128)
    p≥262p \ge 2^{62}(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 4×54 \times 5 matrix of rank 3 modulo 17 whose first two rows are proportional, compared with nmod_mat_lu:

Examplenmod_mat_ple_rank_profile.cchecked by make checkdoc
#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;
}
Output
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 LL, 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 {0,2,3}\{0, 2, 3\}, and the leading 2×22 \times 2 submatrix of AA, of rank 1, contains one entry of the rank profile matrix, at (0,1)(0, 1). 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
Cite this page

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).