Compare commits

...

37 Commits

Author SHA1 Message Date
Glen Whitney
d7f394f957 Correct the TrueMethods: elt must be associate and have inverses to have quotients 2018-09-12 08:34:44 -04:00
Glen Whitney
1cf79a02c3 small amendments. 2017-10-30 12:30:15 -04:00
Glen Whitney
03d80d9acb Increase priority of LOOPS IsCommutative. 2017-10-30 12:14:36 -04:00
Glen Whitney
69a866d5db Maybe if we undup the declaration. 2017-10-30 12:11:29 -04:00
Glen Whitney
b3b9e3817b Can IsCommutative be a regular method. 2017-10-30 12:09:24 -04:00
Glen Whitney
aaaf1a04a9 typo in that. 2017-10-30 11:59:07 -04:00
Glen Whitney
8f7f6891ec loop element reps are quasigroup element reps. 2017-10-30 11:58:26 -04:00
Glen Whitney
4c2817b04f define opposites even if just have cayley table. 2017-10-30 11:53:35 -04:00
Glen Whitney
2dcde99549 oops need to check first factor for being loop in DirectProductLoop. 2017-10-30 11:45:14 -04:00
Glen Whitney
143e51b801 oops parens where there should have been brackets. 2017-10-30 11:38:00 -04:00
Glen Whitney
4254b760c9 the quasigroup elm rep should be a quasigroup element 2017-10-30 11:32:44 -04:00
Glen Whitney
8c5670ef3f typo in last 2017-10-30 11:24:56 -04:00
Glen Whitney
2696cd6ff2 Have to cut many other methods down to be more specific to element reps 2017-10-30 11:23:55 -04:00
Glen Whitney
a69dcd05d3 Fix a couple of typos 2017-10-30 01:00:42 -04:00
Glen Whitney
244492a7ed Finish the merge with 3.4.0
Aha, somehow I did not include the PackageInfo.g and the properly merged
gap/quasigroups.gd into the last commit; here they are
2017-10-30 00:35:55 -04:00
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
Glen Whitney
fb39c9e02e Print methods for quasigroup/loop elms jsut for this specific representation 2017-10-29 15:12:01 -04:00
Glen Whitney
71672eeb9c Oops, unused local variable 2017-10-26 21:15:21 -04:00
Glen Whitney
df74177abb typo 2017-10-25 16:28:33 +02:00
Glen Whitney
1493a9c480 factor out parts of direct product computation that can be reused for racks 2017-10-25 16:18:04 +02:00
Glen Whitney
68390c3869 oops lapsed back to python 0-based 2017-10-24 23:12:35 +02:00
Glen Whitney
2521a38635 Generalize CayleyTableByPerms so that it is more useful in rack/quandle context 2017-10-24 16:16:27 +02:00
Glen Whitney
54c503e356 Don't create multiplication tables just for the sake of Opposite 2017-10-20 11:06:49 +02:00
Glen Whitney
6d095fe0d3 Remove stray comment 2017-10-19 03:11:29 +02:00
Glen Whitney
7c3f421a20 delete leftover code 2017-10-19 02:12:38 +02:00
Glen Whitney
1ec279ab0b stupid brackets 2017-10-19 02:11:34 +02:00
Glen Whitney
a4d79ff66e provide facility to bind element names 2017-10-19 02:10:01 +02:00
Glen Whitney
87e7b01333 provide facility to bind element names 2017-10-19 02:08:47 +02:00
Glen Whitney
88533fd7de extend the element naming prefix to more general setting, and so use more elaborate identifier 2017-10-19 01:11:26 +02:00
Glen Whitney
9d8dfdb9e2 Fix more typo. 2017-10-18 22:05:39 +02:00
Glen Whitney
c848f29524 Fix typo. 2017-10-18 22:00:16 +02:00
Glen Whitney
1ac424c524 Lift some functions to magmas in general 2017-10-18 21:36:03 +02:00
Glen Whitney
cf2bc14423 Looks like quasigroup elements have to be multiplicative elements (as that is not implied by ExtL and ExtRElement) 2017-10-17 22:28:41 +02:00
Glen Whitney
66b00d7d34 Close a parenthesis. 2017-10-17 21:59:04 +02:00
Glen Whitney
75fe6fa0e3 Fix typo. 2017-10-17 21:57:31 +02:00
Glen Whitney
e05b2a6deb Properties take just a single filter, I guess. 2017-10-17 21:56:00 +02:00
Glen Whitney
722f25e51f Rudimentary support for left/right quasigroups. 2017-10-17 21:42:47 +02:00
6 changed files with 438 additions and 260 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -12,34 +12,99 @@
## 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 associative 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.)
## By noting this property, we can create a RightQuasigroup from, e.g., group
## elements
InstallTrueMethod(IsRightQuotientElement,
IsMultiplicativeElementWithInverse and IsAssociativeElement);
## 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 associative element with an inverse can form left quotients
InstallTrueMethod(IsLeftQuotientElement,
IsMultiplicativeElementWithInverse and IsAssociativeElement);
## Again, ideally (in some sense) we'd like to redeclare
## DeclareOperation("LeftQuotient", [IsLeftQuotientElement,IsExtLElement]);
## element of a quasigroup
DeclareCategory( "IsQuasigroupElement", IsMultiplicativeElement );
DeclareSynonym( "IsQuasigroupElement",
IsMultiplicativeElement and
IsLeftQuotientElement and IsRightQuotientElement );
DeclareRepresentation( "IsQuasigroupElmRep",
IsPositionalObjectRep and IsMultiplicativeElement, [1] );
IsPositionalObjectRep and IsQuasigroupElement, [1] );
## element of a loop
DeclareCategory( "IsLoopElement",
DeclareSynonym( "IsLoopElement",
IsQuasigroupElement and IsMultiplicativeElementWithInverse );
DeclareRepresentation( "IsLoopElmRep",
IsPositionalObjectRep and IsMultiplicativeElementWithInverse, [1] );
IsQuasigroupElmRep and IsMultiplicativeElementWithInverse, [1] );
## latin (auxiliary category for GAP to tell apart IsMagma and IsQuasigroup)
DeclareCategory( "IsLatinMagma", IsObject );
## Right quasigroup
DeclareCategory("IsRightQuasigroup",
IsMagma and IsRightQuotientElementCollection);
## Although the following assertion is mathematically correct, unfortunately
## it interferes with method selection for standard group operations
## in GAP. As an example, if it is uncommented, it will no longer be possible
## to construct a CyclicGroup; trying to do so eventually dies in
## GeneratorsOfRightQuasigroup. Those errors could conceivably be corrected by
## delving further into GAP's method selection mechanism and adjusting the
## declarations of various quasigroup operations, but it doesn't seem worth
## the effort as there is unlikely to be much call to consider a group as a
## 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.
## InstallTrueMethod(IsRightQuasigroup, IsGroup);
## Left quasigroup
DeclareCategory("IsLeftQuasigroup",
IsMagma and IsLeftQuotientElementCollection);
## We forego the following for the reasons outlined above for right quasigroups.
## InstallTrueMethod(IsLeftQuasigroup, IsGroup);
## quasigroup
DeclareCategory( "IsQuasigroup", IsMagma and IsLatinMagma );
DeclareSynonym( "IsQuasigroup", IsRightQuasigroup and IsLeftQuasigroup );
## loop
DeclareCategory( "IsLoop", IsQuasigroup and IsMultiplicativeElementWithInverseCollection);
DeclareSynonym( "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 ] );
@ -47,12 +112,15 @@ DeclareOperation( "NormalizedQuasigroupTable", [ IsMatrix ] );
## CREATING QUASIGROUPS AND LOOPS MANUALLY
## -------------------------------------------------------------------------
DeclareAttribute( "CayleyTable", IsQuasigroup );
DeclareAttribute( "CayleyTable", IsMagma );
DeclareOperation( "QuasigroupByCayleyTable", [ IsMatrix ] );
DeclareOperation( "LoopByCayleyTable", [ IsMatrix ] );
DeclareOperation( "SetQuasigroupElmName", [ IsQuasigroup, IsString ] );
DeclareSynonym( "SetLoopElmName", SetQuasigroupElmName );
DeclareOperation( "CanonicalCopy", [ IsQuasigroup ] );
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
@ -65,7 +133,7 @@ DeclareOperation( "LoopFromFile", [ IsString, IsString ] );
## CREATING QUASIGROUPS AND LOOPS BY SECTIONS
## -------------------------------------------------------------------------
DeclareOperation( "CayleyTableByPerms", [ IsPermCollection ] );
DeclareGlobalFunction("CayleyTableByPerms");
DeclareOperation( "QuasigroupByLeftSection", [ IsPermCollection ] );
DeclareOperation( "LoopByLeftSection", [ IsPermCollection ] );
DeclareOperation( "QuasigroupByRightSection", [ IsPermCollection ] );
@ -87,15 +155,16 @@ DeclareOperation( "IntoGroup", [ IsMagma ] );
## PRODUCTS OF QUASIGROUPS AND LOOPS
## --------------------------------------------------------------------------
DeclareGlobalFunction("ProductTableOfCanonicalCayleyTables");
#DirectProduct already declared for groups.
#############################################################################
## OPPOSITE QUASIGROUPS AND LOOPS
## --------------------------------------------------------------------------
DeclareOperation( "OppositeQuasigroup", [ IsQuasigroup ] );
DeclareOperation( "OppositeLoop", [ IsLoop ] );
DeclareAttribute( "Opposite", IsQuasigroup );
DeclareGlobalFunction( "OppositeQuasigroup");
DeclareGlobalFunction( "OppositeLoop");
DeclareAttribute( "Opposite", IsMagma );
#############################################################################
## AUXILIARY

View File

@ -14,12 +14,12 @@
#############################################################################
##
#O IsQuasigroupTable( ls )
#O IsLeftQuasigroupTable( ls )
##
## Returns true if <ls> is an n by n latin square with n distinct
## integral entries.
## Returns true if <ls> 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 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 )
@ -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 <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 )
@ -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 ) );
@ -110,10 +149,12 @@ end );
##
#A CayleyTable( Q )
##
## Returns the Cayley table of the quasigroup <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 quasigroup",
[ IsQuasigroup ],
InstallMethod( CayleyTable, "for magma",
[ IsMagma ],
function( Q )
local elms, parent_elms;
elms := Elements( Q );
@ -136,7 +177,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
@ -146,7 +187,7 @@ function( ct )
NewType( F, IsQuasigroupElement and IsQuasigroupElmRep), [ i ] ) ) );
F!.set := elms;
F!.cayleyTable := ct;
F!.names := "q";
F!.elmNamePrefix := "q";
# creating the quasigroup
Q := Objectify( NewType( FamilyObj( elms ),
IsQuasigroup and IsAttributeStoringRep ), rec() );
@ -155,6 +196,7 @@ function( ct )
SetAsSSortedList( Q, elms );
SetParent( Q, Q );
SetCayleyTable( Q, ct );
SetConstructorFromTable(Q, QuasigroupByCayleyTable);
return Q;
end );
@ -173,7 +215,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
@ -183,7 +225,7 @@ function( ct )
NewType( F, IsLoopElement and IsLoopElmRep), [ i ] ) ) );
F!.set := elms;
F!.cayleyTable := ct;
F!.names := "l";
F!.elmNamePrefix := "l";
# creating the loop
L := Objectify( NewType( FamilyObj( elms ),
IsLoop and IsAttributeStoringRep ), rec() );
@ -193,39 +235,88 @@ function( ct )
SetParent( L, L );
SetCayleyTable( L, ct );
SetOne( L, elms[ 1 ] );
SetConstructorFromTable(L, LoopByCayleyTable);
return L;
end );
#############################################################################
##
#O SetQuasigroupElmName( Q, name )
#O SpecifyElmNamePrefix( C, name )
##
## Changes the name of elements of quasigroup or loop <Q> to <name>
## Sets the elmNamePrefix property on the family of a Representative of
## collection <C>. For quasigroups, loops, and possibly related structures
## this changes the prefix with which the elements of that family are printed.
InstallMethod( SetQuasigroupElmName, "for quasigroup and string",
[ IsQuasigroup, IsString ],
InstallMethod( SpecifyElmNamePrefix, "for collection and string",
[ IsCollection, IsString ],
function( Q, name )
local F;
F := FamilyObj( Elements( Q )[ 1 ] );
F!.names := name;
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 Parent( <O> ) = <O>.
## 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 quasigroup or loop",
[ IsQuasigroup ],
function( Q )
if IsLoop( Q ) then
return LoopByCayleyTable( CayleyTable( Q ) );
fi;
return QuasigroupByCayleyTable( CayleyTable( Q ) );
end);
InstallMethod( CanonicalCopy, "for magma",
[ IsMagma ],
M -> ConstructorFromTable(M)(MultiplicationTable(M))
);
#############################################################################
@ -347,30 +438,44 @@ end );
#############################################################################
##
#O CayleyTableByPerms( perms )
#O CayleyTableByPerms( perms, [X] )
##
## Given a set <perms> of n permutations of an n-element set X, returns
## n by n Cayley table ct such that 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>
## move all points of 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>.
InstallMethod( CayleyTableByPerms,
"for a list of permutations",
[ IsPermCollection ],
function( perms )
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
pts := MovedPoints( perms[ 2 ] );
if pts = [] then
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 );
# we permute the whole interval [1..max] and then keep only those coordinates corresponding to 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);
@ -627,95 +732,104 @@ end);
# 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,
# we must take care of it.
# 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.
# We therefore take care of both situations.
# 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.
InstallOtherMethod( DirectProductOp, "for DirectProduct( <IsList>, <IsGroup> )",
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." ); fi;
if not ForAny( list, IsQuasigroup ) then
# there are no quasigroups or loops on the list
TryNextMethod();
if IsEmpty( list ) then
Error( "LOOPS: <1> must be nonempty." );
elif Length(list) = 1 then
return list[1];
fi;
if ForAny( list, G -> (not IsGroup( G )) and (not IsQuasigroup( G ) ) ) then
# there are other objects beside groups, loops and quasigroups on the list
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;
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);
return DirectProductOp( list, list[ 1 ] );
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, nL, nM, TL, TM, T, j, k, s;
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 ForAny( list, G -> (not IsGroup( G )) and (not IsQuasigroup( G ) ) ) then
TryNextMethod();
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
group_list := Filtered( list, G -> IsGroup( G ) );
quasigroup_list := Filtered( list, G -> IsQuasigroup( G ) );
# 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;
# 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
# now only quasigroups and loops are on the list, with at least 2 of them
n := Length( quasigroup_list );
if n=1 then
return quasigroup_list[ 1 ];
fi;
# 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->[] );
# We will not use recursion; start by making all Cayley tables canonical
Apply(quasigroup_list,
Q -> CanonicalCayleyTableOfLeftQuasigroupTable( CayleyTable( Q ) ) );
T := ProductTableOfCanonicalCayleyTables( quasigroup_list );
# 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] );
return LoopByCayleyTable( T );
fi;
return quasigroup_list[ 1 ];
return QuasigroupByCayleyTable( T );
end );
#############################################################################
@ -726,41 +840,36 @@ function( list, dummy )
##
#O OppositeQuasigroup( Q )
##
## Returns the quasigroup opposite to the quasigroup <Q>.
## Identical to Opposite, except forces its return to be a quasigroup if
## possible
InstallMethod( OppositeQuasigroup, "for quasigroup",
[ IsQuasigroup ],
function( Q )
return QuasigroupByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
end );
InstallGlobalFunction( OppositeQuasigroup,
Q -> IntoQuasigroup( Opposite( Q ) ) );
#############################################################################
##
#O OppositeLoop( Q )
##
## Returns the loop opposite to the loop <Q>.
## Identical to Opposite, except forces its return to be a loop if possible
InstallMethod( OppositeLoop, "for loop",
[ IsLoop ],
function( Q )
return LoopByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
end );
InstallGlobalFunction( OppositeLoop, L -> IntoLoop( Opposite( L ) ) );
#############################################################################
##
#A Opposite( Q )
#A Opposite( M )
##
## Returns the quasigroup opposite to the quasigroup <Q>. When
## <Q> is a loop, a loop is returned.
## 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 ) ) )
);
InstallMethod( Opposite, "for quasigroup",
[ IsQuasigroup ],
function( Q )
if IsLoop( Q ) then
return LoopByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
fi;
return QuasigroupByCayleyTable( TransposedMat( CayleyTable( Q ) ) );
end );
[ IsQuasigroup ], # Might not have a multiplication table
M -> ConstructorFromTable(M)( TransposedMat( CayleyTable( M ) ) )
);
#############################################################################
## DISPLAYING QUASIGROUPS AND LOOPS