Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/GAP/pkg/ctbllib/gap4/   (Algebra von RWTH Aachen Version 4.15.1©)  Datei vom 9.0.2025 mit Größe 107 kB image not shown  

Quelle  ctdbattr.g   Sprache: unbekannt

 
#############################################################################
##
#W  ctdbattr.g           GAP 4 package CTblLib                  Thomas Breuer
##
##  This file contains the declarations for the database id enumerators
##  `CTblLib.Data.IdEnumerator' and `CTblLib.Data.IdEnumeratorExt`,
##  and their database attributes.
##  See the GAP package `Browse' for technical details.
##  Among others, the enumerators provide the data for the GAP attribute
##  `GroupInfoForCharacterTable'.
##
##  The component `reverseEval' is used only by the function
##  `CharacterTableForGroupInfo',
##  it is not needed by the database attribute handling mechanism.
##
##  (Some attributes are available only if additional GAP packages are
##  available, for example the TomLib package.
##  Their data are read via suitable package extensions that are listed
##  in CTblLib's 'PackageInfo.g' file.)
##


#############################################################################
##
#F  IsDihedralCharacterTable( <tbl> )
##
##  Let <A>tbl</A> be the character table of a group <M>G</M>, say.
##  Then <M>G</M> is a dihedral group if and only if
##  the order of <M>G</M> is an even integer <M>n</M>,
##  <M>G</M> contains a cyclic normal subgroup <M>N</M> of index two,
##  <M>G \setminus N</M> contains involutions,
##  and the table of <M>G</M> is real.
##
BindGlobal( "IsDihedralCharacterTable", function( tbl )
    local size, orders, nsg, ccl;

    size:= Size( tbl );
    if size mod 2 = 1 then
      return false;
    fi;
    orders:= OrdersClassRepresentatives( tbl );
    nsg:= Filtered( ClassPositionsOfNormalSubgroups( tbl ),
                    n -> size / 2 in orders{ n } );
    ccl:= [ 1 .. NrConjugacyClasses( tbl ) ];
    return ForAny( nsg, n -> 2 in orders{ Difference( ccl, n ) } )
           and PowerMap( tbl, -1 ) = ccl;
    end );


#############################################################################
##
#F  CTblLib.Data.CharacterTablesOfNormalSubgroupWithGivenImage( <tbl>,
#F      <classes> )
##
##  Let <tbl> be an ordinary character table, and <classes> be a list of
##  class positions for <tbl>.
##  `CTblLib.Data.CharacterTablesOfNormalSubgroupWithGivenImage' returns the
##  list of those library tables that store a class fusion to <tbl> having
##  image exactly <classes> and whose size equals the union of the
##  class lengths for <classes>.
##
CTblLib.Data.CharacterTablesOfNormalSubgroupWithGivenImage:=
    function( tbl, classes )
    local n, result, ids, name, subtbl, fus, next;

    n:= Sum( SizesConjugacyClasses( tbl ){ classes } );
    result:= [];
    ids:= [];
    for name in NamesOfFusionSources( tbl ) do
      subtbl:= CharacterTable( name );
      if subtbl <> fail and Size( subtbl ) mod n = 0 then
        fus:= GetFusionMap( subtbl, tbl );
        if Set( fus ) = classes and Size( subtbl ) = n then
          # The table itself satisfies the conditions.
          if not Identifier( subtbl ) in ids then
            Add( result, [ subtbl, fus ] );
            AddSet( ids, Identifier( subtbl ) );
          fi;
        elif ClassPositionsOfKernel( fus ) = [ 1 ]
             and IsSubset( fus, classes ) then
          # Maybe a subgroup fits, so enter a recursion.
          for next in
            CTblLib.Data.CharacterTablesOfNormalSubgroupWithGivenImage( subtbl,
              Filtered( [ 1 .. Length( fus ) ], i -> fus[i] in classes ) ) do
            if not Identifier( next[1] ) in ids then
              Add( result, [ next[1], fus{ next[2] } ] );
              AddSet( ids, Identifier( next[1] ) );
            fi;
          od;
        fi;
      fi;
    od;
    return result;
    end;


#############################################################################
##
#F  CTblLib.FindTableForGroup( <G>, <tbls>, <pos> )
##
##  Let <G> be a group, <tbls> be a list of ordinary character tables,
##  and <pos> be a position in this list such that <G> belongs to (at least)
##  one of these tables.
##  `CTblLib.FindTableForGroup' tries to decide whether <G> belongs to the
##  <pos>-th entry in <tbls>.
##  If this is possible then `true' or `false' is returned,
##  otherwise `fail' is returned.
##
CTblLib.FindTableForGroup:= function( G, tbls, pos )
    local funs, invs, rng, bound, k, g, i, val;

    # Use element orders and centralizer orders.
    funs:= [ Order,
             g -> Size( Centralizer( G, g ) ) ];
    invs:= List( tbls, t -> TransposedMat(
                                [ OrdersClassRepresentatives( t ),
                                  SizesCentralizers( t ) ] ) );

    # Can we decide the question, or is there a chance to fail?
    rng:= [ 1 .. Length( tbls ) ];
    if ForAll( rng, i -> i = pos or
                 not ( IsEmpty( Difference( invs[i], invs[ pos ] ) ) or
                       IsEmpty( Difference( invs[i], invs[ pos ] ) ) ) ) then
      bound:= infinity;
    else
      bound:= 100;
    fi;

    k:= 1;
    while k <= bound do
      g:= PseudoRandom( G );
      for i in [ 1 .. Length( funs ) ] do
        val:= funs[i]( g );
        rng:= Filtered( rng,
                        j -> ForAny( invs[j], tuple -> val = tuple[i] ) );
        if   rng = [ pos ] then
          return true;
        elif not pos in rng then
          return false;
        fi;
      od;
      k:= k + 1;
    od;

    return fail;
    end;


#############################################################################
##
##  Provide utilities for the computation of database attribute values.
##  They allow one to access table data without actually creating the tables.
##


#############################################################################
##
#F  CTblLib.AttrDataString( <entry>, <default>, <withcomment> )
##
##  This is the default value for the `string' component of database
##  attributes,
##  which is used for storing the attriute values in files.
##
CTblLib.AttrDataString:= function( entry, default, withcomment )
    local encode, result, comment, data, l, x;

    encode:= function( value )
      if IsString( value ) and
         ( IsStringRep( value ) or not IsEmpty( value ) ) then
        value:= ReplacedString( value, "\"", "\\\"" );
        value:= ReplacedString( value, "\n", "\\n" );
        value:= Concatenation( "\"", value, "\"" );
      elif IsList( value ) then
        value:= Concatenation( "[",
                    JoinStringsWithSeparator( List( value, encode ), "," ),
                    "]" );
      else
        value:= String( value );
        value:= ReplacedString( value, "\"", "\\\"" );
        value:= ReplacedString( value, "\n", "\\n" );
      fi;
      return value;
    end;

    # Default values need not be stored.
    if IsList( entry[2] ) and entry[2] = default then
      return "";
    fi;

    # The first entry is the identifier.
    result:= Concatenation( "[", encode( entry[1] ), "," );

    if withcomment then
      # A comment may be stored at the second position.
      comment:= entry[2][1];
      data:= entry[2][2];
      Append( result, Concatenation( "[", encode( comment ), "," ) );
    else
      data:= entry[2];
    fi;
    Append( result, encode( data ) );
    if withcomment then
      Append( result, "]" );
    fi;
    Append( result, "],\n" );
    return result;
end;


#############################################################################
##
#F  CTblLib.Data.InvariantByRecursion( <list>, <funcs> )
##
##  ... is used only for computing the `Size' and `NrConjugacyClasses' values
##
##  <funcs> is a record with the components
##    `gentablefunc'
##      a function that takes two arguments,
##      a generic character table and ...
##    `wreathsymmfunc'
##      a function ...
##    `cheaptest'
##      a function ...
##
CTblLib.Data.InvariantByRecursion:= function( list, funcs )
    local id, info, r, res, cen;

    id:= list[1];
    info:= LibInfoCharacterTable( id );
    id:= info.firstName;
    if info.fileName = "ctgeneri" then
      # Evaluate only part of the generic table.
      return funcs.gentablefunc( CharacterTable( info.firstName ), list );
    elif Length( list ) = 1
         and IsBound( CTblLib.Data.CharacterTableInfo.( id ) ) then
      r:= CTblLib.Data.CharacterTableInfo.( id );
      res:= funcs.cheaptest( r );
      if res <>  fail then
        return res;
      elif IsBound( r.ConstructionInfoCharacterTable )
           and IsList( r.ConstructionInfoCharacterTable ) then
        # Determine the size/nccl from the sizes/nccls of the input tables.
        if   r.ConstructionInfoCharacterTable[1] in
                  [ "ConstructDirectProduct", "ConstructIsoclinic" ] then
          r:= List( r.ConstructionInfoCharacterTable[2],
                    l -> CTblLib.Data.InvariantByRecursion( l, funcs ) );
          if fail in r then
            return fail;
          fi;
          return Product( r, 1 );
        elif r.ConstructionInfoCharacterTable[1] in
                  [ "ConstructPermuted", "ConstructAdjusted" ] then
          r:= CTblLib.Data.InvariantByRecursion(
                  r.ConstructionInfoCharacterTable[2], funcs );
          if r = fail then
            return fail;
          fi;
          return r;
        elif r.ConstructionInfoCharacterTable[1]
             = "ConstructWreathSymmetric" then
          id:= r.ConstructionInfoCharacterTable[3];
          r:= CTblLib.Data.InvariantByRecursion(
                  r.ConstructionInfoCharacterTable[2], funcs );
          if r = fail then
            return fail;
          fi;
          return funcs.wreathsymmfunc( r, id );
        elif r.ConstructionInfoCharacterTable[1]
             = "ConstructCentralProduct" then
          cen:= r.ConstructionInfoCharacterTable[3];
          r:= List( r.ConstructionInfoCharacterTable[2],
                    l -> CTblLib.Data.InvariantByRecursion( l, funcs ) );
          if fail in r then
            return fail;
          fi;
          return Product( r, 1 ) / Length( cen );
        fi;
      fi;
    fi;
    return fail;
end;

CTblLib.Data.prepare:= function( attr )
  CTblLib.Data.unload:= UserPreference( "CTblLib", "UnloadCTblLibFiles" );
  SetUserPreference( "CTblLib", "UnloadCTblLibFiles", false );
end;

CTblLib.Data.cleanup:= function( attr )
  SetUserPreference( "CTblLib", "UnloadCTblLibFiles", CTblLib.Data.unload );
  Unbind( CTblLib.Data.unload );
end;

# a function that does nothing
CTblLib.Data.MyIdFunc:= function( arg ); end;

CTblLib.Data.TABLE_ACCESS_FUNCTIONS:= [
  rec(),
  rec(
       LIBTABLE := rec( LOADSTATUS := rec(), clmelab := [], clmexsp := [] ),

       # These functions are used in the data files.
       GALOIS := CTblLib.Data.MyIdFunc,
       TENSOR := CTblLib.Data.MyIdFunc,
       ALF := CTblLib.Data.MyIdFunc,
       ACM := CTblLib.Data.MyIdFunc,
       ARC := CTblLib.Data.MyIdFunc,
       ALN := CTblLib.Data.MyIdFunc,
       MBT := CTblLib.Data.MyIdFunc,
       MOT := CTblLib.Data.MyIdFunc,
      ) ];

CTblLib.Data.SaveTableAccessFunctions := function()
  local name;

  if CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1] <> rec() then
    Info( InfoDatabaseAttributeX, 1, "functions were already saved" );
    return;
  fi;

  Info( InfoDatabaseAttributeX, 1, "before saving global variables" );
  for name in RecNames( CTblLib.Data.TABLE_ACCESS_FUNCTIONS[2] ) do
    if IsBoundGlobal( name ) then
      if IsReadOnlyGlobal( name ) then
        MakeReadWriteGlobal( name );
        CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1].( name ):=
            [ ValueGlobal( name ), "readonly" ];
      else
        CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1].( name ):=
            [ ValueGlobal( name ) ];
      fi;
      UnbindGlobal( name );
    fi;
    ASS_GVAR( name, CTblLib.Data.TABLE_ACCESS_FUNCTIONS[2].( name ) );
  od;
end;

CTblLib.Data.RestoreTableAccessFunctions := function()
  local name;

  if CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1] = rec() then
    Info( InfoDatabaseAttributeX, 1, "cannot restore without saving" );
    return;
  fi;

  for name in RecNames( CTblLib.Data.TABLE_ACCESS_FUNCTIONS[2] ) do
    UnbindGlobal( name );
    if IsBound( CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1].( name ) ) then
      ASS_GVAR( name, CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1].( name )[1] );
      if Length( CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1].( name ) ) = 2 then
        MakeReadOnlyGlobal( name );
      fi;
      Unbind( CTblLib.Data.TABLE_ACCESS_FUNCTIONS[1].( name ) );
    fi;
  od;
  Info( InfoDatabaseAttributeX, 1, "after restoring global variables" );
end;


#############################################################################
##
#F  CTblLib.Data.ComputeCharacterTableInfoByScanningLibraryFiles(
#F      <neededcomponents>, <providedcomponents>, <pairs> )
##
##  If `CTblLib.Data.knownComponents' contains all entries of the list
##  <neededcomponents> then nothing is done.
##
##  Otherwise, ...
##
##  The argument <A>pairs</A> must be a list of pairs
##  <C>[ <A>nam</A>, <A>fun</A> ]</C>
##  where <A>nam</A> is the name of a function to be reassigned during the
##  reread process (such as <C>"MOT"</C>, <C>"ARC"</C>),
##  and <A>fun</A> is the corresponding value.
##
CTblLib.Data.ComputeCharacterTableInfoByScanningLibraryFiles :=
    function( neededcomponents, providedcomponents, pairs )
    local filenames, pair, name;

    # Check if we have to do something.
    if IsBound( CTblLib.Data.knownComponents ) and
       IsSubset( CTblLib.Data.knownComponents, neededcomponents ) then
      return;
    fi;

    # Remember the names of all character table library files.
    filenames:= LIBLIST.files;

    # Disable the table library access.
    CTblLib.Data.SaveTableAccessFunctions();

    # Define appropriate access functions.
    for pair in pairs do
      ASS_GVAR( pair[1], pair[2] );
    od;

    # Clear the cache.
    CTblLib.Data.CharacterTableInfo:= rec();

    # Loop over the library files.
    for name in filenames do
      if name[1] = '/' then
        # private extension
        Read( Concatenation( name, ".tbl" ) );
      else
        ReadPackage( "ctbllib", Concatenation( "data/", name, ".tbl" ) );
      fi;
    od;

    # Restore the ordinary table library access.
    CTblLib.Data.RestoreTableAccessFunctions();

    # Replace the component list for the next call.
    CTblLib.Data.knownComponents:= providedcomponents;
    end;


#############################################################################
##
##  Create the database id enumerator to which the database attributes refer.
##
CTblLib.Data.IdEnumerator:= DatabaseIdEnumeratorX( rec(
    identifiers:= Set( LIBLIST.firstnames_orig ),
    isSorted:= true,
    entry:= function( idenum, id ) return CharacterTable( id ); end,
    version:= LIBLIST.lastupdated,
    update:= function( idenum )
      idenum.identifiers:= Set( AllCharacterTableNames() );
      idenum.version:= LIBLIST.lastupdated;
      return true;
      end,
#   isUpToDate:= idenum -> idenum.version = LIBLIST.lastupdated,
#T note: notifying a new table should then change the lastupdated field!
    viewSort:= CTblLib.CompareAsNumbersAndNonnumbers,
    align:= "lt",
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.Size
##
AddSet( CTblLib.SupportedAttributes, "Size" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "Size",
  description:= "sizes of GAP library character tables",
  type:= "pairs",
  name:= "Size",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "attr_size.json" ),
  dataDefault:= fail,
  isSorted:= true,
  neededAttributes:= [],
  prepareAttributeComputation:= function( attr )
    CTblLib.Data.prepare( attr );
    CTblLib.Data.ComputeCharacterTableInfoByScanningLibraryFiles(
      [ "ConstructionInfoCharacterTable", "SizesCentralizers" ],
      [ "ConstructionInfoCharacterTable", "InfoText", "Maxes",
        "SizesCentralizers" ],
      [ [ "MOT", function( arg )
          local record;
            record:= rec( InfoText:= arg[2],
                          SizesCentralizers:= arg[3] );
            if IsBound( arg[7] ) then
              record.ConstructionInfoCharacterTable:= arg[7];
            fi;
            CTblLib.Data.CharacterTableInfo.( arg[1] ):= record;
          end ],
        [ "ARC", function( arg )
            if arg[2] = "maxes" then
              CTblLib.Data.CharacterTableInfo.( arg[1] ).Maxes:= arg[3];
            fi;
          end ] ] );
    end,
  cleanupAfterAttributeComputation:= CTblLib.Data.cleanup,
  create:= function( attr, id )
    local r, other;

    if IsBound( CTblLib.Data.CharacterTableInfo ) and
       IsBound( CTblLib.Data.CharacterTableInfo.( id ) ) then
      r:= CTblLib.Data.InvariantByRecursion( [ id ],
              rec( gentablefunc:= function( gentable, list)
                     return gentable.size( list[2] );
                   end,
                   cheaptest:= function( r )
                     if IsList( r.SizesCentralizers ) then
                       return r.SizesCentralizers[1];
                     fi;
                     return fail;
                   end,
                   wreathsymmfunc:= function( subval, n )
                     return subval^n * Factorial( n );
                   end,
                 ) );
    else
      r:= fail;
    fi;
    if r = fail then
      Info( InfoDatabaseAttributeX, 1,
            "hard test for Size computation of ", id );
      return Size( CharacterTable( id ) );
    else
      return r;
    fi;
    end,
  string:= entry -> CTblLib.AttrDataString( entry, fail, false ),
  check:= ReturnTrue,

  align:= "t",
  viewLabel:= "size",
  viewSort:= CTblLib.CompareLenLex,
  categoryValue:= res -> Concatenation( "size = ", String( res ) ),
  sortParameters:= [ "add counter on categorizing", "yes" ],
  widthCol:= 25,
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.IdentifierOfMainTable
##
DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "IdentifierOfMainTable",
  description:= "identifier of the table for which the current table is a duplicate",
  type:= "pairs",
  name:= "IdentifierOfMainTable",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "attr_main.json" ),
  dataDefault:= fail,
  isSorted:= true,
  neededAttributes:= [],
  prepareAttributeComputation:= function( attr )
    CTblLib.Data.prepare( attr );
    CTblLib.Data.ComputeCharacterTableInfoByScanningLibraryFiles(
      [ "ConstructionInfoCharacterTable" ],
      [ "ConstructionInfoCharacterTable", "InfoText", "Maxes",
        "SizesCentralizers" ],
      [ [ "MOT", function( arg )
          local record;
            record:= rec( InfoText:= arg[2],
                          SizesCentralizers:= arg[3] );
            if IsBound( arg[7] ) then
              record.ConstructionInfoCharacterTable:= arg[7];
            fi;
            CTblLib.Data.CharacterTableInfo.( arg[1] ):= record;
          end ],
        [ "ARC", function( arg )
            if arg[2] = "maxes" then
              CTblLib.Data.CharacterTableInfo.( arg[1] ).Maxes:= arg[3];
            fi;
          end ] ] );
    end,
  cleanupAfterAttributeComputation:= CTblLib.Data.cleanup,
  create:= function( attr, id )
    local t, info;

    if IsBound( CTblLib.Data.CharacterTableInfo ) and
       IsBound( CTblLib.Data.CharacterTableInfo.( id ) ) then
      t:= CTblLib.Data.CharacterTableInfo.( id );
      if IsBound( t.ConstructionInfoCharacterTable ) then
        info:= t.ConstructionInfoCharacterTable;
      fi;
    else
      t:= CharacterTable( id );
      if HasConstructionInfoCharacterTable( t ) then
        info:= ConstructionInfoCharacterTable( t );
      fi;
    fi;
    if IsBound( info ) and IsList( info )
                       and info[1] = "ConstructPermuted" then
      info:= info[2];
      if Length( info ) = 1 then
        # permuted library table
        return info[1];
      fi;
    fi;

    return fail;
    end,
  string:= entry -> CTblLib.AttrDataString( entry, fail, false ),
  check:= ReturnTrue,

  align:= "t",
  viewLabel:= "main",
  categoryValue:= res -> Concatenation( "main table = ", String( res ) ),
  sortParameters:= [ "add counter on categorizing", "yes" ],
  widthCol:= 12,
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.IsDuplicateTable
##
AddSet( CTblLib.SupportedAttributes, "IsDuplicateTable" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "IsDuplicateTable",
  description:= "are GAP library character tables duplicates of others",
  type:= "values",
  name:= "IsDuplicateTable",
  align:= "c",
  categoryValue:= function( val )
    if val then
      return "duplicate";
    else
      return "not duplicate";
    fi;
    end,
  neededAttributes:= [ "IdentifierOfMainTable" ],
  create:= function( attr, id )
    local main;

    main:= attr.idenumerator.attributes.IdentifierOfMainTable;
    return IsString( main.attributeValue( main, id ) );
    end,
  viewValue:= x -> CTblLib.ReplacedEntry( x, [ false, true ],
                                                [ "-", "+" ] ),
  viewLabel:= "duplicate?",
  sortParameters:= [ "add counter on categorizing", "yes" ],
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.IdentifiersOfDuplicateTables
##
AddSet( CTblLib.SupportedAttributes, "IdentifiersOfDuplicateTables" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "IdentifiersOfDuplicateTables",
  description:= "identifiers of GAP library character tables that are duplicates",
  type:= "pairs",
  name:= "IdentifiersOfDuplicateTables",
  dataDefault:= [],
  isSorted:= true,
  neededAttributes:= [ "IdentifierOfMainTable" ],
  prepareAttributeComputation:= function( attr )
      local main, id, mainid;

      main:= attr.idenumerator.attributes.IdentifierOfMainTable;
      CTblLib.IdentifiersOfDuplicateTables:= rec();

      for id in attr.idenumerator.identifiers do
        mainid:= main.attributeValue( main, id );
        if mainid <> fail then
          if not IsBound( CTblLib.IdentifiersOfDuplicateTables.( mainid ) ) then
            CTblLib.IdentifiersOfDuplicateTables.( mainid ):= [ id ];
          else
            AddSet( CTblLib.IdentifiersOfDuplicateTables.( mainid ), id );
          fi;
        fi;
      od;
    end,
  cleanupAfterAttributeComputation:= function( attr )
      Unbind( CTblLib.IdentifiersOfDuplicateTables );
    end,
  create:= function( attr, id )
    if IsBound( CTblLib.IdentifiersOfDuplicateTables.( id ) ) then
      return CTblLib.IdentifiersOfDuplicateTables.( id );
    else
      return [];
    fi;
    end,
  viewLabel:= "duplicates",
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.NrConjugacyClasses
##
AddSet( CTblLib.SupportedAttributes, "NrConjugacyClasses" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "NrConjugacyClasses",
  description:= "class numbers of GAP library character tables",
  type:= "pairs",
  name:= "NrConjugacyClasses",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "attr_nccl.json" ),
  dataDefault:= fail,
  isSorted:= true,
  neededAttributes:= [],
  prepareAttributeComputation:= function( attr )
    CTblLib.Data.prepare( attr );
    CTblLib.Data.ComputeCharacterTableInfoByScanningLibraryFiles(
      [ "ConstructionInfoCharacterTable", "SizesCentralizers" ],
      [ "ConstructionInfoCharacterTable", "InfoText", "Maxes",
        "SizesCentralizers" ],
      [ [ "MOT", function( arg )
          local record;
            record:= rec( InfoText:= arg[2],
                          SizesCentralizers:= arg[3] );
            if IsBound( arg[7] ) then
              record.ConstructionInfoCharacterTable:= arg[7];
            fi;
            CTblLib.Data.CharacterTableInfo.( arg[1] ):= record;
          end ],
        [ "ARC", function( arg )
            if arg[2] = "maxes" then
              CTblLib.Data.CharacterTableInfo.( arg[1] ).Maxes:= arg[3];
            fi;
          end ] ] );
    end,
  cleanupAfterAttributeComputation:= CTblLib.Data.cleanup,
  create:= function( attr, id )
    local r, other;

    if IsBound( CTblLib.Data.CharacterTableInfo ) and
       IsBound( CTblLib.Data.CharacterTableInfo.( id ) ) then
      r:= CTblLib.Data.InvariantByRecursion( [ id ],
              rec( gentablefunc:= function( gentable, list )
                     return Sum( List( gentable.classparam,
                                       p -> Length( p( list[2] ) ) ), 0 );
                   end,
                   cheaptest:= function( r )
                     if IsList( r.SizesCentralizers ) then
                       return Length( r.SizesCentralizers );
                     fi;
                     return fail;
                   end,
                   wreathsymmfunc:= function( subval, n )
                     return NrPartitionTuples( n, subval );
                   end,
                 ) );
    else
      r:= fail;
    fi;
    if r = fail then
      return NrConjugacyClasses( CharacterTable( id ) );
    else
      return r;
    fi;
    end,
  string:= entry -> CTblLib.AttrDataString( entry, fail, false ),
  check:= ReturnTrue,

  align:= "t",
  viewLabel:= "nccl",
  viewSort:= CTblLib.CompareLenLex,
  categoryValue:= res -> Concatenation( "nccl = ", String( res ) ),
  sortParameters:= [ "add counter on categorizing", "yes" ],
  widthCol:= 4,
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.InfoText
##
##  This is used for creating the group overviews
##  (plain strings or Browse tables or HTML files).
##
AddSet( CTblLib.SupportedAttributes, "InfoText" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "InfoText",
  description:= "info text of GAP library character tables",
  type:= "pairs",
  name:= "InfoText",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "attr_text.json" ),
  dataDefault:= "",
  isSorted:= true,
  neededAttributes:= [],
  prepareAttributeComputation:= function( attr )
    CTblLib.Data.prepare( attr );
    CTblLib.Data.ComputeCharacterTableInfoByScanningLibraryFiles(
      [ "ConstructionInfoCharacterTable", "InfoText" ],
      [ "ConstructionInfoCharacterTable", "InfoText", "Maxes",
        "SizesCentralizers" ],
      [ [ "MOT", function( arg )
          local record;
            record:= rec( InfoText:= arg[2],
                          SizesCentralizers:= arg[3] );
            if IsBound( arg[7] ) then
              record.ConstructionInfoCharacterTable:= arg[7];
            fi;
            CTblLib.Data.CharacterTableInfo.( arg[1] ):= record;
          end ],
        [ "ARC", function( arg )
            if arg[2] = "maxes" then
              CTblLib.Data.CharacterTableInfo.( arg[1] ).Maxes:= arg[3];
            fi;
          end ] ] );
    end,
  cleanupAfterAttributeComputation:= CTblLib.Data.cleanup,
  create:= function( attr, id )
    local info, r;

    if IsBound( CTblLib.Data.CharacterTableInfo ) and
       IsBound( CTblLib.Data.CharacterTableInfo.( id ) ) then
      info:= LibInfoCharacterTable( id );
      id:= info.firstName;
      if info.fileName = "ctgeneri" then
        # Evaluate only part of the generic table.
        return CharacterTable( info.firstName ).text;
      else
        r:= CTblLib.Data.CharacterTableInfo.( id );
        if IsList( r.InfoText ) then
          return Concatenation( r.InfoText );
        fi;
        return "";
      fi;
    else
      r:= CharacterTable( id );
      if HasInfoText( r ) then
        return InfoText( r );
      else
        return "";
      fi;
    fi;
    end,
  string:= entry -> CTblLib.AttrDataString( entry, "", false ),
  check:= ReturnTrue,

  align:= "t",
  viewLabel:= "info",
  widthCol:= 20,
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.Maxes
##
AddSet( CTblLib.SupportedAttributes, "Maxes" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "Maxes",
  description:= "maxes lists of GAP library character tables",
  type:= "pairs",
  name:= "Maxes",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "attr_maxes.json" ),
  dataDefault:= fail,
  isSorted:= true,
  neededAttributes:= [],
  prepareAttributeComputation:= function( attr )
    CTblLib.Data.prepare( attr );
    CTblLib.Data.ComputeCharacterTableInfoByScanningLibraryFiles(
      [ "Maxes" ],
      [ "ConstructionInfoCharacterTable", "InfoText", "Maxes",
        "SizesCentralizers" ],
      [ [ "MOT", function( arg )
          local record;
            record:= rec( InfoText:= arg[2],
                          SizesCentralizers:= arg[3] );
            if IsBound( arg[7] ) then
              record.ConstructionInfoCharacterTable:= arg[7];
            fi;
            CTblLib.Data.CharacterTableInfo.( arg[1] ):= record;
          end ],
        [ "ARC", function( arg )
            if arg[2] = "maxes" then
              CTblLib.Data.CharacterTableInfo.( arg[1] ).Maxes:= arg[3];
            fi;
          end ] ] );
    end,
  cleanupAfterAttributeComputation:= CTblLib.Data.cleanup,
  create:= function( attr, id )
    if IsBound( CTblLib.Data.CharacterTableInfo ) and
       IsBound( CTblLib.Data.CharacterTableInfo.( id ) ) and
       IsBound( CTblLib.Data.CharacterTableInfo.( id ).Maxes ) then
      return CTblLib.Data.CharacterTableInfo.( id ).Maxes;
    else
      return fail;
    fi;
    end,
  string:= entry -> CTblLib.AttrDataString( entry, fail, false ),
  check:= ReturnTrue,

  align:= "t",
  viewLabel:= "maxes",
  widthCol:= 25,
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.NamesOfFusionSources
##
AddSet( CTblLib.SupportedAttributes, "NamesOfFusionSources" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "NamesOfFusionSources",
  description:= "fusions from other character tables to the given one",
  type:= "values",
  name:= "NamesOfFusionSources",
  align:= "tl",
  create:= function( attr, id )
    local pos;

    pos:= Position( LIBLIST.firstnames, id );
#T firstnames is not sorted, better use allnames & position?
    if pos <> fail then
      return LIBLIST.fusionsource[ pos ];
    fi;
    return [];
    end,
  version:= CTblLib.Data.IdEnumerator.version,
  viewValue:= x -> rec( rows:= x, align:= "tl" ),
  viewLabel:= "fusions -> G",
  categoryValue:= function( val )
    if IsEmpty( val ) then
      return "(no fusions to these tables)";
    fi;
    return List( val, x -> Concatenation( "fusions from ", x ) );
    end,
  sortParameters:= [ "add counter on categorizing", "yes",
                     "split rows on categorizing", "yes" ],
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.FusionsTo
##
DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "FusionsTo",
  description:= "fusions from the given character table to other ones",
  type:= "values",
  align:= "tl",
  categoryValue:= function( val )
    if IsEmpty( val ) then
      return "(no fusions from these tables)";
    fi;
    return List( val, x -> Concatenation( "fusions to ", x ) );
    end,
  prepareAttributeComputation:= function( attr )
    local i, nam, src;

    CTblLib.Data.sortedfirstnames:= ShallowCopy( LIBLIST.firstnames );
    CTblLib.Data.position:= [ 1 .. Length( LIBLIST.firstnames ) ];
    SortParallel( CTblLib.Data.sortedfirstnames, CTblLib.Data.position );
    CTblLib.Data.FusionInfo:= List( [ 1 .. Length( LIBLIST.firstnames ) ],
                                   i -> [] );
    for i in [ 1 .. Length( LIBLIST.firstnames ) ] do
      nam:= LIBLIST.firstnames[i];
      for src in LIBLIST.fusionsource[i] do
        Add( CTblLib.Data.FusionInfo[ PositionSet(
               CTblLib.Data.sortedfirstnames, src ) ], nam );
      od;
    od;
    end,
  create:= function( attr, id )
    local pos;

    pos:= PositionSet( CTblLib.Data.sortedfirstnames, id );
    if pos <> fail then
      return CTblLib.Data.FusionInfo[ pos ];
    fi;
    return [];
    end,
  viewValue:= x -> rec( rows:= x, align:= "tl" ),
  viewLabel:= "fusions G ->",
  sortParameters:= [ "add counter on categorizing", "yes",
                     "split rows on categorizing", "yes" ],
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.basic
##
Add( CTblLib.Data.attributesRelevantForGroupInfoForCharacterTable, "basic" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "basic",
  description:= "mapping between CTblLib and GAP's basic groups libraries",
  type:= "pairs",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "grp_basic.json" ),
  dataDefault:= [],
  isSorted:= true,
  reverseEval:= function( attr, info )
    local data, entry;

    if info[1] in [ "AlternatingGroup", "CyclicGroup",
                    "DihedralGroup", "MathieuGroup", "POmega", "PSL",
                    "PSU", "PSp", "ReeGroup", "SuzukiGroup",
                    "SymmetricGroup" ] then
      if not IsBound( attr.data )  then
        Read( attr.datafile );
      fi;
      for data in [ attr.data.automatic, attr.data.nonautomatic ] do
        for entry in data do
          if info in entry[2] then
            return entry[1];
          fi;
        od;
      od;
    fi;
    return fail;
    end,
  neededAttributes:= [ "IsDuplicateTable", "IdentifiersOfDuplicateTables" ],
  prepareAttributeComputation:=  function( attr )
    local i;

    CTblLib.Data.invposition:= InverseMap( LIBLIST.position );
    for i in [ 1 .. Length( CTblLib.Data.invposition ) ] do
      if IsInt( CTblLib.Data.invposition[i] ) then
        CTblLib.Data.invposition[i]:= [ CTblLib.Data.invposition[i] ];
      fi;
    od;
    CTblLib.Data.attrvalues_basic:= rec();
    CTblLib.Data.prepare( attr );
    end,
  cleanupAfterAttributeComputation:= function( attr )
    Unbind( CTblLib.Data.invposition );
    Unbind( CTblLib.Data.attrvalues_basic );
    CTblLib.Data.cleanup( attr );
    end,
  create:= function( attr, id )
    local main, mainid, result, tbl, type, r, nsg, simp, cen, ext, explicit,
          entry, name;

    # For duplicate tables, take (and cache) the result for the main table.
    main:= attr.idenumerator.attributes.IdentifierOfMainTable;
    mainid:= main.attributeValue( main, id );
    if mainid <> fail then
      id:= mainid;
    fi;
    if IsBound( CTblLib.Data.attrvalues_basic ) and
       IsBound( CTblLib.Data.attrvalues_basic.( id ) ) then
      return CTblLib.Data.attrvalues_basic.( id );
    fi;

    # Now we know that we have to work.
    result:= [];
    tbl:= CharacterTable( id );
    if IsSimpleCharacterTable( tbl ) then
      type:= IsomorphismTypeInfoFiniteSimpleGroup( tbl );
      if   type.series = "A" then
        Add( result, [ "AlternatingGroup", [ type.parameter ] ] );
        if   type.parameter = 5 then
          Add( result, [ "PSL", [ 2, 4 ] ] );
          Add( result, [ "PSL", [ 2, 5 ] ] );
        elif type.parameter = 6 then
          Add( result, [ "PSL", [ 2, 9 ] ] );
        elif type.parameter = 8 then
          Add( result, [ "PSL", [ 4, 2 ] ] );
        fi;
      elif type.series = "B" then
        Add( result,
             [ "POmega", [ 2 * type.parameter[1] + 1, type.parameter[2] ] ] );
        if IsEvenInt( type.parameter[2] ) or type.parameter[1] = 2 then
          Add( result,
               [ "PSp", [ 2 * type.parameter[1], type.parameter[2] ] ] );
        fi;
        if type.parameter = [ 2, 3 ] then
          Add( result, [ "PSU", [ 4, 2 ] ] );
          Add( result, [ "POmega", [ -1, 6, 2 ] ] );
        fi;
      elif type.series = "C" then
        Add( result,
             [ "PSp", [ 2 * type.parameter[1], type.parameter[2] ] ] );
        if type.parameter[1] = 2 then
          Add( result, [ "POmega", [ 5, type.parameter[2] ] ] );
        fi;
      elif type.series = "D" then
        Add( result,
             [ "POmega", [ 1, 2 * type.parameter[1], type.parameter[2] ] ] );
      elif type.series = "L" then
        Add( result, [ "PSL", type.parameter ] );
        if   type.parameter[1] = 2 then
          Add( result, [ "POmega", [ 3, type.parameter[2] ] ] );
          Add( result, [ "PSp", type.parameter ] );
          Add( result, [ "PSU", type.parameter ] );
          r:= RootInt( type.parameter[2] );
          if r^2 = type.parameter[2] then
            Add( result, [ "POmega", [ -1, 4, r ] ] );
          fi;
          if type.parameter[2] = 7 then
            Add( result, [ "PSL", [ 3, 2 ] ] );
          fi;
        elif type.parameter[1] = 4 then
          Add( result, [ "POmega", [ 1, 6, type.parameter[2] ] ] );
        fi;
      elif type.series = "G" then
        Add( result, [ "SimpleGroup", [ "G", 2, type.parameter ] ] );
      elif type.series = "2A" then
        Add( result,
             [ "PSU", [ type.parameter[1] + 1, type.parameter[2] ] ] );
        if type.parameter[1] = 3 then
          Add( result, [ "POmega", [ -1, 6, type.parameter[2] ] ] );
        fi;
      elif type.series = "2B" then
        Add( result, [ "SuzukiGroup", [ type.parameter ] ] );
      elif type.series = "2D" then
        Add( result,
             [ "POmega", [ -1, 2 * type.parameter[1], type.parameter[2] ] ] );
      elif type.series = "2G" then
        Add( result, [ "ReeGroup", [ type.parameter ] ] );
      elif type.series = "3D" then
        Add( result, [ "SimpleGroup", [ "3D", 4, type.parameter ] ] );
      elif type.series = "Spor" then
        if   type.name = "M(11)" then
          Add( result, [ "MathieuGroup", [ 11 ] ] );
        elif type.name = "M(12)" then
          Add( result, [ "MathieuGroup", [ 12 ] ] );
        elif type.name = "M(22)" then
          Add( result, [ "MathieuGroup", [ 22 ] ] );
        elif type.name = "M(23)" then
          Add( result, [ "MathieuGroup", [ 23 ] ] );
        elif type.name = "M(24)" then
          Add( result, [ "MathieuGroup", [ 24 ] ] );
        fi;
#T more series of simple groups?
      fi;
    elif IsPerfectCharacterTable( tbl ) then
      # Detect nonsolvable groups SL(2,q), for odd q.
      cen:= ClassPositionsOfCentre( tbl );
      if Size( cen ) = 2 then
        simp:= tbl / cen;
        if IsSimpleCharacterTable( simp ) then
          type:= IsomorphismTypeInfoFiniteSimpleGroup( simp );
          if type.series = "L" and type.parameter[1] = 2 then
            Add( result, [ "SL", type.parameter ] );
          fi;
        fi;
      fi;
#T detect DoubleCoverOfAlternatingGroup:
#T modulo central inv., a simple group of type An, except in small cases
    fi;

    # Detect nonsolvable symmetric groups
#T Can this be done without the character table of the socle?
    # and automorphism groups of simple groups.
    if IsAlmostSimpleCharacterTable( tbl ) and
       not IsSimpleCharacterTable( tbl ) then
      # There is a unique minimal normal subgroup.
      nsg:= ClassPositionsOfMinimalNormalSubgroups( tbl )[1];
      simp:= CTblLib.Data.CharacterTablesOfNormalSubgroupWithGivenImage(
                 tbl, nsg );
      if not IsEmpty( simp ) then
        simp:= simp[1][1];
        type:= IsomorphismTypeInfoFiniteSimpleGroup( simp );
        if type.series = "A" then
          if type.parameter <> 6 then
            Add( result, [ "SymmetricGroup", [ type.parameter ] ] );
          elif not 8 in OrdersClassRepresentatives( tbl ) then
            # 'A6.2_1'
            Add( result, [ "SymmetricGroup", [ type.parameter ] ] );
            Add( result, [ "PSp", [ 4, 2 ] ] );
            Add( result, [ "POmega", [ 5, 2 ] ] );
          elif not 10 in OrdersClassRepresentatives( tbl ) then
            # 'A6.2_3'
            Add( result, [ "MathieuGroup", [ 10 ] ] );
          elif NrConjugacyClasses( tbl ) = 11 then
            # 'A6.2_2'
            Add( result, [ "PGL", [ 2, 9 ] ] );
            Add( result, [ "PGU", [ 2, 9 ] ] );
          else
            # 'A6.2^2'
            Add( result, [ "PGammaL", [ 2, 9 ] ] );
          fi;
        fi;

        if HasExtensionInfoCharacterTable( simp ) then
          ext:= ExtensionInfoCharacterTable( simp );
          if ext[2] <> "" then
            ext:= CharacterTable( Concatenation( Identifier( simp ),
                                      ".", ext[2] ) );
            if ext <> fail and Size( ext ) = Size( tbl ) then
              Add( result, [ "Aut", [ Identifier( simp ) ] ] );
            fi;
          fi;
        fi;

        # Detect some projective and semilinear groups.
        # (Note that we give no abstract description.)
        explicit:= [
          ["L2(16).4",[["PGammaL",[2,16]],["SigmaL",[2,16]],["PSigmaL",[2,16]]]],
          ["L2(25).2^2",[["PGammaL",[2,25]]]],
          ["L2(25).2_1",[["PGL",[2,25]]]],
          ["L2(25).2_2",[["PSigmaL",[2,25]]]],
          ["L2(27).2",[["PGL",[2,27]]]],
          ["L2(27).3",[["PSigmaL",[2,27]]]],
          ["L2(27).6",[["PGammaL",[2,27]]]],
          ["L2(32).5",[["PGammaL",[2,32]],["SigmaL",[2,32]],["PSigmaL",[2,32]]]],
          ["L2(49).2^2",[["PGammaL",[2,49]]]],
          ["L2(49).2_1",[["PGL",[2,49]]]],
          ["L2(49).2_2",[["PSigmaL",[2,49]]]],
          ["L2(64).6",[["PGammaL",[2,64]]]],
          ["L2(81).(2x4)",[["PGammaL",[2,81]]]],
          ["L2(81).2_2",[["PGL",[2,81]]]],
          ["L2(81).4_1",[["PSigmaL",[2,81]]]],
          ["L3(7).3",[["PGL",[3,7]]]],
          ["L3(8).3",[["PGammaL",[3,8]],["SigmaL",[3,8]],["PSigmaL",[3,8]]]],
          ["L3(9).2_2",[["PGammaL",[3,9]],["SigmaL",[3,9]],["PSigmaL",[3,9]]]],
          ["L4(3).2_1",[["PGL",[4,3]]]],
          ["L4(3).2_2",[["PGO",[+1,6,3]]]],
          ["L4(4).2_1",[["PSigmaL",[4,4]]]],
          ["O8+(3).2_1",[["PSO",[+1,8,3]]]],
          ["O8+(3).(2^2)_{122}",[["PGO",[+1,8,3]]]],
          ["O8-(3).2_1",[["PGO",[-1,8,3]]]],
          ["S4(5).2",[["SO",[5,5]]]],
          ["U3(11).3",[["PGU",[3,11]]]],
          ["U3(5).3",[["PGU",[3,5]]]],
          ["U3(8).3^2",[["PGammaU",[3,8]]]],
          ["U3(8).3_1",[["PSigmaU",[3,8]]]],
          ["U3(8).3_2",[["PGU",[3,8]]]],
          ["U4(2).2",[["PSO",[5,3]]]],
          ["U4(3).2_1",[["PSO",[-1,6,3]]]],
          ["U4(5).2_2",[["PGU",[4,5]]]],
        ];
        entry:= First( explicit, x -> Identifier( tbl ) = x[1] );
        if entry <> fail then
          Append( result, entry[2] );
        fi;
      fi;
    fi;

    # Detect some AGLs.
    # (Note that we give no abstract description.)
    explicit:= [
      ["S3",[["AGL",[1,3]]]],
      ["a4",[["AGL",[1,4]]]],
      ["5:4",[["AGL",[1,5]]]],
      ["7:6",[["AGL",[1,7]]]],
      ["2^3:7",[["AGL",[1,8]]]],
      ["11:10",[["AGL",[1,11]]]],
      ["13:12",[["AGL",[1,13]]]],
      ["17:16",[["AGL",[1,17]]]],
      ["19:18",[["AGL",[1,19]]]],
      ["23:22",[["AGL",[1,23]]]],
      ["frob",[["AGL",[1,29]]]],
      ["31:30",[["AGL",[1,31]]]],
      ["41:40",[["AGL",[1,41]]]],
      ["s4",[["AGL",[2,2]]]],
      ["3^2.2.S4",[["AGL",[2,3]]]],
      ["j3m4",[["AGL",[2,4]]]],
      ["5^2:4s5",[["AGL",[2,5]]]],
      ["2^6:(7xL2(8))",[["AGL",[2,8]]]],
      ["3^4:GL2(9)",[["AGL",[2,9]]]],
      ["11^2:(5x2L2(11).2)",[["AGL",[2,11]]]],
      ["2^3:sl(3,2)",[["AGL",[3,2]]]],
      ["2^4:a8",[["AGL",[4,2]]]],
      ["2^5:L5(2)",[["AGL",[5,2]]]],
      ["2^6:L6(2)",[["AGL",[6,2]]]],
      ["P1L82",[["AGL",[7,2]]]],
    ];
    entry:= First( explicit, x -> Identifier( tbl ) = x[1] );
    if entry <> fail then
      Append( result, entry[2] );
    fi;

    # Detect some solvable groups.
    if   Size( tbl ) = 12 and NrConjugacyClasses( tbl ) = 4 then
      Add( result, [ "AlternatingGroup", [ 4 ] ] );
      Add( result, [ "PSL", [ 2, 3 ] ] );
      Add( result, [ "PSU", [ 2, 3 ] ] );
      Add( result, [ "PSp", [ 2, 3 ] ] );
    elif Size( tbl ) = 20 and NrConjugacyClasses( tbl ) = 5 then
      Add( result, [ "SuzukiGroup", [ 2 ] ] );
    elif Size( tbl ) = 24 and NrConjugacyClasses( tbl ) = 5 then
      Add( result, [ "SymmetricGroup", [ 4 ] ] );
    elif Size( tbl ) = 72 and NrConjugacyClasses( tbl ) = 6 then
      Add( result, [ "MathieuGroup", [ 9 ] ] );
      Add( result, [ "PSU", [ 3, 2 ] ] );
    fi;

    if IsCyclic( tbl ) then
      Add( result, [ "CyclicGroup", [ Size( tbl ) ] ] );
      if Size( tbl ) = 2 then
        Add( result, [ "SymmetricGroup", [ 2 ] ] );
      elif Size( tbl ) = 3 then
        Add( result, [ "AlternatingGroup", [ 3 ] ] );
      fi;
    fi;

    if IsDihedralCharacterTable( tbl ) then
      Add( result, [ "DihedralGroup", [ Size( tbl ) ] ] );
      if Size( tbl ) = 6 then
        Add( result, [ "SymmetricGroup", [ 3 ] ] );
        Add( result, [ "PSL", [ 2, 2 ] ] );
        Add( result, [ "PSU", [ 2, 2 ] ] );
        Add( result, [ "PSp", [ 2, 2 ] ] );
      fi;
    fi;

    if IsEmpty( result ) then
      result:= attr.dataDefault;
    else
      result:= Set( result );
    fi;

    # Cache the result.
    CTblLib.Data.attrvalues_basic.( id ):= result;

    return result;
    end,
  string:= entry -> CTblLib.AttrDataString( entry, [], false ),
  check:= ReturnTrue,
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.perf
##
Add( CTblLib.Data.attributesRelevantForGroupInfoForCharacterTable, "perf" );

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "perf",
  description:= "mapping between CTblLib and  GAP's library of perfect groups",
  type:= "pairs",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "grp_perf.json" ),
  dataDefault:= [],
  isSorted:= true,
  eval:= function( attr, l )
           return List( l, val -> [ "PerfectGroup", val ] );
         end,
  reverseEval:= function( attr, info )
    local data, entry;

    if info[1] = "PerfectGroup" then
      if not IsBound( attr.data )  then
        Read( attr.datafile );
      fi;
      for data in [ attr.data.automatic, attr.data.nonautomatic ] do
        for entry in data do
          if info[2] in entry[2] then
            return entry[1];
          fi;
        od;
      od;
    fi;
    return fail;
    end,
  neededAttributes:= [ "IsDuplicateTable", "IdentifiersOfDuplicateTables" ],
  prepareAttributeComputation:= function( attr )
    local i;

    CTblLib.Data.invposition:= InverseMap( LIBLIST.position );
    for i in [ 1 .. Length( CTblLib.Data.invposition ) ] do
      if IsInt( CTblLib.Data.invposition[i] ) then
        CTblLib.Data.invposition[i]:= [ CTblLib.Data.invposition[i] ];
      fi;
    od;
    CTblLib.Data.attrvalues_perf:= rec();
    end,
  cleanupAfterAttributeComputation:= function( attr )
    Unbind( CTblLib.Data.invposition );
    Unbind( CTblLib.Data.attrvalues_perf );
    end,
  create:= function( attr, id )
    local main, mainid, tbl, result, n, nr, pos, type, i, G;

    # For duplicate tables, take (and cache) the result for the main table.
    main:= attr.idenumerator.attributes.IdentifierOfMainTable;
    mainid:= main.attributeValue( main, id );
    if mainid <> fail then
      id:= mainid;
    fi;
    if IsBound( CTblLib.Data.attrvalues_perf ) and
       IsBound( CTblLib.Data.attrvalues_perf.( id ) ) then
      return CTblLib.Data.attrvalues_perf.( id );
    fi;

    # Now we know that we have to work.
    tbl:= CharacterTable( id );
    result:= [];
    if IsPerfectCharacterTable( tbl ) then
      n:= Size( tbl );
      nr:= NumberPerfectLibraryGroups( n );
      if IsPosInt( nr ) then
        if   NumberPerfectGroups( n ) = 1 then
          # If there is only one perfect group of this order
          # (and we believe this) then we assign the table name to it.
          pos:= 1;
        elif IsSimpleCharacterTable( tbl ) then
          # If the table is simple then compare isomorphism types.
          type:= IsomorphismTypeInfoFiniteSimpleGroup( tbl );
          for i in [ 1 .. nr ] do
            G:= PerfectGroup( IsPermGroup, n, i );
            if IsSimpleGroup( G ) and
               IsomorphismTypeInfoFiniteSimpleGroup( G ).name = type.name then
              pos:= i;
              break;
            fi;
          od;
        else
          # Do the hard test.
#T perhaps compare the lattice of normal subgroups first?
          for i in [ 1 .. nr ] do
            G:= PerfectGroup( IsPermGroup, n, i );
            if NrConjugacyClasses( G ) = NrConjugacyClasses( tbl ) and
               IsRecord( TransformingPermutationsCharacterTables(
                         CharacterTable( G ), tbl ) ) then
              pos:= i;
              break;
            fi;
          od;
        fi;
        Add( result, [ n, pos ] );
      fi;
    fi;

    if IsEmpty( result ) then
      result:= attr.dataDefault;
    else
      result:= Set( result );
    fi;

    # Cache the result.
    CTblLib.Data.attrvalues_perf.( id ):= result;

    return result;
    end,
  string:= entry -> CTblLib.AttrDataString( entry, [], false ),
  check:= ReturnTrue,
  ) );


#############################################################################
##
#V  CTblLib.Data.IdEnumerator.attributes.prim
##
Add( CTblLib.Data.attributesRelevantForGroupInfoForCharacterTable, "prim" );

# The library of primitive groups may be not (yet) loaded.
if IsBound( PRIMRANGE ) then
  CTblLib.MAXPRIMRANGE:= PRIMRANGE[ Length( PRIMRANGE ) ];
else
  CTblLib.MAXPRIMRANGE:= 2499;
fi;

DatabaseAttributeAddX( CTblLib.Data.IdEnumerator, rec(
  identifier:= "prim",
  description:= "mapping between CTblLib and GAP's library of prim. groups",
  type:= "pairs",
  datafile:= Filename( DirectoriesPackageLibrary( "ctbllib", "data" )[1],
                       "grp_prim.json" ),
  dataDefault:= [ "no information", [] ],
  isSorted:= true,
  eval:= function( attr, l )
      return List( l[2], val -> [ "PrimitiveGroup", val ] );
    end,
  reverseEval:= function( attr, info )
    local data, entry;

    if info[1] = "PrimitiveGroup" then
      if not IsBound( attr.data )  then
        Read( attr.datafile );
      fi;
      for data in [ attr.data.automatic, attr.data.nonautomatic ] do
        for entry in data do
          if info[2] in entry[2][2] then
            return entry[1];
          fi;
        od;
      od;
    fi;
    return fail;
    end,
  neededAttributes:= [ "IsDuplicateTable", "IdentifiersOfDuplicateTables" ],
  prepareAttributeComputation:= function( attr )
    local result, i;

    CTblLib.Data.prepare( attr );

    CTblLib.Data.invposition:= InverseMap( LIBLIST.position );
    for i in [ 1 .. Length( CTblLib.Data.invposition ) ] do
      if IsInt( CTblLib.Data.invposition[i] ) then
        CTblLib.Data.invposition[i]:= [ CTblLib.Data.invposition[i] ];
      fi;
    od;
    CTblLib.Data.attrvalues_prim:= rec();
    end,

  cleanupAfterAttributeComputation:= function( attr )
    CTblLib.Data.cleanup( attr );
    Unbind( CTblLib.Data.invposition );
    Unbind( CTblLib.Data.attrvalues_prim );
    end,

  create:= function( attr, id )
    local main, mainid, tbl, nsg, nsgsizes, n, solvmin, deg, cand, result,
          type, G, gcd, dupl, names, name, simp, outinfo, info, facttbl, der,
          socle, soclefact, tbls, tblpos, cand2, try, mustbesplit, fuscand,
          s, pos;

    # For duplicate tables, take (and cache) the result for the main table.
    main:= attr.idenumerator.attributes.IdentifierOfMainTable;
    mainid:= main.attributeValue( main, id );
    if mainid <> fail then
      id:= mainid;
    fi;
    if IsBound( CTblLib.Data.attrvalues_prim ) and
       IsBound( CTblLib.Data.attrvalues_prim.( id ) ) then
      return CTblLib.Data.attrvalues_prim.( id );
    fi;

    # Now we know that we have to work.
    tbl:= CharacterTable( id );

    # Let $G$ be a primitive permutation group of degree $n$
    # that contains a *solvable* minimal normal subgroup $N$.
    # Then we have $|N| = n$, $|G|$ divides $|N|!$, $G$ is centerless
    # (Any point stabilizer $M$ is maximal in $G$, and $M \cap Z(G)$ is
    # contained in $Core_G(M)$, which is trivial; if $Z(G)$ is nontrivial
    # then $G = \langle M, Z(G) \rangle \cong M \times Z(G)$, contradiction.),
    # $N$ is the unique minimal normal subgroup of $G$,
    # and $G$ is a split extension of $N$.
    # (Proof:
    # Let $M$ be a core-free maximal subgroup of index $n$ in $G$.
    # Then $M \cap N$ is invariant under $M$ and (since $N$ is abelian)
    # under $N$, and because $N$ is not contained in $M$, we have $G = M N$,
    # so $M \cap N$ is normal in $G$ and hence $|M \cap N| = 1$.
    # This implies $n = [G:M] = |N|$, and clearly $G$ embeds into $Sym(n)$.
    # If $G$ would contain a nontrivial central subgroup $Z$ of prime order
    # then $|M \cap Z| = 1$ holds, which implies that $M$ is normal in $G$,
    # a contradiction.
    # Obviously $G$ cannot contain another *solvable* minimal normal
    # subgroup.  Suppose there is a nonsolvable minimal normal subgroup $T$,
    # say.  Then $T$ commutes with $N$, so $T \cap M$ is normal in $M$ and
    # commutes with $N$, hence is normal in $G$ and thus trivial --but this
    # implies that $G$ is a split extension of $T$ with $M$, hence the order
    # of $T$ is the prime power $n$, a contradiction.)

    # So we can immediately exclude tables with nontrivial centre,
    # as well as tables with a minimal normal subgroup $N$ of prime power
    # order that is either *larger* than the largest degree in the library
    # of primitive groups
    # or *too small* in the sense that the group order does not divide the
    # factorial of $|N|$.
    # Also note that for tables with a minimal normal subgroup $N$
    # of prime power order, the only possible degree is $|N|$,
    # and the table must admit a class fusion from the factor modulo $N$,
    # corresponding to the embedding of the point stabilizer
    # (so nonsplit extensions may be excluded using the character table).

    if 1 < Length( ClassPositionsOfCentre( tbl ) ) then
      if IsBound( CTblLib.Data.attrvalues_prim ) then
        CTblLib.Data.attrvalues_prim.( id ):= attr.dataDefault;
      fi;
      return attr.dataDefault;
    fi;
    nsg:= ClassPositionsOfMinimalNormalSubgroups( tbl );
    nsgsizes:= List( nsg, x -> Sum( SizesConjugacyClasses( tbl ){ x } ) );
    n:= Size( tbl );
    solvmin:= Filtered( nsgsizes, IsPrimePowerInt );
    if   Length( solvmin ) >= 1 and Length( nsg ) > 1 then
      # A primitive group containing a solvable minimal subgroup cannot
      # contain another minimal normal subgroup.
      if IsBound( CTblLib.Data.attrvalues_prim ) then
        CTblLib.Data.attrvalues_prim.( id ):= attr.dataDefault;
      fi;
      return attr.dataDefault;
    elif Length( solvmin ) = 1 then
      # We know the possible degree.
      deg:= solvmin[1];
      if deg > CTblLib.MAXPRIMRANGE or Factorial( deg ) mod n <> 0 then
#T the factorial value is no longer cached, thus we can do better
#T (n is much smaller than it):
#T some prime divisor of n is larger than deg,
#T or some prime power dividing n does not divide the factorial
#T (compute this condition)
        if IsBound( CTblLib.Data.attrvalues_prim ) then
          CTblLib.Data.attrvalues_prim.( id ):= attr.dataDefault;
        fi;
        return attr.dataDefault;
      fi;
      # Use only those invariants that are already stored for the groups
      # in the GAP library of primitive groups;
      # for example, do not force computing the number of conjugacy classes.
      cand:= AllPrimitiveGroups(
                 NrMovedPoints, deg,
                 Size, n,
                 IsSimple, IsSimple( tbl ),
                 IsSolvable, IsSolvable( tbl ),
                 IsPerfect, IsPerfect( tbl ) );
    else
      # Use only those invariants that are already stored for the groups
      # in the GAP library of primitive groups;
      # for example, do not force computing the number of conjugacy classes.
      cand:= AllPrimitiveGroups(
                 # avoid the annoying ``Degree restricted to ...'' message.
                 NrMovedPoints, [ 1 .. CTblLib.MAXPRIMRANGE ],
                 Size, n,
                 IsSimple, IsSimple( tbl ),
                 IsSolvable, IsSolvable( tbl ),
                 IsPerfect, IsPerfect( tbl ) );
    fi;
    if cand = [] then
      if IsBound( CTblLib.Data.attrvalues_prim ) then
        CTblLib.Data.attrvalues_prim.( id ):= attr.dataDefault;
      fi;
      return attr.dataDefault;
    fi;
    result:= [];
    if   IsSimple( tbl ) then
      # The isomorphism type of simple tables can be determined.
      # Simply assign the name to the simple group.
      type:= IsomorphismTypeInfoFiniteSimpleGroup( tbl );
      for G in cand do
        if IsomorphismTypeInfoFiniteSimpleGroup( G ).name = type.name then
          Add( result, [ NrMovedPoints( G ), PrimitiveIdentification( G ) ] );
        fi;
      od;
      result:= [ "simple group", result ];
      if IsBound( CTblLib.Data.attrvalues_prim ) then
        CTblLib.Data.attrvalues_prim.( id ):= result;
      fi;
      return result;
    elif IsPerfect( tbl ) and NumberPerfectGroups( n ) = 1 then
      # If there is a unique perfect group of this order then we are done.
      for G in cand do
        Add( result, [ NrMovedPoints( G ), PrimitiveIdentification( G ) ] );
      od;
      result:= [ "unique perfect group of its order", result ];
      if IsBound( CTblLib.Data.attrvalues_prim ) then
        CTblLib.Data.attrvalues_prim.( id ):= result;
      fi;
      return result;
    fi;

    # For any minimal normal subgroup $N$ (not necessarily abelian)
    # in a primitive group, the degree of the action divides $|N|$ because
    # $N$ acts transitively.
    # So we can exclude all candidates that do not satisfy this condition.
    gcd:= Gcd( nsgsizes );
    cand:= Filtered( cand, G -> gcd mod NrMovedPoints( G ) = 0 );

    dupl:= attr.idenumerator.attributes.IdentifiersOfDuplicateTables;
    names:= Concatenation( [ id ], dupl.attributeValue( dupl, id ) );

    if IsAlmostSimpleCharacterTable( tbl ) then
      for name in names do
        # Determine the isomorphism type of the socle.
        # If the character table library provides enough information about
        # the automorphic extensions of this group then try to determine the
        # isomorphism type of the almost simple group.
        tbl:= CharacterTable( name );
        nsg:= ClassPositionsOfMinimalNormalSubgroups( tbl );
        simp:= CTblLib.Data.CharacterTablesOfNormalSubgroupWithGivenImage(
                   tbl, nsg[1] );
        if not IsEmpty( simp )
           and HasExtensionInfoCharacterTable( simp[1][1] ) then
          simp:= simp[1][1];
          type:= IsomorphismTypeInfoFiniteSimpleGroup( simp );
          # The following list contains pairs `[ <nam>, <indices> ]'
          # where <nam> runs over the suffixes of the names of
          # full automorphism groups of simple groups that occur in the
          # character table library, and <indices> is a list of orders of
          # socle factors for which the isomorphism type of the extension
          # is uniquely determined by these orders.
          outinfo:= [
                      [ "2",      [ 2 ] ],
                      [ "3",      [ 3 ] ],
                      [ "4",      [ 2, 4 ] ],
                      [ "2^2",    [ 4 ] ],
                      [ "5",      [ 5 ] ],
                      [ "6",      [ 2, 3, 6 ] ],
                      [ "3.2",    [ 2, 3, 6 ] ],
                      [ "(2x4)",  [ 8 ] ],
                      [ "D8",     [ 8 ] ],
                      [ "D12",    [ 3, 4, 12 ] ],
                      [ "(2xD8)", [ 16 ] ],
                      [ "(S3x3)", [ 2, 9, 18 ] ],
                      [ "5:4",    [ 2, 4, 5, 10, 20 ] ],
                      [ "S4",     [ 3, 6, 8, 12, 24 ] ],
                    ];
          info:= ExtensionInfoCharacterTable( simp )[2];
          info:= First( outinfo, x -> x[1] = info );
          facttbl:= tbl / nsg[1];
          if   info = fail then
            Print( "#E problem: is the table of ", id,
                   " really almost simple?\n ");
          elif    Size( tbl ) / Size( simp ) in info[2]
               or ( info[1] = "(2x4)" and Size( tbl ) / Size( simp ) = 4
                                      and not IsCyclic( facttbl ) )
               or ( info[1] = "D8" and Size( tbl ) / Size( simp ) = 4
                                   and IsCyclic( facttbl ) )
               or ( info[1] = "D12" and Size( tbl ) / Size( simp ) = 6
                                    and IsCyclic( facttbl ) )
               or ( info[1] = "(S3x3)" and Size( tbl ) / Size( simp ) = 6 )
               or ( info[1] = "S4" and Size( tbl ) / Size( simp ) = 4
                                   and IsCyclic( facttbl ) ) then
            # We can identify the group.
            for G in cand do
              if IsAlmostSimpleGroup( G ) then
                der:= DerivedSeriesOfGroup( G );
                socle:= der[ Length( der ) ];
                soclefact:= G / socle;
                if type.name = IsomorphismTypeInfoFiniteSimpleGroup( socle ).name and
                   ( Size( tbl ) / Size( simp ) in info[2] or
                     ( info[1] = "(2x4)" and not IsCyclic( soclefact ) ) or
                     ( info[1] = "D8" and IsCyclic( soclefact ) ) or
                     ( info[1] = "D12" and IsCyclic( soclefact ) ) or
                     ( info[1] = "(S3x3)" and IsCyclic( soclefact )
                                          and IsCyclic( facttbl ) ) or
                     ( info[1] = "(S3x3)" and not IsCyclic( soclefact )
                                          and not IsCyclic( facttbl ) ) or
                     ( info[1] = "S4" and IsCyclic( soclefact ) ) ) then
                  Add( result, [ NrMovedPoints( G ),
                                 PrimitiveIdentification( G ) ] );
                fi;
              fi;
            od;

            result:= [ Concatenation( "unique almost simple group with the ",
                                      "given socle and socle factor" ),
                       result ];
            if IsBound( CTblLib.Data.attrvalues_prim ) then
              CTblLib.Data.attrvalues_prim.( id ):= result;
            fi;
            return result;
          else
            # Try to identify the extension of the socle by excluding
            # all but one of the possibilities
            # if we have all tables for these possibilities.
            outinfo:= [
                        [ "2^2",    [ [ 2, [ "2_1", "2_2", "2_3" ] ] ] ],
                        [ "(2x4)",  [ [ 2, [ "2_1", "2_2", "2_3" ] ],
                                      [ 4, [ "2^2", "4_1", "4_2" ] ] ] ],
                        [ "D8",     [ [ 2, [ "2_1", "2_2", "2_3" ] ],
                                      [ 4, [ "4", "(2^2)_{122}",
                                             "(2^2)_{133}" ] ] ] ],
                        [ "D12",    [ [ 2, [ "2_1", "2_2", "2_3" ] ],
                                      [ 6, [ "6", "3.2_2", "3.2_3" ] ] ] ],
                        [ "(S3x3)", [ [ 3, [ "3_1", "3_2", "3_3" ] ] ] ],
                        [ "S4",     [ [ 2, [ "2_1", "2_2" ] ],
                                      [ 4, [ "4", "(2^2)_{111}",
                                             "(2^2)_{122}" ] ] ] ],
                      ];
            info:= First( outinfo, x -> x[1] = info[1] );
            if info <> fail then
              info:= First( info[2],
                            x -> x[1] = Size( tbl ) / Size( simp ) );
              if info <> fail then
                tbls:= List( info[2],
                             s -> CharacterTable( Concatenation(
                                      Identifier( simp ), ".", s ) ) );
                if ForAll( tbls, IsCharacterTable ) then
                  tblpos:= First( [ 1 .. Length( tbls ) ],
                               i -> TransformingPermutationsCharacterTables(
                                        tbl, tbls[i] ) <> fail );
                  cand2:= [];
                  for G in cand do
                    if IsAlmostSimpleGroup( G ) then
                      der:= DerivedSeriesOfGroup( G );
                      socle:= der[ Length( der ) ];
                      if type.name = IsomorphismTypeInfoFiniteSimpleGroup( socle ).name
                         then
                        try:= CTblLib.FindTableForGroup( G, tbls, tblpos );
                        if try = true then
                          Add( result, [ NrMovedPoints( G ),
                                         PrimitiveIdentification( G ) ] );
                        elif try = fail then
                          Add( cand2, G );
                        fi;
                      fi;
                    fi;
                  od;
                  if cand2 = [] then
                    result:= [ Concatenation( "almost simple group with ",
                        "the given socle and socle factor that fits" ),
                               result ];
                    if IsBound( CTblLib.Data.attrvalues_prim ) then
--> --------------------

--> maximum size reached

--> --------------------

[ zur Elbe Produktseite wechseln0.60Quellennavigators  Analyse erneut starten  ]