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

Quelle  test.g   Sprache: unbekannt

 
Spracherkennung für: .g vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

#############################################################################
##
#W  test.g               GAP 4 package CTblLib                  Thomas Breuer
##
##  This file contains functions to test the data available in the
##  GAP Character Table Library.
##


#############################################################################
##
##  <#GAPDoc Label="tests">
##  The fact that the &GAP; Character Table Library is designed as an
##  open database
##  (see Chapter <Ref Chap="ch:introduction"/>)
##  makes it especially desirable to have consistency checks available
##  which can be run automatically whenever new data get added.
##  <P/>
##  The file <F>tst/testall.g</F> of the package
##  contains <Ref Func="Test" BookName="ref"/> statements
##  for executing a collection of such sanity checks;
##  one can run them by calling
##  <C>ReadPackage( "CTblLib", "tst/testall.g" )</C>.
##  If no problem occurs then &GAP; prints only lines starting with one of
##  the following.
##  <P/>
##  <Log><![CDATA[
##  + Input file:
##  + GAP4stones:
##  ]]></Log>
##  <P/>
##  The examples in the package manual form a part of the tests,
##  they are collected in the file <F>tst/docxpl.tst</F> of the package.
##  <P/>
##  The following tests concern only <E>ordinary</E> character tables.
##  In all cases,
##  let <M>tbl</M> be the ordinary character table of a group <M>G</M>, say.
##  The return value is <K>false</K> if an error occurred,
##  and <K>true</K> otherwise.
##  <P/>
##  <List>
##  <#Include Label="test:CTblLib.Test.InfoText">
##  <#Include Label="test:CTblLib.Test.RelativeNames">
##  <#Include Label="test:CTblLib.Test.FindRelativeNames">
##  <#Include Label="test:CTblLib.Test.PowerMaps">
##  <#Include Label="test:CTblLib.Test.TableAutomorphisms">
##  <#Include Label="test:CTblLib.Test.CompatibleFactorFusions">
##  <#Include Label="test:CTblLib.Test.FactorsModPCore">
##  <#Include Label="test:CTblLib.Test.Fusions">
##  <#Include Label="test:CTblLib.Test.Maxes">
##  <#Include Label="test:CTblLib.Test.ClassParameters">
##  <#Include Label="test:CTblLib.Test.TablesOfSymmetricGroup">
##  <#Include Label="test:CTblLib.Test.Constructions">
##  <#Include Label="test:CTblLib.Test.ExtensionInfo">
##  <#Include Label="test:CTblLib.Test.GroupForGroupInfo">
##  </List>
##  <P/>
##  The following tests concern only <E>modular</E> character tables.
##  In all cases,
##  let <M>modtbl</M> be a Brauer character table of a group <M>G</M>, say.
##  <P/>
##  <List>
##  <#Include Label="test:CTblLib.Test.BlocksInfo">
##  <#Include Label="test:CTblLib.Test.TensorDecomposition">
##  <#Include Label="test:CTblLib.Test.Indicators">
##  <#Include Label="test:CTblLib.Test.FactorBlocks">
##  </List>
##  <#/GAPDoc>
##


#############################################################################
##
##  1. General tools for checking character tables
##

CTblLib.BlanklessString:= function( obj, ncols )
    local result, stream;

    result:= "";
    stream:= OutputTextString( result, true );
    SetPrintFormattingStatus( stream, true );
    BlanklessPrintTo( stream, obj, ncols, 0, false );
    CloseStream( stream );
    return result;
end;

CTblLib.Test.BracketsString:= function( string )
    local pos, open, i, partner;

    pos:= 1;
    open:= [];
    for i in [ 1 .. Length( string ) ] do
      if string[i] in "({[" then
        Add( open, string[i] );
      elif string[i] in ")}]" then
        if Length( open ) = 0 then
          return false;
        fi;
        partner:= open[ Length( open ) ];
        if ( string[i] = ')' and partner = '(' ) or
           ( string[i] = '}' and partner = '{' ) or
           ( string[i] = ']' and partner = '[' ) then
          Unbind( open[ Length( open ) ] );
        else
          return false;
        fi;
      fi;
    od;
    return IsEmpty( open );
end;


#############################################################################
##
#F  CTblLib.PrintTestLog( <type>, <functionname>, <tblname>, <texts1>,
#F                       <texts2>, ... )
#F  CTblLib.PrintTestLog( <type>, <functionname>, <tblname>, <textlist> )
##
##  This function is used in the test functions in this file.
##  <type> should be one of the strings "E", "I";
##
CTblLib.PrintTestLog:= function( arg )
    local type, functionname, info, pos, tblname, texts, libinfo, text, entry;

    type:= arg[1];         # either "E" or "I"
    functionname:= arg[2];
    info:= arg[3];
    pos:= PositionSublist( info, " -> " );
    if pos = fail then
      tblname:= info;
    else
      tblname:= info{ [ 1 .. pos-1 ] };
    fi;
    texts:= arg{ [ 4 .. Length( arg  ) ] };
    if Length( texts ) = 1 and IsList( texts[1] )
                           and not IsString( texts[1] ) then
      texts:= texts[1];
    fi;
    libinfo:= LibInfoCharacterTable( tblname );
    if libinfo = fail then
      libinfo:= "no library file";
    else
      libinfo:= libinfo.fileName;
    fi;
    Print( "#", type, "  ", functionname, ":  (at time ", Runtime(), ")\n",
           "#", type, "  for " );
    if tblname = info then
      Print( "table ", tblname, " (in ", libinfo, ")\n" );
    else
      Print( info, " (in ", libinfo, ")\n" );
    fi;
    for text in texts do
      Print( "#", type, "  " );
      if IsString( text ) or not IsList( text ) then
        Print( text );
      else
        for entry in text do
          Print( entry );
        od;
      fi;
      Print( "\n" );
    od;
end;


#############################################################################
##
#F  CTblLib.AdmissibleNames( <tblname> )
##
CTblLib.AdmissibleNames:= function( tblname )
    local pos;

    pos:= Position( LIBLIST.allnames, LowercaseString( tblname ) );
    if pos = fail then
      # The table does not belong to the library.
      return [ tblname ];
#T not clean but needed in the functions below,
#T in order to deal with new tables before adding them?
    else
      pos:= LIBLIST.position[ pos ];
      return LIBLIST.allnames{ Filtered( [ 1 .. Length( LIBLIST.position ) ],
                                         i -> LIBLIST.position[i] = pos ) };
    fi;
end;


#############################################################################
##
#F  CTblLib.Test.RelativeNames( <tbl>[, <tblname>] )
##
##  <#GAPDoc Label="test:CTblLib.Test.RelativeNames">
##  <Mark><C>CTblLib.Test.RelativeNames( </C><M>tbl</M><C>[, </C><M>tblname</M><C>] )</C></Mark>
##  <Item>
##    checks some properties of those admissible names for <M>tbl</M>
##    that refer to a related group <M>H</M>, say.
##    Let <M>name</M> be an admissible name for the character table of
##    <M>H</M>.  (In particular, <M>name</M> is not an empty string.)
##    Then the following relative names are considered.
##    <P/>
##    <List>
##    <Mark><M>name</M><C>M</C><M>n</M></Mark>
##    <Item>
##      <M>G</M> is isomorphic with the groups in the <M>n</M>-th class of
##      maximal subgroups of <M>H</M>.
##      An example is <C>"M12M1"</C> for the Mathieu group <M>M_{11}</M>.
##      We consider only cases where <M>name</M> does <E>not</E> contain
##      the letter <C>x</C>.
##      For example, <C>2xM12</C> denotes the direct product of a cyclic group
##      of order two and the Mathieu group <M>M_{12}</M>
##      but <E>not</E> a maximal subgroup of <Q><C>2x</C></Q>.
##      Similarly, <C>3x2.M22M5</C> denotes the direct product of a cyclic
##      group of order three and a group in the fifth class of maximal
##      subgroups of <M>2.M_{22}</M>
##      but <E>not</E> a maximal subgroup of <Q><C>3x2.M22</C></Q>.
##    </Item>
##    <Mark><M>name</M><C>N</C><M>p</M></Mark>
##    <Item>
##      <M>G</M> is isomorphic with the normalizers of the
##      Sylow <M>p</M>-subgroups of <M>H</M>.
##      An example is <C>"M24N2"</C> for the (self-normalizing)
##      Sylow <M>2</M>-subgroup in the Mathieu group <M>M_{24}</M>.
##    </Item>
##    <Mark><M>name</M><C>N</C><M>cnam</M></Mark>
##    <Item>
##      <M>G</M> is isomorphic with the normalizers of the
##      cyclic subgroups generated by the elements in the class with the name
##      <M>cnam</M> of <M>H</M>.
##      An example is <C>"O7(3)N3A"</C> for the normalizer of an element
##      in the class <C>3A</C> of the simple group <M>O_7(3)</M>.
##    </Item>
##    <Mark><M>name</M><C>C</C><M>cnam</M></Mark>
##    <Item>
##      <M>G</M> is isomorphic with the groups in the centralizers of the
##      elements in the class with the name <M>cnam</M> of <M>H</M>.
##      An example is <C>"M24C2A"</C> for the centralizer of an element in the
##      class <C>2A</C> in the Mathieu group <M>M_{24}</M>.
##    </Item>
##    </List>
##    <P/>
##    In these cases, <C>CTblLib.Test.RelativeNames</C> checks whether a
##    library table with the admissible name <M>name</M> exists and a class
##    fusion to <M>tbl</M> is stored on this table.
##    <P/>
##    In the case of Sylow <M>p</M>-normalizers,
##    it is also checked whether <M>G</M> contains a normal
##    Sylow <M>p</M>-subgroup of the same order as the
##    Sylow <M>p</M>-subgroups in <M>H</M>.
##    If the normal Sylow <M>p</M>-subgroup of <M>G</M> is cyclic then it is
##    also checked whether <M>G</M> is the full Sylow <M>p</M>-normalizer in
##    <M>H</M>.
##    (In general this information cannot be read off
##    from the character table of <M>H</M>).
##    <P/>
##    In the case of normalizers (centralizers) of cyclic subgroups,
##    it is also checked whether <M>H</M> really normalizes (centralizes) a
##    subgroup of the given order,
##    and whether the class fusion from <M>tbl</M> to the table of <M>H</M>
##    is compatible with the relative name.
##    <P/>
##    If the optional argument <M>tblname</M> is given then only this name
##    is tested.
##    If there is only one argument then all admissible names for <M>tbl</M>
##    are tested.
##  </Item>
##  <#/GAPDoc>
##
CTblLib.Test.RelativeNames:= function( arg )
    local result, tbl, name, tocheck, tblname, parse, filt, p, size, classes,
          supertbl, orders, centralizers, cand, cen, fus, classname, cname,
          orbits, supname;

    result:= true;
    tbl:= arg[1];
    if Length( arg ) = 1 then
      for name in CTblLib.AdmissibleNames( Identifier( tbl ) ) do
        result:= CTblLib.Test.RelativeNames( tbl, name ) and result;
      od;
    else
      tocheck:= [];
      tblname:= Identifier( tbl );
      name:= LowercaseString( arg[2] );

      # The names of ordinary tables must not involve the substring `mod'.
      if PositionSublist( tblname, "mod" ) <> fail then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
            [ [ "`", name, "' contains substring `mod'" ] ] );
        result:= false;
      fi;

      # Brackets in the name must occur in pairs.
      if not CTblLib.Test.BracketsString( name ) then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
            [ [ "inconsistent brackets in `", name, "'" ] ] );
        result:= false;
      fi;

      # Check names of the form <grpname>M<n>.
      # (We are not interested in names such as "3xM12" or "3x2.M22M5".)
      parse:= PParseBackwards( name, [ IsChar, "m", IsDigitChar ] );
      if parse <> fail and parse[3] <> 0 and not 'x' in parse[1] then
        Add( tocheck, parse[1] );
      fi;

      # Check names of the form <grpname>N<p>.
      parse:= PParseBackwards( name, [ IsChar, "n", IsDigitChar ] );
      if parse <> fail and parse[3] <> 0 then
        p:= parse[3];
        if not IsPrimeInt( p ) then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
              [ [ "`", p, "' is not a prime" ] ] );
          result:= false;
        else
          Add( tocheck, parse[1] );
          # Check whether the Sylow `p' subgroup is normal.
          size:= p ^ Number( Factors( Size( tbl ) ), x -> x = p );
          classes:= SizesConjugacyClasses( tbl );
          if ForAll( ClassPositionsOfNormalSubgroups( tbl ),
                     l -> Sum( classes{ l } ) <> size ) then
            CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                [ [ "Sylow ", p, " subgroup is not normal" ] ] );
            result:= false;
          fi;
          # Check whether the two Sylow `p' subgroups have the same order.
          supertbl:= CharacterTable( parse[1] );
          if supertbl <> fail then
            if size
               <> p ^ Number( Factors( Size( supertbl ) ), x -> x = p ) then
              CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                  [ [ "the Sylow ", p, " subgroup of `", parse[1],
                      "' has different order" ] ] );
              result:= false;
            fi;

            # If the Sylow `p' subgroup is cyclic then check the order of the
            # normalizer.
            orders:= OrdersClassRepresentatives( supertbl );
            if size in orders and
               Size( tbl )
               <> SizesCentralizers( supertbl )[ Position( orders, size ) ]
                  * Phi( size ) / Number( orders, x -> x = size ) then
              CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                  [ [ "order is not that of the Sylow ", p,
                      " normalizer of `", parse[1], "'" ] ] );
              result:= false;
            fi;
          fi;
        fi;
      fi;

      # Check names of the form <grpname>C<nam>.
      parse:= PParseBackwards( name,
                  [ IsChar, "c", IsDigitChar, IsAlphaChar ] );
      if parse <> fail and parse[3] <> 0 and not IsEmpty( parse[4] ) then
        Add( tocheck, parse[1] );
        supertbl:= CharacterTable( parse[1] );
        if supertbl <> fail then
          # Check whether a class in the big group has a centralizer order
          # equal to the order of `tbl',
          # such that the class fusion is compatible with that.
          centralizers:= SizesCentralizers( supertbl );
          orders:= OrdersClassRepresentatives( supertbl );
          cand:= Filtered( [ 1 .. Length( centralizers ) ],
                           i -> centralizers[i] = Size( tbl ) and
                                orders[i] = parse[3] );
          if IsEmpty( cand ) then
            CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                [ [ "not the centralizer of an el. of order ", parse[3],
                    " in `", parse[1], "'" ] ] );
            result:= false;
          else
            orders:= OrdersClassRepresentatives( tbl );
            cen:= Filtered( ClassPositionsOfCentre( tbl ),
                            i -> orders[i] = parse[3] );
            fus:= GetFusionMap( tbl, supertbl );
            if fus <> fail then
              cen:= Filtered( cen, i -> fus[i] in cand );
            fi;
            if IsEmpty( cen ) then
              CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                  [ [ "not the centralizer of an el. of order ", parse[3],
                      " in `", parse[1], "'" ] ] );
              result:= false;
            elif fus <> fail then
              classname:= LowercaseString( Concatenation(
                  String( parse[3] ), parse[4] ) );
              cname:= List( ClassNames( supertbl ){ fus{ cen } },
                            LowercaseString );
              if not classname in cname then
                CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                    [ "centralizer in `", parse[1], "' of a class in `" ],
                    [ String( cname ), "' not of `", classname, "'" ] );
                result:= false;
              fi;
            fi;
          fi;
        fi;
      fi;

      # Check names of the form <grpname>N<nam>.
      parse:= PParseBackwards( name,
                  [ IsChar, "n", IsDigitChar, IsAlphaChar ] );
      if parse <> fail and parse[3] <> 0 and not IsEmpty( parse[4] ) then
        Add( tocheck, parse[1] );
        supertbl:= CharacterTable( parse[1] );
        if supertbl <> fail then
          # Check whether a class in the big group has a normalizer order
          # equal to the order of `tbl',
          # such that the class fusion is compatible with that.
          centralizers:= SizesCentralizers( supertbl );
          orders:= OrdersClassRepresentatives( supertbl );
          orbits:= List( [ 1 .. NrConjugacyClasses( supertbl ) ],
                         i -> Length( ClassOrbit( supertbl, i ) ) );
          cand:= Filtered( [ 1 .. Length( centralizers ) ],
                           i -> centralizers[i] * Phi( orders[i] )
                                / orbits[i] = Size( tbl ) and
                                orders[i] = parse[3] );
          if IsEmpty( cand ) then
            CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                [ [ "not the centralizer of an el. of order ", parse[3],
                    " in `", parse[1], "'" ] ] );
            result:= false;
          else
            orders:= OrdersClassRepresentatives( tbl );
            classes:= SizesConjugacyClasses( tbl );
            cen:= Filtered( [ 1 .. Length( orders ) ],
                    i -> orders[i] = parse[3] and
                         orders[i] = Sum(
              classes{ ClassPositionsOfNormalClosure( tbl, [ i ] ) } ) );
            fus:= GetFusionMap( tbl, supertbl );
            if fus <> fail then
              cen:= Filtered( cen, i -> fus[i] in cand );
            fi;
            if IsEmpty( cen ) then
              CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                  [ [ "not the normalizer of an el. of order ", parse[3],
                      " in `", parse[1], "'" ] ] );
              result:= false;
            elif fus <> fail then
              classname:= LowercaseString( Concatenation(
                  String( parse[3] ), parse[4] ) );
              cname:= List( ClassNames( supertbl ){ fus{ cen } },
                            LowercaseString );
              if not classname in cname then
                CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
                    [ "normalizer in `", parse[1], "' of a class in `" ],
                    [ String( cname ), "' not of `", classname, "'" ] );
                result:= false;
              fi;
            fi;
          fi;
        fi;
      fi;

      for supname in tocheck do
        supertbl:= CharacterTable( supname );
        if supertbl = fail then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.RelativeNames", tblname,
              [ [ "no character table with name `", supname, "'" ] ] );
          result:= false;
        elif GetFusionMap( tbl, supertbl ) = fail then
          # Check that a class fusion is stored.
          CTblLib.PrintTestLog( "I", "CTblLib.Test.RelativeNames", tblname,
              [ [ "no fusion to `", Identifier( supertbl ), "' stored" ] ] );
          fus:= CTblLib.Test.SubgroupFusion( tbl, supertbl );
          if IsRecord( fus ) then
            CTblLib.PrintTestLog( "I", "CTblLib.Test.RelativeNames", tblname,
                "store the following fusion" );
            fus:= ShallowCopy( fus );
            fus.name:= supertbl;
            Print( LibraryFusion( tbl, fus ) );
          fi;
          result:= false;
        fi;
      od;
    fi;

    return result;
    end;


#############################################################################
##
#F  CTblLib.Test.FindRelativeNames( <tbl> )
##
##  <#GAPDoc Label="test:CTblLib.Test.FindRelativeNames">
##  <Mark><C>CTblLib.Test.FindRelativeNames( </C><M>tbl</M><C> )</C></Mark>
##  <Item>
##    runs over the class fusions stored on <M>tbl</M>.
##    If <M>tbl</M> is the full centralizer/normalizer of a cyclic subgroup
##    in the table to which the class fusion points
##    then the function proposes to make the corresponding relative name
##    an admissible name for <M>tbl</M>.
##  </Item>
##  <#/GAPDoc>
##
CTblLib.Test.FindRelativeNames:= function( tbl )
    local orders, classes, cen, nor, result, tocheck, record, fus, supertbl,
          centralizers, superclasses, i, orbits, j, name, info;

    orders:= OrdersClassRepresentatives( tbl );
    classes:= SizesConjugacyClasses( tbl );
    cen:= ClassPositionsOfCentre( tbl );
    nor:= Filtered( [ 1 .. NrConjugacyClasses( tbl ) ],
              i -> orders[i] = Sum( classes{
                   ClassPositionsOfNormalClosure( tbl, [ i ] ) } ) );

    result:= true;

    tocheck:= [];
    for record in ComputedClassFusions( tbl ) do
      fus:= record.map;
      if Length( ClassPositionsOfKernel( fus ) ) = 1 then
        supertbl:= CharacterTable( record.name );
        if supertbl <> fail then
          centralizers:= SizesCentralizers( supertbl );
          superclasses:= SizesConjugacyClasses( supertbl );
          orders:= OrdersClassRepresentatives( supertbl );
          # Is `tbl' is an element centralizer in a bigger table?
          if 1 < Length( cen ) then
            for i in [ 2 .. Length( cen ) ] do
              if centralizers[ fus[ cen[i] ] ] = Size( tbl ) and
                 not orders[ fus[ cen[i] ] ]
                     = Sum( superclasses{ ClassPositionsOfNormalClosure(
                              supertbl, [ fus[ cen[i] ] ] ) } ) and
                 not orders[ fus[ cen[i] ] ] = 2 then
                Add( tocheck, Concatenation( Identifier( supertbl ), "C",
                                ClassNames( supertbl )[ fus[ cen[i] ] ] ) );
              fi;
            od;
          fi;

          # Is `tbl' is an element normalizer in a bigger table?
          if 1 < Length( nor ) then
            orbits:= List( [ 1 .. NrConjugacyClasses( supertbl ) ],
                           i -> Length( ClassOrbit( supertbl, i ) ) );
            for i in [ 2 .. Length( nor ) ] do
              j:= fus[ nor[i] ];
              if centralizers[j] * Phi( orders[j] ) / orbits[j]
                 = Size( tbl ) and
                 not orders[ fus[ nor[i] ] ]
                 = Sum( superclasses{ ClassPositionsOfNormalClosure(
                            supertbl, [ fus[ nor[i] ] ] ) } ) then
                if IsPrimePowerInt( orders[j] ) and
                   Gcd( orders[j], Size( supertbl ) / orders[j] ) = 1 then
                  # Prefer the Sylow normalizer name.
                  Add( tocheck, Concatenation( Identifier( supertbl ), "N",
                                    String( Factors( orders[j] )[1] ) ) );
                else
                  # Choose the name of the normalizer of a cyclic subgroup.
                  Add( tocheck, Concatenation( Identifier( supertbl ), "N",
                    ClassNames( supertbl )[ fus[ nor[i] ] ] ) );
                fi;
              fi;
            od;
          fi;
        fi;
      fi;
    od;

    for name in Set( tocheck ) do
      info:= LibInfoCharacterTable( name );
      if info = fail then
        CTblLib.PrintTestLog( "I", "CTblLib.Test.FindRelativeNames",
            Identifier( tbl ),
            [ [ "add the new name `", name, "'" ] ] );
      elif info.firstName <> Identifier( tbl ) then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.FindRelativeNames",
            Identifier( tbl ),
            [ [ "`", name, "' should be a name for `",
                Identifier( tbl ), "' not `", info.firstName, "'" ] ] );
        result:= false;
      fi;
    od;

    return result;
    end;


#############################################################################
##
#F  CTblLib.Test.Decompositions( <sub>, <fuslist>, <tbl> )
##
##  Let <sub> and <tbl> be ordinary character tables, and <fuslist> a list of
##  possible class fusions from <sub> to <tbl>.
##
##  `CTblLibTestDecompositions' returns the set of all those entries in
##  <fuslist> such that for all available $p$-modular Brauer tables of <sub>
##  and <tbl>, the $p$-modular Brauer characters of <tbl> decompose into
##  $p$-modular Brauer characters of <sub>.
##
CTblLib.Test.Decompositions:= function( sub, fuslist, tbl )
    local bad, p, modtbl, modsub, modfuslist, modfus;

    if IsEmpty( fuslist ) then
      return [];
    fi;

    bad:= [];

    for p in PrimeDivisors( Size( tbl ) ) do
      modtbl:= tbl mod p;
      if modtbl <> fail then
        modsub:= sub mod p;
        if modsub <> fail then
          modfuslist:= List( fuslist, fus ->
              CompositionMaps( InverseMap( GetFusionMap( modtbl, tbl ) ),
                               CompositionMaps( fus,
                                   GetFusionMap( modsub, sub ) ) ) );
          for modfus in Set( modfuslist ) do
            if fail in Decomposition( Irr( modsub ),
                           List( Irr( modtbl ), chi -> chi{ modfus } ),
                           "nonnegative" ) then
              UniteSet( bad,
                  fuslist{ Filtered( [ 1 .. Length( fuslist ) ],
                                     i -> modfuslist[i] = modfus ) } );
            fi;
          od;
        fi;
      fi;
    od;

    return Difference( fuslist, bad );
    end;
#T Jon Thackray says: LinearIndependentColumns runs forever in
#T some computation with Ly ...


#############################################################################
##
#V  CTblLib.IgnoreFactorFusionsCompatibility
#F  CTblLib.PermutationInducedOnFactor( <factfus>, <perm> )
#F  CTblLib.Test.CompatibleFactorFusions( <tbl> )
##
##  <#GAPDoc Label="test:CTblLib.Test.CompatibleFactorFusions">
##  <Mark><C>CTblLib.Test.CompatibleFactorFusions( </C><M>tbl</M><C> )</C></Mark>
##  <Item>
##    checks whether triangles and quadrangles of factor fusions from
##    <M>tbl</M> to other library tables commute
##    (where the entries in the list
##    <C>CTblLib.IgnoreFactorFusionsCompatibility</C> are excluded from the
##    tests),
##    and whether the factor fusions commute with the actions of
##    corresponding outer automorphisms.
##  </Item>
##  <#/GAPDoc>
##
CTblLib.IgnoreFactorFusionsCompatibility:= [
    [ "2x2^3:L3(2)x2", "2x2^3:L3(2)", "C2" ],
    [ "2.A5xA5", "2.A5", "A5" ],
    [ "2.A5xA5", "A5xA5", "A5" ],
    [ "2.A5xA5", Set( [ "A5xA5", "2.A5" ] ), "A5" ],
  ];

CTblLib.PermutationInducedOnFactor:= function( factfus, perm )
    local ind, i, pre, img;

    ind:= [];
    for i in [ 1 .. Length( factfus ) ] do
      pre:= factfus[i];
      img:= factfus[ i^perm ];
      if IsBound( ind[ pre ] ) then
        if ind[ pre ] <> img then
          return fail;
        fi;
      else
        ind[ pre ]:= img;
      fi;
    od;
    return PermList( ind );
    end;

CTblLib.Test.CompatibleFactorFusions:= function( tbl )
    local result, tbls, ids, incid, t, factfus, facttbl, comp, triple1,
          triple2, i, j, auts, fact, ker, triple, factauts, ind, kerimg,
          supfact, facttriple;

    result:= true;

    # Collect factor fusions from `tbl' to other tables,
    # and from these tables to other tables.
    tbls:= [ tbl ];
    ids:= [ Identifier( tbl ) ];
    incid:= [];
    for t in tbls do
      for factfus in Filtered( ComputedClassFusions( t ),
              r -> 1 < Length( ClassPositionsOfKernel( r.map ) ) ) do
        if not factfus.name in ids then
          facttbl:= CharacterTable( factfus.name );
          if facttbl <> fail then
            Add( ids, factfus.name );
            Add( tbls, facttbl );
          fi;
        fi;
        Add( incid, [ Identifier( t ), factfus.name, factfus.map ] );
      od;
    od;

    # Check triangles and quadrangles in the directed graph.
    comp:= [];
    for triple1 in incid do
      for triple2 in incid do
        if triple1[2] = triple2[1] then
          # for t1 -> t2 and t2 -> t3, store [ t1, t3, map, t2 ]
          Add( comp, [ triple1[1], triple2[2],
               CompositionMaps( triple2[3], triple1[3] ), triple1[2] ] );
        fi;
      od;
    od;
    for i in [ 1 .. Length( comp ) ] do
      triple1:= comp[i];
      # triangles
      for triple2 in incid do
        if triple1[1] = triple2[1] and triple1[2] = triple2[2] and
           triple1[3] <> triple2[3] and
           not [ triple1[1], triple1[4], triple1[2] ] in
             CTblLib.IgnoreFactorFusionsCompatibility then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.CompatibleFactorFusions",
              Identifier( tbl ),
              [ [ "inconsistent triangle: ", triple1[1], " ->> ", triple1[4],
                  " ->> ", triple1[2] ] ] );
          result:= false;
        fi;
      od;
      # quadrangles
      for j in [ 1 .. i-1 ] do
        triple2:= comp[j];
        if triple1[1] = triple2[1] and triple1[2] = triple2[2] and
           triple1[3] <> triple2[3] and
           not [ triple1[1], Set( [ triple1[4], triple2[4] ] ), triple1[2] ] in
             CTblLib.IgnoreFactorFusionsCompatibility then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.CompatibleFactorFusions",
              Identifier( tbl ),
              [ [ "inconsistent quadrangle: ",
                  triple1[1], " ->> (", triple1[4], " or ",
                  triple2[4], ") ->> ", triple1[2] ] ] );
          result:= false;
        fi;
      od;
    od;

    # Check compatibility of factor fusions with outer automorphims.
    auts:= CTblLib.PermutationsInducedByOuterAutomorphisms( tbl );
    for factfus in Filtered( ComputedClassFusions( t ),
            r -> 1 < Length( ClassPositionsOfKernel( r.map ) ) ) do
      fact:= CharacterTable( factfus.name );
      if fact <> fail then
        ker:= ClassPositionsOfKernel( factfus.map );
        for triple in auts do
          if Set( ker ) = Set( OnTuples( ker, triple[1] ) ) then
            # The outer automorphism acts on the factor group.
            factauts:= CTblLib.PermutationsInducedByOuterAutomorphisms( fact );
            ind:= CTblLib.PermutationInducedOnFactor( factfus.map, triple[1] );
            if ind <> fail then
              kerimg:= Set( triple[3]{ ker } );
              supfact:= First( ComputedClassFusions( triple[2] ),
                            r -> ClassPositionsOfKernel( r.map ) = kerimg );
              if supfact <> fail then
                facttriple:= First( factauts,
                                 x -> Identifier( x[2] ) = supfact.name );
                if facttriple <> fail then
                  if ind <> facttriple[1] then
                    # The permutations do not fit together.
                    result:= false;
                    CTblLib.PrintTestLog( "E",
                        "CTblLib.Test.CompatibleFactorFusions",
                        Identifier( tbl ),
                        [ [ "autom. induced by ", Identifier( triple[2] ),
                            " incompatible with factor ", factfus.name ] ] );
                  fi;
                fi;
              fi;
            fi;
          fi;
        od;
      fi;
    od;

    return result;
    end;


#############################################################################
##
#V  CTblLib.HardFusions
##
##  `CTblLib.HardFusions' is a list of pairs `[ <subname>, <tblname> ]'
##  where <subname> and <tblname> are `Identifier' values of character
##  tables such that `CTblLib.Test.SubgroupFusion' shall omit the compatibility
##  check for the class fusion between these tables.
##
CTblLib.HardFusions:= [];

Add( CTblLib.HardFusions, [ "Co1N3", "Co1" ] );
Add( CTblLib.HardFusions, [ "Co1N2", "Co1" ] );
Add( CTblLib.HardFusions, [ "Co2N2", "Co2" ] );
     # computed using the groups, fusion status unclear
Add( CTblLib.HardFusions, [ "2^10.2^3.2^3.S3", "Co2" ] );
     # computed using the groups, fusion status unclear
Add( CTblLib.HardFusions, [ "2^10.2^3.2^3.S3", "2^10:m22:2" ] );
     # computed using the groups, fusion status unclear
Add( CTblLib.HardFusions, [ "Fi22N3", "Fi22" ] );
     # computed via factorization through 3^(1+6):2^(3+4):3^2:2
Add( CTblLib.HardFusions, [ "M24N2", "M24" ] );
     # computed from the groups, time 227180 msec, incl. tables comput.
     # (25-11-2002)
Add( CTblLib.HardFusions, [ "M24N2", "He" ] );
     # computed from the groups, time 12451360 msec, incl. tables comput.
     # (26-11-2002)
Add( CTblLib.HardFusions, [ "O8+(3)M14", "O8+(3)" ] );
     # 1 orbit, 648 sol., time 154539590 msec on regulus (22-11-2002)
Add( CTblLib.HardFusions, [ "L3(3)", "B" ] );
     # 1 orbit, 36 sol., harmless if one forbids decomposition
Add( CTblLib.HardFusions, [ "2^2xF4(2)", "2.2E6(2).2" ] );
Add( CTblLib.HardFusions, [ "(3^2:D8xU4(3).2^2).2", "B" ] );
Add( CTblLib.HardFusions, [ "[2^35].(S5xL3(2))", "B" ] );
     # unique, takes 34621084 msec (2009-04-20)
Add( CTblLib.HardFusions, [ "2^2x3xS3xU4(2)", "(2^2x3).U6(2)" ] );
     # takes a long time ... (February 2010)
Add( CTblLib.HardFusions, [ "2xM11", "2.B" ] );
     # takes a long time, needs a lot of space ... (February 2010)
Add( CTblLib.HardFusions, [ "(2^2x3).2E6(2)", "(2^2x3).2E6(2).2" ] );
     # not ready after 48 h ... (February 2010)
Add( CTblLib.HardFusions, [ "2xCo1N3", "2.Co1" ] );
     # use the fusion Co1N3 -> Co1, and set "decompose" to `false'
     # (with the latter, the computations are very quick;
     # the decomposition test takes at least hours)

Add( CTblLib.HardFusions, [ "3.2E6(2)M8", "3.2E6(2)" ] );
Add( CTblLib.HardFusions, [ "3.2E6(2)M9", "3.2E6(2)" ] );
     # the test says ``text should not mention that fusion is relative''
     # in these two cases; but the fusions ARE relative,
     # just the reference table of 3.2E6(2).3 is missing
     # (so the pairs can be removed from `CTblLib.HardFusions' as soon as
     # this table will be available)


#############################################################################
##
#F  CTblLib.InitFusionsStatistics( <statfile> )
#F  CTblLib.AmendFusionsStatistics( <entry> )
#F  CTblLib.FinalizeFusionsStatistics()
##
##  Create a file with information about all subgroup fusions stored in the
##  GAP Character Table Library.
##  For the fusion from the table with identifier <subtbl> into that with
##  identifier <tbl>, a list entry of the following form is printed.
##
##  `[<subtbl>,<tbl>,<nrfus>,<nrorbs>,<nrcomp>,<nrcorbs>,<normtime>],'
##
##  Here <nrfus> is the number of fusions,
##  <nrorbs> is the number of orbits on the maps under table automorphisms,
##  <nrcomp> is the number of those fusions that are compatible with the
##  Brauer tables available for <subtbl> and <tbl>,
##  <nrcorbs> is the number of orbits on the compatible maps under table
##  automorphisms, and
##  <normtime> is the time needed to compute the fusions,
##  divided by ... (so this value is expected to be more or less independent
##  of the machine used).
##
##  Thus the fusion is unique if <nrfus> is $1$,
##  it is unique up to table automorphisms if <nrorbs> is $1$;
##  otherwise the fusion is ambiguous.
##  If <nrcomp> is smaller than <nrfus> then the Brauer tables impose
##  extra conditions on the fusions, and if <nrcorbs> is smaller than
##  <nrorbs> then the Brauer tables reduce the ambiguity.
##
CTblLib.InitFusionsStatistics:= function( statfile )
    local time, l, i, j;

    # Measure the time for some typical computations.
    time:= Runtime();
    l:= [];
    for i in [ 1 .. 1000 ] do
      for j in [ 1 .. 1000 ] do
        l[j]:= j;
      od;
    od;
    time:= ( Runtime() - time );

    # Create the file.
    PrintTo( statfile, "[\n" );

    LIBTABLE.FusionsStatistics:= rec( statfile:= statfile, time:= time );
    end;

CTblLib.AmendFusionsStatistics:= function( entry )
    if IsBound( LIBTABLE.FusionsStatistics ) then
      AppendTo( LIBTABLE.FusionsStatistics.statfile, Concatenation( "[\"",
        Identifier( entry[1] ),
        "\",\"",
        Identifier( entry[2] ),
        "\",",
        String( Length( entry[3] ) ),
        ",",
        String( Length( entry[4] ) ),
        ",",
        String( Length( entry[5] ) ),
        ",",
        String( Length( entry[6] ) ),
        ",",
        String( Int( entry[7] / LIBTABLE.FusionsStatistics.time ) ),
        "],\n" ) );
    fi;
    end;

CTblLib.FinalizeFusionsStatistics:= function()
    AppendTo( LIBTABLE.FusionsStatistics.statfile, "\n];\n" );
    end;


#############################################################################
##
#V  CTblLib.ExcludedFromFusionCompatibility
##
##  a list of those quadruples [ <sub>, <tbl>, <subfact>, <tblfact> ]
##  or [ <sub>, <tbl>, <subext>, <tblext> ]
##  for which no commutative diagram of stored fusions is guaranteed
##
CTblLib.ExcludedFromFusionCompatibility:= [
  # quadruples involving two subgroup fusions and two factor fusions
  [ "2.O7(3)M5", "2.O7(3)", "G2(3)", "O7(3)" ],
  [ "3.O7(3)M8", "3.O7(3)", "S6(2)", "O7(3)" ],
  [ "3.O7(3)M11", "3.O7(3)", "A9.2", "O7(3)" ],
  [ "2.S6(2)", "2.O8+(2)", "S6(2)", "O8+(2)" ],
  [ "2^(1+6)_+.A8", "2.O8+(2)", "2^6:A8", "O8+(2)" ],
  [ "2.A9", "2.O8+(2)", "A9", "O8+(2)" ],

  [ "2.U4(3).2_2", "2.U6(2)", "U4(3).2_2", "U6(2)" ],
    # since 2.U4(3).2_2 = 2.U6(2)M5, U4(3).2_2 = U6(2)M4

  [ "6_1.U4(3).2_2", "6.U6(2)", "U4(3).2_2", "U6(2)" ],
    # since 6_1.U4(3).2_2 = 6.U6(2)M5, U4(3).2_2 = U6(2)M4

  [ "6_1.U4(3).2_2", "6.U6(2)", "3_1.U4(3).2_2", "3.U6(2)" ],
    # since 6_1.U4(3).2_2 = 6.U6(2)M5, 3_1.U4(3).2_2 = U6(2)M4

  [ "2.M22", "2.U6(2)", "M22", "U6(2)" ],
    # since 2.M22 = 2.U6(2)M12, M22 = U6(2)M11

  [ "6.M22", "6.U6(2)", "M22", "U6(2)" ],
    # since 6.M22 = 6.U6(2)M12, M22 = U6(2)M11

  [ "6.M22", "6.U6(2)", "3.M22", "3.U6(2)" ],
    # since 6.M22 = 6.U6(2)M12, 3.M22 = 3.U6(2)M11

  [ "2.Fi22", "2.2E6(2)", "Fi22", "2E6(2)" ],
    # since 2.Fi22 = 2.2E6(2)M8, Fi22 = 2E6(2)M7

  [ "6.Fi22", "6.2E6(2)", "Fi22", "2E6(2)" ],
    # since 6.Fi22 = 6.2E6(2)M8, Fi22 = 2E6(2)M7

  [ "6.Fi22", "6.2E6(2)", "3.Fi22", "3.2E6(2)" ],
    # since 6.Fi22 = 6.2E6(2)M8, 3.Fi22 = 3.2E6(2)M7

  [ "2.F4(2)", "2.2E6(2)", "F4(2)", "2E6(2)" ],
    # since 2.F4(2) = 2.2E6(2)M4, "F4(2) = 2E6(2)M3

  [ "6.M22", "6.U6(2)", "2.M22", "2.U6(2)" ],
# why?

  [ "2xM22", "2.U6(2)", "M22", "U6(2)" ],
#T why?

# [ "2^2.U6(2)M12","2^2.U6(2)", "2.M22", "2.U6(2)" ],
#T no! excluding 6.M22 -> 3.M22 and 2.M22 -> M22 should suffice!

  # quadruples involving four subgroup fusions
  [ "L2(11)", "M12", "L2(11).2", "M12.2" ],
    # since L2(11).2 occurs as both the novelty M12.2M2 (cont. 2B elements)
    # and as the extension M12.2M3 (cont. 2A elements) in M12.2;
    # in AtlasRep and MFER, the novelty occurs first
  ];


#############################################################################
##
#F  CTblLib.AddIncompatibleFusionsOfFactors( <sub>, <tbl>, <fus>, <incompat> )
##
CTblLib.AddIncompatibleFusionsOfFactors:= function( sub, tbl, fus, incompat )
    local tblfactfus, record, ker, kersize, pair, subfact, tblfact,
          storedfus, ffus;

    # Compute a list of pairs [ <kernelsize>, <factfusrec> ] for `tbl'.
    tblfactfus:= Filtered( List( ComputedClassFusions( tbl ),
          r -> [ Sum( SizesConjugacyClasses( tbl ){ ClassPositionsOfKernel(
                                                      r.map ) } ), r ] ),
        pair -> pair[1] <> 1 );

    # Collect commutative diagrams of factor fusions and subgroup fusions.
    # We consider only those diagrams where a subgroup fusion between the
    # factor tables is already stored.
    for record in ComputedClassFusions( sub ) do
      ker:= ClassPositionsOfKernel( record.map );
      if ker <> [ 1 ] then
        kersize:= Sum( SizesConjugacyClasses( sub ){ ker } );
        for pair in Filtered( tblfactfus, x -> x[1] = kersize ) do
          subfact:= CharacterTable( record.name );
          tblfact:= CharacterTable( pair[2].name );
          if subfact <> fail and tblfact <> fail then
            if List( [ sub, tbl, subfact, tblfact ], Identifier )
                 in CTblLib.ExcludedFromFusionCompatibility then
              incompat.someexcluded:= true;
            else
              storedfus:= GetFusionMap( subfact, tblfact );
              if storedfus <> fail and
                 Set( fus{ ker } ) = ClassPositionsOfKernel( pair[2].map ) then
                # Compute the induced fusion between the factors and compare.
                ffus:= CompositionMaps( pair[2].map,
                         CompositionMaps( fus, InverseMap( record.map ) ) );
                if ffus <> storedfus then
                  Add( incompat.badfusions, [ sub, tbl, subfact, tblfact ] );
                fi;
                CTblLib.AddIncompatibleFusionsOfFactors( subfact, tblfact,
                    storedfus, incompat );
              fi;
            fi;
          fi;
        od;
      fi;
    od;
    end;


#############################################################################
##
#F  CTblLib.Test.SubgroupFusionOfMaximalSubgroup( <sub>, <tbl> )
##
##  returns
##  `false' if was not applicable,
##  `true' if no further treatment of the fusion is needed,
##  because it is interpreted relative to another fusion
##  (also if inconsistent!).
##
CTblLib.MaxesNames:= function( tbl )
    local result, tblname, pos, name, index;

    if HasMaxes( tbl ) then
      return Maxes( tbl );
    fi;

    result:= [];
    tblname:= Concatenation( LowercaseString( Identifier( tbl ) ), "m" );
    pos:= PositionSorted( LIBLIST.allnames, tblname );
    while pos <= Length( LIBLIST.allnames ) do
      name:= LIBLIST.allnames[ pos ];
      if Length( name ) <= Length( tblname ) then
        break;
      fi;
      index:= name{ [ Length( tblname ) + 1 .. Length( name ) ] };
      if ForAll( index, IsDigitChar ) then
        Add( result,
             [ Int( index ), LibInfoCharacterTable( name ).firstName ] );
      fi;
      pos:= pos + 1;
    od;
    Sort( result );
    return List( result, x -> x[2] );
end;


#T this function strikes for example for 2^2.L3(4).2_1 -> 2^2.L3(4).2^2
#T (w.r.t. Brauer tables for p = 5 or 7)
#T but why??
CTblLib.PermutationsInducedByOuterAutomorphisms:= function( tbl )
    local result, r, suptbl, fus, order, img, inv, lists, stab, cand, p,
          modtbl, modfus, range;

    result:= [];
    for r in ComputedClassFusions( tbl ) do
      suptbl:= CharacterTable( r.name );
      if suptbl <> fail then
        fus:= r.map;
        order:= Size( suptbl ) / Size( tbl );
        img:= Set( fus );
        if img in ClassPositionsOfNormalSubgroups( suptbl ) and
           Sum( SizesConjugacyClasses( suptbl ){ img } ) = Size( tbl ) and
           IsCyclic( suptbl / img ) then
          # `tbl' is a normal subgroup of index `order' in `suptbl'.
          inv:= InverseMap( fus );
          lists:= Filtered( inv, IsList );
          if not IsEmpty( lists ) then
            stab:= Stabilizer( AutomorphismsOfTable( tbl ),
                               Filtered( inv, IsInt ), OnTuples );
            stab:= Stabilizer( stab, lists, OnTuplesSets );
            cand:= Filtered( stab,
                       x -> Order( x ) = order and
                            ForAll( lists, l -> l <> OnTuples( l, x ) ) );
            cand:= Set( List( cand, SmallestGeneratorPerm ) );

            # Check whether the restriction to p-regular classes defines
            # a table automorphism.
            for p in PrimeDivisors( Size( tbl ) ) do
              modtbl:= tbl mod p;
              if modtbl <> fail then
                modfus:= GetFusionMap( modtbl, tbl );
                range:= [ 1 .. NrConjugacyClasses( tbl ) ];
                cand:= Filtered( cand, pi -> PermList( CompositionMaps(
                                               InverseMap( modfus ),
                   CompositionMaps( OnTuples( range, pi ), modfus ) ) ) in
                       AutomorphismsOfTable( modtbl ) );
              fi;
            od;

            if Length( cand ) = 0 then
              CTblLib.PrintTestLog( "E",
                  "CTblLib.PermutationsInducedByOuterAutomorphisms",
                  Identifier( tbl ),
                  [ [ "no table autom. induced by fusion to ",
                      Identifier( suptbl ), "?" ] ] );
            elif Length( cand ) = 1 then
              Add( result, [ cand[1], suptbl, fus ] );
            elif Length( cand ) = 2 and Identifier( tbl ) = "U3(8)"
                 and Identifier( suptbl ) = "U3(8).3_3" then
              # There are two groups U3(8).3_3 and U3(8).3_3',
              # to which the two candidates belong.
              # Note that the automorphisms 3_3 and 3_3' arise as 3_1 * 3_2
              # and 3_1 / 3_2, respectively; we choose the smaller of the two
              # possibilities as 3_3.
              Add( result, [ (6,7,8)(11,12,13)(14,15,16)(17,19,21)(18,20,22)*
                             (23,25,27)(24,26,28), suptbl, fus ] );
            else
              CTblLib.PrintTestLog( "I",
                  "CTblLib.PermutationsInducedByOuterAutomorphisms",
                  Identifier( tbl ),
                  [ [ "cannot identify table autom. induced by action of ",
                      Identifier( suptbl ) ] ] );
#T maybe we need just the orbits not the action itself
#T (if the classes in question are not hit, for example ...)
            fi;
          fi;
        fi;
      fi;
    od;

    return result;
    end;

CTblLib.Test.SubgroupFusionOfMaximalSubgroup:= function( sub, tbl )
#T this function did not detect that the stored fusion J3M3 -> J3 
#T was not the one from J3M2 permuted by the group aut.;
#T the stored fusion did not admit decomposition of the restriction
#T of the 19-modular 110
    local maxesnames, pos, fusid, mx, tr, choice, fusions, auts, triple,
          cand, suptbl,
          fus, invariant, extcand, extname, ext, extfus, orb, orbreps,
          i, map, pos2, done, rep, fusrec, relative, source, text, incompat,
          incompatnames;

    maxesnames:= CTblLib.MaxesNames( tbl );
    pos:= PositionsProperty( maxesnames, x -> Identifier( sub ) = x );
    if IsEmpty( pos ) then
      # The subgroup is not known to be maximal,
      # so this function is not useful for this fusion.
      return false;
    fi;
    fusid:= Concatenation( Identifier( sub ), " -> ", Identifier( tbl ) );

    # Collect maxes with tables equivalent to that of `sub'.
    mx:= List( maxesnames, CharacterTable );
    tr:= List( [ 1 .. Length( mx ) ],
           i -> TransformingPermutationsCharacterTables( sub, mx[i] ) );
    choice:= PositionsProperty( tr, x -> x <> fail );
    fusions:= List( choice, i -> GetFusionMap( mx[i], tbl ) );
    tr:= tr{ choice };
    if fail in fusions then
      CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusionOfMaximalSubgroup",
          fusid,
          [ [ "missing fusion from some maxes of ", Identifier( tbl ) ] ] );
      return false;
    fi;

    # Compute the action of outer automorphisms of `tbl'.
    auts:= [];
    for triple in CTblLib.PermutationsInducedByOuterAutomorphisms( tbl ) do
      cand:= triple[1];
      suptbl:= triple[2];
fus:= GetFusionMap( sub, tbl );
      Add( auts, cand );
      invariant:= RepresentativeAction( AutomorphismsOfTable( sub ),
                      fus, OnTuples( fus, cand ), Permuted ) <> fail;
      extcand:= Intersection( CTblLib.MaxesNames( suptbl ),
                    List( ComputedClassFusions( sub ), x -> x.name ) );
      RemoveSet( extcand, Identifier( tbl ) );
      for extname in extcand do
        ext:= CharacterTable( extname );
        if ext <> fail and
           Size( ext ) / Size( sub ) = Size( suptbl ) / Size( tbl ) then
          extfus:= GetFusionMap( ext, suptbl );
          if extfus <> fail then
            if CompositionMaps( extfus, GetFusionMap( sub, ext ) ) =
               CompositionMaps( triple[3], fus ) then
              if not invariant then
                # The subgroup behaves as if it would extend to the
                # autom. extension but it cannot extend.
                CTblLib.PrintTestLog( "E",
                    "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
                    "strange/invalid commutative diagram",
                  [ Identifier( sub ), " -> ", Identifier( suptbl ) ],
                  [ "via ", Identifier( ext ), " and ", Identifier( tbl ) ] );
              fi;
            elif invariant
                 and not List( [ sub, tbl, ext, suptbl ], Identifier )
                         in CTblLib.ExcludedFromFusionCompatibility then
              # The diagram shold commute.
              CTblLib.PrintTestLog( "E",
                  "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
                  "diagram should commute:",
                  [ Identifier( sub ), " -> ", Identifier( suptbl ) ],
                  [ "via ", Identifier( ext ), " and ", Identifier( tbl ) ] );
            fi;
          fi;
        fi;
      od;
    od;

    if IsEmpty( auts ) then
      # We cannot derive conditions from outer automorphisms.
      # so this function is not useful for this fusion.
      return false;
    fi;

    # Compute the orbit of the given fusion under `auts',
    # and representatives under table automorphisms of `sub'.
    fusions:= List( [ 1 .. Length( tr ) ],
                    i -> Permuted( fusions[i], tr[i].columns ) );
    fus:= fusions[ Position( choice, pos[1] ) ];
    orb:= Orbit( Group( auts ), fus, OnTuples );
    orbreps:= [ fus ];
    for i in [ 2 .. Length( orb ) ] do
      map:= orb[i];
      pos2:= PositionProperty( orbreps, rep -> RepresentativeAction(
               AutomorphismsOfTable( sub ), map, rep, Permuted ) <> fail );
      if pos2 = fail then
        Add( orbreps, map );
      fi;
    od;

    # Each of the essentially different fusions must occur
    # among the maxes fusions (with the same multiplicity).
    done:= [];
    for rep in orbreps do
      pos2:= PositionsProperty( fusions,
                 map -> RepresentativeAction( AutomorphismsOfTable( sub ),
                          map, rep, Permuted ) <> fail );
      UniteSet( done, choice{ pos2 } );
      if Length( pos2 ) = 0 then
#T better check that always the same nonzero length ...
        CTblLib.PrintTestLog( "E",
            "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
            "multiplicity of same fusion among maxes does not fit" );
      fi;
    od;

    # The fusion of `sub' can be interpreted relative to another fusion.
    fusrec:= First( ComputedClassFusions( sub ),
                    r -> r.name = Identifier( tbl ) );
    relative:= false;
    if IsBound( fusrec.text ) then
      source:= fail;
      text:= ReplacedString( fusrec.text, "\n", " " );
      pos2:= PositionSublist( text, ", mapped under" );
      if pos2 <> fail then
        relative:= true;
        text:= text{ [ 1 .. pos2 - 1 ] };
        pos2:= PositionSublist( text, " from " );
        if pos2 <> fail then
          source:= CharacterTable( text{ [ pos2 + 6 .. Length( text ) ] } );
        fi;
      fi;
      if source <> fail then
        if Identifier( source ) <> maxesnames[ Minimum( done ) ] and
           not ( relative and pos[1] = Minimum( done ) ) then
          CTblLib.PrintTestLog( "E",
            "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
            [ [ "text should mention that fusion is relative to ",
                Concatenation( maxesnames[ Minimum( done ) ],
                  " not ", Identifier( source ) ) ] ] );
        fi;

        # Check that the fusions lie in the same orbit.
        if TransformingPermutationsCharacterTables( source, sub ) = fail then
          CTblLib.PrintTestLog( "E",
            "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
            "wrong source stored for relative fusion" );
        fi;
      elif relative then
        CTblLib.PrintTestLog( "E",
          "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
          "missing source information in relative fusion" );
      fi;
    fi;

    if pos[1] = Minimum( done ) then
      # `sub' is the first table among the maxes in its orbit,
      # it should not be a relative fusion.
      if relative then
        CTblLib.PrintTestLog( "E",
            "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
            "text should not mention that fusion is relative" );
      fi;

      # Return `false' in order to check the fusion.
      return false;
    fi;

    # `sub' is not the first table among the maxes in its orbit,
    # so the fusion should be a relative fusion.
    if not relative then
      CTblLib.PrintTestLog( "E",
          "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
          [ [ "text should mention that fusion is relative to ",
              maxesnames[ Minimum( done ) ] ] ] );
    fi;

    # Check that the stored fusion is compatible with factors.
    incompat:= rec( badfusions:= [], someexcluded:= false );
    CTblLib.AddIncompatibleFusionsOfFactors( sub, tbl, fus, incompat );
    if not IsEmpty( incompat.badfusions ) then
      incompatnames:= List( incompat.badfusions,
          entry -> Concatenation( [ "  ", Identifier( entry[3] ),
                                    " -> ", Identifier( entry[4] ),
                                    " (w.r.t. ", Identifier( entry[1] ),
                                    " -> ", Identifier( entry[2] ), ")" ] ) );
      CTblLib.PrintTestLog( "E",
          "CTblLib.Test.SubgroupFusionOfMaximalSubgroup", fusid,
          Concatenation(
            [ "no fusion (compatible with Brauer tables if applicable and)",
              " consistent with" ], incompatnames ) );
    fi;

    return true;
end;


#############################################################################
##
#F  CTblLib.Test.SubgroupFusion( <sub>, <tbl> )
##
##  If no class fusion from <sub> to <tbl> is possible or if the possible
##  class fusions contradict the stored fusion then `false' is returned.
##  If a fusion is stored and is compatible with the possible fusions,
##  and the fusion is not unique up to table automorphisms and if the stored
##  fusion has no `text' component then `fail' is returned.
##  Otherwise the fusion record is returned.
##
##  If the pair of identifiers of <sub> and <tbl> occurs in the global list
##  `CTblLib.HardFusions' amd if a fusion is stored then the fusion record is
##  returned without tests, and a message is printed.
##
CTblLib.Test.SubgroupFusion:= function( sub, tbl )
    local fusrec, fusid, spec, tom, pos, perms, pi, storedfus, time, fus,
          filt, fusreps, filtreps, fus_c, reducedby, someexcluded, map,
          incompat, entry, pair, pairstring, fusreps_c, filt_c, filtreps_c,
          comp, libinfo, changedtext, reducedbyBrauer, reducedbyfactors,
          result;

    fusrec:= First( ComputedClassFusions( sub ),
                    r -> r.name = Identifier( tbl ) );
    fusid:= Concatenation( Identifier( sub ), " -> ", Identifier( tbl ) );

    # Verify a specification of the kind 'tom:<n>'.
    if fusrec <> fail and IsBound( fusrec.specification ) then
      spec:= fusrec.specification;
      if IsString( spec ) and 4 < Length( spec )
                          and spec{ [ 1 .. 4 ] } = "tom:" then
        tom:= TableOfMarks( tbl );
        pos:= Int( spec{ [ 5 .. Length( spec ) ] } );
        if tom = fail then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
              [ [ "specification ", spec, " but no table of marks?" ] ] );
        elif not IsInt( pos ) then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
              [ [ "strange specification ", spec ] ] );
        elif not HasFusionToTom( tbl ) then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
              "no fusion to tom" );
        else
          perms:= PermCharsTom( tbl, tom );
          if Length( perms ) < pos then
            CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
                [ [ "specification ", spec, ": too few trans. perm. char." ] ] );
          else
            pi:= InducedClassFunctionsByFusionMap( sub, tbl,
                     [ TrivialCharacter( sub ) ], fusrec.map )[1];
            if not pi in perms then
              CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
                  "stored fusion does not fit to any trans. perm. char." );
            elif pi <> perms[ pos ] then
              CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
                [ [ "specification ", spec, " is wrong, store `tom:",
                    Position( perms, pi ), "' instead" ] ] );
#T might be another position ...
            fi;
          fi;
        fi;
      fi;
    fi;
#T propose a specification tom:<n> when the image knows its table of marks!

    # Shall the test be omitted?
    if [ Identifier( sub ), Identifier( tbl ) ] in CTblLib.HardFusions then
      if fusrec = fail then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
            "omitting fusion check (no map stored)" );
      else
        # At least test the existing map for consistency.
        fus:= PossibleClassFusions( sub, tbl,
                  rec( fusionmap:= fusrec.map ) );
        if IsEmpty( fus ) then
          CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
              "stored fusion is wrong" );
        else
          CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
              "omitting fusion check" );
        fi;
      fi;
      return fusrec;
    fi;

    # If the fusion must be interpreted relative to another one
    # then do not test anything else.
    if CTblLib.Test.SubgroupFusionOfMaximalSubgroup( sub, tbl ) then
      return fusrec;
    fi;

    if fusrec = fail then
      fusrec:= rec();
      storedfus:= fail;
    else
      storedfus:= fusrec.map;
    fi;

    # Recompute the possible class fusions.
    time:= Runtime();
    fus:= PossibleClassFusions( sub, tbl );
    time:= Runtime() - time;
    fusreps:= RepresentativesFusions( sub, fus, tbl );
    filt:= CTblLib.Test.Decompositions( sub, fus, tbl );
    filtreps:= RepresentativesFusions( sub, filt, tbl );

    # Amend the statistics if wanted.
    CTblLib.AmendFusionsStatistics(
        [ sub, tbl, fus, fusreps, filt, filtreps, time ] );

    # We may have no fusion at all.
    if   IsEmpty( fus ) then
      CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
          "no fusion possible" );
      return false;
    elif IsEmpty( filt ) then
      CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
          "no fusion compatible with Brauer tables" );
      return false;
    fi;

    # Consider factor fusions.
    # We use the information about factors for preferably choosing a map
    # from the set of compatible fusions,
    # and for printing warnings in case of inconsistencies,
    # but stored incompatible fusions are not automatically replaced.
    fus_c:= [];
    reducedby:= [ [], [] ];
    someexcluded:= false;
    for map in fus do
      incompat:= rec( badfusions:= [], someexcluded:= false );
      CTblLib.AddIncompatibleFusionsOfFactors( sub, tbl, map, incompat );
      someexcluded:= someexcluded or incompat.someexcluded;
      if IsEmpty( incompat.badfusions ) then
        Add( fus_c, map );
      else
        for entry in incompat.badfusions do
          pairstring:= Concatenation( [ "  ", Identifier( entry[3] ),
                                      " -> ", Identifier( entry[4] ),
                                      " (w.r.t. ", Identifier( entry[1] ),
                                      " -> ", Identifier( entry[2] ), ")" ] );
          if not pairstring in reducedby[1] then
            Add( reducedby[1], pairstring );
            Add( reducedby[2], entry{ [ 3, 4 ] } );
          fi;
        od;
        SortParallel( reducedby[1], reducedby[2] );
      fi;
    od;
    fusreps_c:= RepresentativesFusions( sub, fus_c, tbl );
    filt_c:= Filtered( fus_c, x -> x in filt );
    filtreps_c:= RepresentativesFusions( sub, filt_c, tbl );

    # We may have no consistent fusion.
    if IsEmpty( fus_c ) then
      CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
          Concatenation( [ "no fusion consistent with" ], reducedby[1] ) );
      # Propose changing the fusion between factors.
      for pair in reducedby[2] do
        if GetFusionMap( sub, pair[1] ) <> fail and
           GetFusionMap( tbl, pair[2] ) <> fail then
          comp:= Set( List( filt, map -> CompositionMaps(
                               GetFusionMap( tbl, pair[2] ),
                               CompositionMaps( map, InverseMap( GetFusionMap(
                                   sub, pair[1] ) ) ) ) ) );
          # Consider only those fusions that map the kernel compatibly.
          comp:= Filtered( comp, x -> ForAll( x, IsInt ) );
          libinfo:= LibInfoCharacterTable( pair[1] );
          if libinfo = fail then
            libinfo:= "no library file";
          else
            libinfo:= libinfo.fileName;
          fi;
          if Length( comp ) = 1 then
            CTblLib.PrintTestLog( "I", "CTblLib.Test.SubgroupFusion", fusid,
              "replace fusion of factors by the following unique fusion",
              Concatenation( "(in ", libinfo, ")" ) );
            Print( LibraryFusion( pair[1], rec( name:= pair[2], map:= comp[1],
                     text:= Concatenation(
                       "unique map that is compatible with ",
                       Identifier( sub ), " -> ", Identifier( tbl ) ) ) ) );
          elif Length( RepresentativesFusions( pair[1], comp, pair[2] ) )
               = 1 then
            CTblLib.PrintTestLog( "I", "CTblLib.Test.SubgroupFusion", fusid,
              "perhaps replace fusion of factors by the following (u.t.a.)",
              Concatenation( "(in ", libinfo, ")" ) );
            Print( LibraryFusion( pair[1], rec( name:= pair[2], map:= comp[1],
                     text:= Concatenation(
                       "compatible with ",
                       Identifier( sub ), " -> ", Identifier( tbl ) ) ) ) );
          else
            CTblLib.PrintTestLog( "I", "CTblLib.Test.SubgroupFusion", fusid,
              "perhaps replace fusion of factors by the following (ambig.)",
              Concatenation( "(in ", libinfo, ")" ) );
            Print( LibraryFusion( pair[1], rec( name:= pair[2], map:= comp[1]
                       ) ) );
          fi;
        fi;
      od;
      return false;
    elif IsEmpty( filt_c ) then
      CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
          Concatenation( [ "no fusion compatible with Brauer tables ",
                           "and consistent with" ], reducedby[1] ) );
      return false;
    fi;

    # Now we have consistent candidates.
    # Check whether the stored text mentions (only) the reductions used.
    # (The words ``factorization'' and ``factors through'' are allowed.)
    changedtext:= false;
    reducedbyBrauer:= IsBound( fusrec.text ) and
        PositionSublist( fusrec.text, "Brauer" ) <> fail;
    reducedbyfactors:= IsBound( fusrec.text ) and
        PositionSublist( ReplacedString( ReplacedString( fusrec.text,
            "factori", "" ), "factors through", "" ), "factor" ) <> fail;

    if Length( filt ) < Length( fus ) then
      # We have to choose a fusion that is compatible with Brauer tables,
      # perhaps the Brauer tables resolve ambiguities.
      if not reducedbyBrauer then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
            "text should mention reduction by Brauer tables" );
        changedtext:= true;
      fi;
    elif reducedbyBrauer then
      CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
          "text needs not mention reduction by Brauer tables" );
      changedtext:= true;
    fi;
    if Length( filt_c ) < Length( filt ) then
      # In addition to the compatibility with Brauer tables,
      # we have to choose a fusion that is compatible with factor tables,
      # perhaps the factor tables resolve ambiguities.
      if not reducedbyfactors then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
            "text should mention reduction by fusions of factors" );
        changedtext:= true;
      fi;
    elif reducedbyfactors then
      CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
          "text needs not mention reduction by fusions of factors" );
      changedtext:= true;
    fi;

    # Check whether the stored fusion is one of the candidates.
    if storedfus <> fail then
      if not storedfus in filt then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
            "stored fusion is wrong" );
      elif not storedfus in filt_c then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
            Concatenation( [
                "stored fusion not compatible with factor embeddings" ],
                reducedby[1] ) );
      fi;
    fi;

    # Check the uniqueness status, which should be mentioned in the text.
    if Length( fus ) = 1 then
      # The fusion is unique.
      if IsBound( fusrec.text )
         and fusrec.text <> "fusion map is unique"
         and ( Length( fusrec.text ) < 21 or
               fusrec.text{ [ 1 .. 21 ] } <> "fusion map is unique," )
         and ( Length( fusrec.text ) < 22 or
               fusrec.text{ [ 1 .. 22 ] } <> "fusion map is unique (" ) then
        CTblLib.PrintTestLog( "E", "CTblLib.Test.SubgroupFusion", fusid,
            "text for stored fusion is wrong (map is unique!)" );
        changedtext:= true;
      fi;
      result:= rec( name := Identifier( tbl ),
                    map  := fus[1],
                    text := "fusion map is unique" );
--> --------------------

--> maximum size reached

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

[ Dauer der Verarbeitung: 0.131 Sekunden  ]