Skip to content

Conversation

@Joseph-Edwards
Copy link
Collaborator

@Joseph-Edwards Joseph-Edwards commented Sep 2, 2025

This PR updates the package to use libsemigroups v3.

Presently, this is a draft and there is a lot more work to be done. In a few critical places, I have added errors of the form "<the thing you are trying to do> is not yet implemented". With this, running any of the test files should clearly highlight further changes that need to be made.

Some notable things still to be done:

  • A way to construct Congruence objects from FroidurePin objects.
  • A way to construct a FroidurePin objects from ToddCoxeter objects.
  • An analogue of the old Congruence::ntc function.
  • A way to quotient FroidurePin objects.

@james-d-mitchell
Copy link
Collaborator

Requires libsemigroups/libsemigroups#880 (and possibly a release to be made) to be merged first before this has any chance that the CI will pass.

@Joseph-Edwards
Copy link
Collaborator Author

I've been doing some digging about why the tests on master are failing.

Since the most recent release of GAP, gap-system/gap#6064 has been merged. The change that I think is impacting us is described in that PR:

The only change arising from this should be that the IsomorphismPermGroup method requiring IsGroup and IsFinite and IsHandledByNiceMonomorphism is now ranked below the method requiring IsCyclotomicMatrixGroup, and I think this is intended behaviour.

This is changing the behaviour of the following line that gets called at some point during the call to InjectionPrincipalFactor (the function in the failing test):

map := IsomorphismPermGroup(SchutzenbergerGroup(H));

In the example given in the test file, H has size 8.

Previously, using the same input as given in the test file, this line returned a map whose Range was a group of size 8. Now it returns a map whose Range is a group of size 24261120, but whose Image is a group of size 8. It's not clear to me if this is a bug or not; when an isomorphism is being defined by a map (in the computational sense, rather than a mathematical one) should it be required to be surjective onto its range, or only the image? The way the code is written and tested, it seems like the intention was the latter.

If this isn't a bug, then the following changes in acting.gi seem to resolve the issue:

 InstallMethod(IsomorphismPermGroup, "for H-class of an acting semigroup",
 [IsGreensHClass and IsActingSemigroupGreensClass],
 function(H)
   local map, id, iso, inv;
 
   if not IsGroupHClass(H) then
     ErrorNoReturn("the argument (a Green's H-class) is not a group");
   elif not IsPermGroup(SchutzenbergerGroup(H)) then
     map := IsomorphismPermGroup(SchutzenbergerGroup(H));
   else
     map := IdentityMapping(SchutzenbergerGroup(H));
   fi;
 
   id := ConvertToInternalElement(Parent(H), MultiplicativeNeutralElement(H));
 
   iso := function(x)
     if not x in H then
       ErrorNoReturn("the argument does not belong to the domain of the ",
                     "function");
     fi;
     x := ConvertToInternalElement(Parent(H), x);
     return LambdaPerm(Parent(H))(id, x) ^ map;
   end;
   inv := function(x)
     local S, act;
     if not x in Image(map) then
       ErrorNoReturn("the argument does not belong to the domain of the ",
                     "function");
     fi;
     S := Parent(H);
     act := StabilizerAction(S);
     return ConvertToExternalElement(S,
-                                    act(id, x ^ InverseGeneralMapping(map)));
+                                    act(id, x ^ RestrictedInverseGeneralMapping(map)));
   end;
-  return MappingByFunction(H, Range(map), iso, inv);
+  return MappingByFunction(H, Image(map), iso, inv);
 end);

This makes sure our inverse map satisfies IsTotal by restricting its source to the Image of map.

@Joseph-Edwards
Copy link
Collaborator Author

I've been doing some more digging into the failing tests, this time the one on v4,12. I haven't yet got to the bottom of it, but the following seems relevant.

On 4.12:

gap> TraceMethods([SetEquivalenceRelationPartition]);
gap> LoadPackage("semig", false);
true
gap> S := FreeSemigroup(1);
<free semigroup on the generators [ s1 ]>
gap> x := GeneratorsOfSemigroup(S)[1];
s1
gap> gens := [x ^ 2, x ^ 4];
[ s1^2, s1^4 ]
gap> cong := SemigroupCongruence(S, [x ^ 2, x ^ 2]);;

On master:

gap> TraceMethods([SetEquivalenceRelationPartition]);
gap> LoadPackage("semig", false);
true
gap> S := FreeSemigroup(1);
<free semigroup on the generators [ s1 ]>
gap> x := GeneratorsOfSemigroup(S)[1];
s1
gap> gens := [x ^ 2, x ^ 4];
[ s1^2, s1^4 ]
gap> cong := SemigroupCongruence(S, [x ^ 2, x ^ 2]);;
#I  SetEquivalenceRelationPartition: system setter at /home/joseph/Dev/Comp_Maths/gap/lib/relation.gd:740

In particular, the call to SemigroupCongruences doesn't set EquivalenceRelationPartition in v4.12. Later down the line, this causes an issue when we try to compute the EquivalenceRelationPartition of something. Since it hasn't been stored already, we enter this function (which causes an error):

InstallMethod(EquivalenceRelationPartition,
"for CanUseLibsemigroupsCongruence with known generating pairs",
[CanUseLibsemigroupsCongruence and
 HasGeneratingPairsOfLeftRightOrTwoSidedCongruence],
function(C)
  local S, CC, reverse, words, ntc, super, gens, class, i, j;

  S := Range(C);
  if not IsFinite(S) or CanUseLibsemigroupsFroidurePin(S) then
    CC := LibsemigroupsCongruence(C);
    if IsLeftMagmaCongruence(C) and not IsRightMagmaCongruence(C) then
      reverse := Reversed;
    else
      reverse := IdFunc;
    fi;
    if IsFinite(S) then
      words := List(S, x -> reverse(Factorization(S, x) - 1));
      ntc := libsemigroups.congruence_non_trivial_classes(CC, words) + 1;
    elif IsFpSemigroup(S) or IsFreeSemigroup(S)
        or IsFpMonoid(S) or IsFreeMonoid(S) then
      super := LibsemigroupsCongruence(UnderlyingCongruence(S));      # <- This line is where the error is thrown.
      ntc := libsemigroups.infinite_congruence_non_trivial_classes(
               super, CC) + 1;
    else
      TryNextMethod();
    fi;
    gens := GeneratorsOfSemigroup(S);
    for i in [1 .. Length(ntc)] do
      class := ntc[i];
      for j in [1 .. Length(class)] do
        class[j] := EvaluateWord(gens, reverse(class[j]));
      od;
    od;
    return ntc;
  elif CanUseGapFroidurePin(S) then
    # in this case libsemigroups.Congruence.ntc doesn't work, because S is not
    # represented in the libsemigroups object
    return Filtered(EquivalenceRelationPartitionWithSingletons(C),
                    x -> Size(x) > 1);
  fi;
  TryNextMethod();
end);

Regardless of this bug, we should probably add some checks to see whether or not that UnderlyingCongruence is actually computable.

@Joseph-Edwards
Copy link
Collaborator Author

Joseph-Edwards commented Jan 30, 2026

Some more digging has revealed the following:

On 4.12:

gap> LoadPackage("semigroups", false);;
gap> S := FreeSemigroup(1);;
gap> x := GeneratorsOfSemigroup(S)[1];;
gap> for f in ApplicableMethod(SemigroupCongruenceByGeneratingPairs, [S, []], 0, "all") do
>     Print(FilenameFunc(f));
>     Print("\n");
> od;
~/.gap/pkg/Semigroups/gap/congruences/congpart.gi
~/gap-4.12.2/lib/semicong.gi

On master:

gap> LoadPackage("semigroups", false);;
gap> S := FreeSemigroup(1);;
gap> x := GeneratorsOfSemigroup(S)[1];;
gap> for f in ApplicableMethod(SemigroupCongruenceByGeneratingPairs, [S, []], 0, "all") do
>     Print(FilenameFunc(f));
>     Print("\n");
> od;
~/gap/lib/semicong.gi
~/.gap/pkg/Semigroups/gap/congruences/congpart.gi

The order in which methods are tried has changed. I'm not sure why; I can't see any changes in the semicong.gi file that would suggest its preference might have changed between versions.

Looking into the SemigroupCongruenceByGeneratingPairs provided by gap, there are two methods:

InstallMethod( SemigroupCongruenceByGeneratingPairs,
    "for a semigroup and a list of pairs of its elements",
    IsElmsColls,
    [ IsSemigroup, IsList ], 0,
    function( M, gens )
        local cong;
        cong := LR2MagmaCongruenceByGeneratingPairsCAT(M, gens,
                    IsMagmaCongruence);
        SetIsSemigroupCongruence(cong,true);
        return cong;
    end );

InstallMethod( SemigroupCongruenceByGeneratingPairs,
    "for a semigroup and an empty list",
    true,
    [ IsSemigroup, IsList and IsEmpty], 0,
    function( M, gens )
        local cong;
        cong := LR2MagmaCongruenceByGeneratingPairsCAT(M, gens,
                    IsMagmaCongruence);
        SetIsSemigroupCongruence(cong,true);
        SetEquivalenceRelationPartition(cong,[]);                        # <- Additional line
        return cong;
    end );

In the case where the list gens is empty, the equivalence relation partition is also set (see the additional line above). We can add something like this in the function provided by Semigroups, which I think will fix the issue in our test.

That said, this doesn't explain why the order of preference has changed, but maybe that's not important.

@Joseph-Edwards
Copy link
Collaborator Author

When we install our SemigroupCongruenceByGeneratingPairs, there is the following comment:

InstallMethod(SemigroupCongruenceByGeneratingPairs,
"for a semigroup and a list",
[IsSemigroup, IsList],
19,  # to beat the library method for IsList and IsEmpty
function(S, pairs)

However, we don't beat the library method for IsList and IsEmpty (as explained in the previous comment). I'm not sure what the best way to address this is, but we probably should.

@james-d-mitchell
Copy link
Collaborator

Increase the rank further

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants