FLINT3.7.0-dev

Solving systems of linear equations

Synopsis#include <flint/nmod_mat.h>
int nmod_mat_solve(nmod_mat_t X, const nmod_mat_t A, const nmod_mat_t B)source

Solves AX=BAX = B for a square matrix AA. If AA is nonsingular, sets XX to the unique solution and returns 1. Otherwise returns 0 and leaves XX unchanged. XX may be aliased with AA or BB.

int nmod_mat_solve_vec(nn_ptr x, const nmod_mat_t A, nn_srcptr b)source

Solves Ax=bAx = b for a square m×mm \times m matrix AA and vectors xx, bb of length mm, as nmod_mat_solve. xx may be equal to bb.

int nmod_mat_can_solve(nmod_mat_t X, const nmod_mat_t A, const nmod_mat_t B)source

Decides whether AX=BAX = B has a solution for an arbitrary m×nm \times n matrix AA and m×cm \times c matrix BB. If so, sets the n×cn \times c matrix XX to a solution and returns 1; otherwise sets XX to zero and returns 0. Returns 0 if the dimensions are incompatible. XX may be aliased with BB but not with AA.

int nmod_mat_can_solve_inner(slong * rank, slong * perm, slong * pivots, nmod_mat_t X, const nmod_mat_t A, const nmod_mat_t B)source

The same as nmod_mat_can_solve, and in addition, for each pointer that is not NULL, stores the rank of AA in *rank, the row permutation of the LU decomposition of AA in perm (length mm), and the indices of the pivot columns in the first *rank entries of pivots (length at least min⁡(m,n)\min(m, n)).

These functions solve AX=BAX = B over Z/pZ\mathbb{Z}/p\mathbb{Z} for a prime pp. nmod_mat_solve and nmod_mat_solve_vec are for a square matrix AA and report failure if AA is singular. nmod_mat_can_solve accepts any shape and decides whether a solution exists. All of them compute an LU decomposition of a copy of AA and finish with triangular solves, so their cost is that of nmod_mat_lu plus two triangular solves with the columns of BB.

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

Square systems#

nmod_mat_solve(X, A, B) decomposes a copy of AA with nmod_mat_lu and rank_check set, so a singular AA is detected as soon as a column without a pivot appears. It then permutes the rows of BB as in the decomposition and solves Ly=PBLy = PB and UX=yUX = y with nmod_mat_solve_tril and nmod_mat_solve_triu. If AA is singular it returns 0 without writing to XX, even if the system has solutions; use nmod_mat_can_solve for that case. Since AA and BB are copied before XX is written, XX may be the same matrix as AA or BB when the dimensions agree. If AA has no rows or BB has no columns, the functions return 1.

nmod_mat_solve_vec(x, A, b) is the same for one right-hand side given as an array, and allows x == b.

Systems of any shape#

nmod_mat_can_solve(X, A, B), for an m×nm \times n matrix AA, works as follows.

  1. Compute an LU decomposition A=ΠLUA = \Pi L U of rank rr without rank check, and the pivot columns c0<⋯<cr−1c_0 < \cdots < c_{r-1} of UU.
  2. Permute BB and solve L1Y=(Π−1B)1L_1 Y = (\Pi^{-1} B)_{1} for the first rr rows, where L1L_1 is the top r×rr \times r block of LL.
  3. If r<mr < m, check that the remaining m−rm - r rows satisfy L2Y=(Π−1B)2L_2 Y = (\Pi^{-1} B)_2, with one matrix multiplication. If not, the system is inconsistent: set XX to zero and return 0.
  4. Solve U′Z=YU' Z = Y, where U′U' is the r×rr \times r upper triangular matrix formed by the pivot columns of UU, and set row cic_i of XX to row ii of ZZ and all other rows to zero.

The solution returned is thus the one whose entries for the free (nonpivot) variables are zero. All solutions are obtained by adding elements of the nullspace, see nmod_mat_nullspace. nmod_mat_can_solve_inner also returns the rank, the permutation and the pivot columns found in steps 1 and 2. The functions return 0 if the dimensions of XX, AA and BB do not match, and 1 with X=0X = 0 if AA has no rows or BB has no columns. XX may be aliased with BB but not with AA.

Many right-hand sides#

Putting all right-hand sides in the columns of one matrix BB is the most efficient way to solve several systems with the same matrix: the triangular solves then reduce to matrix multiplication. When the right-hand sides become available one at a time, compute the LU decomposition once and do the two triangular solves for each of them, as in the second example below.

Composite moduli#

For a composite modulus the functions may raise a FLINT_IMPINV exception when a pivot is not invertible, and a return value of 0 does not prove that no solution exists. Over Z/nZ\mathbb{Z}/n\mathbb{Z}, linear systems can be solved with the Howell form [StoMul1998], which nmod_mat_howell_form computes; the module has no solver built on it.

Examples#

A nonsingular system, then a singular 3×43 \times 4 system with a consistent and an inconsistent right-hand side:

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

int main(void)
{
    nmod_mat_t A, X, B, S;
    ulong x[3], b[3] = { 1, 2, 3 };
    slong rank, perm[3], pivots[3];
    int ok;

    /* a nonsingular system modulo 7 */
    nmod_mat_init(A, 3, 3, 7);
    nmod_mat_entry(A, 0, 0) = 2; nmod_mat_entry(A, 0, 1) = 1;
    nmod_mat_entry(A, 1, 1) = 3; nmod_mat_entry(A, 1, 2) = 1;
    nmod_mat_entry(A, 2, 0) = 1; nmod_mat_entry(A, 2, 2) = 4;

    ok = nmod_mat_solve_vec(x, A, b);
    flint_printf("solve_vec: %d, x = (%wu, %wu, %wu)\n", ok, x[0], x[1], x[2]);

    /* a singular 3 x 4 matrix: row 2 = row 0 + row 1 */
    nmod_mat_clear(A);
    nmod_mat_init(A, 3, 4, 7);
    nmod_mat_entry(A, 0, 0) = 1; nmod_mat_entry(A, 0, 1) = 2;
    nmod_mat_entry(A, 1, 2) = 1; nmod_mat_entry(A, 1, 3) = 5;
    nmod_mat_entry(A, 2, 0) = 1; nmod_mat_entry(A, 2, 1) = 2;
    nmod_mat_entry(A, 2, 2) = 1; nmod_mat_entry(A, 2, 3) = 5;

    nmod_mat_init(B, 3, 1, 7);
    nmod_mat_init(X, 4, 1, 7);
    nmod_mat_init(S, 3, 1, 7);

    /* consistent right-hand side: b2 = b0 + b1 */
    nmod_mat_entry(B, 0, 0) = 3;
    nmod_mat_entry(B, 1, 0) = 4;
    nmod_mat_entry(B, 2, 0) = 0;
    ok = nmod_mat_can_solve_inner(&rank, perm, pivots, X, A, B);
    nmod_mat_mul(S, A, X);
    flint_printf("can_solve: %d, rank %wd, pivots %wd %wd, X^T = (%wu %wu %wu %wu), AX == B: %d\n",
        ok, rank, pivots[0], pivots[1],
        nmod_mat_entry(X, 0, 0), nmod_mat_entry(X, 1, 0),
        nmod_mat_entry(X, 2, 0), nmod_mat_entry(X, 3, 0), nmod_mat_equal(S, B));

    /* inconsistent right-hand side */
    nmod_mat_entry(B, 2, 0) = 1;
    ok = nmod_mat_can_solve(X, A, B);
    flint_printf("can_solve: %d, X is zero: %d\n", ok, nmod_mat_is_zero(X));

    nmod_mat_clear(A);
    nmod_mat_clear(B);
    nmod_mat_clear(X);
    nmod_mat_clear(S);
    return 0;
}
Output
solve_vec: 1, x = (0, 1, 6)
can_solve: 1, rank 2, pivots 0 2, X^T = (3 0 4 0), AX == B: 1
can_solve: 0, X is zero: 1

The free variables x1x_1 and x3x_3 (columns 1 and 3 have no pivot) are set to zero. One LU decomposition reused for three right-hand sides:

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

int main(void)
{
    slong n = 50, i, j, t, rank;
    slong * P = flint_malloc(n * sizeof(slong));
    nmod_mat_t A, LU, b, y, x, check;
    ulong p = 1000003;
    int all_ok = 1;

    nmod_mat_init(A, n, n, p);
    for (i = 0; i < n; i++)
        for (j = 0; j < n; j++)
            nmod_mat_entry(A, i, j) = nmod_pow_ui(i + 2, j + 1, A->mod);

    /* a Vandermonde-like matrix, nonsingular; factor it once */
    nmod_mat_init_set(LU, A);
    rank = nmod_mat_lu(P, LU, 1);
    flint_printf("rank = %wd\n", rank);

    nmod_mat_init(b, n, 1, p);
    nmod_mat_init(y, n, 1, p);
    nmod_mat_init(x, n, 1, p);
    nmod_mat_init(check, n, 1, p);

    /* then solve A x = b for several right-hand sides b */
    for (t = 0; t < 3 && rank == n; t++)
    {
        for (i = 0; i < n; i++)
            nmod_mat_entry(b, i, 0) = (t + 1) * (i + 1) % p;

        /* permute b, solve L y = b with unit diagonal, then U x = y */
        for (i = 0; i < n; i++)
            nmod_mat_entry(y, i, 0) = nmod_mat_entry(b, P[i], 0);
        nmod_mat_solve_tril(y, LU, y, 1);
        nmod_mat_solve_triu(x, LU, y, 0);

        nmod_mat_mul(check, A, x);
        all_ok &= nmod_mat_equal(check, b);
        flint_printf("b%wd: x[0] = %wu\n", t, nmod_mat_entry(x, 0, 0));
    }
    flint_printf("A x == b in all cases: %d\n", all_ok);

    nmod_mat_clear(A);
    nmod_mat_clear(LU);
    nmod_mat_clear(b);
    nmod_mat_clear(y);
    nmod_mat_clear(x);
    nmod_mat_clear(check);
    flint_free(P);
    return 0;
}
Output
rank = 50
b0: x[0] = 862864
b1: x[0] = 725725
b2: x[0] = 588586
A x == b in all cases: 1

The upper triangular solve reads only the upper triangular part of LU, and the lower triangular solve with unit set reads only the entries below the diagonal, so both use the packed output of nmod_mat_lu directly.

Guides#

References#

  • [StoMul1998] Storjohann, Arne and Mulders, Thom : Fast algorithms for linear algebra modulo :math:NN : Algorithms—{ESA} '98 (Venice), Lecture Notes in Comput. Sci. 1461 139–150
Cite this page

The FLINT team, “Solving systems of linear equations (nmod_mat.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/nmod_mat/solve.html (generated 2026-11-02).