############################################################################
##
#W  grpperm.gi                  GAP library                   Heiko Thei"sen
##
#H  @(#)$Id: grpperm.gi,v 4.141.2.3 2003/03/21 17:33:04 gap Exp $
##
#Y  Copyright (C)  1996,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
#Y  (C) 1998 School Math and Comp. Sci., University of St.  Andrews, Scotland
#Y  Copyright (C) 2002 The GAP Group
##
Revision.grpperm_gi :=
    "@(#)$Id: grpperm.gi,v 4.141.2.3 2003/03/21 17:33:04 gap Exp $";


#############################################################################
##
#M  CanEasilyTestMembership( <permgroup> )
##
InstallTrueMethod(CanEasilyTestMembership,IsPermGroup);


#############################################################################
##
#M  CanEasilyComputePcgs( <permgroup> )
##
##  solvable permgroups can compute a pcgs. (The group may have been told it
##  is solvable without actually doing a solvability test, so we need the
##  implication.)
InstallTrueMethod(CanEasilyComputePcgs,IsPermGroup and IsSolvableGroup);

#M  CanComputeSizeAnySubgroup
InstallTrueMethod(CanComputeSizeAnySubgroup,IsPermGroup);

#############################################################################
##
#M  AsSubgroup( <G>, <U> )  . . . . . . . . . . . .  with stab chain transfer
##
InstallMethod( AsSubgroup,"perm groups",
    IsIdenticalObj, [ IsPermGroup, IsPermGroup ], 0,
    function( G, U )
    local S;
    # test if the parent is already alright
    if HasParent(U) and IsIdenticalObj(Parent(U),G) then
      return U;
    fi;

    if not IsSubset( G, U ) then
      return fail;
    fi;
    S:= SubgroupNC( G, GeneratorsOfGroup( U ) );
    UseIsomorphismRelation( U, S );
    UseSubsetRelation( U, S );
    if HasStabChainMutable( U )  then
        SetStabChainMutable( S, StabChainMutable( U ) );
    fi;
    return S;
end );

#############################################################################
##
#F  IndependentGeneratorsAbelianPPermGroup( <P>, <p> )  . . . nice generators
##
InstallGlobalFunction( IndependentGeneratorsAbelianPPermGroup,
    function ( P, p )
    local   inds,       # independent generators, result
            pows,       # their powers
            base,       # the base of the vectorspace
            size,       # the size of the group generated by <inds>
            orbs,       # orbits
            trns,       # transversal
            gens,       # remaining generators
            gens2,      # remaining generators for next round
            exp,        # exponent of <P>
            g,          # one generator from <gens>
            h,          # its power
            b,          # basepoint
            c,          # other point in orbit
            i, j, k;    # loop variables

    # initialize the list of independent generators
    inds := [];
    pows := [];
    base := [];
    size := 1;
    orbs := [];
    trns := [];

    # gens are the generators for the remaining group
    gens := GeneratorsOfGroup( P );

    # loop over the exponents
    exp := Maximum( List( gens, g -> LogInt( Order( g ), p ) ) );
    for i  in [exp,exp-1..1]  do

        # loop over the remaining generators
        gens2 := [];
        for j  in [1..Length(gens)]  do
            g := gens[j];
            h := g ^ (p^(i-1));

            # reduce <g> and <h>
            while h <> h^0
              and IsBound(trns[SmallestMovedPoint(h)^h])
            do
                g := g / pows[ trns[SmallestMovedPoint(h)^h] ];
                h := h / base[ trns[SmallestMovedPoint(h)^h] ];
            od;

            # if this is linear indepenent, add it to the generators
            if h <> h^0  then
                Add( inds, g );
                Add( pows, g );
                Add( base, h );
                size := size * p^i;
                b := SmallestMovedPoint(h);
                if not IsBound( orbs[b] )  then
                    orbs[b] := [ b ];
                    trns[b] := [ () ];
                fi;
                for c  in ShallowCopy(orbs[b])  do
                    for k  in [1..p-1]  do
                        Add( orbs[b], c ^ (h^k) );
                        trns[c^(h^k)] := Length(base);
                    od;
                od;

            # otherwise reduce and add to gens2
            else
                Add( gens2, g );

            fi;

        od;

        # prepare for the next round
        gens := gens2;
        pows := List( pows,i->i^ p );

    od;

    # return the indepenent generators
    return inds;
end );

#############################################################################
##
#M  IndependentGeneratorsOfAbelianGroup( <G> )  . . . . . . . nice generators
##
InstallMethod( IndependentGeneratorsOfAbelianGroup, "for perm group", true,
        [ IsPermGroup and IsAbelian ], 0,
    function ( G )
    local   inds,       # independent generators, result
            p,          # prime factor of group size
            gens,       # generators of <p>-Sylowsubgroup
            g,          # one generator
            o;          # its order

    # loop over all primes
    inds := [];
    for p  in Union( List( GeneratorsOfGroup( G ),
            g -> Factors(Order(g)) ) )  do
      if p <> 1  then

        # compute the generators for the <p>-Sylowsubgroup
        gens := [];
        for g  in GeneratorsOfGroup( G )  do
            o := Order(g);
            while o mod p = 0  do o := o / p; od;
            if g^o <> g^0  then Add( gens, g^o );  fi;
        od;

        # append the independent generators for the <p>-Sylowsubgroup
        Append( inds,
                IndependentGeneratorsAbelianPPermGroup(
                    GroupByGenerators( gens, () ), p ) );

      fi;
    od;

    # return the independent generators
    return inds;
end );

#############################################################################
##
#F  OrbitPerms( <gens>, <d> ) . . . . . . . . . . . . . orbit of permutations
##
InstallGlobalFunction( OrbitPerms, function( gens, d )
    local   max,  orb,  new,  pnt,  img,  gen;
  
        # get the largest point <max> moved by the group <G>
        max := LargestMovedPoint( gens );

        # handle fixpoints
        if not d in [1..max]  then
            return [ d ];
        fi;

        # start with the singleton orbit
        orb := [ d ];
        new := BlistList( [1..max], [1..max] );
        new[d] := false;

        # loop over all points found
        for pnt  in orb  do

            # apply all generators <gen>
            for gen  in gens  do
                img := pnt ^ gen;

                # add the image <img> to the orbit if it is new
                if new[img]  then
                    Add( orb, img );
                    new[img] := false;
                fi;

            od;

        od;
        return orb;
end );

#############################################################################
##
#F  OrbitsPerms( <gens>, <D> )  . . . . . . . . . . .  orbits of permutations
##
InstallGlobalFunction( OrbitsPerms, function( gens, D )
    local   max,  dom,  new,  orbs,  fst,  orb,  pnt,  gen,  img;
    
        # get the largest point <max> moved by the group <G>
        max := LargestMovedPoint( gens );
        dom := BlistList( [1..max], D );
        new := BlistList( [1..max], [1..max] );
        orbs := [];

        # repeat until the domain is exhausted
        fst := Position( dom, true );
        while fst <> fail  do

            # start with the singleton orbit
            orb := [ fst ];
            new[fst] := false;
            dom[fst] := false;

            # loop over all points found
            for pnt  in orb  do

                # apply all generators <gen>
                for gen  in gens  do
                    img := pnt ^ gen;

                    # add the image <img> to the orbit if it is new
                    if new[img]  then
                        Add( orb, img );
                        new[img] := false;
                        dom[img] := false;
                    fi;

                od;

            od;

            # add the orbit to the list of orbits and take next point
            Add( orbs, orb );
            fst := Position( dom, true, fst );

        od;

        # add the remaining points of <D>, they are fixed
        for pnt  in D do
	  if pnt>max then
            Add( orbs, [ pnt ] );
	  fi;
        od;
        
        return orbs;
end );


#############################################################################
##
#M  SmallestMovedPoint( <C> ) . . . . . . .  for a collection of permutations
##
SmallestMovedPointPerms := function( C )
    local   min,  m,  gen;
    
    min := infinity;
    for gen  in C  do
        if not IsOne( gen ) then
            m := SmallestMovedPointPerm( gen );
            if m < min  then
                min := m;
            fi;
        fi;
    od;
    return min;
end;

InstallMethod( SmallestMovedPoint,
    "for a collection of permutations",
    true,
    [ IsPermCollection ], 0,
    SmallestMovedPointPerms );

InstallMethod( SmallestMovedPoint,
    "for an empty list",
    true,
    [ IsList and IsEmpty ], 0,
    SmallestMovedPointPerms );


#############################################################################
##
#M  LargestMovedPoint( <C> )  . . . . . . .  for a collection of permutations 
##
LargestMovedPointPerms := function( C )
    local   max,  m,  gen;
    
    max := 0;
    for gen  in C  do
        if not IsOne( gen )  then
            m := LargestMovedPointPerm( gen );
            if max < m  then
                max := m;
            fi;
        fi;
    od;
    return max;
end;

InstallMethod( LargestMovedPoint,
    "for a collection of permutations",
    true,
    [ IsPermCollection ], 0,
    LargestMovedPointPerms );

InstallMethod( LargestMovedPoint,
    "for an empty list",
    true,
    [ IsList and IsEmpty ], 0,
    LargestMovedPointPerms );


#############################################################################
##
#F  MovedPoints( <C> )  . . . . . . . . . .  for a collection of permutations 
##
MovedPointsPerms:= function( C )
    local   mov,  gen,  pnt;
    
    mov := [  ];
    for gen  in C  do
        if gen <> One( gen )  then
            for pnt  in [ SmallestMovedPoint( gen ) ..
                           LargestMovedPoint( gen ) ]  do
                if pnt ^ gen <> pnt  then
                    mov[ pnt ] := pnt;
                fi;
            od;
        fi;
    od;
    return Set( mov );
end;

InstallMethod( MovedPoints, "for a collection of permutations", true,
    [ IsPermCollection ], 0,
    MovedPointsPerms );

InstallMethod( MovedPoints, "for an empty list", true,
    [ IsList and IsEmpty ], 0,
    MovedPointsPerms );

InstallMethod( MovedPoints, "for a permutation", true, [ IsPerm ], 0,
function(p)
  return MovedPointsPerms([p]);
end);


#############################################################################
##
#M  NrMovedPoints( <C> )  . . . . . . . . .  for a collection of permutations 
##
NrMovedPointsPerms := function( C )
    local   mov,  sma,  pnt;
    
    mov := 0;
    sma := SmallestMovedPoint( C );
    if sma = infinity  then
        return 0;
    fi;
    for pnt  in [ sma .. LargestMovedPoint( C ) ]  do
        if ForAny( C, gen -> pnt ^ gen <> pnt )  then
            mov := mov + 1;
        fi;
    od;
    return mov;
end;

InstallMethod( NrMovedPoints,
    "for a collection of permutations",
    true,
    [ IsPermCollection ], 0,
    NrMovedPointsPerms );

InstallMethod( NrMovedPoints,
    "for an empty list",
    true,
    [ IsList and IsEmpty ], 0,
    NrMovedPointsPerms );


#############################################################################
##
#M  LargestMovedPoint( <G> )  . . . . . . . . . . . . for a permutation group
##
InstallMethod( LargestMovedPoint,
    "for a permutation group",
    true,
    [ IsPermGroup ], 0,
    G -> LargestMovedPoint( GeneratorsOfGroup( G ) ) );


#############################################################################
##
#M  SmallestMovedPoint( <G> ) . . . . . . . . . . . . for a permutation group
##
InstallMethod( SmallestMovedPoint,
    "for a permutation group",
    true,
    [ IsPermGroup ], 0,
    G -> SmallestMovedPoint( GeneratorsOfGroup( G ) ) );


#############################################################################
##
#M  MovedPoints( <G> )  . . . . . . . . . . . . . . . for a permutation group
##
InstallMethod( MovedPoints,
    "for a permutation group",
    true,
    [ IsPermGroup ], 0,
    G -> MovedPoints( GeneratorsOfGroup( G ) ) );


#############################################################################
##
#M  NrMovedPoints( <G> )  . . . . . . . . . . . . . . for a permutation group
##
InstallMethod( NrMovedPoints,
    "for a permutation group",
    true,
    [ IsPermGroup ], 0,
    G -> NrMovedPoints( GeneratorsOfGroup( G ) ) );


#############################################################################
##
#M  BaseOfGroup( <G> )
##
InstallMethod( BaseOfGroup,
    "for a permutation group",
    true,
    [ IsPermGroup ], 0,
    G -> BaseStabChain( StabChainMutable( G ) ) );


#############################################################################
##
#M  Size( <G> ) . . . . . . . . . . . . . . . . . . size of permutation group
##
InstallMethod( Size,
    "for a permutation group",
    true,
    [ IsPermGroup ], 0,
    G -> SizeStabChain( StabChainMutable( G ) ) );


#############################################################################
##
#R  IsPermGroupEnumeratorRep  . . . . . . . . . . . enumerator for perm group
##
DeclareRepresentation( "IsPermGroupEnumeratorRep",
    IsAttributeStoringRep, [ "stabChain" ] );


#############################################################################
##
#M  Enumerator( <G> ) . . . . . . . . . . . . enumerator of permutation group
##
InstallMethod( Enumerator,
    "for a permutation group",
    true,
    [ IsPermGroup ], 0,
    G -> Objectify( NewType( FamilyObj( G ),
                             IsList and IsPermGroupEnumeratorRep ),
                    rec( stabChain := StabChainMutable( G ) ) ) );

InstallMethod( Length,
    "for enumerator of a permutation group",
    true,
    [ IsList and IsPermGroupEnumeratorRep ], 0,
    G -> SizeStabChain( G!.stabChain ) );

InstallMethod( \[\],
    "for enumerator of a permutation group, and pos. integer",
    true,
    [ IsList and IsPermGroupEnumeratorRep, IsPosInt ], 0,
    function( G, pos )
    local   elm,  S,  len;
    
    S := G!.stabChain;
    elm := S.identity;
    pos := pos - 1;
    while Length( S.genlabels ) <> 0  do
        len := Length( S.orbit );
        elm := LeftQuotient( InverseRepresentative
                       ( S, S.orbit[ pos mod len + 1 ] ), elm );
        pos := QuoInt( pos, len );
        S := S.stabilizer;
    od;
    return elm;
end );

InstallMethod( Position,
    "for enumerator of a permutation group, permutation, and zero",
    true,
    [ IsList and IsPermGroupEnumeratorRep, IsPerm, IsZeroCyc ], 0,
    function( G, elm, zero )
    local   pos,  val,  S,  img;
    
    pos := 1;
    val := 1;
    S := G!.stabChain;
    while Length( S.genlabels ) <> 0  do
        img := S.orbit[ 1 ] ^ elm;
        pos := pos + val * ( Position( S.orbit, img ) - 1 );
        val := val * Length( S.orbit );
        elm := elm * InverseRepresentative( S, img );
        S := S.stabilizer;
    od;
    if elm <> S.identity  then
        return fail;
    fi;
    return pos;
end );

#############################################################################
##
#M  PositionCanonical
##
InstallMethod(PositionCanonical,"perm grp enumerator",true,
  [IsList and IsPermGroupEnumeratorRep,IsObject],0, Position);


#############################################################################
##
#M  ViewObj( <enum> ) . . . . . . . . . . . . . . . . .  enum. of perm. group
##
InstallMethod( ViewObj,
    "for enumerator of a permutation group",
    true,
    [ IsList and IsPermGroupEnumeratorRep ], 0,
    function( G )
    Print( "<enumerator of perm group>" );
end );


#############################################################################
##
#M  PrintObj( <enum> )  . . . . . . . . . . . . . . . .  enum. of perm. group
##
InstallMethod( PrintObj,
    "for enumerator of a permutation group",
    true,
    [ IsList and IsPermGroupEnumeratorRep ], 0,
    function( G )
    Print( "<enumerator of perm group>" );
end );
#T this is not nice!


#############################################################################
##
#M  Random( <G> ) . . . . . . . . . . . . . . . . . . . . . .  random element
##
InstallMethod( Random,
    "for a permutation group",
    true,
    [ IsPermGroup ], 10,
    function( G )
    local   S,  rnd;

    # go down the stabchain and multiply random representatives
    S := StabChainMutable( G );
    rnd := S.identity;
    while Length( S.genlabels ) <> 0  do
        rnd := LeftQuotient( InverseRepresentative( S,
                       Random( S.orbit ) ), rnd );
        S := S.stabilizer;
    od;

    # return the random element
    return rnd;
end );


#############################################################################
##
#M  <g> in <G>  . . . . . . . . . . . . . . . . . . . . . . . membership test
##
InstallMethod( \in,
    "for a permutation, and a permutation group",
    true,
    [ IsPerm, IsPermGroup ], 0,
    function( g, G )
    if g = One( G )  or  g in GeneratorsOfGroup( G )  then
        return true;
    else
        G := StabChainMutable( G );
        return SiftedPermutation( G, g ) = G.identity;
    fi;
end );


#############################################################################
##
#M  ClosureGroup( <G>, <gens>, <options> )  . . . . . .  closure with options
##
##  The following function implements the method and may be called even with
##  incomplete (temp) stabilizer chains.
BindGlobal("DoClosurePrmGp",function( G, gens, options )
    local   C,          # closure of < <G>, <obj> >, result
            P, inpar,   # parent of the closure
            g,          # an element of gens
            chain;      # the stabilizer chain created

    options:=ShallowCopy(options); # options will be overwritten
    # if all generators are in <G>, <G> is the closure
    gens := Filtered( gens, gen -> not gen in G );
    if IsEmpty( gens )  then
        return G;
    fi;
        
    # otherwise decide between random and deterministic methods
    P := Parent( G );
    inpar := IsSubset( P, gens );
    while not inpar  and  not IsIdenticalObj( P, Parent( P ) )  do
        P := Parent( P );
        inpar := IsSubset( P, gens );
    od;
    if inpar  then
        CopyOptionsDefaults( P, options );
    elif not IsBound( options.random )  then
        options.random := DefaultStabChainOptions.random;
    fi;
    
    # perhaps <G> is normal in <C> with solvable factor group

#AH 5-feb-96: Disabled (see gap-dev discussion).
#    if DefaultStabChainOptions.tryPcgs
#       and ForAll( gens, gen -> ForAll( GeneratorsOfGroup( G ),
#                   g -> g ^ gen in G ) )  then
#        if inpar  then
#            C := SubgroupNC( P,
#                         Concatenation( GeneratorsOfGroup( G ), gens ) );
#        else
#            C := GroupByGenerators
#                 ( Concatenation( GeneratorsOfGroup( G ), gens ) );
#        fi;
#        pcgs:=TryPcgsPermGroup( [ C, G ], false, false, false );
#        if IsPcgs( pcgs )  then
#	  chain:=pcgs!.stabChain;
#	  if inpar  then  C := GroupStabChain( P, chain, true );
#		    else  C := GroupStabChain( chain );           fi;
#	  SetStabChainOptions( C, rec( random := options.random ) );
#
#	  UseSubsetRelation( C, G );
#	  return C;
#        fi;
#    fi;
    
    # make the base of G compatible with options.base
    chain := StructuralCopy( StabChainMutable( G ) );
    if IsBound( options.base )  then
        ChangeStabChain( chain, options.base,
                IsBound( options.reduced ) and options.reduced );
    fi;
    
    if LargestMovedPoint( Concatenation( GeneratorsOfGroup( G ),
               gens ) ) <= 100  then
        options := ShallowCopy( options );
        options.base := BaseStabChain( chain );
        for g  in gens  do
            if SiftedPermutation( chain, g ) <> chain.identity  then
                StabChainStrong( chain, [ g ], options );
            fi;
        od;
    else
        chain := ClosureRandomPermGroup( chain, gens, options );
    fi;
    if inpar  then  C := GroupStabChain( P, chain, true );
              else  C := GroupStabChain( chain );           fi;
    SetStabChainOptions( C, rec( random := options.random ) );

    UseSubsetRelation( C, G );
    return C;
end );

BindGlobal("PG_EMPTY_OPT",rec());

InstallOtherMethod( ClosureGroup,"permgroup, elements, options",
    true,
    [ IsPermGroup, IsList and IsPermCollection, IsRecord ], 0,
    DoClosurePrmGp);

InstallOtherMethod( ClosureGroup, "empty list",true,
        [ IsPermGroup, IsList and IsEmpty ], 0,
function( G, nogens )
    return G;
end );

InstallMethod( ClosureGroup, "permgroup, element",true,
  [ IsPermGroup, IsPerm ], 0,
function( G, g )
  return DoClosurePrmGp( G, [ g ], PG_EMPTY_OPT );
end );

InstallMethod( ClosureGroup, "permgroup, elements",true,
        [ IsPermGroup, IsList and IsPermCollection ], 0,
function( G, gens )
  return DoClosurePrmGp( G, gens, PG_EMPTY_OPT );
end );

InstallOtherMethod( ClosureGroup, "permgroup, element, options",true,
  [ IsPermGroup, IsPerm, IsRecord ], 0,
function( G, g, options )
  return DoClosurePrmGp( G, [ g ], options );
end );

InstallOtherMethod( ClosureGroup, "permgroup, permgroup, options", true,
  [ IsPermGroup, IsPermGroup, IsRecord ], 0,
function( G, H, options )
  return DoClosurePrmGp( G, GeneratorsOfGroup( H ), options );
end );

InstallOtherMethod( ClosureGroup, "empty list and options",true,
        [ IsPermGroup, IsList and IsEmpty, IsRecord ], 0,
    function( G, nogens, options )
    return G;
end );

#############################################################################
##
#M  NormalClosureOp( <G>, <U> ) . . . . . . . . . . . . . . . . in perm group
##
BindGlobal("DoNormalClosurePermGroup",function ( G, U )
    local   N,          # normal closure of <U> in <G>, result
            chain,      # stabilizer chain for the result
            rchain,     # restored version of <chain>, for `VerifySGS'
            options,    # options record for stabilizer chain construction
            gensG,      # generators of the group <G>
            genG,       # one generator of the group <G>
            gensN,      # generators of the group <N>
            genN,       # one generator of the group <N>
            cnj,        # conjugated of a generator of <U>
            random,  k, # values measuring randomness of <chain>
            param,  missing,  correct,  result,  i;

    # get a set of monoid generators of <G>
    gensG := GeneratorsOfGroup( G );

    # make a copy of the group to be closed
    N := SubgroupNC( G, GeneratorsOfGroup(U) );
    UseIsomorphismRelation(U,N);
    UseSubsetRelation(U,N);
    SetStabChainMutable( N, StabChainMutable( U ) );
    options := ShallowCopy( StabChainOptions( U ) );
    if IsBound( options.random )  then  random := options.random;
                                  else  random := 1000;            fi;
    options.random := 0;
    options.temp   := true;

    # make list of conjugates to be added to N
    repeat 
        gensN := [  ];
        for i  in [ 1 .. 10 ]  do 
            genG := SCRRandomSubproduct( gensG );
            cnj  := SCRRandomSubproduct( Concatenation
                            ( GeneratorsOfGroup( N ), gensN ) ) ^ genG;
            if not cnj in N  then 
                Add( gensN, cnj );
            fi;
        od;
        if not IsEmpty( gensN )  then
           N := ClosureGroup( N, gensN, options );
        fi;
    until IsEmpty( gensN );
    
    # Guarantee that all conjugates are in the normal  closure: Loop over all
    # generators of N
    gensN := ShallowCopy( GeneratorsOfGroup( N ) );
    for genN  in gensN  do

        # loop over the generators of G
        for genG  in gensG  do

            # make sure that the conjugated element is in the closure
            cnj := genN ^ genG;
            if not cnj in N  then
                N := ClosureGroup( N, [ cnj ], options );
                Add( gensN, cnj );
            fi;

        od;

    od;
    
    # Verify the stabilizer chain.
    chain := StabChainMutable( N );
    if not IsBound( chain.orbits )  then
        if IsBound( chain.aux )  then
            chain := SCRRestoredRecord( chain );
        fi;
        result := chain.identity;
    elif random = 1000  then
        missing := chain.missing;
        correct := chain.correct;
        rchain  := SCRRestoredRecord( chain );
        result  := VerifySGS( rchain, missing, correct );
        if not IsPerm(result) then 
            repeat 
                result := SCRStrongGenTest2(chain,[0,0,1,10/chain.diam,0,0]);
            until result <> ();
        fi;
        chain := rchain;
    else
        k := First([0..14],x->(3/5)^x <= 1-random/1000);
        if IsBound(options.knownBase) then 
            param := [k,4,0,0,0,0];
        else
            param := [QuoInt(k,2),4,QuoInt(k+1,2),4,50,5];
        fi;
        if options.random <= 200 then 
            param[2] := 2;
            param[4] := 2;
        fi;
        result := SCRStrongGenTest( chain, param, chain.orbits,
                          chain.basesize, chain.base,
                          chain.correct, chain.missing );
        if result = chain.identity  and  not chain.correct  then
            result := SCRStrongGenTest2( chain, param );
        fi;
        chain := SCRRestoredRecord( chain );
    fi;
    SetStabChainMutable( N, chain );
    if result <> chain.identity  then
        N := ClosureGroup( N, [ result ] );
    fi;

    # give N the proper randomness
    StabChainOptions(N).random:=random;
    
    # return the normal closure
    UseSubsetRelation( N, U );
    return N;
end );

InstallMethod( NormalClosureOp, "subgroup of perm group",
  IsIdenticalObj, [ IsPermGroup, IsPermGroup ], 0,
function(G,U)
  # test applicability
  if not IsSubset(G,U) then
    TryNextMethod();
  fi;
  return DoNormalClosurePermGroup(G,U);
end);

#############################################################################
##
#M  ConjugateGroup( <G>, <g> )  . . . . . . . . . . . .  of permutation group
##
InstallMethod( ConjugateGroup, "<P>, <g>", true, [ IsPermGroup, IsPerm ], 0,
function( G, g )
local   H,  S;
    
    H := GroupByGenerators( OnTuples( GeneratorsOfGroup( G ), g ), One( G ) );
    if HasStabChainMutable(G) then
      S := EmptyStabChain( [  ], One( H ) );
      ConjugateStabChain( StabChainMutable( G ), S, g, g );
      SetStabChainMutable( H, S );
    elif HasSize(G) then
      SetSize(H,Size(G));
    fi;
    UseIsomorphismRelation( G, H );
    return H;
end );

#############################################################################
##
#M  CommutatorSubgroup( <U>, <V> )  . . . . . . . . . . . . . for perm groups
##
InstallMethod( CommutatorSubgroup, "permgroups", IsIdenticalObj,
        [ IsPermGroup, IsPermGroup ], 0,
    function( U, V )
    local   C,       # the commutator subgroup
            CUV,     # closure of U,V
            doneCUV, # boolean; true if CUV is computed
            u,       # random subproduct of U.generators
            v,       # random subproduct of V.generators 
            comm,    # commutator of u,v
            list,    # list of commutators
            i;       # loop variable

    # [ <U>, <V> ] = normal closure of < [ <u>, <v> ] >.
    C := TrivialSubgroup( U );
    doneCUV := false;
    
    # if there are lot of generators, use random subproducts
    if Length( GeneratorsOfGroup( U ) ) *
       Length( GeneratorsOfGroup( V ) ) > 10  then
        repeat
           list := [];
           for i in [1..10] do
               u := SCRRandomSubproduct( GeneratorsOfGroup( U ) );
               v := SCRRandomSubproduct( GeneratorsOfGroup( V ) );
               comm := Comm( u, v );
               if not comm in C then
                   Add( list, comm ) ;
               fi;
           od;
           if Length(list) > 0 then
               C := ClosureGroup( C, list, rec( random := 0,
                                                  temp := true ) );
               if not doneCUV then
                   CUV := ClosureGroup( U, V );
                   doneCUV := true;
               fi;
               C := DoNormalClosurePermGroup( CUV, C );
           fi;
        until IsEmpty( list );
    fi;

    # do the deterministic method; it will also check correctness 
    list := [];
    for u  in GeneratorsOfGroup( U )  do
        for v  in GeneratorsOfGroup( V )  do
           comm := Comm( u, v );
           if not comm in C then 
               Add( list, comm );
           fi;
        od;
    od;
    if not IsEmpty( list )  then
        C := ClosureGroup( C, list, rec( random := 0,
                                           temp := true ) );
        if not doneCUV then
           CUV := ClosureGroup( U, V );
           doneCUV := true;
        fi;
        C := DoNormalClosurePermGroup( CUV, C );
    fi;

    return C;
end );

#############################################################################
##
#M  DerivedSubgroup( <G> )  . . . . . . . . . . . . . .  of permutation group
##
InstallMethod( DerivedSubgroup,"permgrps",true, [ IsPermGroup ], 0,
    function( G )
    local   D,          # derived subgroup of <G>, result
            g, h,       # random subproducts of generators 
            comm,       # their commutator
            list,       # list of commutators
            i,j;  # loop variables

    # find the subgroup generated by the commutators of the generators
    D := TrivialSubgroup( G );

    # if there are >4 generators, use random subproducts
    if Length( GeneratorsOfGroup( G ) ) > 4 then 
        repeat 
            list := [];
            for i in [1..10] do
                g := SCRRandomSubproduct( GeneratorsOfGroup( G ) );
                h := SCRRandomSubproduct( GeneratorsOfGroup( G ) );
                comm := Comm( g, h );
                if not comm in D then  
                   Add( list, comm );
                fi;
            od;
            if Length(list) > 0 then 
               D := ClosureGroup(D,list,rec( random := 0,
                                               temp := true ) );
               D := DoNormalClosurePermGroup( G, D );
            fi;
        until list = [];
    fi;

    # do the deterministic method; it will also check random result
    list := [];
    for i in [ 2 .. Length( GeneratorsOfGroup( G ) ) ]  do
         for j  in [ 1 .. i - 1 ]  do
             comm := Comm( GeneratorsOfGroup( G )[i],
                           GeneratorsOfGroup( G )[j] );
             if not comm in D then 
                 Add( list, comm );
             fi;
         od;
    od;
    if not IsEmpty( list )  then
        D := ClosureGroup(D,list,rec( random := 0,
                                        temp := true ) );
	# give D a proper randomness
	if IsBound(StabChainOptions(G).random) then
	  StabChainOptions(D).random:=StabChainOptions(G).random;
	else
	  StabChainOptions(D).random:=DefaultStabChainOptions.random;
	fi;

        D := DoNormalClosurePermGroup( G, D );
    fi;

    return D;
end );

#############################################################################
##
#M  IsSimpleGroup( <G> )  . . . . . . . test if a permutation group is simple
##
##  This  is  a most interesting function.   It  tests whether  a permutation
##  group is  simple  by testing whether the group is  perfect and then  only
##  looking at the size of the group and the degree of a primitive operation.
##  Basically  it uses  the O'Nan--Scott theorem, which gives  a pretty clear
##  description of perfect primitive groups.  This algorithm is described  in
##  William M. Kantor,
##  Finding Composition Factors of Permutation Groups of Degree $n\leq 10^6$,
##  J. Symbolic Computation, 12:517--526, 1991.
##
InstallMethod( IsSimpleGroup,"for permgrp", true, [ IsPermGroup ], 0,
    function ( G )
    local   D,          # operation domain of <G>
            hom,        # transitive constituent or blocks homomorphism
            d,          # degree of <G>
            n, m,       # $d = n^m$
            simple,     # list of orders of simple groups
            transperf,  # list of orders of transitive perfect groups
            s, t;       # loop variables

    # if <G> is the trivial group, it is simple
    if IsTrivial( G )  then
        return true;
    fi;

    # first find a transitive representation for <G>
    D := Orbit( G, SmallestMovedPoint( G ) );
    if not IsEqualSet( MovedPoints( G ), D )  then
        hom := ActionHomomorphism( G, D,"surjective" );
        if Size( G ) <> Size( Image( hom ) )  then
            return false;
        fi;
        G := Image( hom );
    fi;

    # next find a primitive representation for <G>
    D := Blocks( G, MovedPoints( G ) );
    while Length( D ) <> 1  do
        hom := ActionHomomorphism( G, D, OnSets,"surjective" );
        if Size( G ) <> Size( Image( hom ) )  then
            return false;
        fi;
        G := Image( hom );
        D := Blocks( G, MovedPoints( G ) );
    od;

    # compute the degree $d$ and express it as $d = n^m$
    D := MovedPoints( G );
    d := Length( D );
    n := SmallestRootInt( d );
    m := LogInt( d, n );
    if 10^6 < d  then
        Error("cannot decide whether <G> is simple or not");
    fi;

    # if $G = C_p$, it is simple
    if    IsPrimeInt( Size( G ) )  then
        return true;

    # if $G = A_d$, it is simple (unless $d < 5$)
    elif  Size( G ) = Factorial( d ) / 2  then
        return 5 <= d;

    # if $G = S_d$, it is not simple (except $S_2$)
    elif  Size( G ) = Factorial( d )  then
        return 2 = d;

    # if $G$ is not perfect, it is not simple (unless $G = C_p$, see above)
    elif  Size( DerivedSubgroup( G ) ) < Size( G )  then
        return false;

    # if $\|G\| = d^2$, it is not simple (Kantor's Lemma 4)
    elif  Size( G ) = d ^ 2  then
        return false;

    # if $d$ is a prime, <G> is simple
    elif  IsPrimeInt( d )  then
        return true;

    # if $G = U(4,2)$, it is simple (operation on 27 points)
    elif  d = 27 and Size( G ) = 25920  then
        return true;

    # if $G = PSL(n,q)$, it is simple (operations on prime power points)
    elif  (  (d =      8 and Size(G) = (7^3-7)/2          )  # PSL(2,7)
          or (d =      9 and Size(G) = (8^3-8)            )  # PSL(2,8)
          or (d =     32 and Size(G) = (31^3-31)/2        )  # PSL(2,31)
          or (d =    121 and Size(G) =        237783237120)  # PSL(5,3)
          or (d =    128 and Size(G) = (127^3-127)/2      )  # PSL(2,127)
          or (d =   8192 and Size(G) = (8191^3-8191)/2    )  # PSL(2,8191)
          or (d = 131072 and Size(G) = (131071^3-131071)/2)  # PSL(2,131071)
          or (d = 524288 and Size(G) = (524287^3-524287)/2)) # PSL(2,524287)
      and IsTransitive( Stabilizer( G, D[1] ), Difference( D, [ D[1] ] ) )
    then
        return true;

    # if $d$ is a prime power, <G> is not simple (except the cases above)
    elif  IsPrimePowerInt( d )  then
        return false;

    # if we don't have at least an $A_5$ acting on the top, <G> is simple
    elif  m < 5  then
        return true;

    # otherwise we must check for some special cases
    else

        # orders of simple subgroups of $S_n$ with primitive normalizer
        simple := [ ,,,,,
          [60,360],,,,                  #  5: A(5), A(6)
          [60,360,1814400],,            # 10: A(5), A(6), A(10)
          [660,7920,95040,239500800],,  # 12: PSL(2,11), M(11), M(12), A(12)
          [1092,43589145600],           # 14: PSL(2,13), A(14)
          [360,2520,20160,653837184000] # 15: A(6), A(7), A(8), A(15)
        ];

        # orders of transitive perfect subgroups of $S_m$
        transperf := [ ,,,,
          [60],                         # 5: A(5)
          [60,360],                     # 6: A(5), A(6)
          [168,2520],                   # 7: PSL(3,2), A(7)
          [168,8168,20160]              # 8: PSL(3,2), AGL(3,2), A(8)
        ];

        # test the special cases (Kantor's Lemma 3)
        for s  in simple[n]  do
            for t  in transperf[m]  do
                if    Size( G ) mod (t * s^m) = 0
                  and (((t * (2*s)^m) mod Size( G ) = 0 and s <> 360)
                    or ((t * (4*s)^m) mod Size( G ) = 0 and s =  360))
                then
                    return false;
                fi;
            od;
        od;

        # otherwise <G> is simple
        return true;

    fi;

end );

#############################################################################
##
#M  IsSolvableGroup( <G> )  . . . . . . . . . . . . . . . .  solvability test
##
InstallMethod( IsSolvableGroup,"for permgrp", true, [ IsPermGroup ], 0,
function(G)
local pcgs;
  pcgs:=TryPcgsPermGroup( G, false, false, true );
  if IsPcgs(pcgs) then
    if not HasPcgs(G) then
      SetPcgs(G,pcgs);
    fi;
    if not HasPcgsElementaryAbelianSeries(G) then
      SetPcgsElementaryAbelianSeries(G,pcgs);
    fi;
    return true;
  else
    return false;
  fi;
end);

#############################################################################
##
#M  IsNilpotentGroup( <G> ) . . . . . . . . . . . . . . . . . nilpotency test
##
InstallMethod( IsNilpotentGroup,"for permgrp", true, [ IsPermGroup ], 0,
    G -> IsPcgs(PcgsCentralSeries(G) ) );

#############################################################################
##
#M  PcgsCentralSeries( <G> )
##
InstallMethod( PcgsCentralSeries,"for permgrp", true, [ IsPermGroup ], 0,
function(G)
local pcgs;
  pcgs:=TryPcgsPermGroup( G, true, false, false );
  if IsPcgs(pcgs) and not HasPcgs(G) then
    SetPcgs(G,pcgs);
  fi;
  return pcgs;
end);

#############################################################################
##
#M  DerivedSeriesOfGroup( <G> ) . . . . . . . . . . . . . . .  derived series
##
InstallMethod( DerivedSeriesOfGroup,"for permgrp", true, [ IsPermGroup ], 0,
    function( G )
    local  pcgs,  series;

    if   (not DefaultStabChainOptions.tryPcgs
       or ( HasIsSolvableGroup( G )  and  not IsSolvableGroup( G ) ))
       and not (HasIsSolvableGroup(G) and IsSolvableGroup(G)) then
        TryNextMethod();
    fi;
    
    pcgs := TryPcgsPermGroup( G, false, true, false );
    if not IsPcgs( pcgs )  then
        TryNextMethod();
    fi;
    series := NormalSeriesByPcgs( pcgs );
    if not HasDerivedSubgroup( G )  then
        if Length( series ) > 1  then  SetDerivedSubgroup( G, series[ 2 ] );
                                 else  SetDerivedSubgroup( G, G );  fi;
    fi;
    return series;
end );

#############################################################################
##
#M  LowerCentralSeriesOfGroup( <G> )  . . . . . . . . .  lower central series
##
InstallMethod( LowerCentralSeriesOfGroup,"for permgrp", true, [ IsPermGroup ], 0,
    function( G )
    local  pcgs,  series;

    if    not DefaultStabChainOptions.tryPcgs
       or HasIsNilpotentGroup( G )  and  not IsNilpotentGroup( G ) 
       and not (HasIsNilpotentGroup(G) and IsNilpotentGroup(G)) then
        TryNextMethod();
    fi;
    
    pcgs := TryPcgsPermGroup( G, true, true, false );
    if not IsPcgs( pcgs )  then
        TryNextMethod();
    fi;
    series := NormalSeriesByPcgs( pcgs );
    if not HasDerivedSubgroup( G )  then
        if Length( series ) > 1  then  SetDerivedSubgroup( G, series[ 2 ] );
                                 else  SetDerivedSubgroup( G, G );  fi;
    fi;
    return series;
end );

InstallOtherMethod( ElementaryAbelianSeries, "perm group", true,
 [ IsPermGroup and IsFinite], 
  # we want this to come *before* the method for Pcgs computable groups
  RankFilter(IsPermGroup and CanEasilyComputePcgs and IsFinite)
  -RankFilter(IsPermGroup and IsFinite),
function(G)
local pcgs,ser,i,j,u,v;
  pcgs:=PcgsElementaryAbelianSeries(G);
  if not IsPcgs(pcgs) then
    TryNextMethod();
  fi;
  ser:=NormalSeriesByPcgs(pcgs);
  # work around inconsistency -- check that the series is indeed elab
  i:=2;
  while i<=Length(ser) do
    if not HasElementaryAbelianFactorGroup(ser[i-1],ser[i]) then
      u:=Core(G,CompositionSeries(ser[i-1])[2]);
      v:=Concatenation([u],List(ser{[i..Length(ser)]},j->Intersection(u,j)));
      ser:=ser{[1..i-1]};
      for j in v do
	if Size(j)<Size(ser[Length(ser)]) then
	  Add(ser,j);
	fi;
      od;
    fi;
    i:=i+1;
  od;
  return ser;
end);

#InstallOtherMethod( ElementaryAbelianSeries, fam -> IsIdenticalObj
#        ( fam, CollectionsFamily( CollectionsFamily( PermutationsFamily ) ) ),
#        [ IsList ], 0,
#    function( list )
#    local  pcgs;
#    
#    if    IsEmpty( list )
#       or not DefaultStabChainOptions.tryPcgs
#       or (      HasIsSolvableGroup( list[ 1 ] )
#            and not IsSolvableGroup( list[ 1 ] ) )  then
#        TryNextMethod();
##    fi;
#    
##    pcgs := TryPcgsPermGroup( list, false, false, true );
#    if not IsPcgs( pcgs )  then  return fail;
#                           else  return ElementaryAbelianSeries( pcgs );  fi;
#end );

#############################################################################
##
#M  PcgsPCentralSeriesPGroup( <G> )
##
InstallMethod( PcgsPCentralSeriesPGroup,"for permgrp", true, [ IsPermGroup ], 0,
function(G)
local pcgs;
  pcgs:=TryPcgsPermGroup( G, true, true, Factors(Size(G))[1] );
  if IsPcgs(pcgs) then
    Setter(IsPcgsPCentralSeriesPGroup)(pcgs,true);
  fi;
  if IsPcgs(pcgs) and not HasPcgs(G) then
    SetPcgs(G,pcgs);
  fi;
  return pcgs;
end);

#############################################################################
##
#M  PCentralSeries( <G>, <p> )  . . . . . . . . . . . . . .  p-central series
##
InstallMethod( PCentralSeriesOp,"for permgrp", true, [ IsPermGroup, IsPosInt ], 0,
function( G, p )
    local  pcgs;

  if Length(Factors(Size(G)))>1 then
    TryNextMethod();
  fi;
  pcgs:=PcgsPCentralSeriesPGroup(G);
  if not IsPcgs(pcgs) then
    TryNextMethod();
  fi;
  return NormalSeriesByPcgs(pcgs);
end);

#############################################################################
##
#M  SylowSubgroup( <G>, <p> ) . . . . . . . . . . . . . .  Sylow $p$-subgroup
##
InstallMethod( SylowSubgroupOp,"permutation groups", true,
  [ IsPermGroup, IsPosInt ], 0,
function( G, p )
local   S;
  if not HasIsSolvableGroup(G) and IsSolvableGroup(G) then
    # enforce solvable methods if we detected anew that the group is
    # solvable.
    return SylowSubgroupOp(G,p);
  fi;
  S := SylowSubgroupPermGroup( G, p );
  S := GroupStabChain( G, StabChainMutable( S ) );
  SetIsNilpotentGroup( S, true );
  return S;
end );

InstallGlobalFunction( SylowSubgroupPermGroup, function( G, p )
    local   S,          # <p>-Sylow subgroup of <G>, result
            q,          # largest power of <p> dividing the size of <G>
            D,          # domain of operation of <G>
            O,          # one orbit of <G> in this domain
            B,          # blocks of the operation of <G> on <D>
            f,          # action homomorphism of <G> on <O> or <B>
            T,          # <p>-Sylow subgroup in the image of <f>
            g, g2,      # one <p> element of <G>
            C, C2;      # centralizer of <g> in <G>
    
    # get the size of the <p>-Sylow subgroup
    q := 1;  while Size( G ) mod (q * p) = 0  do q := q * p;  od;

    # handle trivial subgroup
    if   q = 1  then
        return TrivialSubgroup( G );
    fi;

    # go down in stabilizers as long as possible
    S := StabChainMutable( G );
    while Length( S.orbit ) mod p <> 0  do
        S := S.stabilizer;
    od;
    G := GroupStabChain( G, S, true );

    # handle full group
    if q = Size( G )  then
        return G;
    fi;

    # handle <p>-Sylow subgroups of size <p>
    if q = p  then
        repeat g := Random( G );  until Order( g ) mod p = 0;
        g := g ^ (Order( g ) / p);
        return SubgroupNC( G, [ g ] );
    fi;

    # if the group is not transitive work with the transitive constituents
    D := MovedPoints( G );
    if not IsTransitive( G, D )  then
        S := G;
        D := ShallowCopy( D );
        while q < Size( S )  do
            O := Orbit( S, D[1] );
            f := ActionHomomorphism( S, O,"surjective" );
            T := SylowSubgroupPermGroup( ImagesSource( f ), p );
            S := PreImagesSet( f, T );
            SubtractSet( D, O );
        od;
        return S;
    fi;

    # if the group is not primitive work in the image first
    B := Blocks( G, D );
    if Length( B ) <> 1  then
        f := ActionHomomorphism( G, B, OnSets,"surjective" );
        T := SylowSubgroupPermGroup( ImagesSource( f ), p );
        if Size( T ) < Size( ImagesSource( f ) )  then
            S := SylowSubgroupPermGroup( PreImagesSet( f, T ), p );
            return S;
        fi;
    fi;

    # find a <p> element whose centralizer contains a full <p>-Sylow subgroup
    repeat g := Random( G );  until Order( g ) mod p = 0;
    g := g ^ (Order( g ) / p);
    C := Centralizer( G, g );
    while GcdInt( q, Size( C ) ) < q  do
        repeat g2 := Random( C );  until Order( g2 ) mod p = 0;
        g2 := g2 ^ (Order( g2 ) / p);
        C2 := Centralizer( G, g2 );
        if GcdInt( q, Size( C ) ) < GcdInt( q, Size( C2 ) )  then
            C := C2;  g := g2;
        fi;
    od;

    # the centralizer acts on the cycles of the <p> element
    B := List( Cycles( g, D ), Set );
    f := ActionHomomorphism( C, B, OnSets,"surjective" );
    T := SylowSubgroupPermGroup( ImagesSource( f ), p );
    S := PreImagesSet( f, T );
    return S;

end );

#############################################################################
##
#M  Socle( <G> )  . . . . . . . . . . .  socle of primitive permutation group
##
InstallMethod( Socle,"for permgrp", true, [ IsPermGroup ], 0,
    function( G )
    local   Omega,  deg,  shortcut,  coll,  d,  m,  c,  ds,  L,  z,  ord,
            p,  i;
    
    Omega := MovedPoints( G );
    
    if not IsPrimitive( G, Omega )  then
        TryNextMethod();
    fi;
    
    # Affine groups first.
    L := Earns( G, Omega );
    if L <> fail  then
        return L;
    fi;
    
    deg := Length( Omega );
    if deg >= 6103515625  then
        TryNextMethod();
    elif deg < 12960000  then
        shortcut := true;
        if deg >= 3125  then
            coll := Collected( FactorsInt( deg ) );
            d := Gcd( List( coll, c -> c[ 2 ] ) );
            if d mod 5 = 0  then
                m := 1;
                for c  in coll  do
                    m := m * c[ 1 ] ^ ( c[ 2 ] / d );
                od;
                if m >= 5  then
                    shortcut := false;
                fi;
            fi;
        fi;
        if shortcut  then
            ds := DerivedSeriesOfGroup( G );
            return ds[ Length( ds ) ];
        fi;
    fi;
    
    coll := Collected( FactorsInt( Size( G ) ) );
    if deg < 78125  then
        p := coll[ Length( coll ) ][ 1 ];
    else
        i := Length( coll );
        while coll[ i ][ 2 ] = 1  do
            i := i - 1;
        od;
        p := coll[ i ][ 1 ];
    fi;
    repeat
        repeat
            z := Random( G );
            ord := Order( z );
        until ord mod p = 0;
        z := z ^ ( ord / p );
    until Index( G, Centralizer( G, z ) ) mod p <> 0;
    L := NormalClosure( G, SubgroupNC( G, [ z ] ) );
    if deg >= 78125  then
        ds := DerivedSeriesOfGroup( L );
        L := ds[ Length( ds ) ];
    fi;
    if IsSemiRegular( L, Omega )  then
        L := ClosureSubgroup( L, Centralizer( G, L ) );
    fi;
    return L;
end );
    
#############################################################################
##
#M  FrattiniSubgroup( <G> ) . . . . . . . . . . . .  for permutation p-groups
##
InstallMethod( FrattiniSubgroup,"for permgrp", true, [ IsPermGroup ], 0,
    function( G )
    local   fac,  p,  l,  k,  i,  j;

    fac := Set( FactorsInt( Size( G ) ) );
    if Length( fac ) > 1  then
        TryNextMethod();
    elif fac[1]=1 then
      return G;
    fi;
    p := fac[ 1 ];
    l := GeneratorsOfGroup( G );
    k := [ l[1]^p ];
    for i  in [ 2 .. Length(l) ]  do
        for j  in [ 1 .. i-1 ]  do
            Add(k, Comm(l[i], l[j]));
        od;
        Add(k, l[i]^p);
    od;
    return SolvableNormalClosurePermGroup( G, k );
end );
        
#############################################################################
##
#M  OmegaOp( <G>, <p>, <n> )  . . . . . . . . . . . . for abelian perm groups
##
InstallMethod( OmegaOp, "in abelian perm groups", true,
        [ IsPermGroup, IsPosInt, IsPosInt ], 0,
    function( G, p, n )
    local   gens,  q,  gen,  ord,  o;
    
    if not IsAbelian( G )  then
        TryNextMethod();
    fi;
    q := p ^ n;
    gens := [  ];
    for gen  in IndependentGeneratorsOfAbelianGroup( G )  do
        ord := Order( gen );
        o := GcdInt( ord, q );
        if o <> 1  then
            Add( gens, gen ^ ( ord / o ) );
        fi;
    od;
    return SubgroupNC( G, gens );
end );

#############################################################################
##
#F  MinimizeExplicitTransversal( <U>, <maxmoved> )  . . . . . . . . . . local
##
InstallGlobalFunction( MinimizeExplicitTransversal, function( U, maxmoved )
    local   explicit,  lenflock,  flock,  lenblock,  index,  s;
    
    if     IsBound( U.explicit )
       and IsBound( U.stabilizer )  then
        explicit := U.explicit;
        lenflock := U.stabilizer.index * U.lenblock / Length( U.orbit );
        flock    := U.flock;
        lenblock := U.lenblock;
        index    := U.index;
        ChangeStabChain( U, [ 1 .. maxmoved ] );
        for s  in [ 1 .. Length( explicit ) ]  do
            explicit[ s ] := MinimalElementCosetStabChain( U, explicit[ s ] );
        od;
        Sort( explicit );
        U.explicit := explicit;
        U.lenflock := lenflock;
        U.flock    := flock;
        U.lenblock := lenblock;
        U.index    := index;
    fi;
end );

#############################################################################
##
#F  RightTransversalPermGroupConstructor( <filter>, <G>, <U> )  . constructor
##
BindGlobal( "RightTransversalPermGroupConstructor", function( filter, G, U )
    local   enum,  orbs,  domain,  bpt;
    
    enum := Objectify( NewType( FamilyObj( G ),
                           filter and IsList and IsDuplicateFreeList
                           and IsAttributeStoringRep ),
          rec( group := G,
            subgroup := U,
      stabChainGroup := CopyStabChain( StabChainMutable( G ) ),
   stabChainSubgroup := CopyStabChain( StabChainMutable( U ) ) ) );
    if not IsTrivial( G )  then
        orbs := ShallowCopy( OrbitsDomain( U, MovedPoints( G ) ) );
        Sort( orbs, function( o1, o2 )
            return Length( o1 ) < Length( o2 ); end );
        domain := Concatenation( orbs );
        G := enum!.stabChainGroup;
        U := enum!.stabChainSubgroup;
        while    Length( G.genlabels ) <> 0
              or Length( U.genlabels ) <> 0  do
            bpt := First( domain, p -> not IsFixedStabilizer( G, p ) );
            ChangeStabChain( G, [ bpt ], true  );  G := G.stabilizer;
            ChangeStabChain( U, [ bpt ], false );  U := U.stabilizer;
        od;
    fi;
    AddCosetInfoStabChain( enum!.stabChainGroup, enum!.stabChainSubgroup,
            LargestMovedPoint( enum!.group ) );
    MinimizeExplicitTransversal( enum!.stabChainSubgroup,
            LargestMovedPoint( enum!.group ) );
    return enum;
end );


#############################################################################
##
#R  IsRightTransversalPermGroupRep( <obj> ) . right transversal of perm group
##
DeclareRepresentation( "IsRightTransversalPermGroupRep",
    IsRightTransversalRep,
    [ "stabChainGroup", "stabChainSubgroup" ] );

InstallMethod( \[\],
    "for right transversal of perm. group, and pos. integer",
    true,
    [ IsList and IsRightTransversalPermGroupRep, IsPosInt ], 0,
    function( cs, num )
    return CosetNumber( cs!.stabChainGroup, cs!.stabChainSubgroup, num );
end );

InstallMethod( PositionCanonical,
    "for right transversal of perm. group, and permutation",
    IsCollsElms,
    [ IsList and IsRightTransversalPermGroupRep, IsPerm ], 0,
    function( cs, elm )
    return NumberCoset( cs!.stabChainGroup,
                        cs!.stabChainSubgroup,
                        elm );
end );


#############################################################################
##
#M  RightTransversalOp( <G>, <U> )  . . . . . . . . . . . . . for perm groups
##
InstallMethod( RightTransversalOp,
    "for two perm. groups",
    IsIdenticalObj,
    [ IsPermGroup, IsPermGroup ], 0,
    function( G, U )
    return RightTransversalPermGroupConstructor(
               IsRightTransversalPermGroupRep, G, U );
end );


#############################################################################
##
#R  IsRightTransversalByBaseImagesRep( <obj> )
##
DeclareRepresentation( "IsRightTransversalByBaseImagesRep",
    IsRightTransversalPermGroupRep, [] );

InstallMethod( \[\],
    "for right transversal by base images, and pos. integer",
    true,
    [ IsList and IsRightTransversalByBaseImagesRep, IsPosInt ], 0,
    function( cs, num )
    return CosetNumber( cs!.stabChainGroup, cs!.stabChainSubgroup, num,
                   BaseStabChain( cs!.stabChainGroup ) );
end );

InstallMethod( PositionCanonical,
    "for right transversal by base images, and object",
    IsCollsElms,
    [ IsList and IsRightTransversalByBaseImagesRep, IsPerm ], 0,
    function( cs, elm )
    local   S,  rep,  i;
    
    S := cs!.stabChainGroup;
    rep := S.identity;
    for i  in [ 1 .. Length( elm ) ]  do
      rep:= LeftQuotient( InverseRepresentative( S, elm[ i ] / rep ), rep );
      S:= S.stabilizer;
    od;
    return NumberCoset( cs!.stabChainGroup, cs!.stabChainSubgroup, rep );
end );


#############################################################################
##
#F  RightTransversalByBaseImages( <G>, <U> )  . . . . . . . .  by base images
##
BindGlobal( "RightTransversalByBaseImages", function( G, U )
    return RightTransversalPermGroupConstructor
           ( IsRightTransversalByBaseImagesRep, G, U );
end );


#############################################################################
##
#F  AddCosetInfoStabChain( <G>, <U>, <maxmoved> ) . . . . . .  add coset info
##
MAX_SIZE_TRANSVERSAL := 100000;

InstallGlobalFunction( AddCosetInfoStabChain, function( G, U, maxmoved )
    local   orb,  pimg,  img,  vert,  s,  t,  index,
            block,  B,  blist,  pos,  sliced,  lenflock,  i,  j,
            ss,  tt;
    
    if IsEmpty( G.genlabels )  then
        U.index    := 1;
        U.explicit := [ U.identity ];
        U.lenflock := 1;
        U.flock    := U.explicit;
    else
        AddCosetInfoStabChain( G.stabilizer, U.stabilizer, maxmoved );
        
        # U.index := [G_1:U_1];
        U.index := U.stabilizer.index * Length( G.orbit ) / Length( U.orbit );
        
        # block := 1 ^ <U,G_1>; is a block for G.
        block := OrbitPerms( Concatenation( U.generators,
                 G.stabilizer.generators ), G.orbit[ 1 ] );
        U.lenblock := Length( block );
        lenflock := Length( G.orbit ) / U.lenblock;

        # For small indices,  permutations   are multiplied,  so  we  need  a
        # multiplied transversal.
        if     IsBound( U.stabilizer.explicit )
           and U.lenblock * maxmoved <= MAX_SIZE_TRANSVERSAL
           and U.index    * maxmoved <= MAX_SIZE_TRANSVERSAL * lenflock  then
            U.explicit := [  ];
            U.flock    := [ G.identity ];
            tt := [  ];  tt[ G.orbit[ 1 ] ] := G.identity;
            for t  in G.orbit  do
                tt[ t ] := tt[ t ^ G.transversal[ t ] ] /
                           G.transversal[ t ];
            od;
        fi;
        
        # flock := { G.transversal[ B[1] ] | B in block system };
        blist := BlistList( G.orbit, block );
        pos := Position( blist, false );
        while pos <> fail  do
            img := G.orbit[ pos ];
            B := block{ [ 1 .. U.lenblock ] };
            sliced := [  ];
            while img <> G.orbit[ 1 ]  do
                Add( sliced, G.transversal[ img ] );
                img := img ^ G.transversal[ img ];
            od;
            for i  in Reversed( [ 1 .. Length( sliced ) ] )  do
                for j  in [ 1 .. Length( B ) ]  do
                    B[ j ] := B[ j ] / sliced[ i ];
                od;
            od;
            Append( block, B );
            if IsBound( U.explicit )  then
                Add( U.flock, tt[ B[ 1 ] ] );
            fi;
            #UniteBlist( blist, BlistList( G.orbit, B ) );
            UniteBlistList(G.orbit, blist, B );
            pos := Position( blist, false, pos );
        od;
        G.orbit := block;
        
        # Let <s> loop over the transversal elements in the stabilizer.
        U.repsStab := List( [ 1 .. U.lenblock ], x ->
                           BlistList( [ 1 .. U.stabilizer.index ], [  ] ) );
        U.repsStab[ 1 ] := BlistList( [ 1 .. U.stabilizer.index ],
                                      [ 1 .. U.stabilizer.index ] );
        index := U.stabilizer.index * lenflock;
        s := 1;
        
        # For  large  indices, store only   the  numbers of  the  transversal
        # elements needed.
        if not IsBound( U.explicit )  then

            # If  the   stabilizer   is the   topmost  level   with  explicit
            # transversal, this must contain minimal coset representatives.
            MinimizeExplicitTransversal( U.stabilizer, maxmoved );
            
            orb := G.orbit{ [ 1 .. U.lenblock ] };
            pimg := [  ];
            while index < U.index  do
                pimg{ orb } := CosetNumber( G.stabilizer, U.stabilizer, s,
                                       orb );
                t := 2;
                while t <= U.lenblock  and  index < U.index  do
                    
                    # For this point  in the  block,  find the images  of the
                    # earlier points under the representative.
                    vert := G.orbit{ [ 1 .. t - 1 ] };
                    img := G.orbit[ t ];
                    while img <> G.orbit[ 1 ]  do
                        vert := OnTuples( vert, G.transversal[ img ] );
                        img  := img           ^ G.transversal[ img ];
                    od;
            
                    # If $Ust = Us't'$ then $1t'/t/s in 1U$. Also if $1t'/t/s
                    # in 1U$ then $st/t' =  u.g_1$ with $u  in U, g_1 in G_1$
                    # and $g_1  =  u_1.s'$ with $u_1  in U_1,  s' in S_1$, so
                    # $Ust = Us't'$.
                    if ForAll( [ 1 .. t - 1 ], i -> not IsBound
                       ( U.translabels[ pimg[ vert[ i ] ] ] ) )  then
                        U.repsStab[ t ][ s ] := true;
                        index := index + lenflock;
                    fi;
                    
                    t := t + 1;
                od;
                s := s + 1;
            od;
            
        # For small indices, store a transversal explicitly.
        else
            for ss  in U.stabilizer.flock  do
                Append( U.explicit, U.stabilizer.explicit * ss );
            od;
            while index < U.index  do
                t := 2;
                while t <= U.lenblock  and  index < U.index  do
                    ss := U.explicit[ s ] * tt[ G.orbit[ t ] ];
                    if ForAll( [ 1 .. t - 1 ], i -> not IsBound
                           ( U.translabels[ G.orbit[ i ] / ss ] ) )  then
                        U.repsStab[ t ][ s ] := true;
                        Add( U.explicit, ss );
                        index := index + lenflock;
                    fi;
                    t := t + 1;
                od;
                s := s + 1;
            od;
            Unbind( U.stabilizer.explicit );
            Unbind( U.stabilizer.flock    );
        fi;
                    
    fi;
end );

#############################################################################
##
#F  NumberCoset( <G>, <U>, <r> )  . . . . . . . . . . . . . . coset to number
##
InstallGlobalFunction( NumberCoset, function( G, U, r )
    local   num,  b,  t,  u,  g1,  pnt,  bpt;
    
    if IsEmpty( G.genlabels )  or  U.index = 1  then
        return 1;
    fi;
    
    # Find the block number of $r$.
    bpt := G.orbit[ 1 ];
    b := QuoInt( Position( G.orbit, bpt ^ r ) - 1, U.lenblock );
        
    # For small indices, look at the explicit transversal.
    if IsBound( U.explicit )  then
        return b * U.lenflock + Position( U.explicit,
               MinimalElementCosetStabChain( U, r / U.flock[ b + 1 ] ) );
    fi;
        
    pnt := G.orbit[ b * U.lenblock + 1 ];
    while pnt <> bpt  do
        r   := r   * G.transversal[ pnt ];
        pnt := pnt ^ G.transversal[ pnt ];
    od;
    
    # Now $r$ stabilises the block. Find the first $t in G/G_1$ such that $Ur
    # = Ust$ for $s in G_1$. In this code, G.orbit[ <t> ] = bpt ^ $t$.
    num := b * U.stabilizer.index * U.lenblock / Length( U.orbit );
             # \_________This is [<U,G_1>:U] = U.lenflock_________/
    t := 1;
    pnt := G.orbit[ t ] / r;
    while not IsBound( U.translabels[ pnt ] )  do
        num := num + SizeBlist( U.repsStab[ t ] );
        t := t + 1;
        pnt := G.orbit[ t ] / r;
    od;
        
    # $r/t = u.g_1$ with $u in U, g_1 in G_1$, hence $t/r.u = g_1^-1$.
    u := U.identity;
    while pnt ^ u <> bpt  do
        u := u * U.transversal[ pnt ^ u ];
    od;
    g1 := LeftQuotient( u, r );  # Now <g1> = $g_1.t = u mod r$.
    while bpt ^ g1 <> bpt  do
        g1 := g1 * G.transversal[ bpt ^ g1 ];
    od;
                
    # The number of $r$  is the number of $g_1$  plus an offset <num> for
    # the earlier values of $t$.
    return num + SizeBlist( U.repsStab[ t ]{ [ 1 ..
                   NumberCoset( G.stabilizer, U.stabilizer, g1 ) ] } );

end );

#############################################################################
##
#F  CosetNumber( <arg> )  . . . . . . . . . . . . . . . . . . number to coset
##
InstallGlobalFunction( CosetNumber, function( arg )
    local   G,  U,  num,  tup,  b,  t,  rep,  pnt,  bpt,  index,  len;

    # Get the arguments.
    G := arg[ 1 ];  U := arg[ 2 ];  num := arg[ 3 ];
    if Length( arg ) > 3  then  tup := arg[ 4 ];
                          else  tup := false;     fi;

    if num = 1  then
        if tup = false  then  return G.identity;
                        else  return tup;         fi;
    fi;
    
    # Find the block $b$ addressed by <num>.
    if IsBound( U.explicit )  then
        index := U.lenflock;
    else
        index := U.stabilizer.index * U.lenblock / Length( U.orbit );
               # \_________This is [<U,G_1>:U] = U.lenflock_________/
    fi;
    b := QuoInt( num - 1, index );
    num := ( num - 1 ) mod index + 1;
        
    # For small indices, look at the explicit transversal.
    if IsBound( U.explicit )  then
        if tup = false  then
            return U.explicit[ num ] * U.flock[ b + 1 ];
        else
            return List( tup, t -> t / U.flock[ b + 1 ] / U.explicit[ num ] );
        fi;
    fi;
        
    # Otherwise, find the point $t$ addressed by <num>.
    t := 1;
    len := SizeBlist( U.repsStab[ t ] );
    while num > len  do
        num := num - len;
        t := t + 1;
        len := SizeBlist( U.repsStab[ t ] );
    od;
    if len < U.stabilizer.index  then
        num := PositionNthTrueBlist( U.repsStab[ t ], num );
    fi;
        
    # Find the representative $s$ in   the stabilizer addressed by <num>  and
    # return $st$.
    rep := G.identity;
    bpt := G.orbit[ 1 ];
    if tup = false  then
        pnt := G.orbit[ b * U.lenblock + 1 ];
        while pnt <> bpt  do
            rep := rep * G.transversal[ pnt ];
            pnt := pnt ^ G.transversal[ pnt ];
        od;
        pnt := G.orbit[ t ];
        while pnt <> bpt  do
            rep := rep * G.transversal[ pnt ];
            pnt := pnt ^ G.transversal[ pnt ];
        od;
        return CosetNumber( G.stabilizer, U.stabilizer, num ) / rep;
    else
        pnt := G.orbit[ b * U.lenblock + 1 ];
        while pnt <> bpt  do
            tup := OnTuples( tup, G.transversal[ pnt ] );
            pnt := pnt ^ G.transversal[ pnt ];
        od;
        pnt := G.orbit[ t ];
        while pnt <> bpt  do
            tup := OnTuples( tup, G.transversal[ pnt ] );
            pnt := pnt ^ G.transversal[ pnt ];
        od;
        return CosetNumber( G.stabilizer, U.stabilizer, num, tup );
    fi;
end );

#############################################################################
##
#M  ApproximateSuborbitsStabilizerPermGroup(<G>,<pnt>) . . . approximation of
##  the orbits of Stab_G(pnt) on all points of the orbit pnt^G. (As not
##  all schreier generators are used, the results may be the orbits of a
##  subgroup.)
##
InstallGlobalFunction( ApproximateSuborbitsStabilizerPermGroup,
    function(G,punkt)
local orbit,trans,stab,gen,pnt,img,norb,no,i,j,changed,processStabGen,
      StabGenAvailable,stgp,orblen,gens;
  if HasStabChainMutable( G ) and punkt in StabChainMutable(G).orbit then
# if we already have a stab chain and bother computing the proper
# stabilizer, a trivial orbit algorithm seems best.
    return Concatenation([[punkt]],
              OrbitsDomain(Stabilizer(G,punkt),
	                   Difference(MovedPoints(G),[punkt])));
#    orbit:=Difference(StabChainMutable(G).orbit,[punkt]);
#    stab:=Stabilizer(G,punkt));
#    # catch trivial case
#    if Length(stab)=0 then
#      stab:=[()];
#    fi;
#    stgp:=1;
#
#    processStabGen:=function()
#      stgp:=stgp+1;
#      if stgp>Length(stab) then
#	StabGenAvailable:=false;
#      fi;
#      return stab[stgp-1];
#    end;

  else
    # compute orbit and transversal
    orbit := [ punkt ];
    trans := [];
    trans[ punkt ] := ();
    stab:=[];
    for pnt  in orbit  do
      for gen  in GeneratorsOfGroup(G)  do
        img:=pnt/gen;
	if not IsBound( trans[ img ] )  then
	  Add( orbit, img );
	  trans[ img ] := gen;
	fi;
      od;
    od;
    orblen:=Length(orbit);
    orbit:=orbit{[2..Length(orbit)]};

    processStabGen:=function()
      i:=orblen; 
      gen:=();
      while 1<=i do
	gen:=gen*Random(GeneratorsOfGroup(G));
	i:=QuoInt(i,2);
      od;
      while punkt^gen<>punkt do
        gen:=gen*trans[punkt^gen];
      od;
      return gen;
    end;

  fi;

  StabGenAvailable:=true;
  # as the first approximation take a subgroup generated by three nontrivial
  # Schreier generators
  changed:=0;
  gens:=[];
  while changed<=3 and StabGenAvailable do
    gen:=processStabGen();
    if gen=() then
      if Random([1..10])>1 then
        changed:=changed+1;
      fi;
    else
      changed:=changed+1;
      Add(gens,gen);
    fi;
  od;
  if Length(gens)=0 then
    orbit:=List(orbit,i->[i]);
  else
    orbit:=OrbitsPerms(gens,orbit);
  fi;
  orbit:=List(orbit,i->Immutable(Set(i)));

  #trivial case
  if Length(orbit)=0 then
    changed:=infinity;
  else
    changed:=0;
  fi;

  # compute suborbits, fusing iteratively under generators
  while changed<=2 and StabGenAvailable do

    gen:=processStabGen();

    if gen=() then
      if Random([1..10])>1 then
        changed:=changed+1;
      fi;
    else
      changed:=changed+1;

      # fuse under gen-action
      norb:=[];
      for i in [1..Length(orbit)] do
	if IsBound(orbit[i]) then
	  no:=ShallowCopy(orbit[i]);
	  Unbind(orbit[i]);
	  i:=i+1;
	  for pnt in no do
	    img:=pnt^gen;
	    if not img in no then
	      img:=First([i..Length(orbit)],j->IsBound(orbit[j])
			 and img in orbit[j]);
	      if img<>fail then
		changed:=0;
		UniteSet(no,orbit[img]);
		Unbind(orbit[img]);
	      fi;
	    fi;
	  od;
	  Add(norb,Immutable(no));
	fi;
      od;
      orbit:=norb;
    fi;
  od;

  return Concatenation([[punkt]],orbit);
end );

#############################################################################
##
#M  AllBlocks . . . Representatives of all block systems
##
InstallMethod(AllBlocks,"generic",true,[IsPermGroup],0,
function(g)
local gens,dom,DoBlocks,pool,subo;

  DoBlocks:=function(b)
  local bl,bld,i,t,n;
    # find representatives of all potential suborbits
    bld:=List(Filtered(subo,i->not ForAny(b,j->j in i)),i->i[1]);
    bl:=[];
    if not IsPrime(Length(dom)/Length(b)) then
      for i in bld do
        t:=Union(b,[i]);
        n:=Blocks(g,dom,t);
        if Length(n)>1 and #ok durch pool:ForAll(Difference(n[1],t),j->j>i) and
           not n[1] in pool then
          t:=n[1];
          Add(pool,t);
          bl:=Concatenation(bl,[t],DoBlocks(t));
        fi;
      od;
    fi;
    return bl;
  end;

  gens:=Filtered(GeneratorsOfGroup(g),i->i<>());
  if Length(gens)=0 then return []; fi;
  subo:=ApproximateSuborbitsStabilizerPermGroup(g,
          Minimum(List(gens,SmallestMovedPoint)));
  dom:=Union(subo);
  pool:=[];
  return DoBlocks(subo[1]);
end);

#############################################################################
##
#F  SignPermGroup
##
InstallGlobalFunction( SignPermGroup, function(g)
  if ForAll(GeneratorsOfGroup(g),i->SignPerm(i)=1) then
    return 1;
  else
    return -1;
  fi;
end );

CreateAllCycleStructures := function(n)
local i,j,l,m;
  l:=[];
  for i in Partitions(n) do
    m:=[];
    for j in i do
      if j>1 then
        if IsBound(m[j-1]) then
          m[j-1]:=m[j-1]+1;
        else 
          m[j-1]:=1;
        fi;
      fi;
    od;
    Add(l,m);
  od;
  return l;
end;

#############################################################################
##
#F  CycleStructuresGroup(G)  list of all cyclestructures occ. in a perm group
##
InstallGlobalFunction( CycleStructuresGroup, function(g)
local l,m,i;
  l:=CreateAllCycleStructures(Length(MovedPoints(g)));
  m:=List([1..Length(l)-1],i->0);
  for i in ConjugacyClasses(g) do
    if Representative(i)<>() then
      m[Position(l,CycleStructurePerm(Representative(i)))-1]:=1;
    fi;
  od;
  return m;
end );

#############################################################################
##
#M  SmallGeneratingSet(<G>) 
##
InstallMethod(SmallGeneratingSet,"random and generators subset, randsims",true,
  [IsPermGroup],0,
function (G)
local  i, j, U, gens;

  # try pc methods first
  if Length(MovedPoints(G))<1000 and HasIsSolvableGroup(G) 
     and IsSolvableGroup(G) and Length(GeneratorsOfGroup(G))>2 then
    return MinimalGeneratingSet(G);
  fi;

  gens := Set(GeneratorsOfGroup(G));
  if Length(gens)>2 then
    i:=2;
    while i<=3 and i<Length(gens) do
      # try to find a small generating system by random search
      j:=1;
      while j<=5 and i<Length(gens) do
        U:=Subgroup(G,List([1..i],j->Random(G)));
        StabChain(U,rec(random:=1));
        if Size(U)=Size(G) then
          gens:=Set(GeneratorsOfGroup(U));
        fi;
        j:=j+1;
      od;
      i:=i+1;
    od;
  fi;
  i := 1;
  if not IsAbelian(G) then
    i:=i+1;
  fi;
  while i < Length(gens)  do
    # random did not improve much, try subsets
    U:=Subgroup(G,gens{Difference([1..Length(gens)],[i])});
    if Size(U)<Size(G) then
      i:=i+1;
    else
      gens:=Set(GeneratorsOfGroup(U));
    fi;
  od;
  return gens;
end);

#############################################################################
##
#M  MinimalGeneratingSet(<G>) . . . . . . . . . . . . . for permutation groups
##
InstallMethod(MinimalGeneratingSet,"solvable perm group via pc",true,
  [IsPermGroup],0,
function(G)
local i;
  if not IsSolvableGroup(G) then
    TryNextMethod();
  fi;
  i:=IsomorphismPcGroup(G);
  G:=Image(i,G);
  G:=MinimalGeneratingSet(G);
  return List(G,j->PreImagesRepresentative(i,j));
end);

#############################################################################
##
#M  GeneratorsSmallest(<G>) . . . . . . . . . . . . . for permutation groups
##
GeneratorsSmallestStab := function ( S )
    local   gens,       # smallest generating system of <S>, result
            gen,        # one generator in <gens>
            orb,        # basic orbit of <S>
            pnt,        # one point in <orb>
            T,          # stabilizer in <S>
	    o2,img,oo,og; # private orbit algorithm

    # handle the anchor case
    if Length(S.generators) = 0  then
        return [];
    fi;

    # now get the smallest generating system of the stabilizer
    gens := GeneratorsSmallestStab( S.stabilizer );

    # get the sorted orbit (the basepoint will be the first point)
    orb := Set( S.orbit );
    SubtractSet( orb, [S.orbit[1]] );

    # handle the other points in the orbit
    while Length(orb) <> 0  do

        # take the smallest point (coset) and one representative
        pnt := orb[1];
        gen := S.identity;
        while S.orbit[1] ^ gen <> pnt  do
           gen := LeftQuotient( S.transversal[ pnt / gen ], gen );
        od;

        # the next generator is the smallest element in this coset
        T := S.stabilizer;
        while Length(T.generators) <> 0  do
            pnt := Minimum( OnTuples( T.orbit, gen ) );
            while T.orbit[1] ^ gen <> pnt  do
                gen := LeftQuotient( T.transversal[ pnt / gen ], gen );
            od;
            T := T.stabilizer;
        od;

        # add this generator to the generators list and reduce orbit
        Add( gens, gen );

        #SubtractSet( orb,
        #             Orbit( GroupByGenerators( gens, () ), S.orbit[1] ) );

	# here we want to be really fast, so use a private orbit algorithm
	o2:=[S.orbit[1]];
	RemoveSet(orb,S.orbit[1]);
	for oo in o2 do
	  for og in gens do
	    img:=oo^og;
	    if img in orb then
	      Add(o2,img);
	      RemoveSet(orb,img);
	    fi;
	  od;
	od;

    od;

    # return the smallest generating system
    return gens;
end;

InstallMethod(GeneratorsSmallest,"perm group via minimal stab chain",true,
  [IsPermGroup],0,
function(G)
  # call the recursive function to do the work
  return GeneratorsSmallestStab(MinimalStabChain(G));
end);

InstallMethod(LargestElementGroup,"perm group via minimal stab chain",true,
  [IsPermGroup],0,
function(G)
  # call the recursive function to do the work
  return LargestElementStabChain(MinimalStabChain(G),());
end);


#############################################################################
##
#M  KnowsHowToDecompose( <G>, <gens> )
##
InstallMethod(KnowsHowToDecompose,
    "perm group and generators: always true",
    IsIdenticalObj,
    [ IsPermGroup, IsList ], 0,
    ReturnTrue);


#############################################################################
##
#M  ViewObj( <G> )
##
InstallMethod( ViewObj,
    "for a permutation group",
    true,
    [ IsPermGroup and HasGeneratorsOfGroup ], 0,
function(G)
  local gens;
  gens:= GeneratorsOfGroup( G );
  if 30 < Length( gens ) * LargestMovedPoint( G ) / VIEWLEN then
    Print("<permutation group");
    if HasSize(G) then
      Print(" of size ",Size(G));
    fi;
    Print( " with ", Length( gens ), " generators>" );
  else
    Print("Group(");
    if IsEmpty( gens ) or ForAll(gens,i->Order(i)=1) then
      ViewObj( One( G ) );
    else
      ViewObj( gens );
    fi;
    Print(")");
  fi;
end);


#############################################################################
##
#M  AsList( <G> ) elements of perm group
##
InstallMethod( AsList,"permgp: AsSSortedList", true,
  [ IsPermGroup ], 0, AsSSortedList );

#############################################################################
##
#M  AsSSortedList( <G> ) elements of perm group
##
InstallMethod( AsSSortedList,"via stabchain",true, [ IsPermGroup ], 0,
function( G )
  return ElementsStabChain( StabChainMutable(G));
end );

InstallMethod( AsSSortedListNonstored,"via stabchain", true, [ IsPermGroup ], 0,
function( G )
  return ElementsStabChain( StabChainMutable(G));
end );

#############################################################################
##
#M  ONanScottType( <G> )
##
InstallMethod(ONanScottType,"primitive permgroups",true,[IsPermGroup],0,
function(G)
local dom,s,cs,t,ts,o;
  dom:=MovedPoints(G);
  if not IsPrimitive(G,dom) then
    Error("<G> must be primitive");
  fi;
  s:=Socle(G);
  if IsAbelian(s) then
    return "1";
  elif IsSimpleGroup(s) then
    return "2";
  elif Length(dom)=Size(s) then
    return "5";
  else
    # now get one simple factor of the socle
    cs:=CompositionSeries(s);
    # if the group is diagonal, the diagonal together with a maximal socle
    # normal subgroup generates the whole socle, so this normal subgroup
    # acts transitively. For product type this is not the case.
    t:=cs[2];
    if IsTransitive(t,dom) then
      # type 3
      if Length(cs)=3 and IsNormal(G,t) then
	# intransitive on 2 components:
	return "3a";
      else
	return "3b";
      fi;
    else
      # type 4
      ts:=Orbit(G,t);
      if Length(ts)=2 and NormalClosure(G,t)<>s then
        return "4a";
      fi;
      # find the block on the T's first: Those t which keep the orbit are
      # glued together diagonally
      o:=Orbit(t,dom[1]);
      ts:=Filtered(ts,i->i=t or IsSubset(o,Orbit(i,dom[1])));
      if Length(ts)=1 then
        return "4c"; 
      else
	return "4b";
      fi;
    fi;
  fi;
end);

#############################################################################
##
#M  SocleTypePrimitiveGroup( <G> )
##
InstallMethod(SocleTypePrimitiveGroup,"primitive permgroups",true,
  [IsPermGroup],0,function(G)
local s,cs,t,id,r;
  s:=Socle(G);
  cs:=CompositionSeries(s);
  t:=cs[Length(cs)-1];
  id:=IsomorphismTypeInfoFiniteSimpleGroup(t);
  r:=rec(series:=id.series,
	     width:=Length(cs)-1,
	     name:=id.name);
  if IsBound(id.parameter) then
    r.parameter:=id.parameter;
  else
    r.parameter:=id.name;
  fi;
  return r;
end);

# this function is needed for the transitive and primitive groups libraries
BindGlobal("STGSelFunc",function(a,b)
  if IsFunction(b) then
    return b(a);
  elif IsList(b) and not IsString(b) then
   return a in b;
 else
   return a=b;
 fi;
end);

InstallGlobalFunction(DiagonalSocleAction,function(g,nr)
local ran,d,emb,u,act;
  ran:=[1..nr];
  d:=DirectProduct(List(ran,i->g));
  emb:=List(ran,i->Embedding(d,i));
  u:=List(GeneratorsOfGroup(g),i->Product(ran,j->Image(emb[j],i)));
  u:=SubgroupNC(d,u);
  act:=ActionHomomorphism(d,RightTransversal(d,u),OnRight,"surjective");
  return Image(act,d);
end);

#############################################################################
##
#E

