From d83bbfd7848fc4c4bfef81cd5a06e44b097d42af Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 16 Aug 2026 14:59:25 +0200 Subject: [PATCH 1/8] Add SmallGroupsAddLayer Extending the library meant allocating two numbers and filling six global arrays; SOTGrps spends 70 lines on it, sglppow the same. Nothing checked that two packages picked different slots, and load order decided which of two overlapping layers won. A layer is now one record. It carries a name, which is what other layers refer to it by: `before` and `after` name the layers this one wants to be consulted before or after, and a name that is not registered is ignored, so wishing about a package that is not loaded does no harm. Neither number is the layer's business any more -- `available` is wrapped so that `lib` and `func` are filled in on the way out, which is also what lets a later registration reorder earlier ones. Registered layers take consecutive slots behind every layer added the old way, so registering one never moves another across a slot this code did not hand out, and released sglppow and SOTGrps keep working unchanged. Ported SOTGrps' Integration.gi as a check: 70 lines to 20, and its own test suite passes. Co-Authored-By: Claude Opus 5 --- CHANGES.md | 4 + doc/overview.xml | 5 +- gap/addlayer.gi | 204 ++++++++++++++++++++++++++++++++++++++++ gap/small.gd | 87 +++++++++++++++++ read.g | 3 + tst/addlayer.tst | 239 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 541 insertions(+), 1 deletion(-) create mode 100644 gap/addlayer.gi create mode 100644 tst/addlayer.tst diff --git a/CHANGES.md b/CHANGES.md index 2322e5e..bd31453 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ This file describes changes in the smallgrp package. # Unreleased + - Added `SmallGroupsAddLayer` for registering a further layer of the + library from another package, in place of filling six global arrays by + hand. Layers now have names, and use them to say which other layers they + want to be consulted before or after (issue #67). - Optimized `NumberSmallGroups` to be much faster in certain cases, for example `NumberSmallGroups(1536, IsSolvableGroup, true)` is now instant instead of running for 90 seconds. diff --git a/doc/overview.xml b/doc/overview.xml index 4c3c9b1..4d8be7a 100644 --- a/doc/overview.xml +++ b/doc/overview.xml @@ -97,7 +97,9 @@ For more information, refer to . Related packages Several other ⪆ packages give access to groups of orders which are not -covered by this library, or which are covered only in part. +covered by this library, or which are covered only in part. A package can +make its groups available through the functions of this chapter by calling +.

SglPPow package @@ -137,6 +139,7 @@ the one used here. See https://gap-packages.github.io/sotgrps/. <#Include Label="IdsOfAllSmallGroups"> <#Include Label="IdGap3SolvableGroup"> <#Include Label="SmallGroupsInformation"> + <#Include Label="SmallGroupsAddLayer"> <#Include Label="UnloadSmallGroupsData"> <#Include Label="SMALL_GROUPS_OLD_ORDER"> diff --git a/gap/addlayer.gi b/gap/addlayer.gi new file mode 100644 index 0000000..a17eeb7 --- /dev/null +++ b/gap/addlayer.gi @@ -0,0 +1,204 @@ +############################################################################# +## +#W addlayer.gi GAP group library Max Horn +## +## Layers added from outside this package. +## + +############################################################################# +## +#V SMALL_GROUPS_LAYERS +## +## the layers registered by 'SmallGroupsAddLayer', keyed by name. +SMALL_GROUPS_LAYERS := rec(); + +############################################################################# +## +#F SMALL_GROUPS_LAYER_ORDER( layers ) +## +## in an order meeting every 'before' and 'after' wish, taking at +## each step the earliest registered of those not waiting on another. A wish +## naming a layer that is not registered is ignored, so wishing about a +## package that is not loaded does no harm. +SMALL_GROUPS_LAYER_ORDER := function( layers ) + local names, pred, layer, other, order, done, next; + + names := List( layers, l -> l.name ); + pred := rec(); + for layer in layers do + pred.( layer.name ) := [ ]; + od; + for layer in layers do + for other in layer.after do + if other in names then + AddSet( pred.( layer.name ), other ); + fi; + od; + for other in layer.before do + if other in names then + AddSet( pred.( other ), layer.name ); + fi; + od; + od; + + order := [ ]; + done := [ ]; + while Length( order ) < Length( layers ) do + next := First( layers, l -> not l.name in done and + IsSubset( done, pred.( l.name ) ) ); + if next = fail then + Error( "SmallGroupsAddLayer: the layers ", + JoinStringsWithSeparator( + Filtered( names, n -> not n in done ), ", " ), + " ask for an order that cannot be met" ); + fi; + Add( order, next ); + AddSet( done, next.name ); + od; + return order; +end; + +############################################################################# +## +#F SMALL_GROUPS_LAYER_INSTALL( order ) +## +## puts the registered layers into consecutive slots, in the given order, +## behind every layer added the old way. Registering one layer thus never +## moves another across a slot this function did not hand out. +SMALL_GROUPS_LAYER_INSTALL := function( order ) + local ours, base, i, layer; + + ours := Set( order, l -> l.lib ); + base := Maximum( Concatenation( [ 0 ], + Filtered( [ 1 .. Length( SMALL_AVAILABLE_FUNCS ) ], + i -> IsBound( SMALL_AVAILABLE_FUNCS[ i ] ) + and not i in ours ) ) ); + for i in ours do + Unbind( SMALL_AVAILABLE_FUNCS[ i ] ); + Unbind( ID_AVAILABLE_FUNCS[ i ] ); + od; + + for i in [ 1 .. Length( order ) ] do + layer := order[ i ]; + layer.lib := base + i; + SMALL_AVAILABLE_FUNCS[ layer.lib ] := layer.smallAvailable; + if IsBound( layer.id ) then + ID_AVAILABLE_FUNCS[ layer.lib ] := layer.smallAvailable; + fi; + od; +end; + +############################################################################# +## +#F SmallGroupsAddLayer( desc ) +## +InstallGlobalFunction( SmallGroupsAddLayer, function( desc ) + local known, comp, layer, layers, order; + + if not IsRecord( desc ) then + Error( " must be a record" ); + fi; + + known := [ "name", "available", "group", "id", "information", "number", + "select", "count", "before", "after" ]; + for comp in RecNames( desc ) do + if not comp in known then + Error( "unknown component .", comp ); + fi; + od; + for comp in [ "name", "available", "group" ] do + if not IsBound( desc.( comp ) ) then + Error( ".", comp, " must be given" ); + fi; + od; + if not IsString( desc.name ) or IsEmpty( desc.name ) then + Error( ".name must be a non-empty string" ); + fi; + if IsBound( SMALL_GROUPS_LAYERS.( desc.name ) ) then + Error( "a layer named \"", desc.name, "\" is already registered" ); + fi; + for comp in [ "available", "group", "id", "information", "number", + "select", "count" ] do + if IsBound( desc.( comp ) ) and not IsFunction( desc.( comp ) ) then + Error( ".", comp, " must be a function" ); + fi; + od; + for comp in [ "before", "after" ] do + if IsBound( desc.( comp ) ) and + not ( IsList( desc.( comp ) ) and ForAll( desc.( comp ), IsString ) ) + then + Error( ".", comp, " must be a list of layer names" ); + fi; + od; + + layer := ShallowCopy( desc ); + for comp in [ "before", "after" ] do + if not IsBound( layer.( comp ) ) then + layer.( comp ) := [ ]; + fi; + od; + layer.seq := Length( RecNames( SMALL_GROUPS_LAYERS ) ) + 1; + layer.lib := Length( SMALL_AVAILABLE_FUNCS ) + 1; + layer.func := Maximum( List( [ SMALL_GROUP_FUNCS, + CODE_SMALL_GROUP_FUNCS, + NUMBER_SMALL_GROUPS_FUNCS, + SELECT_SMALL_GROUPS_FUNCS, + COUNT_SMALL_GROUPS_FUNCS, + SMALL_GROUPS_PROPERTIES_FUNCS, + SMALL_GROUPS_INFORMATION, + ID_GROUP_FUNCS ], Length ) ) + 1; + + # the layer never sees 'lib' or 'func', so its own record is passed on + # with both filled in + layer.smallAvailable := function( size ) + local r; + r := layer.available( size ); + if r = fail then + return fail; + elif not IsRecord( r ) then + Error( "the 'available' function of layer ", layer.name, + " must return 'fail' or a record" ); + fi; + r := ShallowCopy( r ); + r.lib := layer.lib; + r.func := layer.func; + return r; + end; + + # settle the order before anything is written, so a rejected wish leaves + # the library as it was + layers := List( RecNames( SMALL_GROUPS_LAYERS ), + n -> SMALL_GROUPS_LAYERS.( n ) ); + SortBy( layers, l -> l.seq ); + Add( layers, layer ); + order := SMALL_GROUPS_LAYER_ORDER( layers ); + + SMALL_GROUP_FUNCS[ layer.func ] := layer.group; + if IsBound( layer.id ) then + ID_GROUP_FUNCS[ layer.func ] := layer.id; + fi; + if IsBound( layer.number ) then + NUMBER_SMALL_GROUPS_FUNCS[ layer.func ] := function( size, inforec ) + inforec := ShallowCopy( inforec ); + inforec.number := layer.number( size, inforec ); + return inforec; + end; + fi; + if IsBound( layer.select ) then + SELECT_SMALL_GROUPS_FUNCS[ layer.func ] := layer.select; + else + SELECT_SMALL_GROUPS_FUNCS[ layer.func ] := SMALL_GROUPS_SELECT_GENERIC; + COUNT_SMALL_GROUPS_FUNCS[ layer.func ] := SMALL_GROUPS_COUNT_GENERIC; + fi; + if IsBound( layer.count ) then + COUNT_SMALL_GROUPS_FUNCS[ layer.func ] := layer.count; + fi; + if IsBound( layer.information ) then + SMALL_GROUPS_INFORMATION[ layer.func ] := layer.information; + else + SMALL_GROUPS_INFORMATION[ layer.func ] := ReturnTrue; + fi; + + SMALL_GROUPS_LAYERS.( layer.name ) := layer; + SMALL_GROUPS_LAYER_INSTALL( order ); +end ); diff --git a/gap/small.gd b/gap/small.gd index c8b8a24..38b8bce 100644 --- a/gap/small.gd +++ b/gap/small.gd @@ -977,6 +977,93 @@ DeclareGlobalFunction( "IdStandardPresented512Group" ); ## DeclareGlobalFunction( "SmallGroupsInformation" ); +############################################################################# +## +#F SmallGroupsAddLayer( ) +## +## <#GAPDoc Label="SmallGroupsAddLayer"> +## +## +## +## +## adds a further layer to the library, described by the record +## desc. Its groups then become available through +## , +## , and the +## other functions of this chapter. The components of desc are: +## +## name +## +## a string naming the layer. It is how other layers refer to this one, +## and no two layers may share it. +## +## available +## +## a function taking an order and returning fail, or a record +## which is handed to the functions below as inforec. Its +## component number, if present, is the number of groups of that +## order. +## +## group +## +## a function ( order, i, inforec ) returning +## the i-th group of that order. +## +## id +## +## optional, a function ( G, inforec ) returning the +## number of G in this layer. Without it +## stays unavailable for these orders. +## +## number +## +## optional, a function ( order, inforec ) returning +## the number of groups of that order. Needed only where +## available does not report it. +## +## information +## +## optional, a function +## ( order, inforec, number ) printing what +## should say about that order +## beyond the number of groups. +## +## select, count +## +## optional, how and +## are to work through this layer. By +## default the groups of the order are constructed one by one and tested. +## +## before, after +## +## optional lists of names of other layers, to be consulted after +## respectively before this one. A name that is not registered is +## ignored, so wishing about a package that is not loaded does no harm. +## The layers of this package come first, and behind them any layer that +## was added without this function. +## +## +## Where two layers cover an order, the one consulted first wins. +## The layers added this way are collected in the record +## SMALL_GROUPS_LAYERS, keyed by name. +## SOTGroup( order, i ), +## id := { G, inforec } -> IdSOTGroup( G )[2] ) ); +## ]]> +## +## +## <#/GAPDoc> +## +DeclareGlobalFunction( "SmallGroupsAddLayer" ); + ############################################################################# ## #A IdGap3SolvableGroup( ) diff --git a/read.g b/read.g index d6b9e1f..5919a58 100644 --- a/read.g +++ b/read.g @@ -10,6 +10,9 @@ ReadPackage( "smallgrp", "gap/idgrp1.g" ); # read the information function ReadPackage( "smallgrp", "gap/smlinfo.gi" ); +# read the API for adding further layers +ReadPackage( "smallgrp", "gap/addlayer.gi" ); + # read the function-files of the small groups library READ_SMALL_LIB := function() local i, s, LoadFunc; diff --git a/tst/addlayer.tst b/tst/addlayer.tst new file mode 100644 index 0000000..ce724a3 --- /dev/null +++ b/tst/addlayer.tst @@ -0,0 +1,239 @@ +gap> START_TEST("addlayer.tst"); +gap> saved := rec( small := ShallowCopy( SMALL_AVAILABLE_FUNCS ), +> id := ShallowCopy( ID_AVAILABLE_FUNCS ) );; + +# +# The layers below are stand-ins. None of the orders they claim -- 2016, +# 2025, 2040 -- has a layer in this library, and none of them hands out all +# the groups of its orders; they hand out enough to watch the plumbing. +# +gap> SmallGroupsAddLayer( rec( +> name := "two of them", +> available := function( order ) +> if order <> 2016 then +> return fail; +> fi; +> return rec( number := 2 ); +> end, +> group := function( order, i, inforec ) +> if i = 1 then +> return CyclicGroup( order ); +> fi; +> return DihedralGroup( order ); +> end, +> id := function( G, inforec ) +> if IsAbelian( G ) then +> return 1; +> fi; +> return 2; +> end, +> information := function( order, inforec, num ) +> Print( "\n Namely the cyclic and the dihedral one.\n" ); +> end ) ); +gap> SmallGroupsAvailable( 2016 ); +true +gap> IdGroupsAvailable( 2016 ); +true +gap> NumberSmallGroups( 2016 ); +2 +gap> List( [ 1, 2 ], i -> IdGroup( SmallGroup( 2016, i ) ) ); +[ [ 2016, 1 ], [ 2016, 2 ] ] +gap> IdsOfAllSmallGroups( 2016, IsAbelian, true ); +[ [ 2016, 1 ] ] +gap> NumberSmallGroups( 2016, IsAbelian, false ); +1 +gap> SmallGroupsInformation( 2016 ); + + There are 2 groups of order 2016. + + Namely the cyclic and the dihedral one. + + This size belongs to layer 12 of the SmallGroups library. + IdSmallGroup is available for this size. + + +# +# The layer is registered by name, behind the layers of this library. +# +gap> SMALL_GROUPS_LAYERS.("two of them").lib > 11; +true +gap> SmallGroupsAddLayer( rec( name := "two of them", +> available := ReturnFail, +> group := ReturnFail ) ); +Error, a layer named "two of them" is already registered +gap> SmallGroupsAddLayer( rec( name := "typo", available := ReturnFail, +> group := ReturnFail, ids := ReturnFail ) ); +Error, unknown component .ids +gap> SmallGroupsAddLayer( rec( name := "sparse", available := ReturnFail ) ); +Error, .group must be given +gap> SmallGroupsAddLayer( 42 ); +Error, must be a record +gap> SmallGroupsAddLayer( rec( name := 42, available := ReturnFail, +> group := ReturnFail ) ); +Error, .name must be a non-empty string +gap> SmallGroupsAddLayer( rec( name := "", available := ReturnFail, +> group := ReturnFail ) ); +Error, .name must be a non-empty string +gap> SmallGroupsAddLayer( rec( name := "odd", available := 42, +> group := ReturnFail ) ); +Error, .available must be a function +gap> SmallGroupsAddLayer( rec( name := "odd", available := ReturnFail, +> group := ReturnFail, before := [ 42 ] ) ); +Error, .before must be a list of layer names + +# +# Layers all claiming order 2025, to watch the order they are consulted in. +# What they hand out is beside the point; only which of them answers is. +# +gap> claim := function( name, number, wishes ) +> local desc; +> desc := ShallowCopy( wishes ); +> desc.name := name; +> desc.available := function( order ) +> if order <> 2025 then +> return fail; +> fi; +> return rec( number := number ); +> end; +> desc.group := { order, i, inforec } -> CyclicGroup( order ); +> SmallGroupsAddLayer( desc ); +> end;; +gap> claim( "alpha", 3, rec() ); +gap> NumberSmallGroups( 2025 ); +3 +gap> claim( "beta", 4, rec( before := [ "alpha" ] ) ); +gap> NumberSmallGroups( 2025 ); +4 +gap> claim( "gamma", 5, rec( after := [ "beta" ], before := [ "alpha" ] ) ); +gap> NumberSmallGroups( 2025 ); +4 +gap> List( [ "beta", "gamma", "alpha" ], n -> SMALL_GROUPS_LAYERS.( n ).lib ); +[ 13, 14, 15 ] + +# a wish about a layer that is not registered is ignored +gap> claim( "delta", 6, rec( after := [ "not loaded" ] ) ); +gap> NumberSmallGroups( 2025 ); +4 + +# wishes that cannot all be met are refused, and nothing is registered +gap> claim( "loop", 7, rec( before := [ "alpha" ], after := [ "alpha" ] ) ); +Error, SmallGroupsAddLayer: the layers alpha, loop ask for an order that canno\ +t be met +gap> IsBound( SMALL_GROUPS_LAYERS.loop ); +false +gap> NumberSmallGroups( 2025 ); +4 + +# without an 'id' function the identification stays unavailable +gap> IdGroupsAvailable( 2025 ); +false + +# +# 'number' where 'available' does not report the count, and 'count' where the +# layer knows better than to construct the groups. +# +gap> SmallGroupsAddLayer( rec( +> name := "counted", +> available := function( order ) +> if order = 2040 then +> return rec(); +> fi; +> return fail; +> end, +> group := { order, i, inforec } -> CyclicGroup( order ), +> number := { order, inforec } -> 7, +> count := { order, funcs, vals, inforec, idList } -> 42 ) ); +gap> NumberSmallGroups( 2040 ); +7 +gap> NumberSmallGroups( 2040, IsAbelian, true ); +42 + +# +# A layer that selects for itself: no generic count is installed over it, so +# the counting goes through its own selection. +# +gap> SmallGroupsAddLayer( rec( +> name := "selective", +> available := function( order ) +> if order = 2052 then +> return rec( number := 2 ); +> fi; +> return fail; +> end, +> group := { order, i, inforec } -> CyclicGroup( order ), +> select := function( order, funcs, vals, inforec, all, id, idList ) +> if not all then +> return CyclicGroup( order ); +> elif id then +> return [ [ order, 1 ], [ order, 2 ] ]; +> fi; +> return [ CyclicGroup( order ), CyclicGroup( order ) ]; +> end ) ); +gap> IsBound( COUNT_SMALL_GROUPS_FUNCS[ SMALL_GROUPS_LAYERS.selective.func ] ); +false +gap> IdsOfAllSmallGroups( 2052, IsAbelian, true ); +[ [ 2052, 1 ], [ 2052, 2 ] ] +gap> NumberSmallGroups( 2052, IsAbelian, true ); +2 +gap> IsCyclic( OneSmallGroup( 2052, IsAbelian, true ) ); +true + +# +# 'available' has to return 'fail' or a record +# +gap> SmallGroupsAddLayer( rec( +> name := "confused", +> available := function( order ) +> if order = 2058 then +> return 42; +> fi; +> return fail; +> end, +> group := ReturnFail ) ); +gap> SmallGroupsAvailable( 2058 ); +Error, the 'available' function of layer confused must return 'fail' or a reco\ +rd + +# +# A layer added the old way, by filling the arrays directly, keeps its slot, +# and the registered layers move behind it. +# +gap> old := Length( SMALL_AVAILABLE_FUNCS ) + 1;; +gap> SMALL_AVAILABLE_FUNCS[ old ] := function( order ) +> if order <> 2079 then +> return fail; +> fi; +> return rec( lib := old, func := 1000, number := 9 ); +> end;; +gap> NumberSmallGroups( 2079 ); +9 +gap> claim( "epsilon", 8, rec() ); +gap> ForAll( RecNames( SMALL_GROUPS_LAYERS ), +> n -> SMALL_GROUPS_LAYERS.( n ).lib > old ); +true +gap> NumberSmallGroups( 2079 ); +9 +gap> NumberSmallGroups( 2025 ); +4 + +# being contiguous, they can still meet a wish that spans that layer +gap> claim( "zeta", 9, rec( before := [ "beta" ] ) ); +gap> SMALL_GROUPS_LAYERS.zeta.lib < SMALL_GROUPS_LAYERS.beta.lib; +true +gap> SMALL_GROUPS_LAYERS.zeta.lib > old; +true +gap> NumberSmallGroups( 2079 ); +9 + +# +# put the library back as it was, so the stand-ins do not follow the rest of +# the tests around +# +gap> SMALL_GROUPS_LAYERS := rec();; +gap> SMALL_AVAILABLE_FUNCS := saved.small;; +gap> ID_AVAILABLE_FUNCS := saved.id;; +gap> List( [ 2016, 2025, 2040, 2052 ], SmallGroupsAvailable ); +[ false, false, false, false ] + +# +gap> STOP_TEST( "addlayer.tst", 1); From 843fa6ba3abe176a7a7fba3d85f00f2891c05044 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 16 Aug 2026 16:07:01 +0200 Subject: [PATCH 2/8] Dispatch through the layer, not through two numbers The layers of this package become one layer named "SmallGrp", whose methods read the global arrays a layer used to be installed in -- and so also cover those still added that way from outside. Every other layer is a record of the same shape appended after it, and SMALL_AVAILABLE hands the layer that answered back in the inforec, so the high-level functions call its methods directly. What this deletes: the slot arithmetic over eight arrays, the layer and func numbers a registered layer carried, and all of the placement code -- the relocation rule and its hazard go with it, since a layer added the old way is now inside "SmallGrp" rather than between two registered ones. Two components the record gained, because the compat layer cannot be expressed without them: 'idAvailable', since the identification covers other orders than the construction and under other func numbers (id2 stops at 1000, id3 uses func 13, which SMALL_GROUP_FUNCS does not even have), and 'properties', the indexing hook, which registered layers previously had no way to supply. Naming a layer in 'before' now includes "SmallGrp", so a layer may put itself in front of this library. Nothing under small*/ or id*/ is touched: those read inforec.func and inforec.lib, and "SmallGrp" hands their own inforec straight back. Co-Authored-By: Claude Opus 5 --- CHANGES.md | 4 +- gap/addlayer.gi | 209 +++++++++++++++++++++++++++-------------------- gap/small.gd | 19 ++++- gap/small.gi | 70 +++++++++------- gap/smlinfo.gi | 41 ++++++---- tst/addlayer.tst | 71 ++++++++++------ 6 files changed, 253 insertions(+), 161 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bd31453..ec226ed 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,9 @@ This file describes changes in the smallgrp package. - Added `SmallGroupsAddLayer` for registering a further layer of the library from another package, in place of filling six global arrays by hand. Layers now have names, and use them to say which other layers they - want to be consulted before or after (issue #67). + want to be consulted before or after (issue #67). `SmallGroupsInformation` + names the layer an order belongs to where it is one of these, rather than + numbering it. - Optimized `NumberSmallGroups` to be much faster in certain cases, for example `NumberSmallGroups(1536, IsSolvableGroup, true)` is now instant instead of running for 90 seconds. diff --git a/gap/addlayer.gi b/gap/addlayer.gi index a17eeb7..60b1630 100644 --- a/gap/addlayer.gi +++ b/gap/addlayer.gi @@ -2,25 +2,105 @@ ## #W addlayer.gi GAP group library Max Horn ## -## Layers added from outside this package. +## The layers of the library, and the API for adding more. ## ############################################################################# ## #V SMALL_GROUPS_LAYERS ## -## the layers registered by 'SmallGroupsAddLayer', keyed by name. +## every layer, keyed by name. 'SMALL_GROUPS_LAYER_LIST' holds the same +## records in the order they are consulted. SMALL_GROUPS_LAYERS := rec(); ############################################################################# ## -#F SMALL_GROUPS_LAYER_ORDER( layers ) +#V SMALL_GROUPS_LAYERS.SmallGrp +## +## the layers of this package, as one layer: it reads the global arrays a +## layer used to be installed in, and so also covers those still added that +## way from outside. Converting one of the eleven to the interface below is +## then a matter of registering it and letting it shadow this one. +SMALL_GROUPS_LAYERS.SmallGrp := rec( + name := "SmallGrp", + before := [ ], + after := [ ], + + available := function( size ) + local l, r; + for l in [ 1 .. Length( SMALL_AVAILABLE_FUNCS ) ] do + if IsBound( SMALL_AVAILABLE_FUNCS[ l ] ) then + r := SMALL_AVAILABLE_FUNCS[ l ]( size ); + if r <> fail then + return r; + fi; + fi; + od; + return fail; + end, + + idAvailable := function( size ) + local l, r; + for l in [ 1 .. Length( ID_AVAILABLE_FUNCS ) ] do + if IsBound( ID_AVAILABLE_FUNCS[ l ] ) then + r := ID_AVAILABLE_FUNCS[ l ]( size ); + if r <> fail then + return r; + fi; + fi; + od; + return fail; + end, + + group := function( size, i, inforec ) + return SMALL_GROUP_FUNCS[ inforec.func ]( size, i, inforec ); + end, + + id := function( G, inforec ) + return ID_GROUP_FUNCS[ inforec.func ]( G, inforec ); + end, + + numberOf := function( size, inforec ) + return NUMBER_SMALL_GROUPS_FUNCS[ inforec.func ]( size, inforec ); + end, + + properties := function( size, inforec ) + if not IsBound( SMALL_GROUPS_PROPERTIES_FUNCS[ inforec.func ] ) then + return fail; + fi; + return SMALL_GROUPS_PROPERTIES_FUNCS[ inforec.func ]( size, inforec ); + end, + + select := function( size, funcs, vals, inforec, all, id, idList ) + return SELECT_SMALL_GROUPS_FUNCS[ inforec.func ] + ( size, funcs, vals, inforec, all, id, idList ); + end, + + count := function( size, funcs, vals, inforec, idList ) + if IsBound( COUNT_SMALL_GROUPS_FUNCS[ inforec.func ] ) then + return COUNT_SMALL_GROUPS_FUNCS[ inforec.func ] + ( size, funcs, vals, inforec, idList ); + fi; + return Length( SELECT_SMALL_GROUPS_FUNCS[ inforec.func ] + ( size, funcs, vals, inforec, true, true, idList ) ); + end, + + information := function( size, inforec, num ) + SMALL_GROUPS_INFORMATION[ inforec.func ]( size, inforec, num ); + end, +); + +SMALL_GROUPS_LAYER_LIST[ 1 ] := SMALL_GROUPS_LAYERS.SmallGrp; + +############################################################################# +## +#F SMALL_GROUPS_SORT_LAYERS( layers ) ## ## in an order meeting every 'before' and 'after' wish, taking at ## each step the earliest registered of those not waiting on another. A wish ## naming a layer that is not registered is ignored, so wishing about a ## package that is not loaded does no harm. -SMALL_GROUPS_LAYER_ORDER := function( layers ) +SMALL_GROUPS_SORT_LAYERS := function( layers ) local names, pred, layer, other, order, done, next; names := List( layers, l -> l.name ); @@ -58,49 +138,20 @@ SMALL_GROUPS_LAYER_ORDER := function( layers ) return order; end; -############################################################################# -## -#F SMALL_GROUPS_LAYER_INSTALL( order ) -## -## puts the registered layers into consecutive slots, in the given order, -## behind every layer added the old way. Registering one layer thus never -## moves another across a slot this function did not hand out. -SMALL_GROUPS_LAYER_INSTALL := function( order ) - local ours, base, i, layer; - - ours := Set( order, l -> l.lib ); - base := Maximum( Concatenation( [ 0 ], - Filtered( [ 1 .. Length( SMALL_AVAILABLE_FUNCS ) ], - i -> IsBound( SMALL_AVAILABLE_FUNCS[ i ] ) - and not i in ours ) ) ); - for i in ours do - Unbind( SMALL_AVAILABLE_FUNCS[ i ] ); - Unbind( ID_AVAILABLE_FUNCS[ i ] ); - od; - - for i in [ 1 .. Length( order ) ] do - layer := order[ i ]; - layer.lib := base + i; - SMALL_AVAILABLE_FUNCS[ layer.lib ] := layer.smallAvailable; - if IsBound( layer.id ) then - ID_AVAILABLE_FUNCS[ layer.lib ] := layer.smallAvailable; - fi; - od; -end; - ############################################################################# ## #F SmallGroupsAddLayer( desc ) ## InstallGlobalFunction( SmallGroupsAddLayer, function( desc ) - local known, comp, layer, layers, order; + local known, comp, layer, order; if not IsRecord( desc ) then Error( " must be a record" ); fi; - known := [ "name", "available", "group", "id", "information", "number", - "select", "count", "before", "after" ]; + known := [ "name", "available", "group", "id", "idAvailable", "number", + "properties", "information", "select", "count", + "before", "after" ]; for comp in RecNames( desc ) do if not comp in known then Error( "unknown component .", comp ); @@ -117,8 +168,8 @@ InstallGlobalFunction( SmallGroupsAddLayer, function( desc ) if IsBound( SMALL_GROUPS_LAYERS.( desc.name ) ) then Error( "a layer named \"", desc.name, "\" is already registered" ); fi; - for comp in [ "available", "group", "id", "information", "number", - "select", "count" ] do + for comp in [ "available", "group", "id", "idAvailable", "number", + "properties", "information", "select", "count" ] do if IsBound( desc.( comp ) ) and not IsFunction( desc.( comp ) ) then Error( ".", comp, " must be a function" ); fi; @@ -137,68 +188,52 @@ InstallGlobalFunction( SmallGroupsAddLayer, function( desc ) layer.( comp ) := [ ]; fi; od; - layer.seq := Length( RecNames( SMALL_GROUPS_LAYERS ) ) + 1; - layer.lib := Length( SMALL_AVAILABLE_FUNCS ) + 1; - layer.func := Maximum( List( [ SMALL_GROUP_FUNCS, - CODE_SMALL_GROUP_FUNCS, - NUMBER_SMALL_GROUPS_FUNCS, - SELECT_SMALL_GROUPS_FUNCS, - COUNT_SMALL_GROUPS_FUNCS, - SMALL_GROUPS_PROPERTIES_FUNCS, - SMALL_GROUPS_INFORMATION, - ID_GROUP_FUNCS ], Length ) ) + 1; - - # the layer never sees 'lib' or 'func', so its own record is passed on - # with both filled in - layer.smallAvailable := function( size ) + + # what the layer hands back is checked once, here, rather than wherever + # it later turns out not to be a record + layer.available := function( size ) local r; - r := layer.available( size ); - if r = fail then - return fail; - elif not IsRecord( r ) then + r := desc.available( size ); + if r <> fail and not IsRecord( r ) then Error( "the 'available' function of layer ", layer.name, " must return 'fail' or a record" ); fi; - r := ShallowCopy( r ); - r.lib := layer.lib; - r.func := layer.func; return r; end; - - # settle the order before anything is written, so a rejected wish leaves - # the library as it was - layers := List( RecNames( SMALL_GROUPS_LAYERS ), - n -> SMALL_GROUPS_LAYERS.( n ) ); - SortBy( layers, l -> l.seq ); - Add( layers, layer ); - order := SMALL_GROUPS_LAYER_ORDER( layers ); - - SMALL_GROUP_FUNCS[ layer.func ] := layer.group; - if IsBound( layer.id ) then - ID_GROUP_FUNCS[ layer.func ] := layer.id; + if IsBound( layer.id ) and not IsBound( layer.idAvailable ) then + layer.idAvailable := layer.available; fi; - if IsBound( layer.number ) then - NUMBER_SMALL_GROUPS_FUNCS[ layer.func ] := function( size, inforec ) + + # a layer reports a number, the library carries it in the record + if IsBound( desc.number ) then + layer.numberOf := function( size, inforec ) inforec := ShallowCopy( inforec ); - inforec.number := layer.number( size, inforec ); + inforec.number := desc.number( size, inforec ); return inforec; end; - fi; - if IsBound( layer.select ) then - SELECT_SMALL_GROUPS_FUNCS[ layer.func ] := layer.select; else - SELECT_SMALL_GROUPS_FUNCS[ layer.func ] := SMALL_GROUPS_SELECT_GENERIC; - COUNT_SMALL_GROUPS_FUNCS[ layer.func ] := SMALL_GROUPS_COUNT_GENERIC; + layer.numberOf := function( size, inforec ) + Error( "layer ", layer.name, " reports no number of groups of ", + "order ", size ); + end; fi; - if IsBound( layer.count ) then - COUNT_SMALL_GROUPS_FUNCS[ layer.func ] := layer.count; + + # a layer that selects for itself is not counted generically + if not IsBound( layer.select ) then + layer.select := SMALL_GROUPS_SELECT_GENERIC; + if not IsBound( layer.count ) then + layer.count := SMALL_GROUPS_COUNT_GENERIC; + fi; fi; - if IsBound( layer.information ) then - SMALL_GROUPS_INFORMATION[ layer.func ] := layer.information; - else - SMALL_GROUPS_INFORMATION[ layer.func ] := ReturnTrue; + if not IsBound( layer.information ) then + layer.information := ReturnTrue; fi; + # settle the order before anything is written, so a rejected wish leaves + # the library as it was + order := SMALL_GROUPS_SORT_LAYERS( + Concatenation( SMALL_GROUPS_LAYER_LIST, [ layer ] ) ); + SMALL_GROUPS_LAYERS.( layer.name ) := layer; - SMALL_GROUPS_LAYER_INSTALL( order ); + SMALL_GROUPS_LAYER_LIST := order; end ); diff --git a/gap/small.gd b/gap/small.gd index 38b8bce..ec0f4da 100644 --- a/gap/small.gd +++ b/gap/small.gd @@ -1009,11 +1009,14 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## a function ( order, i, inforec ) returning ## the i-th group of that order. ## -## id +## id, idAvailable ## ## optional, a function ( G, inforec ) returning the ## number of G in this layer. Without it -## stays unavailable for these orders. +## stays unavailable for these orders. Where +## the identification covers fewer orders than the layer, or wants a +## record of its own, idAvailable is a second available +## used in its place. ## ## number ## @@ -1028,6 +1031,14 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## should say about that order ## beyond the number of groups. ## +## properties +## +## optional, a function ( order, inforec ) reporting +## which selection criteria follow from the position a group has in this +## layer, so that and +## need not construct the groups to +## decide them. +## ## select, count ## ## optional, how and @@ -1039,8 +1050,8 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## optional lists of names of other layers, to be consulted after ## respectively before this one. A name that is not registered is ## ignored, so wishing about a package that is not loaded does no harm. -## The layers of this package come first, and behind them any layer that -## was added without this function. +## The layers of this package are themselves one layer, named +## "SmallGrp", which without a wish to the contrary comes first. ## ## ## Where two layers cover an order, the one consulted first wins. diff --git a/gap/small.gi b/gap/small.gi index 9230498..3b5c3cd 100644 --- a/gap/small.gi +++ b/gap/small.gi @@ -7,13 +7,23 @@ ## groups and the group identification routines. ## +############################################################################# +## +#V SMALL_GROUPS_LAYER_LIST +## +## every layer, in the order they are consulted. 'gap/addlayer.gi' puts the +## layers of this package into the first entry and appends those registered +## by 'SmallGroupsAddLayer'. +SMALL_GROUPS_LAYER_LIST := [ ]; + ############################################################################# ## #F SMALL_AVAILABLE_FUNCS ## ## On every level of the small groups library one function is written into ## this list. It will detect those sizes, which are contained in this -## library level. +## library level. Layers registered by 'SmallGroupsAddLayer' do not appear +## here; the layer 'SmallGrp' is what reads this list. SMALL_AVAILABLE_FUNCS := [ ]; ############################################################################# @@ -22,20 +32,20 @@ SMALL_AVAILABLE_FUNCS := [ ]; ## ## returns fail if the library of groups of is not installed. ## Otherwise a record with some information about the construction of the -## groups of is returned. +## groups of is returned. Its component 'layer' is the layer that +## answered, which is how the functions below reach the rest of its methods. InstallGlobalFunction( SMALL_AVAILABLE, function( size ) - local l, r; + local layer, r; if not IsPosInt( size ) then Error( " must be a positive integer"); fi; - for l in [ 1 .. Length( SMALL_AVAILABLE_FUNCS ) ] do - if IsBound( SMALL_AVAILABLE_FUNCS[ l ] ) then - r := SMALL_AVAILABLE_FUNCS[ l ]( size ); - if r <> fail then - return r; - fi; + for layer in SMALL_GROUPS_LAYER_LIST do + r := layer.available( size ); + if r <> fail then + r.layer := layer; + return r; fi; od; return fail; @@ -548,11 +558,14 @@ SMALL_GROUPS_PROPERTY_IDS := function( size, inforec, funcs, vals ) od; ids := [ [ 1 .. inforec.number ] ]; - if ForAll( hits, x -> x = fail ) or - not IsBound( SMALL_GROUPS_PROPERTIES_FUNCS[ inforec.func ] ) then + if ForAll( hits, x -> x = fail ) + or not IsBound( inforec.layer.properties ) then + return rec( ids := ids, funcs := funcs, vals := vals ); + fi; + props := inforec.layer.properties( size, inforec ); + if props = fail then return rec( ids := ids, funcs := funcs, vals := vals ); fi; - props := SMALL_GROUPS_PROPERTIES_FUNCS[ inforec.func ]( size, inforec ); evalfuncs := [ ]; evalvals := [ ]; @@ -613,7 +626,7 @@ SMALL_GROUPS_SELECT_GENERIC := function( size, funcs, vals, inforec, local result, i, g, ok, j, sel, range; if not IsBound( inforec.number ) then - inforec := NUMBER_SMALL_GROUPS_FUNCS[ inforec.func ]( size, inforec); + inforec := inforec.layer.numberOf( size, inforec ); fi; # narrow down the candidates using properties which can be decided from @@ -640,7 +653,7 @@ SMALL_GROUPS_SELECT_GENERIC := function( size, funcs, vals, inforec, result := [ ]; for i in range do - g := SMALL_GROUP_FUNCS[ inforec.func ]( + g := inforec.layer.group( size, SMALL_GROUPS_LIBRARY_NUMBER( size, i ), inforec ); SetIdGroup( g, [ size, i ] ); ok := true; @@ -673,7 +686,7 @@ SMALL_GROUPS_COUNT_GENERIC := function( size, funcs, vals, inforec, idList ) local sel, range, n, i, g; if not IsBound( inforec.number ) then - inforec := NUMBER_SMALL_GROUPS_FUNCS[ inforec.func ]( size, inforec ); + inforec := inforec.layer.numberOf( size, inforec ); fi; sel := SMALL_GROUPS_PROPERTY_IDS( size, inforec, funcs, vals ); @@ -692,7 +705,7 @@ SMALL_GROUPS_COUNT_GENERIC := function( size, funcs, vals, inforec, idList ) n := 0; for i in range do - g := SMALL_GROUP_FUNCS[ inforec.func ]( + g := inforec.layer.group( size, SMALL_GROUPS_LIBRARY_NUMBER( size, i ), inforec ); SetIdGroup( g, [ size, i ] ); if ForAll( [ 1 .. Length( sel.funcs ) ], @@ -719,11 +732,11 @@ SMALL_GROUPS_COUNT := function( argl ) Error( "NumberSmallGroups: groups of order ", size, " not available" ); fi; - if IsBound( COUNT_SMALL_GROUPS_FUNCS[ inforec.func ] ) then - n := n + COUNT_SMALL_GROUPS_FUNCS[ inforec.func ] + if IsBound( inforec.layer.count ) then + n := n + inforec.layer.count ( size, query.funcs, query.vals, inforec, query.idList ); else - n := n + Length( SELECT_SMALL_GROUPS_FUNCS[ inforec.func ] + n := n + Length( inforec.layer.select ( size, query.funcs, query.vals, inforec, true, true, query.idList ) ); fi; @@ -776,7 +789,7 @@ InstallGlobalFunction( SmallGroup, function( arg ) if inforec = fail then Error( "the library of groups of size ", size, " is not available" ); fi; - g := SMALL_GROUP_FUNCS[ inforec.func ]( + g := inforec.layer.group( size, SMALL_GROUPS_LIBRARY_NUMBER( size, i ), inforec ); SetIdGroup( g, [ size, i ] ); IsPGroup( g ); @@ -802,7 +815,7 @@ SMALL_GROUPS_NUMBER := function( size ) if IsBound( inforec.number ) then return inforec.number; fi; - return NUMBER_SMALL_GROUPS_FUNCS[ inforec.func ]( size, inforec ).number; + return inforec.layer.numberOf( size, inforec ).number; end; ############################################################################# @@ -866,7 +879,7 @@ InstallGlobalFunction( SelectSmallGroups, function( argl, all, id ) Error( "AllSmallGroups / OneSmallGroup: groups of order ", size, " not available" ); fi; - gs := SELECT_SMALL_GROUPS_FUNCS[ inforec.func ] + gs := inforec.layer.select ( size, funcs, vals, inforec, all, id, idList ); if all then Append( result, gs ); @@ -893,14 +906,15 @@ ID_AVAILABLE_FUNCS := [ ]; #F ID_AVAILABLE ## InstallGlobalFunction( ID_AVAILABLE, function( size ) - local l, r; + local layer, r; if not IsInt( size ) then return fail; fi; - for l in [ 1 .. Length( ID_AVAILABLE_FUNCS ) ] do - if IsBound( ID_AVAILABLE_FUNCS[ l ] ) then - r := ID_AVAILABLE_FUNCS[ l ]( size ); - if r <> fail then + for layer in SMALL_GROUPS_LAYER_LIST do + if IsBound( layer.idAvailable ) then + r := layer.idAvailable( size ); + if r <> fail then + r.layer := layer; return r; fi; fi; @@ -958,7 +972,7 @@ function( G ) G := PcGroupCode( CodePcGroup( G ), Size( G ) ); fi; - id := ID_GROUP_FUNCS[ inforec.func ]( G, inforec ); + id := inforec.layer.id( G, inforec ); if not SMALL_GROUPS_OLD_ORDER then if size = 3^7 then diff --git a/gap/smlinfo.gi b/gap/smlinfo.gi index 33bb401..1b8c12d 100644 --- a/gap/smlinfo.gi +++ b/gap/smlinfo.gi @@ -74,15 +74,10 @@ InstallGlobalFunction( SmallGroupsInformation, function( size ) return; fi; - lib := 1; - if IsBound( smav.lib ) then - lib := smav.lib; - fi; - if IsBound( smav.number ) then num := smav.number; else - num := NUMBER_SMALL_GROUPS_FUNCS[ smav.func ]( size, smav ).number; + num := smav.layer.numberOf( size, smav ).number; fi; if num = 1 then Print("\n There is 1 group of order ",size,".\n"); @@ -90,25 +85,37 @@ InstallGlobalFunction( SmallGroupsInformation, function( size ) Print("\n There are ",num," groups of order ",size,".\n" ); fi; - SMALL_GROUPS_INFORMATION[ smav.func ]( size, smav, num ); + smav.layer.information( size, smav, num ); # report those properties whose value the selection functions can read # off from the position a group has in this list - if IsBound( SMALL_GROUPS_PROPERTIES_FUNCS[ smav.func ] ) then + if IsBound( smav.layer.properties ) then if not IsBound( smav.number ) then smav.number := num; fi; - props := SMALL_GROUPS_PROPERTIES_FUNCS[ smav.func ]( size, smav ); - names := List( Filtered( Concatenation( - SMALL_GROUPS_INDEXED_PROPERTIES, - SMALL_GROUPS_INDEXED_ATTRIBUTES ), - t -> IsBound( props.( t[ 1 ] ) ) ), - t -> t[ 3 ] ); - SMALL_GROUPS_PRINT_INDEXED( names ); + props := smav.layer.properties( size, smav ); + if props <> fail then + names := List( Filtered( Concatenation( + SMALL_GROUPS_INDEXED_PROPERTIES, + SMALL_GROUPS_INDEXED_ATTRIBUTES ), + t -> IsBound( props.( t[ 1 ] ) ) ), + t -> t[ 3 ] ); + SMALL_GROUPS_PRINT_INDEXED( names ); + fi; fi; - Print("\n This size belongs to layer ",lib, - " of the SmallGroups library. \n"); + if IsBound( smav.lib ) then + lib := smav.lib; + else + lib := 1; + fi; + if smav.layer.name = "SmallGrp" then + Print("\n This size belongs to layer ",lib, + " of the SmallGroups library. \n"); + else + Print("\n This size belongs to the layer \"",smav.layer.name, + "\". \n"); + fi; if idav <> fail then Print(" IdSmallGroup is available for this size. \n \n"); diff --git a/tst/addlayer.tst b/tst/addlayer.tst index ce724a3..c1a8dab 100644 --- a/tst/addlayer.tst +++ b/tst/addlayer.tst @@ -1,6 +1,6 @@ gap> START_TEST("addlayer.tst"); -gap> saved := rec( small := ShallowCopy( SMALL_AVAILABLE_FUNCS ), -> id := ShallowCopy( ID_AVAILABLE_FUNCS ) );; +gap> saved := rec( layers := ShallowCopy( SMALL_GROUPS_LAYER_LIST ), +> avail := ShallowCopy( SMALL_AVAILABLE_FUNCS ) );; # # The layers below are stand-ins. None of the orders they claim -- 2016, @@ -48,15 +48,15 @@ gap> SmallGroupsInformation( 2016 ); Namely the cyclic and the dihedral one. - This size belongs to layer 12 of the SmallGroups library. + This size belongs to the layer "two of them". IdSmallGroup is available for this size. # # The layer is registered by name, behind the layers of this library. # -gap> SMALL_GROUPS_LAYERS.("two of them").lib > 11; -true +gap> Position( SMALL_GROUPS_LAYER_LIST, SMALL_GROUPS_LAYERS.("two of them") ); +2 gap> SmallGroupsAddLayer( rec( name := "two of them", > available := ReturnFail, > group := ReturnFail ) ); @@ -107,8 +107,8 @@ gap> NumberSmallGroups( 2025 ); gap> claim( "gamma", 5, rec( after := [ "beta" ], before := [ "alpha" ] ) ); gap> NumberSmallGroups( 2025 ); 4 -gap> List( [ "beta", "gamma", "alpha" ], n -> SMALL_GROUPS_LAYERS.( n ).lib ); -[ 13, 14, 15 ] +gap> List( SMALL_GROUPS_LAYER_LIST, l -> l.name ); +[ "SmallGrp", "two of them", "beta", "gamma", "alpha" ] # a wish about a layer that is not registered is ignored gap> claim( "delta", 6, rec( after := [ "not loaded" ] ) ); @@ -169,7 +169,7 @@ gap> SmallGroupsAddLayer( rec( > fi; > return [ CyclicGroup( order ), CyclicGroup( order ) ]; > end ) ); -gap> IsBound( COUNT_SMALL_GROUPS_FUNCS[ SMALL_GROUPS_LAYERS.selective.func ] ); +gap> IsBound( SMALL_GROUPS_LAYERS.selective.count ); false gap> IdsOfAllSmallGroups( 2052, IsAbelian, true ); [ [ 2052, 1 ], [ 2052, 2 ] ] @@ -195,45 +195,68 @@ Error, the 'available' function of layer confused must return 'fail' or a reco\ rd # -# A layer added the old way, by filling the arrays directly, keeps its slot, -# and the registered layers move behind it. +# A layer added the old way, by filling the arrays directly, is picked up by +# the layer "SmallGrp": it needs no slot of its own here, and registering a +# further layer leaves it where it is. # -gap> old := Length( SMALL_AVAILABLE_FUNCS ) + 1;; -gap> SMALL_AVAILABLE_FUNCS[ old ] := function( order ) +gap> SMALL_AVAILABLE_FUNCS[ Length( SMALL_AVAILABLE_FUNCS ) + 1 ] := +> function( order ) > if order <> 2079 then > return fail; > fi; -> return rec( lib := old, func := 1000, number := 9 ); +> return rec( lib := 12, func := 1000, number := 9 ); > end;; gap> NumberSmallGroups( 2079 ); 9 +gap> SMALL_AVAILABLE( 2079 ).layer.name; +"SmallGrp" gap> claim( "epsilon", 8, rec() ); -gap> ForAll( RecNames( SMALL_GROUPS_LAYERS ), -> n -> SMALL_GROUPS_LAYERS.( n ).lib > old ); -true gap> NumberSmallGroups( 2079 ); 9 gap> NumberSmallGroups( 2025 ); 4 -# being contiguous, they can still meet a wish that spans that layer +# a wish is met wherever the layers sit gap> claim( "zeta", 9, rec( before := [ "beta" ] ) ); -gap> SMALL_GROUPS_LAYERS.zeta.lib < SMALL_GROUPS_LAYERS.beta.lib; -true -gap> SMALL_GROUPS_LAYERS.zeta.lib > old; -true +gap> List( SMALL_GROUPS_LAYER_LIST, l -> l.name ); +[ "SmallGrp", "two of them", "delta", "counted", "selective", "confused", + "epsilon", "zeta", "beta", "gamma", "alpha" ] gap> NumberSmallGroups( 2079 ); 9 +# +# a layer may put itself in front of this library, and then answers for an +# order this library covers +# +gap> NumberSmallGroups( 96 ); +231 +gap> SmallGroupsAddLayer( rec( +> name := "in front", +> before := [ "SmallGrp" ], +> available := function( order ) +> if order <> 96 then +> return fail; +> fi; +> return rec( number := 1 ); +> end, +> group := { order, i, inforec } -> CyclicGroup( order ) ) ); +gap> Position( SMALL_GROUPS_LAYER_LIST, SMALL_GROUPS_LAYERS.("in front") ) +> < Position( SMALL_GROUPS_LAYER_LIST, SMALL_GROUPS_LAYERS.SmallGrp ); +true +gap> NumberSmallGroups( 96 ); +1 + # # put the library back as it was, so the stand-ins do not follow the rest of # the tests around # -gap> SMALL_GROUPS_LAYERS := rec();; -gap> SMALL_AVAILABLE_FUNCS := saved.small;; -gap> ID_AVAILABLE_FUNCS := saved.id;; +gap> SMALL_GROUPS_LAYERS := rec( SmallGrp := saved.layers[1] );; +gap> SMALL_GROUPS_LAYER_LIST := saved.layers;; +gap> SMALL_AVAILABLE_FUNCS := saved.avail;; gap> List( [ 2016, 2025, 2040, 2052 ], SmallGroupsAvailable ); [ false, false, false, false ] +gap> NumberSmallGroups( 96 ); +231 # gap> STOP_TEST( "addlayer.tst", 1); From ac19b7be066c53ab7a2225d058c3ef5c11e34ea2 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 16 Aug 2026 17:19:24 +0200 Subject: [PATCH 3/8] tighten --- CHANGES.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ec226ed..4ce5419 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,13 +2,9 @@ This file describes changes in the smallgrp package. # Unreleased - - Added `SmallGroupsAddLayer` for registering a further layer of the - library from another package, in place of filling six global arrays by - hand. Layers now have names, and use them to say which other layers they - want to be consulted before or after (issue #67). `SmallGroupsInformation` - names the layer an order belongs to where it is one of these, rather than - numbering it. - - Optimized `NumberSmallGroups` to be much faster in certain cases, for + - Added `SmallGroupsAddLayer` as a clean interface for extending the + Small Groups Library by a further layer. + - Optimized `NumberSmallGroups` to be much faster in certain cases. For example `NumberSmallGroups(1536, IsSolvableGroup, true)` is now instant instead of running for 90 seconds. From 2e52a89285287bafa34d23247576a5d89f18a5c6 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Mon, 17 Aug 2026 00:41:12 +0200 Subject: [PATCH 4/8] Do not assume the reader knows the old layer machinery Co-Authored-By: Claude Opus 5 --- gap/addlayer.gi | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gap/addlayer.gi b/gap/addlayer.gi index 60b1630..488ddee 100644 --- a/gap/addlayer.gi +++ b/gap/addlayer.gi @@ -17,10 +17,9 @@ SMALL_GROUPS_LAYERS := rec(); ## #V SMALL_GROUPS_LAYERS.SmallGrp ## -## the layers of this package, as one layer: it reads the global arrays a -## layer used to be installed in, and so also covers those still added that -## way from outside. Converting one of the eleven to the interface below is -## then a matter of registering it and letting it shadow this one. +## one layer standing for all those installed the old way, by filling +## 'SMALL_AVAILABLE_FUNCS' and the other global arrays it reads below: the +## layers built into this package, and any a package adds that way. SMALL_GROUPS_LAYERS.SmallGrp := rec( name := "SmallGrp", before := [ ], From b1bdca08f2844d2af5f78df0e4224ff1c4e4c429 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Mon, 17 Aug 2026 09:15:43 +0200 Subject: [PATCH 5/8] Cover the layer that reports no number at all codecov put the whole patch at 214/216: the two lines missed were the default 'numberOf', which errors when a layer supplies neither 'number' nor a number in the record its 'available' returns. Co-Authored-By: Claude Opus 5 --- tst/addlayer.tst | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tst/addlayer.tst b/tst/addlayer.tst index c1a8dab..a443d49 100644 --- a/tst/addlayer.tst +++ b/tst/addlayer.tst @@ -148,6 +148,19 @@ gap> NumberSmallGroups( 2040 ); gap> NumberSmallGroups( 2040, IsAbelian, true ); 42 +# a layer has to report the number one way or the other +gap> SmallGroupsAddLayer( rec( +> name := "countless", +> available := function( order ) +> if order = 2064 then +> return rec(); +> fi; +> return fail; +> end, +> group := ReturnFail ) ); +gap> NumberSmallGroups( 2064 ); +Error, layer countless reports no number of groups of order 2064 + # # A layer that selects for itself: no generic count is installed over it, so # the counting goes through its own selection. @@ -219,8 +232,8 @@ gap> NumberSmallGroups( 2025 ); # a wish is met wherever the layers sit gap> claim( "zeta", 9, rec( before := [ "beta" ] ) ); gap> List( SMALL_GROUPS_LAYER_LIST, l -> l.name ); -[ "SmallGrp", "two of them", "delta", "counted", "selective", "confused", - "epsilon", "zeta", "beta", "gamma", "alpha" ] +[ "SmallGrp", "two of them", "delta", "counted", "countless", "selective", + "confused", "epsilon", "zeta", "beta", "gamma", "alpha" ] gap> NumberSmallGroups( 2079 ); 9 @@ -253,8 +266,8 @@ gap> NumberSmallGroups( 96 ); gap> SMALL_GROUPS_LAYERS := rec( SmallGrp := saved.layers[1] );; gap> SMALL_GROUPS_LAYER_LIST := saved.layers;; gap> SMALL_AVAILABLE_FUNCS := saved.avail;; -gap> List( [ 2016, 2025, 2040, 2052 ], SmallGroupsAvailable ); -[ false, false, false, false ] +gap> List( [ 2016, 2025, 2040, 2052, 2064 ], SmallGroupsAvailable ); +[ false, false, false, false, false ] gap> NumberSmallGroups( 96 ); 231 From f030b00711c04f4dea589a78682be83b565f3cb6 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 18 Aug 2026 16:27:09 +0200 Subject: [PATCH 6/8] Tweak documentation --- gap/small.gd | 45 ++++++++++++++++++++++++--------------------- gap/small.gi | 8 ++++---- gap/smlinfo.gi | 5 ++--- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/gap/small.gd b/gap/small.gd index ec0f4da..bdeb436 100644 --- a/gap/small.gd +++ b/gap/small.gd @@ -994,35 +994,40 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## ## name ## -## a string naming the layer. It is how other layers refer to this one, -## and no two layers may share it. +## a string naming the layer. Must be unique. Can be used by other +## layers to refer to this one. ## ## available ## -## a function taking an order and returning fail, or a record -## which is handed to the functions below as inforec. Its -## component number, if present, is the number of groups of that -## order. +## a function taking an order and either returning fail, to indicate +## this layer does not handle the given order; or else a record +## which is handed to the functions below as inforec. The component +## number of the returned record, if present, is the number of groups +## of that order. ## ## group ## ## a function ( order, i, inforec ) returning ## the i-th group of that order. ## -## id, idAvailable +## idAvailable ## -## optional, a function ( G, inforec ) returning the -## number of G in this layer. Without it -## stays unavailable for these orders. Where -## the identification covers fewer orders than the layer, or wants a -## record of its own, idAvailable is a second available -## used in its place. +## optional, a function taking an order and either returning fail, +## to indicate this layer does not handle identification of groups of the +## given order; or else a record which is handed to the function id +## below as odrec. +## +## id +## +## optional, a function ( G, idrec ) returning the +## index of G among the groups of its order. Without it +## stays unavailable for these orders. ## ## number ## ## optional, a function ( order, inforec ) returning -## the number of groups of that order. Needed only where -## available does not report it. +## the number of groups of that order. Only needed if available +## does not report this count. ## ## information ## @@ -1034,10 +1039,10 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## properties ## ## optional, a function ( order, inforec ) reporting -## which selection criteria follow from the position a group has in this -## layer, so that and -## need not construct the groups to -## decide them. +## which selection criteria the select and count functions +## of this layer can deduce from the position of the groups alone, so that +## and +## need not construct the groups to decide these criteria. ## ## select, count ## @@ -1055,8 +1060,6 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## ## ## Where two layers cover an order, the one consulted first wins. -## The layers added this way are collected in the record -## SMALL_GROUPS_LAYERS, keyed by name. ## fail then Print(" IdSmallGroup is available for this size. \n \n"); else From 662e155502c18c9b6aaf74a00263d5d123bcff7c Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 18 Aug 2026 22:37:11 +0200 Subject: [PATCH 7/8] Fix a typo and a stray space in the layer documentation Co-Authored-By: Claude Opus 5 --- gap/small.gd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gap/small.gd b/gap/small.gd index bdeb436..b9e60c5 100644 --- a/gap/small.gd +++ b/gap/small.gd @@ -1010,12 +1010,12 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## a function ( order, i, inforec ) returning ## the i-th group of that order. ## -## idAvailable +## idAvailable ## ## optional, a function taking an order and either returning fail, ## to indicate this layer does not handle identification of groups of the ## given order; or else a record which is handed to the function id -## below as odrec. +## below as idrec. ## ## id ## From b955120543dfe725cac72d59199cd08541472850 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 18 Aug 2026 22:53:35 +0200 Subject: [PATCH 8/8] more --- gap/addlayer.gi | 3 +++ gap/small.gd | 5 ++++- tst/addlayer.tst | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/gap/addlayer.gi b/gap/addlayer.gi index 488ddee..1ce8727 100644 --- a/gap/addlayer.gi +++ b/gap/addlayer.gi @@ -180,6 +180,9 @@ InstallGlobalFunction( SmallGroupsAddLayer, function( desc ) Error( ".", comp, " must be a list of layer names" ); fi; od; + if IsBound( desc.idAvailable ) and not IsBound( desc.id ) then + Error( ".idAvailable is of no use without .id" ); + fi; layer := ShallowCopy( desc ); for comp in [ "before", "after" ] do diff --git a/gap/small.gd b/gap/small.gd index b9e60c5..c351d54 100644 --- a/gap/small.gd +++ b/gap/small.gd @@ -1015,7 +1015,10 @@ DeclareGlobalFunction( "SmallGroupsInformation" ); ## optional, a function taking an order and either returning fail, ## to indicate this layer does not handle identification of groups of the ## given order; or else a record which is handed to the function id -## below as idrec. +## below as idrec. Defaults to available, so it is only +## needed where the identification covers other orders than the +## construction, or wants a record of its own. If idAvailable is +## defined then id must also be provided. ## ## id ## diff --git a/tst/addlayer.tst b/tst/addlayer.tst index a443d49..a455e7a 100644 --- a/tst/addlayer.tst +++ b/tst/addlayer.tst @@ -259,6 +259,38 @@ true gap> NumberSmallGroups( 96 ); 1 +# +# 'idAvailable' where the identification covers fewer orders than the groups +# +gap> SmallGroupsAddLayer( rec( +> name := "half identified", +> available := function( order ) +> if order in [ 2072, 2080 ] then +> return rec( number := 1 ); +> fi; +> return fail; +> end, +> group := { order, i, inforec } -> CyclicGroup( order ), +> idAvailable := function( order ) +> if order = 2072 then +> return rec(); +> fi; +> return fail; +> end, +> id := { G, idrec } -> 1 ) ); +gap> List( [ 2072, 2080 ], SmallGroupsAvailable ); +[ true, true ] +gap> List( [ 2072, 2080 ], IdGroupsAvailable ); +[ true, false ] +gap> IdGroup( CyclicGroup( 2072 ) ); +[ 2072, 1 ] + +# on its own it identifies nothing +gap> SmallGroupsAddLayer( rec( name := "no id", available := ReturnFail, +> group := ReturnFail, +> idAvailable := ReturnFail ) ); +Error, .idAvailable is of no use without .id + # # put the library back as it was, so the stand-ins do not follow the rest of # the tests around @@ -266,8 +298,8 @@ gap> NumberSmallGroups( 96 ); gap> SMALL_GROUPS_LAYERS := rec( SmallGrp := saved.layers[1] );; gap> SMALL_GROUPS_LAYER_LIST := saved.layers;; gap> SMALL_AVAILABLE_FUNCS := saved.avail;; -gap> List( [ 2016, 2025, 2040, 2052, 2064 ], SmallGroupsAvailable ); -[ false, false, false, false, false ] +gap> List( [ 2016, 2025, 2040, 2052, 2064, 2072 ], SmallGroupsAvailable ); +[ false, false, false, false, false, false ] gap> NumberSmallGroups( 96 ); 231