# Some steps in the verification of the ordinary character table of the Baby Monster group

### THOMAS BREUER, KAY MAGAARD, ROBERT A. WILSON

### May 17th, 2019

We show the details of certain computations that are described in [BMW20].

# Contents

1 Overview
2 Verification of a presentation for B
3 Invariants that distinguish conjugacy classes of B
4 Centralizers of elements of prime order
5 The character table of 21+22.Co2
6 Conjugacy classes of B and their centralizer orders
7 The irreducible characters of B
8 Appendix: Standardizing the generators of Co2
9 Appendix: Words for generators of the kernel 222
10 Appendix: Words for class representatives of 222.Co2
11 Appendix: About the character table of 29+16.S8(2)

## 1 Overview

The aim of [BMW20] is to verify the ordinary character table of the Baby Monster group B. Here we collect, in the form of an explicit GAP [GAP24] session protocol, the computations that are needed in that paper.
We proceed as follows.
Section 2 shows the computations that are described in [BMW20,Section 3]. At this point, we know that the three matrix groups that are used later on are in fact representations of the group B, w. r. t. compatible (standard) generators.
Section 3 turns the class invariants and the power map information from [BMW20,Section 4] into a GAP function that identifies the class label of a given word in terms of the given standard generators.
Section 4 shows part of the computations described in [BMW20,Section 5].
Section 5 shows the computation of the character table of an involution centralizer of type 21+22.Co2 in B.
Section 6 shows how the conjugacy classes, the corresponding centralizer orders, and the power maps of B are determined.
In Section 6, we put these pieces together and write down the list of class representatives of B, together with their centralizer orders and power maps.
With this information and with the (already verified) character tables of some known subgroups of B, computing the irreducible characters of B is then easy; this corresponds to [BMW20,Section 7], and is done in Section 7.
We will use the GAP Character Table Library and the interface to the ATLAS of Group Representations [WWT+], thus we load these GAP packages.

        gap> LoadPackage( "ctbllib", false );
        true
        gap> LoadPackage( "atlasrep", false );
        true

The MAGMA [BCP97] system will be needed for computing a character table and for several conjugacy tests. If the following command returns `false` then these steps will not work.

        gap> CTblLib.IsMagmaAvailable();
        true

## 2 Verification of a presentation for B

We show the computations that are described in [BMW20,Section 4]. First we create the free generators and relators of the presentation.

        gap> F:= FreeGroup( List( "abcdefghijk", x -> [ x ] ) );;
        gap> gens:= GeneratorsOfGroup( F );;
        gap> rels:= List( gens, x -> x^2 );;
        gap> ord3pairs:= List( [ 1 .. 7 ], i -> [ i, i+1 ] );
        [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ], [ 5, 6 ], [ 6, 7 ], [ 7, 8 ] ]
        gap> Append( ord3pairs, [ [ 5, 9 ], [ 9, 10 ], [ 10, 11 ] ] );
        gap> for pair in ord3pairs do
        >      Add( rels, ( gens[ pair[1] ] * gens[ pair[2] ] )^3 );
        >    od;
        gap> for i in [ 1 .. 11 ] do
        >      for j in [ i+1 .. 11 ] do
        >        if not [ i, j ] in ord3pairs then
        >          Add( rels, ( gens[i] * gens[j] )^2 );
        >        fi;
        >      od;
        >    od;
        gap> Add( rels, Product( gens{ [ 5, 4, 3, 5, 6, 7, 5, 9, 10 ] } )^10 );

We do not call `FreeGroup( 11 )` because later on we want to translate the relators into straight line programs, and we can use `StraightLineProgram` with first argument a string only if no generator name is a prefix of another generator name.

        gap> gensstrings:= List( gens, String );;
        gap> relsslps:= List( List( rels, String ),
        >                     x -> StraightLineProgram( x, gensstrings ) );;

Next we write a straight line program that computes the 11 generators t1, …, t11, following the steps shown in [BMW20,Table 1]. We start with the two standard generators a and b, say, in the slots 1 and 2, and compute expressions for the subsequent slots. The product a b will be in position 3, its 5th power (a b)5 (which will be needed later on) in position 4, the power (a b)15 in position 5, and d = (a b)15 b in position 6. The generators t11 = d19 gets stored in position 7.

        gap> slp:= [ [ 1, 1, 2, 1 ], [ 3, 5 ], [ 4, 3 ], [ 5, 1, 2, 1 ] ];;
        gap> resultpos:= [];;
        gap> Add( slp, [ 6, 19 ] );
        gap> resultpos[11]:= Length( slp ) + 2;;

Next we compute c = (a t11)3 (position 9), e = ((c d3)10)d (position 13), ...

        gap> Append( slp, [ [ 1, 1, 7, 1 ], [ 8, 3 ] ] );
        gap> Append( slp, [ [ 6, 3 ], [ 9, 1, 10, 1 ], [ 11, 10 ],
        >                   [ 6, -1, 12, 1, 6, 1 ] ] );

... t1 = f = ((((e c)6 c (e c)3 )2 e c e2 c)5)((e c)4) (position 24), ...

        gap> # 14: e*c,  15: (e*c)^2,  16: (e*c)^3,  17: (e*c)^4,  18: (e*c)^6
        gap> Append( slp, [ [ 13, 1, 9, 1 ], [ 14, 2 ], [ 14, 1, 15, 1 ],
        >                   [ 15, 2 ], [ 16, 2 ] ] );
        gap> # 19: e*c*e,  20: e*c*e^2*c
        gap> Append( slp, [ [ 14, 1, 13, 1 ], [ 19, 1, 14, 1 ] ] );
        gap> # 21: (e*c)^6*c*(e*c)^3,  22: ((e*c)^6*c*(e*c)^3)^2*e*c*e^2*c
        gap> Append( slp, [ [ 18, 1, 9, 1, 16, 1 ], [ 21, 2, 20, 1 ] ] );
        gap> # 23: (((e*c)^6*c*(e*c)^3)^2*e*c*e^2*c)^5
        gap> Append( slp, [ [ 22, 5 ] ] );
        gap> # 24: t1 = f = ((((e*c)^6*c*(e*c)^3)^2*e*c*e^2*c)^5)^((e*c)^4)
        gap> Append( slp, [ [ 17, -1, 23, 1, 17, 1 ] ] );
        gap> resultpos[1]:= Length( slp ) + 2;;

... g = ((e c)8 c (e c)3 )(e c e2 c)2 (position 27) and t2 = fgf (position 30), ...

        gap> # 25: (e*c)^8*c*(e*c)^3,  26: (e*c*e^2*c)^2
        gap> Append( slp, [ [ 15, 1, 21, 1 ], [ 20, 2 ] ] );
        gap> # 27: g = ((e*c)^8*c*(e*c)^3)^((e*c*e^2*c)^2)
        gap> Append( slp, [ [ 26, -1, 25, 1, 26, 1 ] ] );
        gap> # 28: g f,  29: g^-1,  30: t2 = f^{{g f}}
        gap> Append( slp, [ [ 27, 1, 24, 1 ], [ 27, -1 ],
        >                   [ 28, -1, 24, 1, 28, 1 ] ] );
        gap> resultpos[2]:= Length( slp ) + 2;;

... t3 = fg f g, t4 = fg f g2, t5 = fg f g3, t6 = fg f g4, t7 = fg f g5, t8 = fg f g6 (positions 31 to 36), ...

        gap> # 31: t3 = f^( g * f * g )
        gap> Append( slp, [ [ 29, 1, 30, 1, 27, 1 ] ] );
        gap> resultpos[3]:= Length( slp ) + 2;;
        gap> # 32: t4 = f^( g * f * g^2 )
        gap> Append( slp, [ [ 29, 1, 31, 1, 27, 1 ] ] );
        gap> resultpos[4]:= Length( slp ) + 2;;
        gap> # 33: t5 = f^( g * f * g^3 )
        gap> Append( slp, [ [ 29, 1, 32, 1, 27, 1 ] ] );
        gap> resultpos[5]:= Length( slp ) + 2;;
        gap> # 34: t6 = f^( g * f * g^4 )
        gap> Append( slp, [ [ 29, 1, 33, 1, 27, 1 ] ] );
        gap> resultpos[6]:= Length( slp ) + 2;;
        gap> # 35: t7 = f^( g * f * g^5 )
        gap> Append( slp, [ [ 29, 1, 34, 1, 27, 1 ] ] );
        gap> resultpos[7]:= Length( slp ) + 2;;
        gap> # 36: t8 = f^( g * f * g^6 )
        gap> Append( slp, [ [ 29, 1, 35, 1, 27, 1 ] ] );
        gap> resultpos[8]:= Length( slp ) + 2;;

... p = ((a b)5 t11 (a b)−5 t1 (a b)5)−1 (position 38) and i = dp (position 41), ...

        gap> # 37: (a*b)^5*t11*(a*b)^-5*t1*(a*b)^5,
        gap> # 38: p = ((a*b)^5*t11*(a*b)^-5*t1*(a*b)^5)^-1
        gap> Append( slp, [ [ 4, 1, 7, 1, 4, -1, 24, 1, 4, 1 ], [ 37, -1 ] ] );
        gap> # 39: p^-1,  40: h = c^p, 41: i = d^p
        gap> Append( slp, [ [ 38, -1 ], [ 39, 1, 9, 1, 38, 1 ],
        >                   [ 39, 1, 6, 1, 38, 1 ] ] );

... j = [t5i2, t3 t4] (position 45) and k = [t5i5, t3 t4] (position 48), ...

        gap> # 42: i^2,  43: t5^(i^2),  44: t3*t4
        gap> Append( slp, [ [ 41, 2 ], [ 42, -1, 33, 1, 42, 1 ],
        >                   [ 31, 1, 32, 1 ] ] );
        gap> # 45: j = Comm( t5^( i^2 ), t3*t4 )
        gap> Append( slp, [ [ 43, -1, 44, -1, 43, 1, 44, 1 ] ] );
        gap> # 46: i^3,  47: t5^(i^5),  48: k = Comm( t5^(i^5), t3*t4 )
        gap> Append( slp, [ [ 41, 1, 42, 1 ], [ 46, -1, 43, 1, 46, 1 ],
        >                   [ 47, -1, 44, -1, 47, 1, 44, 1 ] ] );

... l = [t8j k, t6 t7] [t8k j, t6 t7] (position 57), ...

        gap> # 49: t6*t7,  50: (t6*t7)^-1,  51: j*k,  52: k*j,  53: t8^(j*k)
        gap> Append( slp, [ [ 34, 1, 35, 1 ], [ 49, -1 ], [ 45, 1, 48, 1 ],
        >                   [ 48, 1, 45, 1 ], [ 51, -1, 36, 1, 51, 1 ] ] );
        gap> # 54: Comm( t8^(j*k), t6*t7),  55: t8^(k*j)
        gap> Append( slp, [ [ 53, -1, 50, 1, 53, 1, 49, 1 ] ] );
        gap> Append( slp, [ [ 52, -1, 36, 1, 52, 1 ] ] );
        gap> # 56: Comm( t8^(k*j), t6*t7 )
        gap> Append( slp, [ [ 55, -1, 50, 1, 55, 1, 49, 1 ] ] );
        gap> # 57: l = Comm( t8^(j*k), t6*t7 ) * Comm( t8^(k*j), t6*t7 )
        gap> Append( slp, [ [ 54, 1, 56, 1 ] ] );

... l3 = [t8(j k)4, t6 t7] (position 61), l4 = t8(j k)3 k j (position 62), l5 = (l l3 l4)3 l3 l4 (position 65), ...

        gap> # 58: (j*k)^3,  59: (j*k)^-3,  60: t8^((j*k)^4)
        gap> Append( slp, [ [ 51, 3 ], [ 58, -1 ], [ 59, 1, 53, 1, 58, 1 ] ] );
        gap> # 61: l3 = Comm( t8^((j*k)^4), t6*t7 )
        gap> Append( slp, [ [ 60, -1, 50, 1, 60, 1, 49, 1 ] ] );
        gap> # 62: l4 = t8^((j*k)^3*k*j)
        gap> Append( slp, [ [ 52, -1, 59, 1, 36, 1, 58, 1, 52, 1 ] ] );
        gap> # 63: l3*l4,  64: l*l3*l4
        gap> Append( slp, [ [ 61, 1, 62, 1 ], [ 57, 1, 63, 1 ] ] );
        gap> # 65: l5:= ( l * l3 * l4 )^3 * l3 * l4;;
        gap> Append( slp, [ [ 64, 3, 63, 1 ] ] );

... m2 = l4l54 (position 67), m3 = m2l5 (position 68), t10 = m3 m2 l4 m2 m3 (position 69), and t9 = l4 m2 t10 m2 l4 (position 70).

        gap> # 66: l5^4,  67: m2 = l4^(l5^4)
        gap> Append( slp, [ [ 65, 4 ], [ 66, -1, 62, 1, 66, 1 ] ] );
        gap> # 68: m3 = m2^l5
        gap> Append( slp, [ [ 65, -1, 67, 1, 65, 1 ] ] );
        gap> # 69: t10 = m3*m2*l4*m2*m3
        gap> Append( slp, [ [ 68, 1, 67, 1, 62, 1, 67, 1, 68, 1 ] ] );
        gap> resultpos[10]:= Length( slp ) + 2;;
        gap> # 70: t9 = l4*m2*t10*m2*l4
        gap> Append( slp, [ [ 62, 1, 67, 1, 69, 1, 67, 1, 62, 1 ] ] );
        gap> resultpos[9]:= Length( slp ) + 2;;

Finally, we specify the list of outputs, and create the straight line program object.

        gap> Add( slp, List( resultpos, x -> [ x, 1 ] ) );
        gap> slp:= StraightLineProgram( slp, 2 );
        <straight line program>

And now we compute, for each of the three pairs of generators we are interested in, the 11 generators, and test whether these generators satisfy the presentation.

        gap> b_2:= AtlasGroup( "B", Characteristic, 2, Dimension, 4370 );;
        gap> b_3:= AtlasGroup( "B", Characteristic, 3, Dimension, 4371 );;
        gap> b_5:= AtlasGroup( "B", Characteristic, 5, Dimension, 4371 );;
        gap> gens_2:= GeneratorsOfGroup( b_2 );;
        gap> gens_3:= GeneratorsOfGroup( b_3 );;
        gap> gens_5:= GeneratorsOfGroup( b_5 );;
        gap> res_2:= ResultOfStraightLineProgram( slp, gens_2 );;
        gap> ForAll( relsslps,
        >            prg -> IsOne( ResultOfStraightLineProgram( prg, res_2 ) ) );
        true
        gap> res_3:= ResultOfStraightLineProgram( slp, gens_3 );;
        gap> ForAll( relsslps,
        >            prg -> IsOne( ResultOfStraightLineProgram( prg, res_3 ) ) );
        true
        gap> res_5:= ResultOfStraightLineProgram( slp, gens_5 );;
        gap> ForAll( relsslps,
        >            prg -> IsOne( ResultOfStraightLineProgram( prg, res_5 ) ) );
        true

In order to prove that the 11 elements that satisfy the relations generate the same group as the original generators, we create a straight line program that computes the elements a′, b′ stated in [BMW20,Section 4.4], first the elements r and s (positions 12 and 13), ...

        gap> revslp:= [ Concatenation( List( [ 1 .. 8 ], i -> [ i, 1 ] ) ),
        >        Concatenation( List( [ 5, 9, 10, 11 ], i -> [ i, 1 ] ) ) ];;

... and then a′ = (r7 s)15 (position 15) and b′ = (t1 t2)(sr)10 (position 20).

        gap> Append( revslp, [ [ 12, 7, 13, 1 ], [ 14, 15 ], [ 13, 1, 12, 1 ],
        >                      [ 16, 10 ], [ 17, -1 ], [ 1, 1, 2, 1 ],
        >                      [ 18, 1, 19, 1, 17, 1 ] ] );

Again, we specify the outputs and create the straight line program object.

        gap> Add( revslp, List( [ 15, 20 ], x -> [ x, 1 ] ) );
        gap> revslp:= StraightLineProgram( revslp, 11 );
        <straight line program>

We claim that, for the three representations in question, evaluating the straight line program `revslp` at the 11 generators yields a pair a′, b′ of matrices that is simultaneously conjugate to the original matrices a, b. Once this is established, we know that the group 〈a, b 〉 is equal to the group generated by the 11 generators, and that mapping the original generators of any of the three representations to the original generators of another one defines a group isomorphism.
In order to show the conjugacy property, we use that the nullspace of w(a, b) = a0 + a b + b a + b is 1-dimensional, in all three cases.

        gap> a:= gens_2[1];; b:= gens_2[2];;
        gap> w:= One( a ) + b*a + a*b + b;;
        gap> nsp_2:= NullspaceMat( w );; Length( nsp_2 );
        1
        gap> a:= gens_3[1];; b:= gens_3[2];;
        gap> w:= One( a ) + b*a + a*b + b;;
        gap> nsp_3:= NullspaceMat( w );; Length( nsp_3 );
        1
        gap> a:= gens_5[1];; b:= gens_5[2];;
        gap> w:= One( a ) + b*a + a*b + b;;
        gap> nsp_5:= NullspaceMat( w );; Length( nsp_5 );
        1

The standard basis w. r. t. given generators and a vector v is defined by starting with the list b = [ v ] and iteratively adding those images of the vectors in b under the right multiplication with the generators that increase the dimension of the vector space generated by b. (Since such a function is apparently not available in GAP's MeatAxe, we provide it here.)

        gap> StdBasis:= function( F, mats, seed )
        >      local n, b, mb, v, m, new;
        >
        >      n:= Length( mats[1] );
        >      b:= [ seed ];
        >      mb:= MutableBasis( F, b );
        >      for v in b do
        >        for m in mats do
        >          new:= v * m;
        >          if not IsContainedInSpan( mb, new ) then
        >            Add( b, new );
        >            if Length( b ) = n then
        >              break;
        >            fi;
        >            CloseMutableBasis( mb, new );
        >          fi;
        >        od;
        >        if Length( b ) = n then
        >          break;
        >        fi;
        >      od;
        >      return b;
        >    end;;

All we have to check is that the matrices of the linear mappings a, b w. r. t. their standard basis and a generating vector of the nullspace of w(a, b) are equal to the matrices of a′, b′ w. r. t. their standard basis and a generating vector of the nullspace of w(a′, b′).
We verify this in characteristic 2, ...

        gap> stdbas_2:= StdBasis( GF(2), gens_2, nsp_2[1] );;
        gap> inv:= stdbas_2^-1;;
        gap> stdgens_2:= List( gens_2, m -> stdbas_2 * m * inv );;
        gap> newgens_2:= ResultOfStraightLineProgram( revslp, res_2 );;
        gap> aa:= newgens_2[1];;  bb:= newgens_2[2];;
        gap> neww:= One( aa ) + bb * aa + aa * bb + bb;;
        gap> newnsp_2:= NullspaceMat( neww );;  Length( newnsp_2 );
        1
        gap> newstdbas_2:= StdBasis( GF(2), newgens_2, newnsp_2[1] );;
        gap> inv:= newstdbas_2^-1;;
        gap> newstdgens_2:= List( newgens_2, m -> newstdbas_2 * m * inv );;
        gap> stdgens_2 = newstdgens_2;
        true

... in characteristic 3, ...

        gap> stdbas_3:= StdBasis( GF(3), gens_3, nsp_3[1] );;
        gap> inv:= stdbas_3^-1;;
        gap> stdgens_3:= List( gens_3, m -> stdbas_3 * m * inv );;
        gap> newgens_3:= ResultOfStraightLineProgram( revslp, res_3 );;
        gap> aa:= newgens_3[1];;  bb:= newgens_3[2];;
        gap> neww:= One( aa ) + bb * aa + aa * bb + bb;;
        gap> newnsp_3:= NullspaceMat( neww );;  Length( newnsp_3 );
        1
        gap> newstdbas_3:= StdBasis( GF(3), newgens_3, newnsp_3[1] );;
        gap> inv:= newstdbas_3^-1;;
        gap> newstdgens_3:= List( newgens_3, m -> newstdbas_3 * m * inv );;
        gap> stdgens_3 = newstdgens_3;
        true

... and in characteristic 5.

        gap> stdbas_5:= StdBasis( GF(5), gens_5, nsp_5[1] );;
        gap> inv:= stdbas_5^-1;;
        gap> stdgens_5:= List( gens_5, m -> stdbas_5 * m * inv );;
        gap> newgens_5:= ResultOfStraightLineProgram( revslp, res_5 );;
        gap> aa:= newgens_5[1];;  bb:= newgens_5[2];;
        gap> neww:= One( aa ) + bb * aa + aa * bb + bb;;
        gap> newnsp_5:= NullspaceMat( neww );;  Length( newnsp_5 );
        1
        gap> newstdbas_5:= StdBasis( GF(5), newgens_5, newnsp_5[1] );;
        gap> inv:= newstdbas_5^-1;;
        gap> newstdgens_5:= List( newgens_5, m -> newstdbas_5 * m * inv );;
        gap> stdgens_5 = newstdgens_5;
        true

## 3 Invariants that distinguish conjugacy classes of B

The function `IdentifyClassName` shown below implements the invariants defined in [BMW20,Section 5], that distinguish 183 conjugacy classes of B.
Its input can be as follows.

- Three matrices `data2`, `data3`, `data5`, representing an element of B in the given three matrix representations, in characteristics 2, 3, and 5, respectively; in this case, the argument `slp` should be `fail`.
- Lists `data2`, `data3`, `data5` of standard generators of B in the given three matrix representations such that `slp` is a straight line program that takes these generators as inputs, and computes the element in question.

In both cases, the argument `order` can be either `fail` or the order of the element. A known order allows us to omit any computation with matrices in several cases.
The output is the label for the union of conjugacy classes as defined in [BMW20,Table 2], except that labels containing two letters are returned in those cases that will later turn out to describe two Galois conjugate classes -these are `"23AB"`, `"30GH"`, `"31AB"`, `"32AB"`, `"32CD"`, `"34BC"`, `"46AB"`, `"47AB"`, `"56AB"`- and in the case of `"16DF"` where we have no invariant that distinguishes two classes that are not Galois conjugate.

        gap> IdentifyClassName:= function( data2, data3, data5, slp, order )
        >      local data, mats, elm, cand, nams, trace, pos, one, rank;
        >
        >      data:= [ , data2, data3,, data5 ];
        >      mats:= [];
        >
        >      elm:= function( p )
        >        if not IsBound( mats[p] ) then
        >          if slp = fail then
        >            mats[p]:= data[p];
        >          else
        >            mats[p]:= ResultOfStraightLineProgram( slp, data[p] );
        >          fi;
        >        fi;
        >        return mats[p];
        >      end;
        >
        >      if order = fail then
        >        order:= Order( elm(2) );
        >      fi;
        >
        >      # The element order suffices in certain cases.
        >      if order in [ 23, 31, 46, 47, 56 ] then
        >        # There are two Galois conjugate classes of elements of this order.
        >        return Concatenation( String( order ), "AB" );
        >      elif order in [ 1, 7, 11, 13, 17, 19, 21, 25, 27, 33, 35, 38, 39,
        >                      44, 47, 52, 55, 66, 70 ] then
        >        # There is exactly one conjugacy class of elements of this order.
        >        return Concatenation( String( order ), "A" );
        >      fi;
        >
        >      if order in [ 3, 5, 9, 15 ] then
        >        # The trace in the 2-modular representation suffices.
        >        cand:= [ [3,1], [3,0], [5,0], [5,1], [9,0], [9,1], [15,0], [15,1] ];
        >        nams:= [ "3A", "3B", "5A", "5B", "9A", "9B", "15A", "15B" ];
        >        trace:= Int( TraceMat( elm(2) ) );
        >        return nams[ Position( cand, [ order, trace ] ) ];
        >      elif order mod 4 = 2 then
        >        # Compute the rank of 1 + x.
        >        cand:= [ [ 2, 1860 ], [ 2, 2048 ], [ 2, 2158 ], [ 2, 2168 ],
        >                 [ 6, 3486 ], [ 6, 3510 ], [ 6, 3566 ], [ 6, 3534 ],
        >                 [ 6, 3606 ], [ 6, 3604 ], [ 6, 3596 ], [ 6, 3610 ],
        >                 [ 6, 3636 ], [ 6, 3638 ], [ 6, 3634 ],
        >                 [ 10, 3860 ], [ 10, 3896 ], [ 10, 3918 ], [ 10, 3908 ],
        >                 [ 10, 3920 ], [ 10, 3932 ],
        >                 [ 14, 3996 ], [ 14, 4008 ], [ 14, 4048 ], [ 14,4034 ],
        >                 [ 14, 4052 ],
        >                 [ 18, 4088 ], [ 18, 4090 ], [ 18, 4110 ], [ 18, 4124 ],
        >                 [ 18, 4128 ], [ 18, 4122 ],
        >                 [ 22, 4140 ], [ 22, 4158 ],
        >                 [ 26, 4198 ], [ 26, 4176 ],
        >                 [ 30, 4190 ], [ 30, 4212 ], [ 30, 4206 ], [ 30, 4214 ],
        >                 [ 30, 4224 ], [ 30, 4216 ],
        >                 [ 34, 4238 ], [ 34, 4220 ],
        >                 [ 42, 4242 ], [ 42, 4258 ] ];
        >        nams:= [ "2A", "2B", "2C", "2D",
        >                 "6A", "6B", "6C", "6D", "6E", "6F",
        >                 "6G", "6H", "6I", "6J", "6K",
        >                 "10A", "10B", "10C", "10D", "10E", "10F",
        >                 "14A", "14B", "14C", "14D", "14E",
        >                 "18A", "18B", "18C", "18D", "18E", "18F",
        >                 "22A", "22B",
        >                 "26A", "26B",
        >                 "30AB", "30C", "30D", "30E", "30F", "30GH",
        >                 "34A", "34BC",
        >                 "42AB", "42C" ];
        >        one:= elm(2)^0;
        >        rank:= RankMat( elm(2) + one );
        >        pos:= Position( cand, [ order, rank ] );
        >        if nams[ pos ] = "30AB" then
        >          rank:= RankMat( elm(2)^5 + one );
        >          if   rank = 3510 then return "30A";
        >          elif rank = 3486 then return "30B";
        >          else Error( "wrong rank" );
        >          fi;
        >        elif nams[ pos ] = "42AB" then
        >          rank:= RankMat( elm(2)^3 + one );
        >          if   rank = 3996 then return "42A";
        >          elif rank = 4008 then return "42B";
        >          else Error( "wrong rank" );
        >          fi;
        >        else
        >          return nams[ pos ];
        >        fi;
        >      elif order in [ 36, 60 ] then
        >        cand:= [ [36,4226],[36,4238],[36,4248],[60,4280],[60,4286],[60,4296] ];
        >        nams:= [ "36A", "36B", "36C", "60A", "60B", "60C" ];
        >        one:= elm(2)^0;
        >        rank:= RankMat( elm(2) + one );
        >        return nams[ Position( cand, [ order, rank ] ) ];
        >      elif order = 28 then
        >        one:= elm(2)^0;
        >        rank:= RankMat( elm(2) + one );
        >        trace:= Int( TraceMat( elm(3)^7 ) );
        >        if rank = 4188 then   # 28A or 28C
        >          if   trace = 0 then return "28A";
        >          elif trace = 1 then return "28C";
        >          else Error( "wrong trace" );
        >          fi;
        >        elif rank = 4200 then   # 28B or 28D
        >          if   trace = 1 then return "28B";
        >          elif trace = 0 then return "28D";
        >          else Error( "wrong trace" );
        >          fi;
        >        elif rank = 4210 then return "28E";
        >        else Error( "wrong rank" );
        >        fi;
        >      elif order = 32 then
        >        trace:= Int( TraceMat( elm(3)^2 ) );
        >        if   trace = 2 then return "32AB";
        >        elif trace = 0 then return "32CD";
        >        else Error( "wrong trace" );
        >        fi;
        >      elif order = 40 then
        >        one:= elm(2)^0;
        >        rank:= RankMat( elm(2) + one );
        >        if rank = 4242 then   # 40A or 40B 0r 40C
        >          trace:= Int( TraceMat( elm(3) ) );
        >          if   trace = 0 then return "40A";
        >          elif trace = 1 then return "40B";
        >          else return "40C";
        >          fi;
        >        elif rank = 4250 then return "40D";
        >        elif rank = 4258 then return "40E";
        >        else Error( "wrong rank" );
        >        fi;
        >      elif order = 48 then
        >        trace:= Int( TraceMat( elm(3) ) );
        >        if   trace = 0 then return "48A";
        >        elif trace = 1 then return "48B";
        >        else Error( "wrong trace" );
        >        fi;
        >      elif order in [ 4, 8 ] then
        >        cand:= [ [4,3114],[4,3192],[4,3256],[4,3202],[4,3204],[4,3266],
        >                 [4,3264],[8,3774],[8,3738],[8,3778],[8,3780],[8,3810],
        >                 [8,3786],[8,3812],[8,3818] ];
        >        nams:= [ "4A-B", "4C-D", "4E", "4F", "4G", "4H-J", "4I",
        >                 "8A", "8B-C-E", "8D", "8F-H", "8G", "8I-L", "8J", "8K-M-N" ];
        >        one:= elm(2)^0;
        >        rank:= RankMat( elm(2) + one );
        >        pos:= Position( cand, [ order, rank ] );
        >        if not '-' in nams[ pos ] then
        >          return nams[ pos ];
        >        elif order = 4 then
        >          trace:= Int( TraceMat( elm(3) ) );
        >          if trace = 0 then
        >            if   nams[ pos ] = "4A-B" then return "4B";
        >            elif nams[ pos ] = "4C-D" then return "4D";
        >            else return "4H";
        >            fi;
        >          elif trace = 1 then
        >            if   nams[ pos ] = "4A-B" then return "4A";
        >            elif nams[ pos ] = "4C-D" then return "4C";
        >            else return "4J";
        >            fi;
        >          else
        >            Error( "wrong trace" );
        >          fi;
        >        elif order = 8 then
        >          if nams[ pos ] = "8B-C-E" then
        >            trace:= Int( TraceMat( elm(3) ) );
        >            if   trace = 1 then return "8B";
        >            elif trace = 0 then return "8C";
        >            else return "8E";
        >            fi;
        >          elif nams[ pos ] = "8F-H" then
        >            rank:= RankMat( ( elm(2) + one )^3 );
        >            if   rank = 2619 then return "8F";
        >            elif rank = 2620 then return "8H";
        >            else Error( "wrong rank" );
        >            fi;
        >          elif nams[ pos ] = "8I-L" then
        >            rank:= RankMat( ( elm(2) + one )^2 );
        >            if   rank = 3202 then return "8I";
        >            elif rank = 3204 then return "8L";
        >            else Error( "wrong rank" );
        >            fi;
        >          else   # 8K-M-N
        >            rank:= RankMat( ( elm(2) + one )^3 );
        >            if rank = 2714 then   # 8K-M
        >              trace:= Int( TraceMat( elm(3) ) );
        >              if   trace = 1 then return "8K";
        >              elif trace = 2 then return "8M";
        >              else Error( "wrong trace" );
        >              fi;
        >            elif rank = 2717 then return "8N";
        >            else Error( "wrong rank" );
        >            fi;
        >          fi;
        >        fi;
        >      elif order in [ 12, 24 ] then
        >        cand:= [ [12,3936],[12,3942],[12,3958],[12,3996],[12,3962],[12,3964],
        >                 [12,3986],[12,3978],[12,3966],[12,4000],[12,3982],[12,3988],
        >                 [12,4002],[12,4004],[24,4152],[24,4164],[24,4170],[24,4182],
        >                 [24,4176],[24,4178],[24,4174],[24,4186] ];
        >        nams:= [ "12A-C-D", "12B", "12E", "12F", "12G-H", "12I", "12J",
        >                 "12K-M", "12L", "12N", "12O", "12P", "12Q-R-T", "12S",
        >                 "24A-B-C-D", "24E-G", "24F", "24H", "24I-M", "24J", "24K",
        >                 "24L-N" ];
        >        one:= elm(2)^0;
        >        rank:= RankMat( elm(2) + one );
        >        pos:= Position( cand, [ order, rank ] );
        >        if not '-' in nams[ pos ] then
        >          return nams[ pos ];
        >        elif order = 12 then
        >          if nams[ pos ] = "12A-C-D" then
        >            trace:= Int( TraceMat( elm(5) ) );
        >            if   trace = 3 then return "12A";
        >            elif trace = 4 then return "12C";
        >            elif trace = 1 then return "12D";
        >            else Error( "wrong trace" );
        >            fi;
        >          else
        >            trace:= Int( TraceMat( elm(3) ) );
        >            if trace = 0 then
        >              if   nams[ pos ] = "12G-H" then return "12H";
        >              elif nams[ pos ] = "12K-M" then return "12K";
        >              else return "12Q";  # 12Q-R-T
        >              fi;
        >            elif trace = 1 then
        >              if   nams[ pos ] = "12G-H" then return "12G";
        >              elif nams[ pos ] = "12K-M" then return "12M";
        >              else return "12R";  # 12Q-R-T
        >              fi;
        >            elif nams[ pos ] = "12Q-R-T" then
        >              return "12T";
        >            else
        >              Error( "wrong trace" );
        >            fi;
        >          fi;
        >        elif order = 24 then
        >          if nams[ pos ] = "24I-M" then
        >            rank:= RankMat( elm(2)^2 + one );
        >            if   rank = 3986 then return "24I";
        >            elif rank = 3982 then return "24M";
        >            else Error( "wrong rank" );
        >            fi;
        >          elif nams[ pos ] = "24E-G" then
        >            rank:= RankMat( elm(2)^3 + one );
        >            if   rank = 3774 then return "24E";
        >            elif rank = 3778 then return "24G";
        >            else Error( "wrong rank" );
        >            fi;
        >          elif nams[ pos ] = "24L-N" then
        >            trace:= Int( TraceMat( elm(3) ) );
        >            if   trace = 1 then return "24L";
        >            elif trace = 2 then return "24N";
        >            else Error( "wrong trace" );
        >            fi;
        >          else   # 24A-B-C-D"
        >            trace:= Int( TraceMat( elm(5) ) );
        >            if   trace = 3 then return "24A";
        >            elif trace = 0 then return "24B";
        >            elif trace = 2 then return "24C";
        >            elif trace = 1 then return "24D";
        >            else Error( "wrong trace" );
        >            fi;
        >          fi;
        >        fi;
        >      elif order in [ 16, 20 ] then
        >        cand:= [ [ 16, 4072 ], [ 16, 4074 ], [ 16, 4094 ],
        >                 [ 20, 4114 ], [ 20, 4128 ], [ 20, 4132 ], [ 20, 4148 ],
        >                 [ 20, 4144 ], [ 20, 4138 ], [ 20, 4150 ] ];
        >        nams:= [ "16A-B", "16C-D-E-F", "16G-H",
        >                 "20A-B-C-D", "20E", "20F", "20G", "20H", "20I", "20J" ];
        >        one:= elm(2)^0;
        >        rank:= RankMat( elm(2) + one );
        >        pos:= Position( cand, [ order, rank ] );
        >        if not '-' in nams[ pos ] then
        >          return nams[ pos ];
        >        elif order = 20 then
        >          rank:= RankMat( elm(2)^2 + one );
        >          if   rank = 3908 then return "20B";
        >          elif rank <> 3896 then Error( "wrong rank" );
        >          else
        >            trace:= Int( TraceMat( elm(3) ) );
        >            if   trace = 2 then return "20A";
        >            elif trace = 0 then return "20C";
        >            elif trace = 1 then return "20D";
        >            else Error( "wrong trace" );
        >            fi;
        >          fi;
        >        else   # order = 16
        >          if nams[ pos ] = "16A-B" then
        >            trace:= Int( TraceMat( elm(3) ) );
        >            if   trace = 0 then return "16A";
        >            elif trace = 1 then return "16B";
        >            else Error( "wrong trace" );
        >            fi;
        >          elif nams[ pos ] = "16G-H" then
        >            trace:= Int( TraceMat( elm(3)^2 ) );
        >            if   trace = 1 then return "16G";
        >            elif trace = 2 then return "16H";
        >            else Error( "wrong trace" );
        >            fi;
        >          else   # 16C-D-E-F
        >            trace:= Int( TraceMat( elm(3) ) );
        >            if trace = 0 then   # We cannot distinguish 16D and 16F.
        >              return "16DF";
        >            elif trace = 2 then   # 16C-E
        >              one:= elm(2)^0;
        >              rank:= RankMat( elm(2)^2 + one );
        >              if   rank = 3780 then return "16C";
        >              elif rank = 3778 then return "16E";
        >              else Error( "wrong rank" );
        >              fi;
        >            else
        >              Error( "wrong trace" );
        >            fi;
        >          fi;
        >        fi;
        >      else Error( "wrong element order" );
        >      fi;
        >  end;;

Elements of B to which the labels belong can be generated as follows. The straight line program `"BG1-cycW1"` from [WWT+] computes generators of the maximally cyclic subgroups of B.

        gap> cycprg:= AtlasProgram( "B", "cyclic" );;
        gap> cycprg.identifier;
        [ "B", "BG1-cycW1", 1 ]
        gap> cycprg.outputs;
        [ "12A", "12H", "12I", "12L", "12P", "12S", "12T", "16E", "16F", "16G",
          "16H", "18A", "18B", "18D", "18F", "20B", "20C", "20H", "20I", "20J",
          "24A", "24B", "24C", "24D", "24E", "24F", "24H", "24I", "24J", "24K",
          "24L", "24M", "24N", "25A", "26B", "27A", "28A", "28C", "28D", "28E",
          "30A", "30B", "30C", "30E", "30G-H", "31A-B", "32A-B", "32C-D", "34A",
          "34BC", "36A", "36B", "36C", "38A", "39A", "40A", "40B", "40C", "40D",
          "40E", "42A", "42B", "42C", "44A", "46AB", "47AB", "48A", "48B", "52A",
          "55A", "56AB", "60A", "60B", "60C", "66A", "70A" ]

The remaining representatives are obtained as suitable powers of them. The following list encodes the definition of these powers, see [BMW20,Table 2].

        gap> DefinitionsViaPowerMaps:= [
        >     [ "70A", 2, "35A" ], [ "66A", 2, "33A" ], [ "60A", 2, "30D" ],
        >     [ "60C", 2, "30F" ], [ "56AB", 2, "28B" ], [ "52A", 2, "26A" ],
        >     [ "48B", 2, "24G" ], [ "46AB", 2, "23AB" ], [ "44A", 2, "22B" ],
        >     [ "66A", 3, "22A" ], [ "42C", 2, "21A" ], [ "40E", 2, "20G" ],
        >     [ "40D", 2, "20F" ], [ "60B", 3, "20E" ], [ "60A", 3, "20A" ],
        >     [ "40C", 2, "20D" ], [ "38A", 2, "19A" ], [ "36C", 2, "18E" ],
        >     [ "36B", 2, "18C" ], [ "34A", 2, "17A" ], [ "32CD", 2, "16DF" ],
        >     [ "32AB", 2, "16C" ], [ "48B", 3, "16B" ], [ "48A", 3, "16A" ],
        >     [ "30F", 2, "15B" ], [ "30A", 2, "15A" ], [ "28E", 2, "14E" ],
        >     [ "28A", 2, "14D" ], [ "42A", 3, "14A" ], [ "42B", 3, "14B" ],
        >     [ "42C", 3, "14C" ], [ "26A", 2, "13A" ], [ "24N", 2, "12R" ],
        >     [ "24M", 2, "12O" ], [ "24L", 2, "12Q" ], [ "24K", 2, "12M" ],
        >     [ "24J", 2, "12J" ], [ "24H", 2, "12F" ], [ "24G", 2, "12G" ],
        >     [ "24D", 2, "12D" ], [ "36C", 3, "12N" ], [ "36B", 3, "12K" ],
        >     [ "36A", 3, "12B" ], [ "60A", 5, "12C" ], [ "60B", 5, "12E" ],
        >     [ "22B", 2, "11A" ], [ "20J", 2, "10F" ], [ "20I", 2, "10D" ],
        >     [ "20H", 2, "10C" ], [ "20F", 2, "10B" ], [ "30A", 3, "10A" ],
        >     [ "30E", 3, "10E" ], [ "18F", 2, "9B" ], [ "18E", 2, "9A" ],
        >     [ "16H", 2, "8M" ], [ "16G", 2, "8K" ], [ "16DF", 2, "8H" ],
        >     [ "16E", 2, "8D" ], [ "24J", 3, "8J" ], [ "24M", 3, "8I" ],
        >     [ "24I", 3, "8G" ], [ "24K", 3, "8F" ], [ "24C", 3, "8E" ],
        >     [ "24B", 3, "8C" ], [ "24A", 3, "8B" ], [ "24E", 3, "8A" ],
        >     [ "24N", 3, "8N" ], [ "40D", 5, "8L" ], [ "14D", 2, "7A" ],
        >     [ "12T", 2, "6K" ], [ "12S", 2, "6J" ], [ "12R", 2, "6I" ],
        >     [ "12P", 2, "6H" ], [ "12O", 2, "6G" ], [ "12I", 2, "6C" ],
        >     [ "18A", 3, "6D" ], [ "30B", 5, "6A" ], [ "30A", 5, "6B" ],
        >     [ "30E", 5, "6E" ], [ "30C", 5, "6F" ], [ "12C", 3, "4A" ],
        >     [ "10F", 2, "5B" ], [ "10B", 2, "5A" ], [ "8N", 2, "4J" ],
        >     [ "8M", 2, "4H" ], [ "8L", 2, "4G" ], [ "8J", 2, "4E" ],
        >     [ "8I", 2, "4F" ], [ "8H", 2, "4C" ], [ "8E", 2, "4B" ],
        >     [ "12E", 3, "4D" ], [ "12T", 3, "4I" ], [ "6K", 2, "3B" ],
        >     [ "6A", 2, "3A" ], [ "4J", 2, "2D" ], [ "4I", 2, "2C" ],
        >     [ "4A", 2, "2B" ], [ "6A", 3, "2A" ], [ "2B", 2, "1A" ],
        >    ];;

The following function takes a label and the straight line program data shown above, and returns a straight line program for computing an element for the given label from standard generators of B.

        gap> SLPForClassName:= function( nam, cycslp, outputnames )
        >      local pos, rule;
        >
        >      pos:= Position( outputnames, nam );
        >      if pos <> fail then
        >        return RestrictOutputsOfSLP( cycslp.program, pos );
        >      fi;
        >
        >      rule:= First( DefinitionsViaPowerMaps, x -> x[3] = nam );
        >      if rule = fail then
        >        Error( "'nam' is not an admiss. name for a cyclic subgroup of B" );
        >      fi;
        >
        >      return CompositionOfStraightLinePrograms(
        >                 StraightLineProgram( [ [ 1, rule[2] ] ], 1 ),
        >                 SLPForClassName( rule[1], cycslp, outputnames ) );
        > end;;

Let us verify that `IdentifyClassName` computes the claimed labels.

        gap> outputnames:= List( cycprg.outputs,
        >                        x -> ReplacedString( x, "-", "" ) );;
        gap> outputnames:= List( outputnames,
        >                        x -> ReplacedString( x, "16F", "16DF" ) );;
        gap> labels:= Union( outputnames,
        >                    List( DefinitionsViaPowerMaps, x -> x[3] ) );;
        gap> for l in labels do
        >      slp:= SLPForClassName( l, cycprg, outputnames );
        >      id:= IdentifyClassName( gens_2, gens_3, gens_5, slp, fail );
        >      if id <> l then
        >        Print( "#E  problem with identification: ", id, " vs. ", l, "\n" );
        >      fi;
        >    od;

As we get no outputs, the identification is correct.
For later use, we collect power map information for the labels. In order to simplify later loops, we sort the labels w. r. t. increasing element order.

        gap> SortParallel( List( labels, x -> Int( Filtered( x, IsDigitChar ) ) ),
        >                  labels );
        gap> powerinfo:= [];;
        gap> for l in labels do
        >      slp:= SLPForClassName( l, cycprg, outputnames );
        >      ord:= Int( Filtered( l, IsDigitChar ) );
        >      pow:= [];
        >      if not ( IsPrimeInt( ord ) or ord = 1 ) then
        >        for p in Set( Factors( ord ) ) do
        >          powerslp:= CompositionOfStraightLinePrograms(
        >                         StraightLineProgram( [ [ 1, p ] ], 1 ), slp );
        >          id:= IdentifyClassName( gens_2, gens_3, gens_5, powerslp,
        >                                  ord / p );
        >          Add( pow, [ p, id ] );
        >        od;
        >      fi;
        >      Add( powerinfo, [ l, pow ] );
        >    od;

## 4 Centralizers of elements of prime order

We document part of the computations needed in [BMW20,Section 5].
We know from [Str76] that B has exactly four classes of involutions, whose normalizers in B have the following properties.

- The normalizer H of a `2A` involution has the structure 2.2E6(2).2, such that H is an extension of its derived subgroup H′ by a field automorphism of order two, see [Str76,p. 505]. This implies that H is a split extension of H′, and this means that the character table of H is the one that is shown in the ATLAS of Finite Groups and that has the identifier `"2.2E6(2).2"` in GAP's library of character tables; note that the isoclinic variant of H is a non-split extension of 2.2E6(2).
- The normalizer C of a `2B` involution has the structure 21+22.Co2. Its character table has been constructed in Section 5.
- The normalizer of a `2C` involution is a subdirect product of F4(2).2 and a dihedral group D8 of order eight, see [Str76,La. 3.1, 5.6]. The character table of this group can easily be constructed character-theoretically from the known character tables of F4(2), F4(2).2, 22, and D8.
- The normalizer of a `2D` involution has order 11 689 182 992 793 600 and is contained in the normalizer in B of an elementary abelian group of order 28. The character table of the latter normalizer has been computed from a subgroup of B, see Appendix 11. This table has a unique class with centralizer order equal to the order of the normalizer of a `2D` element in B.

Let H ≅ 2.2E6(2).2 be a `2A` centralizer in B. The involution classes in H are as stated in the table in [BMW20,Section 5.1].

        gap> h:= CharacterTable( "2.2E6(2).2" );;
        gap> invpos:= Positions( OrdersClassRepresentatives( h ), 2 );
        [ 2, 3, 4, 5, 6, 7, 175, 176, 177, 178 ]
        gap> ClassNames( h ){ invpos };
        [ "2a", "2b", "2c", "2d", "2e", "2f", "2g", "2h", "2i", "2j" ]
        gap> AtlasClassNames( h ){ invpos };
        [ "1A_1", "2A_0", "2A_1", "2B_0", "2B_1", "2C_0", "2D_0", "2D_1", "2E_0",
          "2E_1" ]

(The subscripts 0 and 1 that appear above are denoted by signs + and − in [BMW20].)
Let us try to compute the necessary information about the permutation action of B on the cosets of H, restricted to H. We calculate the first three transitive constituents of the permutation character as listed in[BMW20,Section 5.2].
The point stabilizer of the action of H on the first orbit contains the centre Z(H) of H, thus we may perform the computations with H/Z(H). If we assume that the rank of the permutation character is 5 then we can compute the possible degrees of the irreducible constituents combinatorially, as follows.

        gap> f:= CharacterTable( "2E6(2).2" );;
        gap> index1:= 3968055;;
        gap> constit:= Filtered( Irr( f ), x -> x[1] <= index1 );;
        gap> degrees:= Set( constit, x -> x[1] );
        [ 1, 1938, 48620, 554268, 815100, 1322685, 1828332, 2089164, 2909907, 2956096
         ]
        gap> lincom:= Filtered( UnorderedTuples( degrees, 5 ),
        >                       x -> Sum( x ) = index1 );
        [ [ 1, 1938, 48620, 1828332, 2089164 ] ]

The degrees are uniquely determined. Now we compute which sums of irreducibles of these degrees have nonnegative values.

        gap> degrees:= lincom[1];
        [ 1, 1938, 48620, 1828332, 2089164 ]
        gap> constit:= List( degrees, d -> Filtered( constit, x -> x[1] = d ) );;
        gap> cand1:= List( Cartesian( constit ), Sum );;
        gap> cand1:= Filtered( cand1, x -> ForAll( x, y -> y >= 0 ) );;
        gap> List( ConstituentsOfCharacter( cand1[1] ),
        >          x -> Position( Irr( f ), x ) );
        [ 1, 3, 5, 13, 15 ]

Thus this permutation character is uniquely determined. Alternatively, we can ask GAP to compute the possible permutation characters of the given degree, and get the same result (without a priori knowledge about the rank of the permutation action).

        gap> PermComb( f, rec( degree:= index1 ) ) = cand1;
        true

We compute the permutation character of the action on the second orbit in two steps. First we induce the trivial character of F4(2) to H and then we compute the unique subcharacter of this character that has the right degree and only nonnegative values.

        gap> u:= CharacterTable( "F4(2)" );;
        gap> ufush:= PossibleClassFusions( u, h );;
        gap> ind:= Set( ufush,
        >               map -> InducedClassFunctionsByFusionMap( u, h,
        >                          [ TrivialCharacter( u ) ], map )[1] );;
        gap> Length( ind );
        1
        gap> const:= ConstituentsOfCharacter( ind[1] );;
        gap> Sum( const ) = ind[1];
        true
        gap> sub:= List( Cartesian( List( const, x -> [ Zero( x ), x ] ) ), Sum );;
        gap> cand2:= Filtered( sub,
        >                      x -> x[1] = ind[1][1] / 4 and Minimum( x ) >= 0 );;
        gap> Length( cand2 );
        1
        gap> List( ConstituentsOfCharacter( cand2[1] ),
        >          x -> Position( Irr( h ), x ) );
        [ 1, 5, 17, 24 ]

The character table of the point stabilizer Fi22.2 of the action of H on the third orbit is available. We compute the corresponding permutation character by inducing the trivial character of Fi22.2 to H. Note that the class fusion from Fi22.2 to H is unique up to the group automorphism of H that multiplies the elements outside the derived subgroup of H by the central involution in H; we know that the class `2D` of the point stabilizer lies in a class of H that fuses into the class `2A` of B, thus the first of the two fusions is the right one.

        gap> s:= CharacterTable( "Fi22.2" );;
        gap> sfush:= PossibleClassFusions( s, h );;
        gap> Length( sfush );
        2
        gap> Positions( OrdersClassRepresentatives( s ), 2 );
        [ 2, 3, 4, 60, 61, 62 ]
        gap> List( sfush, x -> x[60] );
        [ 175, 176 ]
        gap> cand3:= InducedClassFunctionsByFusionMap( s, h,
        >                [ TrivialCharacter( s ) ], sfush[1] );;
        gap> SortedList( List( ConstituentsOfCharacter( cand3[1] ),
        >                      x -> Position( Irr( h ), x ) ) );
        [ 1, 3, 5, 13, 17, 28, 49, 76, 190, 192, 196, 202, 210, 217 ]

Next we compute the value of the permutation character of the action on the fourth orbit listed in[BMW20,Section 5.2] on the class `3C` of H. The point stabilizer is H5 ≅ 21+20.U4(3).22, thus the subgroup H5 Z(H) / Z(H) of H / Z(H) ≅ 2E6(2).2 lies in a maximal subgroup of type (21+20:U6(2)).2, see [CCN+85,p. 191].
Because the extension of 21+20 by U6(2) splits, we know that H5 Z(H) / Z(H) has U4(3) type subgroups, thus H5 has subgroups of one of the types U4(3), 2.U4(3). Computing possible class fusions from both possibilities to H, we get that the class of elements of order 3 in U4(3) or 2.U4(3) that belongs to the `3A` class of U4(3) lies in the class `3C` of H, and the other classes of 3-elements lie in the classes `3A` or `3B` of H, as claimed.

        gap> h:= CharacterTable( "2.2E6(2).2" );;
        gap> u:= CharacterTable( "U4(3)" );;
        gap> Positions( OrdersClassRepresentatives( h ), 3 );
        [ 8, 10, 12 ]
        gap> ufush:= PossibleClassFusions( u, h );;
        gap> 3pos:= Positions( OrdersClassRepresentatives( u ), 3 );
        [ 3, 4, 5, 6 ]
        gap> Set( ufush, x -> x{ 3pos } );
        [ [ 12, 8, 10, 10 ], [ 12, 10, 8, 10 ] ]
        gap> u:= CharacterTable( "2.U4(3)" );;
        gap> ufush:= PossibleClassFusions( u, h );;
        gap> 3pos:= Positions( OrdersClassRepresentatives( u ), 3 );
        [ 5, 7, 9, 11 ]
        gap> Set( ufush, x -> x{ 3pos } );
        [ [ 12, 8, 10, 10 ], [ 12, 10, 8, 10 ] ]

The `3A` elements in U4(3) have centralizer order 23 ·36 in this group. The centralizer order in 220.U4(3) is 25 ·36, since the fixed space of a `3A` element on the unique 20 dimensional irreducible module in characteristic 2 has dimension 2 -this can be read off from the fact that the Brauer character value on the class `3A` is −7.

        gap> u:= CharacterTable( "U4(3)" ) mod 2;;
        gap> phi:= Filtered( Irr( u ), x -> x[1] = 20 );;
        gap> Display( u, rec( chars:= phi, powermap:= false ) );
        U4(3)mod2

             2  7  3  2  2  .  .  .  .  .  .  .  .
             3  6  6  5  5  4  .  .  .  3  3  3  3
             5  1  .  .  .  .  1  .  .  .  .  .  .
             7  1  .  .  .  .  .  1  1  .  .  .  .

               1a 3a 3b 3c 3d 5a 7a 7b 9a 9b 9c 9d

        Y.1    20 -7  2  2  2  . -1 -1 -1 -1 -1 -1

Now the centralizer order gets doubled in the central extension to 21+20.U4(3), and the two upwards extensions cannot fuse the `3A` class with another class, thus the centralizer order is again doubled in each case, which means that |CH5(`3A`)| = 28 ·36.
The permutation character of the action of B on the cosets of H has the value 1 620 on the class `3C` of H.

        gap> 3pos:= Positions( OrdersClassRepresentatives( f ), 3 );;
        gap> val3C:= 1 + cand1[1][ 3pos[3] ];;
        gap> 3pos:= Positions( OrdersClassRepresentatives( h ), 3 );;
        gap> val3C:= val3C + cand2[1][ 3pos[3] ] + cand3[1][ 3pos[3] ];;
        gap> val3C:= val3C + SizesCentralizers( h )[ 3pos[3] ] / ( 2^8 * 3^6 );
        1620

Thus we have computed |CB(`3B`)| = 213 ·313 ·5.

        gap> Collected( Factors( val3C * SizesCentralizers( h )[ 3pos[3] ] ) );
        [ [ 2, 13 ], [ 3, 13 ], [ 5, 1 ] ]

## 5 The character table of 21+22.Co2

Let z be an involution in B whose class is called `2B` in the ATLAS of Finite Groups. The centralizer C of z in B has the structure 21+22.Co2, the construction of its character table is described in [Pah07], and this table is available in GAP. However, that paper assumes the knowledge of the character table of B, hence we are not allowed to use the known character table of C in the verification of the character table of B.
In this section, we recompute the character table of C, as follows. We start with the three certified matrix representations of B in characteristic 2, 3, and 5, and with the straight line program for restricting these representations to a `2B` centralizer.
First we standardize the generators of the subgroup such that the known straight line program for computing class representatives of Co2 can be applied. Next we compute a permutation representation of degree 4600 for the factor group C / 〈z 〉. Using this representation, we construct a straight line program that computes class representatives of C / 〈Z 〉 from the images of the given generators of C under the natural epimorphism. Applying this straight line program to the restrictions of the given representations of B and then computing the class labels in B yields a "preliminary class fusion´´ from C to B. Furthermore, the given matrix representations of C in characteristic 3 and 5 have a unique faithful constituent, which lifts to the unique ordinary irreducible character of degree 2048 of C. Hence the Brauer characters of the two representations yield most of the values of this ordinary character. Together with the character table of C / 〈Z 〉 that can be computed by MAGMA [BCP97] from the permutation representation, this information suffices to complete the character table of C.
Using the given degree 4371 matrix representation of B over the field with three elements (verified as described in Section 2) and the description from [WWT+] how to restrict this representation to (a conjugate of) C, we compute generators of C.

        gap> slp_maxes_2:= AtlasProgram( "B", "maxes", 2 );;
        gap> cgens_3:= ResultOfStraightLineProgram( slp_maxes_2.program,
        >                  GeneratorsOfGroup( b_3 ) );;

The composition factors of the 4371 dimensional module for C have the dimensions 23, 2300, and 2048, respectively. The kernels of the actions of C on these factors will turn out to have the orders 223, 2, and 1, respectively.

        gap> m:= GModuleByMats( cgens_3, GF(3) );;
        gap> cf_3:= MTX.CompositionFactors( m );;
        gap> SortParallel( List( cf_3, x -> x.dimension ), cf_3 );
        gap> List( cf_3, x -> x.dimension );
        [ 23, 2048, 2300 ]

We use the action on the 23-dimensional module to find words in terms of the generators of C that act on this module as standard generators of Co2, as defined in [Wil96].
For that, we find two elements a, b that generate Co2 and lie in the conjugacy classes `2A` and `5A`, respectively, such that the product a b has order 28.
Let us call the generators in the 23-dimensional composition factor x and y, and set c = y (y x)3. Then the elements y12, c−1 ((y4 x)4) c are standard generators of Co2, see Appendix 8. The following straight line program computes these elements when it is applied to x and y.

        gap> slp_co2:= StraightLineProgram( [
        >     [ 2, 1, 1, 1 ], [ 2, 2 ], [ 4, 1, 1, 1 ], [ 4, 2 ],
        >     [ 6, 1, 1, 1 ], [ 7, 4 ], [ 3, 2 ], [ 5, 1, 9, 1 ],
        >     [ 10, -1 ], [ 6, 3 ], [ 11, 1, 8, 1 ], [ 13, 1, 10, 1 ],
        >     [ [ 12, 1 ], [ 14, 1 ] ] ], 2 );;
        gap> f:= FreeGroup( "x", "y" );;  x:= f.1;;  y:= f.2;;
        gap> words:= ResultOfStraightLineProgram( slp_co2, [ x, y ] );;
        gap> words = [ y^12, ((y^4*x)^4)^(y*(y*x)^3) ];
        true
        gap> co2gens:= cf_3[1].generators;;
        gap> co2stdgens:= ResultOfStraightLineProgram( slp_co2, co2gens );;

Next we find an orbit of length 4600 under the action of C on the 2300-dimensional module. This will allow us to represent C/〈z 〉 as a permutation group P, say, of degree 4600.
Note that Co2 contains a maximal subgroup of the structure U6(2).2, of index 2300, and that there is a 221.U6(2).2 type subgroup of C that fixes a 1-dimensional subspace in the given 2300-dimensional representation of C over the field with three elements. We can compute generators for (a sufficiently large subgroup of) 222.U6(2).2 by applying the straight line program for computing generators of a U6(2).2 type subgroup from standard generators of Co2. Then we find a vector in the 1-dimensional subspace as the common fixed vector of squares of commutators in this subgroup.

        gap> fgens:= cf_3[3].generators;;
        gap> fstdgens:= ResultOfStraightLineProgram( slp_co2, fgens );;
        gap> slp_co2m1:= AtlasProgram( "Co2", "maxes", 1 );;
        gap> ugens:= ResultOfStraightLineProgram( slp_co2m1.program, fstdgens );;
        gap> one:= ugens[1]^0;;
        gap> comm:= Comm( ugens[1], ugens[2] );;
        gap> Order( comm );
        12
        gap> pow:= comm^2;;
        gap> mats:= List( [ pow, pow^ugens[1] ], x -> x - one );;
        gap> nsp:= List( mats, NullspaceMat );;
        gap> List( nsp, Length );
        [ 434, 434 ]
        gap> si:= SumIntersectionMat( nsp[1], nsp[2] );;
        gap> Length( si[2] );
        1
        gap> orb:= Orbit( Group( fstdgens ), si[2][1] );;
        gap> Length( orb );
        4600
        gap> orb:= SortedList( orb );;
        gap> stdperms:= List( fstdgens, x -> Permutation( x, orb ) );;
        gap> List( stdperms, Order );
        [ 2, 5 ]

In P, we compute a basis for the elementary abelian normal subgroup N of order 222, and the following words for the basis vectors in terms of the standard generators of Co2, see Appendix 9.

(b2 a)9,
(b a b)9,
(a b2)9,
(b2 a b)9,
(b a b2)9,
((a b)2 a)9,
(a b (b a)2)9,
((a b)2 b a)9,
(b3 (b a)2)4,
(b (b2 a)2)4,
(b2 a b3 a)4,
(b a b4 a)4,
(b2 (b a)2 b)4,
((b2 a)2 b)4,
(b a b3 a b)4,
(b (b a)2 b2)4,
((b a b)2 b)4,
((b2 a)2 b a)12,
(b (b a)2 b2 a)12,
((b a b)2 b a)12,
((b a b)2 a b)12,
((b2 a)2 b a b a)12

The following straight line program computes these basis vectors when it is applied to x and y.

        gap> slp_ker:= StraightLineProgram( [
        >     [ 1, 1, 2, 1 ], [ 2, 1, 1, 1 ], [ 2, 2 ], [ 4, 1, 2, 1 ],
        >     [ 2, 1, 4, 1 ], [ 3, 1, 2, 1 ], [ 3, 2 ], [ 4, 2 ], [ 6, 2 ],
        >     [ 7, 2 ], [ 2, 1, 10, 1 ], [ 2, 1, 6, 1 ], [ 10, 1, 2, 1 ],
        >     [ 5, 1, 3, 1 ], [ 4, 1, 5, 1 ], [ 9, 1, 1, 1 ], [ 3, 1, 10, 1 ],
        >     [ 9, 1, 4, 1 ], [ 5, 1, 13, 1 ], [ 2, 1, 12, 1 ], [ 14, 1, 7, 1 ],
        >     [ 17, 1, 7, 1 ], [ 5, 1, 15, 1 ], [ 12, 1, 2, 1 ], [ 6, 1, 14, 1 ],
        >     [ 13, 1, 5, 1 ], [ 11, 1, 2, 1 ], [ 12, 1, 4, 1 ], [ 13, 1, 7, 1 ],
        >     [ 11, 1, 4, 1 ], [ 11, 1, 3, 1 ], [ 12, 1, 10, 1 ],
        >     [ [ 7, 9 ], [ 6, 9 ], [ 8, 9 ], [ 16, 9 ], [ 17, 9 ], [ 18, 9 ],
        >       [ 19, 9 ], [ 20, 9 ], [ 21, 4 ], [ 22, 4 ], [ 23, 4 ], [ 24, 4 ],
        >       [ 25, 4 ], [ 26, 4 ], [ 27, 4 ], [ 28, 4 ], [ 29, 4 ], [ 30, 12 ],
        >       [ 31, 12 ], [ 32, 12 ], [ 33, 12 ], [ 34, 12 ] ] ], 2 );;
        gap> f:= FreeGroup( "a", "b" );;  a:= f.1;;  b:= f.2;;
        gap> ResultOfStraightLineProgram( slp_ker, [ a, b ] );
        [ (b^2*a)^9, (b*a*b)^9, (a*b^2)^9, (b^2*a*b)^9, (b*a*b^2)^9, ((a*b)^2*a)^9,
          (a*b*(b*a)^2)^9, ((a*b)^2*b*a)^9, (b^3*(b*a)^2)^4, (b*(b^2*a)^2)^4,
          (b^2*a*b^3*a)^4, (b*a*b^4*a)^4, (b^2*(b*a)^2*b)^4, ((b^2*a)^2*b)^4,
          (b*a*b^3*a*b)^4, (b*(b*a)^2*b^2)^4, ((b*a*b)^2*b)^4, ((b^2*a)^2*b*a)^12,
          (b*(b*a)^2*b^2*a)^12, ((b*a*b)^2*b*a)^12, ((b*a*b)^2*a*b)^12,
          ((b^2*a)^2*b*a*b*a)^12 ]

the straight line program that is available in [WWT+] computes class representatives of Co2 from standard generators.

        gap> slp_co2classreps:= AtlasProgram( "Co2", "classes" );;

Hence we can describe representatives of the 388 classes of P as products of the class representatives of Co2 and suitable elements of N. The necessary computations are described in Appendix 10.
The list `classrepsinfo` contains 60 entries; the i-th entry describes the preimage classes of the i-th class of Co2, by listing the positions of those basis vectors that can be multiplied with the i-th output of `slp_co2classreps` in order to get the class representatives in question.
(The ordering of the representatives fits to the ordering of the classes in the relevant factor of the library character table of C. The entry `[ 0 ]` means that the identity matrix is taken instead of the representative.)

        gap> classrepsinfo:= [
        >  [  "1A", [ [ 0 ], [ 3, 4, 22 ], [  ], [ 8 ], [ 2 ], [ 1 ] ] ],
        >  [  "2A", [ [ 1, 6, 8, 9, 11 ], [ 12 ], [ 1, 4, 9, 10, 19 ],
        >             [ 5, 7 ], [ 1, 9 ], [ 1, 16 ], [  ], [ 1 ], [ 5 ],
        >             [ 1, 5, 18 ], [ 3 ] ] ],
        >  [  "2B", [ [ 1, 3, 4, 17, 20 ], [ 2, 14, 17 ], [ 2, 10, 22 ],
        >             [ 14, 21 ], [  ], [ 9 ], [ 1, 5 ], [ 10, 11 ], [ 6, 12 ],
        >             [ 1 ], [ 2 ] ] ],
        >  [  "2C", [ [ 4, 11, 13, 20 ], [ 1, 10, 12, 17 ], [ 3, 18, 21 ],
        >             [ 8, 13, 17 ], [ 16, 22 ], [ 4, 5, 15 ], [ 8, 13 ],
        >             [ 1, 10 ], [ 1, 21 ], [ 3, 9, 18, 21 ], [ 8 ],
        >             [ 1, 10, 12 ], [ 4 ], [ 21 ], [ 6 ], [ 1 ], [  ] ] ],
        >  [  "3A", [ [  ], [ 21 ], [ 1 ] ] ],
        >  [  "3B", [ [ 17, 20 ], [ 17 ], [ 1, 5, 17 ], [ 3, 18 ], [ 1 ], [ 10 ],
        >             [ 12 ], [ 3 ], [  ] ] ],
        >  [  "4A", [ [ 11 ], [  ], [ 1 ], [ 1, 12 ], [ 2 ] ] ],
        >  [  "4B", [ [ 2, 7, 19, 20 ], [ 2, 12 ], [ 9, 21 ], [ 5 ], [  ],
        >             [ 14 ], [ 9, 20 ], [ 2, 14 ], [ 6 ], [ 1 ], [ 8 ], [ 2 ] ] ],
        >  [  "4C", [ [ 3, 7, 11 ], [ 18, 22 ], [ 4, 6 ], [ 21 ], [ 2, 17, 18 ],
        >             [ 1, 14 ], [ 5, 12, 17 ], [ 14 ], [ 3, 4 ], [ 3 ], [ 10 ],
        >             [ 2 ], [ 1 ], [  ] ] ],
        >  [  "4D", [ [ 20 ], [ 9, 22 ], [ 8, 16 ], [ 6 ], [ 6, 17 ], [  ],
        >             [ 1 ] ] ],
        >  [  "4E", [ [ 11, 19 ], [ 3, 7 ], [ 1, 22 ], [ 1, 2, 22 ], [ 1, 10 ],
        >             [ 3, 18 ], [  ], [ 5, 20 ], [ 2 ], [ 1, 15 ], [ 1 ], [ 3 ],
        >             [ 8 ] ] ],
        >  [  "4F", [ [ 3, 4 ], [ 3, 19, 21 ], [ 4, 16 ], [ 4, 13 ], [ 3, 7 ],
        >             [ 4 ], [ 3 ], [ 4, 17 ], [ 13 ], [ 1, 7 ], [  ], [ 1, 9 ],
        >             [ 4, 14 ], [ 17 ], [ 19 ], [ 1 ], [ 1, 4 ], [ 18 ],
        >             [ 11 ] ] ],
        >  [  "4G", [ [ 10 ], [ 1, 4 ], [ 8 ], [ 20 ], [  ], [ 16 ], [ 1 ],
        >             [ 2 ] ] ],
        >  [  "5A", [ [  ], [ 1 ] ] ],
        >  [  "5B", [ [  ], [ 1 ], [ 2 ], [ 19 ], [ 15, 19 ], [ 4 ], [ 8 ] ] ],
        >  [  "6A", [ [ 2 ], [  ], [ 1 ] ] ],
        >  [  "6B", [ [  ], [ 21 ], [ 1, 11 ], [ 1 ], [ 10 ] ] ],
        >  [  "6C", [ [ 8 ], [ 1, 14 ], [  ], [ 9 ], [ 1 ], [ 10 ], [ 2 ],
        >             [ 3 ] ] ],
        >  [  "6D", [ [  ], [ 19 ], [ 12, 17 ], [ 1, 11 ], [ 7 ], [ 4, 10 ], [ 1 ],
        >             [ 2, 3 ], [ 1, 7 ], [ 10 ], [ 3 ] ] ],
        >  [  "6E", [ [ 9 ], [ 1, 8 ], [ 2 ], [ 22 ], [  ], [ 2, 3 ], [ 1 ],
        >             [ 3, 4 ], [ 3 ], [ 13 ], [ 7 ], [ 6 ] ] ],
        >  [  "6F", [ [ 5, 10 ], [ 1, 2, 4 ], [ 4, 13 ], [ 10, 18 ], [ 4, 12 ],
        >             [ 4, 9 ], [ 21 ], [ 4 ], [ 8 ], [ 1, 10 ], [ 1, 8, 18 ],
        >             [ 10 ], [ 6 ], [ 1 ], [  ], [ 3 ] ] ],
        >  [  "7A", [ [ 1, 2 ], [ 2 ], [  ], [ 7 ], [ 1, 10 ], [ 1 ], [ 11 ] ] ],
        >  [  "8A", [ [ 2, 18 ], [  ], [ 2 ], [ 9 ], [ 7 ], [ 1 ] ] ],
        >  [  "8B", [ [ 8, 17 ], [ 1, 7 ], [ 11, 21 ], [ 2, 8 ], [  ], [ 1 ],
        >             [ 2, 12 ], [ 7 ], [ 5 ], [ 2 ] ] ],
        >  [  "8C", [ [ 1, 8 ], [ 13 ], [ 9 ], [  ], [ 1 ] ] ],
        >  [  "8D", [ [ 1, 2 ], [ 7 ], [ 1 ], [ 6 ], [  ] ] ],
        >  [  "8E", [ [ 2, 12 ], [ 3, 22 ], [ 4, 22 ], [ 4, 12 ], [ 1, 11 ],
        >             [ 2, 22 ], [ 6, 12 ], [ 9 ], [ 2, 10 ], [ 20 ], [ 1, 9 ],
        >             [ 11 ], [ 10 ], [ 1 ], [  ], [ 7 ] ] ],
        >  [  "8F", [ [ 1, 10 ], [ 18 ], [ 4 ], [ 9 ], [ 20 ], [  ], [ 6 ],
        >             [ 1 ] ] ],
        >  [  "9A", [ [  ], [ 4 ], [ 3 ], [ 5 ] ] ],
        >  [ "10A", [ [  ], [ 1 ] ] ],
        >  [ "10B", [ [ 2 ], [ 12 ], [  ], [ 1 ], [ 9 ], [ 10 ], [ 14 ], [ 3 ] ] ],
        >  [ "10C", [ [ 1, 20 ], [ 5 ], [ 1, 8 ], [ 12 ], [ 2 ], [  ], [ 10 ],
        >             [ 3 ], [ 1 ] ] ],
        >  [ "11A", [ [ 2 ], [ 1 ], [ 3 ], [  ] ] ],
        >  [ "12A", [ [ 10 ], [  ], [ 1 ] ] ],
        >  [ "12B", [ [  ], [ 14 ], [ 1 ], [ 3 ], [ 4 ], [ 10 ] ] ],
        >  [ "12C", [ [ 19 ], [  ], [ 7 ], [ 1, 7 ], [ 1 ] ] ],
        >  [ "12D", [ [ 1 ], [ 11 ], [ 8 ], [ 15 ], [  ], [ 2 ] ] ],
        >  [ "12E", [ [ 3 ], [ 5 ], [  ] ] ],
        >  [ "12F", [ [ 9 ], [ 1, 15 ], [ 5 ], [  ], [ 2 ], [ 12 ], [ 8 ],
        >             [ 1 ] ] ],
        >  [ "12G", [ [  ], [ 1 ], [ 3 ] ] ],
        >  [ "12H", [ [ 1, 4 ], [ 4, 14 ], [ 4, 9 ], [ 4 ], [ 14 ], [ 9 ], [ 1 ],
        >             [  ], [ 11 ], [ 18 ] ] ],
        >  [ "14A", [ [ 1, 2 ], [ 1, 7 ], [ 10 ], [ 1 ], [ 19 ], [  ], [ 16 ] ] ],
        >  [ "14B", [ [ 7 ], [  ], [ 1 ], [ 11 ] ] ],
        >  [ "14C", [ [ 7 ], [  ], [ 1 ], [ 11 ] ] ],
        >  [ "15A", [ [  ], [ 2 ], [ 3 ], [ 1 ] ] ],
        >  [ "15B", [ [  ] ] ],
        >  [ "15C", [ [  ] ] ],
        >  [ "16A", [ [ 6 ], [ 11 ], [  ], [ 1 ] ] ],
        >  [ "16B", [ [ 3 ], [  ], [ 6 ], [ 1 ] ] ],
        >  [ "18A", [ [ 4 ], [  ], [ 5 ], [ 3 ] ] ],
        >  [ "20A", [ [ 6 ], [ 1 ], [  ], [ 7 ] ] ],
        >  [ "20B", [ [ 3 ], [ 1 ], [ 2 ], [  ] ] ],
        >  [ "23A", [ [  ] ] ],
        >  [ "23B", [ [  ] ] ],
        >  [ "24A", [ [ 18 ], [ 7 ], [  ], [ 1 ] ] ],
        >  [ "24B", [ [ 10 ], [  ], [ 1 ], [ 4 ] ] ],
        >  [ "28A", [ [ 5 ], [  ], [ 10 ], [ 1 ] ] ],
        >  [ "30A", [ [ 2 ], [  ], [ 1 ], [ 3 ] ] ],
        >  [ "30B", [ [  ] ] ],
        >  [ "30C", [ [  ] ] ] ];;

The GAP code for turning this information into a straight line program is shorter than the lines of this program, hence we show this program.

        gap> create_classreps_slp:= function( classreps )
        >     local words, l, len, lines, kerneloffset, inputoffset, cache, k,
        >           found, pair, pos, diff, pos2, first, n, outputs, i, list;
        >
        >     # Find words for the products of kernel generators that occur.
        >     words:= [];
        >     for l in Set( Concatenation( List( classreps, x -> x[2] ) ) ) do
        >       len:= Length( l );
        >       if 2 <= len then
        >         if not IsBound( words[ len ] ) then
        >           words[ len ]:= [];
        >         fi;
        >         Add( words[ len ], l );
        >       fi;
        >     od;
        >
        >     lines:= [];
        >     kerneloffset:= 60;
        >     inputoffset:= 82;
        >
        >     # We have to form all products of length 2 of kernel generators.
        >     cache:= [ [], [] ];
        >     for l in words[2] do
        >       Add( lines,
        >            [ l[1] + kerneloffset, 1, l[2] + kerneloffset, 1 ] );
        >       Add( cache[1], l );
        >       Add( cache[2], Length( lines ) + inputoffset );
        >     od;
        >
        >     # For products of length at least 3, we may use known products
        >     # of length 2.  Longer matches are not considered.
        >     for k in [ 3 .. Length( words ) ] do
        >       for l in words[k] do
        >         found:= false;
        >         for pair in Combinations( l, 2 ) do
        >           pos:= Position( cache[1], pair );
        >           if pos <> fail then
        >             diff:= Difference( l, pair );
        >             if Length( diff ) = 1 then
        >               Add( lines,
        >                 [ cache[2][ pos ], 1, diff[1] + kerneloffset, 1 ] );
        >             else
        >               pos2:= Position( cache[1], diff );
        >               if pos2 <> fail then
        >                 Add( lines,
        >                   [ cache[2][ pos ], 1, cache[2][ pos2 ], 1 ] );
        >               else
        >                 first:= cache[2][ pos ];
        >                 for n in diff do
        >                   Add( lines, [ first, 1, n + kerneloffset, 1 ] );
        >                   first:= Length( lines ) + inputoffset;
        >                 od;
        >               fi;
        >             fi;
        >             Add( cache[1], l );
        >             Add( cache[2], Length( lines ) + inputoffset );
        >             found:= true;
        >             break;
        >           fi;
        >         od;
        >         if not found then
        >           first:= l[1] + kerneloffset;
        >           for n in l{ [ 2 .. Length( l ) ] } do
        >             Add( lines, [ first, 1, n + kerneloffset, 1 ] );
        >             first:= Length( lines ) + inputoffset;
        >           od;
        >           Add( cache[1], l );
        >           Add( cache[2], Length( lines ) + inputoffset );
        >         fi;
        >       od;
        >     od;
        >
        >     outputs:= [];
        >
        >     for i in [ 1 .. Length( classreps ) ] do
        >       list:= classreps[i][2];
        >       for l in list do
        >         if l = [ 0 ] then
        >           Add( outputs, [ kerneloffset + 1, 2 ] );
        >         elif l = [] then
        >           Add( outputs, [ i, 1 ] );
        >         elif Length( l ) = 1 then
        >           Add( lines, [ i, 1, l[1] + kerneloffset, 1 ] );
        >           Add( outputs, [ Length( lines ) + inputoffset, 1 ] );
        >         else
        >           # The words are already cached.
        >           pos:= Position( cache[1], l );
        >           Add( lines, [ i, 1, cache[2][ pos ], 1 ] );
        >           Add( outputs, [ Length( lines ) + inputoffset, 1 ] );
        >         fi;
        >       od;
        >     od;
        >
        >     Add( lines, outputs );
        >
        >     return StraightLineProgram( lines, inputoffset );
        > end;;
        gap> slp_classreps:= create_classreps_slp( classrepsinfo );;

We compute the class representatives of P.

        gap> kerperms:= ResultOfStraightLineProgram( slp_ker, stdperms );;
        gap> co2classreps:= ResultOfStraightLineProgram(
        >        slp_co2classreps.program, stdperms );;
        gap> classreps:= ResultOfStraightLineProgram( slp_classreps,
        >        Concatenation( co2classreps, kerperms ) );;

Now the MAGMA [BCP97] system is invoked for computing the irreducible characters of P. The function `CharacterTableComputedByMagma` guarantees that the columns are indexed by the class representatives we have chosen.

        gap> g:= Group( stdperms );;
        gap> SetConjugacyClasses( g,
        >        List( classreps, x -> ConjugacyClass( g, x ) ) );
        gap> libcb2b:= CharacterTable( "BM2" );
        CharacterTable( "2^(1+22).Co2" )
        gap> cen:= ClassPositionsOfCentre( libcb2b );;
        gap> if CTblLib.IsMagmaAvailable() then
        >      mgmt:= CharacterTableComputedByMagma( g, "2^22.Co2-Magma" );
        >    else
        >      mgmt:= libcb2b / cen;  # this is a hack ...
        >    fi;

Our next goal is to compute the character table of C, together with the information about the correspondence of the conjugacy classes in C and B.
In order to write down class representatives of C, we evaluate the words for class representatives of C / 〈z 〉 in the generators of the faithful 2048-dimensional module for C, in characteristic 3 and 5. For elements of order not divisible by 15, we can compute the Brauer character value in at least one of the two representations, and interpret it as the value of the unique faithful irreducible ordinary character of degree 2048 of C. Whenever this character value is nonzero, we know that the corresponding class of C / 〈z 〉 splits into two classes of C, on which the values of this character differ by sign. For classes where the character value is zero, it will turn out later that no splitting occurs; for the moment, we leave this question open.
We get words in terms of the generators a and b of C for the elements in question, that is, for the class representatives of C / 〈z 〉 and for products of some of them with the central involution of C.
We evaluate these words in the three 4371-dimensional representations, and run the identification program in order to assign the label of one of the preliminary conjugacy classes of B to them.
Let us collect the necessary data, that is, the class representatives of C / 〈z 〉 in the restrictions of the three representations of B to C and in the two 2048-dimensional representations of C, in characteristics 3 and 5, respectively.

        gap> cgens_2:= ResultOfStraightLineProgram( slp_maxes_2.program,
        >                  GeneratorsOfGroup( b_2 ) );;
        gap> cgens_5:= ResultOfStraightLineProgram( slp_maxes_2.program,
        >                  GeneratorsOfGroup( b_5 ) );;
        gap> cgens_2_std:= ResultOfStraightLineProgram( slp_co2, cgens_2 );;
        gap> cgens_3_std:= ResultOfStraightLineProgram( slp_co2, cgens_3 );;
        gap> cgens_5_std:= ResultOfStraightLineProgram( slp_co2, cgens_5 );;
        gap> m:= GModuleByMats( cgens_5_std, GF(5) );;
        gap> cf_5:= MTX.CompositionFactors( m );;
        gap> SortParallel( List( cf_5, x -> x.dimension ), cf_5 );
        gap> List( cf_5, x -> x.dimension );
        [ 23, 2048, 2300 ]
        gap> inputsB:= List( [ cgens_2_std, cgens_3_std, cgens_5_std ],
        >      l -> Concatenation(
        >             ResultOfStraightLineProgram( slp_co2classreps.program, l ),
        >             ResultOfStraightLineProgram( slp_ker, l ) ) );;
        gap> cf3std:= ResultOfStraightLineProgram( slp_co2, cf_3[2].generators );;
        gap> inputs2048:= List( [ cf3std, cf_5[2].generators ],
        >      l -> Concatenation(
        >             ResultOfStraightLineProgram( slp_co2classreps.program, l ),
        >             ResultOfStraightLineProgram( slp_ker, l ) ) );;

Next we need the central involution of C in the five representations. We are lucky, the preimage of the identity of C / 〈z 〉 under the epimorphism from C that is computed by the straight line program `slp_classreps` has order two.

        gap> slp:= RestrictOutputsOfSLP( slp_classreps, 1 );;
        gap> centralinv_2048:= List( inputs2048,
        >        l -> ResultOfStraightLineProgram( slp, l ) );;
        gap> List( centralinv_2048, Order );
        [ 2, 2 ]
        gap> centralinv_B:= List( inputsB,
        >        l -> ResultOfStraightLineProgram( slp, l ) );;

Now we run over the class representatives of C / 〈z 〉, and collect the data in a record with the following components.
`preimages`: one or two preimage classes, depending on whether the class in question need not split or must split,
`fusionlabels`: for each class of C / 〈z 〉, one or two labels of class names in B,
`projcharacter`: the Brauer character value of the first preimage of the class in one of the 2048-dimensional representations, if possible; otherwise `fail`.

        gap> cent_table:= rec( preimages:= [],
        >                      fusionlabels:= [],
        >                      projcharacter:= [] );;
        gap> for i in [ 1 .. Length( classreps ) ] do
        >      # identify the representative
        >      slp:= RestrictOutputsOfSLP( slp_classreps, i );
        >      id:= IdentifyClassName( inputsB[1], inputsB[2], inputsB[3], slp, fail );
        >      order:= Int( Filtered( id, IsDigitChar ) );
        >
        >      if order mod 3 = 0 then
        >        if order mod 5 = 0 then
        >          # We cannot compute the Brauer character value.
        >          value:= fail;
        >        else
        >          value:= BrauerCharacterValue(
        >                      ResultOfStraightLineProgram( slp, inputs2048[2] ) );
        >        fi;
        >      else
        >        value:= BrauerCharacterValue(
        >                    ResultOfStraightLineProgram( slp, inputs2048[1] ) );
        >      fi;
        >
        >      if value = 0 then
        >        # Assume no splitting.
        >        Add( cent_table.preimages, [ i ] );
        >        Add( cent_table.fusionlabels, [ id ] );
        >        Add( cent_table.projcharacter, value );
        >      else
        >        # Identify the class of the other preimage.
        >        mats:= List( inputsB,
        >                     l -> ResultOfStraightLineProgram( slp, l ) );
        >        id2:= IdentifyClassName( mats[1] * centralinv_B[1],
        >                            mats[2] * centralinv_B[2],
        >                            mats[3] * centralinv_B[3],
        >                            fail, fail );
        >        if value = fail then
        >          # no Brauer character value known
        >          Add( cent_table.preimages, [ i ] );
        >          Add( cent_table.fusionlabels, [ id, id2 ] );
        >          Add( cent_table.projcharacter, value );
        >        else
        >          # two preimage classes, take the positive value first
        >          Add( cent_table.preimages, [ i, i ] );
        >          if value > 0 then
        >            Add( cent_table.fusionlabels, [ id, id2 ] );
        >            Add( cent_table.projcharacter, value );
        >          else
        >            Add( cent_table.fusionlabels, [ id2, id ] );
        >            Add( cent_table.projcharacter, -value );
        >          fi;
        >        fi;
        >      fi;
        >    od;

Let us compute the missing values for the faithful irreducible character of C. We know that these values are integers, and the character values at the p-th powers are known, for p ∈ { 3, 5 }. The value at the p-th power of an element g, say, determines the congruence class of the value at g modulo p, thus we know the congruence classes of the missing values modulo 15. For each of the classes of C where the character value is not known yet, the class length is at least |C| / 120, and a character value of absolute value 7 or larger on any of these classes would lead to a contribution of at least 72 / 120 to the norm of the character. However, the known character values contribute already more than 1 − 72 / 120 to the norm, hence the missing character values are uniquely determined by their congruence class modulo 15.

        gap> chi:= cent_table.projcharacter;;
        gap> failpos:= Positions( chi, fail );;
        gap> OrdersClassRepresentatives( mgmt ){ failpos };
        [ 15, 30, 30, 30, 15, 15, 30, 30, 30, 30, 30, 30 ]
        gap> SizesCentralizers( mgmt ){ failpos };
        [ 120, 120, 120, 120, 30, 30, 120, 120, 120, 120, 30, 30 ]
        gap> used:= 0;;
        gap> for i in[ 1 .. 388 ] do
        >      if chi[i] <> 0 and chi[i] <> fail then
        >        used:= used + 2 * SizesConjugacyClasses( mgmt )[i] * chi[i]^2;
        >      fi;
        >    od;
        gap> used / ( 2 * Size( mgmt ) ) > 1 - 7^2 / 120;
        true

Thus we may complete the character, as follows.

        gap> for i in failpos do
        >      v:= ChineseRem( [ 3, 5 ], [ chi[ PowerMap( mgmt, 3, i ) ],
        >                                  chi[ PowerMap( mgmt, 5, i ) ] ] );
        >     if v > 7 then
        >       v:= v - 15;
        >     fi;
        >     chi[i]:= AbsInt( v );
        >   od;
        gap> chi{ failpos };
        [ 2, 0, 0, 0, 1, 1, 2, 0, 0, 0, 1, 1 ]

In order to assign the right labels to the preimages of those classes of C / 〈z 〉 that split into two classes of C, we use congruences w. r. t. the third power map. Note that we have chosen that positive values of the projective character belong to the first entry in each pair of class labels.

        gap> split:= Filtered( failpos, x -> chi[x] <> 0 );
        [ 343, 347, 348, 383, 387, 388 ]
        gap> nonsplit:= Difference( failpos, split );
        [ 344, 345, 346, 384, 385, 386 ]
        gap> pow:= PowerMap( mgmt, 3 ){ split };
        [ 138, 136, 136, 263, 261, 261 ]
        gap> chi{ split };
        [ 2, 1, 1, 2, 1, 1 ]
        gap> chi{ pow };
        [ 8, 2, 2, 4, 2, 2 ]
        gap> cent_table.fusionlabels{ split };
        [ [ "15A", "30D" ], [ "15B", "30GH" ], [ "15B", "30GH" ], [ "30A", "30E" ],
          [ "30GH", "30F" ], [ "30GH", "30F" ] ]
        gap> cent_table.fusionlabels{ pow };
        [ [ "5A", "10B" ], [ "10D", "5B" ], [ "10D", "5B" ], [ "10A", "10E" ],
          [ "10F", "10D" ], [ "10F", "10D" ] ]

We see from the element orders that the first three pairs are already sorted correctly. The fourth pair must be swapped because `30A` cubes to `10A` and the positive values of the projective character are not congruent modulo 3. Similarly, the last two pairs need not be swapped because `30F` cubes to `10F`.

        gap> First( powerinfo, l -> l[1] = "30A" );
        [ "30A", [ [ 2, "15A" ], [ 3, "10A" ], [ 5, "6B" ] ] ]
        gap> First( powerinfo, l -> l[1] = "30F" );
        [ "30F", [ [ 2, "15B" ], [ 3, "10F" ], [ 5, "6I" ] ] ]
        gap> cent_table.fusionlabels[ split[4] ]:= Permuted(
        >        cent_table.fusionlabels[ split[4] ], (1,2) );;
        gap> for i in nonsplit do
        >      Unbind( cent_table.fusionlabels[i][2] );
        >    od;

Assuming that not more classes of C / 〈z 〉 split under the epimorphism from C, we create the (preliminary) character table head of C.

        gap> for i in split do
        >      cent_table.preimages[i]:= [ i, i ];
        >    od;
        gap> factorfusion:= Concatenation( cent_table.preimages );;
        gap> cb2b:= rec( UnderlyingCharacteristic:= 0,
        >                Size:= 2 * Size( mgmt ),
        >                Identifier:= "C_B(2B)",
        >                SizesCentralizers:= [] );;
        gap> proj:= InverseMap( factorfusion );;
        gap> for i in [ 1 .. Length( proj ) ] do
        >      if IsInt( proj[i] ) then
        >        cb2b.SizesCentralizers[ proj[i] ]:= SizesCentralizers( mgmt )[i];
        >      else
        >        cb2b.SizesCentralizers{ proj[i] }:=
        >            SizesCentralizers( mgmt )[i] * [ 2, 2 ];
        >      fi;
        >    od;

The element orders are given by the class labels.

        gap> fusionlabels:= Concatenation( cent_table.fusionlabels );;
        gap> cb2b.OrdersClassRepresentatives:= List(
        >        Concatenation( cent_table.fusionlabels ),
        >        x -> Int( Filtered( x, IsDigitChar ) ) );;
        gap> ConvertToCharacterTable( cb2b );;

Next we turn the projective character `chi` of C/〈z 〉 into a character for C, and inflate the characters of the factor group to C.

        gap> chi_c:= [];;
        gap> for i in [ 1 .. Length( chi ) ] do
        >      if chi[i] = 0 then
        >        chi_c[ proj[i] ]:= 0;
        >      else
        >        chi_c{ proj[i] }:= [ 1, -1 ] * chi[i];
        >      fi;
        >    od;
        gap> factirr:= List( Irr( mgmt ), x -> x{ factorfusion } );;

Tensoring the faithful irreducible character with the 60 irreducible characters of the factor group Co2 yields 60 irreducible characters of C, which are in fact all the missing irreducibles. In particular, no further splitting of classes occurs.

        gap> factirr_co2:= Filtered( factirr,
        >                      x -> ClassPositionsOfKernel( x ) <> [ 1, 2 ] );;
        gap> Length( factirr_co2 );
        60
        gap> ten:= Tensored( factirr_co2, [ chi_c ] );;
        gap> Set( List( ten, x -> ScalarProduct( cb2b, x, x ) ) );
        [ 1 ]
        gap> irr:= Concatenation( factirr, ten );;
        gap> Size( cb2b ) = Sum( List( irr, x -> x[1]^2 ) );
        true
        gap> SetIrr( cb2b, List( irr, x -> Character( cb2b, x ) ) );

The only missing information on the character table of C is that on power maps. We use the power maps of the table for C / 〈z 〉 as approximations, and let the standard algorithms compute the candidates for the maps of C; for all primes, there is only one candidate.

        gap> powermaps:= ComputedPowerMaps( cb2b );
        [  ]
        gap> for p in Set( Factors( Size( cb2b ) ) ) do
        >      init:= CompositionMaps( InverseMap( factorfusion ),
        >          CompositionMaps( PowerMap( mgmt, p ), factorfusion ) );
        >      poss:= PossiblePowerMaps( cb2b, p, rec( powermap:= init ) );
        >      if Length( poss ) <> 1 then
        >        Error( Ordinal( p ), " power map is not unique" );
        >      fi;
        >      powermaps[p]:= poss[1];
        >    od;

Finally, we compare the newly computed character table with that from GAP's library.

        gap> libtbl:= CharacterTable( "BM2" );;
        gap> Set( Irr( libtbl ) ) = Set( Irr( cb2b ) );
        true
        gap> IsRecord( TransformingPermutationsCharacterTables( libtbl, cb2b ) );
        true

## 6 Conjugacy classes of B and their centralizer orders

In this section, we determine the conjugacy classes of B and their centralizer orders, using the fact that for each element g (of prime order) in a group G, say, the conjugacy classes of elements in G that contain roots of g are in bijection with the conjugacy classes in NG(〈g 〉) that contain roots of g, and that this bijection respects centralizer orders.
We have the following information about the elements of odd prime order in B, and their normalizers, see [BMW20,Section 6].

- There are exactly two classes of element order 3 in B, `3A` with normalizer S3 ×Fi22.2 and `3B` with normalizer 31+8.21+6.U4(2).2. Both subgroups have been constructed explicitly in the certified copy of B, and the character table of the second subgroup has been recomputed from a permutation representation -it is equivalent to the character table in GAP's library of character tables.
- There are exactly two classes of element order 5 in B, `5A` with normalizer 5:4 ×HS and `5B` with normalizer 51+4.21+4.A5.4. According to [BMW20,Section 6], the two subgroups have been constructed explicitly in the certified copy of B, and the character table of the second subgroup has been recomputed from a permutation representation -it is equivalent to the character table in GAP's library of character tables.
- There is exactly one class of element order 7 in B, with normalizer of order 29 ·33 ·5 ·72 and contained in maximal subgroups of type 2.2E6(2).2.
- There is exactly one class of element order 11 in B, with normalizer of type S5 ×11:10.
- There is exactly one class of element order 13 in B, with normalizer of type S4 ×13:12.
- There is exactly one class of element order 17 in B, with normalizer of order 26 ·17.
- There is exactly one class of element order 19 in B, with normalizer of order 22 ·32 ·19.
- There are exactly two classes of element order 23 in B, which are Galois conjugate and have normalizer 2 ×23:11.
- There are exactly two classes of element order 31 in B, which are Galois conjugate and have normalizer 31:15.
- There are exactly two classes of element order 47 in B, which are Galois conjugate and have normalizer 47:23.

We are going to create the character table head for B. For that, we collect the information about already identified classes in a record; its component names are the class names, the corresponding values are the element order and the centralizer order. The following auxiliary function sets a value in this record (and signals an error if the value constradicts the one stored in the library character tableof B).

        gap> libB:= CharacterTable( "B" );;
        gap> libBnames:= ClassNames( libB, "ATLAS" );;
        gap> Bclassinfo:= rec();;
        gap> SetCentralizerOrder:= function( classname, value )
        >        local pos;
        >
        >        pos:= Position( libBnames, classname );
        >        if pos = fail then
        >          Print( "no class name '", classname, "'?" );
        >          return false;
        >        elif SizesCentralizers( libB )[ pos ] <> value then
        >          Error( "wrong centralizer order!" );
        >        fi;
        >        Bclassinfo.( classname ):=
        >            [ Int( Filtered( classname, IsDigitChar ) ), value ];
        >        return true;
        >    end;;

The values mentioned above, for elements of prime order at least 11, are entered by hand.

        gap> SetCentralizerOrder( "1A", Size( libB ) );;
        gap> SetCentralizerOrder( "11A", 11 * 120 );;
        gap> SetCentralizerOrder( "13A", 13 * 24 );;
        gap> SetCentralizerOrder( "17A", 17 * 4 );;
        gap> SetCentralizerOrder( "19A", 19 * 2 );;
        gap> SetCentralizerOrder( "23A", 23 * 2 );;
        gap> SetCentralizerOrder( "23B", 23 * 2 );;
        gap> SetCentralizerOrder( "31A", 31 );;
        gap> SetCentralizerOrder( "31B", 31 );;
        gap> SetCentralizerOrder( "47A", 47 );;
        gap> SetCentralizerOrder( "47B", 47 );;

We will need the information about how many root classes a given class of B has, based on the power map information for the labels (the list `powerinfo`).

        gap> RootInfoFromLabels:= function( label )
        >     local found, exp, res, entry, pos, root, ord;
        >
        >     found:= [ label ];
        >     exp:= [ Int( Filtered( label, IsDigitChar ) ) ];
        >     res:= rec( total:= [], labels:= [] );
        >     res.total[ exp[1] ]:= 1;
        >     res.labels[ exp[1] ]:= [ label ];
        >     for entry in powerinfo do
        >       for root in entry[2] do
        >         if not entry[1] in found then
        >           pos:= Position( found, root[2] );
        >           if pos <> fail then
        >             ord:= exp[ pos ] * root[1];
        >             Add( exp, ord );
        >             Add( found, entry[1] );
        >             if not IsBound( res.total[ ord ] ) then
        >               res.total[ ord ]:= 0;
        >               res.labels[ ord ]:= [];
        >             fi;
        >             res.total[ ord ]:= res.total[ ord ] + 1;
        >             Add( res.labels[ ord ], entry[1] );
        >           fi;
        >         fi;
        >       od;
        >     od;
        >     return res;
        > end;;

Analogously, we will need this roots information for the normalizers of certain elements, computed from the character tables.

        gap> RootInfoFromTable:= function( tbl, pos )
        >     local orders, posord, res, i, ord;
        >
        >     orders:= OrdersClassRepresentatives( tbl );
        >     posord:= orders[ pos ];
        >     res:= rec( total:= [], classpos:= [] );
        >     for i in [ 1 .. NrConjugacyClasses( tbl ) ] do
        >       ord:= orders[i];
        >       if ord mod posord = 0 and
        >          PowerMap( tbl, ord / posord, i ) = pos then
        >         if not IsBound( res.total[ ord ] ) then
        >           res.total[ ord ]:= 0;
        >           res.classpos[ ord ]:= [];
        >         fi;
        >         res.total[ ord ]:= res.total[ ord ] + 1;
        >         Add( res.classpos[ ord ], i );
        >       fi;
        >     od;
        >     return res;
        > end;;

We compute the roots info for the four involution normalizers, from the available character tables.

        gap> norm2A:= CharacterTable( "2.2E6(2).2" );;
        gap> pos2A:= ClassPositionsOfCentre( norm2A );
        [ 1, 2 ]
        gap> rootinfo2A_t:= RootInfoFromTable( norm2A, pos2A[2] );;
        gap> pos2B:= ClassPositionsOfCentre( cb2b );
        [ 1, 2 ]
        gap> rootinfo2B_t:= RootInfoFromTable( cb2b, pos2B[2] );;
        gap> norm2C:= CharacterTableOfIndexTwoSubdirectProduct(
        >      CharacterTable( "F4(2)" ), CharacterTable( "F4(2).2" ),
        >      CharacterTable( "2^2" ), CharacterTable( "D8" ), "norm2C" );;
        gap> pos2C:= ClassPositionsOfCentre( norm2C.table );
        [ 1, 2 ]
        gap> rootinfo2C_t:= RootInfoFromTable( norm2C.table, pos2C[2] );;
        gap> sup_norm2D:= CharacterTable( "BM4" );
        CharacterTable( "2^(9+16).S8(2)" )
        gap> pos2D:= Positions( SizesCentralizers( sup_norm2D ), 11689182992793600 );
        [ 4 ]
        gap> rootinfo2D_t:= RootInfoFromTable( sup_norm2D, pos2D[1] );;
        gap> rootinfo2A_l:= RootInfoFromLabels( "2A" );;
        gap> rootinfo2B_l:= RootInfoFromLabels( "2B" );;
        gap> rootinfo2C_l:= RootInfoFromLabels( "2C" );;
        gap> rootinfo2D_l:= RootInfoFromLabels( "2D" );;

We have to justify that the labels `"2A"`, `"2B"`, `"2C"`, `"2D"` for class representatives fit to the class names `2A`, `2B`, `2C`, `2D` which are used in [Str76]. For that, we check for the availability of roots of certain orders.

        gap> List( [ rootinfo2A_t, rootinfo2B_t, rootinfo2C_t, rootinfo2D_t ],
        >          r -> Filtered( [ 52, 56, 70 ], i -> IsBound( r.total[i] ) ) );
        [ [ 70 ], [ 56 ], [ 52 ], [  ] ]
        gap> List( [ rootinfo2A_l, rootinfo2B_l, rootinfo2C_l, rootinfo2D_l ],
        >          r -> Filtered( [ 52, 56, 70 ], i -> IsBound( r.total[i] ) ) );
        [ [ 70 ], [ 56 ], [ 52 ], [  ] ]

Now we can start to identify the root classes of the normalizers with the class labels.

        gap> List( [ rootinfo2A_t, rootinfo2B_t, rootinfo2C_t, rootinfo2D_t ],
        >          r -> Sum( Compacted( r.total ) ) );
        [ 20, 83, 14, 40 ]
        gap> List( [ rootinfo2A_l, rootinfo2B_l, rootinfo2C_l, rootinfo2D_l ],
        >          r -> Sum( Compacted( r.total ) ) );
        [ 19, 77, 14, 40 ]

We see that some of the class labels for B belong to more than one conjugacy class. Exactly one such case occurs for a root class of `2A`, the label `"34BC"` belongs to two Galois conjugate classes `34B`, `34C`. (This was already clear from the discussion of classes of elements of order 17.)

        gap> rootinfo2A_t.total[34];
        2
        gap> rootinfo2A_l.total[34];
        1
        gap> rootinfo2A_l.labels[34];
        [ "34BC" ]
        gap> pos34:= rootinfo2A_t.classpos[34];
        [ 133, 135 ]
        gap> PowerMap( norm2A, 3 ){ pos34 };
        [ 135, 133 ]

The other six cases where we have to adjust the labels occur for roots of `2B`.

        gap> rootinfo2B_t.total{ [ 16, 30, 32, 46, 56 ] };
        [ 6, 3, 4, 2, 2 ]
        gap> rootinfo2B_l.total{ [ 16, 30, 32, 46, 56 ] };
        [ 5, 2, 2, 1, 1 ]

For the classes of element order different from 16, we have to replace a class label by a pair of labels which belong to Galois conjugate classes:

        gap> rootinfo2B_l.labels[30];
        [ "30D", "30GH" ]
        gap> pos30:= rootinfo2B_t.classpos[30];
        [ 388, 393, 395 ]
        gap> PowerMap( cb2b, 7 ){ pos30 };
        [ 388, 395, 393 ]
        gap> List( pos30,
        >          i -> Number( PowerMap( cb2b, 2 ), x -> x = i ) );
        [ 2, 0, 0 ]
        gap> List( rootinfo2B_l.labels[30],
        >          l -> Length( RootInfoFromLabels( l ).total ) );
        [ 60, 30 ]

We have to replace the label `"30GH"` (for which there are no square roots) into two labels, which we call `"30G"` and `"30H"`.

        gap> rootinfo2B_l.labels[32];
        [ "32AB", "32CD" ]
        gap> pos32:= rootinfo2B_t.classpos[32];
        [ 399, 400, 403, 404 ]
        gap> PowerMap( cb2b, 5 ){ pos32 };
        [ 400, 399, 404, 403 ]

We replace `"32AB"` by `"32A"` and `"32B"`, and replace `"32GH"` by `"32G"` and `"32H"`.

        gap> rootinfo2B_l.labels[46];
        [ "46AB" ]

It was already clear from the presence of two Galois conjugate classes of element order 23 that there must be two Galois conjugate classes of element order 46; we replace `"46AB"` by `"46A"` and `"46B"`.

        gap> rootinfo2B_l.labels[56];
        [ "56AB" ]
        gap> pos56:= rootinfo2B_t.classpos[56];
        [ 437, 438 ]
        gap> PowerMap( cb2b, 3 ){ pos56 };
        [ 437, 438 ]
        gap> PowerMap( cb2b, 5 ){ pos56 };
        [ 438, 437 ]

We replace `"56AB"` by `"56A"` and `"56B"`.
Now one case of element order 16 is left. There are two classes of element order 8 for which we need three classes of square roots. We have three square roots for the label `"8D"` but only two for `"8H"`, which are `"16C"` and `"16DF"`. Since both of them have square roots, we conclude that we have to introduce one new label for a class of element order 16 that has no square roots. We call this new label `"16F"`, and replace `"16DF"` by `"16D"`.

        gap> pos:= rootinfo2B_t.classpos[16];
        [ 233, 234, 235, 251, 252, 258 ]
        gap> PowerMap( cb2b, 2 ){ pos };
        [ 69, 69, 69, 104, 104, 104 ]
        gap> List( rootinfo2B_t.classpos[16],
        >          i -> Number( PowerMap( cb2b, 2 ), x -> x = i ) );
        [ 0, 0, 0, 2, 0, 2 ]
        gap> rootinfo2B_l.labels[16];
        [ "16A", "16B", "16E", "16DF", "16C" ]
        gap> Filtered( powerinfo, l -> l[1] in rootinfo2B_l.labels[16] );
        [ [ "16A", [ [ 2, "8D" ] ] ], [ "16B", [ [ 2, "8D" ] ] ],
          [ "16E", [ [ 2, "8D" ] ] ], [ "16DF", [ [ 2, "8H" ] ] ],
          [ "16C", [ [ 2, "8H" ] ] ] ]
        gap> List( rootinfo2B_l.labels[16],
        >          l -> IsBound( RootInfoFromLabels( l ).total[32] ) );
        [ false, false, false, true, true ]

We record the splittings in the list `powerinfo`. First we replace the entry for each splitting label by two entries.

        gap> tosplit:= List( Filtered( powerinfo,
        >                              x -> Number( x[1], IsAlphaChar ) > 1 ),
        >                    x -> x[1] );
        [ "16DF", "23AB", "30GH", "31AB", "32AB", "32CD", "34BC", "46AB", "47AB",
          "56AB" ]
        gap> for l in tosplit do
        >      ord:= Filtered( l, IsDigitChar );
        >      new:= List( Filtered( l, IsAlphaChar ),
        >                  c -> Concatenation( ord, [ c ] ) );
        >      pos:= PositionProperty( powerinfo, x -> x[1] = l );
        >      Append( powerinfo, List( new, x -> [ x,
        >                  StructuralCopy( powerinfo[ pos ][2] ) ] ) );
        >      Unbind( powerinfo[ pos ] );
        >    od;
        gap> powerinfo:= Compacted( powerinfo );;
        gap> SortParallel(
        >      List( powerinfo,
        >            x -> [ Int( Filtered( x[1], IsDigitChar ) ), x[1] ] ),
        >      powerinfo );

The splitting classes occur as powers only in the follwoing cases: We may choose `"23A"` as the square of `"46A"`, and `"23B"` as the square of `"46B"`. And we have already said that the square of both `"32C"` and `"32D"` shall be `"16D"`. We adjust these cases by hand.

        gap> Filtered( powerinfo, x -> ForAny( x[2], p -> p[2] in tosplit ) );
        [ [ "32C", [ [ 2, "16DF" ] ] ], [ "32D", [ [ 2, "16DF" ] ] ],
          [ "46A", [ [ 2, "23AB" ], [ 23, "2B" ] ] ],
          [ "46B", [ [ 2, "23AB" ], [ 23, "2B" ] ] ] ]
        gap> entry:= First( powerinfo, x -> x[1] = "32C" );;
        gap> entry[2][1][2]:= "16D";;
        gap> entry:= First( powerinfo, x -> x[1] = "32D" );;
        gap> entry[2][1][2]:= "16D";;
        gap> entry:= First( powerinfo, x -> x[1] = "46A" );;
        gap> entry[2][1][2]:= "23A";;
        gap> entry:= First( powerinfo, x -> x[1] = "46B" );;
        gap> entry[2][1][2]:= "23B";;

Now the numbers of roots for the four involution labels coincide with the corresponding numbers of root classes in the normalizers. We know that B has 184 conjugacy classes.

        gap> Length( powerinfo );
        184
        gap> Bnames:= List( powerinfo, x -> x[1] );;

Next we verify that the labels for elements of odd order describe already the conjugacy classes of elements of odd order. For that, it is sufficient to check the normalizers of `"3A"`, `"3B"`, `"5A"`, `"5B"`. Note that `"3A"` has roots of order 66 and `"3B"` has no such roots, and `"5A"` has roots of order 70 and `"5B"` has not; this means that the names of the labels coincide with the class names.

        gap> n3a:= CharacterTable( "S3xFi22.2" );;
        gap> pos:= ClassPositionsOfPCore( n3a, 3 );
        [ 1, 113 ]
        gap> rootinfo3A_t:= RootInfoFromTable( n3a, pos[2] );;
        gap> rootinfo3A_l:= RootInfoFromLabels( "3A" );;
        gap> n3b:= CharacterTable( "3^(1+8).2^(1+6).U4(2).2" );;
        gap> pos:= ClassPositionsOfPCore( n3b, 3 );
        [ 1 .. 4 ]
        gap> Filtered( ClassPositionsOfNormalSubgroups( n3b ),
        >              n -> IsSubset( pos, n ) );
        [ [ 1 ], [ 1, 2 ], [ 1 .. 4 ] ]
        gap> rootinfo3B_t:= RootInfoFromTable( n3b, pos[2] );;
        gap> rootinfo3B_l:= RootInfoFromLabels( "3B" );;
        gap> IsBound( rootinfo3A_t.total[66] );
        true
        gap> IsBound( rootinfo3B_t.total[66] );
        false
        gap> rootinfo3A_t.total = rootinfo3A_l.total;
        true
        gap> rootinfo3B_t.total = rootinfo3B_l.total;
        true
        gap> n5a:= CharacterTable( "5:4xHS.2" );
        CharacterTable( "5:4xHS.2" )
        gap> pos:= ClassPositionsOfPCore( n5a, 5 );
        [ 1, 40 ]
        gap> rootinfo5A_t:= RootInfoFromTable( n5a, pos[2] );;
        gap> rootinfo5A_l:= RootInfoFromLabels( "5A" );;
        gap> n5b:= CharacterTable( "5^(1+4).2^(1+4).A5.4" );
        CharacterTable( "5^(1+4).2^(1+4).A5.4" )
        gap> pos:= ClassPositionsOfPCore( n5b, 5 );
        [ 1 .. 4 ]
        gap> Filtered( ClassPositionsOfNormalSubgroups( n5b ),
        >              n -> IsSubset( pos, n ) );
        [ [ 1 ], [ 1, 2 ], [ 1 .. 4 ] ]
        gap> rootinfo5B_t:= RootInfoFromTable( n5b, pos[2] );;
        gap> rootinfo5B_l:= RootInfoFromLabels( "5B" );;
        gap> IsBound( rootinfo5A_t.total[70] );
        true
        gap> IsBound( rootinfo5B_t.total[70] );
        false
        gap> rootinfo5A_t.total = rootinfo5A_l.total;
        true
        gap> rootinfo5B_t.total = rootinfo5B_l.total;
        true

It will be useful to provide the `powerinfo` information in a record.

        gap> powerinforec:= rec();;
        gap> for entry in powerinfo do
        >      powerinforec.( entry[1] ):= entry[2];
        >    od;

We recompute the roots information, according to the changed data.

        gap> rootinfo2A_l:= RootInfoFromLabels( "2A" );;
        gap> rootinfo2B_l:= RootInfoFromLabels( "2B" );;
        gap> rootinfo2C_l:= RootInfoFromLabels( "2C" );;
        gap> rootinfo2D_l:= RootInfoFromLabels( "2D" );;
        gap> rootinfo2A_t.total = rootinfo2A_l.total;
        true
        gap> rootinfo2B_t.total = rootinfo2B_l.total;
        true
        gap> rootinfo2C_t.total = rootinfo2C_l.total;
        true
        gap> rootinfo2D_t.total = rootinfo2D_l.total;
        true

We try to identify the classes with labels. The numbers of classes and labels fit together, now we compute the bijection. The following function identifies classes which are determined either already by the element order or as a power of an identified class or as a unique root of an identified class.

        gap> IdentifyCentralizerOrders:= function( normtbl, rl, rt )
        >     local n, identified, found, i, unknown, class, d, linfo, p, e, cand,
        >           imgs, im, pos, powerlabel, dd, cent;
        >     n:= First( [ 1 .. Length( rl ) ], i -> IsBound( rl[i] ) );
        >     identified:= [ [], [] ];
        >     found:= true;
        >     while found do
        >       found:= false;
        >       for i in [ 1 .. Length( rl ) ] do
        >       if IsBound( rl[i] ) then
        >         unknown:= Difference( rl[i], identified[1] );
        >         if Length( unknown ) = 1 then
        >           # Identify the class.
        >           class:= Difference( rt[i], identified[2] )[1];
        >           Add( identified[1], unknown[1] );
        >           Add( identified[2], class );
        >           found:= true;
        >           # Identify the admissible powers.
        >           for d in Difference( DivisorsInt( i / n ), [ 1 ] ) do
        >             linfo:= powerinforec.( unknown[1] );
        >             for p in Factors( d ) do
        >               e:= First( linfo, x -> x[1] = p );
        >               linfo:= powerinforec.( e[2] );
        >             od;
        >             if not e[2] in identified[1] then
        >               Add( identified[1], e[2] );
        >               Add( identified[2], PowerMap( normtbl, d, class ) );
        >               found:= true;
        >             fi;
        >           od;
        >         else
        >           # Try to identify roots whose powers are identified.
        >           for d in Difference( DivisorsInt( i / n ), [ 1 ] ) do
        >             cand:= Difference( rt[i], identified[2] );
        >             imgs:= PowerMap( normtbl, d ){ cand };
        >             for im in Intersection( imgs, identified[2] ) do
        >               pos:= Positions( imgs, im );
        >               if Length( pos ) = 1 then
        >                 class:= cand[ pos[1] ];
        >                 powerlabel:= identified[1][
        >                                  Position( identified[2], im ) ];
        >                 # Find the labels of the 'd'-th powers of 'unknown'.
        >                 linfo:= List( unknown, l -> powerinforec.( l ) );
        >                 for p in Factors( d ) do
        >                   e:= List( linfo, ll -> First( ll, x -> x[1] = p ) );
        >                   linfo:= List( e, ee -> powerinforec.( ee[2] ) );
        >                 od;
        >                 linfo:= List( e, x -> x[2] );
        >                 pos:= Position( linfo, powerlabel );
        >                 Add( identified[1], unknown[ pos ] );
        >                 Add( identified[2], class );
        >                 # Identify the admissible powers.
        >                 for dd in Difference( DivisorsInt( i / n ), [ 1 ] ) do
        >                   linfo:= powerinforec.( unknown[ pos ] );
        >                   for p in Factors( dd ) do
        >                     e:= First( linfo, x -> x[1] = p );
        >                     linfo:= powerinforec.( e[2] );
        >                   od;
        >                   if not e[2] in identified[1] then
        >                     Add( identified[1], e[2] );
        >                     Add( identified[2], PowerMap( normtbl, dd, class ) );
        >                     found:= true;
        >                   fi;
        >                 od;
        >                 found:= true;
        >                 break; # since we have to update 'unknown'
        >               fi;
        >             od;
        >             if found then
        >               break; # since we have to update 'unknown'
        >             fi;
        >           od;
        >         fi;
        >       fi;
        >     od;
        >   od;
        >   # Where the centralizer order is unique, set it.
        >   for i in [ 1 .. Length( rl ) ] do
        >     if IsBound( rl[i] ) then
        >       cand:= Difference( rt[i], identified[2] );
        >       cent:= Set( SizesCentralizers( normtbl ){ cand } );
        >       if Length( cent ) = 1 then
        >         Append( identified[1], Difference( rl[i], identified[1] ) );
        >         Append( identified[2], cand );
        >       fi;
        >     fi;
        >   od;
        >   # Set the centralizer orders.
        >   for i in [ 1 .. Length( identified[1] ) ] do
        >     if not IsBound( Bclassinfo.( identified[1][i] ) ) then
        >       Print( "#I  identify ", identified[1][i], "\n" );
        >     fi;
        >     SetCentralizerOrder( identified[1][i],
        >         SizesCentralizers( normtbl )[ identified[2][i] ] );
        >   od;
        >   # Return the information about unidentified classes.
        >   return [ Difference( Concatenation( Compacted( rl ) ), identified[1] ),
        >            Difference( Concatenation( Compacted( rt ) ), identified[2] ) ];
        > end;;

We try the function with the four involution normalizers. In the case of `cb2b`, we are better off since we know most of the class fusion to B. Thus we use also the information about the labels that belong to roots of `"2B"` elements where this is available.

        gap> IdentifyCentralizerOrders( norm2A,
        >        rootinfo2A_l.labels, rootinfo2A_t.classpos );
        #I  identify 2A
        #I  identify 10A
        #I  identify 22A
        #I  identify 26B
        #I  identify 38A
        #I  identify 66A
        #I  identify 6A
        #I  identify 70A
        #I  identify 14A
        #I  identify 14B
        #I  identify 42A
        #I  identify 6B
        #I  identify 6D
        #I  identify 42B
        #I  identify 30B
        #I  identify 30A
        #I  identify 34B
        #I  identify 34C
        [ [ "18A", "18B" ], [ 74, 76 ] ]
        gap> for i in Union( rootinfo2B_t.classpos ) do
        >      if IsBound( powerinforec.( fusionlabels[i] ) ) then
        >        SetCentralizerOrder( fusionlabels[i],
        >            SizesCentralizers( cb2b )[i] );
        >      fi;
        >    od;
        gap> IdentifyCentralizerOrders( cb2b,
        >        rootinfo2B_l.labels, rootinfo2B_t.classpos );
        #I  identify 30G
        #I  identify 30H
        #I  identify 32A
        #I  identify 32B
        #I  identify 32C
        #I  identify 32D
        #I  identify 46A
        #I  identify 46B
        #I  identify 56A
        #I  identify 56B
        [ [ "12A", "12D", "12G", "12I", "12L", "12M", "12O", "16A", "16B", "16C",
              "16D", "16E", "16F", "20B", "20C", "20F", "20I", "24A", "24B", "24C",
              "24D", "24E", "24F", "24G", "24K", "24M", "40A", "40B", "40C", "40D",
              "4F", "4G", "8A", "8B", "8C", "8D", "8E", "8F", "8H", "8I", "8L" ],
          [ 28, 42, 47, 48, 49, 61, 63, 68, 69, 103, 104, 116, 145, 151, 158, 163,
              169, 187, 199, 215, 217, 218, 219, 220, 233, 234, 235, 251, 252, 258,
              292, 308, 309, 310, 311, 320, 332, 333, 344, 357, 420 ] ]
        gap> IdentifyCentralizerOrders( norm2C.table,
        >        rootinfo2C_l.labels, rootinfo2C_t.classpos );
        #I  identify 2C
        #I  identify 4I
        #I  identify 10C
        #I  identify 12T
        #I  identify 6K
        #I  identify 14C
        #I  identify 18F
        #I  identify 20H
        #I  identify 26A
        #I  identify 30C
        #I  identify 6F
        #I  identify 34A
        #I  identify 42C
        #I  identify 52A
        [ [  ], [  ] ]
        gap> IdentifyCentralizerOrders( sup_norm2D,
        >        rootinfo2D_l.labels, rootinfo2D_t.classpos );
        #I  identify 2D
        #I  identify 14E
        #I  identify 28E
        #I  identify 4E
        #I  identify 36C
        #I  identify 18E
        #I  identify 12N
        #I  identify 6J
        #I  identify 40E
        #I  identify 20G
        #I  identify 10F
        #I  identify 8G
        #I  identify 60C
        #I  identify 30F
        #I  identify 12F
        #I  identify 6I
        #I  identify 8J
        #I  identify 10E
        #I  identify 12S
        #I  identify 4H
        #I  identify 18D
        #I  identify 20J
        #I  identify 4J
        #I  identify 24H
        #I  identify 30E
        #I  identify 6E
        #I  identify 6H
        #I  identify 8N
        #I  identify 12J
        #I  identify 12P
        #I  identify 12Q
        #I  identify 24L
        #I  identify 8K
        #I  identify 8M
        #I  identify 12R
        #I  identify 16G
        #I  identify 24N
        #I  identify 16H
        #I  identify 24I
        #I  identify 24J
        [ [  ], [  ] ]

        gap> IdentifyCentralizerOrders( n3a,
        >        rootinfo3A_l.labels, rootinfo3A_t.classpos );;
        #I  identify 3A
        #I  identify 15A
        #I  identify 21A
        #I  identify 33A
        #I  identify 39A
        gap> IdentifyCentralizerOrders( n3b,
        >        rootinfo3B_l.labels, rootinfo3B_t.classpos );;
        #I  identify 3B
        #I  identify 15B
        #I  identify 27A
        #I  identify 9A
        #I  identify 9B
        gap> IdentifyCentralizerOrders( n5a,
        >        rootinfo5A_l.labels, rootinfo5A_t.classpos );;
        #I  identify 5A
        #I  identify 35A
        #I  identify 55A
        gap> IdentifyCentralizerOrders( n5b,
        >        rootinfo5B_l.labels, rootinfo5B_t.classpos );;
        #I  identify 5B
        #I  identify 25A

Let us see which classes of B are not identified yet.

        gap> Difference( RecNames( powerinforec ), RecNames( Bclassinfo ) );
        [ "16D", "16F", "18A", "18B", "7A" ]

For simplicity, we set the centralizer order for `"7A"` by hand.

        gap> SetCentralizerOrder( "7A", 2^8 * 3^2 * 5 * 7^2 );;

The classes `18A` and `"18B"` are roots of `"2A"`. They have no roots and the same power maps. The centralizer orders of the two classes in the `"2A"` centralizer are 1296 = 24 ·34 and 648 = 23 ·34, respectively. We know a class in `cb2b` that fuses into `18A` and has centralizer order 144 = 24 ·32 in `cb2b`. Thus `18A` must have centralizer order 1296 in B.

        gap> SetCentralizerOrder( "18A", 1296 );;
        gap> SetCentralizerOrder( "18B", 648 );;

The cases `"16D"` and `"16F"` will be handled below.
Later we will need the class fusion from CB(`2B`) to B, and we know it already as far as the class invariants reach. We compute part of the missing information.

        gap> diff:= Difference( fusionlabels, RecNames( powerinforec ) );
        [ "16DF", "23AB", "30GH", "32AB", "32CD", "46AB", "56AB" ]
        gap> List( diff, x -> Positions( fusionlabels, x ) );
        [ [ 251, 252, 274, 281, 396 ], [ 421, 423 ], [ 393, 395, 445, 447 ],
          [ 399, 400 ], [ 403, 404 ], [ 422, 424 ], [ 437, 438 ] ]
        gap> Length( fusionlabels );
        448
        gap> NrConjugacyClasses( cb2b );
        448

We are free to choose the images of the class fusion for the elements of order 23 (which then determines the classes of element order 46), 32, and 56, since the question is about independent pairs of Galois conjugate classes.

        gap> fusionlabels[421]:= "23A";;
        gap> fusionlabels[423]:= "23B";;
        gap> fusionlabels[399]:= "32A";;
        gap> fusionlabels[400]:= "32B";;
        gap> fusionlabels[403]:= "32C";;
        gap> fusionlabels[404]:= "32D";;
        gap> fusionlabels[437]:= "56A";;
        gap> fusionlabels[438]:= "56B";;
        gap> pos46:= Positions( OrdersClassRepresentatives( cb2b ), 46 );
        [ 422, 424 ]
        gap> PowerMap( cb2b, 2 ){ pos46 };
        [ 421, 423 ]
        gap> fusionlabels[422]:= "46A";;
        gap> fusionlabels[424]:= "46B";;

Thus we are left with the question about the fusion to the classes with the labels `"16D"`, `"16F"`, `"30G"`, and `"30H"`.
For the order 30 elements, we may choose images for one pair of Galois conjugate classes, and later try to distinguish the two possibilities for the other pair, for example via induced characters.

        gap> fusionlabels[393]:= "30G";;
        gap> fusionlabels[395]:= "30H";;

Two classes of order 16 elements with fusion label `"16DF"` are roots of the central involution of `cb2b`, and we can distinguish them by the fact that `"16D"` has square roots whereas `"16F"` has not. For the other three classes, we are left with two possibilities.

        gap> fusionlabels[251]:= "16D";;
        gap> fusionlabels[252]:= "16F";;
        gap> SetCentralizerOrder( "16D", SizesCentralizers( cb2b )[251] );;
        gap> SetCentralizerOrder( "16F", SizesCentralizers( cb2b )[252] );;

This means that we have currently 25 candidates for the class fusion from `cb2b` to B.

        gap> cb2bfusb:= List( fusionlabels, l -> Position( Bnames, l ) );;
        gap> Positions( cb2bfusb, fail );
        [ 274, 281, 396, 445, 447 ]
        gap> pos:= Positions( fusionlabels, "16DF" );
        [ 274, 281, 396 ]
        gap> for i in pos do
        >      cb2bfusb[i]:= [ 86, 88 ];
        >    od;
        gap> pos:= Positions( fusionlabels, "30GH" );
        [ 445, 447 ]
        gap> for i in pos do
        >      cb2bfusb[i]:= [ 143, 144 ];
        >    od;

Before we compute the irreducible characters of B, we create the character table head for B.

        gap> bhead:= rec( UnderlyingCharacteristic:= 0,
        >                 Size:= Bclassinfo.( "1A" )[2],
        >                 Identifier:= "Bnew" );;
        gap> bhead.SizesCentralizers:= List( Bnames, x -> Bclassinfo.( x )[2] );;
        gap> bhead.OrdersClassRepresentatives:= List( Bnames,
        >        x -> Bclassinfo.( x )[1] );;
        gap> bhead.ComputedPowerMaps:= [];;
        gap> galoisinfo:= rec(
        >     classes:= [ "23A", "23B", "30G", "30H", "31A", "31B", "32A", "32B",
        >                 "32C", "32D", "34B", "34C", "46A", "46B", "47A", "47B",
        >                 "56A", "56B" ],
        >     partners:= [ "23B", "23A", "30H", "30G", "31B", "31A", "32B", "32A",
        >                  "32D", "32C", "34C", "34B", "46B", "46A", "47B", "47A",
        >                  "56B", "56A" ],
        >     rootsof:= [ -23, -23, -15, -15, -31, -31, 2, 2,
        >                 -2, -2, 17, 17, -23, -23, -47, -47,
        >                 7, 7 ] );;
        gap> galoisinfo.irrats:= List( galoisinfo.rootsof, Sqrt );;
        gap> for p in Filtered( [ 1 .. Maximum( bhead.OrdersClassRepresentatives ) ],
        >                       IsPrimeInt ) do
        >      map:= [ 1 ];
        >      for i in [ 2 .. Length( Bnames ) ] do
        >        if bhead.OrdersClassRepresentatives[i] = p then
        >          map[i]:= 1;
        >        elif bhead.OrdersClassRepresentatives[i] mod p = 0 then
        >          # The 'p'-th power has smaller order, we know the image class.
        >          info:= First( powerinforec.( Bnames[i] ), pair -> pair[1] = p );
        >          map[i]:= Position( Bnames, info[2] );
        >        else
        >          # The 'p'-th power is a Galois conjugate.
        >          pos:= Position( galoisinfo.classes, Bnames[i] );
        >          if pos = fail then
        >            # The 'i'-th class is rational.
        >            map[i]:= i;
        >          else
        >            # Determine whether the pair gets swapped.
        >            irrat:= galoisinfo.irrats[ pos ];
        >            if GaloisCyc( irrat, p ) <> irrat then
        >              map[i]:= Position( Bnames, galoisinfo.partners[ pos ] );
        >            else
        >              map[i]:= i;
        >            fi;
        >          fi;
        >        fi;
        >      od;
        >      bhead.ComputedPowerMaps[p]:= map;
        >    od;
        gap> ConvertToCharacterTable( bhead );;

Check the library table of B against the character table head.

        gap> b:= CharacterTable( "B" );;
        gap> for p in Filtered( [ 2 ..
        >                 Maximum( OrdersClassRepresentatives( b ) ) ],
        >                 IsPrimeInt ) do
        >      PowerMap( b, p );
        >    od;
        gap> ComputedPowerMaps( bhead ) = ComputedPowerMaps( b );
        true

Determine the missing pieces of the class fusion from the `2B` centralizer to B.

        gap> maps:= ContainedMaps( cb2bfusb );;
        gap> Length( maps );
        32
        gap> good:= [];;
        gap> for map in maps do
        >      ind:= InducedClassFunctionsByFusionMap( cb2b, b, Irr( cb2b ), map );
        >      if ForAll( ind, x -> IsInt( ScalarProduct( b, x, x ) ) ) then
        >        Add( good, map );
        >      fi;
        >    od;
        gap> Length( good );
        1
        gap> b2b:= CharacterTable( "BM2" );;
        gap> good[1] = GetFusionMap( b2b, b );
        true

In our situation (where the classes of the subgroup have been identified via the class invariants for B, and where we have made appropriate choices for the pairs of Galois conjugate classes), the class fusion is unique.
The newly computed fusion coincides with the one that is stored on the GAP library table.

## 7 The irreducible characters of B

We assume the following information about the Baby Monster group B.

- The conjugacy class lengths, the element orders, and the power maps (for all primes up to the maximal element order in B) are known and coincide with the information that is shown in [CCN+85].
- The group B contains subgroups of the structures 2.2E6(2).2, Fi23, and HN.2. The ordinary character tables of these groups have been verified (see [BMO17]) and thus may be used in our computations.
- The character table of the `2B` centralizer in B is known. Also the class fusion from this table to the table head of B is known by the construction of this character table in Section 5.

For the sake of simplicity, we start with the ATLAS table of B and store the power maps up to the maximal element order (needed for inducing from cyclic subgroups).

        gap> b:= CharacterTable( "B" );;
        gap> for p in Filtered( [ 2 ..
        >                 Maximum( OrdersClassRepresentatives( b ) ) ],
        >                 IsPrimeInt ) do
        >      PowerMap( b, p );
        >    od;

In order to make sure that the irreducible characters that are stored on the table are not silently used inside some computations, we delete them from the character table.

        gap> irr_atlas:= Irr( b );;
        gap> ResetFilterObj( b, HasIrr );

Now we compute candidates for the class fusions from the subgroups which we are allowed to use. For that, we write a small GAP program. The input parameters are the character tables of the subgroup and B, a list of characters of B, and perhaps a first approximation of the class fusion in question.

        gap> tryFusion:= function( s, b, ind, initmap )
        >      local i, sfusb, poss, good, test, map, indmap, indgood;
        >
        >      for i in [ 1 .. Length( ComputedPowerMaps( b ) ) ] do
        >        if IsBound( ComputedPowerMaps( b )[i] ) then
        >          PowerMap( s, i );
        >        fi;
        >      od;
        >
        >      if initmap = fail then
        >        sfusb:= InitFusion( s, b );;
        >      else
        >        sfusb:= initmap;
        >      fi;
        >
        >      if not TestConsistencyMaps( ComputedPowerMaps( s ), sfusb,
        >                 ComputedPowerMaps( b ) ) then
        >        Error( "inconsistency in power maps!" );
        >      fi;
        >
        >      poss:= FusionsAllowedByRestrictions( s, b, Irr( s ), ind, sfusb,
        >                 rec( decompose:= true, minamb:= 2, maxamb:= 10^4,
        >                      quick:= false, maxlen:= 10,
        >                      contained:= ContainedPossibleCharacters ) );
        >      indgood:= [];
        >      if ForAll( poss, x -> ForAll( x, IsInt ) ) then
        >        # All candidates in 'poss' are unique.
        >        # Consider only representatives under the symmetry group of 's'.
        >        poss:= RepresentativesFusions( s, poss, Group( () ) );
        >
        >        # Discard candidates for which the scalar products
        >        # of induced characters are not integral.
        >        good:= [];
        >        test:= ( n  -> IsInt( n ) and 0 <= n );
        >        for map in poss do
        >          indmap:= InducedClassFunctionsByFusionMap( s, b, Irr( s ), map );
        >          if ForAll( indmap,
        >                 x -> ForAll( indmap,
        >                          y -> test( ScalarProduct( b, x, y ) ) ) ) then
        >            Add( good, map );
        >          fi;
        >        od;
        >        poss:= good;
        >
        >        # Compute those induced characters that arise independent of
        >        # the fusion map.
        >        indgood:= Intersection( List( good,
        >            map -> InducedClassFunctionsByFusionMap( s, b, Irr( s ),
        >                       map ) ) );
        >      fi;
        >
        >      return rec( maps:= poss, induced:= indgood );
        >    end;;

Our initial characters of B are the trivial character and the characters that arise from inducing irreducible characters of cyclic subgroups.

        gap> knownirr:= [ TrivialCharacter( b ) ];;
        gap> indcyc:= InducedCyclic( b, [ 2 .. NrConjugacyClasses( b ) ], "all" );;

The class fusion from Fi23 to B is determined uniquely by the available data, and this takes only a few seconds.

        gap> fi23:= CharacterTable( "Fi23" );;
        gap> fi23fusb:= tryFusion( fi23, b, indcyc, fail );;
        gap> Length( fi23fusb.maps );
        1
        gap> indfi23:= fi23fusb.induced;;

The class fusion from CB(2B) to B may be assumed, see Section 5.

        gap> b2b:= CharacterTable( "BM2" );;
        gap> b2bfusb:= GetFusionMap( b2b, b );;
        gap> indb2b:= Set( InducedClassFunctionsByFusionMap( b2b, b,
        >                      Irr( b2b ), b2bfusb ) );;

The subgroups Th and HN.2 are treated in the same way as Fi23.

        gap> hn2:= CharacterTable( "HN.2" );;
        gap> ind:= Concatenation( indfi23, indb2b, indcyc );;
        gap> hn2fusb:= tryFusion( hn2, b, ind, fail );;
        gap> Length( hn2fusb.maps );
        1
        gap> indhn2:= hn2fusb.induced;;
        gap> th:= CharacterTable( "Th" );;
        gap> ind:= Concatenation( indfi23, indb2b, indcyc );;
        gap> thfusb:= tryFusion( th, b, ind, fail );;
        gap> Length( thfusb.maps );
        1
        gap> indth:= thfusb.induced;;

Now we want to determine the class fusion from H = 2.2E6(2).2 to B. The approach used above is not feasible in this case. In order to refine the initial approximation of the class fusion, we use that H′ contains a subgroup of the type 2.Fi22 that is contained also in a Fi23 type subgroup of B. Note that H is the centralizer of an involution z in B from the class `2A`, and the class `2A` of Fi23 lies in this class. We may choose our Fi23 subgroup such that it contains z. The centralizer of z in Fi23 has then the type 2.Fi22.
Thus we compute the possible class fusions from 2.Fi22 to Fi23 and to H′. The compositions of the former maps with the known fusion from Fi23 to B yields the possible class fusions from 2.Fi22 to B, and the compositions of these fusions with the inverses of the latter maps yield the desired approximations for the fusion from Fi23 to B.

        gap> 2fi22:= CharacterTable( "2.Fi22" );;
        gap> 2fi22fusfi23:= PossibleClassFusions( 2fi22, fi23 );;
        gap> 2fi22fusb:= Set( List( 2fi22fusfi23, map -> CompositionMaps(
        >        fi23fusb.maps[1], map ) ) );;
        gap> Length( 2fi22fusb );
        2
        gap> hh:= CharacterTable( "2.2E6(2)" );;
        gap> 2fi22fushh:= PossibleClassFusions( 2fi22, hh );;
        gap> approxhhfusb:= [];;
        gap> for map1 in 2fi22fushh do
        >      for map2 in 2fi22fusb do
        >        AddSet( approxhhfusb, CompositionMaps( map2, InverseMap( map1 ) ) );
        >      od;
        >    od;
        gap> Length( approxhhfusb );
        4
        gap> inithhfusb:= InitFusion( hh, b );;
        gap> TestConsistencyMaps( ComputedPowerMaps( hh ), inithhfusb,
        >        ComputedPowerMaps( b ) );
        true
        gap> for i in [ 1 .. Length( approxhhfusb ) ] do
        >      if MeetMaps( approxhhfusb[i], inithhfusb ) <> true then
        >        Unbind( approxhhfusb[i] );
        >      fi;
        >    od;
        gap> approxhhfusb:= Compacted( approxhhfusb );;
        gap> Length( approxhhfusb );
        2

We get two initial approximations, and the computation of the class fusion from H′ to B is now easy.

        gap> hhfusb:= List( approxhhfusb, map -> tryFusion( hh, b, ind, map ) );;
        gap> List( hhfusb, r -> Length( r.maps ) );
        [ 1, 1 ]
        gap> hhfusb[1] = hhfusb[2];
        true
        gap> hhfusb:= hhfusb[1];;

Thus we have determined the class fusion from H′ to B uniquely. The next step is to compute the class fusion for the classes in H that do not lie in H′.

        gap> h:= CharacterTable( "2.2E6(2).2" );;
        gap> hhfush:= PossibleClassFusions( hh, h );;
        gap> Length( hhfush );
        4
        gap> approxhfusb:= Set( List( hhfush,
        >        map -> CompositionMaps( hhfusb.maps[1], InverseMap( map ) ) ) );;
        gap> Length( approxhfusb );
        2
        gap> inithfusb:= InitFusion( h, b );;
        gap> TestConsistencyMaps( ComputedPowerMaps( h ), inithfusb,
        >        ComputedPowerMaps( b ) );
        true
        gap> List( approxhfusb, map -> MeetMaps( map, inithfusb ) );
        [ true, true ]
        gap> ind:= Concatenation( indfi23, indb2b, indhn2, hhfusb.induced,
        >              indcyc );;
        gap> hfusb:= List( approxhfusb, map -> tryFusion( h, b, ind, map ) );;
        gap> List( hfusb, r -> Length( r.maps ) );
        [ 1, 1 ]
        gap> hfusb[1].induced = hfusb[2].induced;
        true
        gap> hfusb:= hfusb[1];;
        gap> indh:= hfusb.induced;;

Now we know many induced characters of B. The Z-lattice that is spanned by these characters contains several irreducible characters. Unfortunately, the LLL program in GAP does not find them immediately.
Therefore, we proceed now in two steps. First, we assume that B has a rational ordinary irreducible character χ, say, of degree 4371 whose 3- and 5-modular restrictions are the Brauer characters of the representations which we have used in Section 3. From χ together with the known induced characters, we easily compute a list of vectors of norm 1 such that the input characters are linear combinations of these vectors, with nonnegative integer coefficients. In the second step, we will then not use χ but we use the vectors found in the first step as our candidates for the irreducible characters, which just have to be verified.
Let us start with the first step, and compute the values of χ. For each representative of order not divisible by 30, we compute the Brauer character value from a representation in characteristic coprime to the element order; for the remaining classes, we store 'fail' as a preliminary value.

        gap> chi:= [];;
        gap> for nam in labels do
        >      slp:= SLPForClassName( nam, cycprg, outputnames );
        >      ord:= Int( Filtered( nam, IsDigitChar ) );
        >      if ord mod 2 <> 0 then
        >        val:= 1 + BrauerCharacterValue(
        >                      ResultOfStraightLineProgram( slp, gens_2 ) );
        >      elif ord mod 3 <> 0 then
        >        val:= BrauerCharacterValue(
        >                  ResultOfStraightLineProgram( slp, gens_3 ) );
        >      elif ord mod 5 <> 0 then
        >        val:= BrauerCharacterValue(
        >                  ResultOfStraightLineProgram( slp, gens_5 ) );
        >      else
        >        val:= fail;
        >      fi;
        >      Add( chi, val );
        >    od;

Next we transfer these values to the class positions in the character table. For that, we compute the mapping from the character table head to the labels.

        gap> bfuslabels:= [];;
        gap> for i in [ 1 .. Length( libBnames ) ] do
        >      poss:= Filtered( labels, x -> Filtered( libBnames[i], IsDigitChar )
        >             = Filtered( x, IsDigitChar ) and
        >             ForAny( Filtered( x, IsAlphaChar ), y -> y in libBnames[i] ) );
        >      if Length( poss ) <> 1 then
        >        Print( "problem for ", libBnames[i], "\n" );
        >      fi;
        >      bfuslabels[i]:= Position( labels, poss[1] );
        >    od;

The character values are unknown for eight classes of element order 30 and three classes of element order 60.

        gap> chi:= chi{ bfuslabels };
        [ 4371, -493, 275, -53, 19, 78, -3, -77, 51, 19, -21, 35, -13, 11, 3, -1, -5,
          21, -4, -34, 20, 14, -7, 4, -8, 5, -2, 13, 1, 1, 10, -21, 7, -9, 11, -1,
          -5, -5, 3, -1, 3, 7, -1, -1, -1, -3, 6, 7, 5, -3, 0, -1, 4, 4, -12, -3, 4,
          6, -6, 5, -2, 0, -4, 2, -3, 2, 1, -1, 5, 0, -3, 1, 3, -1, 3, -10, 4, -4, 2,
          -2, 3, 2, 3, -5, -1, 3, -1, 3, 1, 1, 2, 2, 2, 2, -2, 1, -2, 1, -7, -2, 3,
          1, -1, 1, 0, -1, 2, 0, 1, 2, 0, 1, 1, -2, 0, 2, -4, 0, -3, 2, 1, -2, 0, 1,
          1, -1, -1, 1, -1, 1, 0, 2, -2, 0, 0, 0, fail, fail, fail, fail, fail, fail,
          fail, fail, 0, 0, 1, 1, -1, -1, 1, -2, 0, 0, 0, 0, 0, -1, 1, 0, -3, 1, -1,
          -1, 0, -1, 1, -1, 0, -1, -1, 0, 0, 0, -2, -1, -1, 0, 0, fail, fail, fail,
          -1, 0 ]
        gap> failpos:= Positions( last, fail );
        [ 137, 138, 139, 140, 141, 142, 143, 144, 180, 181, 182 ]
        gap> OrdersClassRepresentatives( b ){ failpos };
        [ 30, 30, 30, 30, 30, 30, 30, 30, 60, 60, 60 ]

In order to compute the missing values, we use the same idea as in Section 5. We know that these values are integers. For all classes in question except one, the class lengths are at least |B| / 360, thus the absolute value of the character value at these classes cannot exceed 5.

        gap> SizesCentralizers( b ){ failpos };
        [ 3600, 360, 360, 240, 240, 120, 60, 60, 120, 120, 60 ]
        gap> bclasses:= SizesConjugacyClasses( b );;
        gap> normpart:= Sum(
        >        List( Difference( [ 1 .. Length( bclasses ) ], failpos ),
        >              i -> bclasses[i] * chi[i]^2 ) );;
        gap> normpart + 6^2 * Size( b ) / 360 > Size( b );
        true

Since χ( gp ) ≡ χ(g) mod p for p ∈ { 3, 5 } and since the values χ( gp ) are known, we know the congruence class of χ( g ) modulo 15 for all missing classes except one. This determines χ.

        gap> for i in failpos do
        >      v:= ChineseRem( [ 3, 5 ], [ chi[ PowerMap( b, 3, i ) ],
        >                                  chi[ PowerMap( b, 5, i ) ] ] );
        >      if v > 5 then
        >        v:= v - 15;
        >      fi;
        >      chi[i]:= v;
        >    od;
        gap> chi{ failpos };
        [ -5, 1, -3, -1, -1, -2, 0, 0, -1, -1, 0 ]
        gap> Sum( List( [ 1 .. Length( bclasses ) ],
        >               i -> bclasses[i] * chi[i]^2 ) ) = Size( b );
        true

Now we compute candidates for the irreducibles, under the assumption that χ is a character. The following simple function is later used in a loop. It takes a character table `tbl` and three lists of (virtual) characters of this table: `knownirr` contains known irreducibles, `newirr` contains newly found irreducibles, and `knownvirt` contains virtual characters. The idea is as follows. The list `knownirr` gets extended (in place) by `newirr`, symmetrizations of the characters in `newirr` and tensor products of the characters in `knownirr` with those in `newirr` are created, and the concatenation of `knownvirt` and these characters gets reduced with `knownirr` and `newirr`; this process is iterated as long as new irreducible characters are found in the reduction step, with `newirr` replaced by these characters; the function returns the list of non-irreducible remainders of `knownvirt`.

        gap> ExtendCandidates:= function( tbl, knownirr, newirr, knownvirt )
        >      while 0 < Length( newirr ) do
        >        Append( knownirr, newirr );
        >        knownvirt:= Concatenation( knownvirt,
        >                        Concatenation( List( [ 2, 3, 5 ],
        >                            p -> Symmetrizations( b, newirr, p ) ) ),
        >                        Set( Tensored( knownirr, newirr ) ) );
        >        knownvirt:= Reduced( tbl, knownirr, knownvirt );
        >        newirr:= knownvirt.irreducibles;
        >        knownvirt:= knownvirt.remainders;
        >      od;
        >
        >      Print( "#I  ", Length( knownirr ), " irreducibles known\n" );
        >      return knownvirt;
        >    end;;

We start with the trivial character of B and χ, and note that the antisymmetric square of χ is irreducible. We initialize `knownvirt` with the union of the induced characters which were generated above.

        gap> psi:= AntiSymmetricParts( b, [ chi ], 2 )[1];;
        gap> ScalarProduct( b, psi, psi );
        1
        gap> testind:= Concatenation( indfi23, indb2b, indhn2, indh, indth, indcyc );;

Reducing the characters with our three irreducible characters and applying the LLL algorithm to the remainders yields 7 new irreducibles.

        gap> knownirr:= [];;
        gap> l:= ExtendCandidates( b, knownirr,
        >         [ TrivialCharacter( b ), chi, psi ], testind );;
        #I  3 irreducibles known
        gap> lll:= LLL( b, l, 99/100 );;  Length( lll.irreducibles );
        7

We are lucky, a short loop of reductions with the new irreducibles and LLL reduction yields the complete list of irreducible characters. The ch

        gap> n:= NrConjugacyClasses( b );;
        gap> while 0 < Length( lll.irreducibles ) and
        >       Length( lll.irreducibles ) + Length( knownirr ) < n do
        >      l:= ExtendCandidates( b, knownirr,
        >                            lll.irreducibles, lll.remainders );;
        >      lll:= LLL( b, l, 99/100 );;
        >    od;
        #I  11 irreducibles known
        #I  17 irreducibles known
        gap> Append( knownirr, lll.irreducibles );

The irreducible characters found this way coincide with the ones from the ATLAS table of B.

        gap> Set( irr_atlas ) = Set( knownirr );
        true

Now comes step two. As stated above, all what remains is to verify the candidate vectors, where we are allowed to use the induced characters but not χ.
For each candidate vector, we compute whether it occurs in the Z-span of the induced characters, and if yes, we compute the coefficients of a linear combination in terms of them. This way, we find/verify the first 30 irreducible characters of B.

        gap> ind:= Concatenation( indfi23, indb2b, indhn2, indh, indcyc );;
        gap> mat:= MatScalarProducts( b, knownirr, ind );;
        gap> irr:= [ TrivialCharacter( b ) ];;
        gap> one:= IdentityMat( NrConjugacyClasses( b ) );;
        gap> for i in [ 2 .. Length( one ) ] do
        >      coeffs:= SolutionIntMat( mat, one[i] );
        >      if coeffs <> fail and ForAll( coeffs, IsInt ) then
        >        Add( irr, coeffs * ind );
        >      fi;
        >    od;
        gap> Length( irr );
        30
        gap> Set( List( irr, chi -> ScalarProduct( b, chi, chi ) ) );
        [ 1 ]
        gap> ForAll( irr, chi -> chi[1] > 0 );
        true

In order to get the missing irreducible characters of B, we add symmetrizations and tensor products of the known irreducibles to the list of induced characters, and project onto the orthogonal space of the space that is spanned by the known irreducibles. No new irreducible characters are found directly this way, but the ``oracle`' that was used above tells us how to express the missing irreducibles as integral linear combinations of the known characters.

        gap> sym:= Symmetrizations( b, irr, 2 );;
        gap> Append( sym, Symmetrizations( b, irr, 3 ) );
        gap> Append( sym, Symmetrizations( b, irr, 5 ) );
        gap> ten:= Set( Tensored( irr, irr ) );;
        gap> cand:= Reduced( b, irr, Concatenation( sym, ten, ind ) );;
        gap> cand.irreducibles;
        [  ]
        gap> cand:= cand.remainders;;
        gap> newirr:= [];;
        gap> mat:= MatScalarProducts( b, knownirr, cand );;
        gap> for i in [ 2 .. Length( one ) ] do
        >      coeffs:= SolutionIntMat( mat, one[i] );
        >      if coeffs <> fail and ForAll( coeffs, IsInt ) then
        >        Add( newirr, coeffs * cand );
        >      fi;
        >    od;
        gap> Length( newirr );
        154
        gap> Set( List( newirr, chi -> ScalarProduct( b, chi, chi ) ) );
        [ 1 ]
        gap> ForAll( newirr, chi -> chi[1] > 0 );
        true

It is not surprising that the irreducible characters found this way coincide with the ones from the ATLAS table of B.

        gap> Append( irr, newirr );
        gap> Set( irr_atlas ) = Set( irr );
        true

## 8 Appendix: Standardizing the generators of Co2

In Section 5, we have restricted the 3-modular 4371-dimensional representation of B to a subgroup C of the structure 21+22.Co2, and obtained a 23-dimensional composition factor with a faithful action of the factor group Co2, with generators x and y, say. Our aim is to find short words in terms of x and y that yield standard generators for Co2, that is, elements a, b from the classes `2A` and `5A` of Co2, with the properties that the product a b has order 28 and that a and b generate the full group Co2.
The classes `2A` and `5A` of Co2 are determined by the Brauer character values −9 and −2, respectively, in the unique irreducible 23-dimensional 3-modular representation of Co2.

        gap> t:= CharacterTable( "Co2" ) mod 3;;
        gap> dim23:= Filtered( Irr( t ), chi -> chi[1] = 23 );;
        gap> Length( dim23 );
        1
        gap> pos:= PositionsProperty( OrdersClassRepresentatives( t ),
        >                             x -> x in [ 2, 5 ] );
        [ 2, 3, 4, 12, 13 ]
        gap> dim23[1]{ pos };
        [ -9, 7, -1, -2, 3 ]

The 12-th power of the second generator yields a `2A` element.

        gap> List( co2gens, Order );
        [ 2, 24 ]
        gap> BrauerCharacterValue( co2gens[1] );
        7
        gap> a:= co2gens[2]^12;;
        gap> BrauerCharacterValue( a );
        -9

A short word that defines a `5A` element is (y4 x)4. It can be found as follows.

        gap> f:= FreeMonoid( 2 );;
        gap> fgens:= GeneratorsOfMonoid( f );
        [ m1, m2 ]
        gap> for w in Iterator( f ) do
        >      m:= MappedWord( w, fgens, co2gens );
        >      ord:= Order( m );
        >      if ord mod 5 = 0 then
        >        cand:= m^( ord / 5 );
        >        if BrauerCharacterValue( cand ) = -2 then
        >          break;
        >        fi;
        >      fi;
        >    od;
        gap> w;
        m2^4*m1
        gap> ord;
        20

Similarly, we find a conjugate of the `5A` element such that the product has order 28.

        gap> for w in Iterator( f ) do
        >      m:= MappedWord( w, fgens, co2gens );
        >      b:= cand ^ m;
        >      if Order( a * b ) = 28 then
        >        break;
        >      fi;
        >    od;
        gap> w;
        m2*(m2*m1)^3

In order to show that the two elements `a` and `b` really generate Co2, we use the fact that no proper subgroup of Co2 contains elements of the orders 23 and 30.

        gap> t:= CharacterTable( "Co2" );;
        gap> mx:= List( Maxes( t ), CharacterTable );;
        gap> ForAny( List( Maxes( t ), CharacterTable ),
        >            x -> IsSubset( OrdersClassRepresentatives( x ),
        >                           [ 23, 30 ] ) );
        false

It suffices to find products of the generators that have these orders. We compute random elements until we are successful.

        gap> u:= Group( a, b );;
        gap> found:= [];;
        gap> repeat
        >      x:= Order( PseudoRandom( u ) );
        >      if x in [ 23, 30 ] then
        >        AddSet( found, x );
        >      fi;
        >    until Length( found ) = 2;
        gap> found;
        [ 23, 30 ]

## 9 Appendix: Words for generators of the kernel 222

We assume (see Section 5) that we have permutation generators `stdperms` on 4060 points for the group P ≅ 222.Co2 such that mapping them to standard generators of Co2 defines an epimorphism. Our aim is to find (short) words in terms of `stdperms` for generators of the elementary abelian normal subgroup of order 222.
For that, we work in parallel with standard generators `co2permgens` of Co2.

        gap> co2permgens:= GeneratorsOfGroup( AtlasGroup( "Co2" ) );;
        gap> f:= FreeMonoid( 2 );;
        gap> fgens:= GeneratorsOfMonoid( f );;
        gap> kernelgens:= [];;
        gap> kernelwords:= [];;
        gap> kernel:= Group( () );;
        gap> for w in Iterator( f ) do
        >      m1:= MappedWord( w, fgens, stdperms );
        >      m2:= MappedWord( w, fgens, co2permgens );
        >      ord1:= Order( m1 );
        >      ord2:= Order( m2 );
        >      if ord1 <> ord2 then
        >        cand:= m1 ^ ord2;
        >        if not cand in kernel then
        >          Add( kernelgens, cand );
        >          Add( kernelwords, [ w, ord2 ] );
        >          kernel:= ClosureGroup( kernel, cand );
        >        fi;
        >      fi;
        >      if Length( kernelgens ) = 22 then
        >        break;
        >      fi;
        >    od;

The kernel generators obtained with the above procedure are equal to the results of the straight line program `slp_ker` from Section 5.

        gap> ResultOfStraightLineProgram( slp_ker, fgens )
        >    = List( kernelwords, pair -> pair[1]^pair[2] );
        true

## 10 Appendix: Words for class representatives of 222.Co2

The group P ≅ 222.Co2 from Section 5 has 388 conjugacy classes, as we can compute from the permutation representation on 4060 points. Our aim is to find (short) words in terms of the generators `stdperms` for conjugacy class representatives of P.
For that, we first compute preimages in P of class representatives of its factor group Co2, using the straight line program `slp_co2classreps` from [WWT+], and use them to initialize lists of known class representatives and of words for each class of Co2. In the lists of words, we record the indices of kernel generators with which we have to multiply the preimage; thus the preimages themselves are denoted by empty lists.

        gap> factccls:= ResultOfStraightLineProgram( slp_co2classreps.program,
        >                   stdperms );;
        gap> classreps:= List( factccls, x -> [ x ] );;
        gap> classwords:= List( factccls, x -> [ [] ] );;

The preimage of the identity element in Co2 is not the identity in P. Since it would be hard to get the identity as a product of this preimage with a product of kernel generators, we add the identity element by hand, and denote it by the word `[ 0 ]`.

        gap> classreps[1]:= Concatenation( [ () ], classreps[1] );;
        gap> classwords[1]:= Concatenation( [ [ 0 ] ], classwords[1] );;

Now we multiply the class representatives by words (of increasing length) in terms of the kernel generators and add the elements if they yields new classes, until we have found enough class representatives.
The conjugacy checks in the following piece of GAP code cannot be executed with GAP 4.9.3 or earlier versions in reasonable time. We have done these computations via delegations to MAGMA [BCP97], using the auxiliary function `IsConjugateViaMagma`. (We hope that eventually the necessary functionality will become available also in GAP.)

        gap> if CTblLib.IsMagmaAvailable() then
        >      IsConjugateViaMagma:= function( permgroup, pi, known )
        >        local path, inputs, str, out, result, pos;
        >
        >        path:= UserPreference( "CTblLib", "MagmaPath" );
        >        inputs:= [ CTblLib.MagmaStringOfPermGroup( permgroup, "G" ),
        >                   Concatenation( "p:= G!", String( pi ), ";" ),
        >                   "l:= [" ];
        >        if Length( known ) > 0 then
        >          Append( inputs,
        >                List( known, p -> Concatenation( "G!", String( p ), "," ) ) );
        >          Remove( inputs[ Length( inputs ) ] );
        >        fi;
        >        Append( inputs,
        >                [ "];",
        >                  "conj:= false;",
        >                  "for q in l do",
        >                  "  conj:= IsConjugate( G, p, q );",
        >                  "  if conj then break; end if;",
        >                  "end for;",
        >                  "if conj then",
        >                  "  print true;",
        >                  "else",       # Do not call '# Class( G, p );'.
        >                  "  print \"#\", # Centralizer( G, p );",
        >                  "end if;" ] );
        >        str:= "";
        >        out:= OutputTextString( str, true );
        >        result:= Process( DirectoryCurrent(), path,
        >            InputTextString( JoinStringsWithSeparator( inputs, "\n" ) ),
        >            out, [] );
        >        CloseStream( out );
        >        if result <> 0 then
        >          Error( "Process returned ", result );
        >        fi;
        >        pos:= PositionSublist( str, "\n# " );
        >        if pos <> fail then
        >          return Int( str{ [ pos + 3 .. Position( str, '\n', pos+1 )-1 ] } );
        >        elif PositionSublist( str, "true" ) <> fail then
        >          # 'pi' is conjugate to a perm. in 'known'
        >          return true;
        >        else
        >          Error( "Magma failed" );
        >        fi;
        >      end;
        >      co2tbl:= CharacterTable( "Co2" );;
        >      g:= Group( stdperms );;
        >      for i in [ 1 .. Length( factccls ) ] do
        >        iclasses:= List( classreps[i], x -> ConjugacyClass( g, x ) );
        >        sum:= 2^22 * SizesConjugacyClasses( co2tbl )[i]
        >              - Sum( List( iclasses, Size ) );
        >        len:= 1;
        >        while sum > 0 do
        >          for tup in Combinations( [ 1 .. 22 ], len ) do
        >            cand:= factccls[i] * Product( kernelgens{ tup } );
        >            # We call Magma anyhow in order to compute the class length.
        >            conj:= IsConjugateViaMagma( g, cand, classreps[i] );
        >            if conj <> true then
        >              Add( classreps[i], cand );
        >              Add( classwords[i], tup );
        >              sum:= sum - Size( g ) / conj;
        >              if sum <= 0 then
        >                break;
        >              fi;
        >            fi;
        >          od;
        >          len:= len + 1;;
        >        od;
        >      od;
        >    fi;

Let us check whether the class representatives fit to the ones used in Section 5 (up to ordering).

        gap> if CTblLib.IsMagmaAvailable() then
        >      Print( ForAll( [ 1 .. Length( factccls ) ],
        >        i -> Set( classrepsinfo[i][2] ) = Set( classwords[i] ) ), "\n" );
        true
        >    else
        >      Print( "Magma not available, no check of class representatives\n" );
        >    fi;

## 11 Appendix: About the character table of 29+16.S8(2)

As has been stated in Section 6, we use the character table of a maximal subgroup of B that normalizes an elementary abelian group of order 28 instead of the table of the `2D` normalizer in B. The character table of this maximal subgroup, of the structure 29+16.S8(2), had been computed from a matrix representation of the group. We verify that this matrix group can indeed be obtained from the restriction of one of our certified matrix representations of B.
First we restrict our representation of B over the field with two elements to the fourth maximal subgroup.

        gap> prg:= AtlasProgram( "B", "maxes", 4 );;
        gap> gens:= ResultOfStraightLineProgram( prg.program, gens_2 );;

Next we compute a 180 dimensional representation of this group. (The fact that this representation is faithful follows from the computations with it; this is beyond the scope of this note.)

        gap> m:= GModuleByMats( gens, GF(2) );;
        gap> a:= gens[1];;  b:= gens[2];;
        gap> mat:= a^2 + a*b^2 + b^3 * a * b^2 + b*a + b^3 * a;;
        gap> nsp:= NullspaceMat( mat );;  Length( nsp );
        5
        gap> s:= MTX.SubGModule( m, nsp[4] );;
        gap> ind:= MTX.InducedActionSubmodule( m, s );;
        gap> MTX.Dimension( ind );
        206
        gap> css:= MTX.BasesCompositionSeries( ind );;
        gap> List( css, Length );
        [ 0, 26, 27, 35, 163, 171, 172, 198, 206 ]
        gap> ind2:= MTX.InducedActionFactorModule( ind, css[2] );;
        gap> MTX.Dimension( ind2 );
        180

The word used above has a 3 dimensional nullspace on the 180 dimensional module. We try each nonzero vector in that space as a seed vector, and consider the standard bases of the submodules generated by them; for that, we apply the function `StdBasis` from Section 2.

        gap> gensnew:= ind2.generators;;
        gap> a:= gensnew[1];;  b:= gensnew[2];;
        gap> mat:= a^2 + a*b^2 + b^3 * a * b^2 + b*a + b^3 * a;;
        gap> nsp:= NullspaceMat( mat );;  Length( nsp );
        3
        gap> stdbasnew:= List( NormedRowVectors( VectorSpace( GF(2), nsp ) ),
        >                      seed -> StdBasis( GF(2), gensnew, seed ) );;
        gap> List( stdbasnew, Length );
        [ 180, 180, 9, 180, 145, 145, 180 ]
        gap> stdbasnew:= Filtered( stdbasnew, x -> Length( x ) = 180 );;
        gap> stdgensnew:= List( stdbasnew, x -> List( gensnew, m -> x*m*x^-1 ) );;

We repeat the same process with the matrix generators from which the character table of the maximal subgroup 29+16.S8(2) of B has been computed.

        gap> gensold:= OneAtlasGeneratingSet( "2^(9+16).S8(2)", Dimension, 180 );;
        gap> gensold.repname;
        "Bmax4G0-f2r180B0"
        gap> gensold:= gensold.generators;;
        gap> a:= gensold[1];;  b:= gensold[2];;
        gap> mat:= a^2 + a*b^2 + b^3 * a * b^2 + b*a + b^3 * a;;
        gap> nsp:= NullspaceMat( mat );;  Length( nsp );
        3
        gap> stdbasold:= List( NormedRowVectors( VectorSpace( GF(2), nsp ) ),
        >                      seed -> StdBasis( GF(2), gensold, seed ) );;
        gap> List( stdbasold, Length) ;
        [ 180, 180, 9, 180, 145, 145, 180 ]
        gap> stdbasold:= Filtered( stdbasold, x -> Length( x ) = 180 );;
        gap> stdgensold:= List( stdbasold, x -> List( gensold, m -> x*m*x^-1 ) );;

It turns out that the normal forms obtained this way coincide.

        gap> List( stdgensnew, x -> Position( stdgensold, x ) );
        [ 1, 2, 4, 3 ]

## References

[BCP97]
  W. Bosma, J. Cannon, and C. Playoust, The Magma algebra system. I. The user language, J. Symbolic Comput. 24 (1997), no. 3-4, 235-265. MR 1484478
[BGH+17]
  M. Bhargava, R. Guralnick, G. Hiss, K. Lux, and P. H. Tiep (eds.), Finite simple groups: thirty years of the Atlas and beyond, Contemporary Mathematics, vol. 694, Providence, RI, American Mathematical Society, 2017. MR 3682583
[BMO17]
  T. Breuer, G. Malle, and E. A. O'Brien, Reliability and reproducibility of Atlas information, in Bhargava et al. [BGH+17], p. 21-31. MR 3682588
[BMW20]
  T. Breuer, K. Magaard, and R. A. Wilson, Verification of the ordinary character table of the Baby Monster, J. Algebra 561 (2020), 111-130. MR 4135540
[CCN+85]
  J. H. Conway, R. T. Curtis, S. P. Norton, R. A. Parker, and R. A. Wilson, Atlas of finite groups, Oxford University Press, Eynsham, 1985, Maximal subgroups and ordinary characters for simple groups, With computational assistance from J. G. Thackray. MR 827219 (88g:20025)
[GAP24]
  GAP - Groups, Algorithms, and Programming, Version 4.13.1, `https://www.gap`-`system.org`, Mar 2024.
[Pah07]
  H. Pahlings, The character table of 21+22+.Co2, J. Algebra 315 (2007), no. 1, 301-325. MR 2344348 (2008g:20023)
[Str76]
  G. Stroth, A characterization of Fischer's sporadic simple group of the order 241 ·313 ·56 ·72 ·11 ·13 ·17 ·19 ·23 ·31 ·47, J. Algebra 40 (1976), no. 2, 499-531. MR 0417277 (54 #5334)
[Wil96]
  R. A. Wilson, Standard generators for sporadic simple groups, J. Algebra 184 (1996), no. 2, 505-515. MR 1409225 (98e:20025)
[WWT+]
  R. A. Wilson, P. Walsh, J. Tripp, I. Suleiman, R. A. Parker, S. P. Norton, S. Nickerson, S. Linton, J. Bray, and R. Abbott, ATLAS of Finite Group Representations, `http://brauer.maths.qmul.ac.uk/` `Atlas/v3`.

File translated from TEX by TTH, version 3.59.
On 22 May 2025, 15:03.