Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/GAP/pkg/atlasrep/gap/   (Algebra von RWTH Aachen Version 4.15.1©)  Datei vom 17.7.2023 mit Größe 63 kB image not shown  

Quelle  utils.gi   Sprache: unbekannt

 
#############################################################################
##
#W  utils.gi             GAP 4 package AtlasRep                 Thomas Breuer
##
##  This file contains the implementations of utility functions for the
##  ATLAS of Group Representations.
##


#############################################################################
##
#V  AtlasClassNamesOffsetInfo
##
##  The component `ordinary' is used for cases where the outer automorphism
##  group is not of prime order, and so the ordering of cosets is important
##  for constructing the names.
##
##  The component `special' is used for cases where one table of a subgroup
##  is used more than once.
##  Each entry is a list of length three, with first entry the `Identifier'
##  of the table in question, second entry the list of `Identifier' values of
##  the tables that cover the classes of the table in question, and third
##  entry the list of corresponding class fusions (those stored on the tables
##  can be omitted here).
##
InstallValue( AtlasClassNamesOffsetInfo, rec(
    ordinary:= [
    [ "A6", "A6.2_1", "A6.2_2", "A6.2_3" ],
    [ "L2(16)", "L2(16).2", "L2(16).4" ],
    [ "L2(25)", "L2(25).2_1", "L2(25).2_2", "L2(25).2_3" ],
    [ "L2(27)", "L2(27).2", "L2(27).3", "L2(27).6" ],
    [ "L2(49)", "L2(49).2_1", "L2(49).2_2", "L2(49).2_3" ],
    [ "L2(81)", "L2(81).2_1", "L2(81).4_1", "L2(81).4_2", "L2(81).2_2",
      "L2(81).2_3" ],
    [ "L3(4)", "L3(4).2_1", "L3(4).3", "L3(4).6", "L3(4).2_2", "L3(4).2_3" ],
    [ "L3(7)", "L3(7).3", "L3(7).2" ],
    [ "L3(8)", "L3(8).2", "L3(8).3", "L3(8).6" ],
    [ "L3(9)", "L3(9).2_1", "L3(9).2_2", "L3(9).2_3" ],
    [ "L4(3)", "L4(3).2_1", "L4(3).2_2", "L4(3).2_3" ],
    [ "L4(4)", "L4(4).2_1", "L4(4).2_2", "L4(4).2_3" ],
    [ "O8-(3)", "O8-(3).2_1", "O8-(3).2_2", "O8-(3).2_3" ],
    [ "O8+(2)", "O8+(2).3", "O8+(2).2" ],
    [ "O8+(3)", "O8+(3).2_1", "O8+(3).3", "O8+(3).2_2", "O8+(3).4" ],
    [ "S4(4)", "S4(4).2", "S4(4).4" ],
    [ "S4(9)", "S4(9).2_1", "S4(9).2_2", "S4(9).2_3" ],
    [ "2E6(2)", "2E6(2).2", "2E6(2).3" ],
    [ "U3(4)", "U3(4).2", "U3(4).4" ],
    [ "U3(5)", "U3(5).3", "U3(5).2" ],
    [ "U3(8)", "U3(8).3_1", "U3(8).3_2", "U3(8).3_3", "U3(8).2", "U3(8).6" ],
    [ "U3(9)", "U3(9).2", "U3(9).4" ],
    [ "U3(11)", "U3(11).3", "U3(11).2" ],
    [ "U4(3)", "U4(3).2_1", "U4(3).4", "U4(3).2_2", "U4(3).2_3" ],
    [ "U4(5)", "U4(5).2_1", "U4(5).2_2", "U4(5).2_3" ],
    [ "U6(2)", "U6(2).3", "U6(2).2" ],
    ],
    special:= [
    [ "O8+(3).(2^2)_{111}",
      [ "O8+(3)", "O8+(3).2_1", "O8+(3).2_1", "O8+(3).2_1" ],
      [,,[1,3,4,2,5,6,8,9,7,8,10,10,11,12,13,15,16,14,17,19,20,18,22,23,21,
      24,26,27,25,26,29,30,28,29,32,33,31,34,34,35,35,36,37,38,39,41,42,40,
      41,43,43,44,44,46,47,45,49,50,48,52,53,51,52,54,55,55,57,58,56,57,59,
      60,62,63,61,65,66,64,65,68,69,67,68,71,72,70,122,123,124,125,126,127,
      128,129,129,130,130,131,131,132,133,134,136,135,137,139,138,140,142,
      141,143,144,145,145,146,147,148,149,149,150,151,152,152,153,155,154,
      157,156,158,158,159,160,161,162,164,163,165,165,166,166,169,170,167,
      168],[1,4,2,3,5,6,9,7,8,9,10,10,11,12,13,16,14,15,17,20,18,19,23,21,22,
      24,27,25,26,27,30,28,29,30,33,31,32,34,34,35,35,36,37,38,39,42,40,41,
      42,43,43,44,44,47,45,46,50,48,49,53,51,52,53,54,55,55,58,56,57,58,59,
      60,63,61,62,66,64,65,66,69,67,68,69,72,70,71,171,172,173,174,175,176,
      177,178,178,179,179,180,180,181,182,183,184,185,186,187,188,189,190,
      191,192,193,194,194,195,196,197,198,198,199,200,201,201,202,203,204,
      205,206,207,207,208,209,210,211,212,213,214,214,215,215,216,217,218,
      219]] ],
    [ "O8+(3).(2^2)_{122}",
      [ "O8+(3)", "O8+(3).2_1", "O8+(3).2_2", "O8+(3).2_2" ],
      [,,,[1,2,3,4,5,8,7,6,7,10,10,9,11,12,13,14,15,16,17,18,19,20,21,24,23,
      22,23,27,26,25,26,28,29,31,31,30,33,33,32,34,35,36,37,40,39,38,39,42,
      42,41,44,44,43,45,46,47,48,51,50,49,50,52,54,54,53,57,56,55,56,58,59,
      60,61,64,63,62,63,67,66,65,66,68,69,167,168,169,170,171,172,173,174,
      175,176,177,177,178,179,180,181,181,182,183,184,185,186,187,188,189,
      190,191,192,193,194,195,196,197,198,199,199,200,201,202,203,204,204,
      205,205,206,207,208,209,210,211,212,213]] ],
    [ "O8+(3).D8",
      [ "O8+(3)", "O8+(3).2_1", "O8+(3).2_1", "O8+(3).2_2", "O8+(3).4" ],
      [,,[1,3,2,3,4,5,7,6,7,7,8,8,9,10,11,13,12,13,14,16,15,16,18,17,18,19,
      21,20,21,21,23,22,23,23,25,24,25,26,26,27,27,28,29,30,31,33,32,33,33,
      34,34,35,35,37,36,37,39,38,39,41,40,41,41,42,43,43,45,44,45,45,46,47,
      49,48,49,51,50,51,51,53,52,53,53,55,54,55,97,98,99,100,101,102,103,104,
      104,105,105,106,106,107,108,109,110,111,112,113,114,115,116,117,118,
      119,120,120,121,122,123,124,124,125,126,127,127,128,129,130,131,132,
      133,133,134,135,136,137,138,139,140,140,141,141,142,143,144,145]] ],
    [ "U4(3).(2^2)_{122}",
      [ "U4(3)", "U4(3).2_1", "U4(3).2_2", "U4(3).2_2" ],
      [,,,[1,2,3,5,4,6,7,8,9,10,12,11,13,14,16,16,15,17,46,47,48,49,50,50,51,
      52,53,54,55,56,57,58,59,59]] ],
    [ "U4(3).(2^2)_{133}",
      [ "U4(3)", "U4(3).2_1", "U4(3).2_3", "U4(3).2_3" ],
      [,,,[1,2,3,4,5,6,7,8,9,10,11,12,13,13,14,36,37,38,39,40,41,42,43,44,44]
      ] ],
    ] ) );


#############################################################################
##
#F  AtlasClassNames( <tbl> )
##
InstallGlobalFunction( AtlasClassNames, function( tbl )
    local ordtbl, names, n, fact, factnames, map, i, j, name, pos,
          solvres, simplename, F, Finv, info, tbls, classes, tblname,
          subtbl, derclasses, subF, size,
          gens,
          fus,
          filt,
          special,
          subtblfustbl,
          Fproxies,
          inv,
          proxies,
          orb,
          imgs,
          img,
          count,
          k,
          alpha,    # alphabet
          lalpha,   # length of the alphabet
          orders,   # list of representative orders
          innernames,
          number,   # at position <i> the current number of
                    # classes of order <i>
          suborders,
          depname,
          dashes,
          subnames,
          relevant,
          intermed,       # loop over intermediate tables
          tblfusF;

    if not IsCharacterTable( tbl ) then

      Error( "<tbl> must be a character table" );

    elif IsBrauerTable( tbl ) then

      # Derive the class names from the names of the ordinary table.
      ordtbl:= OrdinaryCharacterTable( tbl );
      names:= AtlasClassNames( ordtbl );
      if names = fail then
        return fail;
      fi;
      return names{ GetFusionMap( tbl, ordtbl ) };

    elif IsSimpleCharacterTable( tbl ) then

      # For tables of simple groups, `ClassNames' is good enough.
      return ClassNames( tbl, "Atlas" );

    fi;

    # For not almost simple tables,
    # derive the class names from the names for the almost simple factor.
    n:= ClassPositionsOfFittingSubgroup( tbl );

    if Length( n ) <> 1 then
      fus:= First( ComputedClassFusions( tbl ),
                   r -> ClassPositionsOfKernel( r.map ) = n );
      if fus = fail then
        return fail;
      fi;
      fact:= CharacterTable( fus.name );
      factnames:= AtlasClassNames( fact );
      if factnames = fail then
        Info( InfoAtlasRep, 2,
              Identifier( tbl ),
              " is not a downward extension of an almost simple table" );
        return fail;
      fi;

      map:= InverseMap( GetFusionMap( tbl, fact ) );
      names:= [];
      for i in [ 1 .. Length( map ) ] do
        if IsInt( map[i] ) then
          names[ map[i] ]:= Concatenation( factnames[i], "_0" );
        # Add( names, Concatenation( factnames[i], "_0" ) );
        else
          for j in [ 0 .. Length( map[i] )-1 ] do
            names[ map[i][ j+1 ] ]:= Concatenation( factnames[i], "_",
                                         String( j ) );
          od;
        # Append( names, List( [ 0 .. Length( map[i] )-1 ],
        #     j -> Concatenation( factnames[i], "_", String( j ) ) ) );
        fi;
      od;
      return names;
    elif not IsAlmostSimpleCharacterTable( tbl ) then
      Info( InfoAtlasRep, 2,
            Identifier( tbl ), " is not an almost simple table" );
      return fail;
    fi;

    # Now `tbl' is almost simple and not simple.
    # Find out which nonabelian simple group is involved,
    # and which upward extension is given.
    # (We use the `Identifier' value of `tbl';
    # note that this function makes sense only for library tables.)
    name:= Identifier( tbl );
    pos:= Position( name, '.' );
    if pos = fail then
      Info( InfoAtlasRep, 2,
            "strange name `", name, "'" );
      return fail;
    fi;

    # Get the table of the solvable residuum.
    solvres:= CharacterTable( name{ [ 1 .. pos-1 ] } );
    if solvres = fail then
      Info( InfoAtlasRep, 2,
            "the identifier `", name,
            "' does not fit to an almost simple group" );
      return fail;
    fi;

    simplename:= Identifier( solvres );
    F:= tbl / ClassPositionsOfSolvableResiduum( tbl );
    Finv:= InverseMap( GetFusionMap( tbl, F ) );

    # We use the global variable `AtlasClassNamesOffsetInfo'.
    info:= First( AtlasClassNamesOffsetInfo.ordinary,
                  x -> x[1] = simplename );

    # Compute the tables of all cyclic upward extensions of `solvres'
    # that are contained in `tbl',
    # and store the positions of the corresponding relevant classes in `tbl'.
    # Tables that are *not* involved in `tbl' but whose class names force
    # offsets for the class names of `tbl' are also stored,
    tbls:= [ solvres ];
    classes:= [ [ Finv[1], true ] ];

    if IsPrimeInt( Size( tbl ) / Size( solvres ) ) then

      # Here `AtlasClassNamesOffsetInfo' may be missing.
      if info = fail then
        Info( InfoCharacterTable, 2,
              "AtlasClassNames: ",
              "no info by `AtlasClassNamesOffsetInfo' available" );
      else

        tblname:= ShallowCopy( Identifier( tbl ) );
        while tblname[ Length( tblname ) ] = '\'' do
          Unbind( tblname[ Length( tblname ) ] );
        od;
        pos:= Position( info, tblname );
        for i in [ 2 .. pos-1 ] do

          subtbl:= CharacterTable( info[i] );
          derclasses:= ClassPositionsOfDerivedSubgroup( subtbl );
          subF:= subtbl / derclasses;

          # The classes are *not* involved in `tbl',
          # store the positions of the classes in generator cosets!
          size:= Size( subF );
          gens:= Filtered( [ 1 .. size ],
                     i -> OrdersClassRepresentatives( subF )[i] = size );
          fus:= GetFusionMap( subtbl, subF );
          filt:= Filtered( [ 1 .. NrConjugacyClasses( subtbl ) ],
                           i -> fus[i] in gens );

          Add( tbls, subtbl );
          Add( classes, [ filt, false ] );

        od;

      fi;
      special:= fail;

      # Add `tbl' itself.
      Add( tbls, tbl );
      Add( classes,
           [ Difference( [ 1 .. NrConjugacyClasses( tbl ) ], Finv[1] ),
             true ] );

    elif Size( solvres ) <> Size( tbl ) then

      # Here we definitely need `AtlasClassNamesOffsetInfo'.
      if info = fail then
        Error( "not enough information about <tbl>" );
      fi;

      # More information is needed if a table occurs more than once.
      special:= First( AtlasClassNamesOffsetInfo.special,
                       list -> list[1] = Identifier( tbl ) );
      if special <> fail then
        special:= ShallowCopy( special );
        special[4]:= [];
        info:= special[2];
      fi;

      # Test which intermediate tables are needed.
      # These are exactly the ones having a fusion into `tbl'.
      # The others are taken with `false' in `classes'.
      for i in [ 2 .. Length( info ) ] do

        subtbl:= CharacterTable( info[i] );
        derclasses:= ClassPositionsOfDerivedSubgroup( subtbl );
        subF:= subtbl / derclasses;

        if special = fail or not IsBound( special[3][i] ) then
          subtblfustbl:= GetFusionMap( subtbl, tbl );
        else
          subtblfustbl:= special[3][i];
        fi;

        if subtblfustbl = fail then

          # The classes are *not* involved in `tbl',
          # or `subtbl' is equal to `tbl'.
          # Store the positions of the classes in generator cosets!
          size:= Size( subF );
          gens:= Filtered( [ 1 .. size ],
                     i -> OrdersClassRepresentatives( subF )[i] = size );
          fus:= GetFusionMap( subtbl, subF );
          filt:= Filtered( [ 1 .. NrConjugacyClasses( subtbl ) ],
                           i -> fus[i] in gens );

          if Identifier( tbl ) = info[i] then
            Add( tbls, tbl );
            Add( classes, [ filt, true ] );
          else
            Add( tbls, subtbl );
            Add( classes, [ filt, false ] );
          fi;

        elif Set( subtblfustbl{ derclasses } ) <> Finv[1] then
          Error( "strange fusion ", Identifier( subtbl ),
                 " -> ", Identifier( tbl ) );
        else

          # The table is needed.
          # Store the positions in `tbl' of the classes in generator cosets!
          size:= Size( subF );
          gens:= Filtered( [ 1 .. size ],
                     i -> OrdersClassRepresentatives( subF )[i] = size );
          fus:= GetFusionMap( subtbl, subF );
          filt:= Filtered( [ 1 .. NrConjugacyClasses( subtbl ) ],
                           i -> fus[i] in gens );

          Add( tbls, subtbl );
          Add( classes, [ Set( subtblfustbl{ filt } ), true ] );

        fi;

      od;

      # Check whether all necessary tables are available.
      if Union( List( Filtered( classes, x -> x[2] = true ), y -> y[1] ) )
             <> [ 1 .. NrConjugacyClasses( tbl ) ] then
        Info( InfoAtlasRep, 2,
              "AtlasClassNames: ",
              "not all necessary tables are available for ",
              Identifier( tbl ) );
        return fail;
      fi;

    fi;

    # Define a function that creates class names in ATLAS style.
    alpha:= List( "ABCDEFGHIJKLMNOPQRSTUVWXYZ", x -> [ x ] );
    for i in alpha do
      ConvertToStringRep( i );
    od;
    lalpha:= Length( alpha );
    name:= function( n )
      local m;
      if n <= lalpha then
        return alpha[n];
      else
        m:= (n-1) mod lalpha + 1;
        n:= ( n - m ) / lalpha;
        return Concatenation( alpha[m], String( n ) );
      fi;
    end;

    # Initialize the list of class names
    # and the counter for the names already constructed.
    names:= [];
    number:= [];

    # Loop over the tables.
    for pos in [ 1 .. Length( tbls ) ] do

      subtbl:= tbls[ pos ];
      relevant:= classes[ pos ][1];

      if special <> fail and IsBound( special[3][ pos ] ) then
        fus:= special[3][ pos ];
        subnames:= special[4][ Position( special[2], special[2][ pos ] ) ];
        for i in [ 1 .. Length( subnames ) ] do
          if not IsBound( subnames[i] ) then
            subnames[i]:= "?";
          fi;
        od;
        subnames:= Concatenation( [ 1 .. Maximum( Filtered(
            [ 1 .. Length( fus ) ], x -> fus[x] in relevant ) )
            - Length( subnames ) ], subnames );
        dashes:= Number( [ 1 .. pos-1 ],
                         x -> special[2][x] = special[2][ pos ] );
        dashes:= ListWithIdenticalEntries( dashes, '\'' );
        subnames:= List( subnames, ShallowCopy );
        for i in [ 1 .. Length( subnames ) ] do
          Append( subnames[i], dashes );
        od;

      else

        if classes[ pos ][2] then
          if Size( subtbl ) = Size( tbl ) then
            fus:= [ 1 .. NrConjugacyClasses( tbl ) ];
          elif special <> fail and IsBound( special[3][ pos ] ) then
            fus:= special[3][ pos ];
          else
            fus:= GetFusionMap( subtbl, tbl );
            if fus = fail then
              for intermed in tbls do
                if     GetFusionMap( subtbl, intermed ) <> fail
                   and GetFusionMap( intermed, tbl ) <> fail then
                  fus:= CompositionMaps( GetFusionMap( intermed, tbl ),
                                         GetFusionMap( subtbl, intermed ) );
                  break;
                fi;
              od;
            fi;
          fi;
        else
          fus:= fail;
        fi;

        # Choose proxy classes in the factor group,
        # that is, one generator class for each cyclic subgroup.
        F:= subtbl / ClassPositionsOfDerivedSubgroup( subtbl );
        Fproxies:= [];
        for i in [ 1 .. NrConjugacyClasses( F ) ] do
          if not IsBound( Fproxies[i] ) then
            for j in ClassOrbit( F, i ) do
              Fproxies[j]:= i;
            od;
          fi;
        od;

        # Transfer the proxy classes to `subtbl'.
        tblfusF:= GetFusionMap( subtbl, F );
        proxies:= [];
        for i in [ 1 .. Length( tblfusF ) ] do
          if not IsBound( proxies[i] ) then
            orb:= ClassOrbit( subtbl, i );
            imgs:= tblfusF{ orb };
            for j in [ 1 .. Length( orb ) ] do

              # Classes mapping to a proxy class in `F' are proxies also
              # in `subtbl'.
              # For the other classes,
              # we make use of the convention that in GAP tables
              # (of upward extensions of simple groups),
              # the follower classes come immediately after their proxies.
              k:= j;
              while Fproxies[ imgs[k] ] <> imgs[k] do
                k:= k-1;
              od;
              proxies[ orb[j] ]:= orb[k];

            od;
          fi;
        od;

        # Compute the non-order parts of the names w.r.t. the subgroup.
        subnames:= [];
        suborders:= OrdersClassRepresentatives( subtbl );
        for i in [ 1 .. NrConjugacyClasses( subtbl ) ] do
          if ( fus <> fail and fus[i] in relevant ) then
            if proxies[i] = i then
              if not IsBound( number[ suborders[i] ] ) then
                number[ suborders[i] ]:= 1;
              fi;
              subnames[i]:= name( number[ suborders[i] ] );
              number[ suborders[i] ]:= number[ suborders[i] ] + 1;
            else
              depname:= ShallowCopy( subnames[ proxies[i] ] );
              while ForAny( [ 1 .. i-1 ], x -> IsBound( subnames[x] )
                         and subnames[x] = depname
                         and suborders[x] = suborders[i] ) do
                Add( depname, '\'' );
              od;
              subnames[i]:= depname;
            fi;
          fi;
        od;

        if special <> fail then
          special[4][ pos ]:= subnames;
        fi;

      fi;

      # For tables that are not needed,
      # just compute the class names for all outer generator classes
      if fus = fail then
        for i in classes[ pos ][1] do
          if Fproxies[ tblfusF[i] ] = tblfusF[i] then
            if not IsBound( number[ suborders[i] ] ) then
              number[ suborders[i] ]:= 1;
            fi;
            name( number[ suborders[i] ] );
            number[ suborders[i] ]:= number[ suborders[i] ] + 1;
          fi;
        od;
      fi;

      # Compute the dashes that are forced by the table name.
      dashes:= "";
      if pos <> 1 then
        i:= Length( Identifier( subtbl ) );
        while Identifier( subtbl )[i] = '\'' do
          Add( dashes, '\'' );
          i:= i-1;
        od;
      fi;

      # If the table is needed then form orbit concatenations of these names.
      if fus <> fail then
        orders:= OrdersClassRepresentatives( tbl );
        inv:= InverseMap( fus );
        for i in relevant do
          if IsInt( inv[i] ) then
            orb:= [ subnames[ inv[i] ] ];
          else
            orb:= List( inv[i], x -> subnames[x] );
            if     ForAny( orb, x -> '\'' in x )
               and not ForAll( orb, x -> '\'' in x ) then
              orb:= Filtered( orb, x -> not '\'' in x );
            fi;
          fi;
          orb:= List( orb, x -> Concatenation( x, dashes ) );
          names[i]:= Concatenation( String( orders[i] ),
                         Concatenation( orb ) );
        od;
      fi;

    od;

    # Return the list of classnames.
    return names;
end );


#############################################################################
##
#F  AtlasCharacterNames( <tbl> )
##
InstallGlobalFunction( AtlasCharacterNames, function( tbl )
    local alpha, i, lalpha, name, ordtbl, names, degrees, chi, pos;

    if not IsCharacterTable( tbl ) then
      Error( "<tbl> must be a character table" );
    fi;

    # Define a function that creates character names in ATLAS style.
    alpha:= List( "abcdefghijlkmnopqrstuvwxyz", x -> [ x ] );
    for i in alpha do
      ConvertToStringRep( i );
    od;
    lalpha:= Length( alpha );
    name:= function( n )
      local m;
      if n <= lalpha then
        return alpha[n];
      else
        m:= (n-1) mod lalpha + 1;
        n:= ( n - m ) / lalpha;
        return Concatenation( alpha[m], String( n ) );
      fi;
    end;

    if UnderlyingCharacteristic( tbl ) = 0 then
      ordtbl:= tbl;
    else
      ordtbl:= OrdinaryCharacterTable( tbl );
    fi;

    if IsSimpleCharacterTable( ordtbl ) then

      # For tables of simple groups, use the degrees.
      names:= [];
      degrees:= [ [], [] ];
      for chi in Irr( tbl ) do
        pos:= Position( degrees[1], chi[1] );
        if pos = fail then
          Add( degrees[1], chi[1] );
          Add( degrees[2], 1 );
          Add( names, Concatenation( String( chi[1] ), name( 1 ) ) );
        else
          degrees[2][ pos ]:= degrees[2][ pos ] + 1;
          Add( names,
               Concatenation( String( chi[1] ), name( degrees[2][ pos ] ) ) );
        fi;
      od;
      return names;

    else

      Info( InfoAtlasRep, 2,
            "AtlasCharacterNames: ",
            "not available for ", Identifier( tbl ) );
      return fail;

    fi;
end );


#############################################################################
##
#F  StringOfAtlasProgramCycToCcls( <prgstring>, <tbl>, <mode> )
##
InstallGlobalFunction( StringOfAtlasProgramCycToCcls,
    function( prgstring, tbl, mode )
    local classnames, labels, numbers, prgline, line, string, pos, nrlabels,
          inputline, inline, nccl, result, i, dashedclassnames, orders,
          primes, known, unchanged, p, map, img, pp, orb, k, j, e, namline,
          resline;

    # Check the input.
    if not ( IsString( prgstring ) and IsOrdinaryTable( tbl ) ) then
      Error("usage: StringOfAtlasProgramCycToCcls(<prgstring>,<tbl>,<mode>)");
    fi;

    # Fetch the `echo' lines starting with `Classes'.
    # They serve as inputs for the result program.

    # Compute the classnames.
    classnames:= AtlasClassNames( tbl );

    # Determine the labels that occur.
    # `labels' is a list of labels that occur in `echo' lines of the
    # given script (class names, without dashes).
    # `numbers' is a list of labels that occur in `oup' lines of the
    # given script (numbers or classnames, with dashes).
    labels:= [];
    numbers:= [];
    for prgline in SplitString( prgstring, "\n" ) do

      # Ignore lines that are neither `echo' nor `oup' statements.
      if   5 < Length( prgline ) and prgline{ [ 1 .. 4 ] } = "echo" then
        line:= SplitString( prgline, "", "\" " );
        if   "classes" in line then
          Append( labels,
              line{ [ Position( line, "classes" )+1 .. Length( line ) ] } );
        elif "Classes" in line then
          Append( labels,
              line{ [ Position( line, "Classes" )+1 .. Length( line ) ] } );
        elif not "Here" in line then
          Append( labels,
              line{ [ Position( line, "echo" )+1 .. Length( line ) ] } );
        fi;
      elif  4 < Length( prgline ) and prgline{ [ 1 .. 3 ] } = "oup" then
        line:= SplitString( prgline, "", "\" \n" );
        Append( numbers, line{ [ 3 .. Length( line ) ] } );
      fi;

    od;

    # Construct the list of class representatives from the labels.
    if   IsEmpty( labels ) then
      Info( InfoCMeatAxe, 1,
            "no class names specified as outputs" );
      return fail;
    elif not ForAll( labels, str -> str in classnames ) then
      Info( InfoCMeatAxe, 1,
            "labels `",
            Filtered( labels, str -> not str in classnames ),
            "' aren't class names" );
      return fail;
    fi;
    string:= "";

    # Write down the line(s) specifying the input list.
    pos:= 1;
    nrlabels:= Length( labels );
    while pos <= nrlabels do
      inputline:= "";
      inline:= 0;
      while pos <= nrlabels and
            Length( inputline ) + Length( numbers[ pos ] ) <= 71 do
        Add( inputline, ' ' );
        Append( inputline, numbers[ pos ] );
        pos:= pos + 1;
        inline:= inline + 1;
      od;
      Append( string, "inp " );
      Append( string, String( inline ) );
      Append( string, inputline );
      Add( string, '\n' );
    od;

    # The program shall return conjugacy class representatives.
    nccl:= Length( classnames );
    result:= [];
    for i in [ 1 .. nccl ] do
      if classnames[i] in labels then
        result[i]:= true;
      fi;
    od;

    # The inputs are numbers or class names,
    # and depending on `mode', the outputs are numbers or class names.
    if   mode = "names" then
      # Dashes in the labels must be escaped with backslashes.
      # (Note that names in `echo' lines must *not* be escaped.)
      numbers:= Concatenation( numbers,
                    List( Filtered( classnames, x -> not x in labels ),
                          str -> ReplacedString( str, "'", "\\'" ) ) );
    elif ForAll( numbers, x -> Int( x ) <> fail ) then
      # The inputs are numbers, and the outputs shall be numbers.
      numbers:= Concatenation( numbers,
                    Difference( List( [ 1 .. nccl ], String ), numbers ) );
    elif IsSubset( classnames, numbers ) and numbers = labels then
      # The inputs are class names (with dashes escaped),
      # and the outputs shall be numbers,
      dashedclassnames:= List( classnames,
                               str -> ReplacedString( str, "'", "\\'" ) );
      numbers:= Concatenation( numbers,
                    List( Difference( [ 1 .. nccl ],
                              List( numbers,
                                    x -> Position( dashedclassnames, x ) ) ),
                          String ) );
    else
      Error( "all in <numbers> must be numbers or in <classnames>" );
    fi;
    labels:= Concatenation( labels,
                 Filtered( classnames, x -> not x in labels ) );

    # Use power maps to fill missing entries of smaller element order.
    orders:= OrdersClassRepresentatives( tbl );
    primes:= PrimeDivisors( Size( tbl ) );
    known:= Filtered( [ 1 .. nccl ], x -> IsBound( result[x] ) );
    SortParallel( - orders{ known }, known );
    repeat
      unchanged:= true;
      for p in primes do
        map:= PowerMap( tbl, p );
        for i in known do
          img:= map[i];
          pp:= p mod orders[i];
          if pp = 0 then
            pp:= p;
          fi;
          if not img in known then
            Append( string, "pwr " );
            Append( string, String( pp ) );
            Append( string, " " );
            Append( string, numbers[ Position( labels, classnames[i] ) ] );
            Append( string, " " );
            Append( string, numbers[ Position( labels,
                                               classnames[ img ] ) ] );
            Append( string, "\n" );
            result[ img ]:= true;
            Add( known, img );
            unchanged:= false;
          fi;
        od;
      od;
    until unchanged;

    # Use Galois conjugacy to fill missing entries.
    for i in Difference( [ 1 .. nccl ], known ) do
      if not IsBound( result[i] ) then
        orb:= ClassOrbit( tbl, i );
        k:= First( orb, x -> x in known );
        if k = fail then
          Info( InfoCMeatAxe, 1,
                "at least Galois orbit representatives of classes in\n",
                "#I  `", classnames{ orb }, "' are missing" );
          return fail;
        fi;
        for j in orb do

          e:= 1;
          while not IsBound( result[j] ) do

            # Find a *small* power that maps k to j.
            e:= e+1;
            if orders[k] mod e <> 0 then
              if PowerMap( tbl, e, k ) = j then
                Append( string, "pwr " );
                Append( string, String( e ) );
                Append( string, " " );
                Append( string, numbers[ Position( labels,
                                         classnames[k] ) ] );
                Append( string, " " );
                Append( string, numbers[ Position( labels,
                                                   classnames[j] ) ] );
                Append( string, "\n" );
                result[j]:= true;
              fi;
            fi;

          od;

        od;
      fi;
    od;

    # Write the `echo' and `oup' statements.
    # (Split the output specifications into lines if necessary.)
    i:= 1;
    namline:= "";
    resline:= "";
    inline:= 0;
    while i <= nccl do
      if    60 < Length( namline ) + Length( classnames[i] )
         or 60 < Length( resline ) + Length( numbers[ Position( labels,
                                         classnames[i] ) ] ) then
        Append( string,
            Concatenation( "echo \"Classes", namline, "\"\n" ) );
        Append( string,
            Concatenation( "oup ", String( inline ), resline, "\n" ) );
        namline:= "";
        resline:= "";
        inline:= 0;
      fi;
      Add( namline, ' ' );
      Append( namline, classnames[i] );
      Add( resline, ' ' );
      Append( resline, numbers[ Position( labels, classnames[i] ) ] );
      inline:= inline + 1;
      i:= i + 1;
    od;
    if inline <> 0 then
      Append( string,
          Concatenation( "echo \"Classes", namline, "\"\n" ) );
      Append( string,
          Concatenation( "oup ", String( inline ), resline, "\n" ) );
    fi;

    # Return the string.
    return string;
end );


#############################################################################
##
#F  AGR.ComputeKernelGeneratorsInner( <gens>, <fgens>, <size>, <fsize>,
#F                                    <goodorders>, <bound> )
##
##  This function does the work for 'AtlasRepComputedKernelGenerators'.
##
##  <gens> and <fgens> must be lists of generators as occur in the records
##  that are returned by 'AtlasGenerators',
##  and <size> and <fsize> must be the orders of the groups that are
##  generated by these lists.
##  The lists of generators are assumed to be compatible in the sense that
##  mapping <gens> to <fgens> defines an epimorphism.
##  Let <M>G</M> be the group generated by <gens>.
##
##  <goodorders> can be 'true' or the list of all those element orders in the
##  factor group for which a preimage in the group has larger order.
##
##  The return value of the function and the meaning of <bound> are the same
##  as described for 'AtlasRepComputedKernelGenerators'.
##
AGR.ComputeKernelGeneratorsInner:= function( gens, fgens, size, fsize,
                                             goodorders, bound )
    local kersize, ker, kerwords, f, mgens, iter, gensorders, i, word,
          extrep, gm, fm, gord, ford, kergen;

    if Length( gens ) <> Length( fgens ) then
      Info( InfoAtlasRep, 3,
            "AtlasRepComputeKernelGenerators:\n",
            "#I  incompatible generators (lengths ", Length( gens ),
            " and ", Length( fgens ), "\n" );
      return fail;
    fi;

    if size = fail or fsize = fail then
      kersize:= fail;
    else
      kersize:= size / fsize;
      if not IsPosInt( kersize ) or kersize = 1 then
        Info( InfoAtlasRep, 3,
              "AtlasRepComputeKernelGenerators:\n",
              "#I  strange kernel size ", kersize, "\n" );
        return fail;
      fi;
    fi;

    ker:= TrivialSubgroup( Group( gens[1] ) );
    SetAsSSortedList( ker, [ gens[1]^0 ] );
    kerwords:= [];
    f:= FreeMonoid( Length( gens ) );
    mgens:= GeneratorsOfMonoid( f );
    iter:= Iterator( f );
    gensorders:= List( gens, Order );

    # Check at most 'bound' words.
    i:= 1;
    while i <= bound do
      word:= NextIterator( iter );
      extrep:= ExtRepOfObj( word );
      if ForAll( [ 2, 4 .. Length( extrep ) ],
                 j -> extrep[j] < gensorders[ extrep[ j-1 ] ] ) then
        # No exponent in a syllable exceeds the generator order in question.
        i:= i + 1;
        fm:= MappedWord( word, mgens, fgens );
        ford:= Order( fm );
        if goodorders = true or ford in goodorders then
          gm:= MappedWord( word, mgens, gens );
          gord:= Order( gm );
          if gord <> ford then
            if gord mod ford <> 0 or kersize mod ( gord / ford ) <> 0 then
              # The generators are not compatible.
              Info( InfoAtlasRep, 3,
                    "AtlasRepComputeKernelGenerators:\n",
                    "#I  incompatible generators (elements orders ", gord,
                    " and ", ford, " for word ", word, "\n" );
              return fail;
            elif kersize <> fail and gord / ford = kersize then
              # One generator suffices.
              return [ [ [ word, ford ] ], true ];
            else
              kergen:= gm^ford;
              # The membership test does not involve the computation
              # of a ``nice monomorphism'' because we have forced the
              # elements list of 'ker'.
              if not kergen in ker then
                ker:= ClosureGroup( ker, kergen );
                Add( kerwords, [ word, ford ] );
                if kersize = fail then
                  # We do not know how much we need,
                  # return at least this word.
                  return [ kerwords, false ];
                elif Size( ker ) = kersize then
                  return [ kerwords, true ];
                fi;
              fi;
            fi;
          fi;
        fi;
      fi;
    od;

    # We did not find enough elements among the first 'bound' words.
    return [ kerwords, false ];
end;


#############################################################################
##
#F  AtlasRepComputedKernelGenerators( <gapname>, <std>,
#F                                    <factgapname>, <factstd>,
#F                                    <bound> )
##
InstallGlobalFunction( AtlasRepComputedKernelGenerators,
    function( gapname, std, factgapname, factstd, bound )
    local gens, fgens, tbl, facttbl, goodorders, fus, orders, factorders, i,
          size, fsize;

    gens:= AtlasGroup( gapname, std, "contents", "local" );
    if gens = fail then
      return fail;
    fi;
    fgens:= AtlasGroup( factgapname, factstd, "contents", "local" );
    if fgens = fail then
      return fail;
    fi;

    # Representations of both G and F are available.
    # Assume that they are compatible.
    # Try to compute a list of interesting element orders in the factor.
    tbl:= CharacterTable( gapname );
    facttbl:= CharacterTable( factgapname );
    if tbl = fail or facttbl = fail then
      goodorders:= true;
    else
      fus:= GetFusionMap( tbl, facttbl );
      if fus = fail then
        goodorders:= true;
      else
        goodorders:= [];
        orders:= OrdersClassRepresentatives( tbl );
        factorders:= OrdersClassRepresentatives( facttbl );
        for i in [ 1 .. Length( fus ) ] do
          if orders[i] <> factorders[ fus[i] ] then
            AddSet( goodorders, factorders[ fus[i] ] );
          fi;
        od;
      fi;
    fi;

    if HasSize( gens ) then
      size:= Size( gens );
    elif tbl <> fail then
      size:= Size( tbl );
    else
      size:= fail;
    fi;
    if HasSize( fgens ) then
      fsize:= Size( fgens );
    elif facttbl <> fail then
      fsize:= Size( facttbl );
    else
      fsize:= fail;
    fi;

    # Run the loop.
    return AGR.ComputeKernelGeneratorsInner( GeneratorsOfGroup( gens ),
               GeneratorsOfGroup( fgens ), size, fsize, goodorders, bound );
end );


#############################################################################
##
#F  CurrentDateTimeString( [<options>] )
##
InstallGlobalFunction( CurrentDateTimeString, function( arg )
    local options, name, str, out;

    if Length( arg ) = 0 then
      options:= [ "-u", "+%s" ];
    elif Length( arg ) = 1 then
      options:= arg[1];
    fi;

    name:= Filename( DirectoriesSystemPrograms(), "date" );
    if name = fail then
      return "unknown";
    fi;

    str:= "";
    out:= OutputTextString( str, true );
    Process( DirectoryCurrent(), name, InputTextNone(), out, options );
    CloseStream( out );

    # Strip the trailing newline character.
    Unbind( str[ Length( str ) ] );

    # In the default case, transform to a format that is compatible with
    # `StringDate' and `StringTime'.
    if Length( arg ) = 0 then
      str:= Int( str );
      str:= Concatenation( StringDate( Int( str / 86400 ) ),
                           ", ",
                           StringTime( 1000 * ( str mod 86400 ) ),
                           " UTC" );
    fi;

    return str;
end );


#############################################################################
##
#F  SendMail( <sendto>, <copyto>, <subject>, <text> )
##
InstallGlobalFunction( SendMail, function( sendto, copyto, subject, text )
    local sendmail, inp;

    sendto:= JoinStringsWithSeparator( sendto, "," );
    copyto:= JoinStringsWithSeparator( copyto, "," );
    sendmail:= Filename( DirectoriesSystemPrograms(), "mail" );
    inp:= InputTextString( text );

    return Process( DirectoryCurrent(), sendmail, inp, OutputTextNone(),
                    [ "-s", subject, "-c", copyto, sendto ] );
end  );


#############################################################################
##
#F  ParseBackwards( <string>, <format> )
##
InstallGlobalFunction( "ParseBackwards", function( string, format )
    local result, pos, j, pos2;

    # Scan the string backwards.
    result:= [];
    pos:= Length( string );
    for j in Reversed( format ) do
      if IsString( j ) then
        pos2:= pos - Length( j );
        if pos2 < 0 or string{ [ pos2+1 .. pos ] } <> j then
          return fail;
        fi;
      else
        pos2:= pos;
        while 0 < pos2 and j( string[ pos2 ] ) do
          pos2:= pos2-1;
        od;
      fi;
      if j = IsDigitChar then
        Add( result, Int( string{ [ pos2+1 .. pos ] } ) );
      else
        Add( result, string{ [ pos2+1 .. pos ] } );
      fi;
      pos:= pos2;
    od;
    if 0 < pos then
      return fail;
    fi;

    return Reversed( result );
    end );


#############################################################################
##
#F  ParseBackwardsWithPrefix( <string>, <format> )
##
InstallGlobalFunction( "ParseBackwardsWithPrefix", function( string, format )
    local prefixes, len, flen, fstr, fstrlen, result;

    # Remove string prefixes.
    prefixes:= [];
    len:= Length( string );
    flen:= Length( format );
    while 0 < flen and IsString( format[1] ) do
      fstr:= format[1];
      fstrlen:= Length( fstr );
      if len < fstrlen or string{ [ 1 .. fstrlen ] } <> fstr then
        return fail;
      fi;
      Add( prefixes, fstr );
      string:= string{ [ fstrlen + 1 .. len ] };
      format:= format{ [ 2 .. flen ] };
      len:= len - fstrlen;
      flen:= flen-1;
    od;

    # Parse the remaining string backwards.
    result:= ParseBackwards( string, format );
    if result = fail then
      return fail;
    fi;

    Append( prefixes, result );
    return prefixes;
end );


#############################################################################
##
#F  ParseForwards( <string>, <format> )
##
InstallGlobalFunction( "ParseForwards", function( string, format )
    local result, pos, j, pos2, len;

    result:= [];
    pos:= 0;
    for j in format do
      len:= Length( string );
      if IsString( j ) then
        pos2:= pos + Length( j );
        if len < pos2 or string{ [ pos+1 .. pos2 ] } <> j then
          return fail;
        fi;
      else
        pos2:= pos + 1;
        while pos2 <= len and j( string[ pos2 ] ) do
          pos2:= pos2 + 1;
        od;
        pos2:= pos2 - 1;
      fi;
      if j = IsDigitChar then
        Add( result, Int( string{ [ pos+1 .. pos2 ] } ) );
      else
        Add( result, string{ [ pos+1 .. pos2 ] } );
      fi;
      pos:= pos2;
    od;
    if pos <> len then
      return fail;
    fi;

    return result;
end );


#############################################################################
##
#F  ParseForwardsWithSuffix( <string>, <format> )
##
InstallGlobalFunction( "ParseForwardsWithSuffix", function( string, format )
    local suffixes, len, flen, fstr, fstrlen, result;

    # Remove string suffixes.
    suffixes:= [];
    len:= Length( string );
    flen:= Length( format );
    while 0 < flen and IsString( format[ flen ] ) do
      fstr:= format[ flen ];
      fstrlen:= Length( fstr );
      if len < fstrlen or string{ [ len-fstrlen+1 .. len ] } <> fstr then
        return fail;
      fi;
      suffixes:= Concatenation( [ fstr ], suffixes );
      len:= len - fstrlen;
      flen:= flen-1;
      string:= string{ [ 1 .. len ] };
      format:= format{ [ 1 .. flen ] };
    od;

    # Parse the remaining string forwards.
    result:= ParseForwards( string, format );
    if result = fail then
      return fail;
    fi;

    Append( result, suffixes );
    return result;
end );


#############################################################################
##
#F  AtlasRepIdentifier( <oldid> )
#F  AtlasRepIdentifier( <id>, "old" )
##
InstallGlobalFunction( AtlasRepIdentifier, function( arg )
    local id, tocid, groupname, files, res, type;

    if Length( arg ) = 1 and IsList( arg[1] ) then
      # Convert an old type identifier to a new type identifier.
      id:= arg[1];
      if not IsDenseList( id ) or Length( id ) < 3 then
        return fail;
      elif IsString( id[1] ) then
        # The identifier belongs to non-private data.
        return StructuralCopy( id );
      elif IsDenseList( id[1] ) and Length( id[1] ) = 2 then
        # The identifier belongs to private data.
        tocid:= id[1][1];
        groupname:= id[1][2];
        files:= id[2];
        if IsString( files ) then
          files:= [ files ];
        fi;
        res:= StructuralCopy( id );
        res[1]:= groupname;
        res[2]:= List( files, x -> [ tocid, x ] );
        return res;
      else
        return fail;
      fi;
    elif Length( arg ) = 2 and IsList( arg[1] ) and arg[2] = "old" then
      # Convert a new type identifier to an old type identifier if possible.
      id:= arg[1];
      if not IsDenseList( id ) or Length( id ) < 3 then
        return fail;
      elif IsString( id[2] ) or
           ( IsList( id[2] ) and ForAll( id[2], IsString ) ) then
        # The identifier belongs to non-private data.
        return StructuralCopy( id );
      elif IsDenseList( id[2] ) and not ForAny( id[2], IsString ) then
        # The identifier belongs to private data.
        files:= id[2];
        tocid:= Set( List( files, x -> x[1] ) );
        if Length( tocid ) = 1 then
          # The private data belong to the same extension.
          tocid:= tocid[1];
        else
          return fail;
        fi;
        groupname:= id[1];
        res:= StructuralCopy( id );
        res[1]:= [ tocid, groupname ];
        res[2]:= List( files, x -> x[2] );
        if Length( res[2] ) = 1 then
          # If the list describes MeatAxe matrices or permutations
          # then keep the list, otherwise strip it.
          res[2]:= res[2][1];
          for type in AGR.DataTypes( "rep" ) do
            if type[1] in [ "perm", "matff" ] and
               AGR.ParseFilenameFormat( res[2], type[2].FilenameFormat )
               <> fail then
              res[2]:= [ res[2] ];
              break;
            fi;
          od;
        fi;
        return res;
      else
        return fail;
      fi;
    else
      Error( "usage: AtlasRepIdentifier( <id>[, \"old\"] )" );
    fi;
    end );


#############################################################################
##
#F  CompositionOfSLDAndSLP( <sld>, <slp> )
##
##  Return a straight line decision that first applies the straight line
##  program <slp> to its inputs and then returns the result of the
##  straight line decision <sld> on the outputs.
##
##  A typical situation is that <slp> is a restandardization script
##  and <sld> is a presentation.
##
InstallGlobalFunction( CompositionOfSLDAndSLP,
    function( sld, slp )
    local lines, len, lastline, inp2, max, i, pos, line;

    lines:= ShallowCopy( LinesOfStraightLineProgram( slp ) );
    len:= Length( lines );
    lastline:= lines[ len ];
    inp2:= NrInputsOfStraightLineDecision( sld );

    if ForAll( lastline, IsList ) then

      # Check that the programs fit together.
      if inp2 <> Length( lastline ) then
        Error( "outputs of <slp> incompatible with inputs of <sld>" );
      fi;

      # The last line is a list of external representations of assoc. words.
      # Copy them first to safe positions, then to the first positions.
      max:= NrInputsOfStraightLineProgram( slp );
      for i in [ 1 .. len-1 ] do
        if IsList( lines[i][1] ) then
          max:= Maximum( max, lines[i][2] );
        else
          max:= max + 1;
        fi;
      od;
      Unbind( lines[ len ] );
      pos:= max;
      for i in lastline do
        max:= max + 1;
        Add( lines, [ i, max ] );
      od;
      for i in [ 1 .. Length( lastline ) ] do
        Add( lines, [ [ pos + i, 1 ], i ] );
      od;

    else
      # Check that the programs fit together.
      if inp2 <> 1 then
        Error( "outputs of <slp> incompatible with inputs of <sld>" );
      fi;

      if Length( lastline ) = 2 and IsList( lastline[1] ) then

        # The last line is a pair of the external representation of an assoc.
        # word and a positive integer.
        # Copy the word to position 1 if necessary.
        if lastline[2] <> 1 then
          Add( lines, [ [ lastline[2], 1 ], 1 ] );
        fi;

      else

        # The last line is the external representation of an assoc. word.
        # Store it at position 1.
        lines[ Length( lines ) ]:= [ lastline, 1 ];

      fi;

    fi;

    # Append the lines of `sld'.
    # (Rewrite lines of type 1.)
    max:= inp2;
    for line in LinesOfStraightLineDecision( sld ) do
      if line[1] = "Order" then
        Add( lines, line );
      elif ForAll( line, IsInt ) then
        max:= max + 1;
        Add( lines, [ line, max ] );
      else
        max:= Maximum( max, line[2] );
        Add( lines, line );
      fi;
    od;

    # Construct and return the new decision.
    return StraightLineDecisionNC( lines,
                                   NrInputsOfStraightLineProgram( slp ) );
    end );


#############################################################################
##
#F  AGR.CompareAsNumbersAndNonnumbers( <nam1>, <nam2> )
##
##  This function is available as `BrowseData.CompareAsNumbersAndNonnumbers'
##  if the Browse package is available.
##  But we must deal also with the case that this package is not available.
##
AGR.CompareAsNumbersAndNonnumbers:= function( nam1, nam2 )
    local len1, len2, len, digit, comparenumber, i;

    # Essentially the code does the following, just more efficiently.
    # return BrowseData.SplitStringIntoNumbersAndNonnumbers( nam1 ) <
    #        BrowseData.SplitStringIntoNumbersAndNonnumbers( nam2 );

    len1:= Length( nam1 );
    len2:= Length( nam2 );
    len:= len1;
    if len2 < len then
      len:= len2;
    fi;
    digit:= false;
    comparenumber:= 0;
    for i in [ 1 .. len ] do
      if nam1[i] in DIGITS then
        if nam2[i] in DIGITS then
          digit:= true;
          if comparenumber = 0 then
            # first digit of a number, or previous digits were equal
            if nam1[i] < nam2[i] then
              comparenumber:= 1;
            elif nam1[i] <> nam2[i] then
              comparenumber:= -1;
            fi;
          fi;
        else
          # if digit then the current number in `nam2' is shorter,
          # so `nam2' is smaller;
          # if not digit then a number starts in `nam1' but not in `nam2',
          # so `nam1' is smaller
          return not digit;
        fi;
      elif nam2[i] in DIGITS then
        # if digit then the current number in `nam1' is shorter,
        # so `nam1' is smaller;
        # if not digit then a number starts in `nam2' but not in `nam1',
        # so `nam2' is smaller
        return digit;
      else
        # both characters are non-digits
        if digit then
          # first evaluate the current numbers (which have the same length)
          if comparenumber = 1 then
            # nam1 is smaller
            return true;
          elif comparenumber = -1 then
            # nam2 is smaller
            return false;
          fi;
          digit:= false;
        fi;
        # now compare the non-digits
        if nam1[i] <> nam2[i] then
          return nam1[i] < nam2[i];
        fi;
      fi;
    od;

    if digit then
      # The suffix of the shorter string is a number.
      # If the longer string continues with a digit then it is larger,
      # otherwise the first digits of the number decide.
      if len < len1 and nam1[ len+1 ] in DIGITS then
        # nam2 is smaller
        return false;
      elif len < len2 and nam2[ len+1 ] in DIGITS then
        # nam1 is smaller
        return true;
      elif comparenumber = 1 then
        # nam1 is smaller
        return true;
      elif comparenumber = -1 then
        # nam2 is smaller
        return false;
      fi;
    fi;

    # Now the longer string is larger.
    return len1 < len2;
    end;


#############################################################################
##
#F  AGR.IsEquivalentSLP( <lines1>, <lines2>, <gens> )
##
##  returns 'true' if the straight line programs defined by the lists
##  <lines1> and <lines2>, respectively, evaluate to the same results
##  when applied to the list <gens>;
##  returns 'false' otherwise.
##
AGR.IsEquivalentSLP:= function( lines1, lines2, gens )
    local n, slp1, slp2;

    if lines1 = lines2 then
      return true;
    fi;

    n:= Length( gens );
    slp1:= StraightLineProgram( lines1, n );
    slp2:= StraightLineProgram( lines2, n );
    return ResultOfStraightLineProgram( slp1, gens )
           = ResultOfStraightLineProgram( slp2, gens );
    end;


#############################################################################
##
#F  AGR.CleanedGroupName( <name> )
##
##  The function is used for the creation of HTML files.
##  Replace backslash and colon, as 'Filename' does not accept them.
##
AGR.CleanedGroupName:= name -> JoinStringsWithSeparator(
                                   SplitString( name, ":\\" ), "." );


#############################################################################
##
#F  AGR.CurrentAtlasPage( <atlasname> )
##
##  The usual URLs refer to the location of the data,
##  but we need the web page with the overview (currently in the v3 variant).
##
##  The return value is 'fail' if the ''official'' AGR contains at least one
##  file for the group <atlasname>.
##
##  (cf. MFERCurrentAtlasPage)
##
AGR.CurrentAtlasPage:= function( atlasname )
    local prefix, list, pos, test, j, entry, info;

    prefix:= Concatenation( atlasname, "G" );
    list:= AtlasOfGroupRepresentationsInfo.filenames;
    pos:= PositionSorted( list, [ prefix ] );
    if Length( list ) < pos then
      return fail;
    fi;

    # Assume that only the standardizations 0, 1, 2 occur.
    test:= List( [ "0", "1", "2" ],
                 i -> Concatenation( "/", prefix, i, "-" ) );

    for j in [ pos .. Length( list ) ] do
      entry:= list[j][2];
      if list[j][3] = "core" and
         ForAny( test, x -> ReplacedString( entry, x, "" ) <> entry ) then
        entry:= SplitString( entry, "/" );
        info:= AtlasOfGroupRepresentationsInfo.servers[1];
        return Concatenation( "http://", info[1], "/", info[2], "v3/",
                              entry[1], "/", entry[2] );
      fi;
    od;

    return fail;
    end;


#############################################################################
##
#F  AGR.HTMLInfoForGroup( <tocids>, <gapname> )
##
##  the common part of the HTML file for a single group or of the combined
##  file for all groups
##
AGR.HTMLInfoForGroup:= function( tocids, gapname )
    local pref, str, inforeps, list, entry, infoprgs, i, pos;

    # Make sure that 'AGR.ShowOnlyASCII' returns 'true',
    # otherwise we cannot safely replace the "<=" substring.
    pref:= UserPreference( "AtlasRep", "DisplayFunction" );
    SetUserPreference( "AtlasRep", "DisplayFunction", "Print" );

    str:= "";

    # Append the information about representations.
    Append( str, "<dl>\n" );
    inforeps:= AGR.InfoReps( [ gapname, "contents", tocids ] );
#T add links to data which are available in the internet
    if not IsEmpty( inforeps.list ) then
      Append( str, "<dt>\n" );
      Append( str, Concatenation( inforeps.header[1],
                       NormalizedNameOfGroup( inforeps.header[2], "HTML" ) ) );
      Append( str, Concatenation(
                       inforeps.header{ [ 3 .. Length( inforeps.header ) ] } ) );
      Append( str, "\n" );
      Append( str, "</dt>\n" );
      Append( str, "<dd>\n" );

      list:= [];
      for entry in inforeps.list do
        entry[2][1]:= ReplacedString( entry[2][1], "<=",
                          MarkupGlobals.HTML.leq );
        entry[2][1]:= ReplacedString( entry[2][1], ",Z)",
                          Concatenation( ",", MarkupGlobals.HTML.Z, ")" ) );
        if 3 <= Length( entry[3] ) then
          entry[3][3]:= NormalizedNameOfGroup( entry[3][3], "HTML" );
        fi;
        Add( list, [ entry[1][1], entry[2][1], Concatenation( entry[3] ) ] );
      od;
      Append( str, HTMLStandardTable( fail, list,
                                      "datatable",
                                      [ "pright", "pleft", "pleft" ] ) );
      Append( str, "</dd>\n" );
    fi;
    Append( str, "</dl>\n" );

    # Append the information about programs.
    infoprgs:= AGR.InfoPrgs( [ gapname, "contents", tocids ] );
    if ForAny( infoprgs.list, x -> not IsEmpty( x ) ) then
      Append( str, "<dl>\n" );
      Append( str, "<dt>\n" );
      Append( str, infoprgs.header[1] );
      Append( str, NormalizedNameOfGroup( infoprgs.header[2], "HTML" ) );
      Append( str, Concatenation(
                       infoprgs.header{ [ 3 .. Length( infoprgs.header ) ] } ) );
      Append( str, "\n" );
      Append( str, "</dt>\n" );
      Append( str, "<dd>\n" );
      Append( str, "<ul>\n" );
      for entry in infoprgs.list do
        if not IsEmpty( entry ) then
          Append( str, "<li>\n" );
          Append( str, entry[1] );
          if 1 < Length( entry ) then
            Append( str, ":" );
          fi;
          Append( str, "\n" );
          if 1 < Length( entry ) then
            list:= entry{ [ 2 .. Length( entry ) ] };
            if IsString( list[1] ) then
              Append( str, list[1] );
            else
              for i in [ 1 .. Length( list ) ] do
                if list[i] = "" then
                  pos:= fail;
                else
                  pos:= Position( list[i][1], ':' );
                fi;
                if pos = fail or
                   Int( NormalizedWhitespace( list[i][1]{ [ 1 .. pos-1 ] } ) )
                     = fail then
                  list[i]:= [ list[i][1], "" ];
                else
                  # This happens currently only for 'maxes'.
                  list[i]:= [ list[i][1]{ [ 1 .. pos-1 ] },
                              NormalizedNameOfGroup( NormalizedWhitespace(
                                  list[i][1]{ [ pos+1 .. Length( list[i][1] ) ] } ),
                                  "HTML" ) ];
                fi;
              od;
              if ForAll( list, x -> x[2] = "" ) then
                Append( str, HTMLStandardTable( fail, List( list, x -> [x[1]] ),
                                          "datatable",
                                          [ "pleft" ] ) );
              else
                Append( str, HTMLStandardTable( fail, list,
                                          "datatable",
                                          [ "pright", "pleft" ] ) );
              fi;
            fi;
          fi;
          Append( str, "</li>\n" );
        fi;
      od;
      Append( str, "</ul>\n" );
      Append( str, "</dd>\n" );
      Append( str, "</dl>\n" );
      Append( str, "\n" );
    fi;

    SetUserPreference( "AtlasRep", "DisplayFunction", pref );

    return str;
    end;


#############################################################################
##
#F  AGR.CreateHTMLInfoForGroup( <tocids>, <gapname>, <dirname> )
##
##  <tocid> is a string or a list of strings,
##  <gapname> and <atlasname> are the names of the group in question,
##  <dirname> is the directory where the file will be created if necessary.
##

#T what about MathJax? -> introduce as a new option besides "HTML"?

AGR.CreateHTMLInfoForGroup:= function( tocids, gapname, dirname )
    local str, atlasname, link, inforeps, list, entry, infoprgs, i, pos;

    # Create the file header.
    str:= HTMLHeader( "GAP Package AtlasRep",
                      "../../atlasrep.css",
                      Concatenation( "<a href=\"../../index.html\">",
                          "GAP Package AtlasRep</a>" ),
#T -> indiv. title, link to AtlasRep, link to AGR, ...
                      Concatenation( "AtlasRep Info for ",
                          NormalizedNameOfGroup( gapname, "HTML" ) ) );
#T change parameters:
#T - automatically provide minimal css file in the dir. if not available,
#T - turn title with link into an argument
    Append( str, "<dl>\n" );

    # Append the links to the overview
    # and to the page for this group in the ATLAS database.
    Append( str, "<dt>\n" );
    atlasname:= AGR.GAPnamesRec.( gapname )[2];
    link:= AGR.CurrentAtlasPage( atlasname );
#T not for groups not in the AGR?
    if link <> fail then
      # There is a web page of the AGR to which we can point.
      Append( str, Concatenation( "<a href=\"", link, "\">",
                       MarkupGlobals.HTML.rightarrow,
                       " ATLAS page for ",
                       NormalizedNameOfGroup( gapname, "HTML" ),
                       "</a>\n" ) );
    fi;
    Append( str, "</dt>\n" );
    Append( str, "<dt>\n" );
    Append( str, Concatenation( "<a href=\"overview.htm\">",
                     MarkupGlobals.HTML.rightarrow,
                     " Overview of Groups</a>\n" ) );
    Append( str, "</dt>\n" );
    Append( str, "</dl>\n" );

    Append( str, AGR.HTMLInfoForGroup( tocids, gapname ) );

    # Append the footer string.
    Append( str, HTMLFooter() );

    # Create the file.
    return PrintToIfChanged( Concatenation( dirname, "/",
               AGR.CleanedGroupName( gapname ), ".htm" ), str );
    end;


#############################################################################
##
#F  AGR.CreateHTMLOverview( <tocid>[, <info>] )
##
##  <tocid> must be the string "core" or an ID of a data extension.
##  <info> must be a record with the components
##  - title
##  - cssfile
##  - cornerlink
##  - headerline
##  - overviewtext
##  - dir
##
##  The code was copied from 'DisplayAtlasInfoOverview'.
##
AGR.CreateHTMLOverview:= function( tocid, info... )
    local conditions, tocs, title, cssfile, cornerlink, headerline, str, 
          gapnames, groupnames, columns, type, matrix, alignments, col, i,
          row, dir, name;

    if Length( info ) = 0 then
      info:= rec();
    elif IsRecord( info[1] ) then
      info:= info[1];
    else
      Error( "<info> must be a record" );
    fi;
 
    
    conditions:= [ "contents", tocid ];
    tocs:= AGR.TablesOfContents( tocid );
    if Length( tocs ) = 0 then
      Error( "no id <tocid> known" );
    fi;

    # Create the file header.
    if IsBound( info.title ) then
      title:= info.title;
    elif tocid = "core" then
      title:= "GAP Package AtlasRep";
    else
      title:= Concatenation( "AtlasRep extension '", tocid, "'" );
    fi;

    if IsBound( info.cssfile ) then
      cssfile:= info.cssfile;
    elif tocid = "core" then
      cssfile:= "../../atlasrep.css";
    else
      cssfile:= fail;
    fi;

    if IsBound( info.cornerlink ) then
      cornerlink:= info.cornerlink;
    elif tocid = "core" then
      cornerlink:= Concatenation( "<a href=\"../../index.html\">",
                                  "GAP Package AtlasRep</a>" );
    else
      cornerlink:= fail;
    fi;

    if IsBound( info.headerline ) then
      headerline:= info.headerline;
    elif tocid = "core" then
      headerline:= "Available via the GAP Interface";
    else
      headerline:= fail;
    fi;

    str:= HTMLHeader( title, cssfile, cornerlink, headerline );
#T -> mention the name of the extension, the URL, ...

    # Insert the explanatory text.
    if IsBound( info.overviewtext ) then
      Append( str, info.overviewtext );
    elif tocid = "core" then
      Append( str, AGR.StringFile( Filename(
                       DirectoriesPackageLibrary( "atlasrep", "dev" ),
                       "overviewtxt.htm" ) ) );
    else
      Append( str, "" );
    fi;

    # Consider only those names for which actually information is available.
    gapnames:= Filtered( AtlasOfGroupRepresentationsInfo.GAPnamesSortDisp,
                   x -> ForAny( tocs, toc -> IsBound( toc.( x[2] ) ) ) );

    # Construct the links for the names.
    groupnames:= List( gapnames,
                       x -> Concatenation( "<a href=\"",
                                AGR.CleanedGroupName( x[1] ), ".htm\">",
                                NormalizedNameOfGroup( x[1], "HTML" ),
                                "</a>" ) );

    # Compute the data of the columns.
    columns:= [ [ "group", "l", groupnames ] ];
    for type in AGR.DataTypes( "rep", "prg" ) do
      if type[2].DisplayOverviewInfo <> fail then
        Add( columns, [
             type[2].DisplayOverviewInfo[1],
             type[2].DisplayOverviewInfo[2],
             List( gapnames,
                   name -> type[2].DisplayOverviewInfo[3](
                               Concatenation( [ name ], conditions ) ) ) ] );
      fi;
    od;
#T now omit empty columns

    matrix:= [ [] ];
    alignments:= [];

    # Add the table header line.
    for col in columns do
      Add( matrix[1], col[1] );
      if col[2] = "l" then
        Add( alignments, "tdleft" );
      elif col[2] = "r" then
        Add( alignments, "tdright" );
      else
        Add( alignments, "tdcenter" );
      fi;
    od;

    if IsBound( info.dir ) then
      dir:= info.dir;
    elif tocid = "core" then
      dir:= Filename( DirectoriesPackageLibrary( "atlasrep", "htm/data" ), "" );
    else
      Error( "info.dir must be bound" );
    fi;

    # Collect the information for each group.
    for i in [ 1 .. Length( gapnames ) ] do
      row:= [ columns[1][3][i] ];
      for col in columns{ [ 2 .. Length( columns ) ] } do
        Add( row, col[3][i][1] );
      od;
      Add( matrix, row );

      # Create the file for this group.
      info:= AGR.CreateHTMLInfoForGroup( tocid, gapnames[i][1], dir );
      if not StartsWith( info, "unchanged" ) then
        Print( "#I  ", info, "\n" );
      fi;
    od;

    Append( str, HTMLStandardTable( fail, matrix, "datatable", alignments ) );

    # Append the footer string.
    Append( str, HTMLFooter() );

    # Create the overview file.
    info:= PrintToIfChanged( Concatenation( dir, "/overview.htm" ), str );
    if not StartsWith( info, "unchanged" ) then
      Print( "#I  ", info, "\n" );
    fi;

    # Finally, report about HTML files that should be removed.
    # List the files in `toc'.
    str:= Difference( DirectoryContents( dir ), List( gapnames,
          x -> Concatenation( AGR.CleanedGroupName( x[1] ), ".htm" ) ) );
    SubtractSet( str, [ "changes.htm", "changes.htm.old",
                        "overview.htm", ".", ".." ] );
    if not IsEmpty( str ) then
      Print( "#I  Remove the following files from '", dir, "':\n" );
      for name in str do
        Print( "#I  ", name, "\n" );
      od;
    fi;
    end;


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


[ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ]