From b779f018214e8f920118b6af4ead081b63b50387 Mon Sep 17 00:00:00 2001 From: "James D. Mitchell" Date: Tue, 14 Jun 2022 09:59:03 +0100 Subject: [PATCH] semiquo: improve support for quotient semigroups --- gap/attributes/attr.gd | 6 +- gap/attributes/attr.gi | 5 +- gap/congruences/cong.gi | 7 + gap/congruences/conginv.gi | 24 ++++ gap/congruences/congpairs.gi | 19 +++ gap/congruences/congpart.gi | 4 +- gap/libsemigroups/cong.gi | 88 ++++++------- gap/libsemigroups/froidure-pin.gi | 46 +++++-- gap/main/froidure-pin.gi | 12 +- gap/main/setup.gd | 2 +- gap/main/setup.gi | 4 +- gap/semigroups/semigrp.gd | 16 ++- gap/semigroups/semigrp.gi | 23 ++-- gap/semigroups/semiquo.gi | 14 +- src/cong.cpp | 3 + src/froidure-pin.hpp | 52 ++++---- tst/standard/congruences/conginv.tst | 4 +- tst/standard/main/froidure-pin.tst | 4 +- tst/standard/semigroups/semiquo.tst | 187 +++++++++++++++++++++++++++ tst/testinstall.tst | 12 +- 20 files changed, 406 insertions(+), 126 deletions(-) diff --git a/gap/attributes/attr.gd b/gap/attributes/attr.gd index aff4b7b68..17ca76889 100644 --- a/gap/attributes/attr.gd +++ b/gap/attributes/attr.gd @@ -27,8 +27,12 @@ DeclareAttribute("NormalizedPrincipalFactor", IsGreensDClass); DeclareAttribute("MultiplicativeZero", IsSemigroup); DeclareAttribute("LengthOfLongestDClassChain", IsSemigroup); +# We use IsListOrCollection here because some collections of semigroup +# generators (such as elements/congruence classes in a quotient semigroup) do +# not satisfy IsMultiplicativeElementCollection (although the classes +# themselves do satisfy IsMultiplicativeElement). DeclareAttribute("SmallSemigroupGeneratingSet", - IsMultiplicativeElementCollection); + IsListOrCollection); DeclareAttribute("SmallMonoidGeneratingSet", IsMultiplicativeElementWithOneCollection); DeclareAttribute("SmallInverseSemigroupGeneratingSet", diff --git a/gap/attributes/attr.gi b/gap/attributes/attr.gi index 15cddf0a9..858ad85ce 100644 --- a/gap/attributes/attr.gi +++ b/gap/attributes/attr.gi @@ -288,9 +288,8 @@ InstallMethod(NormalizedPrincipalFactor, "for a Green's D-class", # different method for ideals, not yet implemented -InstallMethod(SmallSemigroupGeneratingSet, -"for a multiplicative element collection", -[IsMultiplicativeElementCollection], +InstallMethod(SmallSemigroupGeneratingSet, "for a list or collection", +[IsListOrCollection], function(coll) if Length(coll) < 2 then return coll; diff --git a/gap/congruences/cong.gi b/gap/congruences/cong.gi index 6ff7c005b..c75e22f72 100644 --- a/gap/congruences/cong.gi +++ b/gap/congruences/cong.gi @@ -37,6 +37,13 @@ InstallMethod(CongruenceHandednessString, "for a left congruence", InstallMethod(CongruenceHandednessString, "for a 2-sided congruence", [IsMagmaCongruence and IsSemigroupCongruence], C -> "2-sided"); +# This is required for QuotientSemigroups and their subsemigroups. +InstallImmediateMethod(CanEasilyCompareElements, +IsCongruenceClass and HasEquivalenceClassRelation, 0, +function(C) + return CanUseLibsemigroupsCongruence(EquivalenceClassRelation(C)); +end); + ######################################################################## # Flexible functions for creating congruences ######################################################################## diff --git a/gap/congruences/conginv.gi b/gap/congruences/conginv.gi index c2a0aa2e1..7b30dfff9 100644 --- a/gap/congruences/conginv.gi +++ b/gap/congruences/conginv.gi @@ -561,3 +561,27 @@ function(S) return InverseSemigroupCongruenceByKernelTraceNC(S, ker, traceBlocks); end); + +# TODO(later) this is a completely generic version implementation, surely we +# can do better than this! + +InstallMethod(GeneratingPairsOfMagmaCongruence, +"for inverse semigroup congruence by kernel and trace", +[IsInverseSemigroupCongruenceByKernelTrace], +function(C) + local CC, pairs, class, i, j; + + CC := SemigroupCongruenceByGeneratingPairs(Source(C), []); + for class in EquivalenceRelationPartition(C) do + for i in [1 .. Length(class) - 1] do + for j in [i + 1 .. Length(class)] do + if not [class[i], class[j]] in CC then + pairs := GeneratingPairsOfSemigroupCongruence(CC); + pairs := Concatenation(pairs, [[class[i], class[j]]]); + CC := SemigroupCongruenceByGeneratingPairs(Source(C), pairs); + fi; + od; + od; + od; + return pairs; +end); diff --git a/gap/congruences/congpairs.gi b/gap/congruences/congpairs.gi index 3c7fdcd2a..4142fa33a 100644 --- a/gap/congruences/congpairs.gi +++ b/gap/congruences/congpairs.gi @@ -30,6 +30,25 @@ InstallImmediateMethod(GeneratingPairsOfLeftRightOrTwoSidedCongruence, 0, GeneratingPairsOfRightMagmaCongruence); +# Some types of congruences (such as CongruenceByKernelAndTrace) do not know +# their generating pairs by default, and hence we require the following methods +# in addition to the immediate methods above. + +InstallMethod(GeneratingPairsOfLeftRightOrTwoSidedCongruence, +"for a right semigroup congruence", +[IsRightSemigroupCongruence], +GeneratingPairsOfRightMagmaCongruence); + +InstallMethod(GeneratingPairsOfLeftRightOrTwoSidedCongruence, +"for a left semigroup congruence", +[IsLeftSemigroupCongruence], +GeneratingPairsOfLeftMagmaCongruence); + +InstallMethod(GeneratingPairsOfLeftRightOrTwoSidedCongruence, +"for a semigroup congruence", +[IsSemigroupCongruence], +GeneratingPairsOfMagmaCongruence); + InstallMethod(AsSemigroupCongruenceByGeneratingPairs, "for semigroup congruence", [IsSemigroupCongruence], diff --git a/gap/congruences/congpart.gi b/gap/congruences/congpart.gi index e4c768b8c..fe1b562ee 100644 --- a/gap/congruences/congpart.gi +++ b/gap/congruences/congpart.gi @@ -281,8 +281,8 @@ end); # This is declared only for CanComputeEquivalenceRelationPartition because no # other types of congruence have CongruenceTestMembershipNC implemented. InstallMethod(\in, -"for pair of mult. elt. and left, right, or 2-sided congruence", -[IsMultiplicativeElementCollection, CanComputeEquivalenceRelationPartition], +"for pair of elements and left, right, or 2-sided congruence", +[IsListOrCollection, CanComputeEquivalenceRelationPartition], function(pair, C) local S, string; diff --git a/gap/libsemigroups/cong.gi b/gap/libsemigroups/cong.gi index 2965b33c5..429f61f2c 100644 --- a/gap/libsemigroups/cong.gi +++ b/gap/libsemigroups/cong.gi @@ -10,8 +10,6 @@ ## This file contains the interface to libsemigroups Congruence objects. -# TODO: A method for MeetXSemigroupCongruences - ########################################################################### # Categories + properties + true methods ########################################################################### @@ -33,7 +31,6 @@ InstallImmediateMethod(CanUseLibsemigroupsCongruence, IsLeftRightOrTwoSidedCongruence - and HasGeneratingPairsOfLeftRightOrTwoSidedCongruence and HasRange, 0, C -> CanUseFroidurePin(Range(C)) @@ -43,32 +40,12 @@ InstallImmediateMethod(CanUseLibsemigroupsCongruence, or (HasIsFreeMonoid(Range(C)) and IsFreeMonoid(Range(C)))); -InstallImmediateMethod(CanUseLibsemigroupsCongruence, - IsRMSCongruenceByLinkedTriple, - 0, - ReturnFalse); - -InstallImmediateMethod(CanUseLibsemigroupsCongruence, - IsRZMSCongruenceByLinkedTriple, - 0, - ReturnFalse); - -InstallImmediateMethod(CanUseLibsemigroupsCongruence, - IsSimpleSemigroupCongruence, - 0, - ReturnFalse); - -InstallImmediateMethod(CanUseLibsemigroupsCongruence, - IsInverseSemigroupCongruenceByKernelTrace, - 0, - ReturnFalse); - InstallMethod(CanUseLibsemigroupsCongruence, "for a left, right, or 2-sided congruence that can compute partition", [CanComputeEquivalenceRelationPartition], ReturnFalse); -# TODO(now) remove CanUseLibsemigroupsCongruences? +# TODO(later) remove CanUseLibsemigroupsCongruences? # A semigroup satisfies this property if its congruences should belong to # CanUseLibsemigroupsCongruence. @@ -84,6 +61,8 @@ InstallTrueMethod(CanUseLibsemigroupsCongruences, HasIsFreeSemigroup and IsFreeSemigroup); InstallTrueMethod(CanUseLibsemigroupsCongruences, HasIsFreeMonoid and IsFreeMonoid); +InstallTrueMethod(CanUseLibsemigroupsCongruence, + IsInverseSemigroupCongruenceByKernelTrace); ########################################################################### # Functions/methods that are declared in this file and that use the @@ -164,6 +143,13 @@ function(S) return libsemigroups.Congruence.make_from_froidurepin_pbr; end); +InstallMethod(LibsemigroupsCongruenceConstructor, +"for a quotient semigroup and CanUseLibsemigroupsCongruences", +[IsQuotientSemigroup and CanUseLibsemigroupsCongruences], +function(S) + return libsemigroups.Congruence.make_from_froidurepinbase; +end); + # Get the libsemigroups::Congruence object associated to a GAP object BindGlobal("LibsemigroupsCongruence", @@ -205,6 +191,7 @@ function(C) libsemigroups.Congruence.add_runner(CC, tc); factor := MinimalFactorization; else + # TODO(QUOTIENT): What about if IsQuotientSemigroup(Range(C))? # Shouldn't be possible to reach the next line, and can't currently test it TryNextMethod(); fi; @@ -242,10 +229,10 @@ end); ######################################################################## InstallMethod(CongruenceLessNC, - "for CanUseLibsemigroupsCongruence and two mult. elements", - [CanUseLibsemigroupsCongruence, - IsMultiplicativeElement, - IsMultiplicativeElement], +"for CanUseLibsemigroupsCongruence and two mult. elements", +[CanUseLibsemigroupsCongruence, + IsMultiplicativeElement, + IsMultiplicativeElement], function(C, elm1, elm2) local S, pos1, pos2, lookup, word1, word2, CC; @@ -261,7 +248,8 @@ function(C, elm1, elm2) word2 := MinimalFactorization(S, pos2); fi; elif IsFpSemigroup(S) or (HasIsFreeSemigroup(S) and IsFreeSemigroup(S)) - or IsFpMonoid(S) or (HasIsFreeMonoid(S) and IsFreeMonoid(S)) then + or IsFpMonoid(S) or (HasIsFreeMonoid(S) and IsFreeMonoid(S)) + or IsQuotientSemigroup(S) then word1 := Factorization(S, elm1); word2 := Factorization(S, elm2); else @@ -277,8 +265,10 @@ end); # libsemigroups object directly ########################################################################### -InstallMethod(NrEquivalenceClasses, "for CanUseLibsemigroupsCongruence", -[CanUseLibsemigroupsCongruence], +InstallMethod(NrEquivalenceClasses, +"for CanUseLibsemigroupsCongruence with known generating pairs", +[CanUseLibsemigroupsCongruence and + HasGeneratingPairsOfLeftRightOrTwoSidedCongruence], function(C) local number_of_classes, result; number_of_classes := libsemigroups.Congruence.number_of_classes; @@ -290,8 +280,9 @@ function(C) end); InstallMethod(CongruenceTestMembershipNC, -"for CanUseLibsemigroupsCongruence and two mult. elements", -[CanUseLibsemigroupsCongruence, +"for CanUseLibsemigroupsCongruence with known gen. pairs and 2 mult. elts", +[CanUseLibsemigroupsCongruence and + HasGeneratingPairsOfLeftRightOrTwoSidedCongruence, IsMultiplicativeElement, IsMultiplicativeElement], 100, @@ -321,8 +312,10 @@ function(C, elm1, elm2) return libsemigroups.Congruence.contains(CC, word1 - 1, word2 - 1); end); -InstallMethod(EquivalenceRelationPartition, "for CanUseLibsemigroupsCongruence", -[CanUseLibsemigroupsCongruence], +InstallMethod(EquivalenceRelationPartition, +"for CanUseLibsemigroupsCongruence with known generating pairs", +[CanUseLibsemigroupsCongruence and + HasGeneratingPairsOfLeftRightOrTwoSidedCongruence], function(C) local S, CC, ntc, gens, class, i, j; S := Range(C); @@ -357,10 +350,11 @@ function(class1, class2) local C, word1, word2, CC; C := EquivalenceClassRelation(class1); - if C <> EquivalenceClassRelation(class2) then - return false; - elif not CanUseLibsemigroupsCongruence(C) then + if not CanUseLibsemigroupsCongruence(C) + or not HasGeneratingPairsOfLeftRightOrTwoSidedCongruence(C) then TryNextMethod(); + elif C <> EquivalenceClassRelation(class2) then + return false; fi; word1 := Factorization(Range(C), Representative(class1)); @@ -369,8 +363,10 @@ function(class1, class2) return libsemigroups.Congruence.less(CC, word1 - 1, word2 - 1); end); -InstallMethod(EquivalenceClasses, "for CanUseLibsemigroupsCongruence", -[CanUseLibsemigroupsCongruence], +InstallMethod(EquivalenceClasses, +"for CanUseLibsemigroupsCongruence with known generating pairs", +[CanUseLibsemigroupsCongruence and + HasGeneratingPairsOfLeftRightOrTwoSidedCongruence], function(C) local result, CC, gens, class_index_to_word, rep, i; @@ -396,7 +392,9 @@ end); ########################################################################### InstallMethod(EquivalenceRelationPartitionWithSingletons, -"for CanUseLibsemigroupsCongruence", [CanUseLibsemigroupsCongruence], +"for CanUseLibsemigroupsCongruence with known generating pairs", +[CanUseLibsemigroupsCongruence and + HasGeneratingPairsOfLeftRightOrTwoSidedCongruence], function(C) local part, i, x; if not IsFinite(Range(C)) then @@ -416,8 +414,9 @@ function(C) end); InstallMethod(ImagesElm, -"for CanUseLibsemigroupsCongruence and a multiplicative element", -[CanUseLibsemigroupsCongruence, IsMultiplicativeElement], +"for CanUseLibsemigroupsCongruence with known gen. pairs and a mult. elt.", +[CanUseLibsemigroupsCongruence and + HasGeneratingPairsOfLeftRightOrTwoSidedCongruence, IsMultiplicativeElement], function(cong, elm) local lookup, id, part, pos; @@ -429,7 +428,8 @@ function(cong, elm) elif IsFpSemigroup(Range(cong)) or (HasIsFreeSemigroup(Range(cong)) and IsFreeSemigroup(Range(cong))) or IsFpMonoid(Range(cong)) - or (HasIsFreeSemigroup(Range(cong)) and IsFreeMonoid(Range(cong))) then + or (HasIsFreeSemigroup(Range(cong)) and IsFreeMonoid(Range(cong))) + or IsQuotientSemigroup(Range(cong)) then part := EquivalenceRelationPartition(cong); pos := PositionProperty(part, l -> [elm, l[1]] in cong); if pos = fail then diff --git a/gap/libsemigroups/froidure-pin.gi b/gap/libsemigroups/froidure-pin.gi index 4945ea8c3..214b09564 100644 --- a/gap/libsemigroups/froidure-pin.gi +++ b/gap/libsemigroups/froidure-pin.gi @@ -38,6 +38,10 @@ od; InstallMethod(CanUseLibsemigroupsFroidurePin, "for a semigroup", [IsSemigroup], ReturnFalse); +InstallImmediateMethod(CanUseLibsemigroupsFroidurePin, +IsQuotientSemigroup and HasQuotientSemigroupCongruence, 0, +Q -> CanUseLibsemigroupsCongruence(QuotientSemigroupCongruence(Q))); + ########################################################################### ## Function for getting the correct record from the `libsemigroups` record. ########################################################################### @@ -127,6 +131,9 @@ InstallMethod(FroidurePinMemFnRec, "for an fp semigroup", InstallMethod(FroidurePinMemFnRec, "for an fp monoid", [IsFpMonoid], S -> libsemigroups.FroidurePinBase); +InstallMethod(FroidurePinMemFnRec, "for quotient semigroup", +[IsQuotientSemigroup], S -> libsemigroups.FroidurePinBase); + BindGlobal("_GetElement", function(coll, x) Assert(1, IsMultiplicativeElementCollection(coll)); @@ -175,6 +182,13 @@ function(S) elif IsFpSemigroup(S) or IsFpMonoid(S) then C := LibsemigroupsCongruence(UnderlyingCongruence(S)); return libsemigroups.Congruence.quotient_froidure_pin(C); + elif IsQuotientSemigroup(S) then + C := QuotientSemigroupCongruence(S); + if not HasGeneratingPairsOfMagmaCongruence(C) then + GeneratingPairsOfMagmaCongruence(C); + fi; + C := LibsemigroupsCongruence(C); + return libsemigroups.Congruence.quotient_froidure_pin(C); fi; Unbind(S!.LibsemigroupsFroidurePin); record := FroidurePinMemFnRec(S); @@ -204,7 +218,7 @@ end); InstallMethod(IsFinite, "for a semigroup with CanUseLibsemigroupsFroidurePin", [IsSemigroup and CanUseLibsemigroupsFroidurePin], function(S) - if IsFpSemigroup(S) or IsFpMonoid(S) then + if IsFpSemigroup(S) or IsFpMonoid(S) or IsQuotientSemigroup(S) then TryNextMethod(); fi; return FroidurePinMemFnRec(S).size(LibsemigroupsFroidurePin(S)) < infinity; @@ -220,10 +234,11 @@ function(S) local result, sorted_at, T, i; if not IsFinite(S) then Error("the argument (a semigroup) is not finite"); - elif IsPartialPermSemigroup(S) or IsFpSemigroup(S) or IsFpMonoid(S) then + elif IsPartialPermSemigroup(S) or IsFpSemigroup(S) or IsFpMonoid(S) + or IsQuotientSemigroup(S) then # Special case required because < for libsemigroups PartialPerms and < for # GAP partial perms are different; and also for IsFpSemigroup and - # IsFpMonoid because there's no sorted_at + # IsFpMonoid and IsQuotientSemigroup because there's no sorted_at return AsSet(AsList(S)); fi; result := EmptyPlist(Size(S)); @@ -243,7 +258,7 @@ function(S) local result, at, T, i; if not IsFinite(S) then Error("the argument (a semigroup) is not finite"); - elif IsFpSemigroup(S) or IsFpMonoid(S) then + elif IsFpSemigroup(S) or IsFpMonoid(S) or IsQuotientSemigroup(S) then at := {T, i} -> EvaluateWord(GeneratorsOfSemigroup(S), FroidurePinMemFnRec(S).factorisation(T, i) + 1); else @@ -269,7 +284,7 @@ InstallMethod(PositionCanonical, "for a semigroup with CanUseLibsemigroupsFroidurePin and mult. element", [IsSemigroup and CanUseLibsemigroupsFroidurePin, IsMultiplicativeElement], function(S, x) - local T, record, word, pos; + local T, record, word, pos, C; if IsPartialPermSemigroup(S) then if DegreeOfPartialPermSemigroup(S) < DegreeOfPartialPerm(x) @@ -293,6 +308,10 @@ function(S, x) pos := record.current_position(T, word); od; return pos + 1; + elif IsQuotientSemigroup(S) then + T := QuotientSemigroupPreimage(S); + C := QuotientSemigroupCongruence(S); + return CongruenceWordToClassIndex(C, Factorization(T, Representative(x))); fi; pos := FroidurePinMemFnRec(S).position(LibsemigroupsFroidurePin(S), @@ -386,7 +405,7 @@ function(S) local F; if not IsFinite(S) then Error("the argument (a semigroup) is not finite"); - elif IsFpSemigroup(S) or IsFpMonoid(S) then + elif IsFpSemigroup(S) or IsFpMonoid(S) or IsQuotientSemigroup(S) then return Length(IdempotentsSubset(S, [1 .. Size(S)])); fi; F := LibsemigroupsFroidurePin(S); @@ -534,7 +553,8 @@ function(S) if not IsFinite(S) then Error("the argument (a semigroup) is not finite"); - elif IsPartialPermSemigroup(S) or IsFpSemigroup(S) or IsFpMonoid(S) then + elif IsPartialPermSemigroup(S) or IsFpSemigroup(S) or IsFpMonoid(S) + or IsQuotientSemigroup(S) then # Special case required because < for libsemigroups ParialPerms and < for # GAP partial perms are different. return AsSet(S); @@ -592,7 +612,7 @@ function(S) return PositionCanonical(S, x); end; - if IsFpSemigroup(S) or IsFpMonoid(S) then + if IsFpSemigroup(S) or IsFpMonoid(S) or IsQuotientSemigroup(S) then factorisation := FroidurePinMemFnRec(S).minimal_factorisation; enum.ElementNumber := function(enum, nr) if nr > Length(enum) then @@ -650,7 +670,7 @@ function(enum, list) TryNextMethod(); fi; result := EmptyPlist(Length(list)); - if IsFpSemigroup(S) or IsFpMonoid(S) then + if IsFpSemigroup(S) or IsFpMonoid(S) or IsQuotientSemigroup(S) then factorisation := FroidurePinMemFnRec(S).minimal_factorisation; at := {T, nr} -> EvaluateWord(GeneratorsOfSemigroup(S), factorisation(T, nr) + 1); @@ -698,7 +718,7 @@ function(S) N := Size(S); result := List([1 .. N], x -> EmptyPlist(N)); T := LibsemigroupsFroidurePin(S); - if IsFpSemigroup(S) or IsFpMonoid(S) then + if IsFpSemigroup(S) or IsFpMonoid(S) or IsQuotientSemigroup(S) then pos_to_pos_sorted := {T, i} -> i; product := FroidurePinMemFnRec(S).product_by_reduction; FroidurePinMemFnRec(S).enumerate(T, N); @@ -724,10 +744,10 @@ end); # not copy then closure. InstallMethod(ClosureSemigroupOrMonoidNC, -"for fn, CanUseLibsemigroupsFroidurePin, finite mult. elt list, and record", +"for fn, CanUseLibsemigroupsFroidurePin, finite list, and record", [IsFunction, IsSemigroup and CanUseLibsemigroupsFroidurePin, - IsMultiplicativeElementCollection and IsFinite and IsList, + IsFinite and IsList, IsRecord], function(Constructor, S, coll, opts) local n, R, M, N, CppT, add_generator, generator, T, x, i; @@ -820,7 +840,7 @@ function(S, list) local product_by_reduction, is_idempotent, T; if not IsFinite(S) then Error("the 1st argument (a semigroup) is not finite"); - elif IsFpSemigroup(S) or IsFpMonoid(S) then + elif IsFpSemigroup(S) or IsFpMonoid(S) or IsQuotientSemigroup(S) then product_by_reduction := FroidurePinMemFnRec(S).product_by_reduction; is_idempotent := {T, x} -> product_by_reduction(T, x, x) = x; else diff --git a/gap/main/froidure-pin.gi b/gap/main/froidure-pin.gi index a8bc5c2a3..bd161f8c8 100644 --- a/gap/main/froidure-pin.gi +++ b/gap/main/froidure-pin.gi @@ -70,9 +70,17 @@ function(R) return CanUseFroidurePin(ParentAttr(R)); end); +# The next method is supposed to catch proper subsemigroups of quotient +# semigroups InstallImmediateMethod(CanUseGapFroidurePin, -IsQuotientSemigroup and HasQuotientSemigroupPreimage, 0, -S -> CanUseFroidurePin(QuotientSemigroupPreimage(S))); +IsAssociativeElementCollColl and HasGeneratorsOfSemigroup, 0, +function(S) + if IsEmpty(GeneratorsOfSemigroup(S)) then + return false; + fi; + return (not IsQuotientSemigroup(S)) + and IsCongruenceClass(GeneratorsOfSemigroup(S)[1]); +end); InstallTrueMethod(CanUseGapFroidurePin, IsSemigroupIdeal and IsReesMatrixSubsemigroup); diff --git a/gap/main/setup.gd b/gap/main/setup.gd index b017a50e1..d2a2cc3bf 100644 --- a/gap/main/setup.gd +++ b/gap/main/setup.gd @@ -12,7 +12,7 @@ # belonging to IsActingSemigroup... DeclareProperty("IsGeneratorsOfActingSemigroup", - IsMultiplicativeElementCollection); + IsListOrCollection); DeclareProperty("IsActingSemigroupWithFixedDegreeMultiplication", IsActingSemigroup); diff --git a/gap/main/setup.gi b/gap/main/setup.gi index 3005a857d..7d7aa2d9e 100644 --- a/gap/main/setup.gi +++ b/gap/main/setup.gi @@ -15,8 +15,8 @@ # IsGeneratorsOfActingSemigroup InstallMethod(IsGeneratorsOfActingSemigroup, -"for a multiplicative element collection", -[IsMultiplicativeElementCollection], ReturnFalse); +"for a list or collection", +[IsListOrCollection], ReturnFalse); # In the below can't do ReturnTrue, since GAP insists that we use # InstallTrueMethod. diff --git a/gap/semigroups/semigrp.gd b/gap/semigroups/semigrp.gd index c6afae69c..eee5df6b7 100644 --- a/gap/semigroups/semigrp.gd +++ b/gap/semigroups/semigrp.gd @@ -17,8 +17,14 @@ InstallTrueMethod(IsSemigroup, IsInverseSemigroup); +# We use IsListOrCollection here because some collections of semigroup +# generators (such as elements/congruence classes in a quotient semigroup) do +# not satisfy IsMultiplicativeElementCollection (although the classes +# themselves do satisfy IsMultiplicativeElement). DeclareOperation("SemigroupByGenerators", - [IsMultiplicativeElementCollection, IsRecord]); + [IsListOrCollection]); +DeclareOperation("SemigroupByGenerators", + [IsListOrCollection, IsRecord]); DeclareOperation("MonoidByGenerators", [IsMultiplicativeElementCollection, IsRecord]); DeclareOperation("InverseMonoidByGenerators", @@ -69,10 +75,14 @@ DeclareOperation("ClosureInverseMonoid", DeclareOperation("ClosureInverseSemigroupOrMonoidNC", [IsFunction, IsSemigroup, IsList and IsFinite, IsRecord]); +# We use IsListOrCollection here because some collections of semigroup +# generators (such as elements/congruence classes in a quotient semigroup) do +# not satisfy IsMultiplicativeElementCollection (although the classes +# themselves do satisfy IsMultiplicativeElement). DeclareOperation("ClosureSemigroup", - [IsSemigroup, IsMultiplicativeElementCollection, IsRecord]); + [IsSemigroup, IsListOrCollection, IsRecord]); DeclareOperation("ClosureSemigroup", - [IsSemigroup, IsMultiplicativeElementCollection]); + [IsSemigroup, IsListOrCollection]); DeclareOperation("ClosureSemigroup", [IsSemigroup, IsListOrCollection and IsEmpty, IsRecord]); DeclareOperation("ClosureSemigroup", diff --git a/gap/semigroups/semigrp.gi b/gap/semigroups/semigrp.gi index 05e84f087..03aa9d29b 100644 --- a/gap/semigroups/semigrp.gi +++ b/gap/semigroups/semigrp.gi @@ -146,15 +146,15 @@ InstallMethod(MagmaByGenerators, [IsAssociativeElementCollection and IsFinite], SemigroupByGenerators); InstallMethod(SemigroupByGenerators, -"for a finite multiplicative element collection", -[IsMultiplicativeElementCollection and IsFinite], +"for a finite list or collection", +[IsListOrCollection and IsFinite], function(coll) return SemigroupByGenerators(coll, SEMIGROUPS.DefaultOptionsRec); end); InstallMethod(SemigroupByGenerators, -"for a finite multiplicative element collection and record", -[IsMultiplicativeElementCollection and IsFinite, IsRecord], +"for a finite list or collection and record", +[IsListOrCollection and IsFinite, IsRecord], function(gens, opts) local filts, S; @@ -373,8 +373,8 @@ end); ############################################################################# InstallMethod(ClosureSemigroup, -"for a semigroup and finite multiplicative element collection", -[IsSemigroup, IsMultiplicativeElementCollection and IsFinite], +"for a semigroup and finite list or collection", +[IsSemigroup, IsListOrCollection and IsFinite], function(S, coll) return ClosureSemigroup(S, coll, SEMIGROUPS.OptionsRec(S)); end); @@ -413,8 +413,8 @@ function(S, x, opts) end); InstallMethod(ClosureSemigroup, -"for a semigroup, finite multiplicative element collection, and record", -[IsSemigroup, IsMultiplicativeElementCollection and IsFinite, IsRecord], +"for a semigroup, finite list or collection, and record", +[IsSemigroup, IsListOrCollection and IsFinite, IsRecord], function(S, coll, opts) # coll is copied here to avoid doing it repeatedly in @@ -494,11 +494,8 @@ function(S, coll, opts) end); InstallMethod(ClosureSemigroupOrMonoidNC, -"for a function, semigroup, finite mult. element collection, and record", -[IsFunction, - IsSemigroup, - IsMultiplicativeElementCollection and IsFinite and IsList, - IsRecord], +"for a function, semigroup, finite list, and record", +[IsFunction, IsSemigroup, IsList and IsFinite, IsRecord], function(Constructor, S, coll, opts) local n; diff --git a/gap/semigroups/semiquo.gi b/gap/semigroups/semiquo.gi index 89263d484..834aa32ac 100644 --- a/gap/semigroups/semiquo.gi +++ b/gap/semigroups/semiquo.gi @@ -55,11 +55,9 @@ function(S, I) return S / ReesCongruenceOfSemigroupIdeal(I); end); -InstallMethod(Size, "for a quotient semigroup", -[IsQuotientSemigroup and IsFinite], 3, -# to beat the CanUseGapFroidurePin method -function(q) - local cong; - cong := QuotientSemigroupCongruence(q); - return NrEquivalenceClasses(cong); -end); +# InstallMethod(Size, "for a quotient semigroup", +# [IsQuotientSemigroup and IsFinite], 3, +# # to beat the CanUseGapFroidurePin method +# function(q) +# return NrEquivalenceClasses(QuotientSemigroupCongruence(q)); +# end); diff --git a/src/cong.cpp b/src/cong.cpp index 5ddcfa5d2..1cc43e8be 100644 --- a/src/cong.cpp +++ b/src/cong.cpp @@ -19,6 +19,7 @@ #include "cong.hpp" #include // for exception +#include // for shared_ptr #include // for true_type #include // for vector @@ -125,6 +126,8 @@ void init_cong(gapbind14::Module &m) { "make_from_froidurepin_ppermUInt4") .def(gapbind14::init{}, "make_from_fpsemigroup") + .def(gapbind14::init>{}, + "make_from_froidurepinbase") .def(gapbind14::init{}, "make_from_table") .def("set_number_of_generators", &Congruence::set_number_of_generators) diff --git a/src/froidure-pin.hpp b/src/froidure-pin.hpp index 9dc39d5e0..3641003d8 100644 --- a/src/froidure-pin.hpp +++ b/src/froidure-pin.hpp @@ -36,11 +36,11 @@ namespace gapbind14 { template - struct IsGapBind14Type const&> + struct IsGapBind14Type const &> : std::true_type {}; template - struct IsGapBind14Type&> + struct IsGapBind14Type &> : std::true_type {}; template <> @@ -48,29 +48,33 @@ namespace gapbind14 { : std::true_type {}; template <> - struct IsGapBind14Type&> + struct IsGapBind14Type &> + : std::true_type {}; + + template <> + struct IsGapBind14Type &&> : std::true_type {}; } // namespace gapbind14 -void init_froidure_pin_bipart(gapbind14::Module&); -void init_froidure_pin_bmat(gapbind14::Module&); -void init_froidure_pin_matrix(gapbind14::Module&); -void init_froidure_pin_max_plus_mat(gapbind14::Module&); -void init_froidure_pin_min_plus_mat(gapbind14::Module&); -void init_froidure_pin_pbr(gapbind14::Module&); -void init_froidure_pin_pperm(gapbind14::Module&); -void init_froidure_pin_transf(gapbind14::Module&); +void init_froidure_pin_bipart(gapbind14::Module &); +void init_froidure_pin_bmat(gapbind14::Module &); +void init_froidure_pin_matrix(gapbind14::Module &); +void init_froidure_pin_max_plus_mat(gapbind14::Module &); +void init_froidure_pin_min_plus_mat(gapbind14::Module &); +void init_froidure_pin_pbr(gapbind14::Module &); +void init_froidure_pin_pperm(gapbind14::Module &); +void init_froidure_pin_transf(gapbind14::Module &); -void init_froidure_pin_base(gapbind14::Module& m); +void init_froidure_pin_base(gapbind14::Module &m); template -void bind_froidure_pin(gapbind14::Module& m, std::string name) { +void bind_froidure_pin(gapbind14::Module &m, std::string name) { using libsemigroups::FroidurePin; using FroidurePin_ = FroidurePin; using const_reference = typename FroidurePin::const_reference; gapbind14::class_(m, name) .def(gapbind14::init<>{}, "make") - .def(gapbind14::init{}, "copy") + .def(gapbind14::init{}, "copy") .def("add_generator", &FroidurePin_::add_generator) .def("generator", &FroidurePin_::generator) .def("closure", @@ -96,10 +100,10 @@ void bind_froidure_pin(gapbind14::Module& m, std::string name) { .def("finished", &FroidurePin_::finished) .def("position", &FroidurePin_::position) .def("rules", - [](FroidurePin_& S) { + [](FroidurePin_ &S) { return gapbind14::make_iterator(S.cbegin_rules(), S.cend_rules()); }) - .def("idempotents", [](FroidurePin_& S) { + .def("idempotents", [](FroidurePin_ &S) { return gapbind14::make_iterator(S.cbegin_idempotents(), S.cend_idempotents()); }); @@ -114,30 +118,30 @@ namespace libsemigroups { template <> struct Complexity { - constexpr inline size_t operator()(WBMat8 const& x) const noexcept { + constexpr inline size_t operator()(WBMat8 const &x) const noexcept { return Complexity()(x.first); } }; template <> struct Degree { - size_t operator()(WBMat8 const& x) const noexcept { + size_t operator()(WBMat8 const &x) const noexcept { return x.second; } }; template <> struct One { - WBMat8 operator()(WBMat8 const& x) const noexcept { + WBMat8 operator()(WBMat8 const &x) const noexcept { return std::make_pair(One()(x.first), x.second); } }; template <> struct Product { - void operator()(WBMat8& xy, - WBMat8 const& x, - WBMat8 const& y, + void operator()(WBMat8 & xy, + WBMat8 const &x, + WBMat8 const &y, size_t = 0) const noexcept { return Product()(xy.first, x.first, y.first); } @@ -145,14 +149,14 @@ namespace libsemigroups { template <> struct Hash { - size_t operator()(WBMat8 const& x) { + size_t operator()(WBMat8 const &x) { return Hash()(x.first); } }; template <> struct IncreaseDegree { - inline void operator()(WBMat8 const&) const noexcept {} + inline void operator()(WBMat8 const &) const noexcept {} }; } // namespace libsemigroups diff --git a/tst/standard/congruences/conginv.tst b/tst/standard/congruences/conginv.tst index ced9c88ee..4f705a44a 100644 --- a/tst/standard/congruences/conginv.tst +++ b/tst/standard/congruences/conginv.tst @@ -167,8 +167,8 @@ gap> cong := SemigroupCongruence(S, gap> [2] in cong; Error, the 1st argument (a list) does not have length 2 gap> [PartialPerm([4], [4]), 42] in cong; -Error, no method found! For debugging hints type ?Recovery from NoMethodFound -Error, no 1st choice method found for `in' on 2 arguments +Error, the items in the 1st argument (a list) do not all belong to the range o\ +f the 2nd argument (a 2-sided semigroup congruence) gap> EquivalenceClassOfElement(cong, (2, 5, 4)); Error, the 2nd argument (a mult. elt.) does not belong to the range of the 1st\ argument (a 2-sided congruence) diff --git a/tst/standard/main/froidure-pin.tst b/tst/standard/main/froidure-pin.tst index d98e37767..08cc9b0c5 100644 --- a/tst/standard/main/froidure-pin.tst +++ b/tst/standard/main/froidure-pin.tst @@ -60,11 +60,11 @@ true # CanUseGapFroidurePin for a quotient semigroup gap> S := FullTransformationMonoid(4);; gap> cong := SemigroupCongruence(S, [S.2, S.3]);; -gap> CanUseGapFroidurePin(S / cong); +gap> CanUseLibsemigroupsFroidurePin(S / cong); true gap> S := Semigroup(SEMIGROUPS.UniversalFakeOne);; gap> cong := SemigroupCongruence(S, [[S.1, S.1]]);; -gap> CanUseGapFroidurePin(S / cong); +gap> CanUseLibsemigroupsFroidurePin(S / cong); false # CanUseGapFroidurePin for a free band diff --git a/tst/standard/semigroups/semiquo.tst b/tst/standard/semigroups/semiquo.tst index b592758cd..620a4ee51 100644 --- a/tst/standard/semigroups/semiquo.tst +++ b/tst/standard/semigroups/semiquo.tst @@ -96,6 +96,193 @@ gap> R := S / I;; gap> Size(R); 5 +# Issue 456 - Quotient semigroups are slow +gap> S := InverseSemigroup([PartialPerm([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]), +> PartialPerm([1, 2, 3, 4, 5, 7], [2, 5, 1, 4, 3, 6]), +> PartialPerm([1, 2, 3, 4, 5, 6], [4, 3, 5, 1, 2, 9]), +> PartialPerm([1, 2, 3, 4, 5, 10], [5, 2, 4, 1, 3, 10]), +> PartialPerm([1, 2, 3, 4, 5, 8], [1, 5, 4, 2, 3, 7]), +> PartialPerm([1, 2, 3, 4, 5, 7], [3, 4, 1, 5, 2, 6]), +> PartialPerm([1, 2, 3, 4, 5, 10], [1, 2, 4, 3, 5, 10]), +> PartialPerm([1, 2, 3, 4, 5, 7], [2, 1, 4, 3, 5, 9]), +> PartialPerm([1, 2, 3, 4, 5, 10], [2, 4, 5, 1, 3, 9]), +> PartialPerm([1, 2, 3, 4, 5, 9], [2, 3, 4, 1, 5, 6]), +> PartialPerm([1, 2, 3, 4, 5, 9], [2, 1, 3, 4, 5, 9]), +> PartialPerm([1, 2, 3, 4, 5, 9], [5, 4, 1, 3, 2, 7]), +> PartialPerm([1, 2, 3, 4, 5, 7], [4, 2, 5, 1, 3, 8]), +> PartialPerm([1, 2, 3, 4, 5, 9], [4, 1, 3, 2, 5, 10])]);; +gap> Size(S); +720 +gap> cong := MinimumGroupCongruence(S); + with congruence pair (6,1)> +gap> Size(S / cong); +120 +gap> IsomorphismPermGroup(S / cong); +MappingByFunction( + with congruence pair (6, +1 + )>>, , function( x ) ... end,\ + function( x ) ... end ) + +# Issue 456 - Quotient semigroups are slow +gap> S := InverseSemigroup([PartialPerm([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]), +> PartialPerm([1, 2, 3, 4, 5, 6], [5, 2, 3, 1, 4, 8]), +> PartialPerm([1, 2, 3, 4, 5, 6], [3, 4, 5, 2, 1, 7]), +> PartialPerm([1, 2, 3, 4, 5, 8], [3, 1, 4, 2, 5, 8]), +> PartialPerm([1, 2, 3, 4, 5, 7], [1, 4, 3, 2, 5, 7]), +> PartialPerm([1, 2, 3, 4, 5, 7], [2, 4, 5, 1, 3, 8]), +> PartialPerm([1, 2, 3, 4, 5, 7], [1, 2, 5, 4, 3, 8]), +> PartialPerm([1, 2, 3, 4, 5, 8], [4, 5, 2, 3, 1, 6])]); + +gap> Size(S); +336 +gap> cong := MinimumGroupCongruence(S); + with congruence pair (4,1)> +gap> Size(S / cong); +120 +gap> IsomorphismPermGroup(S / cong); +MappingByFunction( + with congruence pair (4, +1 + )>>, , function( x ) ... end,\ + function( x ) ... end ) + +# Issue 454 - Missing functionality for quotient semigroups +gap> S := InverseSemigroup([PartialPerm([1, 2, 3, 4], [1, 2, 3, 4]), +> PartialPerm([1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 19, 20, 29, 30], +> [2, 1, 4, 3, 6, 5, 8, 7, 12, 11, 14, 13, 20, 19, 30, 29]), +> PartialPerm([1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 19, 20, 29, 30], +> [4, 3, 2, 1, 8, 7, 6, 5, 10, 9, 16, 15, 18, 17, 32, 31]), +> PartialPerm([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 31, 32], +> [2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 16, 15, 18, 17, 32, 31]), +> PartialPerm([1, 2, 3, 4, 21, 22, 23, 24, 25, 26, 27, 28, 33, 34, 35, 36], +> [4, 3, 2, 1, 24, 23, 22, 21, 28, 27, 26, 25, 36, 35, 34, 33]), +> PartialPerm([1, 2, 3, 4, 21, 22, 23, 24, 25, 26, 27, 28, 33, 34, 35, 36], +> [3, 4, 1, 2, 23, 24, 21, 22, 27, 28, 25, 26, 35, 36, 33, 34]), +> PartialPerm([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 31, 32], +> [4, 3, 2, 1, 8, 7, 6, 5, 12, 11, 14, 13, 20, 19, 30, 29]), +> PartialPerm([1, 2, 3, 4, 19, 20, 29, 30], [4, 3, 2, 1, 18, 17, 32, 31]), +> PartialPerm([1, 2, 3, 4, 22, 24, 33, 35], [4, 3, 2, 1, 23, 21, 36, 34])]); + +gap> cong := MinimumGroupCongruence(S); + with congruence pair (9,1)> +gap> G := S / cong; + with congruence pair (9,1)>> +gap> IrredundantGeneratingSubset(S / cong); +[ <2-sided congruence class of (1,3)(2,4)(21,23)(22,24)(25,27)(26,28)(33,35) + (34,36)>, <2-sided congruence class of [11,10][12,9][13,16][14,15][19,18] + [20,17][29,32][30,31](1,4)(2,3)(5,8)(6,7)> ] +gap> SmallSemigroupGeneratingSet(S / cong); +[ <2-sided congruence class of >, + <2-sided congruence class of (1,3)(2,4)(21,23)(22,24)(25,27)(26,28)(33,35) + (34,36)>, <2-sided congruence class of (1,2)(3,4)(5,6)(7,8)(11,12)(13,14) + (19,20)(29,30)>, <2-sided congruence class of [11,10][12,9][13,16][14,15] + [19,18][20,17][29,32][30,31](1,4)(2,3)(5,8)(6,7)> ] +gap> GeneratorsSmallest(S / cong); +[ <2-sided congruence class of >, + <2-sided congruence class of (1,2)(3,4)(5,6)(7,8)(11,12)(13,14)(19,20) + (29,30)>, <2-sided congruence class of [11,10][12,9][13,16][14,15][19,18] + [20,17][29,32][30,31](1,4)(2,3)(5,8)(6,7)> ] + +# Issue 816 - Subsemigroups of quotient semigroups +gap> S := Semigroup(Transformation([2, 1, 5, 1, 5]), +> Transformation([1, 1, 1, 5, 3]), +> Transformation([2, 5, 3, 5, 3]));; +gap> cong := SemigroupCongruence(S, [Transformation([1, 2, 5, 2, 5]), +> Transformation([2, 1, 5, 1, 5])]); +<2-sided semigroup congruence over with 1 generating pairs> +gap> T := S / cong;; +gap> gens := GeneratorsOfSemigroup(S);; +gap> images := List(gens, gen -> EquivalenceClassOfElement(cong, gen));; +gap> Q := Subsemigroup(T, images); + +gap> Q = T; +true +gap> Factorization(T, images[2]); +[ 2 ] +gap> Factorization(Q, images[2]); +[ 2 ] +gap> H := SubsemigroupNC(T, images); + +gap> T = H; +true +gap> Factorization(H, images[2]); +[ 2 ] +gap> map := IsomorphismFpSemigroup(T); +MappingByFunction( with +1 + generating pairs>>, , functio\ +n( x ) ... end, function( x ) ... end ) +gap> map := IsomorphismFpSemigroup(Q); +MappingByFunction( + , , function( x ) ... end, fu\ +nction( x ) ... end ) +gap> IsomorphismFpSemigroup(H); +MappingByFunction( + , , function( x ) ... end, fu\ +nction( x ) ... end ) + +# Quotients of quotients +gap> S := Semigroup(Transformation([2, 1, 5, 1, 5]), +> Transformation([1, 1, 1, 5, 3]), +> Transformation([2, 5, 3, 5, 3]));; +gap> cong := SemigroupCongruence(S, [Transformation([1, 2, 5, 2, 5]), +> Transformation([2, 1, 5, 1, 5])]); +<2-sided semigroup congruence over with 1 generating pairs> +gap> Q := S / cong; + with 1 generating pairs>> +gap> CongruencesOfSemigroup(Q); +[ <2-sided semigroup congruence over with + 1 generating pairs>> with 0 generating pairs>, + <2-sided semigroup congruence over with + 1 generating pairs>> with 1 generating pairs>, + <2-sided semigroup congruence over with + 1 generating pairs>> with 1 generating pairs>, + <2-sided semigroup congruence over with + 1 generating pairs>> with 1 generating pairs>, + <2-sided semigroup congruence over with + 1 generating pairs>> with 1 generating pairs>, + <2-sided semigroup congruence over with + 1 generating pairs>> with 1 generating pairs> ] +gap> map := QuotientSemigroupHomomorphism(Q); +MappingByFunction( + , with +1 generating pairs>>, function( x ) ... end ) +gap> Q := Q / SemigroupCongruence(Q, +> [[Transformation([2, 1, 5, 1, 5]) ^ map, +> Transformation([2, 5, 3, 5, 3]) ^ map]]); + + with 1 generating pairs>> with 1 generating pairs>> +gap> CongruencesOfSemigroup(Q); +[ <2-sided semigroup congruence over with 1 generating pairs>> with + 1 generating pairs>> with 0 generating pairs>, + <2-sided semigroup congruence over with 1 generating pairs>> with + 1 generating pairs>> with 1 generating pairs> ] +gap> Size(Q); +2 + # SEMIGROUPS_UnbindVariables gap> Unbind(I); gap> Unbind(J); diff --git a/tst/testinstall.tst b/tst/testinstall.tst index 2baf69f8c..6133244c1 100644 --- a/tst/testinstall.tst +++ b/tst/testinstall.tst @@ -904,43 +904,43 @@ gap> x := ReesZeroMatrixSemigroupElement(R, 1, (1, 3), 1);; gap> y := ReesZeroMatrixSemigroupElement(R, 1, (), 1);; gap> cong := SemigroupCongruenceByGeneratingPairs(R, [[x, y]]);; gap> c := Set(EquivalenceClasses(cong)); -[ <2-sided congruence class of 0>, <2-sided congruence class of (1,(),1)>, +[ <2-sided congruence class of (1,(),1)>, <2-sided congruence class of (1,(),2)>, <2-sided congruence class of (1,(),3)>, <2-sided congruence class of (1,(),4)>, <2-sided congruence class of (1,(),5)>, <2-sided congruence class of (1,(),6)>, <2-sided congruence class of (2,(),1)>, + <2-sided congruence class of (3,(),1)>, + <2-sided congruence class of (4,(),1)>, + <2-sided congruence class of (5,(),1)>, + <2-sided congruence class of (6,(),1)>, + <2-sided congruence class of (7,(),1)>, <2-sided congruence class of 0>, <2-sided congruence class of (2,(),2)>, <2-sided congruence class of (2,(),3)>, <2-sided congruence class of (2,(),4)>, <2-sided congruence class of (2,(),5)>, <2-sided congruence class of (2,(),6)>, - <2-sided congruence class of (3,(),1)>, <2-sided congruence class of (3,(),2)>, <2-sided congruence class of (3,(),3)>, <2-sided congruence class of (3,(),4)>, <2-sided congruence class of (3,(),5)>, <2-sided congruence class of (3,(),6)>, - <2-sided congruence class of (4,(),1)>, <2-sided congruence class of (4,(),2)>, <2-sided congruence class of (4,(),3)>, <2-sided congruence class of (4,(),4)>, <2-sided congruence class of (4,(),5)>, <2-sided congruence class of (4,(),6)>, - <2-sided congruence class of (5,(),1)>, <2-sided congruence class of (5,(),2)>, <2-sided congruence class of (5,(),3)>, <2-sided congruence class of (5,(),4)>, <2-sided congruence class of (5,(),5)>, <2-sided congruence class of (5,(),6)>, - <2-sided congruence class of (6,(),1)>, <2-sided congruence class of (6,(),2)>, <2-sided congruence class of (6,(),3)>, <2-sided congruence class of (6,(),4)>, <2-sided congruence class of (6,(),5)>, <2-sided congruence class of (6,(),6)>, - <2-sided congruence class of (7,(),1)>, <2-sided congruence class of (7,(),2)>, <2-sided congruence class of (7,(),3)>, <2-sided congruence class of (7,(),4)>,