From aef1eb435807dcf986de721e86f12f93710560cf Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sat, 8 Aug 2026 13:59:21 +0200 Subject: [PATCH] lib: Add `PcgsSeriesInfo' and use it where the kind of series matters `IndicesEANormalSteps' is in practice used as "the indices of whichever series this pcgs belongs to": four places in the library read it off a central or p-central pcgs. That is why `TryPcgsPermGroup' has to set it for central series as well, although the factors of a central series need not be elementary abelian -- which is how the inconsistent pcgs behind issue #6407 could arise in the first place. Add an attribute per kind of series which returns the pcgs together with the indices belonging to that kind, and a tag based operation dispatching on the kind, so that a caller obtains the indices it asked for rather than whatever the pcgs happens to carry. Use these in the four places above, and give `IndicesEANormalStepsBounded' a variant which takes the indices as an argument instead of reading them off the pcgs. This does not yet change what is stamped onto a central pcgs; it removes the library's dependence on that stamp, which is a prerequisite for doing so. AI disclosure: this change was prepared with the help of Claude Code (Claude Opus 5), which surveyed the affected call sites, drafted the patch, the documentation and the test, and checked the conversion against the previous behaviour; reviewed by me. Co-Authored-By: Claude Opus 5 --- doc/ref/pcgs.xml | 2 + lib/claspcgs.gi | 24 ++++++--- lib/grppc.gi | 18 ++++--- lib/pcgs.gd | 82 ++++++++++++++++++++++++++++++ lib/pcgs.gi | 51 +++++++++++++++++-- tst/testinstall/pcgsseriesinfo.tst | 79 ++++++++++++++++++++++++++++ 6 files changed, 238 insertions(+), 18 deletions(-) create mode 100644 tst/testinstall/pcgsseriesinfo.tst diff --git a/doc/ref/pcgs.xml b/doc/ref/pcgs.xml index 5af593e828..289ea4ff65 100644 --- a/doc/ref/pcgs.xml +++ b/doc/ref/pcgs.xml @@ -373,6 +373,8 @@ series. See also Section for a more explicit possibil <#Include Label="ChiefNormalSeriesByPcgs"> <#Include Label="IndicesNormalSteps"> <#Include Label="NormalSeriesByPcgs"> +<#Include Label="PcgsSeriesInfo"> +<#Include Label="IndicesNormalStepsBounded"> diff --git a/lib/claspcgs.gi b/lib/claspcgs.gi index e5e14dad1a..9f669040b5 100644 --- a/lib/claspcgs.gi +++ b/lib/claspcgs.gi @@ -546,6 +546,7 @@ local G, home, # the group and the home pcgs step, # counter looping over K, L, # members of indstep, # indice normal steps + info, # pcgs and indices of the series used Ldep, # depth of L in pcgs Kp,mK,Lp,mL, # induced and modulo pcgs's LcapH,KcapH, # intersections @@ -627,17 +628,22 @@ local G, home, # the group and the home pcgs # we prescribed a series home:=opt.pcgs; eas:=EANormalSeriesByPcgs(home); + indstep:=IndicesEANormalSteps(home); cent:=false; elif IsPGroup(G) then - home:=PcgsPCentralSeriesPGroup(G); + info:=PcgsPCentralSeriesPGroupInfo(G); + home:=info.pcgs; eas:=PCentralNormalSeriesByPcgsPGroup(home); + indstep:=info.indices; cent:=ReturnTrue; else - home:=PcgsElementaryAbelianSeries(G); + info:=PcgsElementaryAbelianSeriesInfo(G); + home:=info.pcgs; eas:=EANormalSeriesByPcgs(home); + indstep:=info.indices; cent:=function(cl, N, L) return ForAll(N, k -> ForAll @@ -657,10 +663,9 @@ local G, home, # the group and the home pcgs if cent=false then cent:=PcClassFactorCentralityTest; fi; - indstep:=IndicesEANormalSteps(home); # is the series large (but can be rectified)? - step:=IndicesEANormalStepsBounded(home,2^15); + step:=IndicesNormalStepsBounded(home,indstep,2^15); if indstep<>step then indstep:=step; eas:=List(indstep,x->SubgroupByPcgs(GroupOfPcgs(home), @@ -990,6 +995,7 @@ local G,home, # the group and the home pcgs step, # counter looping over K, L, # members of indstep, # indice normal steps + info, # pcgs and indices of the series used Ldep, # depth of L in pcgs Kp,Lp,mL, # induced and modulo pcgs's N, cent, # elementary abelian factor, for affine action @@ -1024,21 +1030,23 @@ local G,home, # the group and the home pcgs # w.r.t. . if IsPGroup(G) then - home:=PcgsPCentralSeriesPGroup(G); + info:=PcgsPCentralSeriesPGroupInfo(G); + home:=info.pcgs; eas:=PCentralNormalSeriesByPcgsPGroup(home); cent:=ReturnTrue; else - home:=PcgsElementaryAbelianSeries(G); + info:=PcgsElementaryAbelianSeriesInfo(G); + home:=info.pcgs; eas:=EANormalSeriesByPcgs(home); cent:=PcClassFactorCentralityTest; fi; - indstep:=IndicesEANormalSteps(home); + indstep:=info.indices; # is the series large (but can be rectified)? - step:=IndicesEANormalStepsBounded(home,2^15); + step:=IndicesNormalStepsBounded(home,indstep,2^15); if indstep<>step then indstep:=step; eas:=List(indstep,x->SubgroupByPcgs(GroupOfPcgs(home), diff --git a/lib/grppc.gi b/lib/grppc.gi index f01a7af92d..e070f9da26 100644 --- a/lib/grppc.gi +++ b/lib/grppc.gi @@ -1271,7 +1271,7 @@ local G, home, # the supergroup (of and ), the home pcgs N, cent, # elementary abelian factor, for affine action cls, # classes in range/source of homomorphism opr, # (elm^opr)=cls.representative - nexpo,indstep,Ldep,allcent; + nexpo,indstep,info,Ldep,allcent; # Treat the case of a trivial group. if IsTrivial( U ) then @@ -1294,16 +1294,18 @@ local G, home, # the supergroup (of and ), the home pcgs # w.r.t. . if IsPGroup( G ) then - home:=PcgsCentralSeries(G); + info:=PcgsCentralSeriesInfo(G); + home:=info.pcgs; eas:=CentralNormalSeriesByPcgs(home); cent:=ReturnTrue; else - home:=PcgsElementaryAbelianSeries(G); + info:=PcgsElementaryAbelianSeriesInfo(G); + home:=info.pcgs; eas:=EANormalSeriesByPcgs(home); cent:=PcClassFactorCentralityTest; fi; - indstep:=IndicesEANormalSteps(home); + indstep:=info.indices; Hp:=InducedPcgs(home,H); @@ -1451,7 +1453,7 @@ local G, # common parent KcapH,LcapH, # pcgs's of intersections with N, cent, # elementary abelian factor, for affine action tra, # transversal for candidates - nexpo,indstep,Ldep,allcent, + nexpo,indstep,info,Ldep,allcent, cl, i; # loop variables # Treat trivial cases. @@ -1479,10 +1481,12 @@ local G, # common parent eas:=fail; if IsPGroup( G ) then - home:=PcgsPCentralSeriesPGroup(G); + info:=PcgsPCentralSeriesPGroupInfo(G); + home:=info.pcgs; eas:=PCentralNormalSeriesByPcgsPGroup(home); if NT in eas then cent := ReturnTrue; + indstep:=info.indices; else eas:=fail; # useless fi; @@ -1492,8 +1496,8 @@ local G, # common parent home:=PcgsElementaryAbelianSeries([G,NT]); eas:=EANormalSeriesByPcgs(home); cent:=PcClassFactorCentralityTest; + indstep:=IndicesEANormalSteps(home); fi; - indstep:=IndicesEANormalSteps(home); # series to NT ea2:=List(eas,i->ClosureGroup(NT,i)); diff --git a/lib/pcgs.gd b/lib/pcgs.gd index e1cf7172d8..8982cf5e98 100644 --- a/lib/pcgs.gd +++ b/lib/pcgs.gd @@ -718,6 +718,88 @@ DeclareAttribute( "IndicesNormalSteps", IsPcgs ); ## DeclareAttribute( "NormalSeriesByPcgs", IsPcgs); + +############################################################################# +## +#A PcgsElementaryAbelianSeriesInfo( ) +#A PcgsCentralSeriesInfo( ) +#A PcgsPCentralSeriesPGroupInfo( ) +#A PcgsChiefSeriesInfo( ) +#O PcgsSeriesInfo( , ) +## +## <#GAPDoc Label="PcgsSeriesInfo"> +## +## Pcgs together with the indices of its series +## +## +## +## +## +## +## +## These attributes return a record with the components pcgs, a pcgs +## for G refining a normal series of the kind in question, and +## indices, the list of indices in this pcgs at which the steps of +## that series start, as described for +## . +##

+## Obtaining both from one call is preferable to obtaining the pcgs and +## then asking it for its indices: a pcgs can belong to several series, and +## the indices stored on it need not be the ones of the series that was +## asked for. +##

+## dispatches on filt, which must be one +## of the properties , +## , +## or +## , and returns what the corresponding +## attribute returns. It is a tag based operation +## (see ), so a package can support +## further kinds of series by installing a method for its own property. +##

+## G := DihedralGroup( 16 );; +## gap> info := PcgsSeriesInfo( IsPcgsCentralSeries, G );; +## gap> info.indices = PcgsCentralSeriesInfo( G ).indices; +## true +## gap> info.indices = IndicesCentralNormalSteps( info.pcgs ); +## true +## ]]> +## +## +## <#/GAPDoc> +## +DeclareAttribute( "PcgsElementaryAbelianSeriesInfo", IsGroup ); +DeclareAttribute( "PcgsCentralSeriesInfo", IsGroup ); +DeclareAttribute( "PcgsPCentralSeriesPGroupInfo", IsGroup ); +DeclareAttribute( "PcgsChiefSeriesInfo", IsGroup ); + +DeclareTagBasedOperation( "PcgsSeriesInfo", [ IsOperation, IsGroup ] ); + + +############################################################################# +## +#F IndicesNormalStepsBounded( , , ) +## +## <#GAPDoc Label="IndicesNormalStepsBounded"> +## +## +## +## +## Let indices be the indices of the steps of a normal series +## refined by pcgs. This function returns a refinement of +## indices, aiming to ensure that no factor of the series is larger +## than bound, without changing pcgs. +##

+## is the special case in which +## the indices are taken from . +## +## +## <#/GAPDoc> +## +DeclareGlobalFunction( "IndicesNormalStepsBounded" ); + + ############################################################################# ## #P IsPrimeOrdersPcgs( ) diff --git a/lib/pcgs.gi b/lib/pcgs.gi index 8b93ad4f6a..9c7f76ec05 100644 --- a/lib/pcgs.gi +++ b/lib/pcgs.gi @@ -1488,12 +1488,57 @@ InstallPcgsSeriesFromIndices(PCentralNormalSeriesByPcgsPGroup, ############################################################################# ## -#M IndicesEANormalStepsBounded( , ) +#M PcgsElementaryAbelianSeriesInfo( ) . . . . pcgs and its own indices +#M PcgsCentralSeriesInfo( ) +#M PcgsPCentralSeriesPGroupInfo( ) +#M PcgsChiefSeriesInfo( ) +#M PcgsSeriesInfo( , ) +## +## Each kind asks for the indices belonging to *its own* series, rather +## than for `IndicesEANormalSteps' which a pcgs may carry for a different +## series it also belongs to. +## +BindGlobal( "InstallPcgsSeriesInfo", function( info, pcgsattr, indices, filt ) + + InstallMethod( info, "from pcgs and its indices", [ IsGroup ], + function( G ) + local pcgs; + pcgs:= pcgsattr( G ); + return rec( pcgs:= pcgs, indices:= indices( pcgs ) ); + end ); + + InstallTagBasedMethod( PcgsSeriesInfo, filt, { f, G } -> info( G ) ); + +end ); + +InstallPcgsSeriesInfo( PcgsElementaryAbelianSeriesInfo, + PcgsElementaryAbelianSeries, IndicesEANormalSteps, + IsPcgsElementaryAbelianSeries ); +InstallPcgsSeriesInfo( PcgsCentralSeriesInfo, + PcgsCentralSeries, IndicesCentralNormalSteps, IsPcgsCentralSeries ); +InstallPcgsSeriesInfo( PcgsPCentralSeriesPGroupInfo, + PcgsPCentralSeriesPGroup, IndicesPCentralNormalStepsPGroup, + IsPcgsPCentralSeriesPGroup ); +InstallPcgsSeriesInfo( PcgsChiefSeriesInfo, + PcgsChiefSeries, IndicesChiefNormalSteps, IsPcgsChiefSeries ); + +InstallTagBasedMethod( PcgsSeriesInfo, function( filt, G ) + Error( "no method for series kind ", NameFunction( filt ), + " installed for `PcgsSeriesInfo'" ); +end ); + +############################################################################# +## +#F IndicesNormalStepsBounded( ,, ) +#F IndicesEANormalStepsBounded( , ) ## InstallGlobalFunction(IndicesEANormalStepsBounded,function(pcgs,bound) -local rel,ind,gp,i,j,try; + return IndicesNormalStepsBounded(pcgs,IndicesEANormalSteps(pcgs),bound); +end); + +InstallGlobalFunction(IndicesNormalStepsBounded,function(pcgs,ind,bound) +local rel,gp,i,j,try; rel:=RelativeOrders(pcgs); - ind:=IndicesEANormalSteps(pcgs); gp:=GroupOfPcgs(pcgs); i:=2; while i<=Length(ind) do diff --git a/tst/testinstall/pcgsseriesinfo.tst b/tst/testinstall/pcgsseriesinfo.tst new file mode 100644 index 0000000000..3f19f2df02 --- /dev/null +++ b/tst/testinstall/pcgsseriesinfo.tst @@ -0,0 +1,79 @@ +#@local G, P, ind, kinds, info, series, checkinfo, isEA, isCentral +gap> START_TEST("pcgsseriesinfo.tst"); + +# the indices returned must describe a normal series refined by the pcgs +# returned, and that series must be of the kind that was asked for +gap> series := info -> List( info.indices, +> i -> SubgroupByPcgs( GroupOfPcgs( info.pcgs ), +> InducedPcgsByPcSequence( info.pcgs, +> info.pcgs{[ i .. Length( info.pcgs ) ]} ) ) );; +gap> isEA := ser -> ForAll( [ 2 .. Length( ser ) ], +> i -> HasElementaryAbelianFactorGroup( ser[i-1], ser[i] ) );; +gap> isCentral := ser -> ForAll( [ 2 .. Length( ser ) ], +> i -> IsSubset( ser[i], CommutatorSubgroup( ser[1], ser[i-1] ) ) );; +gap> checkinfo := function( info, prop ) +> local ser; +> if info.indices[1] <> 1 or +> Last( info.indices ) <> Length( info.pcgs ) + 1 then +> return "indices do not start with 1 or do not end with n+1"; +> fi; +> ser := series( info ); +> if not ForAll( ser, N -> IsNormal( ser[1], N ) ) then +> return "series is not normal"; +> elif not prop( ser ) then +> return "series is not of the kind that was asked for"; +> fi; +> return true; +> end;; + +# pc groups, permutation groups, direct products; the codes are those of +# SmallGroup( 96, 3 ) and SmallGroup( 64, 10 ), spelled out so that this test +# does not need the small groups library +gap> for G in [ DihedralGroup( 16 ), PcGroupCode( 55306968584587147680, 96 ), +> SylowSubgroup( SymmetricGroup( 8 ), 2 ), +> DirectProduct( DihedralGroup( 8 ), CyclicGroup( 4 ) ) ] do +> info := PcgsElementaryAbelianSeriesInfo( G ); +> if checkinfo( info, isEA ) <> true then +> Error( checkinfo( info, isEA ), " for ", G ); +> elif info.indices <> +> PcgsSeriesInfo( IsPcgsElementaryAbelianSeries, G ).indices then +> Error( "tag based dispatch disagrees for ", G ); +> fi; +> od; + +# the central kinds need a nilpotent group; their indices must be the ones of +# the central series, not the ones of some elementary abelian series which +# the same pcgs may also belong to +gap> for G in [ DihedralGroup( 16 ), PcGroupCode( 217336074077211757, 64 ), +> SylowSubgroup( SymmetricGroup( 8 ), 2 ) ] do +> for kinds in [ [ PcgsCentralSeriesInfo, IsPcgsCentralSeries, +> IndicesCentralNormalSteps ], +> [ PcgsPCentralSeriesPGroupInfo, +> IsPcgsPCentralSeriesPGroup, +> IndicesPCentralNormalStepsPGroup ] ] do +> info := kinds[1]( G ); +> if checkinfo( info, isCentral ) <> true then +> Error( checkinfo( info, isCentral ), " for ", G ); +> elif info.indices <> kinds[3]( info.pcgs ) then +> Error( "indices are not the ones of this kind, for ", G ); +> elif info.indices <> PcgsSeriesInfo( kinds[2], G ).indices then +> Error( "tag based dispatch disagrees for ", G ); +> fi; +> od; +> od; + +# an unknown kind is reported as such +gap> PcgsSeriesInfo( IsFinite, DihedralGroup( 8 ) ); +Error, no method for series kind IsFinite installed for `PcgsSeriesInfo' + +# refining indices, without taking them off the pcgs +gap> P := SylowSubgroup( SymmetricGroup( 8 ), 2 );; +gap> info := PcgsPCentralSeriesPGroupInfo( P );; +gap> IndicesNormalStepsBounded( info.pcgs, info.indices, 2^15 ) = info.indices; +true +gap> ind := IndicesNormalStepsBounded( info.pcgs, info.indices, 2 );; +gap> IsSubset( ind, info.indices ) and Length( ind ) >= Length( info.indices ); +true + +# that's all, folks +gap> STOP_TEST( "pcgsseriesinfo.tst" );