Skip to content

Add the Modulus option to RowReduce, NullSpace, MatrixRank, Inverse and LinearSolve - #2010

Merged
rocky merged 5 commits into
Mathics3:masterfrom
tombouctou:fix-linalg-modulus
Oct 17, 2026
Merged

rocky merged 5 commits into
Mathics3:masterfrom
tombouctou:fix-linalg-modulus

Conversation

@tombouctou

@tombouctou tombouctou commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

RowReduce, NullSpace, MatrixRank, Inverse and LinearSolve did not accept the Modulus option, so for example NullSpace[{{1, 1}, {1, 1}}, Modulus -> 2] stayed unevaluated. This adds it for a prime modulus, as described in the WMA docs ("If p is zero, ordinary arithmetic is used").

What it does. With Modulus -> p the matrix entries are reduced mod p and the computation is plain Gaussian elimination on Python ints (new module mathics/eval/numbers/linalg_modular.py). Entries may be integers, or rationals whose denominator is invertible mod p. Results are integers in 0..p-1:

RowReduce[{{1, 0, 4}, {2, 0, 3}, {2, 1, 2}}, Modulus -> 5]    (* {{1, 0, 4}, {0, 1, 4}, {0, 0, 0}} *)
MatrixRank[{{1, 0, 4}, {2, 0, 3}, {2, 1, 2}}, Modulus -> 5]   (* 2 *)
NullSpace[{{1, 1, 0}, {1, 0, 1}, {0, 1, 1}}, Modulus -> 2]     (* {{1, 1, 1}} *)
Inverse[{{12, -2}, {1, 3}}, Modulus -> 5]                     (* {{1, 4}, {3, 4}} *)
LinearSolve[{{1, 1, 1}, {1, 2, 3}, {1, 4, 9}}, {1, 2, 3}, Modulus -> 47]   (* {23, 2, 23} *)

Conventions:

  • RowReduce gives the reduced row echelon form, as without Modulus.
  • NullSpace gives one vector per non-pivot column, with 1 in that column and 0 in the other non-pivot columns, in decreasing order of that column. That order is what WMA gives: running Strickland's LambdaAlgebra.m (below) reproduces his WMA-computed homology bases exactly only with this order. The exact NullSpace (without Modulus) currently lists them in increasing order; I left that alone here.
  • LinearSolve sets the free variables to 0.
  • A singular matrix and an inconsistent system give the existing Inverse::sing and LinearSolve::nosol messages.

What it does not do. A modulus that is not a prime (WMA says the computation "may fail" then), a non-integer modulus, a symbolic entry, or a denominator divisible by p leave the expression unevaluated. Modulus -> 0 keeps the current code path.

Tests. Doc examples, plus test_linalg_modulus and test_linalg_modulus_random in test/builtin/numbers/test_linalg.py; the random test checks the defining properties (rank + nullity, m . ns == 0, m . x == b, m . Inverse[m] == 1 mod p) on 60 random matrices. pytest and the doctests give the same results as on master; the only failures are environmental ones that also fail on master. mypy is clean.

Motivation and a larger check: Strickland's LambdaAlgebra.m (https://strickland1.org/toda/) computes the homology of the mod-2 lambda algebra with all five functions and Modulus -> 2, and his LambdaData.m stores the results computed in WMA. With this PR (and the Transpose and MapAt fixes in #2007 and #2008) Mathics3 reproduces exactly, in the same order, his homology, boundary and non-boundary bases and his canonical forms: 218 of the 296 tridegrees in LambdaData.m (4225 canonical forms), with no difference. The remaining 78 are too slow to finish under Mathics3 in reasonable time; the largest would take days. For comparison, under Wolfram Engine 15 the same script computes all 296 in under a minute and matches LambdaData.m exactly.

🤖 Generated with Claude Code

tombouctou and others added 2 commits October 16, 2026 12:04
…nd LinearSolve

With Modulus -> p for a prime p, these compute over the integers mod p
with plain Gaussian elimination on Python ints. Entries may be integers
or rationals whose denominator is invertible mod p. Modulus -> 0 keeps
ordinary arithmetic; a modulus that is not a prime, or a symbolic entry,
leaves the expression unevaluated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strickland's LambdaAlgebra.m, run in WMA, shows that NullSpace with
Modulus gives the vectors in decreasing order of their free column, so
do the same.

The random tests assigned x, m and p in the shared test session, which
broke later tests that use x as a symbol; use Module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread mathics/eval/numbers/linalg_modular.py Outdated
from mathics.core.list import ListExpression
from mathics.core.symbols import SymbolList

ModMatrix = List[List[int]]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name seems specialized, but the concept isn't - it is a matrix in nested list form.

BTW, whenever you see List or Tuple, that should now be list and tuple since we no longer support Python 3.9 where it had to be List, and Tuple.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d141405. The type is now NestedListMatrix = list[list[Any]] in mathics.core.convert.matrix, and List/Tuple are now list/tuple.

Comment thread mathics/eval/numbers/linalg_modular.py Outdated
return rows


def to_mathics_matrix(rows: ModMatrix) -> ListExpression:

@rocky rocky Oct 17, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is really a matrix in list-of-lists form, this conversion function, which is also generic, should be moved under mathics.core.convert somewhere.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to mathics.core.convert.matrix as to_mathics_matrix(rows, elements_conversion_fn=from_python). It is built on to_mathics_list, and the entry conversion is a parameter, so it is not tied to integers.

tombouctou and others added 3 commits October 17, 2026 09:52
…rsion

As suggested in review: the matrix type is a matrix in nested list form,
not something modular, so it is now NestedListMatrix in
mathics.core.convert.matrix, together with to_mathics_matrix(), which turns
such a matrix into a Mathics3 List of Lists. Use list and tuple instead of
typing.List and typing.Tuple.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rocky
rocky merged commit cc30fb1 into Mathics3:master Oct 17, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants