Skip to content

Speed up the canonical form under a hybrid group - #34

Draft
fingolfin wants to merge 6 commits into
masterfrom
mh/orbit-dictionary
Draft

fingolfin wants to merge 6 commits into
masterfrom
mh/orbit-dictionary

Conversation

@fingolfin

Copy link
Copy Markdown
Member

CanoFormWithAutGroupOfTable spends nearly all its time in gap/cfstab. Profiling showed four costs there that do not depend on the mathematics; each commit removes one. The results are unchanged.

Before:

gap> T := NilpotentTableOfRad( GroupRing( GF(2), SmallGroup( 64, 266 ) ) );;
gap> CanoFormWithAutGroupOfTable( T );; time;
26144
gap> T := TableOfRadQuotient( GroupRing( GF(2), SmallGroup( 256, 56082 ) ), 4 );;
gap> CanoFormWithAutGroupOfTable( T );; time;
58560
gap> T := TableOfRadQuotient( GroupRing( GF(2), SmallGroup( 512, 406945 ) ), 4 );;
gap> CanoFormWithAutGroupOfTable( T );; time;
2691
gap> T := NilpotentTableOfRad( GroupRing( GF(2), SmallGroup( 128, 2326 ) ) );;
gap> AutGroupOfTable( T );;
# didn't finish after 1 hour

After:

gap> T := NilpotentTableOfRad( GroupRing( GF(2), SmallGroup( 64, 266 ) ) );;
gap> CanoFormWithAutGroupOfTable( T );; time;
6490
gap> T := TableOfRadQuotient( GroupRing( GF(2), SmallGroup( 256, 56082 ) ), 4 );;
gap> CanoFormWithAutGroupOfTable( T );; time;
16807
gap> T := TableOfRadQuotient( GroupRing( GF(2), SmallGroup( 512, 406945 ) ), 4 );;
gap> CanoFormWithAutGroupOfTable( T );; time;
713
gap> T := NilpotentTableOfRad( GroupRing( GF(2), SmallGroup( 128, 2326 ) ) );;
gap> AutGroupOfTable( T );; time;
190681

The commits:

  1. BlockCanonicalForm found an image in the orbit with Position, about n^2 g / 2 matrix comparisons for an orbit of length n and g generators. A dictionary makes that n g lookups. (The gl-orbit of SmallGroup(128, 2326) has 499968 points.)
  2. VectorCanonicalForm reduced every tail modulo a fixed basis of the whole space with SolutionMat, which echelonises the basis again on every call. The basis is square, so this is a multiplication by the first columns of its inverse, computed once.
  3. It also tested each entry of a tail for membership in the span of the entries chosen so far with SolutionMat, 3.4 million calls for one group of order 256. The span is now kept echelonised and the entries are sifted.
  4. Every round looked at every element of the current stabiliser, although an element with a zero tail can be neither a pivot nor be multiplied by one: 3387904 of 3389200 visits in that example. Those elements are now skipped until the canonical form moves.
  5. Tests for VectorCanonicalForm, SubspaceCanonicalForm and BlockCanonicalForm (tst/cfstab.tst), which the test suite so far reached only through the examples of the manual. They compare with orbits computed by GAP: the canonical form lies in the orbit and is constant on it, the orbit has length p^(|pcgs| - |stab|), the stabiliser fixes the canonical form and has the right order. They cover GF(4), elements acting trivially, and a hybrid group with and without gl part, and pass on master as well.
  6. A fix for commit 1: NewDictionary chooses its hash function for the field generated by the entries of the first key, so over a field with more than 256 elements that is not prime a later key could make the lookup fail with an error. The dictionary is now given the row space over the field as its domain.

How the results were checked:

  • The new VectorCanonicalForm and SubspaceCanonicalForm return the same canonical form, stabiliser and transversal element as the old ones, element by element, on about 7000 random inputs over 13 fields from GF(2) to GF(65537), among them GF(4), GF(2^9) and GF(3^6); about 2700 of them were also compared with orbits computed by GAP.
  • The canonical tables of ten group algebras (F_2 G and F_3 G for groups of order 27 to 512, the largest ones as quotients A/A^k) are identical to those computed by master; for four of them also with USE_MSERS := true.
  • The minimum of an orbit is the same whether the matrices are compressed or not, so the canonical forms agree with those of released versions.

A port of gap/cfstab/pgroup.gi with the same changes is proposed for the AutPGrp package in gap-packages/autpgrp#30, where it is tested in the same way.

changes.txt has no entry yet.

@codecov

codecov Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.06%. Comparing base (ab45a4c) to head (bc1fb67).

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #34      +/-   ##
==========================================
+ Coverage   94.05%   94.06%   +0.01%     
==========================================
  Files          62       62              
  Lines       26624    26689      +65     
==========================================
+ Hits        25040    25105      +65     
  Misses       1584     1584              
Files with missing lines Coverage Δ
gap/cfstab/general.gi 72.64% <100.00%> (-4.54%) ⬇️
gap/cfstab/orbstab.gi 65.03% <100.00%> (+7.37%) ⬆️
gap/cfstab/pgroup.gi 94.40% <100.00%> (+3.15%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

BlockCanonicalForm located an image in the orbit with Position, so
building an orbit of length n with g generators cost about n^2 g / 2
matrix comparisons.  A dictionary makes that n g lookups.

The keys are compressed by ImmutableMatrix: GAP derives the hash
function of a list of vectors from the sample it is given, and returns
fail on a vector in a different representation.

Timings for AutGroupOfTable on the radical of GF(2)G, same seed:

    G              gl-orbit   before     after
    SmallGroup(64,240)   2520    8.1 s     8.0 s
    SmallGroup(64,264)   8680   21.8 s    12.4 s
    SmallGroup(64,220)  10080   14.9 s     7.6 s
    SmallGroup(64,266)  13888   35.2 s    11.1 s
    SmallGroup(128,2326) 499968     -      3.2 min

Assisted-by: Claude Code (Opus 5)
VectorCanonicalForm reduces every tail modulo a fixed basis of the whole
space with SolutionMat, which echelonises that basis again on each call:
in the canonical form of a rank 7 group of order 256 that was 74000
calls and half the total time.  The basis is square and invertible, so
the projection is a multiplication by the first columns of its inverse;
compute that matrix once per call.

Canonical form of A/A^4 for GF(2)G, with the dictionary of the previous
commit:

    G                    dim   before    after
    SmallGroup(256,26973) 81   10.0 s    4.6 s
    SmallGroup(256,55626)101   29.8 s   13.2 s
    SmallGroup(256,56082)127   74.9 s   33.0 s

Assisted-by: Claude Code (Opus 5)
Each entry of a tail was tested for membership in the span of the
entries chosen so far with SolutionMat, which echelonises that span
again on every test: 3.4 million calls in the canonical form of a rank 6
group of order 256.  Keep the span echelonised instead, together with
the expression of each of its vectors in the chosen entries, and sift.

Canonical form of A/A^4 for GF(2)G, cumulative with the two previous
commits:

    G                    dim   master   dict+proj    +sift
    SmallGroup(256,26973) 81   10.0 s      4.6 s     3.9 s
    SmallGroup(256,55626)101   29.8 s     13.2 s    11.2 s
    SmallGroup(256,56082)127   74.9 s     33.0 s    29.4 s

Assisted-by: Claude Code (Opus 5)
Every round of VectorCanonicalForm looked at the tail of every element
of the current stabiliser, although an element with a zero tail has a
zero entry, so it is neither a pivot nor multiplied by one.  In the
canonical form of a rank 6 group of order 256 that was 3387904 of
3389200 visits.

Keep the elements with a nonzero tail in a list of their own.  A tail
changes only when its element is multiplied or when cano moves, and the
latter did not happen once in that example.

Canonical form of A/A^4 for GF(2)G, cumulative:

    G                    dim   master   3 commits    now
    SmallGroup(256,26973) 81   10.0 s      3.9 s    2.5 s
    SmallGroup(256,55626)101   29.8 s     11.2 s    7.1 s
    SmallGroup(256,56082)127   74.9 s     29.4 s   18.2 s

Assisted-by: Claude Code (Opus 5)
The test suite reached VectorCanonicalForm, SubspaceCanonicalForm and
BlockCanonicalForm only through the examples of the manual. The new
tests compare them with orbits computed by GAP: the canonical form lies
in the orbit and is constant on it, the orbit has length
p^(|pcgs|-|stab|), and the stabilizer fixes the canonical form and has
the right order. They cover a field that is not prime, elements acting
trivially, and a hybrid group with and without gl part.

Assisted-by: Claude Code (Fable 5.1)
NewDictionary chose the hash function from the first orbit point alone,
that is, for the field its entries generate. Over a field with more than
256 elements that is not prime, a first point with entries in a proper
subfield made every later lookup fail with an error:

    gap> F := GF(2^9);; z := PrimitiveRoot( F );;
    gap> g := DirectProductElement( [ 1, [ [ z^0, 0*z ], [ 0*z, z ] ] ] );;
    gap> G := rec( field := F, one := g^0, glAutos := [ g ],
    >      glPerms := [ PermList( Concatenation( [2..511], [1] ) ) ],
    >      glOrder := 511, agAutos := [], size := 511 );;
    gap> BlockCanonicalForm( G, [ [ z^0, z^0 ] ] );
    Error, no method found! [...]

The key is now the matrix as one vector, and the dictionary is given the
row space over the field as its domain. The canonical tables of the six
benchmark group algebras are unchanged.

Assisted-by: Claude Code (Fable 5.1)

This branch has not been deployed

No deployments
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.

1 participant