Compare commits

..

No commits in common. "master" and "Loops3.4.0" have entirely different histories.

6 changed files with 261 additions and 439 deletions

View File

@ -1,8 +1,8 @@
SetPackageInfo( rec( SetPackageInfo( rec(
PackageName := "loops", PackageName := "loops",
Subtitle := "Computing with quasigroups and loops in GAP", Subtitle := "Computing with quasigroups and loops in GAP",
Version := "3.4.1", Version := "3.4.0",
Date := "29/10/2017", Date := "27/10/2017",
ArchiveURL := "http://www.math.du.edu/loops/loops-3.4.0", ArchiveURL := "http://www.math.du.edu/loops/loops-3.4.0",
ArchiveFormats := "-win.zip .tar.gz", ArchiveFormats := "-win.zip .tar.gz",

View File

@ -13,7 +13,7 @@
## ------------------------------------------------------------------------- ## -------------------------------------------------------------------------
DeclareProperty( "IsAssociative", IsLoop ); DeclareProperty( "IsAssociative", IsLoop );
#DeclareProperty( "IsCommutative", IsQuasigroup ); # Already covered by GAP DeclareProperty( "IsCommutative", IsQuasigroup );
DeclareProperty( "IsPowerAssociative", IsQuasigroup ); DeclareProperty( "IsPowerAssociative", IsQuasigroup );
DeclareProperty( "IsDiassociative", IsQuasigroup ); DeclareProperty( "IsDiassociative", IsQuasigroup );

View File

@ -38,8 +38,8 @@ InstallTrueMethod( IsExtraLoop, IsAssociative and IsLoop );
## ##
## Returns true if <Q> is commutative. ## Returns true if <Q> is commutative.
InstallMethod( IsCommutative, "for quasigroup", InstallOtherMethod( IsCommutative, "for quasigroup",
[ IsQuasigroup ], 20, # Need to beat GAP's library methods [ IsQuasigroup ],
function( Q ) function( Q )
return LeftSection( Q ) = RightSection( Q ); return LeftSection( Q ) = RightSection( Q );
end ); end );

View File

@ -12,34 +12,34 @@
## DISPLAYING AND COMPARING ELEMENTS ## DISPLAYING AND COMPARING ELEMENTS
## ------------------------------------------------------------------------- ## -------------------------------------------------------------------------
InstallMethod( PrintObj, "for a default quasigroup element", InstallMethod( PrintObj, "for a quasigroup element",
[ IsQuasigroupElmRep ], [ IsQuasigroupElement ],
function( obj ) function( obj )
local F; local F;
F := FamilyObj( obj ); F := FamilyObj( obj );
Print( F!.elmNamePrefix, obj![ 1 ] ); Print( F!.names, obj![ 1 ] );
end ); end );
InstallMethod( PrintObj, "for a loop element", InstallMethod( PrintObj, "for a loop element",
[ IsLoopElmRep ], [ IsLoopElement ],
function( obj ) function( obj )
local F; local F;
F := FamilyObj( obj ); F := FamilyObj( obj );
Print( F!.elmNamePrefix, obj![ 1 ] ); Print( F!.names, obj![ 1 ] );
end ); end );
InstallMethod( \=, "for two elements of a quasigroup", InstallMethod( \=, "for two elements of a quasigroup",
IsIdenticalObj, IsIdenticalObj,
[ IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement ],
function( x, y ) function( x, y )
return x![ 1 ] = y![ 1 ]; return FamilyObj( x ) = FamilyObj( y ) and x![ 1 ] = y![ 1 ];
end ); end );
InstallMethod( \<, "for two elements of a quasigroup", InstallMethod( \<, "for two elements of a quasigroup",
IsIdenticalObj, IsIdenticalObj,
[ IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement ],
function( x, y ) function( x, y )
return x![ 1 ] < y![ 1 ]; return FamilyObj( x ) = FamilyObj( y ) and x![ 1 ] < y![ 1 ];
end ); end );
InstallMethod( \., "for quasigroup and positive integer", InstallMethod( \., "for quasigroup and positive integer",
@ -57,7 +57,7 @@ end );
## i.e., a*b*c=(a*b)*c. Powers use binary decomposition. ## i.e., a*b*c=(a*b)*c. Powers use binary decomposition.
InstallMethod( \*, "for two quasigroup elements", InstallMethod( \*, "for two quasigroup elements",
IsIdenticalObj, IsIdenticalObj,
[ IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement ],
function( x, y ) function( x, y )
local F; local F;
F := FamilyObj( x ); F := FamilyObj( x );
@ -65,13 +65,13 @@ function( x, y )
end ); end );
InstallOtherMethod( \*, "for a QuasigroupElement and a list", InstallOtherMethod( \*, "for a QuasigroupElement and a list",
[ IsQuasigroupElmRep , IsList ], [ IsQuasigroupElement , IsList ],
function( x, ly ) function( x, ly )
return List( ly, y -> x*y ); return List( ly, y -> x*y );
end ); end );
InstallOtherMethod( \*, "for a list and a QuasigroupElement", InstallOtherMethod( \*, "for a list and a QuasigroupElement",
[ IsList, IsQuasigroupElmRep ], [ IsList, IsQuasigroupElement ],
function( lx, y ) function( lx, y )
return List( lx, x -> x*y ); return List( lx, x -> x*y );
end ); end );
@ -83,7 +83,7 @@ end );
## z=x/y means zy=x ## z=x/y means zy=x
InstallMethod( RightDivision, "for two quasigroup elements", InstallMethod( RightDivision, "for two quasigroup elements",
IsIdenticalObj, IsIdenticalObj,
[ IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement ],
function( x, y ) function( x, y )
local F, ycol; local F, ycol;
F := FamilyObj( x ); F := FamilyObj( x );
@ -93,7 +93,7 @@ end );
InstallOtherMethod( RightDivision, InstallOtherMethod( RightDivision,
"for a list and a quasigroup element", "for a list and a quasigroup element",
[ IsList, IsQuasigroupElmRep ], [ IsList, IsQuasigroupElement ],
0, 0,
function( lx, y ) function( lx, y )
return List( lx, x -> RightDivision(x, y) ); return List( lx, x -> RightDivision(x, y) );
@ -101,7 +101,7 @@ end );
InstallOtherMethod( RightDivision, InstallOtherMethod( RightDivision,
"for a quasigroup element and a list", "for a quasigroup element and a list",
[ IsQuasigroupElmRep, IsList ], [ IsQuasigroupElement, IsList ],
0, 0,
function( x, ly ) function( x, ly )
return List( ly, y -> RightDivision(x, y) ); return List( ly, y -> RightDivision(x, y) );
@ -110,7 +110,7 @@ end );
InstallOtherMethod( \/, InstallOtherMethod( \/,
"for two elements of a quasigroup", "for two elements of a quasigroup",
IsIdenticalObj, IsIdenticalObj,
[ IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement ],
0, 0,
function( x, y ) function( x, y )
return RightDivision( x, y ); return RightDivision( x, y );
@ -118,7 +118,7 @@ end );
InstallOtherMethod( \/, InstallOtherMethod( \/,
"for a list and a quasigroup element", "for a list and a quasigroup element",
[ IsList, IsQuasigroupElmRep ], [ IsList, IsQuasigroupElement ],
0, 0,
function( lx, y ) function( lx, y )
return List( lx, x -> RightDivision(x, y) ); return List( lx, x -> RightDivision(x, y) );
@ -126,7 +126,7 @@ end );
InstallOtherMethod( \/, InstallOtherMethod( \/,
"for a quasigroup element and a list", "for a quasigroup element and a list",
[ IsQuasigroupElmRep, IsList ], [ IsQuasigroupElement, IsList ],
0, 0,
function( x, ly ) function( x, ly )
return List( ly, y -> RightDivision(x, y) ); return List( ly, y -> RightDivision(x, y) );
@ -135,7 +135,7 @@ end );
## z = x\y means xz=y ## z = x\y means xz=y
InstallMethod( LeftDivision, "for two quasigroup elements", InstallMethod( LeftDivision, "for two quasigroup elements",
IsIdenticalObj, IsIdenticalObj,
[ IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement ],
function( x, y ) function( x, y )
local F; local F;
F := FamilyObj( x ); F := FamilyObj( x );
@ -144,7 +144,7 @@ end );
InstallOtherMethod( LeftDivision, InstallOtherMethod( LeftDivision,
"for a list and a quasigroup element", "for a list and a quasigroup element",
[ IsList, IsQuasigroupElmRep ], [ IsList, IsQuasigroupElement ],
0, 0,
function( lx, y ) function( lx, y )
return List( lx, x -> LeftDivision(x, y) ); return List( lx, x -> LeftDivision(x, y) );
@ -152,7 +152,7 @@ end );
InstallOtherMethod( LeftDivision, InstallOtherMethod( LeftDivision,
"for a quasigroup element and a list", "for a quasigroup element and a list",
[ IsQuasigroupElmRep, IsList ], [ IsQuasigroupElement, IsList ],
0, 0,
function( x, ly ) function( x, ly )
return List( ly, y -> LeftDivision(x, y) ); return List( ly, y -> LeftDivision(x, y) );
@ -215,7 +215,7 @@ end );
## ------------------------------------------------------------------------- ## -------------------------------------------------------------------------
InstallMethod( \^, "for a quasigroup element and a permutation", InstallMethod( \^, "for a quasigroup element and a permutation",
[ IsQuasigroupElmRep, IsPerm ], [ IsQuasigroupElement, IsPerm ],
function( x, p ) function( x, p )
local F; local F;
F := FamilyObj( x ); F := FamilyObj( x );
@ -223,7 +223,7 @@ function( x, p )
end ); end );
InstallMethod( OneOp, "for loop elements", InstallMethod( OneOp, "for loop elements",
[ IsLoopElmRep ], [ IsLoopElement ],
function( x ) function( x )
local F; local F;
F := FamilyObj( x ); F := FamilyObj( x );
@ -237,7 +237,7 @@ end );
## If <x> is a loop element, returns the left inverse of <x> ## If <x> is a loop element, returns the left inverse of <x>
InstallMethod( LeftInverse, "for loop elements", InstallMethod( LeftInverse, "for loop elements",
[ IsLoopElmRep ], [ IsLoopElement ],
x -> RightDivision( One( x ), x ) x -> RightDivision( One( x ), x )
); );
@ -248,12 +248,12 @@ InstallMethod( LeftInverse, "for loop elements",
## If <x> is a loop element, returns the left inverse of <x> ## If <x> is a loop element, returns the left inverse of <x>
InstallMethod( RightInverse, "for loop elements", InstallMethod( RightInverse, "for loop elements",
[ IsLoopElmRep ], [ IsLoopElement ],
x -> LeftDivision( x, One( x ) ) x -> LeftDivision( x, One( x ) )
); );
InstallMethod( InverseOp, "for loop elements", InstallMethod( InverseOp, "for loop elements",
[ IsLoopElmRep ], [ IsLoopElement ],
function( x ) function( x )
local y; local y;
y := RightInverse( x ); y := RightInverse( x );
@ -274,7 +274,7 @@ end );
## (xy)z = (x(yz))u. ## (xy)z = (x(yz))u.
InstallMethod( Associator, "for three quasigroup elements", InstallMethod( Associator, "for three quasigroup elements",
[ IsQuasigroupElmRep, IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement, IsQuasigroupElement ],
function( x, y, z ) function( x, y, z )
return LeftDivision( x*(y*z), (x*y)*z ); return LeftDivision( x*(y*z), (x*y)*z );
end); end);
@ -288,7 +288,7 @@ end);
## (xy) = (yx)u. ## (xy) = (yx)u.
InstallMethod( Commutator, "for two quasigroup elements", InstallMethod( Commutator, "for two quasigroup elements",
[ IsQuasigroupElmRep, IsQuasigroupElmRep ], [ IsQuasigroupElement, IsQuasigroupElement ],
function( x, y ) function( x, y )
return LeftDivision( y*x, x*y ); return LeftDivision( y*x, x*y );
end); end);

View File

@ -1,173 +1,104 @@
############################################################################# #############################################################################
## ##
#W quasigroups.gd Representing, creating and displaying quasigroups [loops] #W quasigroups.gd Representing, creating and displaying quasigroups [loops]
## ##
#H @(#)$Id: quasigroups.gd, v 3.4.0 2017/10/17 gap Exp $ #H @(#)$Id: quasigroups.gd, v 3.4.0 2017/10/17 gap Exp $
## ##
#Y Copyright (C) 2004, G. P. Nagy (University of Szeged, Hungary), #Y Copyright (C) 2004, G. P. Nagy (University of Szeged, Hungary),
#Y P. Vojtechovsky (University of Denver, USA) #Y P. Vojtechovsky (University of Denver, USA)
## ##
############################################################################# #############################################################################
## GAP CATEGORIES AND REPRESENTATIONS ## GAP CATEGORIES AND REPRESENTATIONS
## ------------------------------------------------------------------------- ## -------------------------------------------------------------------------
## Categories convenient for defining quasigroups ## element of a quasigroup
DeclareCategory( "IsQuasigroupElement", IsMultiplicativeElement );
## element which is an admissible argument for the right argument of / DeclareRepresentation( "IsQuasigroupElmRep",
DeclareCategory( "IsRightQuotientElement", IsExtLElement); IsPositionalObjectRep and IsMultiplicativeElement, [1] );
DeclareCategoryCollections("IsRightQuotientElement");
DeclareCategoryCollections("IsRightQuotientElementCollection"); ## element of a loop
DeclareCategory( "IsLoopElement",
## Every associative element with an inverse can form right quotients IsQuasigroupElement and IsMultiplicativeElementWithInverse );
## (in fact, in some sense it might be enough to have just a left inverse, DeclareRepresentation( "IsLoopElmRep",
## but there doesn't seem to be any benefit to delving to that level of IsPositionalObjectRep and IsMultiplicativeElementWithInverse, [1] );
## detail at this point.)
## By noting this property, we can create a RightQuasigroup from, e.g., group ## latin (auxiliary category for GAP to tell apart IsMagma and IsQuasigroup)
## elements DeclareCategory( "IsLatinMagma", IsObject );
InstallTrueMethod(IsRightQuotientElement,
IsMultiplicativeElementWithInverse and IsAssociativeElement); ## quasigroup
DeclareCategory( "IsQuasigroup", IsMagma and IsLatinMagma );
## Now what we would like to do is re-declare
## DeclareOperation( "/", [IsExtRElement, IsRightQuotientElement] ); ## loop
## but we can't since "/" is in the kernel, so we will have to content DeclareCategory( "IsLoop", IsQuasigroup and IsMultiplicativeElementWithInverseCollection);
## 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.) ## TESTING MULTIPLICATION TABLES
## -------------------------------------------------------------------------
## Element which is admissible for the left argument of LeftQuotient()
DeclareCategory( "IsLeftQuotientElement", IsExtRElement); DeclareOperation( "IsQuasigroupTable", [ IsMatrix ] );
DeclareCategoryCollections("IsLeftQuotientElement"); DeclareSynonym( "IsQuasigroupCayleyTable", IsQuasigroupTable );
DeclareCategoryCollections("IsLeftQuotientElementCollection"); DeclareOperation( "IsLoopTable", [ IsMatrix ] );
DeclareSynonym( "IsLoopCayleyTable", IsLoopTable );
## Every associative element with an inverse can form left quotients DeclareOperation( "CanonicalCayleyTable", [ IsMatrix ] );
InstallTrueMethod(IsLeftQuotientElement, DeclareOperation( "NormalizedQuasigroupTable", [ IsMatrix ] );
IsMultiplicativeElementWithInverse and IsAssociativeElement);
#############################################################################
## Again, ideally (in some sense) we'd like to redeclare ## CREATING QUASIGROUPS AND LOOPS MANUALLY
## DeclareOperation("LeftQuotient", [IsLeftQuotientElement,IsExtLElement]); ## -------------------------------------------------------------------------
## element of a quasigroup DeclareAttribute( "CayleyTable", IsQuasigroup );
DeclareSynonym( "IsQuasigroupElement", DeclareOperation( "QuasigroupByCayleyTable", [ IsMatrix ] );
IsMultiplicativeElement and DeclareOperation( "LoopByCayleyTable", [ IsMatrix ] );
IsLeftQuotientElement and IsRightQuotientElement ); DeclareOperation( "SetQuasigroupElmName", [ IsQuasigroup, IsString ] );
DeclareRepresentation( "IsQuasigroupElmRep", DeclareSynonym( "SetLoopElmName", SetQuasigroupElmName );
IsPositionalObjectRep and IsQuasigroupElement, [1] ); DeclareOperation( "CanonicalCopy", [ IsQuasigroup ] );
## element of a loop #############################################################################
DeclareSynonym( "IsLoopElement", ## CREATING QUASIGROUPS AND LOOPS FROM A FILE
IsQuasigroupElement and IsMultiplicativeElementWithInverse ); ## -------------------------------------------------------------------------
DeclareRepresentation( "IsLoopElmRep",
IsQuasigroupElmRep and IsMultiplicativeElementWithInverse, [1] ); DeclareOperation( "QuasigroupFromFile", [ IsString, IsString ] );
DeclareOperation( "LoopFromFile", [ IsString, IsString ] );
## Right quasigroup
DeclareCategory("IsRightQuasigroup", #############################################################################
IsMagma and IsRightQuotientElementCollection); ## CREATING QUASIGROUPS AND LOOPS BY SECTIONS
## -------------------------------------------------------------------------
## Although the following assertion is mathematically correct, unfortunately
## it interferes with method selection for standard group operations DeclareOperation( "CayleyTableByPerms", [ IsPermCollection ] );
## in GAP. As an example, if it is uncommented, it will no longer be possible DeclareOperation( "QuasigroupByLeftSection", [ IsPermCollection ] );
## to construct a CyclicGroup; trying to do so eventually dies in DeclareOperation( "LoopByLeftSection", [ IsPermCollection ] );
## GeneratorsOfRightQuasigroup. Those errors could conceivably be corrected by DeclareOperation( "QuasigroupByRightSection", [ IsPermCollection ] );
## delving further into GAP's method selection mechanism and adjusting the DeclareOperation( "LoopByRightSection", [ IsPermCollection ] );
## declarations of various quasigroup operations, but it doesn't seem worth DeclareOperation( "QuasigroupByRightFolder", [ IsGroup, IsGroup, IsMultiplicativeElementCollection ] );
## the effort as there is unlikely to be much call to consider a group as a DeclareOperation( "LoopByRightFolder", [ IsGroup, IsGroup, IsMultiplicativeElementCollection ] );
## quasigroup. If it is desirable to do so in a particular case, it should be
## possible to use the elements of the group to form a quasigroup, since they #############################################################################
## will all satisfy IsRightQuotientElement by a TrueMethod installed above. ## CONVERSIONS
## -------------------------------------------------------------------------
## InstallTrueMethod(IsRightQuasigroup, IsGroup);
DeclareOperation( "IntoQuasigroup", [ IsMagma ] );
## Left quasigroup DeclareOperation( "PrincipalLoopIsotope",
DeclareCategory("IsLeftQuasigroup", [ IsQuasigroup, IsQuasigroupElement, IsQuasigroupElement ] );
IsMagma and IsLeftQuotientElementCollection); DeclareOperation( "IntoLoop", [ IsMagma ] );
DeclareOperation( "IntoGroup", [ IsMagma ] );
## We forego the following for the reasons outlined above for right quasigroups.
#############################################################################
## InstallTrueMethod(IsLeftQuasigroup, IsGroup); ## PRODUCTS OF QUASIGROUPS AND LOOPS
## --------------------------------------------------------------------------
## quasigroup
DeclareSynonym( "IsQuasigroup", IsRightQuasigroup and IsLeftQuasigroup ); #DirectProduct already declared for groups.
## loop #############################################################################
DeclareSynonym( "IsLoop", IsQuasigroup and IsMagmaWithOne and ## OPPOSITE QUASIGROUPS AND LOOPS
IsMultiplicativeElementWithInverseCollection); ## --------------------------------------------------------------------------
############################################################################# DeclareOperation( "OppositeQuasigroup", [ IsQuasigroup ] );
## TESTING MULTIPLICATION TABLES DeclareOperation( "OppositeLoop", [ IsLoop ] );
## ------------------------------------------------------------------------- DeclareAttribute( "Opposite", IsQuasigroup );
DeclareProperty( "IsLeftQuasigroupTable", IsMatrix ); #############################################################################
DeclareProperty( "IsRightQuasigroupTable", IsMatrix ); ## AUXILIARY
DeclareSynonym( "IsQuasigroupTable", ## --------------------------------------------------------------------------
IsLeftQuasigroupTable and IsRightQuasigroupTable ); DeclareGlobalFunction( "LOOPS_ReadCayleyTableFromFile" );
DeclareSynonym( "IsQuasigroupCayleyTable", IsQuasigroupTable ); DeclareGlobalFunction( "LOOPS_CayleyTableByRightFolder" );
DeclareProperty( "IsLoopTable", IsMatrix );
DeclareSynonym( "IsLoopCayleyTable", IsLoopTable );
DeclareGlobalFunction("CanonicalCayleyTableOfLeftQuasigroupTable");
DeclareOperation( "CanonicalCayleyTable", [ IsMatrix ] );
DeclareOperation( "NormalizedQuasigroupTable", [ IsMatrix ] );
#############################################################################
## CREATING QUASIGROUPS AND LOOPS MANUALLY
## -------------------------------------------------------------------------
DeclareAttribute( "CayleyTable", IsMagma );
DeclareOperation( "QuasigroupByCayleyTable", [ IsMatrix ] );
DeclareOperation( "LoopByCayleyTable", [ IsMatrix ] );
DeclareOperation( "SpecifyElmNamePrefix", [ IsCollection, IsString ] );
DeclareSynonym( "SetQuasigroupElmName", SpecifyElmNamePrefix );
DeclareSynonym( "SetLoopElmName", SpecifyElmNamePrefix );
DeclareOperation( "BindElmNames", [ IsMagma ] );
DeclareAttribute( "ConstructorFromTable", IsMagma );
DeclareOperation( "CanonicalCopy", [ IsMagma ] );
#############################################################################
## CREATING QUASIGROUPS AND LOOPS FROM A FILE
## -------------------------------------------------------------------------
DeclareOperation( "QuasigroupFromFile", [ IsString, IsString ] );
DeclareOperation( "LoopFromFile", [ IsString, IsString ] );
#############################################################################
## CREATING QUASIGROUPS AND LOOPS BY SECTIONS
## -------------------------------------------------------------------------
DeclareGlobalFunction("CayleyTableByPerms");
DeclareOperation( "QuasigroupByLeftSection", [ IsPermCollection ] );
DeclareOperation( "LoopByLeftSection", [ IsPermCollection ] );
DeclareOperation( "QuasigroupByRightSection", [ IsPermCollection ] );
DeclareOperation( "LoopByRightSection", [ IsPermCollection ] );
DeclareOperation( "QuasigroupByRightFolder", [ IsGroup, IsGroup, IsMultiplicativeElementCollection ] );
DeclareOperation( "LoopByRightFolder", [ IsGroup, IsGroup, IsMultiplicativeElementCollection ] );
#############################################################################
## CONVERSIONS
## -------------------------------------------------------------------------
DeclareOperation( "IntoQuasigroup", [ IsMagma ] );
DeclareOperation( "PrincipalLoopIsotope",
[ IsQuasigroup, IsQuasigroupElement, IsQuasigroupElement ] );
DeclareOperation( "IntoLoop", [ IsMagma ] );
DeclareOperation( "IntoGroup", [ IsMagma ] );
#############################################################################
## PRODUCTS OF QUASIGROUPS AND LOOPS
## --------------------------------------------------------------------------
DeclareGlobalFunction("ProductTableOfCanonicalCayleyTables");
#DirectProduct already declared for groups.
#############################################################################
## OPPOSITE QUASIGROUPS AND LOOPS
## --------------------------------------------------------------------------
DeclareGlobalFunction( "OppositeQuasigroup");
DeclareGlobalFunction( "OppositeLoop");
DeclareAttribute( "Opposite", IsMagma );
#############################################################################
## AUXILIARY
## --------------------------------------------------------------------------
DeclareGlobalFunction( "LOOPS_ReadCayleyTableFromFile" );
DeclareGlobalFunction( "LOOPS_CayleyTableByRightFolder" );

View File

@ -14,12 +14,12 @@
############################################################################# #############################################################################
## ##
#O IsLeftQuasigroupTable( ls ) #O IsQuasigroupTable( ls )
## ##
## Returns true if <ls> is an n by n matrix with n distinct ## Returns true if <ls> is an n by n latin square with n distinct
## integral entries, each occurring exactly once in each row ## integral entries.
InstallMethod( IsLeftQuasigroupTable, "for matrix", InstallMethod( IsQuasigroupTable, "for matrix",
[ IsMatrix ], [ IsMatrix ],
function( ls ) function( ls )
local first_row; local first_row;
@ -27,21 +27,14 @@ function( ls )
first_row := Set( ls[ 1 ] ); first_row := Set( ls[ 1 ] );
if not Length( first_row ) = Length( ls[ 1 ] ) then return false; fi; 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; 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; return true;
end ); 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 ) #O IsLoopTable( ls )
@ -58,36 +51,6 @@ function( ls )
and ls[ 1 ] = List( [1..Length(ls)], i -> ls[ i ][ 1 ] ); and ls[ 1 ] = List( [1..Length(ls)], i -> ls[ i ][ 1 ] );
end ); 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 ) #O CanonicalCayleyTable( ls )
@ -108,15 +71,13 @@ function( ls )
for i in [1..n] do for j in [1..n] do for i in [1..n] do for j in [1..n] do
AddSet( entries, ls[ i ][ j ] ); AddSet( entries, ls[ i ][ j ] );
od; od; od; od;
if entries = [1..n] then return List(ls, l -> ShallowCopy(l));
fi;
# renaming the entries and making a mutable copy, too # renaming the entries and making a mutable copy, too
T := List( [1..n], i -> [1..n] ); T := List( [1..n], i -> [1..n] );
for i in [1..n] do for j in [1..n] do for i in [1..n] do for j in [1..n] do
T[ i ][ j ] := Position( entries, ls[ i ][ j ] ); T[ i ][ j ] := Position( entries, ls[ i ][ j ] );
od; od; od; od;
return T; return T;
end); end );
############################################################################# #############################################################################
## ##
@ -133,7 +94,7 @@ function( ls )
Error( "LOOPS: <1> must be a latin square." ); Error( "LOOPS: <1> must be a latin square." );
fi; fi;
# renaming the entries to be 1, ..., n # renaming the entries to be 1, ..., n
T := CanonicalCayleyTableOfLeftQuasigroupTable( ls ); T := CanonicalCayleyTable( ls );
# permuting the columns so that the first row reads 1, ..., n # permuting the columns so that the first row reads 1, ..., n
perm := PermList( T[ 1 ] ); perm := PermList( T[ 1 ] );
T := List( T, row -> Permuted( row, perm ) ); T := List( T, row -> Permuted( row, perm ) );
@ -149,12 +110,10 @@ end );
## ##
#A CayleyTable( Q ) #A CayleyTable( Q )
## ##
## Returns the Cayley table of the magma <Q>. This is just like ## Returns the Cayley table of the quasigroup <Q>
## 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", InstallMethod( CayleyTable, "for quasigroup",
[ IsMagma ], [ IsQuasigroup ],
function( Q ) function( Q )
local elms, parent_elms; local elms, parent_elms;
elms := Elements( Q ); elms := Elements( Q );
@ -177,7 +136,7 @@ function( ct )
Error( "LOOPS: <1> must be a latin square." ); Error( "LOOPS: <1> must be a latin square." );
fi; fi;
# Making sure that entries are 1, ..., n # Making sure that entries are 1, ..., n
ct := CanonicalCayleyTableOfLeftQuasigroupTable( ct ); ct := CanonicalCayleyTable( ct );
# constructing the family # constructing the family
F := NewFamily( "QuasigroupByCayleyTableFam", IsQuasigroupElement ); F := NewFamily( "QuasigroupByCayleyTableFam", IsQuasigroupElement );
# installing data for the family # installing data for the family
@ -187,7 +146,7 @@ function( ct )
NewType( F, IsQuasigroupElement and IsQuasigroupElmRep), [ i ] ) ) ); NewType( F, IsQuasigroupElement and IsQuasigroupElmRep), [ i ] ) ) );
F!.set := elms; F!.set := elms;
F!.cayleyTable := ct; F!.cayleyTable := ct;
F!.elmNamePrefix := "q"; F!.names := "q";
# creating the quasigroup # creating the quasigroup
Q := Objectify( NewType( FamilyObj( elms ), Q := Objectify( NewType( FamilyObj( elms ),
IsQuasigroup and IsAttributeStoringRep ), rec() ); IsQuasigroup and IsAttributeStoringRep ), rec() );
@ -196,7 +155,6 @@ function( ct )
SetAsSSortedList( Q, elms ); SetAsSSortedList( Q, elms );
SetParent( Q, Q ); SetParent( Q, Q );
SetCayleyTable( Q, ct ); SetCayleyTable( Q, ct );
SetConstructorFromTable(Q, QuasigroupByCayleyTable);
return Q; return Q;
end ); end );
@ -215,7 +173,7 @@ function( ct )
fi; fi;
# Making sure that the entries are 1, ..., n. # Making sure that the entries are 1, ..., n.
# The table will remain normalized. # The table will remain normalized.
ct := CanonicalCayleyTableOfLeftQuasigroupTable( ct ); ct := CanonicalCayleyTable( ct );
# constructing the family # constructing the family
F := NewFamily( "LoopByCayleyTableFam", IsLoopElement ); F := NewFamily( "LoopByCayleyTableFam", IsLoopElement );
# installing the data for the family # installing the data for the family
@ -225,7 +183,7 @@ function( ct )
NewType( F, IsLoopElement and IsLoopElmRep), [ i ] ) ) ); NewType( F, IsLoopElement and IsLoopElmRep), [ i ] ) ) );
F!.set := elms; F!.set := elms;
F!.cayleyTable := ct; F!.cayleyTable := ct;
F!.elmNamePrefix := "l"; F!.names := "l";
# creating the loop # creating the loop
L := Objectify( NewType( FamilyObj( elms ), L := Objectify( NewType( FamilyObj( elms ),
IsLoop and IsAttributeStoringRep ), rec() ); IsLoop and IsAttributeStoringRep ), rec() );
@ -235,88 +193,39 @@ function( ct )
SetParent( L, L ); SetParent( L, L );
SetCayleyTable( L, ct ); SetCayleyTable( L, ct );
SetOne( L, elms[ 1 ] ); SetOne( L, elms[ 1 ] );
SetConstructorFromTable(L, LoopByCayleyTable);
return L; return L;
end ); end );
############################################################################# #############################################################################
## ##
#O SpecifyElmNamePrefix( C, name ) #O SetQuasigroupElmName( Q, name )
## ##
## Sets the elmNamePrefix property on the family of a Representative of ## Changes the name of elements of quasigroup or loop <Q> to <name>
## 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", InstallMethod( SetQuasigroupElmName, "for quasigroup and string",
[ IsCollection, IsString ], [ IsQuasigroup, IsString ],
function( Q, name ) function( Q, name )
local F; local F;
F := FamilyObj( Representative( Q ) ); F := FamilyObj( Elements( Q )[ 1 ] );
F!.elmNamePrefix := name; F!.names := 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); 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 ) #O CanonicalCopy( Q )
## ##
## Returns a canonical copy of <Q>, that is, an isomorphic object <O> with ## Returns a canonical copy of <Q>, that is, an isomorphic object <O> with
## canonical multiplication table and no parent set. Note that this is ## canonical multiplication table and Parent( <O> ) = <O>.
## guaranteed to be a new object, not satisfying IsIdenticalObj with any
## previously existing structure. THEREFORE:
## (PROG) Properties and attributes are lost! ## (PROG) Properties and attributes are lost!
InstallMethod( CanonicalCopy, "for magma", InstallMethod( CanonicalCopy, "for quasigroup or loop",
[ IsMagma ], [ IsQuasigroup ],
M -> ConstructorFromTable(M)(MultiplicationTable(M)) function( Q )
); if IsLoop( Q ) then
return LoopByCayleyTable( CayleyTable( Q ) );
fi;
return QuasigroupByCayleyTable( CayleyTable( Q ) );
end);
############################################################################# #############################################################################
@ -438,44 +347,30 @@ end );
############################################################################# #############################################################################
## ##
#O CayleyTableByPerms( perms, [X] ) #O CayleyTableByPerms( perms )
## ##
## Given a set <perms> of n permutations of an n-element set <X> of natural ## Given a set <perms> of n permutations of an n-element set X, returns
## numbers, returns an n by n Cayley table ct such that ## n by n Cayley table ct such that ct[i][j] = X[j]^perms[i].
## ct[i][j] = X[j]^perms[i]. ## The operation is safe only if at most one permutation of <perms> is
## ## the identity permutation, and all other permutations of <perms>
## Note that the argument <X> is optional, and if omitted, the function will ## move all points of X.
## 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, InstallMethod( CayleyTableByPerms,
function( perms, rest... ) "for a list of permutations",
[ IsPermCollection ],
function( perms )
local n, pts, max; local n, pts, max;
n := Length( perms ); n := Length( perms );
if n=1 then if n=1 then
return [ [ 1 ] ]; return [ [ 1 ] ];
fi; fi;
# one of perms[ 1 ], perms[ 2 ] must move all points # one of perms[ 1 ], perms[ 2 ] must move all points
if Length(rest) > 0 then pts := MovedPoints( perms[ 2 ] );
pts := rest[1]; if pts = [] then
else
pts := MovedPoints( perms[ 2 ] );
if pts = [] then
pts := MovedPoints( perms[ 1 ] ); 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; fi;
max := Maximum( pts ); max := Maximum( pts );
if max = n then # we permute the whole interval [1..max] and then keep only those coordinates corresponding to pts
# 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 } ); return List( perms, p -> Permuted( [1..max], p^(-1) ){ pts } );
end); end);
@ -732,104 +627,95 @@ end);
# groups in GAP. The idea is as follows: # groups in GAP. The idea is as follows:
# We want to calculate direct product of quasigroups, loops and groups. # We want to calculate direct product of quasigroups, loops and groups.
# If only groups are on the list, standard GAP DirectProduct will take care # 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 # of it. If there are also some quasigroups or loops on the list,
# that is not a quasigroup), we must take care of it. # we must take care of it.
# However, we do not know if such a list will be processed with # However, we do not know if such a list will be processed with
# DirectProductOp( <IsList>, <IsGroup> ), or # DirectProductOp( <IsList>, <IsGroup> ), or
# DirectProductOp( <IsList>, <IsQuasigroup> ), # DirectProductOp( <IsList>, <IsQuasigroup> ),
# since this depends on which algebra is listed first. # since this depends on which algebra is listed first.
# Call the item in the second argument of DirectProductOp the "distinguished" # We therefore take care of both situations.
# 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> )", InstallOtherMethod( DirectProductOp, "for DirectProduct( <IsList>, <IsGroup> )",
[ IsList, IsGroup], [ IsList, IsGroup],
function( list, first ) function( list, first )
local L, p; local L, p;
# Check the arguments. # Check the arguments.
if IsEmpty( list ) then if IsEmpty( list ) then Error( "LOOPS: <1> must be nonempty." ); fi;
Error( "LOOPS: <1> must be nonempty." ); if not ForAny( list, IsQuasigroup ) then
elif Length(list) = 1 then # there are no quasigroups or loops on the list
return list[1]; TryNextMethod();
fi; fi;
for p in [1..Length(list)] do if ForAny( list, G -> (not IsGroup( G )) and (not IsQuasigroup( G ) ) ) then
if not IsGroup(list[p]) then # there are other objects beside groups, loops and quasigroups on the list
return DirectProductOp(Permuted(list, (1,p)), list[p]); TryNextMethod();
fi;
# all arguments are groups, quasigroups or loops, and there is at least one loop
# making sure that a loop is listed first so that this method is not called again
for L in list do
if not IsGroup( L ) then
p := Position( list, L );
list[ 1 ] := L;
list[ p ] := first;
break;
fi; fi;
od; od;
# OK, everything is a group, so let the rest of GAP do the work.
TryNextMethod();
end);
InstallGlobalFunction(ProductTableOfCanonicalCayleyTables, return DirectProductOp( list, list[ 1 ] );
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); end);
InstallOtherMethod( DirectProductOp, "for DirectProduct( <IsList>, <IsQuasigroup> )", InstallOtherMethod( DirectProductOp, "for DirectProduct( <IsList>, <IsQuasigroup> )",
[ IsList, IsQuasigroup ], [ IsList, IsQuasigroup ],
function( list, dummy ) function( list, dummy )
local group_list, quasigroup_list, group_product, are_all_loops, n, i, T; local group_list, quasigroup_list, group_product, are_all_loops,
n, i, nL, nM, TL, TM, T, j, k, s;
# check the arguments # check the arguments
if IsEmpty( list ) then if IsEmpty( list ) then
Error( "LOOPS: <1> must be nonempty." ); Error( "LOOPS: <1> must be nonempty." );
elif Length(list) = 1 then elif ForAny( list, G -> (not IsGroup( G )) and (not IsQuasigroup( G ) ) ) then
return list[1]; TryNextMethod();
fi; 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 # only groups, quasigroups and loops are on the list, with at least one non-group
# non-group; moreover, we have partitioned the list into groups and group_list := Filtered( list, G -> IsGroup( G ) );
# non-groups and checked whether all quasigroups are really loops. quasigroup_list := Filtered( list, G -> IsQuasigroup( G ) );
if not IsEmpty( group_list ) then # some groups are on the list if not IsEmpty( group_list ) then # some groups are on the list
group_product := DirectProductOp( group_list, group_list[ 1 ] ); group_product := DirectProductOp( group_list, group_list[ 1 ] );
Add( quasigroup_list, IntoLoop( group_product ) ); Add( quasigroup_list, IntoLoop( group_product ) );
fi; fi;
# keeping track of whether all algebras are in fact loops
are_all_loops := ForAll( quasigroup_list, IsLoop );
# now only quasigroups and loops are on the list, with at least 2 of them # now only quasigroups and loops are on the list
n := Length( quasigroup_list ); n := Length( quasigroup_list );
# We will not use recursion; start by making all Cayley tables canonical if n=1 then
Apply(quasigroup_list, return quasigroup_list[ 1 ];
Q -> CanonicalCayleyTableOfLeftQuasigroupTable( CayleyTable( Q ) ) );
T := ProductTableOfCanonicalCayleyTables( quasigroup_list );
if are_all_loops then
return LoopByCayleyTable( T );
fi; fi;
return QuasigroupByCayleyTable( T ); # at least 2 quasigroups and loops; we will not use recursion
# making all Cayley tables cannonical
for s in [1..n] do
quasigroup_list[ s ] := QuasigroupByCayleyTable( CanonicalCayleyTable( CayleyTable( quasigroup_list[ s ] ) ) );
od;
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 ] );
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 );
od;
if are_all_loops then
return IntoLoop( quasigroup_list[1] );
fi;
return quasigroup_list[ 1 ];
end ); end );
############################################################################# #############################################################################
@ -840,36 +726,41 @@ function( list, dummy )
## ##
#O OppositeQuasigroup( Q ) #O OppositeQuasigroup( Q )
## ##
## Identical to Opposite, except forces its return to be a quasigroup if ## Returns the quasigroup opposite to the quasigroup <Q>.
## possible
InstallGlobalFunction( OppositeQuasigroup, InstallMethod( OppositeQuasigroup, "for quasigroup",
Q -> IntoQuasigroup( Opposite( Q ) ) ); [ IsQuasigroup ],
function( Q )
return QuasigroupByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
end );
############################################################################# #############################################################################
## ##
#O OppositeLoop( Q ) #O OppositeLoop( Q )
## ##
## Identical to Opposite, except forces its return to be a loop if possible ## Returns the loop opposite to the loop <Q>.
InstallGlobalFunction( OppositeLoop, L -> IntoLoop( Opposite( L ) ) ); InstallMethod( OppositeLoop, "for loop",
[ IsLoop ],
function( Q )
return LoopByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
end );
############################################################################# #############################################################################
## ##
#A Opposite( M ) #A Opposite( Q )
## ##
## Returns the magma opposite to the magma <M>, with as much structure ## Returns the quasigroup opposite to the quasigroup <Q>. When
## as can be preserved. ## <Q> is a loop, a loop is returned.
InstallMethod( Opposite, "for magma",
[ IsMagma and HasMultiplicationTable],
M -> ConstructorFromTable(M)( TransposedMat( MultiplicationTable( M ) ) )
);
InstallMethod( Opposite, "for quasigroup", InstallMethod( Opposite, "for quasigroup",
[ IsQuasigroup ], # Might not have a multiplication table [ IsQuasigroup ],
M -> ConstructorFromTable(M)( TransposedMat( CayleyTable( M ) ) ) function( Q )
); if IsLoop( Q ) then
return LoopByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
fi;
return QuasigroupByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
end );
############################################################################# #############################################################################
## DISPLAYING QUASIGROUPS AND LOOPS ## DISPLAYING QUASIGROUPS AND LOOPS