loops/gap/quasigroups.gi
Glen Whitney 91ba2744c1 Merge branch 'Loops3.4.0'
Incorporate the changes from 3.3.0 to 3.4.0 of LOOPS into this
development. These were mostly straightforward. The only conflicts were
in quasigroups.gd, in which all of the changes from this development
were selected, as "IsLatin" had already been removed.
2017-10-30 00:33:44 -04:00

943 lines
33 KiB
Plaintext

#############################################################################
##
#W quasigroups.gi Representing, creating and displaying quasigroups [loops]
##
#H @(#)$Id: creation.gi, v 3.2.0 2015/11/22 gap Exp $
##
#Y Copyright (C) 2004, G. P. Nagy (University of Szeged, Hungary),
#Y P. Vojtechovsky (University of Denver, USA)
##
#############################################################################
## TESTING MULTIPLICATION TABLES
## -------------------------------------------------------------------------
#############################################################################
##
#O IsLeftQuasigroupTable( ls )
##
## Returns true if <ls> is an n by n matrix with n distinct
## integral entries, each occurring exactly once in each row
InstallMethod( IsLeftQuasigroupTable, "for matrix",
[ IsMatrix ],
function( ls )
local first_row;
# checking rows
first_row := Set( ls[ 1 ] );
if not Length( first_row ) = Length( ls[ 1 ] ) then return false; fi;
if ForAll( ls, row -> Set( row ) = first_row ) = false then return false; fi;
return true;
end );
#############################################################################
##
#O IsRightQuasigroupTable( ls )
##
## Returns true if <ls> is an n by n matrix with n distinct
## integral entries, each occurring exactly once in each column
InstallMethod( IsRightQuasigroupTable, "for matrix",
[ IsMatrix ],
mat -> IsLeftQuasigroupTable(TransposedMat(mat))
);
#############################################################################
##
#O IsLoopTable( ls )
##
## Returns true if <ls> is a normalized latin square. An n by n latin square
## is normalized if the first row and first column read the same, and the
## entries in the first row are ordered.
InstallMethod( IsLoopTable, "for matrix",
[ IsMatrix ],
function( ls )
if not IsQuasigroupTable( ls ) then return false; fi;
return Set( ls[ 1 ] ) = ls[ 1 ]
and ls[ 1 ] = List( [1..Length(ls)], i -> ls[ i ][ 1 ] );
end );
#############################################################################
##
#O CanonicalCayleyTableOfLeftQuasigroupTable( ls )
##
## Returns a Cayley table isomorphic to <ls>, which must already be known
## to be a left quasigroup table, in which the entries of ls
## have been replaced by numerical values 1, ..., n in the following way:
## Let e_1 < ... < e_n be all distinct entries of ls. Then e_i is renamed
## to i. In particular, when {e_1,...e_n} = {1,...,n}, the operation
## does nothing.
InstallGlobalFunction( CanonicalCayleyTableOfLeftQuasigroupTable,
function( ls )
local n, entries, i, j, T;
n := Length( ls );
# finding all distinct entries in the table
entries := [];
for i in ls[1] do
AddSet( entries, i );
od;
if entries = [1..n] then return List(ls, l -> ShallowCopy(l));
fi;
# renaming the entries and making a mutable copy, too
T := List( [1..n], i -> [1..n] );
for i in [1..n] do for j in [1..n] do
T[ i ][ j ] := Position( entries, ls[ i ][ j ] );
od; od;
return T;
end );
#############################################################################
##
#O CanonicalCayleyTable( ls )
##
## Returns a Cayley table isomorphic to <ls>, in which the entries of ls
## have been replaced by numerical values 1, ..., n in the following way:
## Let e_1 < ... < e_n be all distinct entries of ls. Then e_i is renamed
## to i. In particular, when {e_1,...e_n} = {1,...,n}, the operation
## does nothing.
InstallMethod( CanonicalCayleyTable, "for matrix",
[ IsMatrix ],
function( ls )
local n, entries, i, j, T;
n := Length( ls );
# finding all distinct entries in the table
entries := [];
for i in [1..n] do for j in [1..n] do
AddSet( entries, ls[ i ][ j ] );
od; od;
if entries = [1..n] then return List(ls, l -> ShallowCopy(l));
fi;
# renaming the entries and making a mutable copy, too
T := List( [1..n], i -> [1..n] );
for i in [1..n] do for j in [1..n] do
T[ i ][ j ] := Position( entries, ls[ i ][ j ] );
od; od;
return T;
end);
#############################################################################
##
#O NormalizedQuasigroupTable( ls )
##
## Given a latin square <ls>, returns the corresponding normalized
## latin square with entries 1, ..., n.
InstallMethod( NormalizedQuasigroupTable, "for matrix",
[ IsMatrix ],
function( ls )
local T, perm;
if not IsQuasigroupTable( ls ) then
Error( "LOOPS: <1> must be a latin square." );
fi;
# renaming the entries to be 1, ..., n
T := CanonicalCayleyTableOfLeftQuasigroupTable( ls );
# permuting the columns so that the first row reads 1, ..., n
perm := PermList( T[ 1 ] );
T := List( T, row -> Permuted( row, perm ) );
# permuting the rows so that the first column reads 1, ..., n
return Set( T );
end );
#############################################################################
## CREATING QUASIGROUPS AND LOOPS MANUALLY
## -------------------------------------------------------------------------
#############################################################################
##
#A CayleyTable( Q )
##
## Returns the Cayley table of the magma <Q>. This is just like
## its multiplication table, except in case Q is a submagma of P, in which
## case the entries used are the indices in P rather than in Q.
InstallMethod( CayleyTable, "for magma",
[ IsMagma ],
function( Q )
local elms, parent_elms;
elms := Elements( Q );
parent_elms := Elements( Parent( Q ) );
return List( elms, x-> List( elms, y -> Position( parent_elms, x * y ) ) );
end );
#############################################################################
##
#O QuasigroupByCayleyTable( ct )
##
## Returns quasigroup with multiplication table <ct>.
InstallMethod( QuasigroupByCayleyTable, "for matrix",
[ IsMatrix ],
function( ct )
local F, Q, elms, n;
if not IsQuasigroupTable( ct ) then
Error( "LOOPS: <1> must be a latin square." );
fi;
# Making sure that entries are 1, ..., n
ct := CanonicalCayleyTableOfLeftQuasigroupTable( ct );
# constructing the family
F := NewFamily( "QuasigroupByCayleyTableFam", IsQuasigroupElement );
# installing data for the family
n := Length ( ct );
F!.size := n;
elms := Immutable( List( [1..n], i -> Objectify(
NewType( F, IsQuasigroupElement and IsQuasigroupElmRep), [ i ] ) ) );
F!.set := elms;
F!.cayleyTable := ct;
F!.elmNamePrefix := "q";
# creating the quasigroup
Q := Objectify( NewType( FamilyObj( elms ),
IsQuasigroup and IsAttributeStoringRep ), rec() );
# setting attributes for the quasigroup
SetSize( Q, n );
SetAsSSortedList( Q, elms );
SetParent( Q, Q );
SetCayleyTable( Q, ct );
SetConstructorFromTable(Q, QuasigroupByCayleyTable);
return Q;
end );
#############################################################################
##
#O LoopByCayleyTable( ct )
##
## Returns loop with multiplication table <ct>.
InstallMethod( LoopByCayleyTable, "for matrix",
[ IsMatrix ],
function( ct )
local F, L, elms, n;
if not IsLoopTable( ct ) then
Error( "LOOPS: <1> must be a normalized latin square." );
fi;
# Making sure that the entries are 1, ..., n.
# The table will remain normalized.
ct := CanonicalCayleyTableOfLeftQuasigroupTable( ct );
# constructing the family
F := NewFamily( "LoopByCayleyTableFam", IsLoopElement );
# installing the data for the family
n := Length ( ct );
F!.size := n;
elms := Immutable( List( [1..n], i -> Objectify(
NewType( F, IsLoopElement and IsLoopElmRep), [ i ] ) ) );
F!.set := elms;
F!.cayleyTable := ct;
F!.elmNamePrefix := "l";
# creating the loop
L := Objectify( NewType( FamilyObj( elms ),
IsLoop and IsAttributeStoringRep ), rec() );
# setting attributes for the loop
SetSize( L, n );
SetAsSSortedList( L, elms );
SetParent( L, L );
SetCayleyTable( L, ct );
SetOne( L, elms[ 1 ] );
SetConstructorFromTable(L, LoopByCayleyTable);
return L;
end );
#############################################################################
##
#O SpecifyElmNamePrefix( C, name )
##
## Sets the elmNamePrefix property on the family of a Represntative of
## collection <C>. For quasigroups, loops, and possibly related structures
## this changes the prefix with which the elements of that family are printed.
InstallMethod( SpecifyElmNamePrefix, "for collection and string",
[ IsCollection, IsString ],
function( Q, name )
local F;
F := FamilyObj( Representative( Q ) );
F!.elmNamePrefix := name;
end);
#############################################################################
##
#O BindElmNames( M )
##
## For each element e of the magma <M>, binds the identifier named String(e)
## to e.
InstallMethod( BindElmNames, "for a magma",
[ IsMagma ],
function( M )
local e, nm;
for e in Elements(M) do
nm := String(e);
BindGlobal(nm, e);
MakeReadWriteGlobal(nm);
od;
return;
end);
#############################################################################
##
#O ConstructorFromTable( M )
##
## Given a magma <M>, returns a function which will create a domain of the
## same structure as M from from an operation table. This implementation of
## the method is to backfill the constructors for library domains that do
## not set the attribute directly at construction time. New domains that wish
## to use facilities like CanonicalCopy or Opposite should call
## SetConstructorFromTable at creation time.
InstallMethod( ConstructorFromTable, "for other magmas",
[ IsMagma ],
function ( M )
# Go in reverse order of refinement of structure
if IsGroup(M) then
return GroupByMultiplicationTable;
elif IsMagmaWithInverses(M) then
return MagmaWithInversesByMultiplicationTable;
elif IsMonoid(M) then
return MonoidByMultiplicationTable;
elif IsMagmaWithOne(M) then
return MagmaWithOneByMultiplicationTable;
elif IsSemigroup(M) then
return SemigroupByMultiplicationTable;
fi;
return MagmaByMultiplicationTable;
end);
#############################################################################
##
#O CanonicalCopy( Q )
##
## Returns a canonical copy of <Q>, that is, an isomorphic object <O> with
## canonical multiplication table and no parent set. Note that this is
## guaranteed to be a new object, not satisfying IsIdenticalObj with any
## previously existing structure. THEREFORE:
## (PROG) Properties and attributes are lost!
InstallMethod( CanonicalCopy, "for magma",
[ IsMagma ],
M -> ConstructorFromTable(M)(MultiplicationTable(M))
);
#############################################################################
## CREATING QUASIGROUPS AND LOOPS FROM A FILE
## -------------------------------------------------------------------------
#############################################################################
##
#F LOOPS_ReadCayleyTableFromFile( filename, replace_by_spaces )
##
## Auxiliary function. Reads the content of <filename> and tries to
## interpret the data as a multiplication table, according to the rules
## summarized below. If successful, it returns the multiplication table as
## an n by n array.
## ALGORITHM:
## 1) the content of the file is read into one string
## 2) all end-of-lines and all characters in the string <replace_by_spaces>
## are replaced by spaces
## 3) the string is split into chunks, where chunk = maximal substring
## without spaces
## 4) the number n of distinct chunks is found. If the number of chunks
## is not n^2, error is announced and function terminates
## 5) a numerical value 1 .. n is assigned to each chunk, depending on
## its position among the distinct chunks
## 6) multiplication table is constructed and returned
InstallGlobalFunction( LOOPS_ReadCayleyTableFromFile,
function( filename, replace_by_spaces)
local input, s, i, chunks, started, starting_pos, z, j, distinct_chunks, c, n, T;
if not ( IsString( filename) and IsString( replace_by_spaces ) ) then
Error( "LOOPS: <1> must be a file name, and <2> must be a string." );
fi;
input := InputTextFile( filename );
if input = fail then Error( "LOOPS: <1> is not a valid file name." ); fi;
s := ReadAll( input );
# removing end-of-lines, etc.
for i in [1..Length( s )] do
if (s[ i ] = '\n') or (s[ i ] in replace_by_spaces) then
s[ i ] := ' ';
fi;
od;
s[ Length( s ) + 1 ] := ' '; #to make sure that string ends with space
#parsing string into chunks separated by spaces
chunks := [];
started := false; starting_pos := 0;
for i in [1..Length( s )] do
if not started then
if not s[ i ] = ' ' then
started := true;
starting_pos := i;
fi;
else
if s[ i ] = ' ' then #end of chunk
z := "";
for j in [ starting_pos..i-1 ] do
z[ j - starting_pos + 1 ] := s[ j ];
od;
Add( chunks, z );
started := false;
fi;
fi;
od;
distinct_chunks := [];
for c in chunks do if not c in distinct_chunks then
Add( distinct_chunks, c );
fi; od;
n := Length( distinct_chunks );
if not Length( chunks ) = n^2 then
Error( "LOOPS: The data in the file cannot be arranged into a square table." );
fi;
T := List( [1..n], i -> 0*[1..n] );
for i in [1..n] do for j in [1..n] do
T[ i ][ j ] := Position( distinct_chunks, chunks[ (i-1)*n + j ] );
od; od;
return T;
end);
#############################################################################
##
#O QuasigroupFromFile( filename, replace] )
##
## Calls LOOPS_ReadCayleyTableFromFile( filename, replace ) in order to return
## the quasigroup with multiplication table in file <filename>.
InstallMethod( QuasigroupFromFile, "for string and string",
[ IsString, IsString ],
function( filename, replace )
return QuasigroupByCayleyTable( LOOPS_ReadCayleyTableFromFile( filename, replace ) );
end );
#############################################################################
##
#O LoopFromFile( filename , replace] )
##
## Calls LOOPS_ReadCayleyTableFromFile( filename, replace ) in order to return
## the loop with multiplication table in file <filename>.
InstallMethod( LoopFromFile, "for string and string",
[ IsString, IsString ],
function( filename, replace )
return LoopByCayleyTable( LOOPS_ReadCayleyTableFromFile( filename, replace ) );
end );
#############################################################################
## CREATING QUASIGROUPS AND LOOPS BY SECTIONS AND FOLDERS
## -------------------------------------------------------------------------
#############################################################################
##
#O CayleyTableByPerms( perms, [X] )
##
## Given a set <perms> of n permutations of an n-element set <X> of natural
## numbers, returns an n by n Cayley table ct such that
## ct[i][j] = X[j]^perms[i].
##
## Note that the argument <X> is optional, and if omitted, the function will
## assume that at most one permutation of <perms> is the identity
## permutation, and that all other permutations of <perms>
## move all points of <X>.
InstallGlobalFunction( CayleyTableByPerms,
function( perms, rest... )
local n, pts, max;
n := Length( perms );
if n=1 then
return [ [ 1 ] ];
fi;
# one of perms[ 1 ], perms[ 2 ] must move all points
if Length(rest) > 0 then
pts := rest[1];
else
pts := MovedPoints( perms[ 2 ] );
if pts = [] then
pts := MovedPoints( perms[ 1 ] );
fi;
fi;
if Length(pts) <> n then
Error("perms for cayley table of size ", n, " cannot act on ",
Length(pts), " points.");
fi;
max := Maximum( pts );
if max = n then
# Common case, we are permuting [1..n]
return List( perms, x -> ListPerm(x, n));
fi;
# Otherwise we permute the whole interval [1..max] and then keep only those coordinates corresponding to pts
return List( perms, p -> Permuted( [1..max], p^(-1) ){ pts } );
end);
#############################################################################
##
#O QuasigroupByLeftSection( sect )
##
## Returns the quasigroup whose left section is the list of permutations
## <sect>.
InstallMethod( QuasigroupByLeftSection,
"for a set of left translation maps",
[ IsPermCollection ],
function( sect )
return QuasigroupByCayleyTable( CayleyTableByPerms( sect ) );
end);
#############################################################################
##
#O LoopByLeftSection( sect )
##
## Returns the loop whose left section is the list of permutations <sect>.
## Since the order of translations in <sect> is determined by their
## image of the neutral element 1, we disregard the order.
InstallMethod( LoopByLeftSection,
"for a set of left translation maps",
[ IsPermCollection ],
function( sect )
return LoopByCayleyTable( Set ( CayleyTableByPerms( sect ) ) );
end);
#############################################################################
##
#O QuasigroupByRightSection( sect )
##
## Returns the quasigroup whose right section is the list of permutations
## <sect>.
InstallMethod( QuasigroupByRightSection,
"for a set of left translation maps",
[ IsPermCollection ],
function( sect )
return QuasigroupByCayleyTable( TransposedMat ( CayleyTableByPerms( sect ) ) );
end);
#############################################################################
##
#O LoopByRightSection( sect )
##
## Returns the loop whose right section is the list of permutations <sect>.
## Since the order of translations in <sect> is determined by their
## image of the neutral element 1, we disregard the order.
InstallMethod( LoopByRightSection,
"for a set of left translation maps",
[ IsPermCollection ],
function( sect )
return LoopByCayleyTable( TransposedMat ( Set ( CayleyTableByPerms( sect ) ) ) );
end);
#############################################################################
##
#O LOOPS_CayleyTableByRightFolder( G, H, T )
##
## Auxiliary operation.
##
## A right folder is a triple (G,H,T) such that G is a group, H is
## a subgroup of G, and T is a right transversal to H in G.
##
## Returns the multiplication table on {Hx: x in G} by Ht*Hs = H(ts).
##
## The multiplication table is a quasigroup if and only if
## T is a right transversal to every conjugate H^g in G.
InstallGlobalFunction( LOOPS_CayleyTableByRightFolder,
function( G, H, T )
local act, nT, actT, i, p, ct;
# act = action of G on right cosest G/H
act := ActionHomomorphism( G, RightCosets( G, H ), OnRight );
nT := Length( T );
# actT = permutations on G/H induced by elements of T
actT := [1..nT];
for i in [1..nT] do
actT[ i ] := T[ i ]^act;
od;
# the order of right cosets determined by T might not agree with the default order of right cosets ...
p := PermList( List( [1..nT], i -> 1^actT[ i ] ) );
ct := List( [1..nT], i -> ListPerm( p * actT[ i ] * p^(-1) ) );
for i in [1..nT] do
if ct[ i ] = [] then # this can happen since ListPerm( () ) = []
ct[ i ] := [1..nT];
fi;
od;
return TransposedMat( ct );
end);
#############################################################################
##
#O QuasigroupByRightFolder( G, H, T )
##
## See CayleyTableByRightFolder. We do not check if the right folder
## is a quasigroup right folder.
InstallMethod( QuasigroupByRightFolder,
"for a group, a subgroup and right transversal",
[ IsGroup, IsGroup, IsMultiplicativeElementCollection ],
function( G, H, T )
return QuasigroupByCayleyTable( LOOPS_CayleyTableByRightFolder( G, H, T ) );
end);
#############################################################################
##
#O LoopByRightFolder( G, H, T )
##
## See CayleyTableByRigthFolder. We do not check if the right folder
## is a loop right folder.
InstallOtherMethod( LoopByRightFolder,
"for a group, a subgroup and right transversal",
[ IsGroup, IsGroup, IsMultiplicativeElementCollection ],
function( G, H, T )
return LoopByCayleyTable( LOOPS_CayleyTableByRightFolder( G, H, T ) );
end);
#############################################################################
## CONVERSIONS
## -------------------------------------------------------------------------
#############################################################################
##
#O IntoQuasigroup( M )
##
## Given a magma, returns the corresponding quasigroup, if possible.
InstallMethod( IntoQuasigroup, "for magma",
[ IsMagma ],
function( M )
local ct;
if IsQuasigroup( M ) then # leave quasigroups and loops intact
return M;
fi;
# magma, not necessarily a quasigroup
ct := MultiplicationTable( Elements( M ) );
if IsQuasigroupTable( ct ) then
return QuasigroupByCayleyTable( ct );
fi;
return fail;
end);
#############################################################################
##
#O PrincipalLoopIsotope( Q, f, g )
##
## Let Q be a quasigroup and f, g elements of Q.
## Define new operation on Q by x+y = R^{-1}(g)(x) * L^{-1}(f)(y).
## Then (Q,+) is a loop with neutral element f*g.
## We return isomorphic copy of (Q,+) via the isomorphism (1,f*g).
InstallMethod( PrincipalLoopIsotope,
"for quasigroup and two quasigroup elements",
[ IsQuasigroup, IsQuasigroupElement, IsQuasigroupElement ],
function( Q, f, g )
local n, L, R, i, j, ct, p;
if not (f in Q and g in Q) then
Error("LOOPS: <2> and <3> must be elements of quasigroup <1>.");
fi;
# constructing new multiplication
n := Size( Q );
ct := List( [1..n], i -> [1..n] );
L := Inverse( LeftTranslation( Q, f ) ); # inverse of left translation by f
R := Inverse( RightTranslation( Q, g ) ); # inverse or right translation by g
for i in [1..n] do for j in [1..n] do
ct[ i ][ j ] := CayleyTable( Q )[ i^R ][ j^L ];
od; od;
# the neutral element of ct is now f*g. We apply isomorphism (1, f*g).
p := Position(Q, f*g);
if p>1 then
p := (1, p); # note that p is its own inverse
ct := List([1..n], i-> List([1..n], j -> ( ct[ i^p ][ j^p ] )^p ) );
fi;
return LoopByCayleyTable( ct );
end);
#############################################################################
##
#O IntoLoop( M )
##
## Given a magma, returns the corresponding loop, if possible.
InstallMethod( IntoLoop, "for magma",
[ IsMagma ],
function( M )
local e, p, ct;
if IsLoop( M ) then # loops are left intact
return M;
fi;
# magma, not necessarily a loop
M := IntoQuasigroup( M );
if M = fail then
return fail;
fi;
# quasigroup, not necesarily a loop
e := MultiplicativeNeutralElement( M );
if e = fail then # no neutral element, use principal isotope
return PrincipalLoopIsotope( M, Elements( M )[ 1 ], Elements( M )[ 1 ] );
fi;
# quasigroup with neutral element, i.e., a loop
p := Position( M, e );
if p>1 then
p := (1,p); # note that p is its own inverse
ct := List([1..Size(M)], i-> List([1..Size(M)], j ->
( CayleyTable( M )[ i^p ][ j^p ] )^p
) );
else
ct := CayleyTable( M );
fi;
return LoopByCayleyTable( ct );
end );
#############################################################################
##
#O IntoGroup( M )
##
## Given a magma <M>, returns the corresponding group, if possible.
InstallOtherMethod( IntoGroup, "for magma",
[ IsMagma ],
function( M )
if IsGroup( M ) then # groups are left intact
return M;
fi;
# magma, not necessarily a group
M := IntoLoop( M );
if M=fail or (not IsAssociative( M ) ) then
return fail;
fi;
# group
return RightMultiplicationGroup( M );
end);
#############################################################################
## PRODUCTS OF QUASIGROUPS AND LOOPS
## -------------------------------------------------------------------------
#############################################################################
##
#F DirectProduct( Q1, Q2, ..., Qn )
##
## Returns the direct product of quasigroups <Q1>, <Q2>, ... , <Qn>.
## The quasigroups can be declared as quasigroups, loops or groups.
# The following is necessary due to implementation of DirectProduct for
# groups in GAP. The idea is as follows:
# We want to calculate direct product of quasigroups, loops and groups.
# If only groups are on the list, standard GAP DirectProduct will take care
# of it. If there are also some quasigroups or loops on the list (but nothing
# that is not a quasigroup), we must take care of it.
# However, we do not know if such a list will be processed with
# DirectProductOp( <IsList>, <IsGroup> ), or
# DirectProductOp( <IsList>, <IsQuasigroup> ),
# since this depends on which algebra is listed first.
# Call the item in the second argument of DirectProductOp the "distinguished"
# item. To produce the correct result whichever of the two above cases for
# DirectProductOp ends up being called, we add a method in the first case
# which repeats the product call with the first non-group it encounters (if
# any) as the distinguished item. Further, the method for the
# latter case must itself repeat the call with a similar reordering if it
# finds anyitem that is not a quasigroup.
InstallMethod( DirectProductOp, "for DirectProduct( <IsList>, <IsGroup> )",
[ IsList, IsGroup],
function( list, first )
local L, p;
# Check the arguments.
if IsEmpty( list ) then
Error( "LOOPS: <1> must be nonempty." );
elif Length(list) = 1 then
return list[1];
fi;
for p in [1..Length(list)] do
if not IsGroup(list[p]) then
return DirectProductOp(Permuted(list, (1,p)), list[p]);
fi;
od;
# OK, everything is a group, so let the rest of GAP do the work.
TryNextMethod();
end);
InstallGlobalFunction(ProductTableOfCanonicalCayleyTables,
function(tablist)
local i, nL, nM, TL, TM, T, j, k, s;
TL := tablist[1];
for s in [2..Length(tablist)] do
TM := tablist[ s ];
nL := Length( TL); nM := Length( TM );
T := List( [1..nL*nM], j->[] );
# not efficient, but it does the job
for i in [1..nM] do for j in [1..nM] do for k in [1..nL] do
Append( T[ (i-1)*nL + k ], TL[ k ] + nL*(TM[i][j]-1) );
od; od; od;
TL := T;
od;
return TL;
end);
InstallOtherMethod( DirectProductOp, "for DirectProduct( <IsList>, <IsQuasigroup> )",
[ IsList, IsQuasigroup ],
function( list, dummy )
local group_list, quasigroup_list, group_product, are_all_loops, n, i, T;
# check the arguments
if IsEmpty( list ) then
Error( "LOOPS: <1> must be nonempty." );
elif Length(list) = 1 then
return list(1);
fi;
group_list := [];
quasigroup_list := list{[1]};
are_all_loops := true;
for i in [2..Length(list)] do
if IsGroup(list[i]) then
Add(group_list, list[i]);
elif IsQuasigroup(list[i]) then
Add(quasigroup_list, list[i]);
if are_all_loops and not IsLoop(list[i]) then
are_all_loops := false;
fi;
else # Oops, something that's neither a group or quasigroup
return DirectProductOp(Permuted(list,(1.i)), list[i]);
fi;
od;
# only groups, quasigroups and loops are on the list, with at least one
# non-group; moreover, we have partitioned the list into groups and
# non-groups and checked whether all quasigroups are really loops.
if not IsEmpty( group_list ) then # some groups are on the list
group_product := DirectProductOp( group_list, group_list[ 1 ] );
Add( quasigroup_list, IntoLoop( group_product ) );
fi;
# now only quasigroups and loops are on the list, with at least 2 of them
n := Length( quasigroup_list );
# We willl not use recursion; start by making all Cayley tables canonical
Apply(quasigroup_list,
Q -> CanonicalCayleyTableOfLeftQuasigroupTable( CayleyTable( Q ) ) );
T := ProductTableOfCanonicalCayleyTables( quasigroup_list );
if are_all_loops then
return LoopByCayleyTable( T );
fi;
return QuasigroupByCayleyTable( T );
end );
#############################################################################
## OPPOSITE QUASIGROUPS AND LOOPS
## --------------------------------------------------------------------------
#############################################################################
##
#O OppositeQuasigroup( Q )
##
## Identical to Opposite, except forces its return to be a quasigroup if
## possible
InstallGlobalFunction( OppositeQuasigroup,
Q -> IntoQuasigroup( Opposite( Q ) ) );
#############################################################################
##
#O OppositeLoop( Q )
##
## Identical to Opposite, except forces its return to be a loop if possible
InstallGlobalFunction( OppositeLoop, L -> IntoLoop( Opposite( L ) ) );
#############################################################################
##
#A Opposite( M )
##
## Returns the magma opposite to the magma <M>, with as much structure
## as can be preserved.
InstallMethod( Opposite, "for magma",
[ IsMagma and HasMultiplicationTable],
M -> ConstructorFromTable(M)( TransposedMat( MultiplicationTable( M ) ) )
);
#############################################################################
## DISPLAYING QUASIGROUPS AND LOOPS
## -------------------------------------------------------------------------
InstallMethod( ViewObj, "for quasigroup",
[ IsQuasigroup ],
function( Q )
Print( "<quasigroup of order ", Size( Q ), ">" );
end );
## dangerous for large quasigroups
InstallMethod( PrintObj, "for quasigroup",
[ IsQuasigroup ],
function( Q )
if HasCayleyTable( Q ) then
Print( "<quasigroup with multiplication table\n" );
PrintArray( CayleyTable( Q ) );
else
Print( "<quasigroup with elements\n" );
Print( Elements( Q ) );
fi;
Print( ">\n" );
end );
InstallMethod( ViewObj, "for loop",
[ IsLoop ],
function( L )
local PrintMe;
PrintMe := function( name, L )
Print( "<", name, " loop of order ", Size( L ), ">");
end;
if HasIsAssociative( L ) and IsAssociative( L ) then PrintMe( "associative", L );
elif HasIsExtraLoop( L ) and IsExtraLoop( L ) then PrintMe( "extra", L );
elif HasIsMoufangLoop( L ) and IsMoufangLoop( L ) then PrintMe( "Moufang", L );
elif HasIsCLoop( L ) and IsCLoop( L ) then PrintMe( "C", L );
elif HasIsLeftBruckLoop( L ) and IsLeftBruckLoop( L ) then PrintMe( "left Bruck", L );
elif HasIsRightBruckLoop( L ) and IsRightBruckLoop( L ) then PrintMe( "right Bruck", L );
elif HasIsLeftBolLoop( L ) and IsLeftBolLoop( L ) then PrintMe( "left Bol", L );
elif HasIsRightBolLoop( L ) and IsRightBolLoop( L ) then PrintMe( "right Bol", L );
elif HasIsAutomorphicLoop( L ) and IsAutomorphicLoop( L ) then PrintMe( "automorphic", L );
elif HasIsLeftAutomorphicLoop( L ) and IsLeftAutomorphicLoop( L ) then PrintMe( "left automorphic", L );
elif HasIsRightAutomorphicLoop( L ) and IsRightAutomorphicLoop( L ) then PrintMe( "right automorphic", L );
elif HasIsLCLoop( L ) and IsLCLoop( L ) then PrintMe( "LC", L );
elif HasIsRCLoop( L ) and IsRCLoop( L ) then PrintMe( "RC", L );
elif HasIsLeftAlternative( L ) and IsLeftAlternative( L ) then
if HasIsRightAlternative( L ) and IsRightAlternative( L ) then
PrintMe("alternative", L );
else
PrintMe("left alternative", L );
fi;
elif HasIsRightAlternative( L ) and IsRightAlternative( L ) then PrintMe( "right alternative", L );
elif HasIsCommutative( L ) and IsCommutative( L ) then PrintMe( "commutative", L );
elif HasIsFlexible( L ) and IsFlexible( L ) then PrintMe( "flexible", L);
else
# MORE ??
Print( "<loop of order ", Size( L ), ">" );
fi;
end );
## dangerous for large loops
InstallMethod( PrintObj, "for loop",
[ IsLoop ],
function( L )
if HasCayleyTable( L ) then
Print( "<loop with multiplication table\n" );
PrintArray( CayleyTable( L ) );
else
Print( "<loop with elements\n" );
Print( Elements( L ) );
fi;
Print( ">\n" );
end );