############################################################################# ## #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 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 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 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 , 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 , 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 , 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 . 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 . 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 . 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 Representative of ## collection . 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 , 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 , 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 , that is, an isomorphic object 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 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 ## 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 . 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 . 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 of n permutations of an n-element set of natural ## numbers, returns an n by n Cayley table ct such that ## ct[i][j] = X[j]^perms[i]. ## ## Note that the argument is optional, and if omitted, the function will ## assume that at most one permutation of is the identity ## permutation, and that all other permutations of ## move all points of . 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 ## . 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 . ## Since the order of translations in 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 ## . 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 . ## Since the order of translations in 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 , 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 , , ... , . ## 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( , ), or # DirectProductOp( , ), # 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], 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 ], 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 := IsLoop(list[1]); 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 will 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 , 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( "" ); end ); ## dangerous for large quasigroups InstallMethod( PrintObj, "for quasigroup", [ IsQuasigroup ], function( Q ) if HasCayleyTable( Q ) then 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( "" ); fi; end ); ## dangerous for large loops InstallMethod( PrintObj, "for loop", [ IsLoop ], function( L ) if HasCayleyTable( L ) then Print( "\n" ); end );