This chapter contains some rather technical complements to the material handled in the chapters Permutations and Permutation Groups of the reference manual.
The command ConjugateGroup(
G,
p )
(see ConjugateGroup in the
reference manual) for a permutation group G with stabilizer chain
equips its result also with a stabilizer chain, namely with the chain of
G conjugate by p. Conjugating a stabilizer chain by a permutation p
means replacing all the points which appear in the orbit
components by
their images under p and replacing every permutation g which appears
in a labels
or transversal
component by its conjugate gp. The
conjugate gp acts on the mapped points exactly as g did on the
original points, i.e., (pnt·p)·gp = (pnt·g)·p. Since the entries in
the translabels
components are integers pointing to positions of the
labels
list, the translabels
lists just have to be permuted by p
for the conjugated stabilizer. Then generators
is reconstructed as
labels{ genlabels }
and transversal{ orbit }
as labels{
translabels{ orbit } }
.
This conjugation technique can be generalized. Instead of mapping points and permutations under the same permutation p, it is sometimes desirable (e.g., in the context of permutation group homomorphisms) to map the points with an arbitrary mapping map and the permutations with a homomorphism hom such that the compatibility of the actions is still valid: map(pnt)·hom(g) = map(pnt·g). (Of course the ordinary conjugation is a special case of this, with map(pnt) = pnt·p and hom(g) = gp.)
In the generalized case, the ``conjugated'' chain need not be a stabilizer chain for the image of hom, since the ``preimage'' of the stabilizer of map(b) (where b is a base point) need not fix b, but only fixes the preimage map−1(map(b)) setwise. Therefore the method can be applied only to one level and the next stabilizer must be computed explicitly. But if map is injective, we have map(b)·hom(g)=map(b) ⇔ b·g=b, and if this holds, then g=w(g1,…,gn) is a word in the generators g1,…,gn of the stabilizer of b and hom(g) = * w(hom(g1),…,hom(gn)) is in the ``conjugated'' stabilizer. If, more generally, hom is a right inverse to a homomorphism ϕ (i.e., ϕ(hom(g))=g ∀g), equality * holds modulo Ker ϕ; in this case the ``conjugated'' chain can be made into a real stabilizer chain by extending each level with the generators Ker ϕ and appending a proper stabilizer chain of Ker ϕ at the end. These special cases will occur in the algorithms for permutation group homomorphisms (see Group Homomorphisms in the reference manual).
To ``conjugate'' the points (i.e., orbit
) and permutations (i.e.,
labels
) of the Schreier tree, a loop is set up over the orbit
list
constructed during the orbit algorithm, and for each vertex b with
unique edge a(l)b ending at b, the label l is mapped with hom and
b with map. We assume that the orbit
list was built w.r.t. a
certain ordering of the labels, where l′ < l means that every point in
the orbit was mapped with l′ before it was mapped with l. This shape
of the orbit
list is guaranteed if the Schreier tree is extended only
by AddGeneratorsExtendSchreierTree
, and it is then also guaranteed for
the ``conjugated'' Schreier tree. (The ordering of the labels cannot be
read from the Schreier tree, however.)
In the generalized case, it can happen that the edge a(l)b bears a label l whose image is ``old'', i.e., equal to the image of an earlier label l′ < l. Because of the compatibility of the actions we then have map(b) = map(a)·hom(l)−1 = map(a)·hom(l′)−1 = map(al′−1), so map(b) is already equal to the image of the vertex al′−1. This vertex must have been encountered before b = al−1 because l′ < l. We conclude that the image of a label can be ``old'' only if the vertex at the end of the corresponding edge has an ``old'' image, too, but then it need not be ``conjugated'' at all. A similar remark applies to labels which map under hom to the identity.
Section Backtrack in the reference manual describes the basic functions for a backtrack search. The purpose of this section is to document how the general backtrack algorithm is implemented in GAP and which parts you have to modify if you want to write your own backtrack routines.
points
cellno
firsts
points[firsts[
j]]
is the first point in
points
which is in cell j,
lengths
lengths
could also be
read off the firsts
list, but since this need not be increasing, it
would require some searching. Similar for cellno
, which could be
replaced by a systematic search of points
, keeping track of what cell
is currently being traversed. With the above components, the mth cell
of a partition P is expressed as
P.points [
P.firsts[
m] ..
P.firsts[
m] +
P.lengths[
m] - 1 ]
. The most important
operations, however, to be performed upon P are the splitting of a cell
and the reuniting of the two parts. Following the strategy of J. Leon,
this is done as follows:
[
P.firsts[
m] ..
last
]
in the list
P.points
(for a suitable value of last).
[
last + 1 ..
P.firsts[
m] +
P.lengths[
m] - 1 ]
will form the additional cell. For this new cell
requires additional entries are added to the lists
P.firsts
(namely,
last+1
) and
P.lengths
(namely,
P.firsts[
m] +
P.lengths[
m] -
last - 1
).
P.cellno [
last+1 ..
P.firsts[
m] +
P.lengths[
m]-1 ]
must be set to the number of the new cell.
P.lengths[
m]
must be reduced to
last -
P.firsts[
m]
+ 1
.
Then reuniting the two cells requires only the reversal of steps 2 to 4
above. The list P
.points
need not be rearranged.
lib/stbcbckt.gi
which contains the code for backtracking in permutation
groups. They are mentioned here because you might find them helpful when
you want to implement you own backtracking function based on the
partition concept. An important argument to most of the functions is the
R-base ℜ, which you should regard as a black box. We will tell you how
to set it up, how to maintain it and where to pass it as argument, but it
is not necessary for you to know its internal representation. However, if
you insist to learn the whole story: Here are the record components from
which an R-base is made up:
domain
base
partition
where
where[ i ]
of Πi
rfm
chain
fix
Fixcells( Πi )
level
chain
, this will be changed to chains for the
stabilizers Ga1... ai for i=1,…,r during the
backtrack algorithm; if G is a symmetric group, only the number of
moved points is stored for each stabilizer
lev
level
entry for
Ga1…ai−1
level2
, lev2
false
otherwise. This second group H activated if
the R-base is constructed as EmptyRBase( [ G, H ], Ω,
Π0 )
(if G = H
, GAP sets level2 = true
instead).
nextLevel
As our guiding example, we present code for the function Centralizer
which calculates the centralizer of an element g in the group G. (The
real code is more general and has a few more subtleties.)
1 Π0 := TrivialPartition( Ω );
2 ℜ := EmptyRBase( G, Ω, Π0 );
3 ℜ.nextLevel := function( Π,
rbase )
4 local fix, p, q, where;
5 NextRBasePoint( Π,
rbase );
6 fix := Fixcells( Π );
7 for p in fix do
8 q := p ^ g;
9 where := IsolatePoint( Π, q );
10 if where <> false then
12 Add( fix, q );
13 ProcessFixpoint( ℜ, q );
14 AddRefinement( ℜ, "Centralizer", [ Π.cellno[ p ], q, where ] );
15 if Π.lengths[ where ] = 1 then
16 p := FixpointCellNo( Π, where );
17 ProcessFixpoint( ℜ, p );
18 AddRefinement( ℜ, "ProcessFixpoint", [ p, where ] );
19 fi;
20 fi;
21 od;
22 end;
23 return PartitionBacktrack(
24 G,
25 c -> g ^ c = g,
26 false,
27 ℜ,
28 [ Π0, g ],
29 L, R );
The list numbers below refer to the line numbers of the code above.
TrivialPartition( Ω )
), but we could have started with a finer
partition, e.g., into unions of g-cycles of the same length.
ℜ.nextLevel
which is called
whenever an additional member in the sequence Π0 ≥ Π1 ≥ …
of partitions is needed. If Πi does not yet contain enough base
points in one-point cells, GAP will call ℜ.nextLevel( Πi,
ℜ )
, and this function will choose a new base point ai+1, refine
Πi to Πi+1 (thereby changing the first argument) and store
all necessary information in ℜ.
NextRBasePoint
(actually, this is done
in the real code for Centralizer
).
Fixcells( Π )
returns the list of points in one-point cells of
Π (ordered as the cells are ordered in Π).
p ^ g
under c ∈ CG(e), we also know ( p ^ g ) ^ c = ( p ^ c ) ^ g
. We
therefore want to isolate these extra points in Π.
where = false
instead.
false
if q was already fixed the
stabilizer of the earlier base points (and true
otherwise; this is not
used here). Another call to ProcessFixpoint
like this was implicitly
made by the function NextRBasePoint
to register the chosen base point.
By contrast, the point q was not chosen this way, so ProcessFixpoint
must be called explicitly for q.
ℑ.partition
, where ℑ is a black box similar to ℜ,
but corresponding to the current ``image partition'' (hence it is an
``R-image'' in analogy to the R-base). Then GAP will call the function
Refinements.Centralizer( ℜ, ℑ, Π.cellno[ p ], p, where
)
, with the then current values of ℜ and ℑ, but where
Π.cellno[ p ]
, p, where still have the values they have at
the time of this AddRefinement
command. This function call will further
refine ℑ.partition
to yield Σi+1 as it is programmed in
the function Refinements.Centralizer
, which is described below. (The
global variable Refinements
is a record which contains all refinement
functions for all backtracking procedures.)
Refinements.ProcessFixpoint( ℜ, ℑ, p, where )
, which
is also described below.
false
if we are looking for a subgroup, true
in the case
of a representative search (when the result would be one
representative),
ℑ.data
, which has
in position 1 the first member Σ0 of the decreasing sequence
of ``image partitions'' mentioned in Backtrack in the
reference manual. In the centralizer example, position 2 contains the
element that is to be centralized. In the case of a representative
search, i.e., a conjugacy test g ^ c ?= h
, we
would have h instead of g here, and possibly a Σ0
different from Π0 (e.g., a partition into unions of h-cycles
of same length).
ℜ.nextLevel
, this has to be
executed once the base point ai+1. The analogous refinement step
from Σi to Σi+1 must be performed for each choice of an
image bi+1 for ai+1, and it will depend on the corresponding
value of Σi∧({bi+1}, Ω−{bi+1}). But before
we can continue our centralizer example, we must, for the interested
reader, document the record components of the other black box ℑ, as we
did above for the R-base black box ℜ. Most of the components change as
GAP walks up and down the levels of the search tree.
data
depth
bimg
ℜ.base
partition
level
ℜ.lev[ i ]
at the current level
perm
Fixcells( Πi )
to Fixcells( Σi
)
(this implies mapping (a1,…,ai) to (b1,…,bi))
level2
, perm2
false
otherwise (and true
if ℜ.level2 = true
)
As declared in the above code for Centralizer
, the refinement is
performed by the function Refinement.Centralizer( ℜ, ℑ,
Π.cellno[ p ], p, where )
. The functions in the record
Refinement
always take two additional arguments before the ones
specified in the AddRefinement
call (in line 13 above), namely the
R-base ℜ and the current value ℑ of the ``R-image''. In our
example, p is a fixpoint of Π = Πi ∧({ai+1}, Ω−{ai+1}) such that where = Π.cellno[ p ^ g ]
. The
Refinement
functions must return false
if the refinement is
unsuccessful (e.g., because it leads to Σi+1 having different
cell sizes from Πi+1) and true
otherwise. Our particular
function looks like this.
1 Refinements.Centralizer := function( ℜ, ℑ, cellno, p, where )
2 local Σ, q;
3 Σ := ℑ.partition;
4 q := FixpointCellNo( Σ, cellno ) ^ ℑ.data[ 2 ];
5 return IsolatePoint( Σ, q ) = where and ProcessFixpoint( ℑ, p, q );
6 end;
The list numbers below refer to the line numbers of the code immediately above.
ℑ.partition
.
cellno = Πi.cellno[
p ]
in Σ under g = ℑ.data[ 2 ]
is calculated.
true
only if the image q has the same cell
number in Σ as p had in Π (i.e., where) and if q can be
prescribed as an image for p under the coset of the stabilizer
Ga1…ai+1·c where c ∈ G is an (already constructed)
element mapping the earlier base points a1,…,ai+1 to the
already chosen images b1,…,bi+1. This latter condition is
tested by ProcessFixpoint( ℑ, p, q )
which, if successful, also
does the necessary bookkeeping in ℑ. In analogy to the remark about
line 12 in the program above, the chosen image bi+1 for the base
point ai+1 has already been processed implicitly by the function
PartitionBacktrack
, and this processing includes the construction of an
element c ∈ G which maps Fixcells( Πi )
to Fixcells(
Σi )
and ai+1 to bi+1. By contrast, the extra
fixpoints p and q in Πi+1 and Σi+1 were not chosen
automatically, so they require an explicit call of ProcessFixpoint
,
which replaces the element c by some c′·c (with c′ ∈ Ga1…ai+1) which in addition maps p to q, or returns false
if this
is impossible.
You should now be able to guess what Refinements.ProcessFixpoint( ℜ,
ℑ, p, where )
does: it simply returns ProcessFixpoint( ℑ,
p, FixpointCellNo( ℑ.partition, where ) )
.
nextLevel
, and the functions in the Refinements
record
which you need. Then you can start the backtrack by passing the R-base
and the additional data (for the data
component of the ``R-image'') to
PartitionBacktrack
.
StratMeetPartition( ℜ, Π, Λ [, g ] )
MeetPartitionStrat( ℜ, ℑ, Λ′ [, g′ ], strat )
Such a StratMeetPartition
command would typically appear in the
function call ℜ.nextLevel( Π, ℜ )
(during the refinement of
Πi to Πi+1). This command replaces Π by Π∧Λ (thereby changing the second argument) and returns a ``meet
strategy'' strat. This is (for us) a black box which serves two
purposes: First, it allows GAP to calculate faster the corresponding
meet Σ∧Λ′, which must then appear in a Refinements
function (during the refinement of Σi to Σi+1). It is
faster to compute Σ∧Λ′ with the ``meet strategy'' of
Π∧Λ because if the refinement of Σ is successful
at all, the intersection of a cell from the left hand side of the
∧ sign with a cell from the right hand side must have the same
size in both cases (and strat records these sizes, so that only
non-empty intersections must be calculated for Σ∧Λ′).
Second, if there is a discrepancy between the behaviour prescribed by
strat and the behaviour observed when refining Σ, the refinement
can immediately be abandoned.
On the other hand, if you only want to meet a partition Π with
Λ for a one-time use, without recording a strategy, you can
simply type StratMeetPartition( Π, Λ )
as in the following
example, which also demonstrates some other partition-related commands.
gap> P := Partition( [[1,2],[3,4,5],[6]] );; Cells( P ); [ [ 1, 2 ], [ 3, 4, 5 ], [ 6 ] ] gap> Q := Partition( OnTuplesTuples( last, (1,3,6) ) );; Cells( Q ); [ [ 3, 2 ], [ 6, 4, 5 ], [ 1 ] ] gap> StratMeetPartition( P, Q ); [ ] gap> # The ``meet strategy'' was not recorded, ignore this result. gap> Cells( P ); [ [ 1 ], [ 5, 4 ], [ 6 ], [ 2 ], [ 3 ] ]
You can even say StratMeetPartition( Π, ∆ )
where ∆
is simple a subset of Ω, it will then be interpreted as the
partition (∆,Ω−∆).
GAP makes use of the advantages of a ``meet strategy'' if the
refinement function in Refinements
contains a MeetPartitionStrat
command where strat is the ``meet strategy'' calculated by
StratMeetPartition
before. Such a command replaces ℑ.partition
by
its meet with Λ′, again changing the argument ℑ. The necessary
reversal of these changes when backtracking from a node (and prescribing
the next possible image for a base point) is automatically done by the
function PartitionBacktrack
.
In all cases, an additional argument g means that the meet is to be taken not with Λ, but instead with Λ·g−1, where operation on ordered partitions is meant cellwise (and setwise on each cell). (Analogously for the primed arguments.)
gap> P := Partition( [[1,2],[3,4,5],[6]] );; gap> StratMeetPartition( P, P, (1,6,3) );; Cells( P ); [ [ 1 ], [ 5, 4 ], [ 6 ], [ 2 ], [ 3 ] ]Note that P·(1,3,6) = Q.
g ^ c <> g
). Even if the construction of
c stops before images for all base points have been chosen, because a
refinement was unsuccessful, several multiplications will already have
been performed by (explicit or implicit) calls of ProcessFixpoint
, and,
actually, the general backtrack procedure implemented in GAP avoids
this.
For this purpose, GAP does not actually multiply the permutations but
rather stores all the factors of the product in a list. Specifically,
instead of carrying out the multiplication in c→ c′·c mentioned
in the comment to line 5 of the above program --- where c′ ∈ Ga1…ai+1 is a product of factorized inverse transversal
elements, see Stabilizer chain records in the reference manual ---
GAP appends the list of these factorized inverse transversal elements
(giving c′) to the list of factors already collected for c. Here c′
is multiplied from the left and is itself a product of inverses of
strong generators of G, but GAP simply spares itself all the work of
inverting permutations and stores only a ``list of inverses'', whose
product is then (c′·c)−1 (which is the new value of c−1). The
``list of inverses'' is extended this way whenever ProcessFixpoint
is
called to improve c.
The product has to be multiplied out only when the property is finally
tested for the element c. But it is often possible to delay the
multiplication even further, namely until after the test, so that no
multiplication is required in the case of an unsuccessful test. Then the
test itself must be carried out with the factorized version of the
element c. For this purpose, PartitionBacktrack
can be passed its
second argument (the property in question) in a different way, not as a
single GAP function, but as a list like in lines 2--4 of the following
alternative excerpt from the code for Centralizer
.
1 return PartitionBacktrack( G,
2 [ g, g,
3 OnPoints,
4 c -> c!.lftObj = c!.rgtObj ],
5 false, ℜ, [ Π0, g ], L, R );
The test for c to have the property in question is of the form opr(
left, c ) = right
where opr is an operation function as
explained in External sets in the reference manual. In other words,
c passes the test if and only if it maps a ``left object'' to a ``right
object'' under a certain operation. In the centralizer example, we have
opr = OnPoints
and left = right = g, but in a conjugacy test, we
would have right = h.
OnPoints
) is the operation opr.
opr( right, c^-1 )
. Here
GAP operates with the inverse of c because this is the product of
the permutations stored in the ``list of inverses''. The preimage of
right under c is then calculated by mapping right with the factors
of c−1 one by one, without the need to multiply these factors. This
mapping of right is automatically done by the ProcessFixpoint
function whenever c is extended, the current value of right is always
stored in c!.rgtObj
. When the test given by the fourth entry is
finally performed, the element c has two components c!.lftObj =
left
and c!.rgtObj = opr( right, c^-1 )
, which must be used
to express the desired relation as a function of c. In our centralizer
example, we simply have to test whether they are equal.
This section describes a way of representing the automorphism group of a group as permutation group, following Sims97. The code however is not yet included in the GAP library.
In this section we present an example in which objects we already know
(namely, automorphisms of solvable groups) are equipped with the
permutation-like operations ^
and /
for action on positive integers.
To achieve this, we must define a new type of objects which behave like
permutations but are represented as automorphisms acting on an
enumerator. Our goal is to generalize the Schreier-Sims algorithm for
construction of a stabilizer chain to groups of such new automorphisms.
StabChainStrong(
EmptyStabChain( [ ], One( A ) ), GroupGenerators( A ) )
where
StabChainStrong
is a function as the one described in the pseudo-code
below:
StabChainStrong := function( S, newgens )
Extend the Schreier tree of S with newgens.
for sch in Schreier generators do
if sch ∉ S.stabilizer then
StabChainStrong( S.stabilizer, [ sch ] );
fi;
od;
end;
The membership test sch ∉ S.stabilizer
can be performed because
the stabilizer chain of S.stabilizer
is already correct at that
moment. We even know a base in advance, namely any generating set for
G. Fix such a generating set (g1,…,gd) and observe that this
base is generally very short compared to the degree |G| of the
operation. The problem with the Schreier-Sims algorithm, however, is then
that the length of the first basic orbit g1·A would already have the
magnitude of |G|, and the basic orbits at deeper levels would not be
much shorter. For the advantage of a short base we pay the high price of
long basic orbits, since the product of the (few) basic orbit lengths
must equal |A|. Such long orbits make the Schreier-Sims algorithm
infeasible, so we have to look for a longer base with shorter basic
orbits.
Assume that G is solvable and choose a characteristic series with elementary abelian factors. For the sake of simplicity we assume that N < G is an elementary abelian characteristic subgroup with elementary abelian factor group G/N. Since N is characteristic, A also acts as a group of automorphisms on the factor group G/N, but of course not necessarily faithfully. To retain a faithful action, we let A act on the disjoint union G/N with G, and choose as base (g1N,…,gdN,g1,…,gd). Now the first d basic orbits lie inside G/N and can have length at most [G:N]. Since the base points g1N,…, gdN form a generating set for G/N, their iterated stabilizer A(d+1) acts trivially on the factor group G/N, i.e., it leaves the cosets giN invariant. Accordingly, the next d basic orbits lie inside giN (for i=1,…,d) and can have length at most |N|.
Generalizing this method to a characteristic series G=N0 > N1 > … > Nl={1} of length l > 2, we can always find a base of length l·d
such that each basic orbit is contained in a coset of a characteristic
factor, i.e. in a set of the form giNj−1/Nj (where gi is one of
the generators of G and 1 ≤ j ≤ l). In particular, the length of
the basic orbits is bounded by the size of the corresponding
characteristic factors. To implement a Schreier-Sims algorithm for such a
base, we must be able to let automorphisms act on cosets of
characteristic factors giNj−1/Nj, for varying i and j. We
would like to translate each such action into an action on
{1,…,[Nj−1: Nj]}, because then we need not enumerate
the operation domain
|
transversal
list whose
first 1000 entries would be unbound, but still require 4 bytes of memory
each (see Stabilizer chain records in the reference manual).
Identifying each coset giNj−1/Nj into {1,…, [Nj−1 : Nj]} of course means that we have to change the action of the automorphisms on every level of the stabilizer chain. Such flexibility is not possible with permutations because their effect on positive integers is ``hardwired'' into them, but we can install new operations for automorphisms.
n = Length( pcgs )
;
range = [ start .. stop ]
indicating that N = 〈pcgs{ [ start .. n ] } 〉
and M = 〈pcgs{ [ stop + 1 .. n ] } 〉
, i.e., the cosets of
pcgs{ range }
form a base for the vector space N/M;
We first define a new representation for such enumerators and then construct them by simply putting these three pieces of data into a record object. The enumerator should behave as a list of group elements (representing cosets modulo M), consequently, its family will be the family of the pcgs itself.
IsCosetSolvableFactorEnumeratorRep := NewRepresentation ( "isCosetSolvableFactorEnumerator", IsEnumerator, [ "pcgs", "range", "representative" ] ); EnumeratorCosetSolvableFactor := function( pcgs, range, g ) return Objectify( NewKind( FamilyObj( pcgs ), IsCosetSolvableFactorEnumeratorRep ), rec( pcgs := pcgs, range := range, representative := g ) ); end;The definition of the operations
Length
, \[\]
and Position
is now
straightforward. The code has sometimes been abbreviated and is meant
``cum grano salis'', e.g., the declaration of the local variables has
been left out.
InstallMethod( Length, [ IsCosetSolvableFactorEnumeratorRep ], enum -> Product( RelativeOrdersPcgs( enum!.pcgs ){ enum!.range } ) );
InstallMethod( \[\], [ IsCosetSolvableFactorEnumeratorRep, IsPosRat and IsInt ], function( enum, pos ) elm := (); pos := pos - 1; for i in Reversed( enum!.range ) do p := RelativeOrderOfPcElement( enum!.pcgs, i ); elm := enum!.pcgs[ i ] ^ ( pos mod p ) * elm; pos := QuoInt( pos, p ); od; return enum!.representative * elm; end );
InstallMethod( Position, [ IsCosetSolvableFactorEnumeratorRep, IsObject, IsZeroCyc ], function( enum, elm, zero ) exp := ExponentsOfPcElement( enum!.pcgs, LeftQuotient( enum!.representative, elm ) ); pos := 0; for i in enum!.range do pos := pos * RelativeOrderOfPcElement( pcgs, i ) + exp[ i ]; od; return pos + 1; end );
pcgs!.group
act on [
1 .. Length( enum ) ]
for such an enumerator enum. We achieve this
by introducing a new representation of automorphisms on enumerators and
by putting the enumerator together with the automorphism into an object
which behaves like a permutation. Turning an ordinary automorphism into
such a special automorphism requires then the construction of a new
object which has the new kind. We provide an operation PermOnEnumerator(
model, aut )
which constructs such a new object having the same kind
as model, but representing the automorphism aut. So aut can be
either an ordinary automorphism or one which already has an enumerator in
its kind, but perhaps different from the one we want (i.e. from the one
in model).
IsPermOnEnumerator := NewCategory( "IsPermOnEnumerator", IsMultiplicativeElementWithInverse and IsPerm );
IsPermOnEnumeratorDefaultRep := NewRepresentation ( "IsPermOnEnumeratorDefaultRep", IsPermOnEnumerator and IsAttributeStoringRep, [ "perm" ] ); PermOnEnumerator := NewOperation( "PermOnEnumerator", [ IsEnumerator, IsObject ] );
InstallMethod( PermOnEnumerator, [ IsEnumerator, IsObject ], function( enum, a ) SetFilterObj( a, IsMultiplicativeElementWithInverse ); a := Objectify( NewKind( PermutationsOnEnumeratorsFamily, IsPermOnEnumeratorDefaultRep ), rec( perm := a ) ); SetEnumerator( a, enum ); return a; end );
InstallMethod( PermOnEnumerator, [ IsEnumerator, IsPermOnEnumeratorDefaultRep ], function( enum, a ) a := Objectify( TypeObj( a ), rec( perm := a!.perm ) ); SetEnumerator( a, enum ); return a; end );Next we have to install new methods for the operations which calculate the product of two automorphisms, because this product must again have the right kind. We also have to write a function which uses the enumerators to apply such an automorphism to positive integers.
InstallMethod( \*, IsIdenticalObj, [ IsPermOnEnumeratorDefaultRep, IsPermOnEnumeratorDefaultRep ], function( a, b ) perm := a!.perm * b!.perm; SetIsBijective( perm, true ); return PermOnEnumerator( Enumerator( a ), perm ); end );
InstallMethod( \^, [ IsPosRat and IsInt, IsPermOnEnumeratorDefaultRep ], function( p, a ) return PositionCanonical( Enumerator( a ), Enumerator( a )[ p ] ^ a!.perm ); end );How the corresponding methods for
p / aut
and aut ^ n
look
like is obvious.
Now we can formulate the recursive procedure StabChainStrong
which
extends the stabilizer chain by adding in new generators newgens. We
content ourselves again with pseudo-code, emphasizing only the lines
which set the EnumeratorDomainPermutation
. We assume that initially S
is a stabilizer chain for the trivial subgroup with a level for each pair
(range,g) characterizing an enumerator (as described above). We also
assume that the identity
element at each level already has the kind
corresponding to that level.
StabChainStrong := function( S, newgens )
for i in [ 1 .. Length( newgens ) ] do
newgens[ i ] := AutomorphismOnEnumerator( S.identity, newgens[ i ] );
od;
Extend the Schreier tree of S with newgens.
for sch in Schreier generators do
if sch ∉ S.stabilizer then
StabChainStrong( S.stabilizer, [ sch ] );
fi;
od;
end;
GAP 4 manual
March 2006