diff --git a/gap/quasigroups.gd b/gap/quasigroups.gd index e11cc6f..e9683c1 100644 --- a/gap/quasigroups.gd +++ b/gap/quasigroups.gd @@ -12,8 +12,40 @@ ## GAP CATEGORIES AND REPRESENTATIONS ## ------------------------------------------------------------------------- +## Categories convenient for defining quasigroups + +## element which is an admissible argument for the right argument of / +DeclareCategory( "IsRightQuotientElement", IsExtLElement); +DeclareCategoryCollections("IsRightQuotientElement"); +DeclareCategoryCollections("IsRightQuotientElementCollection"); + +## Every element with an inverse can form right quotients +## (in fact, in some sense it might be enough to have just a left inverse, +## but there doesn't seem to be any benefit to delving to that level of +## detail at this point.) +InstallTrueMethod(IsRightQuotientElement, IsMultiplicativeElementWithInverse); + +## Now what we would like to do is re-declare +## DeclareOperation( "/", [IsExtRElement, IsRightQuotientElement] ); +## but we can't since "/" is in the kernel, so we will have to content +## ourselves with InstallOtherMethod() calls on /. (I am not actually sure what +## the practical upshot of that is, i.e. if it has any shortcomings as compared +## to if we could declare "/" more generally.) + +## Element which is admissible for the left argument of LeftQuotient() +DeclareCategory( "IsLeftQuotientElement", IsExtRElement); +DeclareCategoryCollections("IsLeftQuotientElement"); +DeclareCategoryCollections("IsLeftQuotientElementCollection"); + +## Every element with an inverse can form left quotients +InstallTrueMethod(IsLeftQuotientElement, IsMultiplicativeElementWithInverse); + +## Again, ideally (in some sense) we'd like to redeclare +## DeclareOperation("LeftQuotient", [IsLeftQuotientElement,IsExtLElement]); + ## element of a quasigroup -DeclareCategory( "IsQuasigroupElement", IsMultiplicativeElement ); +DeclareCategory( "IsQuasigroupElement", + IsLeftQuotientElement and IsRightQuotientElement ); DeclareRepresentation( "IsQuasigroupElmRep", IsPositionalObjectRep and IsMultiplicativeElement, [1] ); @@ -23,23 +55,39 @@ DeclareCategory( "IsLoopElement", DeclareRepresentation( "IsLoopElmRep", IsPositionalObjectRep and IsMultiplicativeElementWithInverse, [1] ); +## Right quasigroup +DeclareCategory("IsRightQuasigroup", + IsMagma and IsRightQuotientElementCollection); + +## Left quasigroup +DeclareCategory("IsLeftQuasigroup", + IsMagma and IsLeftQuotientElementCollection); + + ## latin (auxiliary category for GAP to tell apart IsMagma and IsQuasigroup) -DeclareCategory( "IsLatin", IsObject ); +DeclareSynonym( "IsLatin", + IsRightQuotientElementCollection and + IsLeftQuotientElementCollection ); ## quasigroup DeclareCategory( "IsQuasigroup", IsMagma and IsLatin ); ## loop -DeclareCategory( "IsLoop", IsQuasigroup and IsMultiplicativeElementWithInverseCollection); +DeclareCategory( "IsLoop", IsQuasigroup and IsMagmaWithOne and + IsMultiplicativeElementWithInverseCollection); ############################################################################# ## TESTING MULTIPLICATION TABLES ## ------------------------------------------------------------------------- -DeclareOperation( "IsQuasigroupTable", [ IsMatrix ] ); +DeclareProperty( "IsLeftQuasigroupTable", [ IsMatrix ]); +DeclareProperty( "IsRightQuasigroupTable", [ IsMatrix ]); +DeclareSynonym( "IsQuasigroupTable", + IsLeftQuasigroupTable and IsRightQuasigroupTable ); DeclareSynonym( "IsQuasigroupCayleyTable", IsQuasigroupTable ); -DeclareOperation( "IsLoopTable", [ IsMatrix ] ); +DeclareProperty( "IsLoopTable", [ IsMatrix ] ); DeclareSynonym( "IsLoopCayleyTable", IsLoopTable ); +DeclareGlobalFunction("CanonicalCayleyTableOfLeftQuasigroupTable"); DeclareOperation( "CanonicalCayleyTable", [ IsMatrix ] ); DeclareOperation( "NormalizedQuasigroupTable", [ IsMatrix ] ); @@ -52,7 +100,7 @@ DeclareOperation( "QuasigroupByCayleyTable", [ IsMatrix ] ); DeclareOperation( "LoopByCayleyTable", [ IsMatrix ] ); DeclareOperation( "SetQuasigroupElmName", [ IsQuasigroup, IsString ] ); DeclareSynonym( "SetLoopElmName", SetQuasigroupElmName ); -DeclareOperation( "CanonicalCopy", [ IsQuasigroup ] ); +DeclareOperation( "CanonicalCopy", [ IsMagma ] ); ############################################################################# ## CREATING QUASIGROUPS AND LOOPS FROM A FILE diff --git a/gap/quasigroups.gi b/gap/quasigroups.gi index 5a7627e..a5fce77 100644 --- a/gap/quasigroups.gi +++ b/gap/quasigroups.gi @@ -14,12 +14,12 @@ ############################################################################# ## -#O IsQuasigroupTable( ls ) +#O IsLeftQuasigroupTable( ls ) ## -## Returns true if is an n by n latin square with n distinct -## integral entries. +## Returns true if is an n by n matrix with n distinct +## integral entries, each occurring exactly once in each row -InstallMethod( IsQuasigroupTable, "for matrix", +InstallMethod( IsLeftQuasigroupTable, "for matrix", [ IsMatrix ], function( ls ) local first_row; @@ -27,14 +27,21 @@ function( ls ) 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; - # checking columns - ls := TransposedMat( ls ); - 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 IsRighttQuasigroupTable( ls ) +## +## Returns true if is an n by n matrix with n distinct +## integral entries, each occurring exactly once in each column + +InstallMethod( IsRighttQuasigroupTable, "for matrix", + [ IsMatrix ], + mat -> IsLeftQuasigroupTable(TransposedMat(mat)) +); + ############################################################################# ## #O IsLoopTable( ls ) @@ -51,6 +58,36 @@ function( ls ) 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 ) @@ -71,13 +108,15 @@ function( ls ) 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 ); +end ############################################################################# ## @@ -94,7 +133,7 @@ function( ls ) Error( "LOOPS: <1> must be a latin square." ); fi; # renaming the entries to be 1, ..., n - T := CanonicalCayleyTable( ls ); + 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 ) ); @@ -136,7 +175,7 @@ function( ct ) Error( "LOOPS: <1> must be a latin square." ); fi; # Making sure that entries are 1, ..., n - ct := CanonicalCayleyTable( ct ); + ct := CanonicalCayleyTableOfLeftQuasigroupTable( ct ); # constructing the family F := NewFamily( "QuasigroupByCayleyTableFam", IsQuasigroupElement ); # installing data for the family @@ -173,7 +212,7 @@ function( ct ) fi; # Making sure that the entries are 1, ..., n. # The table will remain normalized. - ct := CanonicalCayleyTable( ct ); + ct := CanonicalCayleyTableOfLeftQuasigroupTable( ct ); # constructing the family F := NewFamily( "LoopByCayleyTableFam", IsLoopElement ); # installing the data for the family @@ -695,27 +734,30 @@ function( list, dummy ) return quasigroup_list[ 1 ]; fi; # at least 2 quasigroups and loops; we will not use recursion - # making all Cayley tables cannonical + # making all Cayley tables canonical for s in [1..n] do - quasigroup_list[ s ] := QuasigroupByCayleyTable( CanonicalCayleyTable( CayleyTable( quasigroup_list[ s ] ) ) ); + quasigroup_list[ s ] := + CanonicalCayleyTableOfLeftQuasigroupTable( + CayleyTable( quasigroup_list[ s ] ) ); od; + # At this point the entries in quasigroup_list are really just tables for s in [2..n] do - nL := Size( quasigroup_list[ 1 ] ); - nM := Size( quasigroup_list[ s ] ); - TL := CayleyTable( quasigroup_list[ 1 ] ); - TM := CayleyTable( quasigroup_list[ s ] ); + nL := Length( quasigroup_list[ 1 ] ); + nM := Length( quasigroup_list[ s ] ); + TL := quasigroup_list[ 1 ]; + TM := quasigroup_list[ s ]; 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; - quasigroup_list[ 1 ] := QuasigroupByCayleyTable( T ); + quasigroup_list[ 1 ] := T; od; if are_all_loops then - return IntoLoop( quasigroup_list[1] ); + return LoopByCayleyTable( quasigroup_list[1] ); fi; - return quasigroup_list[ 1 ]; + return QuasigroupByCayleyTable( quasigroup_list[1] ); end ); #############################################################################