Skip to content

Add IsGenericMatrixRep matrix object type#6322

Open
fingolfin wants to merge 8 commits intomasterfrom
codex/flat-plist-matrix-rep
Open

Add IsGenericMatrixRep matrix object type#6322
fingolfin wants to merge 8 commits intomasterfrom
codex/flat-plist-matrix-rep

Conversation

@fingolfin
Copy link
Copy Markdown
Member

Store dense matrices as plain row lists for faster entry access.

Partially addresses issue #2148 (the unresolved part is that of a "vector object that is not in IsList)
Closes #2973 (an older attempt of something similar)

This PR was created with help of Codex by OpenAI: I planned the work with it, then it did a full first implementation, which was improved by me at first via some rounds of prompts requesting tweaks, then a bunch of manual work to implement details and speedups the AI was not aware.

Since this is fresh, I am sure there are some bugs, and definitely opportunities for things to be improved. And of course plenty of places in the code where using these MatrixObj will blow up because they are not row lists... but that's for future work.


Some microbenchmarks show the benefit, namely that performance for basic arithmetic in many cases matches that of ist-of-list matrices, while IsPlistMatrixRep is basically always slower, sometimes quite substantially so.

I am deliberately doing this over a finite field that is not covered by our compressed matrices, as that is IMHO going to be one of the central use cases in GAP; though for integer/rational matrices, I don't think things will be substantially difference, at least relative to each other. There is no point comparing e.g. over GF(2) to our compressed GF(2) matrices, which of course will be much faster than any of the three implementations being tested here.

Setup:

R:=GF(257);;n:=20;;
M1:=IdentityMat(n,R);;
M2:=IdentityMatrix(IsPlistMatrixRep,R,n);;
M3:=IdentityMatrix(IsFlatPlistMatrixRep,R,n);;

Addition:

gap> for i in [1..1000] do x:=M1+M1; od; time;
4
gap> for i in [1..1000] do x:=M2+M2; od; time;
12
gap> for i in [1..1000] do x:=M3+M3; od; time;
4

Multiplication:

gap> for i in [1..1000] do x:=M1*M1; od; time;
66
gap> for i in [1..1000] do x:=M2*M2; od; time;
1448
gap> for i in [1..1000] do x:=M3*M3; od; time;
60

Inversion:

gap> for i in [1..1000] do x:=Inverse(M1); od; time;
7
gap> for i in [1..1000] do x:=Inverse(M2); od; time;
83
gap> for i in [1..1000] do x:=Inverse(M3); od; time;
4

Powers:

gap>
gap> for i in [1..1000] do x:=M1^5; od; time;
187
gap> for i in [1..1000] do x:=M2^5; od; time;
4350
gap> for i in [1..1000] do x:=M3^5; od; time;
182

Element access (plist-of-plist benefits from a direct kernel implementation; we could add that for both IsPlistMatrixRep and IsFlatPlistMatrixRep, if so desired)

gap> for i in [1..1000000] do x:=M1[1,1]; od; time;
19
gap> for i in [1..1000000] do x:=M2[1,1]; od; time;
78
gap> for i in [1..1000000] do x:=M3[1,1]; od; time;
66

Example of a typical matrix property: IsDiagonalMat

gap> for i in [1..10000] do x:=IsDiagonalMat(M1); od; time;
132
gap> for i in [1..10000] do x:=IsDiagonalMat(M2); od; time;
191
gap> for i in [1..10000] do x:=IsDiagonalMat(M3); od; time;
146

IsOne (surprisingly is slower for IsFlatPlistMatrixRep, I do not yet know why)

gap> for i in [1..10000] do x:=IsOne(M1); od; time;
144
gap> for i in [1..10000] do x:=IsOne(M2); od; time;
145
gap> for i in [1..10000] do x:=IsOne(M3); od; time;
172

PositionNonZeroInRow is among the core functionality for row reduction:

gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M1,1); od; time;
334
gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M2,1); od; time;
486
gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M3,1); od; time;
376

Store dense matrices as plain row lists for faster entry access.

Some microbenchmarks show the benefit, namely that performance for
basic arithmetic in many cases matches that of ist-of-list
matrices, while IsPlistMatrixRep is basically always slower,
sometimes quite substantially so.

Setup:

    R:=GF(257);;n:=20;;
    M1:=IdentityMat(n,R);;
    M2:=IdentityMatrix(IsPlistMatrixRep,R,n);;
    M3:=IdentityMatrix(IsFlatPlistMatrixRep,R,n);;

Addition:

    gap> for i in [1..1000] do x:=M1+M1; od; time;
    4
    gap> for i in [1..1000] do x:=M2+M2; od; time;
    12
    gap> for i in [1..1000] do x:=M3+M3; od; time;
    4

Multiplication:

    gap> for i in [1..1000] do x:=M1*M1; od; time;
    66
    gap> for i in [1..1000] do x:=M2*M2; od; time;
    1448
    gap> for i in [1..1000] do x:=M3*M3; od; time;
    60

Inversion:

    gap> for i in [1..1000] do x:=Inverse(M1); od; time;
    7
    gap> for i in [1..1000] do x:=Inverse(M2); od; time;
    83
    gap> for i in [1..1000] do x:=Inverse(M3); od; time;
    4

Powers:

    gap>
    gap> for i in [1..1000] do x:=M1^5; od; time;
    187
    gap> for i in [1..1000] do x:=M2^5; od; time;
    4350
    gap> for i in [1..1000] do x:=M3^5; od; time;
    182

Element access (plist-of-plist benefits from a direct kernel
implementation; we could add that for both IsPlistMatrixRep
and IsFlatPlistMatrixRep, if so desired)

    gap> for i in [1..1000000] do x:=M1[1,1]; od; time;
    19
    gap> for i in [1..1000000] do x:=M2[1,1]; od; time;
    78
    gap> for i in [1..1000000] do x:=M3[1,1]; od; time;
    66

Example of a typical matrix property: IsDiagonalMat

    gap> for i in [1..10000] do x:=IsDiagonalMat(M1); od; time;
    132
    gap> for i in [1..10000] do x:=IsDiagonalMat(M2); od; time;
    191
    gap> for i in [1..10000] do x:=IsDiagonalMat(M3); od; time;
    146

IsOne (surprisingly is slower for IsFlatPlistMatrixRep, I do not
yet know why)

    gap> for i in [1..10000] do x:=IsOne(M1); od; time;
    144
    gap> for i in [1..10000] do x:=IsOne(M2); od; time;
    145
    gap> for i in [1..10000] do x:=IsOne(M3); od; time;
    172

PositionNonZeroInRow is among the core functionality for row reduction:

    gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M1,1); od; time;
    334
    gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M2,1); od; time;
    486
    gap> for i in [1..1000000] do x:=PositionNonZeroInRow(M3,1); od; time;
    376

Co-authored-by: Codex <codex@openai.com>
@fingolfin fingolfin added kind: enhancement Label for issues suggesting enhancements; and for pull requests implementing enhancements kind: new feature topic: library release notes: use title For PRs: the title of this PR is suitable for direct use in the release notes labels Apr 14, 2026
Comment thread lib/matobjflatplist.gd Outdated
Copy link
Copy Markdown
Contributor

@ThomasBreuer ThomasBreuer left a comment

Choose a reason for hiding this comment

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

just a few minor remarks

if we want to describe this representation (in comparison with others) then my impression is:

  • a matrix type that can be multiplied with any type of vector object, but formally defines IsPlistVectorRep as its CompatibleVectorFilter,
  • a matrix type that deliberately does not support row access,
  • matrix type that supports matrices with zero rows or columns (something which should hold for all matrix types except IsPlistRep.

for curiosity:
Where does the name IsFlatPlistMatrixRep come from?
GAP has a function Flat, thus one could get the idea that an m times n "flat" matrix is internally represented by a "flat" list of length mn.

Comment thread lib/matobjflatplist.gi Outdated
function( row )
local copy, i, len;
len := Length( row );
copy := [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Perhaps

Suggested change
copy := [];
copy := EmptyPlist( len );

and then run from 1 to len?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

First did that but then in fact remove this function altogether. Instead three former callers now use ShallowCopy, and one PlainListCopy

Comment thread lib/matobjflatplist.gi Outdated
Comment thread lib/matobjflatplist.gi Outdated
Comment thread lib/matobjflatplist.gi Outdated
if rows = fail then
return fail;
fi;
return MakeIsFlatPlistMatrixRep( BaseDomain( M ), NrCols( M ), rows, false );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
return MakeIsFlatPlistMatrixRep( BaseDomain( M ), NrCols( M ), rows, false );
return MakeIsFlatPlistMatrixRep( BaseDomain( M ), rows, rows, false );

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't understand: rows is list (of rows) here, but the second argument needs to be the number of columnss

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The function computes the inverse of a matrix, and if we arrive at this line then the matrix is square.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

But rows is a list of rows, not an integer

Comment thread lib/matobjflatplist.gi Outdated
Comment thread lib/matobjflatplist.gi Outdated
@fingolfin
Copy link
Copy Markdown
Member Author

The name IsFlatPlistMatrixRep comes from some recent discussion thread involving you and James, someone brought it up. But yeah, I do see the confusion with Flat.

In PR #6227 I suggested that we could rename IsPlistMatrixRep to IsRowPlistMatrixRep; and then this new type could become the new IsPlistMatrixRep. That's at least in principle viable since no GAP package uses IsPlistMatrixRep for their implementation; in fact only forms uses it in two .tst files, no other code has active code involving it.

However, it might break private user code, so I am not keen on that.

I am open to alternatives. Some braingstorming with AI yields these, but perhaps someone has a better idea:

  1. IsDirectPlistMatrixRep
  2. IsNestedPlistMatrixRep
  3. IsPlainPlistMatrixRep
  4. IsGenericMatrixRep

@ThomasBreuer
Copy link
Copy Markdown
Contributor

I like the idea IsGenericMatrixRep, from the viewpoint that there are no conditions of the base domain and thus this representation can be used for all computation in the context of "matrices over some base domain".

When one wants to "upgrade" code that was written for list-of-lists matrices to matrix objects, switching to IsGenericMatrixRep could be suggested as a first step: Replace the input matrices by matrices in IsGenericMatrixRep, run the code, and fix the errors that happen due to the (mis)use of features of list-of-lists matrices.

@james-d-mitchell
Copy link
Copy Markdown
Contributor

for curiosity: Where does the name IsFlatPlistMatrixRep come from? GAP has a function Flat, thus one could get the idea that an m times n "flat" matrix is internally represented by a "flat" list of length mn.

I have the same question as @ThomasBreuer, I also expected this. Could you possibly indicate what the key difference between matrices in IsFlatPlistMatrixRep and those in IsPlistMatrixRep is @fingolfin? Is it that the rows stored in matrix aren't matrices of the same type as the matrix itself but rather "just" plain lists? Is this also the reason for not providing access to the rows?

@ThomasBreuer
Copy link
Copy Markdown
Contributor

Concerning the difference between IsFlatPlistMatrixRep and IsPlistMatrixRep:
The idea of IsPlistMatrixRep was that the internal data are a plain list of vector objects in IsPlistVectorRep. In this situation, it is natural to define that these vector objects are the rows.
For IsFlatPlistMatrixRep, the idea is not to support access to rows; there are no natural vector objects for that purpose, and the advantage is that the matrix can handle the multiplication with any kind of vector object, by creating an object of the same representation as the result.

@fingolfin
Copy link
Copy Markdown
Member Author

Everything @ThomasBreuer wrote is true, but the main advantage of this approach is speed: by not having every row be a "rich" and thus expensive object, we avoid a lot overhead in both memory usage and speed. Also, we can directly call into GAP kernel routines for e.g. multiplying two "list of list matrices". As a result, the new type can be almost as fast as the classic "list of list matrices", and in some cases even faster (as it avoids expensive method dispatch overhead)

@fingolfin fingolfin changed the title Add IsFlatPlistMatrixRep matrix object type Add IsGenericMatrixRep matrix object type Apr 16, 2026
@fingolfin
Copy link
Copy Markdown
Member Author

I've now renamed this to IsGenericMatrixRep, as discussed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind: enhancement Label for issues suggesting enhancements; and for pull requests implementing enhancements kind: new feature release notes: use title For PRs: the title of this PR is suitable for direct use in the release notes topic: library

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants