diff --git a/CHANGES.md b/CHANGES.md
index 2322e5e..4ce5419 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,7 +2,9 @@ This file describes changes in the smallgrp package.
# Unreleased
- - 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.
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..1ce8727
--- /dev/null
+++ b/gap/addlayer.gi
@@ -0,0 +1,241 @@
+#############################################################################
+##
+#W addlayer.gi GAP group library Max Horn
+##
+## The layers of the library, and the API for adding more.
+##
+
+#############################################################################
+##
+#V SMALL_GROUPS_LAYERS
+##
+## every layer, keyed by name. 'SMALL_GROUPS_LAYER_LIST' holds the same
+## records in the order they are consulted.
+SMALL_GROUPS_LAYERS := rec();
+
+#############################################################################
+##
+#V SMALL_GROUPS_LAYERS.SmallGrp
+##
+## 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 := [ ],
+ 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_SORT_LAYERS := 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 SmallGroupsAddLayer( desc )
+##
+InstallGlobalFunction( SmallGroupsAddLayer, function( desc )
+ local known, comp, layer, order;
+
+ if not IsRecord( desc ) then
+ Error( " must be a record" );
+ fi;
+
+ 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 );
+ 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", "idAvailable", "number",
+ "properties", "information", "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;
+ 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
+ if not IsBound( layer.( comp ) ) then
+ layer.( comp ) := [ ];
+ fi;
+ od;
+
+ # 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 := 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;
+ return r;
+ end;
+ if IsBound( layer.id ) and not IsBound( layer.idAvailable ) then
+ layer.idAvailable := layer.available;
+ fi;
+
+ # 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 := desc.number( size, inforec );
+ return inforec;
+ end;
+ else
+ layer.numberOf := function( size, inforec )
+ Error( "layer ", layer.name, " reports no number of groups of ",
+ "order ", size );
+ end;
+ fi;
+
+ # 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 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_LIST := order;
+end );
diff --git a/gap/small.gd b/gap/small.gd
index c8b8a24..c351d54 100644
--- a/gap/small.gd
+++ b/gap/small.gd
@@ -977,6 +977,110 @@ 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. Must be unique. Can be used by other
+## layers to refer to this one.
+##
+## available
+## -
+## 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.
+##
+## 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 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
+## -
+## 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. Only needed if available
+## does not report this count.
+##
+## information
+## -
+## optional, a function
+## ( order, inforec, number ) printing what
+##
should say about that order
+## beyond the number of groups.
+##
+## properties
+## -
+## optional, a function ( order, inforec ) reporting
+## 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
+## -
+## 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 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.
+## SOTGroup( order, i ),
+## id := { G, inforec } -> IdSOTGroup( G )[2] ) );
+## ]]>
+##
+##
+## <#/GAPDoc>
+##
+DeclareGlobalFunction( "SmallGroupsAddLayer" );
+
#############################################################################
##
#A IdGap3SolvableGroup( )
diff --git a/gap/small.gi b/gap/small.gi
index 9230498..ed191b3 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;
@@ -929,13 +943,13 @@ InstallMethod( IdGroup,
[ IsGroup ],
0,
function( G )
- local inforec, size, id;
+ local idrec, size, id;
size := Size( G );
if size = 1 then return [ 1, 1 ]; fi;
- inforec := ID_AVAILABLE( size );
- if inforec = fail then
+ idrec := ID_AVAILABLE( size );
+ if idrec = fail then
Error( "the group identification for groups of size ", size,
" is not available" );
fi;
@@ -958,7 +972,7 @@ function( G )
G := PcGroupCode( CodePcGroup( G ), Size( G ) );
fi;
- id := ID_GROUP_FUNCS[ inforec.func ]( G, inforec );
+ id := idrec.layer.id( G, idrec );
if not SMALL_GROUPS_OLD_ORDER then
if size = 3^7 then
diff --git a/gap/smlinfo.gi b/gap/smlinfo.gi
index 33bb401..4d9ba0f 100644
--- a/gap/smlinfo.gi
+++ b/gap/smlinfo.gi
@@ -61,28 +61,21 @@ InstallGlobalFunction( SmallGroupsInformation, function( size )
ErrorNoReturn("usage: SmallGroupsInformation( size )");
fi;
- smav := SMALL_AVAILABLE( size );
- idav := ID_AVAILABLE( size );
-
if size = 1024 then
Print( "The groups of size 1024 are not available. \n");
return;
fi;
+ smav := SMALL_AVAILABLE( size );
if smav = fail then
Print( "The groups of size ", size, " are not available. \n");
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,26 +83,39 @@ 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;
+ idav := ID_AVAILABLE( size );
if idav <> fail then
Print(" IdSmallGroup is available for this size. \n \n");
else
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..a455e7a
--- /dev/null
+++ b/tst/addlayer.tst
@@ -0,0 +1,307 @@
+gap> START_TEST("addlayer.tst");
+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,
+# 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 the layer "two of them".
+ IdSmallGroup is available for this size.
+
+
+#
+# The layer is registered by name, behind the layers of this library.
+#
+gap> Position( SMALL_GROUPS_LAYER_LIST, SMALL_GROUPS_LAYERS.("two of them") );
+2
+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( 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" ] ) );
+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 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.
+#
+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( SMALL_GROUPS_LAYERS.selective.count );
+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, 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> SMALL_AVAILABLE_FUNCS[ Length( SMALL_AVAILABLE_FUNCS ) + 1 ] :=
+> function( order )
+> if order <> 2079 then
+> return fail;
+> fi;
+> 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> NumberSmallGroups( 2079 );
+9
+gap> NumberSmallGroups( 2025 );
+4
+
+# 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", "countless", "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
+
+#
+# '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
+#
+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, 2072 ], SmallGroupsAvailable );
+[ false, false, false, false, false, false ]
+gap> NumberSmallGroups( 96 );
+231
+
+#
+gap> STOP_TEST( "addlayer.tst", 1);