#############################################################################
##
## orb package
## search.gi
## Juergen Mueller
## Max Neunhoeffer
## Felix Noeske
##
## Copyright 2005-2008 by the authors.
## This file is free software, see license information at the end.
##
## Implementation stuff for searching in groups.
##
#############################################################################
# Random vectors and matrices:
# Methods for Randomize for gf2 and 8bit vectors have been taken out
# they will be in GAP >= 4.5 in the library. In the meantime use
# cmats.
InstallGlobalFunction( MakeRandomVectors,
function( arg )
local i,l,number,randomsource,sample;
if Length(arg) <=
1 or Length(arg) >
3 then
ErrorNoReturn(
"Usage: MakeRandomVectors( sample, number [,randomsource] )");
fi;
sample := arg[
1];
number := arg[
2];
if Length(arg) =
3 then
randomsource := arg[
3];
else
randomsource := GlobalRandomSource;
fi;
l := [];
for i in [number,number-
1..
1]
do
sample := ShallowCopy(sample);
Randomize(sample,randomsource);
l[i] := sample;
od;
return l;
end );
InstallGlobalFunction( MakeRandomLines,
function( arg )
local i,l,number,pos,randomsource,sample;
if Length(arg) <=
1 or Length(arg) >
3 then
ErrorNoReturn(
"Usage: MakeRandomLines( sample, number [,randomsource] )");
fi;
sample := arg[
1];
number := arg[
2];
if Length(arg) =
3 then
randomsource := arg[
3];
else
randomsource := GlobalRandomSource;
fi;
l := [];
for i in [number,number-
1..
1]
do
sample := ShallowCopy(sample);
repeat
Randomize(sample,randomsource);
pos := PositionNonZero(sample);
until pos <= Length(sample);
MultVector(sample,sample[pos]^-
1);
l[i] := sample;
od;
return l;
end );
# Product replacers:
BindGlobal(
"ProductReplacersType",
NewType( ProductReplacersFamily, IsProductReplacer and IsMutable) );
InstallMethod( ProductReplacer,
"for a list of group generators",
[IsList],
function( gens )
return ProductReplacer( gens, rec( ) );
end );
InstallMethod( ProductReplacer,
"for a group object and an options record",
[IsGroup, IsRecord],
function(g,r) return ProductReplacer(GeneratorsOfGroup(g),r);
end );
InstallMethod( ProductReplacer,
"for a group object",
[IsGroup],
function(g) return ProductReplacer(GeneratorsOfGroup(g),rec());
end );
InstallMethod( ProductReplacer,
"for a list of group generators and an options record",
[IsList, IsRecord],
function( gens, opt )
# First add some default options if not set:
local pr;
if Length(gens) =
0 then
Error(
"ProductReplacer: need at least one generator");
return fail;
fi;
pr := ShallowCopy(opt);
if CanEasilySortElementsFamily(FamilyObj(gens[
1]))
then
pr.gens := Set(gens);
else
pr.gens := ShallowCopy(gens);
fi;
pr.nrgens := Length(pr.gens);
if not IsBound(pr.addslots)
then
pr.addslots :=
5;
fi;
if not IsBound(pr.minslots)
then
pr.minslots :=
0;
fi;
if Length(gens) =
1 and pr.addslots =
0 then
# Make sure that the case of one generator is OK!
pr.minslots := Maximum(
2,pr.minslots);
fi;
if not IsBound(pr.randomsource)
then
pr.randomsource := GlobalRandomSource;
fi;
if not IsBound(pr.scramble)
then
pr.scramble :=
30;
fi;
if not IsBound(pr.scramblefactor)
then
pr.scramblefactor :=
4;
fi;
pr.scramble := Maximum(pr.scramble,pr.scramblefactor*pr.nrgens);
if not IsBound(pr.retirecaptain)
then
pr.retirecaptain :=
2 * pr.scramble;
fi;
if not IsBound(pr.maxdepth)
then
pr.maxdepth := infinity;
fi;
if not IsBound(pr.noaccu)
then
pr.noaccu := false;
fi;
if not IsBound(pr.accus) or pr.accus <
0 then
pr.accus :=
5;
elif pr.accus =
0 then
pr.noaccu := true;
fi;
if not IsBound(pr.accelerator)
then
pr.accelerator := true;
fi;
if not IsBound(pr.normalin)
then
pr.normalin := false;
else
if pr.normalin <> false and
not(IsProductReplacer(pr.normalin))
then
Error(
"normalin option must be a product replacer");
fi;
fi;
pr.slots := pr.nrgens + pr.addslots;
if pr.slots < pr.minslots
then
pr.slots := pr.minslots;
fi;
pr.initialized := false;
pr.steps :=
0;
if pr.noaccu = false
then
pr.accu := ListWithIdenticalEntries(pr.accus,pr.gens[
1]^
0);
pr.accupos :=
0;
else
pr.accu := fail;
pr.accupos :=
0;
fi;
Objectify(ProductReplacersType,pr);
pr!.team := ShallowCopy(pr!.gens);
while Length(pr!.team) < pr!.slots
do
Add(pr!.team,pr!.gens[Random(pr!.randomsource,
1,pr!.nrgens)]);
od;
return pr;
end );
InstallMethod( Reset,
"for a product replacer", [IsProductReplacer],
function(pr)
if not(pr!.initialized)
then return;
fi;
pr!.team := ShallowCopy(pr!.resetteam);
pr!.accu := ShallowCopy(pr!.resetaccu);
pr!.accupos := pr!.resetaccupos;
pr!.steps := pr!.resetsteps;
if pr!.normalin <> false
then
Reset(pr!.normalin);
fi;
end );
InstallMethod( Next,
"for a product replacer", [IsProductReplacer],
function(pr)
local OneStep,i;
OneStep :=
function(pr)
local a, b, c, l, x, result, i;
pr!.steps := pr!.steps +
1;
c := Random(pr!.randomsource,
1,
2);
if pr!.accelerator and pr!.steps <= pr!.retirecaptain
then
a := Random(pr!.randomsource,
2,pr!.slots);
b := Random(pr!.randomsource,
2,pr!.slots);
if c =
1 then
if pr!.normalin <> false
then
pr!.team[
1] := pr!.team[
1]*pr!.team[b]^Next(pr!.normalin);
pr!.team[a] := pr!.team[a]*pr!.team[
1];
else
pr!.team[
1] := pr!.team[
1]*pr!.team[b];
pr!.team[a] := pr!.team[a]*pr!.team[
1];
fi;
else
if pr!.normalin <> false
then
pr!.team[
1] := pr!.team[b]^Next(pr!.normalin)*pr!.team[
1];
pr!.team[a] := pr!.team[
1] * pr!.team[a];
else
pr!.team[
1] := pr!.team[b] * pr!.team[
1];
pr!.team[a] := pr!.team[
1] * pr!.team[a];
fi;
fi;
result := pr!.team[a];
else
a := Random(pr!.randomsource,
1,pr!.slots);
b := Random(pr!.randomsource,
1,pr!.slots-
1);
if b >= a
then b := b +
1;
fi;
if c =
1 then
if pr!.normalin <> false
then
pr!.team[a]:=pr!.team[a]*pr!.team[b]^Next(pr!.normalin);
else
pr!.team[a]:=pr!.team[a]*pr!.team[b];
fi;
else
if pr!.normalin <> false
then
pr!.team[a]:=pr!.team[b]^Next(pr!.normalin)*pr!.team[a];
else
pr!.team[a]:=pr!.team[b]*pr!.team[a];
fi;
fi;
result := pr!.team[a];
fi;
if pr!.noaccu or
pr!.steps <= Maximum(pr!.nrgens * pr!.scramblefactor, pr!.scramble)
- pr!.accus
then
return result;
else
pr!.accupos := pr!.accupos +
1;
if pr!.accupos > pr!.accus
then pr!.accupos :=
1;
fi;
pr!.accu[pr!.accupos] := pr!.accu[pr!.accupos] * result;
# Rattle
return pr!.accu[pr!.accupos];
fi;
end;
if pr!.steps > pr!.maxdepth
then Reset(pr);
fi;
if not(pr!.initialized)
then
for i in [
1..Maximum(pr!.nrgens * pr!.scramblefactor, pr!.scramble)]
do
OneStep(pr);
od;
pr!.resetteam := ShallowCopy(pr!.team);
# Remember for reset
pr!.resetaccu := ShallowCopy(pr!.accu);
pr!.resetaccupos := pr!.accupos;
pr!.resetsteps := pr!.steps;
pr!.initialized := true;
fi;
return OneStep(pr);
end );
InstallMethod( AddGeneratorToProductReplacer,
"for a product replacer and a new generator",
[ IsProductReplacer, IsObject ],
function(pr,el)
local i;
Add(pr!.gens,el);
pr!.nrgens := pr!.nrgens +
1;
for i in [
1..Length(pr!.team)]
do
pr!.team[i] := pr!.team[i] * el^Random(
0,
7);
od;
Add(pr!.team,el);
if pr!.initialized
then
for i in [
1..Length(pr!.resetteam)]
do
pr!.resetteam[i] := pr!.resetteam[i] * el^Random(
0,
7);
od;
Add(pr!.resetteam,el);
fi;
pr!.slots := pr!.slots +
1;
end );
InstallMethod( ViewObj,
"for a product replacer", [IsProductReplacer],
function(pr)
Print(
"<product replacer gens=",pr!.nrgens,
" slots=",pr!.slots,
" scramble=",Maximum(pr!.nrgens*pr!.scramblefactor,pr!.scramble),
" maxdepth=",pr!.maxdepth,
"\n steps=",pr!.steps);
if pr!.noaccu
then Print(
" (without accu");
else Print(
" (rattle accus=",pr!.accus);
fi;
if pr!.accelerator
then Print(
", with accelerator)");
else Print(
")");
fi;
if pr!.normalin <> false
then
Print(
"\n normalin=");
ViewObj(pr!.normalin);
fi;
Print(
">");
end );
# Finding things in groups, random searchers:
BindGlobal(
"RandomSearchersType",
NewType( RandomSearchersFamily,
IsRandomSearcher and IsMutable ) );
InstallMethod( RandomSearcher,
"for a list of generators and a test function",
[IsList, IsFunction],
function( gens, testfunc )
return RandomSearcher( gens, testfunc, rec() );
end );
InstallMethod( RandomSearcher,
"for a list of generators, a test function, and an options record",
[IsList, IsFunction, IsRecord],
function( gens, testfunc, opt )
# Set some default options:
local r;
r := ShallowCopy(opt);
if not IsBound(r.exceptions)
then
r.exceptions := [];
fi;
if not IsBound(r.maxdepth)
then
r.maxdepth :=
4 * Maximum(
100,
10*Length(gens));
fi;
if not IsBound(r.addslots)
then
r.addslots :=
10;
fi;
if not IsBound(r.limit)
then
r.limit := infinity;
fi;
r.stops :=
0;
r.tries :=
0;
r.testfunc := testfunc;
if IsBound( r.scramble )
then
r.productreplacer := ProductReplacer( gens,
rec( maxdepth := r.maxdepth,
scramble :=
100,
scramblefactor :=
10,
addslots := r.addslots ) );
# this is the standard PseudoRandom behaviour
else # the standard case using all random elements:
r.productreplacer := ProductReplacer( gens,
rec( maxdepth := r.maxdepth,
scramble :=
0,
scramblefactor :=
0,
addslots := r.addslots ) );
# this uses all random elements from the first on
fi;
Objectify( RandomSearchersType,r );
return r;
end );
InstallMethod( ViewObj,
"for a random searcher", [IsRandomSearcher],
function( rs )
Print(
"<random searcher");
if IsBound(rs!.target)
then
Print(
" for ",rs!.target);
fi;
Print(
", tries: ",rs!.tries,
" stops: ",rs!.stops,
", exceptions: ",
Length(rs!.exceptions),
">");
end );
InstallMethod( Search,
"for a random searcher", [IsRandomSearcher],
function( rs )
local x,y;
repeat
repeat
x := Next( rs!.productreplacer );
rs!.tries := rs!.tries +
1;
if rs!.tries mod
10000 =
0 then
Info(InfoOrb,
2,
"Tried ",rs!.tries,
" elements.");
fi;
if IsObjWithMemory(x)
then
y := x!.el;
else
y := x;
fi;
if rs!.tries > rs!.limit
then
return fail;
fi;
until not( y in rs!.exceptions );
until rs!.testfunc(y);
rs!.stops := rs!.stops +
1;
Add(rs!.exceptions,y);
return x;
end );
###################################################
# Involution centralisers and the dihedral trick: #
###################################################
InstallGlobalFunction( FindInvolution,
function(pr)
# g a matrix group
local i,o,x;
for i in [
1..
1000]
do
x := Next(pr);
o := Order(StripMemory(x));
if o mod
2 =
0 then
return x^(o/
2);
fi;
od;
return fail;
end );
InstallGlobalFunction( FindCentralisingElementOfInvolution,
function(pr,x)
# pr a product replacer for G, x an involution in G
local o,r,y,z;
r := Next(pr);
y := x^r;
# Now x and y generate a dihedral group
if x=y
then return r;
fi;
z := x*y;
o := Order(StripMemory(z));
if IsEvenInt(o)
then
return z^(o/
2);
else
return z^((o+
1)/
2)*r^(-
1);
fi;
end );
InstallGlobalFunction( FindInvolutionCentraliser,
function(pr,x,n)
# pr a product replacer for G, x an involution in G, n an integer
local i,l,y;
l := [];
for i in [
1..n]
do # find 20 generators of the centraliser
y := FindCentralisingElementOfInvolution(pr,x);
AddSet(l,y);
od;
return l;
end );
InstallGlobalFunction( ReduceNumberOfGeneratorsUsingRecog,
function(l,Sizerecogniser)
local drop,i,ll,ri,ri2,si,wholegroup;
wholegroup := Group(StripMemory(l));
ri := Sizerecogniser(wholegroup);
si := Size(ri);
Print(
"Whole size: ",si,
"\n");
i := Length(l);
l := ShallowCopy(l);
while i >=
1 do
drop := false;
ll := Concatenation(l{[
1..i-
1]},l{[i+
1..Length(l)]});
ri := Sizerecogniser(Group(StripMemory(ll)));
if Size(ri) <> si
then
ri2 := Sizerecogniser(Group(StripMemory(ll)));
if Size(ri2) <> Size(ri)
then
if Size(ri2) = si
then
# we do not need this generator
drop := true;
fi;
# otherwise non-conclusive, leave the generator where it is
fi;
else
# we most probably do not need this generator
drop := true;
fi;
if drop
then
Remove(l,i);
i := i -
1;
Print(
"-\c");
else
i := i -
1;
Print(
"+\c");
fi;
od;
Print(
"\n");
return l;
end );
###############################################
# Find class representatives using powermaps: #
###############################################
InstallGlobalFunction( ClassMaker,
function(ct,cyc)
# ct an ordinary character table and cyc a list of class names we have
local cln,
done,erg,i,l,m,p,po,pow,todo,start;
pow := ComputedPowerMaps(ct);
cln := ClassNames(ct);
cyc := List(cyc,LowercaseString);
for i in [
1..Length(cyc)]
do
p := Position(cyc[i],
'-');
if p <> fail
then
Info(InfoOrb,
1,
"Warning: Class name containing \"-\
"!");
cyc[i] := cyc[i]{[
1..p-
1]};
fi;
od;
erg := [];
todo := [];
for i in [
1..Length(cyc)]
do
po := Position(cln,cyc[i]);
erg[po] := [i,
1];
Add(todo,po);
od;
done := Length(cyc);
while done < Length(cln) and Length(todo) >
0 do
i := todo[
1];
todo := todo{[
2..Length(todo)]};
for p in [
1..Length(pow)]
do
if IsBound(pow[p])
then
m := pow[p];
if not(IsBound(erg[m[i]]))
then
erg[m[i]] := [erg[i][
1],erg[i][
2]*p];
Add(todo,m[i]);
done :=
done +
1;
fi;
fi;
od;
od;
if done < Length(cln)
then
Error(
"Unvorhergesehener Fall Nummer 1!");
fi;
if cln[
1] =
"1a" then
l := [[
1,
0]];
start :=
2;
else
l := [];
start :=
1;
fi;
for i in [start..Length(cln)]
do
Add(l,erg[i]);
od;
return StraightLineProgramNC([l],Length(cyc));
end);
###########################
# Finding nice quotients: #
###########################
InstallGlobalFunction( OrbitStatisticOnVectorSpace,
function(gens,size,ti)
# gens must be a list of compressed matrices, size the order of the group
local len,nr,o,t,total,v;
v := ShallowCopy(gens[
1][
1]);
t := Runtime();
total :=
0;
nr :=
0;
while Runtime() < t + ti*
1000 do
Randomize(v);
o := Orb(gens,v,OnRight,rec(orbsizebound := size,
hashlen :=
3*size, report :=
0));
Enumerate(o);
len := Length(o!.orbit);
total := total + len;
nr := nr +
1;
Print(
"Found length ",String(Length(o!.orbit),
9),
", have now ",
String(nr,
4),
" orbits, average length: ",
QuoInt(total+QuoInt(nr,
2),nr),
" \r");
od;
Print(
"\n");
end);
InstallGlobalFunction( OrbitStatisticOnVectorSpaceLines,
function(gens,size,ti)
# gens must be a list of compressed matrices, size the order of the group
local len,nr,o,t,total,v,c;
v := ShallowCopy(gens[
1][
1]);
t := Runtime();
total :=
0;
nr :=
0;
while Runtime() < t + ti*
1000 do
Randomize(v);
c := PositionNonZero(v);
if c <= Length(v)
then
v := v / v[c];
fi;
o := Orb(gens,v,OnLines,rec(orbsizebound := size,
hashlen :=
3*size, report :=
0));
Enumerate(o);
len := Length(o!.orbit);
total := total + len;
nr := nr +
1;
Print(
"Found length ",String(Length(o!.orbit),
9),
", have now ",
String(nr,
4),
" orbits, average length: ",
QuoInt(total+QuoInt(nr,
2),nr),
" \r");
od;
Print(
"\n");
end);
###############################################
# Finding nice generating sets for subgroups: #
###############################################
InstallGlobalFunction( ORB_PowerSet,
function(s)
local i,l,le,ll;
if Length(s) =
0 then
return [[]];
elif Length(s) =
1 then
return [[],[s[
1]]];
else
l := ORB_PowerSet(s{[
1..Length(s)-
1]});
le := Length(l);
for i in [
1..le]
do
ll := ShallowCopy(l[i]);
Add(ll,s[Length(s)]);
Add(l,ll);
od;
return l;
fi;
end );
InstallGlobalFunction( ORB_SLPLineFromWord,
function(wo)
local li,i,j;
li := [];
i :=
1;
while i <= Length(wo)
do
j := i+
1;
while j <= Length(wo) and wo[j] = wo[i]
do
j := j +
1;
od;
if wo[i] >
0 then
Add(li,wo[i]);
Add(li,j-i);
else
Add(li,-wo[i]);
Add(li,-(j-i));
fi;
i := j;
od;
return li;
end );
InstallMethod( FindShortGeneratorsOfSubgroup,
"without option rec or func",
[ IsGroup, IsGroup ],
function(G,U)
return FindShortGeneratorsOfSubgroup(G,U,
rec( membershiptest := \in, sizetester := Size ) );
end );
InstallMethod( FindShortGeneratorsOfSubgroup,
"with option rec or func",
[ IsGroup, IsGroup, IsObject ],
function(G,U,opt)
local su,o,si,s,ps,min,minsi,subgens,subwords,i,l,membershiptest,sizetester;
if IsFunction(opt)
then
membershiptest := opt;
sizetester := Size;
su := Size(U);
elif IsRecord(opt)
then
if IsBound(opt.membershiptest)
then
membershiptest := opt.membershiptest;
else
membershiptest := \in;
fi;
if IsBound(opt.sizetester)
then
sizetester := opt.sizetester;
else
sizetester := Size;
fi;
if IsBound(opt.sizeU)
then
su := opt.sizeU;
else
if HasSize(U)
then
su := Size(U);
else
su := sizetester(U);
fi;
fi;
fi;
if su =
1 then # the trivial subgroup is easy to generate:
return rec( gens := [One(U)],
slp := StraightLineProgram( [[[
1,
0]]],
Length(GeneratorsOfGroup(G)) ) );
fi;
o := Orb(GeneratorsOfGroup(G),One(G),OnRight,
rec( lookingfor :=
function(o,x) return membershiptest(x,U);
end,
schreier := true ) );
Enumerate(o);
subgens := [o!.orbit[o!.found]];
subwords := [TraceSchreierTreeForward(o,o!.found)];
l :=
1;
# will always be the length of subgens and subwords
si := sizetester(Group(ShallowCopy(subgens)));
Info(InfoOrb,
2,
"Found subgroup of size ",si,
":",subwords);
if si = su
then
# Cyclic subgroup
s := StraightLineProgram([[ORB_SLPLineFromWord(subwords[
1])]],
Length(GeneratorsOfGroup(G)));
return rec( gens := subgens, slp := s );
fi;
while true
do
Enumerate(o);
Add(subgens,o!.orbit[o!.found]);
Add(subwords,TraceSchreierTreeForward(o,o!.found));
l := l +
1;
if l <> Length(subgens) or l <> Length(subwords)
then
Error();
fi;
s := sizetester(Group(ShallowCopy(subgens)));
if s = su
then
# OK, we have got a generating set:
# Now try shortening generating set:
Info(InfoOrb,
2,
"Found full subgroup of size ",si,
":",subwords);
Info(InfoOrb,
2,
"Need ",l,
" generators.");
if l <=
8 then
ps := ORB_PowerSet([
1..l]);
min := Length(ps);
minsi := l;
for i in [
2..Length(ps)-
1]
do
s := sizetester(Group(subgens{ps[i]}));
if s = su and Length(ps[i]) < minsi
then
min := i;
minsi := Length(ps[i]);
Info(InfoOrb,
2,
"Need ",minsi,
" generators.");
fi;
od;
subgens := subgens{ps[min]};
subwords := subwords{ps[min]};
else
i :=
1;
while i <= Length(subgens)
do
s := sizetester(
Group(subgens{Concatenation([
1..i-
1],[i+
1..l])}));
if s = su
then # we do not need generator i
Remove(subgens,i);
Remove(subwords,i);
l := l -
1;
else
i := i +
1;
fi;
od;
fi;
l := List(subwords,ORB_SLPLineFromWord);
s := StraightLineProgram([l],Length(GeneratorsOfGroup(G)));
return rec( gens := subgens, slp := s );
fi;
if s=si
then
Unbind(subgens[l]);
Unbind(subwords[l]);
l := l -
1;
else
si := s;
Info(InfoOrb,
2,
"Found subgroup of size ",si,
":",subwords);
fi;
od;
end );
##############################################################
# Helpers for permutation characters for certain operations: #
##############################################################
InstallGlobalFunction( NumberFixedVectors,
function( mat )
local f,n,q;
n := NullspaceMat(mat - mat^
0);
f := BaseField(mat);
q := Size(f);
return q^Length(n);
end );
InstallGlobalFunction( NumberFixedLines,
function( mat )
local el,f,n,nr,q;
f := BaseField(mat);
q := Size(f);
nr :=
0;
for el in f
do
if not(IsZero(el))
then
n := NullspaceMat(mat - el*mat^
0);
nr := nr + (q^Length(n)-
1)/(q-
1);
fi;
od;
return nr;
end );
InstallGlobalFunction( SpacesOfFixedLines,
function( mats )
local el,f,i,n,q,spcs;
f := BaseField(mats);
q := Size(f);
spcs := List(mats,i->[]);
for i in [
1..Length(mats)]
do
for el in f
do
if not(IsZero(el))
then
n := NullspaceMat(mats[i] - el*mats[i]^
0);
Add(spcs[i],n);
fi;
od;
od;
# to be continued...
return spcs;
end );
##################################################
# Helpers for making short SLPs from word lists: #
##################################################
InstallGlobalFunction( SLPForWordList,
function( wordlist, nrgens )
local bestlen,bestn,bestword,havewords,i,j,l,line,n,p,slp,w,where,wo,
wordset,writepos,ww;
havewords := [[]];
where := [
0];
for i in [
1..nrgens]
do
Add(havewords,[i]);
Add(where,i);
Add(havewords,[-i]);
Add(where,-i);
od;
wordset := Set(wordlist);
slp := [];
writepos := nrgens+
1;
for w in wordset
do
if not(w in havewords)
then
wo := ShallowCopy(w);
line := [];
while Length(wo) >
0 do
# Look through all havewords and see which one helps most:
bestlen :=
0;
bestword :=
0;
bestn :=
0;
for j in [
2..Length(havewords)]
do
ww := havewords[j];
l := Length(ww);
if l <= Length(wo) and wo{[
1..l]} = ww
then
# How often does it fit here?
n :=
1;
while (n+
1) * l <= Length(wo) and
ww = wo{[n*l+
1..(n+
1)*l]}
do
n := n +
1;
od;
if n*l > bestlen
then
bestlen := n*l;
bestword := j;
bestn := n;
fi;
fi;
od;
if where[bestword] >
0 then
Add(line,where[bestword]);
Add(line,bestn);
else
Add(line,-where[bestword]);
Add(line,-bestn);
fi;
l := Length(havewords[bestword]);
wo := wo{[l*bestn+
1..Length(wo)]};
od;
Add(slp,line);
Add(havewords,w);
Add(where,writepos);
Add(havewords,-Reversed(w));
Add(where,-writepos);
writepos := writepos+
1;
fi;
od;
line := [];
for w in wordlist
do
p := Position(havewords,w);
if where[p] >
0 then
Add(line,[where[p],
1]);
elif where[p] =
0 then # The empty word
Add(line,[
1,
0]);
else
Add(line,[-where[p],-
1]);
fi;
od;
Add(slp,line);
return [StraightLineProgram(slp,nrgens),havewords,where];
end );
#############################################################################
# A generic way to find stabilizers:
#############################################################################
InstallGlobalFunction( ORB_EstimatePermGroupSize,
function( gens )
local g,s;
g := Group(gens);
s := StabChain(g,rec( random :=
900 ));
return SizeStabChain(s);
end );
InstallGlobalFunction( ORB_FindStabilizerMC,
function( gens, pt, op, memory, limit, errorbound, estimatefunc, opt )
local counter,
done,est,gensi,mem,o,oldest,orbpart,p,pr,prob,stab,tries,
x,y,z;
mem := Memory(pt);
if mem = fail
then
mem := SIZE_OBJ(pt);
if mem =
0 then mem := GAPInfo.BytesPerVariable;
fi;
fi;
orbpart := QuoInt(memory,mem);
o := Orb( gens, pt, op, rec( report := QuoInt(orbpart,
10),
schreier := true,
hashlen := NextPrimeInt( orbpart*
2 ) ) );
Info( InfoOrb,
2,
"Enumerating up to ",orbpart,
" elements of orbit..." );
Enumerate(o,orbpart);
# Now find stabilizer generators:
stab := [];
done := false;
tries :=
0;
pr := ProductReplacer(gens,opt);
est :=
1;
prob :=
1;
gensi := List(gens,x->x^-
1);
while prob > errorbound
do
counter :=
0;
repeat
counter := counter +
1;
x := Next(pr);
y := op(pt,x);
p := Position(o,y);
Print(
".\c");
until p <> fail or counter > limit;
Print(
"\n");
if p = fail
then
Info( InfoOrb,
1,
"Giving up..." );
return rec(stab := stab, prob := fail);
fi;
z := x * Product(gensi{TraceSchreierTreeBack(o,p)});
if IsOne(z)
then
Info( InfoOrb,
2,
"Found identity element." );
else
AddSet(stab,z);
oldest := est;
if IsFunction(estimatefunc)
then
Info( InfoOrb,
2,
"Starting estimation (old est=", est,
")..." );
est := estimatefunc(stab);
Info( InfoOrb,
2,
"Finished estimation: ", est );
fi;
if est = oldest
then
prob := prob /
2;
Info( InfoOrb,
2,
"New estimate is the same, prob=",prob );
else
prob :=
1;
Info( InfoOrb,
2,
"New estimate is ",est,
", prob=",prob );
fi;
fi;
od;
return rec(stab := stab, prob := prob);
end );
InstallGlobalFunction( ORB_FindNeedleMappers,
function( gens, pt, op, needles, memory, mappers, opt )
local foundmappers,gensi,i,j,lookfunc,mem,o,p,r,size,x;
size := fail;
if IsGroup(gens)
then
if HasSize(gens)
then
size := Size(gens);
fi;
gens := GeneratorsOfGroup(gens);
fi;
if not IsBound(opt.orblen)
then
mem := Memory(pt);
if mem = fail
then
mem := SIZE_OBJ(pt);
if mem =
0 then
mem := GAPInfo.BytesPerVariable;
fi;
fi;
opt.orblen := QuoInt(memory,Length(needles)*mem);
if size <> fail and opt.orblen * opt.orblen > size
then
opt.orblen :=
2^(QuoInt(Log2Int(size),
2)+
1);
fi;
fi;
o := [];
Info( InfoOrb,
1,
"Enumerating ",Length(needles),
" orbits up to length ",
opt.orblen,
"..." );
for i in [
1..Length(needles)]
do
o[i] := Orb( gens, needles[i], op, rec( report := QuoInt(opt.orblen,
5),
schreier := true ) );
Enumerate(o[i],opt.orblen);
Info( InfoOrb,
2,
"Finished one orbit." );
od;
j := fail;
p := fail;
lookfunc :=
function( x )
# Uses o from outer function!
# Writes j and p in outer function!
# This uses the feature of GAP, that j and p here always refer to
# the outer function ORB_FindNeedleMappers
# Reads pt and op from outer function.
j :=
1;
while j <= Length(o)
do
p := Position(o[j],op(pt,x));
if p <> fail
then return true;
fi;
j := j +
1;
od;
j := fail;
p := fail;
return false;
end;
gensi := List(gens,x->x^-
1);
r := RandomSearcher( gens, lookfunc, opt );
# we just hand down the options record to the random searcher!
foundmappers := [];
i :=
1;
while Length(foundmappers) <= mappers and i <=
15 * mappers
do
x := Search(r);
if x = fail
then return foundmappers;
fi;
AddSet(foundmappers,x * Product(gensi{TraceSchreierTreeBack(o[j],p)}));
Info( InfoOrb,
2,
"Found a needle mapper, have now ",
Length(foundmappers),
".");
i := i +
1;
od;
return foundmappers;
end );
############################################################################
# A method to find transversals in matrix groups:
############################################################################
InstallGlobalFunction( FindWordsForRightTransversal,
function( Ggens, Hgens, index )
# Ggens and Hgens must be lists of invertible matrices over a
# finite field. The group H generated by Hgens must be a subgroup
# of G, which is generated by Ggens, index must be the index.
# This function returns words in Ggens that are a complete set
# of coset representatives of the right cosets {Hg}.
local Check,i,inv,l,n,res,v;
# Find invariant vectors:
Info(InfoOrb,
2,
"Computing invariant spaces of generators...");
l := List(Hgens,x->NullspaceMat(x-One(x)));
Info(InfoOrb,
2,
"Found dimensions: ",List(l,Length));
if Length(l) =
1 then
inv := l[
1];
else
inv := SumIntersectionMat(l[
1],l[
2]);
inv := inv[
2];
Info(InfoOrb,
2,
"Intersection has dim ",NrRows(inv));
i :=
3;
while i <= Length(l) and NrRows(inv) >
0 do
inv := SumIntersectionMat(inv,l[i]);
inv := inv[
2];
Info(InfoOrb,
2,
"Intersection has dim ",NrRows(inv));
i := i +
1;
od;
fi;
# Now inv contains vectors that span the invariant space.
if NrRows(inv) =
0 then
Info(InfoOrb,
1,
"no H-invariants except 0");
return fail;
fi;
Unbind(l);
Check :=
function( pt, op )
local i,o,words;
o := Orb(Ggens,pt,op,rec( report :=
2000, schreier := true,
hashlen := NextPrimeInt( index*
3 ) ));
Enumerate(o);
if Length(o) = index
then
# Hurra!
words := [[]];
for i in [
2..Length(o)]
do
Add(words,TraceSchreierTreeForward(o,i));
od;
return rec( words := words, orbit := o );
fi;
Info(InfoOrb,
2,
"Found orbit length ",Length(o));
return fail;
end;
# First try whether one basis vector provides an orbit of the
# right length:
Info(InfoOrb,
2,
"Trying to find a nice orbit on vectors...");
for i in [
1..NrRows(inv)]
do
Info(InfoOrb,
2,
"Using vector #",i,"(",NrRows(inv),")");
res := Check(ShallowCopy(inv[i]),OnRight);
if res <> fail
then return res;
fi;
od;
# Now try tuples of vectors:
n :=
2;
# first pairs, then maybe more
repeat
l := Matrix([],NrCols(inv),inv);
for i in [
1..n]
do
v := ZeroVector(NrRows(inv),inv[
1]);
Randomize(v);
Add(l,v*inv);
od;
Info(InfoOrb,
2,
"Using ",n,
"-tuple...");
res := Check(l,OnRight);
if res <> fail
then return res;
fi;
n := n +
1;
until n = NrRows(inv);
# Now try the last resort:
Info(InfoOrb,
2,
"Trying full invariant space...");
return Check(inv,OnRight);
end );
InstallGlobalFunction( FindWordsForLeftTransversal,
function( Ggens, Hgens, index )
# The same as above, but gives words for coset reps of the left
# cosets {gH}. This is done by using the transposition
# anti-automorphism. This maps a right transversal of the
# transposed groups to a left transversal. Since we take the
# transposed generators in both groups, the words have to be
# reversed. Note that the orbit is the one obtained for the
# transposed generators!
local res;
res := FindWordsForRightTransversal(List(Ggens,TransposedMat),
List(Hgens,TransposedMat),index);
if res = fail
then return fail;
fi;
res.words := List(res.words,Reversed);
return res;
end );
############################################################################
# Find transforming matrices:
############################################################################
InstallGlobalFunction( TransformingMatsLSE,
function( A, B )
local M,n;
# Returns the matrix of the linear system of equations to solve TA=BT
# such that the nullspace of that matrix gives a basis of the solution
# space in flat notation.
# "Flat notation" means Concatenation(T) instead of T.
# Each row of M will be an equation:
# M := [ [A[1][1],A[2][1],A[3][1],0,0,0,0,0,0],
# [A[1][2],A[2][2],A[3][2],0,0,0,0,0,0],
# [A[1][3],A[2][3],A[3][3],0,0,0,0,0,0],
# [0,0,0,A[1][1],A[2][1],A[3][1],0,0,0],
# [0,0,0,A[1][2],A[2][2],A[3][2],0,0,0],
# [0,0,0,A[1][3],A[2][3],A[3][3],0,0,0],
# [0,0,0,0,0,0,A[1][1],A[2][1],A[3][1]],
# [0,0,0,0,0,0,A[1][2],A[2][2],A[3][2]],
# [0,0,0,0,0,0,A[1][3],A[2][3],A[3][3]] ]
# -[ [B[1][1],0,0,B[1][2],0,0,B[1][3],0,0],
# [0,B[1][1],0,0,B[1][2],0,0,B[1][3],0],
# [0,0,B[1][1],0,0,B[1][2],0,0,B[1][3]],
# [B[2][1],0,0,B[2][2],0,0,B[2][3],0,0],
# [0,B[2][1],0,0,B[2][2],0,0,B[2][3],0],
# [0,0,B[2][1],0,0,B[2][2],0,0,B[2][3]],
# [B[3][1],0,0,B[3][2],0,0,B[3][3],0,0],
# [0,B[3][1],0,0,B[3][2],0,0,B[3][3],0],
# [0,0,B[3][1],0,0,B[3][2],0,0,B[3][3]] ];
n := NrRows(A);
if n <> NrCols(A) or n <> NrRows(B) or n <> NrCols(B)
then
Error(
"need square matrices of same size");
fi;
M := KroneckerProduct(OneMutable(A),TransposedMat(A))
- KroneckerProduct(B,OneMutable(B));
return TransposedMat(M);
end);
InstallGlobalFunction( TransformingMats,
function(A, B)
return NullspaceMat(TransformingMatsLSE(A,B));
end );
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <https://www.gnu.org/licenses/>.
##