home *** CD-ROM | disk | FTP | other *** search
/ Math Solutions 1995 October / Math_Solutions_CD-ROM_Walnut_Creek_October_1995.iso / pc / mac / discrete / lib / ctautoms.g < prev    next >
Encoding:
Text File  |  1993-05-05  |  40.7 KB  |  1,028 lines

  1. #############################################################################
  2. ##
  3. #A  ctautoms.g                  GAP library                     Thomas Breuer
  4. ##
  5. #A  @(#)$Id: ctautoms.g,v 3.13 1993/02/09 14:22:15 sam Rel $
  6. ##
  7. #Y  Copyright 1990-1992,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
  8. ##
  9. ##  This file contains functions to calculate automorphisms of matrices, e.g.
  10. ##  the character matrices of character tables, and functions to calculate
  11. ##  permutations transforming the rows of a matrix to the rows of another
  12. ##  matrix.
  13. ##
  14. #H  $Log: ctautoms.g,v $
  15. #H  Revision 3.13  1993/02/09  14:22:15  sam
  16. #H  fixed VERY BAD BUG in 'TransformingPermutationsCharTables'
  17. #H
  18. #H  Revision 3.12  1992/08/07  10:00:26  sam
  19. #H  'TableAutomorphisms' returns now a proper group (not a subgroup)
  20. #H
  21. #H  Revision 3.11  1992/01/17  09:30:34  sam
  22. #H  changed names: 'MatrixAutomorphisms' -> 'MatAutomorphisms',
  23. #H  'MatrixAutomotphismsFamily' -> 'MatAutomorphismsFamily'
  24. #H
  25. #H  Revision 3.10  1991/12/20  14:12:35  sam
  26. #H  call of 'TableAutomorphisms' adjusted
  27. #H
  28. #H  Revision 3.9  1991/12/20  14:02:56  sam
  29. #H  parameters for 'MatrixAutomorphisms' changed
  30. #H
  31. #H  Revision 3.8  1991/12/20  09:42:24  sam
  32. #H  little improvements in 'TransformingPermutationsCharTables'
  33. #H
  34. #H  Revision 3.7  1991/12/06  17:21:14  sam
  35. #H  changed 'ONPOINTS' to 'OnPoints'
  36. #H
  37. #H  Revision 3.6  1991/12/04  17:17:14  sam
  38. #H  changed 'CASPER' in header to 'GAP'
  39. #H
  40. #H  Revision 3.5  1991/12/04  12:59:09  sam
  41. #H  indented functions
  42. #H
  43. #H  Revision 3.4  1991/12/02  17:46:29  sam
  44. #H  adjusted to GAP-3.1
  45. #H
  46. #H  Revision 3.3  1991/09/06  07:13:53  sam
  47. #H  changed calls of 'Group' where empty list may be argument
  48. #H
  49. #H  Revision 3.2  1991/09/05  15:50:40  sam
  50. #H  changed 'Transposed' to 'TransposedMat'
  51. #H
  52. #H  Revision 3.1  1991/09/03  14:59:26  goetz
  53. #H  changed 'reps' to 'orders'.
  54. #H
  55. #H  Revision 3.0  1991/09/03  14:33:50  goetz
  56. #H  Initial Revision.
  57. #H
  58. ##
  59. #H  04.07.91 sam fixed a bug in the last rows of 'TableAutomorphisms'
  60. #H  18.07.91 sam renamed 'GaloisMatrix' to 'GaloisMat'
  61.  
  62.  
  63. #############################################################################
  64. ##
  65. #F  InfoPermGroup1( ... ) . .  information function for the permgroup package
  66. #F  InfoPermGroup2( ... ) . .  information function for the permgroup package
  67. ##
  68.     if not IsBound( InfoPermGroup1 )  then InfoPermGroup1 := Ignore;  fi;
  69.     if not IsBound( InfoPermGroup2 )  then InfoPermGroup2 := Ignore;  fi;
  70.  
  71.  
  72. #############################################################################
  73. ##
  74. #F  FamiliesOfRows( <mat>, <maps> )
  75. ##
  76. ##  distributes the rows of <mat> to families:
  77. ##  Two rows of <mat> belong to the same family if there is a permutation
  78. ##  of columns that maps one row to the other row;
  79. ##  all elements of <maps> are regarded as family of length 1.
  80. ##
  81. ##  'FamiliesOfRows( <mat>, <maps> )' returns a record with fields
  82. ##  'famreps':      list of representatives for each family,
  83. ##  'permutations': list that contains at position 'i' a list of permutations
  84. ##                  which map the members of the family with representative
  85. ##                  'famreps[i]' to that representative,
  86. ##  'families':     list that contains at position <i> the list of positions
  87. ##                  of members of the family of representative 'famreps[<i>]';
  88. ##                  (for the element '<maps>[i]' the only member of the family
  89. ##                  will get the number 'Length( <mat> ) + i'.
  90. ##
  91. FamiliesOfRows := function ( mat, maps )
  92.     local i, j, k, famreps, permutations, copyrow, permrow, pos, famlengths,
  93.           actperm, families, row;
  94.  
  95.     famreps:= [ Copy( mat[1] ) ];    # (sorted) representatives for families
  96.     permutations:= [ [ Sortex( famreps[1] ) ] ];
  97.                                      # list of perms for each family
  98.     families:= [ [ 1 ] ];            # list of members of each family
  99.  
  100.     for j in [ 2 .. Length( mat ) ] do
  101.       copyrow:= Copy( mat[j] );
  102.       permrow:= Sortex( copyrow ); # copyrow is sorted (not stable)
  103.       pos:= PositionSorted( famreps, copyrow );
  104.       if IsBound( famreps[ pos ] ) and famreps[ pos ] = copyrow then
  105.         Add( permutations[ pos ], permrow );
  106.         Add( families[ pos ], j );
  107.       else                        # new family
  108.         for k in Reversed( [ pos .. Length( famreps ) ] ) do
  109.           famreps[ k+1 ]:= famreps[k];
  110.           permutations[ k+1 ]:= permutations[k];
  111.           families[ k+1 ]:= families[k];
  112.         od;
  113.         famreps[ pos ]:= copyrow;
  114.         permutations[ pos ]:= [ permrow ];
  115.         families[ pos ]:= [ j ];
  116.        fi;
  117.     od;
  118.     j:= Length( mat );
  119.     for row in maps do
  120.       j:= j+1;
  121.       Add( famreps, Copy( row ) );
  122.       Add( permutations, [ Sortex( famreps[ Length( famreps ) ] ) ] );
  123.       Add( families, [ j ] );
  124.     od;
  125.     famlengths:= [];               # sort families according to length
  126.     for i in [ 1 .. Length( famreps ) ] do
  127.       famlengths[i]:= Length( permutations[i] );
  128.     od;
  129.     actperm:= Sortex( famlengths );
  130.     return rec( famreps:= Permuted( famreps, actperm ),
  131.                 permutations:= Permuted( permutations, actperm ),
  132.                 families:= Permuted( families, actperm ) );
  133.     end;
  134.  
  135.  
  136. #############################################################################
  137. ##
  138. #F  MatAutomorphismsFamily( <G>, <K>, <family>, <permutations> )
  139. ##
  140. ##  for a family <rows> of rows with representative (i.e. sorted vector)
  141. ##  <famrep> and corresponding permutations
  142. ##  'Sortex(<rows>[j])=<permutations>[j]',
  143. ##  the group of column permutations in <G> is computed that acts on
  144. ##  the set <rows>.
  145. ##
  146. ##  <family> is a list that distributes the columns into families:
  147. ##  Stabilizing <family> is equivalent to stabilizing <famrep>; so the
  148. ##  elements of <permutations> must be computed with respect to <family>, too.
  149. ##  Two columns <i>, <j> lie in the same family iff
  150. ##  '<family>[<i>] = <family>[<j>'.
  151. ##  (More precisely, <family>[i] is the list of all positions lying in the
  152. ##  same family as i.)
  153. ##
  154. ##  <K> is a list of permutation generators for a known subgroup of the
  155. ##  required group.
  156. ##
  157. ##  Note: The returned group has a base compatible with the base of <G>,
  158. ##        i.e. not a reduced base (used for "TransformingPermutationFamily")
  159. ##
  160. MatAutomorphismsFamily := function( G, K, family, permutations )
  161.     local j, k, stabilizes, famlength, nonbase, ElementPropertyCoset,
  162.           FindSubgroupProperty, allowed, gen;
  163.  
  164.     famlength:= Length( permutations );
  165.         
  166.     # compute a stabilizer chain for $G$.
  167.     # select an optimal base that allows us to prune  the  tree  efficiently!
  168.     
  169.     MakeStabChain( G );
  170.     nonbase:= Difference( [ 1 .. Length( family) ], G.operations.Base( G ) );
  171.     
  172.     # call a modified version of 'SubgroupProperty'\:
  173.     #   The subgroup <K> is a parameter.
  174.     #   new parameter <allowed>\: a list with same length as <permutations>;
  175.     #   '<allowed>[<i>]' is the list of all <x> in <permutations> where the
  176.     #   constructed permutation can lie in
  177.     #   '<permutations>[<i>] * Stab( <family> ) / <x>'.
  178.     #   (at the beginning this is <permutations>)
  179.     #   Note that <allowed> will be adjusted when an image
  180.     #   of a basepoint is chosen.
  181.     
  182.     # find a subgroup  $K$  of  $G$  which  preserves  the  property  <prop>,
  183.     # i.e., $prop( x )$ implies $prop( x * k )$  for all  $x \in G, k \in K$.
  184.     # since this subgroup is changed in the algorithm use 'Copy' or  'Group'.
  185.     # make this  subgroup  as  large  as  possible  with  reasonable  effort!
  186.     
  187.     # here: parameter subgrp; add these generators of <G> which stabilize the
  188.     #       whole row family, i.e. for which holds
  189.     #       '<family>[i] = <family>[ i^ ( x^-1 * gen * x ) ]'.
  190.     
  191.     stabilizes:= function( family, gen, x )
  192.     local i;
  193.     for i in [ 1 .. Length( family ) ] do
  194.       if family[ i^x ] <> family[ ( i^gen )^x ] then return false; fi;
  195.     od;
  196.     return true;
  197.     end;
  198.  
  199.     K:= Set( K );
  200.     for gen in G.generators do
  201.       if ForAll( permutations, x -> stabilizes( family, gen, x ) ) then
  202.         AddSet( K, gen );
  203.       fi;
  204.     od;
  205.     K:= Group( K, () );
  206.  
  207.     # make the bases compatible
  208.     MakeStabChain(   K, G.operations.Base( G ) );
  209.     ExtendStabChain( K, G.operations.Base( G ) );
  210.  
  211.     # initialize allowed
  212.     allowed:= List( [ 1 .. famlength ], x -> permutations );
  213.     
  214.     # and now: the functions!
  215.     
  216.     # search through the whole group $G = G * Id$ for an element with <prop>.
  217.  
  218.     # search for an element in a coset $S * s$ of some stabilizer $S$ of $G$.
  219.     # $L$ fixes $S*s$, i.e., $S*s*L = S*s$ and is a subgroup  of  the  wanted
  220.     # subgroup $K$, thus $prop( x )$ implies $prop( x*l )$ for all $l \in L$.
  221.  
  222.     ElementPropertyCoset := function ( S, s, L, allowed )
  223.         local  i, j, x, points, p, ss, LL, elm, l, newallowed, union;
  224.  
  225.         # if $S$ is the trivial group check whether  $s$  has  the  property,
  226.         # i.e. also the non-basepoints map correctly\:
  227.  
  228.         if S.generators = [] then
  229.           for i in [ 1 .. famlength ] do
  230.             for p in nonbase do
  231.               allowed[i]:= Filtered( allowed[i],
  232.               x -> ( p^s )^x in family[ p^permutations[i] ] );
  233.             od;
  234.             if allowed[i] = [] then return false; fi;
  235.           od;
  236.           return s;
  237.         fi;
  238.  
  239.         # make 'points' a subset  of  $S.orbit ^ s$  of  those  points  which
  240.         # correspond to cosets that might contain elements satisfying <prop>.
  241.         # make  this  set  as  small  as  possible  with  reasonable  effort!
  242.         points := Set( OnTuples( S.orbit, s ) );
  243.  
  244.     # better: for the basepoint '$b$ = S.orbit[1]' we have
  245.     #         $b \pi \in orbit \cap \bigcap_{i}
  246.     #         \bigcup_{\pi_j \in 'allowed[i]'} [ family( b \pi_i ) ] \pi_j^{-1}$
  247.  
  248.         for i in [ 1 .. famlength ] do
  249.           union:= [];
  250.           for j in allowed[i] do
  251.             UniteSet( union, List( family[ S.orbit[1] ^ permutations[i] ],
  252.                                    x -> x / j ) );
  253.           od;
  254.           IntersectSet( points, union );
  255.         od;
  256.  
  257.         # run through the points, i.e., through the cosets of the stabilizer.
  258.         while points <> []  do
  259.  
  260.             # take a point $p$.
  261.             p  := points[1];
  262.  
  263.             # find coset representant, i.e., $ss  \in  S, S.orbit[1]^ss = p$.
  264.             ss := s;
  265.             while S.orbit[1]^ss <> p do ss := S.transversal[p/ss] mod ss; od;
  266.  
  267.             # find a subgroup $LL$  of  $L$  which fixes $S.stabilizer * ss$,
  268.             # i.e., an approximation (subgroup) $LL$ of $Stabilizer( L, p )$.
  269.             # note that $LL$ preserves <prop> since it is a subgroup  of $L$.
  270.             # compute a better aproximation, for example using  'ChangeBase'!
  271.             LL:= Subgroup( Parent(G), Filtered( L.generators, l->p^l=p ) );
  272.  
  273.             # search the coset $S.stabilizer * ss$ and  return if successful.
  274.  
  275.     # now adjust allowed: 
  276.  
  277.             newallowed:= [];
  278.             for i in [ 1 .. famlength ] do
  279.               newallowed[i]:= Filtered( allowed[i], x -> p^x in
  280.                                        family[ S.orbit[1]^permutations[i] ] );
  281.             od;
  282.  
  283.             elm := ElementPropertyCoset( S.stabilizer, ss, LL, newallowed );
  284.             if elm <> false  then return elm;  fi;
  285.  
  286.             # if there was no element in $S.stab * Rep(p)$ satisfying  <prop>
  287.             # there can be none in  $S.stab * Rep(p^l) = S.stab * Rep(p) * l$
  288.             # for any $l \in L$ because $L$ preserves  the  property  <prop>.
  289.             # thus we can remove the entire $L$ orbit of $p$ from the points.
  290.             SubtractSet( points, G.operations.Orbit(L,p,OnPoints) );
  291.         od;
  292.  
  293.         # there is no element with the property <prop> in the coset  $S * s$.
  294.         return false;
  295.     end;
  296.  
  297.     # make $L$ the subgroup with the property of some stabilizer $S$ of  $G$.
  298.     # upon  entry  $L$  is  already  a  subgroup  of  this  wanted  subgroup.
  299.     FindSubgroupProperty := function ( S, L, allowed )
  300.         local  points, p, ss, LL, elm, l, newallowed, union, orb, len, k, i,
  301.                gens;
  302.  
  303.         # if $S$ is the trivial group, then so  is  $L$  and  we  are  ready.
  304.         if S.generators = [] then return;  fi;
  305.  
  306.         # make $L.stab$ the full  subgroup  of  $S.stab$  satisfying  <prop>.
  307.  
  308.     # now adjust allowed: (we search in the stabilizer of S.orbit[1])
  309.  
  310.         newallowed:= [];
  311.         for i in [ 1 .. famlength ] do
  312.           newallowed[i]:= Filtered( allowed[i],
  313.                                     x -> S.orbit[1]^x in
  314.                                    family[ S.orbit[1]^permutations[i] ] );
  315.         od;
  316.  
  317.         FindSubgroupProperty( S.stabilizer, L.stabilizer, newallowed );
  318.  
  319.         # add the generators of L.stabilizer to L.generators,
  320.         # update orbit and transversal:
  321.  
  322.         for elm in L.stabilizer.generators do
  323.           if not elm in L.generators then
  324.             G.operations.AddGensExtOrb( L, [ elm ] );
  325.           fi;
  326.         od;
  327.  
  328.         # make  'points'  a  subset  of  $S.orbit$  of  those  points   which
  329.         # correspond to cosets that might contain elements satisfying <prop>.
  330.         # make  this  set  as  small  as  possible  with  reasonable  effort!
  331.         points := Set( S.orbit );
  332.  
  333.     # better: for the basepoint '$b$ = S.orbit[1]' we have
  334.     #         $b \pi \in orbit \cap \bigcap_{i}
  335.     #         \bigcup_{j \in 'allowed[i]'} [ family[ b \pi_i ] ] \pi_j^{-1}$
  336.  
  337.         for i in [ 1 .. famlength ] do
  338.           union:= [];
  339.           for j in allowed[i] do
  340.             UniteSet( union, List( family[ S.orbit[1] ^ permutations[i] ],
  341.                                    x -> x / j ) );
  342.           od;
  343.           IntersectSet( points, union );
  344.         od;
  345.  
  346.         # suppose that $x \in S.stab * Rep(S.orbit[1]^l)$  satisfies  <prop>,
  347.         # since $S.stab*Rep(S.orbit[1]^l)=S.stab*l$ we have $x/l \in S.stab$.
  348.         # because $l \in L$ it follows that $x/l$ satisfies <prop> also,  and
  349.         # since $L.stab$ is the full subgroup of $S.stab$  satisfying  <prop>
  350.         # it follows that $x/l \in L.stab$  and so  $x \in L.stab * l \<= L$.
  351.         # thus we can remove the entire $L$ orbit of  $p$  from  the  points.
  352.         SubtractSet(points,G.operations.Orbit(L,S.orbit[1],OnPoints));
  353.  
  354.         # run through the points, i.e., through the cosets of the stabilizer.
  355.         while points <> []  do
  356.  
  357.             # take a point $p$.
  358.             p := points[1];
  359.  
  360.             # find coset representant, i.e., $ss  \in  S, S.orbit[1]^ss = p$.
  361.             ss := S.identity;
  362.             while S.orbit[1]^ss <> p do ss := S.transversal[p/ss] mod ss; od;
  363.  
  364.             # find a subgroup $LL$  of  $L$  which fixes $S.stabilizer * ss$,
  365.             # i.e., an approximation (subgroup) $LL$ of $Stabilizer( L, p )$.
  366.             # note that $LL$ preserves <prop> since it is a subgroup  of $L$.
  367.             # compute a better aproximation, for example using  'ChangeBase'!
  368.             LL:= Subgroup( Parent(G), Filtered( L.generators, l->p^l=p ) );
  369.  
  370.             # search the coset $S.stabilizer * ss$  and  add  if  successful.
  371.  
  372.     # now adjust allowed: 
  373.  
  374.             newallowed:= [];
  375.             for i in [ 1 .. famlength ] do
  376.               newallowed[i]:= Filtered( allowed[i], x -> p^x in
  377.                                        family[ S.orbit[1]^permutations[i] ] );
  378.             od;
  379.  
  380.             elm := ElementPropertyCoset( S.stabilizer, ss, LL, newallowed );
  381.             if elm <> false then
  382.                 G.operations.AddGensExtOrb( L, [ elm ] );
  383.             fi;
  384.  
  385.             # if there was no element in $S.stab * Rep(p)$ satisfying  <prop>
  386.             # there can be none in  $S.stab * Rep(p^l) = S.stab * Rep(p) * l$
  387.             # for any $l \in L$ because $L$ preserves  the  property  <prop>.
  388.             # thus we can remove the entire $L$ orbit of $p$ from the points.
  389.             # <<this must be reformulated>>
  390.             SubtractSet(points,G.operations.Orbit(L,p,OnPoints) );
  391.         od;
  392.  
  393.         # there is no element with the property <prop> in the coset  $S * s$.
  394.         return;
  395.     end;
  396.     FindSubgroupProperty( G, K, allowed );
  397.     return K;
  398.     end;
  399.  
  400.  
  401. #############################################################################
  402. ##
  403. #F  MatAutomorphisms( <mat>, <maps>, <subgroup> )
  404. ##
  405. ##  returns the permutation group record representing the matrix
  406. ##  automorphisms of the matrix <mat> that respect all lists in the list
  407. ##  <maps>.
  408. ##
  409. ##  <subgroup> is a permutation group record representing a subgroup of
  410. ##  this group.
  411. ##
  412. MatAutomorphisms := function ( mat, maps, subgroup )
  413.     local i, j, k, fam, colfam, values, famreps, permutations, pos,
  414.           famlengths, actperm, nonfixedpoints, blocks, actval, newblocks,
  415.           newnonfixedpoints, generators, permg, omega, block, sblock,
  416.           support, family, famrep, G, row;
  417.  
  418.     if IsPermGroup( subgroup ) then
  419.       subgroup:= Set( subgroup.generators );
  420.     else
  421.       Error( "<subgroup> must be a permutation group record" );
  422.     fi;
  423.  
  424.     # step 1: Distribute the rows into row families
  425.  
  426.     fam:= FamiliesOfRows( mat, maps );
  427.     mat:= Concatenation( mat, maps );
  428.     
  429.     # step 2: Distribute the columns into families using that row families of
  430.     #         length 1 must be fixed by every table automorphism.
  431.     
  432.     nonfixedpoints:= [ [ 1 .. Length( mat[1] ) ] ];
  433.     i:= 1;
  434.     while i <= Length( fam.famreps ) and Length( fam.families[i] ) = 1 do
  435.       row:= mat[ fam.families[i][1] ];
  436.       for j in [ 1 .. Length( nonfixedpoints ) ] do
  437.         colfam:= nonfixedpoints[j];
  438.         values:= Set( Sublist( row, colfam ) );
  439.         nonfixedpoints[j]:= Filtered( colfam, x-> row[x] = values[1] );
  440.         for k in [ 2 .. Length( values ) ] do
  441.           Add( nonfixedpoints, Filtered( colfam, x-> row[x] = values[k] ) );
  442.         od;
  443.       od;
  444.       nonfixedpoints:= Filtered( nonfixedpoints, x -> Length(x) > 1 );
  445.       i:= i+1;
  446.     od;
  447.     
  448.     # step 3: Refine the column families using that members of a family must
  449.     #         have the same sorted column in the restriction to every row
  450.     #         family.
  451.     #         Since trivial row families are already examined, only use
  452.     #         nontrivial ones.
  453.     
  454.     while i <= Length( fam.famreps ) do
  455.       row:= TransposedMat( Sublist( mat, fam.families[i] ) );
  456.       for j in row do Sort( j ); od;
  457.       for j in [ 1 .. Length( nonfixedpoints ) ] do
  458.         colfam:= nonfixedpoints[j];
  459.         values:= Set( Sublist( row, colfam ) );
  460.         nonfixedpoints[j]:= Filtered( colfam, x-> row[x] = values[1] );
  461.         for k in [ 2 .. Length( values ) ] do
  462.           Add( nonfixedpoints, Filtered( colfam, x-> row[x] = values[k] ) );
  463.         od;
  464.       od;
  465.       nonfixedpoints:= Filtered( nonfixedpoints, x -> Length(x) > 1 );
  466.       i:= i+1;
  467.     od;
  468.     
  469.     if nonfixedpoints = [] then return Group( () ); fi;
  470.     
  471.     # step 4: Compute a direct product of symmetric groups that covers the
  472.     #         group of table automorphisms.
  473.     
  474.     generators:= [];
  475.     for omega in nonfixedpoints do
  476.       Add( generators, ( omega[1], omega[2] ) );        # transposition
  477.       if Length( omega ) > 2 then
  478.         permg:= ( omega[1], omega[2] );
  479.         for k in [ 3 .. Length( omega ) ] do
  480.           permg:= ( omega[k-1], omega[k] ) * permg;
  481.         od;
  482.         Add( generators, permg );                       # transitive cycle
  483.       fi;
  484.     od;
  485.     
  486.     # step 5: ... and now: the backtrack search for permutation groups!
  487.     
  488.     permutations:= fam.permutations;
  489.     famreps:= fam.famreps;
  490.     G:= Group( generators, () );
  491.     
  492.     InfoPermGroup2( "#I MatAutomorphisms: There are ",
  493.                     Length( permutations ), " families (",
  494.                     Length( Filtered( permutations, x-> Length(x) =1 ) ),
  495.                     " trivial)\n" );
  496.     
  497.     for i in [ 1 .. Length( famreps ) ] do
  498.       if Length( permutations[i] ) > 1 then
  499.     
  500.         InfoPermGroup2( "#I MatAutomorphismsFamily called for family no. ",
  501.                         i, "\n" );
  502.     
  503.         # First convert <famreps>[i] to 'family': 'family[<k>]' is the list
  504.         # of all positions <j> in <famreps>[i] with
  505.         # '<famreps>[i][<k>] = <famreps>[i][<j>]'.
  506.         # So each permutation stabilizing <famreps>[i] will have to map <k>
  507.         # to a point in '<family>[<k>]'.
  508.         # (Note that <famreps>[i] is sorted.)
  509.     
  510.         famrep:= famreps[i];
  511.         support:= Length( famrep );
  512.         family:= [ ];
  513.         j:= 1;
  514.         while j <= support do
  515.           family[j]:= [ j ];
  516.           k:= j+1;
  517.           while k <= support and famrep[k] = famrep[j] do
  518.             Add( family[j], k );
  519.             family[k]:= family[j];
  520.             k:= k+1;
  521.           od;
  522.           j:= k;
  523.         od;
  524.         G:= MatAutomorphismsFamily( G, Copy(subgroup), family,
  525.                                        permutations[i] );
  526.         ReduceStabChain( G );
  527.       fi;
  528.     od;
  529.     return G;
  530.     end;
  531.  
  532.  
  533. #############################################################################
  534. ##
  535. #F  TableAutomorphisms( <tbl>, <characters> )
  536. #F  TableAutomorphisms( <tbl>, <characters>, \"closed\" )
  537. ##
  538. ##  returns a permutation group record for the group of those matrix
  539. ##  automorphisms of <characters> (see "MatAutomorphisms") which are
  540. ##  compatible with (i.e. which fix) the fields 'orders' and all uniquely
  541. ##  determined (i.e.\ not parametrized) maps in 'powermap' of
  542. ##  the character table <tbl>.
  543. ##
  544. ##  If <characters> is closed under galois conjugacy --this is always
  545. ##  fulfilled for ordinary character tables-- the parameter \"closed\"
  546. ##  may be entered.
  547. ##
  548. TableAutomorphisms := function( arg )
  549.     local i, x, tbl, characters, subgroup, maut, map, admissible, gens, nccl;
  550.  
  551.     if not Length( arg ) in [ 2, 3 ] or not IsCharTable( arg[1] )
  552.        or not IsList( arg[2] )
  553.        or ( Length( arg ) = 3 and arg[3] <> "closed" ) then
  554.       Error( "usage: TableAutomorphisms( <tbl>, <characters> ) resp.\n",
  555.       "              TableAutomorphisms( <tbl>, <characters>, \"closed\" )" );
  556.     fi;
  557.  
  558.     tbl:= arg[1];
  559.     characters:= arg[2];
  560.     if Length( arg ) = 3 then
  561.       subgroup:= Group( GaloisMat( TransposedMat( characters ) ).generators,
  562.                         () );
  563.     else
  564.       subgroup:= Group( () );
  565.     fi;
  566.     #
  567.     if IsBound( tbl.orders ) then
  568.       maut:= MatAutomorphisms( characters, [tbl.orders,tbl.centralizers],
  569.                                   subgroup );
  570.     else
  571.       maut:= MatAutomorphisms( characters,
  572.                                   [ tbl.centralizers ], subgroup );
  573.     fi;
  574.     gens:= List( maut.generators, x-> List( x ) );
  575.     for x in gens do
  576.       for i in [ Length(x)+1 .. Length( tbl.centralizers ) ] do x[i]:= i; od;
  577.     od;
  578.     admissible:= gens;
  579.     if IsBound( tbl.powermap ) then
  580.       for map in tbl.powermap do
  581.         if ForAll( map, IsInt ) then
  582.           admissible:= Filtered( admissible, x-> CompositionMaps(map,x)
  583.                                                  = CompositionMaps(x,map) );
  584.         fi;
  585.       od;
  586.     fi;
  587.     admissible:= List( admissible, PermList );
  588.     if Length( admissible ) <> Length( gens ) then
  589.       InfoPermGroup2( "#I TableAutomorphisms:",
  590.                       " not all matrix automorphisms admissible\n" );
  591.       nccl:= Length( tbl.powermap[ Length( tbl.powermap ) ] );
  592.       admissible:= Group( maut.operations.SubgroupProperty( maut,
  593.                                  perm -> ForAll( tbl.powermap, 
  594.                                             x -> ForAll( [ 1 .. nccl ],
  595.                                          y -> x[ y^perm ] = x[y]^perm ) ),
  596.                                      Group( admissible, maut.identity ) ) );
  597.     else
  598.       admissible:= Group( admissible, maut.identity );
  599.     fi;
  600.     return admissible;
  601.     end;
  602.  
  603.  
  604. ##############################################################################
  605. ##
  606. #F  TransformingPermutationFamily( <G>,<K>,<fam1>,<fam2>,<bij_col>,<family> )
  607. ##
  608. ##  computes a transforming permutation of columns for equivalent families
  609. ##  of rows of two matrices.
  610. ##  (The parameters can be computed from the matrices <mat1>, <mat2> using
  611. ##  "FamiliesOfRows").
  612. ##
  613. ##  'TransformingPermutationFamily' returns either 'false' or a record
  614. ##  with fields 'permutation' and 'group'.
  615. ##
  616. ##  <G>: group with the property that the transforming permutation lies in
  617. ##       the coset '<bij_col> * <G>'
  618. ##  <K>: a subgroup of the group of matrix automorphisms of <fam2> which is
  619. ##       contained in <G>, e.g. Aut( <mat2> )
  620. ##
  621. ##       Note: The bases of <G> and <K> must be compatible!!
  622. ##
  623. ##  <fam1>: the permutations mapping the rows of the family in <mat1> to the
  624. ##          representative (the so-called famrep)
  625. ##  <fam2>: the permutations mapping the rows of the family in mat2 to the
  626. ##          famrep
  627. ##  <bij_col>: permutation corresponding to the bijection of columns in mat1
  628. ##             and mat2
  629. ##  <family>: map that distributes the columns into families; two columns
  630. ##            <i>, <j> are in the same family iff
  631. ##            '<family>[<i>] = <family>[<j>]'.
  632. ##            <G> must stabilize <family>.
  633. ##            Note: Stabilizing the famrep is
  634. ##            equivalent to respecting <family>, so the calculation of
  635. ##            <fam1> and <fam2> must respect <family>, too!
  636. ##
  637. TransformingPermutationFamily := function( G, K, fam1, fam2, bij_col, family )
  638.     local x, allowed, ElementPropertyCoset, nonbase, permutations, result;
  639.     
  640.     # step a: replace permutations 'p' in <fam1> by '<bij_col>^(-1) * p',
  641.     #         initialize allowed
  642.     
  643.     permutations:= List( fam1, x -> bij_col^(-1) * x );
  644.     allowed:= List( [ 1 .. Length( fam1 ) ], x -> fam2 );
  645.     
  646.     # step b: 'ElementProperty'
  647.  
  648.     # search for an element in a coset $S * s$ of some stabilizer $S$ of $G$.
  649.     # $L$ is a subgroup of $G$ that fixes $S * s$, i.e., $S * s * L = S * s$,
  650.     # preserving <prop>, $prop( x )$ implies $prop( x*l )$ for all $l \in L$.
  651.     ElementPropertyCoset := function ( S, s, L, allowed )
  652.         local  i, j, points, p, ss, LL, elm, l, newallowed, union;
  653.  
  654.         # if $S$ is the trivial group check whether  $s$  has  the  property.
  655.         if S.generators = [] then
  656.  
  657.     # property: does s map the nonbasepoints in the right way?
  658.  
  659.           for i in [ 1 .. Length( permutations ) ] do
  660.             for p in nonbase do
  661.               allowed[i]:= Filtered( allowed[i],
  662.                            x -> ( p^s )^x in family[ p^permutations[i] ] );
  663.             od;
  664.             if allowed[i] = [] then return false; fi;
  665.           od;
  666.           return s;
  667.         fi;
  668.  
  669.         # make 'points' a subset  of  $S.orbit ^ s$  of  those  points  which
  670.         # correspond to cosets that might contain elements satisfying <prop>.
  671.         # make  this  set  as  small  as  possible  with  reasonable  effort!
  672.         points := Set( OnTuples( S.orbit, s ) );
  673.  
  674.         for i in [ 1 .. Length( permutations ) ] do
  675.           union:= [];
  676.           for j in allowed[i] do
  677.             UniteSet( union, List( family[ S.orbit[1]^permutations[i] ],
  678.                                    x -> x/j ) );
  679.           od;
  680.           IntersectSet( points, union );
  681.         od;
  682.  
  683.         # run through the points, i.e., through the cosets of the stabilizer.
  684.         while points <> []  do
  685.  
  686.             # take a point $p$.
  687.             p := points[1];
  688.  
  689.             # find coset representant, i.e., $ss \in S*s, S.orbit[1]^ss = p$.
  690.             ss := s;
  691.             while S.orbit[1]^ss <> p do ss := S.transversal[p/ss] mod ss; od;
  692.  
  693.             # find a subgroup $LL$  of  $L$  which fixes $S.stabilizer * ss$,
  694.             # i.e., an approximation (subgroup) $LL$ of $Stabilizer( L, p )$.
  695.             # note that $LL$ preserves <prop> since it is a subgroup  of $L$.
  696.             # compute a better aproximation, for example using  'ChangeBase'!
  697.             LL:= Subgroup( Parent(G), Filtered( L.generators, l->p^l=p ) );
  698.  
  699.             # search the coset $S.stabilizer * ss$ and  return if successful.
  700.  
  701.          # first adjust allowed:
  702.  
  703.             newallowed:= [];
  704.             for i in [ 1 .. Length( allowed ) ] do
  705.               newallowed[i]:= Filtered( allowed[i], x -> p^x in
  706.                                        family[ S.orbit[1]^permutations[i] ] );
  707.             od;
  708.             elm := ElementPropertyCoset( S.stabilizer, ss, LL, newallowed );
  709.             if elm <> false  then return elm;  fi;
  710.  
  711.             # if there was no element in $S.stab * Rep(p)$ satisfying  <prop>
  712.             # there can be none in  $S.stab * Rep(p^l) = S.stab * Rep(p) * l$
  713.             # for any $l \in L$ because $L$ preserves  the  property  <prop>.
  714.             # thus we can remove the entire $L$ orbit of $p$ from the points.
  715.             SubtractSet( points, G.operations.Orbit(L,p,OnPoints) );
  716.  
  717.         od;
  718.  
  719.         # there is no element with the property <prop> in the coset  $S * s$.
  720.         return false;
  721.  
  722.     end;
  723.  
  724.     # compute a stabilizer chain for $G$.
  725.     # select an optimal base that allows us to prune  the  tree  efficiently!
  726.     nonbase:= Difference( [ 1 .. Length( family ) ], G.operations.Base( G ) );
  727.  
  728.     # find a subgroup  $K$  of  $G$  which  preserves  the  property  <prop>,
  729.     # i.e., $prop( x )$ implies $prop( x * k )$  for all  $x \in G, k \in K$.
  730.     # make this  subgroup  as  large  as  possible  with  reasonable  effort!
  731.     K := Copy( K );
  732.  
  733.     # search through the whole group $G = G * Id$ for an element with <prop>.
  734.     return ElementPropertyCoset( G, G.identity, K, allowed );
  735.     end;
  736.  
  737.  
  738. ##############################################################################
  739. ##
  740. #F  TransformingPermutations( <mat1>, <mat2> )
  741. ##
  742. ## constructs a permutation $\pi$ that transforms the set of rows of the
  743. ## matrix <mat1> to the set of rows of the matrix <mat2> by permutation
  744. ## of columns.
  745. ## If such a permutation exists, a record with fields 'columns', 'rows'
  746. ## and 'group' is returned, otherwise 'false'\:
  747. ## If $'TransformingPermutations( <mat1>, <mat2> ) = <r>' \not= 'false'$ then
  748. ## 'Permuted( List( <mat1>, x->Permuted( x, <r>.columns ) ),<r>.rows )=<mat2>'
  749. ## and '<r>.group' is the group of matrix automorphisms of <mat2>;
  750. ## this group stabilizes the transformation.
  751. ##
  752. TransformingPermutations := function( mat1, mat2 )
  753.     local i, j, k, fam1, fam2, bijection, bij_col, pos, bij_rows, group,
  754.           family, subgrp, fam, nonfixedpoints, famrep, support,
  755.           actperm, actval, blocks, newnonfixedpoints, generators,
  756.           omega, permg, newblocks, trans, image, preimage, row1, row2,
  757.           values;
  758.     
  759.     if Length( mat1 ) <> Length( mat2 ) then return false; fi;
  760.     if mat1 = [] then
  761.       if mat2 = [] then
  762.         return rec( columns:= (), rows:= (), group:= Group( () ) );
  763.       else
  764.         return false;
  765.       fi;
  766.     fi;
  767.     
  768.     # step 1: Compute a bijection of row families
  769.     #         (only of families, i.e. famreps; we do not need a physical
  770.     #         bijection of the rows themselves)
  771.     #         using that the sorted rows must be equal.
  772.     
  773.     fam1:= FamiliesOfRows( mat1, [] );
  774.     fam2:= FamiliesOfRows( mat2, [] );
  775.     bij_rows:= List( fam1.famreps, x -> Position( fam2.famreps, x ) );
  776.     if false in bij_rows
  777.        or ForAny( fam1.famreps, x -> not ( x in fam2.famreps ) ) 
  778.        or ForAny( [ 1 .. Length( bij_rows ) ],
  779.                   x -> Length( fam1.permutations[x] )
  780.                        <> Length( fam1.permutations[ bij_rows[x] ] ) ) then
  781.       Print( "#I TransformingPermutations: no bijection of row families\n" );
  782.       return false;
  783.     fi;
  784.     
  785.     # step 2: Initialize a bijection of column families using that row
  786.     #         families of length 1 must be in bijection, i.e. the column
  787.     #         families are constant on these rows.
  788.     
  789.     bij_col:= []; # we will have bij_col[1][i] in bijection with bij_col[2][i]
  790.     bij_col[1]:= [ [ 1 .. Length( mat1[1] ) ] ]; # trivial column families
  791.     bij_col[2]:= [ [ 1 .. Length( mat1[1] ) ] ];
  792.     
  793.     for i in [ 1 .. Length( bij_rows ) ] do
  794.       if Length( fam1.families[i] ) = 1 then
  795.         row1:= mat1[ fam1.families[i][1] ];
  796.         row2:= mat2[ fam2.families[ bij_rows[i] ][1] ];
  797.         for j in [ 1 .. Length( bij_col[1] ) ] do
  798.           preimage:= bij_col[1][j];
  799.           image:=    bij_col[2][j];
  800.           values:= Set( Sublist( row1, preimage ) );
  801.           if values <> Set( Sublist( row2, image ) ) then
  802.             Print( "#I TransformingPermutations:",
  803.                    " no bijection of column families\n" );
  804.             return false;
  805.           fi;
  806.           bij_col[1][j]:= Filtered( preimage, x -> row1[x] = values[1] );
  807.           bij_col[2][j]:= Filtered( image, x -> row2[x] = values[1] );
  808.           if Length( bij_col[1][j] ) <> Length( bij_col[2][j] ) then
  809.             Print( "#I TransformingPermutations:",
  810.                    " no bijection of column families\n" );
  811.             return false;
  812.           fi;
  813.           for k in [ 2 .. Length( values ) ] do
  814.             Add( bij_col[1], Filtered( preimage, x-> row1[x] = values[k] ) );
  815.             Add( bij_col[2], Filtered( image, x-> row2[x] = values[k] ) );
  816.             if Length( bij_col[1][ Length( bij_col[1] ) ] )
  817.                <> Length( bij_col[2][ Length( bij_col[2] ) ] ) then
  818.               Print( "#I TransformingPermutations:",
  819.                      " no bijection of column families\n" );
  820.               return false;
  821.             fi;
  822.           od;
  823.         od;
  824.       fi;
  825.     od;
  826.     
  827.     # step 3: Refine the column families and the bijection using that members
  828.     #         of a column family must have the same sorted column in the
  829.     #         restriction to every row family. Since the trivial row families
  830.     #         are already examined, now only use the nontrivial row families.
  831.     #         Except that now the values are sorted lists, the algorithm is
  832.     #         the same as in step 2.
  833.     
  834.     for i in [ 1 .. Length( bij_rows ) ] do
  835.       if Length( fam1.families[i] ) > 1 then
  836.         row1:= TransposedMat( Sublist( mat1, fam1.families[i] ) );
  837.         row2:= TransposedMat( Sublist( mat2, fam2.families[ bij_rows[i] ] ) );
  838.         for j in row1 do Sort( j ); od;
  839.         for j in row2 do Sort( j ); od;
  840.         for j in [ 1 .. Length( bij_col[1] ) ] do
  841.           preimage:= bij_col[1][j];
  842.           image:=    bij_col[2][j];
  843.           values:= Set( Sublist( row1, preimage ) );
  844.           if values <> Set( Sublist( row2, image ) ) then
  845.             Print( "#I TransformingPermutations:",
  846.                    " no bijection of column families\n" );
  847.             return false;
  848.           fi;
  849.           bij_col[1][j]:= Filtered( preimage, x -> row1[x] = values[1] );
  850.           bij_col[2][j]:= Filtered( image, x -> row2[x] = values[1] );
  851.           if Length( bij_col[1][j] ) <> Length( bij_col[2][j] ) then
  852.             Print( "#I TransformingPermutations:",
  853.                    " no bijection of column families\n" );
  854.             return false;
  855.           fi;
  856.           for k in [ 2 .. Length( values ) ] do
  857.             Add( bij_col[1], Filtered( preimage, x-> row1[x] = values[k] ) );
  858.             Add( bij_col[2], Filtered( image, x-> row2[x] = values[k] ) );
  859.             if Length( bij_col[1][ Length( bij_col[1] ) ] )
  860.                <> Length( bij_col[2][ Length( bij_col[2] ) ] ) then
  861.               Print( "#I TransformingPermutations:",
  862.                      " no bijection of column families\n" );
  863.               return false;
  864.             fi;
  865.           od;
  866.         od;
  867.       fi;
  868.     od;
  869.     
  870.     # Choose an arbitrary bijection of columns that respects the bijection of
  871.     # column families.
  872.     
  873.     bijection:= [];
  874.     for i in [ 1 .. Length( bij_col[1] ) ] do
  875.       for j in [ 1 .. Length( bij_col[1][i] ) ] do
  876.         bijection[ bij_col[1][i][j] ]:= bij_col[2][i][j];
  877.       od;
  878.     od;
  879.     nonfixedpoints:= Filtered( bij_col[2], x -> Length(x) > 1 );
  880.     
  881.     # step 4: Compute a direct prouct of symmetric groups that covers the
  882.     #         group of table automorphisms of mat2, using column families
  883.     #         given by 'bij_col[2]'.
  884.     
  885.     generators:= [];
  886.     for omega in nonfixedpoints do
  887.       Add( generators, ( omega[1], omega[2] ) );           # transposition
  888.       if Length( omega ) > 2 then
  889.         permg:= ( omega[1], omega[2] );
  890.         for k in [ 3 .. Length( omega ) ] do
  891.           permg:= ( omega[k-1], omega[k] ) * permg;
  892.         od;
  893.         Add( generators, permg );                           # transitive cycle
  894.       fi;
  895.     od;
  896.     group:= Group( generators, () );
  897.     
  898.     # step 5: backtrack search for a transforming permutation of columns;
  899.     #         for the families of length > 1 recursively call
  900.     #         TransformingPermutationFamily
  901.     
  902.     InfoPermGroup2("#I TransformingPermutations: start of backtrack search\n");
  903.     
  904.     bij_col:= PermList( bijection );
  905.     
  906.     # and now: loop over row families;
  907.     # first convert <famreps>[i] to 'family': 'family[<k>]' is the list of all
  908.     # positions <j> in <famreps>[i] with
  909.     # '<famreps>[i][<k>] = <famreps>[i][<j>]'.
  910.     # So each permutation stabilizing <famreps>[i] will have to map <k> to
  911.     # a point in '<family>[<k>]'.
  912.     # (Note that <famreps>[i] is sorted.)
  913.     
  914.     for i in [ 1 .. Length( fam1.famreps ) ] do
  915.       if Length( fam1.permutations[i] ) > 1 then
  916.         famrep:= fam1.famreps[i];
  917.         support:= Length( famrep );
  918.         family:= [ ];
  919.         j:= 1;
  920.         while j <= support do
  921.           family[j]:= [ j ];
  922.           k:= j+1;
  923.           while k <= support and famrep[k] = famrep[j] do
  924.             Add( family[j], k );
  925.             family[k]:= family[j];
  926.             k:= k+1;
  927.           od;
  928.           j:= k;
  929.         od;
  930.         subgrp:= MatAutomorphismsFamily( group, [], family,
  931.                                           fam2.permutations[ bij_rows[i] ] );
  932.         trans:= TransformingPermutationFamily( group, subgrp,
  933.                                fam1.permutations[i],
  934.                                fam2.permutations[ bij_rows[i] ], bij_col,
  935.                                family );
  936.         group:= subgrp;
  937.         ReduceStabChain( group );
  938.         if trans = false then return false; fi;
  939.         bij_col:= bij_col * trans;
  940.       fi;
  941.     od;
  942.     return rec( columns:= bij_col,
  943.                 rows:= Sortex( List( mat1, x -> Permuted( x, bij_col ) ) )
  944.                        / Sortex( ShallowCopy( mat2 ) ),
  945.                 group:= group                                        ); 
  946.     end;
  947.  
  948. ##############################################################################
  949. ##
  950. #F  TransformingPermutationsCharTables( <tbl1>, <tbl2> )
  951. ##
  952. ##  constructs a permutation $\pi$ that transforms the set of rows of the
  953. ##  matrix '<tbl1>.irreducibles' to the set of rows of the matrix
  954. ##  '<tbl2>.irreducibles' by permutation of columns.
  955. ##
  956. TransformingPermutationsCharTables := function( tbl1, tbl2 )
  957.     local i, map, trans, maut, admissible, both, prop, pi, pi2, nccl;
  958.  
  959.     if not IsBound( tbl1.irreducibles ) or
  960.        not IsBound( tbl2.irreducibles ) then
  961.       Error( "no irreducibles bound" );
  962.     fi;
  963.     trans:= TransformingPermutations( tbl1.irreducibles, tbl2.irreducibles );
  964.     if trans = false then return false; fi;
  965.     
  966.     # first compute the subgroup of table automorphisms of tbl2
  967.     
  968.     nccl:= Length( tbl1.centralizers );
  969.     maut:= trans.group;
  970.     admissible:= maut.generators;
  971.     if not IsBound( tbl1.powermap ) then tbl1.powermap:= []; fi;
  972.     if not IsBound( tbl2.powermap ) then tbl2.powermap:= []; fi;
  973.     for map in tbl2.powermap do
  974.       if ForAll( map, IsInt ) then
  975.         admissible:= Filtered( admissible, x -> ForAll( [ 1 .. nccl ],
  976.                                              y -> map[y^x] = map[y]^x ) );
  977.       fi;
  978.     od;
  979.     if Length( admissible ) <> Length( maut.generators ) then
  980.       Print( "#I TransformingPermutationsCharTables:",
  981.              " not all matrix automorphisms admissible\n" );
  982.       admissible:= Group( maut.operations.SubgroupProperty( maut,
  983.                                      perm -> ForAll( tbl2.powermap, 
  984.                                                 x -> ForAll( [ 1 .. nccl ],
  985.                                              y -> x[y^perm] = x[y]^perm ) ),
  986.                                      Group( admissible, () ) ) );
  987.     else
  988.       admissible:= trans.group;
  989.     fi;
  990.     
  991.     both:= Intersection( Filtered( [ 1 .. Length( tbl1.powermap ) ],
  992.                                     x -> IsBound( tbl1.powermap[x] ) ),
  993.                          Filtered( [ 1 .. Length( tbl2.powermap ) ],
  994.                                     x -> IsBound( tbl2.powermap[x] ) ) );
  995.     pi:= trans.columns;
  996.     if ForAll( both,
  997.                x -> ForAll( [ 1 .. nccl ],
  998.                     y -> tbl2.powermap[x][ y^pi ] = tbl1.powermap[x][y]^pi ) )
  999.        and ( not ( IsBound( tbl1.orders ) and IsBound( tbl2.orders ) ) or
  1000.              Permuted( tbl1.orders, pi ) = tbl2.orders ) then
  1001.       trans.group:= admissible;
  1002.       return trans;
  1003.     else
  1004.     
  1005.       # Look if there is a coset of 'trans.group' over 'admissible' that
  1006.       # consists of transforming permutations\:
  1007.     
  1008.       MakeStabChain( trans.group, admissible.operations.Base( admissible ) );
  1009.  
  1010.       prop:= s -> ForAll( both, x -> ForAll( [ 1 .. nccl ], y ->
  1011.                 tbl2.powermap[x][ (y^pi)^s ] = (tbl1.powermap[x][y]^pi)^s ) )
  1012.              and ( not ( IsBound( tbl1.orders ) and IsBound(tbl2.orders) ) or
  1013.                    Permuted( tbl1.orders, pi*s ) = tbl2.orders );
  1014.  
  1015.       pi2:= PermGroupOps.ElementProperty( trans.group, prop, admissible );
  1016.       if pi2 = false then
  1017.         return false;
  1018.       else
  1019.         return rec( columns:= pi * pi2,
  1020.                     rows:= Sortex( List( tbl1.irreducibles,
  1021.                                          x -> Permuted( x, pi * pi2 ) ) )
  1022.                            / Sortex( ShallowCopy( tbl2.irreducibles ) ),
  1023.                     group:= admissible  );
  1024.       fi;
  1025.     fi;
  1026.     end;
  1027.  
  1028.