Small groups search
Overview
Teaching: 40 min
Exercises: 15 minQuestions
Modular programming: putting functions together
How to check a conjecture for all groups of a given order.
Objectives
Using the Small Groups Library
Designing a system of functions to fit together.
Discovering the first boundaries of GAP and Brute-force approaches.
In this section we are interested in finding some non-trivial groups whose average order of elements is an integer.
The GAP distribution includes a number of data libraries, an overview of which can be found here. One of them is the Small Groups Library by Hans Ulrich Besche, Bettina Eick and Eamonn O’Brien which we want to use in this chapter. First, we have a look at how to work with the Small Groups Library before using it to methodically search for groups with the afore mentioned property. This library contains all groups of a certain ‘small’ order, i.e. order less than a certain bound and orders whose prime factorisation is small in some sense.
Let us have a look at some key functions. To call a group of order n
out of the Small Groups Library
you also need to know its identifying number. For example
gap> H := SmallGroup(64,1)
<pc group of size 64 with 6 generators>
calls the first group of order 64 out of the Small Group Library.
gap> NrSmallGroups(64)
267
shows that there are 267 groups of order 64. Here are a few other key functions of the package:
gap> SmallGroupsInformation(64);
There are 267 groups of order 64.
They are sorted by their ranks.
1 is cyclic.
2 - 54 have rank 2.
55 - 191 have rank 3.
192 - 259 have rank 4.
260 - 266 have rank 5.
267 is elementary abelian.
For the selection functions the values of the following attributes
are precomputed and stored:
IsAbelian, PClassPGroup, RankPGroup, FrattinifactorSize and
FrattinifactorId.
This size belongs to layer 2 of the SmallGroups library.
IdSmallGroup is available for this size.
gap> G:=SmallGroup(64,2);
<pc group of size 64 with 6 generators>
gap> AllSmallGroups(Size,64,NilpotencyClassOfGroup,5);
[ <pc group of size 64 with 6 generators>, <pc group of size 64 with 6 generators>,
<pc group of size 64 with 6 generators> ]
gap> List(last,IdGroup);
[ [ 64, 52 ], [ 64, 53 ], [ 64, 54 ] ]
gap> AllSmallGroups(64, IsAbelian);;
gap> List(last, IdGroup);
[ [ 64, 1 ], [ 64, 2 ], [ 64, 26 ], [ 64, 50 ], [ 64, 55 ], [ 64, 83 ], [ 64, 183 ], [ 64, 192 ], [ 64, 246 ], [ 64, 260 ], [ 64, 267 ] ]
gap> Size(last);
11
gap> AllSmallGroups(64, IsSolubleGroup);;
gap> Size(last);
267
SmallGroupsInformation
gives us basic information about the small groups of a given size as well as precomputed
information about these groups.
The function AllSmallGroups
returns all small groups with a given size provided as the argument. However, it is possible
to use a filter to search for small groups of a given size with additional properies such as abelian groups,
groups with given nilpotency class, cyclic groups and soluble groups.
One can also use this library to identify a given group with a small group. For example
gap> H := Group((1,2,3),(3,6,9),(2,6));;
gap> IdSmallGroup(H);
[ 120, 34 ]
gap> SmallGroup(120,34);
Group([ (1,2,3,4,5), (1,2) ]);
Using the Small Group Library
Use the Small Group Library to compute the number of PC groups of order smaller or equal to
64
. How many groups of order smaller or equal to64
are not PC groups?Solution
gap > Sum( List( [1 .. 64], i-> Size( AllSmallGroups( i, IsPcGroup ) ) ) ); 585
There is one group of order less or equal to 64 that is not a pc group, i.e. the trivial group.
Now we are prepared to use the Small Group Library to search for more groups whose average order of elements is an integer.
We are going to use inline notatation to define a test function for the above property of a given group. Defining a function in this way is possible for one-argument functions.
TestOneGroup := G -> IsInt( AvgOrdOfGroup(G) );
Now let us try
List([TrivialGroup(),Group((1,2))],TestOneGroup);
[ true, false ]
gap> AllSmallGroups(Size,24,TestOneGroup,true);
[ ]
Modular programming begins here
Why is returning booleans a good design decision for such functions, instead of just printing information or returning a string such as
"YES"
?
Let us first design a rudimentary function testing all groups of a given order and returning
a group whose average order of elements is an integer as soon as one is found and fail
, which is a special boolen variable
in GAP, otherwise.
TestOneOrderEasy := function(n)
local i;
for i in [1..NrSmallGroups(n)] do
if TestOneGroup( SmallGroup( n, i ) ) then
return [n,i];
fi;
od;
return fail;
end;
For example,
TestOneOrderEasy(1);
[ 1, 1 ]
TestOneOrderEasy(24);
fail
AllSmallGroups
runs out of memory - what to do?
- Use iteration over
[1..NrSmallGroups(n)]
as shown in the function above- Use
IdsOfAllSmallGroups
which accepts the same arguments asAllSmallGroups
but returns ids instead of groups.
Iterating over [1..NrSmallGroups(n)]
gives you more control over the progress of the calculation.
For example, the next version of our testing function prints additional information about the number of the
group being tested. It also supplies the testing function as an argument which allows us to plug
whatever testing function we want into TestOneOrder
.
TestOneOrder := function(f,n)
local i, G;
for i in [1..NrSmallGroups(n)] do
Print(n, ":", i, "/", NrSmallGroups(n), "\r");
G := SmallGroup( n, i );
if f(G) then
Print("\n");
return [n,i];
fi;
od;
Print("\n");
return fail;
end;
For instance,
TestOneOrder(TestOneGroup,64);
will display a changing counter during calculation and then return fail
:
64:267/267
fail
We suspect that groups whose average order of elements is an integer are rather rare. Hence it is practical to
write a function checking the groups of order 2 up to n
for the desired property. Additionally
we want the function to return a group whose average order of elements is an integer as soon as one is found.
TestAllOrders:=function(f,n)
local i, res;
for i in [2..n] do
res:=TestOneOrder(f,i);
if res <> fail then
return res;
fi;
od;
return fail;
end;
Let us check all groups of order up to 128 for this property:
TestAllOrders(TestOneGroup,128);
2:1/1
3:1/1
4:2/2
5:1/1
6:2/2
7:1/1
8:5/5
...
...
...
100:16/16
101:1/1
102:4/4
103:1/1
104:14/14
105:1/2
[ 105, 1 ]
Our function tells us that the average order of elements ofSmallGroup(105,1)
is an integer.
Let us have a closer look at the group we just found. For example, its isomorphism type is of interest.
It can be computed using the GAP function StructureDescription
. Check here for further information.
G:=SmallGroup(105,1); AvgOrdOfGroup(G); StructureDescription(G);
<pc group of size 105 with 3 generators>
17
"C5 x (C7 : C3)"
Let us continue our hunt for more such groups. As there are only 2 groups of order 105:
gap> NrSmallGroups(105);
2
we check the 2nd group of order 105 manually and continue checking the groups of order 106 up to 256 methodically.
gap> TestOneGroup(SmallGroup(105,2));
false
We already checked the groups of order up to and including 105 for the desired property. Let us modify our function slightly to be able to set a different starting point than order 2.
TestRangeOfOrders:=function(f,n1,n2)
local n, res;
for n in [n1..n2] do
res:=TestOneOrder(f,n);
if res <> fail then
return res;
fi;
od;
return fail;
end;
Let us check the groups of order 106 up to 256:
TestRangeOfOrders(TestOneGroup,106,256);
We notice that checking 2328 groups of order 128 and 56092 groups of order 256 is not feasible and stop the computation.
This is again a situation where theoretical knowledge helps much more than
a brute-force approach. If the group is a p-group then the order of each
conjugacy class of a non-identity element of the group is divisible by p;
therefore, the average order of a group element can not be an integer. Therefore
p-groups can be excluded from our calculation. Let us change our code accordingly using the function
IsPrimePowerInt
:
TestRangeOfOrders:=function(f,n1,n2)
local n, res;
for n in [n1..n2] do
if not IsPrimePowerInt(n) then
res:=TestOneOrder(f,n);
if res <> fail then
return res;
fi;
fi;
od;
return fail;
end;
We can now check all groups of order 106 up to 512, excluding p-groups:
gap> TestRangeOfOrders(TestOneGroup,106,512);
106:2/2
108:45/45
...
350:10/10
351:14/14
352:195/195
354:4/4
355:2/2
356:5/5
357:1/2
[ 357, 1 ]
So we found a group of order 357 satisfying our designated property. Let us have a look at that group:
AvgOrdOfGroup( SmallGroup(357,1)); StructureDescription( SmallGroup(357,1));
65
"C17 x (C7 : C3)"
Our next goal is to make our function as flexible and as user friendly as possible.
To begin with, we modify TestOneOrder
.
Firstly, we are going to make TestOneOrder
variadic, i.e. it may accept two or more
arguments. We will set 2 as minimum number of arguments, the first two being f
and n
,
as we need to know a testing function f
as well as the order n
we want to check.
We additionally allow for optional arguments that will be stored in the list r
, indicated
by r..
in the code below. The entries of r
represent the indentifying number of
the first and last small group of order n
we want to check. By default these numbers are equal
to 1
and NrSmallGroups(n)
. Because of these modifications we have to validate our input and return
user-friendly error messages in case of a wrong input.
Secondly we are going to introduce the concept of Info
messages and Info
levels. Info
messages
may be switched on and off depending on the Info
level set by the user. The need
we address here is the ability to switch the levels of verbosity of the
output without the error-prone approach of walking through the code and commenting
Print
statements in and out. Let us have a quick look at Info
classes before changing the code of
our TestOneGroup
function.
We have to create an InfoClass
first:
gap> InfoSmallGroupsSearch := NewInfoClass("InfoSmallGroupsSearch");
InfoSmallGroupsSearch
Now instead of Print("something");
one can use
Info( InfoSmallGroupsSearch, infolevel, "something" );
where infolevel
is a positive integer specifying the level of verbosity.
This level could be changed to n
using the command
SetInfoLevel( InfoSmallGroupsSearch, n);
. See actual calls of Info
in
the code below:
TestOneOrderVariadic := function(f,n,r...)
local n1, n2, i;
if not Length(r) in [0..2] then
Error("The number of arguments must be 2,3 or 4\n" );
fi;
if not IsFunction( f ) then
Error("The first argument must be a function\n" );
fi;
if not IsPosInt( n ) then
Error("The second argument must be a positive integer\n" );
fi;
if IsBound(r[1]) then
n1:=r[1];
if not n1 in [1..NrSmallGroups(n)] then
Error("The 3rd argument, if present, must belong to ", [1..NrSmallGroups(n)], "\n" );
fi;
else
n1:=1;
fi;
if IsBound(r[2]) then
n2:=r[2];
if not n2 in [1..NrSmallGroups(n)] then
Error("The 4th argument, if present, must belong to ", [1..NrSmallGroups(n)], "\n" );
elif n2 < n1 then
Error("The 4th argument, if present, must be greater or equal to the 3rd \n" );
fi;
else
n2:=NrSmallGroups(n);
fi;
Info( InfoSmallGroupsSearch, 1,
"Checking groups ", n1, " ... ", n2, " of order ", n );
for i in [n1..n2] do
if InfoLevel( InfoSmallGroupsSearch ) > 1 then
Print(i, "/", NrSmallGroups(n), "\r");
fi;
if f(SmallGroup(n,i)) then
Info( InfoSmallGroupsSearch, 1,
"Discovered counterexample: SmallGroup( ", n, ", ", i, " )" );
return [n,i];
fi;
od;
Info( InfoSmallGroupsSearch, 1,
"Search completed - no counterexample discovered" );
return fail;
end;
The following example demonstrates how the output may be controlled
by switching the info level for InfoSmallGroupsSearch
:
gap> TestOneOrderVariadic(TestOneGroup,24);
fail
gap> SetInfoLevel( InfoSmallGroupsSearch, 1 );
gap> TestOneOrderVariadic(TestOneGroup,24);
#I Checking groups 1 ... 15 of order 24
#I Search completed - no counterexample discovered
fail
gap> TestOneOrderVariadic(TestOneGroup,357);
#I Checking groups 1 ... 2 of order 357
#I Discovered counterexample: SmallGroup( 357, 1 )
[ 357, 1 ]
gap> SetInfoLevel( InfoSmallGroupsSearch, 0);
gap> TestOneOrderVariadic(TestOneGroup,357);
[ 357, 1 ]
This causes some problems for our test file since the Test
function
compares the actual output to the reference output. To resolve
this problem we run the tests at info level 0 to suppress all additional output.
# Finding groups with integer average order
gap> SetInfoLevel( InfoSmallGroupsSearch, 0);
gap> res:=[];;
gap> for n in [1..360] do
> if not IsPrimePowerInt(n) then
> t := TestOneOrderVariadic( TestOneGroup,n,1,NrSmallGroups(n) );
> if t <> fail then
> Add(res,t);
> fi;
> fi;
> od;
gap> res;
[ [ 1, 1 ], [ 105, 1 ], [ 357, 1 ] ]
Exercises
Use the Small Group Library to find another group whose order is greater than 1536 and the average order of its elements is an integer.
How many groups of order not higher than 2000 may you be able to check, excluding p-groups and those of order 1536?
Solution
SmallGroup(1785,1)
has a whole numbered average order of elements.Groups of order 1600, 1664, 1728, 1792, 1920, (1944) are a problem
Key Points
Organise the code into functions.
Create small groups one by one instead of producing a huge list of them.
Using
SmallGroupsInformation
may help to reduce the search space.Determining the isomorphism type of a group.
GAP is not a magic tool: theoretical knowledge may help much more than brute-force approach.