From f381cc77f2a610a8562afed9411408b3815c4028 Mon Sep 17 00:00:00 2001 From: Sergio Siccha Date: Tue, 29 Mar 2022 23:44:32 +0200 Subject: [PATCH 01/25] Add option infoLevel to RECOG.TestGroup --- gap/base/recognition.gi | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gap/base/recognition.gi b/gap/base/recognition.gi index a75e9ae1..ed31e6dc 100644 --- a/gap/base/recognition.gi +++ b/gap/base/recognition.gi @@ -892,6 +892,9 @@ RECOG.TestGroupOptions := rec( # a supergroup to check inTests := 30, + # InfoLevel used during the call to TestGroup. Useful for debugging. + infoLevel := 0, + # The following options are off by default as they fail on # many examples at present @@ -921,7 +924,7 @@ RECOG.TestGroup := function(g,proj,size, optionlist...) fi; lvl:=InfoLevel(InfoRecog); - SetInfoLevel(InfoRecog, 0); + SetInfoLevel(InfoRecog, options.infoLevel); repeat count := count + 1; #r := Runtime(); From 2aed139656521bfcfd3067611d5cbcbcb9b249d3 Mon Sep 17 00:00:00 2001 From: Sergio Siccha Date: Wed, 30 Mar 2022 00:04:20 +0200 Subject: [PATCH 02/25] Fix for howtowritearecogmethod.autodoc --- doc/howtowritearecogmethod.autodoc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/howtowritearecogmethod.autodoc b/doc/howtowritearecogmethod.autodoc index 1b1c9539..11ce8483 100644 --- a/doc/howtowritearecogmethod.autodoc +++ b/doc/howtowritearecogmethod.autodoc @@ -86,10 +86,14 @@ Namely, many functions for objects with memory assume that, if the elements live Recall that splitting recognition methods produce an epimorphism $\phi:G\to H$ and then delegate the work to the image $H$ and the kernel $N:=\ker(\phi)$. This means that now $N$ and $H$ have to be constructively recognized. Such a -splitting recognition method only needs to provide a homomorphism, by calling -`SetHomom(ri, hom);`. However, in practice one will want to provide additional +splitting recognition method only needs to +- provide a homomorphism, by calling +`SetHomom(ri, hom);`, and +- optionally update `methodsforimage`, if the representation changed. +However, in practice one will want to provide additional data. + We start with an example, similar to a method used in recog. This refers to permutation groups only! From dd14350030daef69dc81451fea3b664ca4e46d4c Mon Sep 17 00:00:00 2001 From: Sergio Siccha Date: Wed, 30 Mar 2022 01:03:45 +0200 Subject: [PATCH 03/25] Make CallMethods directly write into the RecogNode This has the advantage, that methods can check whether methods they depend upon where already called. --- gap/base/methsel.gi | 6 ++++-- gap/base/recognition.gi | 2 +- gap/matrix/classical.gi | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gap/base/methsel.gi b/gap/base/methsel.gi index 14485ace..66538ae9 100644 --- a/gap/base/methsel.gi +++ b/gap/base/methsel.gi @@ -56,9 +56,11 @@ InstallGlobalFunction( "CallMethods", function(db, tolerancelimit, methargs...) # Second argument is a number, the tolerance limit. # All other arguments are handed through to the methods. - local i, ms, result, tolerance; + local ri, i, ms, result, tolerance; ms := rec(failedMethods := rec(), inapplicableMethods := rec()); + ri := methargs[1]; + Setfhmethsel(ri, ms); # Initialize record: tolerance := 0; # reuse methods that failed that many times @@ -137,5 +139,5 @@ InstallGlobalFunction( "CallMethods", function(db, tolerancelimit, methargs...) Info(InfoMethSel, 1, "Giving up!"); ms.result := TemporaryFailure; ms.tolerance := tolerance; - return ms; + return; end); diff --git a/gap/base/recognition.gi b/gap/base/recognition.gi index ed31e6dc..6e6311e4 100644 --- a/gap/base/recognition.gi +++ b/gap/base/recognition.gi @@ -486,7 +486,7 @@ InstallGlobalFunction( RecogniseGeneric, Assert(0, Length(Set(allmethods, m->m.rank)) = Length(allmethods)); # Find a possible homomorphism (or recognise this group as leaf) - Setfhmethsel(ri, CallMethods( allmethods, 10, ri, H )); + CallMethods( allmethods, 10, ri, H ); # TODO: extract the value 10 into a named constant, and / or make it # an option parameter to the func diff --git a/gap/matrix/classical.gi b/gap/matrix/classical.gi index c51ae28c..6f68af87 100644 --- a/gap/matrix/classical.gi +++ b/gap/matrix/classical.gi @@ -2316,7 +2316,7 @@ function( arg ) ); merkinfolevel := InfoLevel(InfoMethSel); SetInfoLevel(InfoMethSel,0); - ret := CallMethods( ClassicalMethDb, nrrandels, recognise, grp ); + CallMethods( ClassicalMethDb, nrrandels, recognise, grp ); SetInfoLevel(InfoMethSel,merkinfolevel); # fail: bedeutet, dass entnervt aufgegeben wurde # true: bedeutet, dass eine Methode "erfolgreich" war From 47c4b28f5b2049edff6a3973cfb38e3e92abb57f Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Tue, 30 Mar 2021 16:39:35 +0200 Subject: [PATCH 04/25] Squash of SnAnUnknownDegree commits Finish SnAnUnknownDegree and install it as a method for projective groups. Old commit msgs Add FindImageSnSmallDegree Improve a comment in tryThreeCycleCandidate Extend the heuristic three cycle test WIP BIB Conder WIP update comments for Conder's thesis Use `SnAnUnknownDegree` also for 7 and 8. WIP handle small degree Add `GuessSnAnDegree` FIX: replace old `EmptyRecognitionInfoRecord` with `RecogNode` in test file. Test: Adjust permMatGroup Add Todo comment in GuessSnAnDegree for Alt(9) and Sym(8) Add TODO comment and possibly a reference for the inspiration of GuessSnAnDegree Move GuessSnAnDegree into contrib/derek FIX: contrib/derek sum over prime-powers and not primes Co-authored-by: ssiccha --- contrib/derek/SnAnKnownDegree.gi | 129 +++++++ doc/recog.bib | 7 + gap/generic/SnAnUnknownDegree.gi | 340 +++++++++++++++--- gap/projective.gi | 4 + .../quick/GenericSnAnUnknownDegree.tst | 66 ++++ 5 files changed, 505 insertions(+), 41 deletions(-) create mode 100644 contrib/derek/SnAnKnownDegree.gi diff --git a/contrib/derek/SnAnKnownDegree.gi b/contrib/derek/SnAnKnownDegree.gi new file mode 100644 index 00000000..00bc794d --- /dev/null +++ b/contrib/derek/SnAnKnownDegree.gi @@ -0,0 +1,129 @@ +# Inspired from Magma Code: GuessAltsymDegree, in magma/package/Group/GrpFin/SimpleRecog/altsym.m +# Returns a guess at alternating or symmetric and degree n +# (It won't work for Sym(3) or Sym(6)!) +# +# This function samples projective orders of elements, and attempts to guess +# degree n and whether it is Alternating or Symmetric. +# Returns a record with entries: +# - type : string "Alternating" or "Symmetric" +# - degree : integer n +# Returns fail if n<=6 or maxtries elements are sampled with +# no decision made. +# +# At least Max(mintries,fac*n*Log(n)) random elements are chosen without +# the answer changing, where mintries, fac can be given as an optional +# arguments. +# +# TODO: Investigate why Alt(9) and Sym(8) return fail +# TODO: Might be inspired from +# "Fast Constructive Recognition of a Black Box Group Isomorphic to Sn or An using Goldbach’s Conjecture" +# by Sergey Bratus and Igor Pak, +# in Chapter 9. "What To Do If n is Not Known?" + +RECOG.GuessSnAnDegree := function(ri, optionlist...) + local G, r, options, mintries, maxtries, fac, mindego, mindege, ct, cto, cte, proc, g, o, mindeg, o_fact, mindegforg; + # mindego and mindege will be respectively the smallest possible + # degrees of symmetric groups that contain the elements of odd and + # even orders, in the random sample. + # If mindego > mindege we assume the group is alternating, otherwise + # that it is symmetric. + + G := Grp(ri); + if (IsPermGroup(G) and NrMovedPoints(G) <= 6) + or (IsMatrixGroup(G) and DimensionOfMatrixGroup(G) < 3) then + Print("GuessAltsymDegree works only for degree > 6\n"); + return fail; + fi; + + # Set options + options := rec( + mintries := 100, + maxtries := 5000, + fac := 4 + ); + + if Length(optionlist) > 0 then + for r in RecNames(optionlist[1]) do + if not IsBound(options.(r)) then + ErrorNoReturn("Invalid option to GuessSnAnDegree: ", r); + fi; + options.(r) := optionlist[1].(r); + od; + fi; + + mintries := options.mintries; + maxtries := options.maxtries; + fac := options.fac; + + # Init Loop + mindego := 0; + mindege := 0; + cto := 0; + cte := 0; + ct := 0; + mindeg := 0; + if mintries < 1 then + mintries := 1; + fi; + + # Main Loop + while (ct < Maximum(mintries, fac * mindeg * Int(Ceil(Log(Float(mindeg+1))))) + or mindego = mindege+1) and ct <= maxtries do + # The situation mindego = mindege+1 was responsible for most errors + # in the first version! Alt(n+1) was returned instead of Sym(n). + g := RandomElm(ri, "GuessSnAnDegree", false)!.el;; + o := ri!.order(g); + ct := ct + 1; # counter of loop, as long as no new larger degree was detected + if o = 1 then + continue; + fi; + o_fact := Collected(Factors(o)); + mindegforg := Sum(o_fact, f -> f[1] ^ f[2]); # minimum degree is sum over all prime-powers in factorization + if o mod 2 = 0 then + cte := cte + 1; # counter for even orders + if mindegforg > mindege then + mindege := mindegforg; + if mindege > mindeg then + mindeg := mindege; + fi; + ct := 0; + # vprintf IsAltsym: "New E, E = %o, O = %o, elt order = %o, Randoms = %o\n", mindege, mindego, o_fact, cte+cto; + fi; + else + cto := cto + 1; # counter for odd orders + if mindegforg > mindego then + mindego := mindegforg; + if mindego > mindeg then + mindeg := mindego; + fi; + ct := 0; + # vprintf IsAltsym: "New O, E = %o, O = %o, elt order = %o, Randoms = %o\n", mindege, mindego, o_fact, cte+cto; + fi; + fi; + od; + + if ct > maxtries then + # vprintf IsAltsym: "maxtries exceeded - giving up!"; + return fail; + fi; + + # vprintf IsAltsym: "E = %o, O = %o, Randoms = %o\n", mindege, mindego, cte+cto; + + if mindego > mindege then + if mindego <= 6 then + # vprintf IsAltsym: "GuessAltsymDegree works only for degree > 6"; + return fail; + else + # vprintf IsAltsym: "Alternating of degree %o\n", mindego; + return rec(type := "Alternating", degree := mindego); + fi; + else + if mindege <= 6 then + # vprintf IsAltsym: "GuessAltsymDegree works only for degree > 6"; + return fail; + else + # vprintf IsAltsym: "Symmetric of degree %o\n", mindege; + return rec(type := "Symmetric", degree := mindege); + fi; + fi; +end; diff --git a/doc/recog.bib b/doc/recog.bib index 20ffe346..56a41d39 100644 --- a/doc/recog.bib +++ b/doc/recog.bib @@ -599,6 +599,13 @@ @incollection {CLG98 NOTE = {https://doi.org/10.1017/CBO9780511565830.007}, } +@article{C12, + AUTHOR = {Conder, Jonathan}, + TITLE = {Algorithms for Permutation Groups}, + YEAR = {2012}, + NOTE = {TODO}, +} + @article {CLGO06, AUTHOR = {Conder, M. D. E. and Leedham-Green, C. R. and O'Brien, E. A.}, TITLE = {Constructive recognition of {${\rm PSL}(2,q)$}}, diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 31106e85..39a93148 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -41,6 +41,27 @@ RECOG.ThreeCycleCandidatesConstants := function(eps, N) ); end; +# Return false if c can not be a three cycle. This uses cheap tests to +# determine this. It is based on the Magma function heuristicThreeCycleTest. +heuristicThreeCycleTest := function(ri, c, logInt2N) + local r, y, yTo5, k; + c := StripMemory(c); + if not isone(ri)(c ^ 3) then + return false; + fi; + for k in [1 .. logInt2N + 1] do + r := StripMemory(PseudoRandom(Grp(ri))); # TODO: Replace PseudoRandom + # c * c ^ r is a product of two three-cycles, so it should have order + # 1, 2, 3 or 5. + y := c * c ^ r; + yTo5 := y ^ 5; + if not isone(ri)(yTo5) and not isone(ri)(yTo5 * y) then + return false; + fi; + od; + return true; +end; + # ri : recognition node with group G # constants : a record with components M, B, T, C, and logInt2N # @@ -97,15 +118,18 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # integer, loop variable a, # elements, in G - r, tPower, tPowerOld, c; + r, tPower, tPowerOld, c, + # the three cycle candidate + candidate; # Steps 2 & 3: New involution # Check if we either tried enough conjugates or constructed enough # three cycle candidates for the current involution t. # If this is the case, we need to construct the next involution if nrTriedConjugates >= C or nrThreeCycleCandidates >= T then r := RandomElm(ri, "SnAnUnknownDegree", true)!.el; - tPower := r ^ M; + # In the paper, we have t = r ^ M. # Invariant: tPower = (r ^ M) ^ (2 ^ a) + tPower := r ^ M; # We make a small improvement to the version described in # . The order of r ^ M is a 2-power. # It can be at most 2 ^ logInt2N. Thus, if we find an r such that @@ -129,13 +153,20 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # the comment above this function. nrTriedConjugates := nrTriedConjugates + 1; c := t ^ RandomElm(ri, "SnAnUnknownDegree", true)!.el; - if not isequal(ri)(t * c, c * t) then - nrThreeCycleCandidates := nrThreeCycleCandidates + 1; - return (t * c) ^ 2; - else + if isequal(ri)(t * c, c * t) then # we have to call tryThreeCycleCandidate again return fail; fi; + candidate := (t * c) ^ 2; + # We now use a one-sided heuristic to test whether candidate can be a + # three cycle, that is the heuristic can detect whether candidate can + # not be a three cycle, e.g. if it does not have order three. + nrThreeCycleCandidates := nrThreeCycleCandidates + 1; + if heuristicThreeCycleTest(ri, candidate, logInt2N) then + return candidate; + else + return fail; + fi; end; # construct the iterator oneThreeCycleCandidate := function() @@ -641,32 +672,165 @@ RECOG.StandardGenerators := function(ri, g, c, k, eps, N) fi; end; -# This function is an excerpt of the function RECOG.RecogniseSnAn in gap/SnAnBB.gi +# The following two functions RECOG.FindAnElementMappingIToJ and +# RECOG.FindImageSnAnSmallDegree are used for small degrees, 5 <= n < 11, to +# compute a monomorphism into Sn based on Jonathan Conder's thesis . +# +# In Conder's Thesis: Algorithm 7, ConjugateMap +# Returns an element c such that under the monomorphism Grp(ri) -> S_n given by +# s and t the image of c maps the point i to j. +RECOG.FindAnElementMappingIToJ := function(s, t, i, j) + if i < 3 then + if j < 3 then + return t^(j-i); + else + return t^(3-i)*s^(j-3); + fi; + else + return s^(j-i); + fi; +end; + +# In Conder's Thesis: Algorithm 10, ElementToSmallDegreePermutation, +# correctness see Theorem 3.5.2. +# Returns the image of g under the monomorphism Grp(ri) -> S_n given by s and +# t, for n >= 5. +# Under that monomorphism s is mapped to a long cycle, t to a three cycle, +# and the list e (E in Conder's Thesis) to [(1,2,3), (1,2,4), (1,2,5)]. +# Note that the arguments are in a different order than in the thesis, such +# that they are more consistent with the GAP function FindImageSn. +RECOG.FindImageSnAnSmallDegree := function(ri, n, g, s, t, e) + local T, L, H, i, j, k, l, c, h, h1, h2, h2h1, h1h2Comm, S, m, continueI, continueJ; + T := [ e[1], e[2], e[3], e[1] ^ 2 * e[2], e[1] ^ 2 * e[3], e[2] ^ 2 * e[3], e[1] * e[2] ^ 2, + e[1] * e[3] ^ 2, e[2] * e[3] ^ 2, e[2] * e[3] ^ 2 * e[1] * e[2] ^ 2 ]; + L := []; + # H = [ (1,2,3), (1,4,5) ] + H := [T[1], T[6]]; + for l in [1 .. n] do + for j in [1 .. n] do + continueJ := false; + if j = 1 then + c := One(Grp(ri)); + else + h := RECOG.FindAnElementMappingIToJ(s, t, j - 1, j); + c := c * h; + fi; + + for i in [1 .. Length(H)] do + continueI := false; + h1 := H[i] ^ g; + S := [1, 1]; + for k in [1 .. Length(S)] do + h2 := T[5 * k - 4] ^ c; + + h2h1 := h2 * h1; + if isone(ri)(h2h1) or isone(ri)(h2 * h2h1) then + continueI := true; # continue loop i + break; + fi; + h1h2Comm := Comm(h1, h2); + if isone(ri)(h1h2Comm) then + continueJ := true; # continue loop j + break; + elif isone(ri)(h1h2Comm ^ 2) then + S[k] := 2; + fi; + od; + # Jump to some outer loop. + if continueI then + continue; + elif continueJ then + break; + fi; + + if S[1] = S[2] then + if S[1] = 1 then + for k in [2 .. 5] do + if docommute(ri)(h1, T[k] ^ c) then + continueJ := true; # continue loop j + break; + fi; + od; + fi; + else + if S[1] > S[2] then + m := 6; + else + m := 8; + fi; + for k in [1 .. 2] do + if docommute(ri)(h1, T[m + k] ^ c) then + continueJ := true; # continue loop j + break; + fi; + od; + fi; + # Jump to some outer loop. + if continueJ then + break; + fi; + od; + + if continueJ then + continue; + else + Add(L, j); + break; + fi; + od; + + c := RECOG.FindAnElementMappingIToJ(s, t, l, l + 1); + for i in [1 .. Length(H)] do + H[i] := H[i] ^ c; + od; + od; + + return PermList(L); +end; + +# This function is based on the function RECOG.RecogniseSnAn in gap/SnAnBB.gi # ri : recognition node with group G, # n : degree # stdGensAnWithMemory : standard generators of An < G # -# Returns either fail or a record with components: -# [s, stdGens, xis, n], where: +# Returns either fail or a record with components type and isoData, where: # - type: the isomorphism type, that is either the string "Sn" or "An". -# - isoData: a list [stdGens, xis, n] where +# - isoData: a list [stdGens, filter, n] where # - stdGens are the standard generators of G. They do not have memory. -# - xis implicitly defines the isomorphism. It is used by RECOG.FindImageSn -# and RECOG.FindImageAn to compute the isomorphism. +# - filter implicitly defines the isomorphism. It is used by +# RECOG.FindImageSn, RECOG.FindImageAn, and RECOG.FindImageSnAnSmallDegree +# to compute the isomorphism. # - n is the degree of the group. -# - slpToStdGens: an SLP to stdGens. -# -# TODO: Image Computation requires n >= 11. +# - slpToStdGens: an SLP from the generators of G to stdGens. RECOG.ConstructSnAnIsomorphism := function(ri, n, stdGensAnWithMemory) - local stdGensAn, xis, gImage, foundOddPermutation, slp, eval, + local stdGensAn, filter, filterPart, gImage, foundOddPermutation, slp, eval, hWithMemory, bWithMemory, stdGensSnWithMemory, b, h, g; stdGensAn := StripMemory(stdGensAnWithMemory); - xis := RECOG.ConstructXiAn(n, stdGensAn[1], stdGensAn[2]); + # Construct filter. In filter is called E. + # In filter is called `xi`. + if n < 11 then + filterPart := [stdGensAn[2], (~[1] ^ stdGensAn[1]) ^ (2 * (n mod 2) - 1), + (~[2] ^ stdGensAn[1]) ^ (2 * (n mod 2) - 1)]; + filter := [[stdGensAn[1], stdGensAn[2]], filterPart]; + else + filter := RECOG.ConstructXiAn(n, stdGensAn[1], stdGensAn[2]); + fi; foundOddPermutation := false; + # For each generator, check whether its image und the monomorphism into the + # S_n has odd sign. If so, switch to recognizing S_n. + # For each generator also check whether the SLP for its image in the A_n + # was computed correctly. for g in ri!.gensHmem do - gImage := RECOG.FindImageAn(ri, n, StripMemory(g), - stdGensAn[1], stdGensAn[2], - xis[1], xis[2]); + if n < 11 then + gImage := RECOG.FindImageSnAnSmallDegree(ri, n, StripMemory(g), + filter[1][1], filter[1][2], + filter[2]); + else + gImage := RECOG.FindImageAn(ri, n, StripMemory(g), + stdGensAn[1], stdGensAn[2], + filter[1], filter[2]); + fi; if gImage = fail then return fail; fi; if SignPerm(gImage) = -1 then # we found an odd permutation, @@ -681,7 +845,7 @@ RECOG.ConstructSnAnIsomorphism := function(ri, n, stdGensAnWithMemory) od; if not foundOddPermutation then return rec(type := "An", - isoData := [[stdGensAn[1], stdGensAn[2]], xis, n], + isoData := [[stdGensAn[1], stdGensAn[2]], filter, n], slpToStdGens := SLPOfElms(stdGensAnWithMemory)); fi; # Construct standard generators for Sn: [bWithMemory, hWithMemory]. @@ -700,27 +864,31 @@ RECOG.ConstructSnAnIsomorphism := function(ri, n, stdGensAnWithMemory) if not RECOG.SatisfiesSnPresentation(ri, n, b, h) then return fail; fi; - xis := RECOG.ConstructXiSn(n, b, h); + if n >= 11 then + filter := RECOG.ConstructXiSn(n, b, h); + fi; for g in ri!.gensHmem do - gImage := RECOG.FindImageSn(ri, n, StripMemory(g), - b, h, - xis[1], xis[2]); + if n < 11 then + gImage := RECOG.FindImageSnAnSmallDegree(ri, n, StripMemory(g), + filter[1][1], filter[1][2], + filter[2]); + else + gImage := RECOG.FindImageSn(ri, n, StripMemory(g), + b, h, + filter[1], filter[2]); + fi; if gImage = fail then return fail; fi; slp := RECOG.SLPforSn(n, gImage); eval := ResultOfStraightLineProgram(slp, [h, b]); if not isequal(ri)(eval, StripMemory(g)) then return fail; fi; od; return rec(type := "Sn", - isoData := [[b, h], xis, n], + isoData := [[b, h], filter, n], slpToStdGens := SLPOfElms(stdGensSnWithMemory)); end; # This method is an implementation of . It is the main # function of SnAnUnknownDegree. -# Note that it currently only works for 11 <= n. TODO: make it work with -# smaller n, that is include fixes from Jonathan Conder's B.Sc. -# Thesis "Algorithms for Permutation Groups", see PR #265. -# # From : # RECOG.RecogniseSnAn is a one-sided Monte-Carlo algorithm with the following # properties. It takes as input a black-box group G, a natural number @@ -738,12 +906,6 @@ RECOG.RecogniseSnAn := function(ri, eps, N) c := iterator(); while c <> fail do if c = NeverApplicable then return NeverApplicable; fi; - # This is a very cheap test to determine - # if our candidate c could be a three cycle. - if not isone(ri)(StripMemory(c) ^ 3) then - c := iterator(); - continue; - fi; tmp := RECOG.ConstructLongCycle(ri, c, 1. / 8., N); if tmp = fail then c := iterator(); @@ -767,21 +929,85 @@ RECOG.RecogniseSnAn := function(ri, eps, N) return TemporaryFailure; end; + +#! @BeginChunk SnAnSmallUnknownDegree +#! This method checks element orders to rule out whether the input group might +#! be A_5, S_5, A_6, S_6, or Aut(A_6). If it wasn't able to rule that out it +#! tries to find a small orbit, which must exist if the input group is +#! isomorphic to one of the groups in question. +#! This method only takes matrix or projective inputs. +#! @EndChunk SnAnSmallUnknownDegree +BindRecogMethod(FindHomMethodsGeneric, "SnAnSmallUnknownDegree", +"find small orbit if the group might be A_5, S_5, A_6, S_6, or Aut(A_6)", +function(ri, G) + local orders, orbit; + orders := Set(List( + [1..30], + i -> RandomElmOrd(ri, "SnAnSmallUnknownDegree", false).order + )); + # Orders which are not in Aut(A_6): + if ForAny(orders, x -> x >= 11) or 7 in orders or 9 in orders then + return NeverApplicable; + fi; + orbit := Orb(G, One(G)[1], OnPoints, rec(storenumbers := true)); + Enumerate(orbit, 1441); + if Length(orbit) <= 1440 then + # Our reduction is that we found a smallish orbit. + SetHomom(ri, OrbActionHomomorphism(G, orbit)); + Setmethodsforimage(ri, FindHomDbPerm); + return Success; + fi; + return NeverApplicable; +end); + +RECOG.LowerBoundForDegreeOfSnAnViaOrders := function(ri) + local G, orders; + G := Grp(ri); + orders := Set(List( + [1..30], + i -> RandomElmOrd(ri, "LowerBoundForDegreeOfSnAnViaOrders", false).order + )); + return Maximum(List( + orders, + # For a given order, the sum over its factorisation into prime powers, + # gives a lower bound for the degree of the smallest symmetric group, + # that can contain an element of such an order. + o -> Sum(Collected(FactorsInt(o)), + tup -> tup[1] ^ tup[2]) + )); +end; + #! @BeginChunk SnAnUnknownDegree #! This method tries to determine whether the input group given by ri is #! isomorphic to a symmetric group Sn or alternating group An with -#! 11 \leq n. +#! 9 \leq n. #! It is an implementation of . #! #! If Grp(ri) is a permutation group, we assume that it is primitive and #! not a giant (a giant is Sn or An in natural action). #! +#! This method can also recognise a symmetric group Sn or alternating group An with +#! n = 7 or n = 8, but is not required to return a result with +#! the specified error probability. +#! +#! This method cannot recognise a symmetric group Sn or alternating group An with +#! n = 5 or n = 6, since it uses pre-bolstering elements, +#! which need at least 7 moved points. +#! If the input group is isomorphic to a symmetric or alternating group of +#! degrees 5 or 6, then this method might not exit quickly. Thus it is +#! recommended to first call the method +#! . +#! #! @EndChunk BindRecogMethod(FindHomMethodsGeneric, "SnAnUnknownDegree", -"method groups isomorphic to Sn or An with n >= 11", +"method groups isomorphic to Sn or An with n >= 9", function(ri, G) local eps, N, p, d, recogData, isoData, degree, swapSLP; - #G := Grp(ri); + if IsBound(ri!.fhmethsel) and not "SnAnSmallUnknownDegree" + in NamesOfComponents(ri!.fhmethsel.inapplicableMethods) then + ErrorNoReturn("If called via CallMethods then SnAnSmallUnknownDegree must be tried before ", + "SnAnUnknownDegree."); + fi; # TODO find value for eps eps := 1 / 10^2; # Check magma @@ -826,6 +1052,10 @@ function(ri, G) " , Grp() must be an IsPermGroup or an", " IsMatrixGroup"); fi; + # Our upper bound is smaller than our lower bound. + if N < RECOG.LowerBoundForDegreeOfSnAnViaOrders(ri) then + return NeverApplicable; + fi; # Try to find an isomorphism recogData := RECOG.RecogniseSnAn(ri, eps, N); # RECOG.RecogniseSnAn returned NeverApplicable or TemporaryFailure @@ -851,13 +1081,41 @@ function(ri, G) CompositionOfStraightLinePrograms(swapSLP, recogData.slpToStdGens)); if recogData.type = "Sn" then - Setslpforelement(ri, SLPforElementFuncsGeneric.SnUnknownDegree); + if degree < 11 then + Setslpforelement(ri, SLPforElementFuncsGeneric.SnSmallDegree); + else + Setslpforelement(ri, SLPforElementFuncsGeneric.SnUnknownDegree); + fi; else - Setslpforelement(ri, SLPforElementFuncsGeneric.AnUnknownDegree); + if degree < 11 then + Setslpforelement(ri, SLPforElementFuncsGeneric.AnSmallDegree); + else + Setslpforelement(ri, SLPforElementFuncsGeneric.AnUnknownDegree); + fi; fi; return Success; end); +# The SLP function if G is isomorphic to Sn with small degree. +SLPforElementFuncsGeneric.SnSmallDegree := function(ri, g) + local isoData, degree, image; + isoData := ri!.SnAnUnknownDegreeIsoData; + degree := isoData[3]; + image := RECOG.FindImageSnAnSmallDegree(ri, degree, g, isoData[2][1][1], isoData[2][1][2], + isoData[2][2]); + return RECOG.SLPforSn(degree, image); +end; + +# The SLP function if G is isomorphic to An with small degree. +SLPforElementFuncsGeneric.AnSmallDegree := function(ri, g) + local isoData, degree, image; + isoData := ri!.SnAnUnknownDegreeIsoData; + degree := isoData[3]; + image := RECOG.FindImageSnAnSmallDegree(ri, degree, g, isoData[2][1][1], isoData[2][1][2], + isoData[2][2]); + return RECOG.SLPforAn(degree, image); +end; + # The SLP function if G is isomorphic to Sn. SLPforElementFuncsGeneric.SnUnknownDegree := function(ri, g) local isoData, degree, image; diff --git a/gap/projective.gi b/gap/projective.gi index d46910c3..b3712d9a 100644 --- a/gap/projective.gi +++ b/gap/projective.gi @@ -311,6 +311,10 @@ AddMethod(FindHomDbProjective, FindHomMethodsMatrix.ReducibleIso, 1200); AddMethod(FindHomDbProjective, FindHomMethodsProjective.NotAbsolutelyIrred, 1100); +AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnSmallUnknownDegree, 1075); + +AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnUnknownDegree, 1070); + AddMethod(FindHomDbProjective, FindHomMethodsProjective.ClassicalNatural, 1050); AddMethod(FindHomDbProjective, FindHomMethodsProjective.Subfield, 1000); diff --git a/tst/working/quick/GenericSnAnUnknownDegree.tst b/tst/working/quick/GenericSnAnUnknownDegree.tst index 3848d74b..ed144103 100644 --- a/tst/working/quick/GenericSnAnUnknownDegree.tst +++ b/tst/working/quick/GenericSnAnUnknownDegree.tst @@ -5,6 +5,72 @@ gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 58);; gap> AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.SnAnUnknownDegree, 1070);; gap> AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnUnknownDegree, 1220);; +# For each entry (d, k) we construct Sym(d)/Alt(d) acting on k-sets. +# For each entry (d, k), we must have 2 * k ^ 2 > d, +# otherwise LargeBasePrimitive recognises the group instead of SnAnUnknownDegree. +gap> data := [[7, 2], [8, 3], [9, 3], [10, 3], [11, 3], [12, 3], [13, 3]];; + +# TODO: make sure we really use everything +# SymmetricGroup action on k-sets +gap> SymOnKSets := function(d, k) +> local sets; +> sets := Combinations([1 .. d], k);; +> return Action(SymmetricGroup(d), sets, OnSets);; +> end;; + +# +# AlternatingGroup action on 2-sets +gap> AltOnKSets := function(d, k) +> local sets; +> sets := Combinations([1 .. d], k);; +> return Action(AlternatingGroup(d), sets, OnSets);; +> end;; + +# +gap> altGroups := List(data, entry -> AltOnKSets(entry[1], entry[2]));; +gap> symGroups := List(data, entry -> SymOnKSets(entry[1], entry[2]));; +gap> permMatGroup := function(G, q) return Group(List( +> GeneratorsOfGroup(G), +> x -> ImmutableMatrix(q, PermutationMat(x, NrMovedPoints(G), GF(q))) +> )); end;; +gap> altMatGroups := [ +> permMatGroup(AlternatingGroup(11), 7), +> #permMatGroup(AlternatingGroup(5), 7^3), +> ];; +gap> nonAltOrSymGroups := [ +> DihedralGroup(IsPermGroup, 10), +> #DihedralGroup(IsPcGroup, 10), +> #DihedralGroup(IsPermGroup, 2000), +> #DihedralGroup(IsPcGroup, 2000), +> PSL(3, 5), +> SL(3, 5), +> Omega(-1, 4, 5), +> Omega(-1, 4, 3), +> Omega(+1, 4, 5), +> Omega(+1, 4, 3), +> Omega(+1, 8, 5), +> Omega(+1, 8, 3), +> Omega(0, 5, 5), +> Omega(0, 5, 3), +> ];; + +# Test +gap> for i in [1 .. Length(data)] do +> RECOG.TestGroup(altGroups[i], false, Factorial(data[i, 1])/2, rec(), "SnAnUnknownDegree"); +> RECOG.TestGroup(symGroups[i], false, Factorial(data[i, 1]), rec(), "SnAnUnknownDegree"); +> od; +gap> for i in [1 .. Length(altMatGroups)] do +> RECOG.TestGroup(altMatGroups[i], false, Size(altMatGroups[i])); +> od; +gap> for i in [1 .. Length(altMatGroups)] do +> RECOG.TestGroup(altMatGroups[i], true, Size(altMatGroups[i])); +> od; +gap> for i in [1 .. Length(nonAltOrSymGroups)] do +> if FindHomMethodsGeneric.SnAnUnknownDegree(RecogNode(nonAltOrSymGroups[i]), nonAltOrSymGroups[i]) = Success then +> Print("ERROR: Recognised group [", i, "] wrongly as Sn/An!\n"); +> fi; +> od; + # FindHomMethodsGeneric.SnAnUnknownDegree # Sn gap> for d in [11] do From 6a96a7a7780c2f2766cdca6882944891e373bcaa Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Wed, 1 Jun 2022 14:28:49 +0200 Subject: [PATCH 05/25] SnAnUnknownDegree: rename internal functions - heuristicThreeCycleTest -> RECOG.HeuristicThreeCycleTest - RECOG.StandardGenerators -> RECOG.SnAnStandardGenerators --- gap/generic/SnAnUnknownDegree.gi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 39a93148..373fb567 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -17,7 +17,7 @@ ## . ## ############################################################################# -# + # eps : real number, the error bound # N : integer, upper bound for the degree of G # @@ -43,7 +43,7 @@ end; # Return false if c can not be a three cycle. This uses cheap tests to # determine this. It is based on the Magma function heuristicThreeCycleTest. -heuristicThreeCycleTest := function(ri, c, logInt2N) +RECOG.HeuristicThreeCycleTest := function(ri, c, logInt2N) local r, y, yTo5, k; c := StripMemory(c); if not isone(ri)(c ^ 3) then @@ -162,7 +162,7 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # three cycle, that is the heuristic can detect whether candidate can # not be a three cycle, e.g. if it does not have order three. nrThreeCycleCandidates := nrThreeCycleCandidates + 1; - if heuristicThreeCycleTest(ri, candidate, logInt2N) then + if RECOG.HeuristicThreeCycleTest(ri, candidate, logInt2N) then return candidate; else return fail; @@ -635,7 +635,7 @@ end; # 3-cycle, a standard generator of An < G # - kTilde : integer, # degree of group An < G, that is generated by gTilde and cTilde -RECOG.StandardGenerators := function(ri, g, c, k, eps, N) +RECOG.SnAnStandardGenerators := function(ri, g, c, k, eps, N) local s, k0, c2, r, kTilde, gTilde, i, x, m, tmp, cTilde; s := One(g); k0 := k - 2; @@ -914,7 +914,7 @@ RECOG.RecogniseSnAn := function(ri, eps, N) # Now tmp contains [g, k] where # g corresponds to a long cycle # k is its length - tmp := RECOG.StandardGenerators(ri, tmp[1], c, tmp[2], 1. / 8., N); + tmp := RECOG.SnAnStandardGenerators(ri, tmp[1], c, tmp[2], 1. / 8., N); if tmp = fail then c := iterator(); continue; From 206133689d75bdc4e0b99de7091784a77b5adf28 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Wed, 1 Jun 2022 14:32:37 +0200 Subject: [PATCH 06/25] SnAnUnknownDegree: Add MD file describing implementation --- RECOG_SnAn | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 RECOG_SnAn diff --git a/RECOG_SnAn b/RECOG_SnAn new file mode 100644 index 00000000..76ffbcbe --- /dev/null +++ b/RECOG_SnAn @@ -0,0 +1,31 @@ +# SnAnUnknownDegree + +This file describes details of the `SnAnUnknownDegree` implementation in `recog`. The algorithm is based on the paper `[JLNP13]`. + +## General Strategy +Here we describe the general strategy that is used to recognise if a group is ismorphic to `An` or `Sn` for `n > 5`. + +We have to be careful what we do for small degrees. +If we pass an `A5`, `S5`, `A6`, `S6` into `SnAnUnknownDegree`, then it will not recognize it. +If the input acts on a space with a large dimension, then this can take forever. + +- We assume that our input group `G` is a projective irreducible matrix group. +- First try to enumerate a single orbit with upper bound on its order `1440`. + - If we have found an orbit of size at most `1440`, we compute the action homomorphism induced by this orbit. + - Otherwise, if we find more than `1440` elements in an orbit, then we excluded `A5`, `S5`, `A6`, `S6`, `Aut(A6)` and continue. +- Now we can assume for the degree `n >= 7`. We need to ensure this, since `SnAnUnknownDegree` cannot recognize these groups and could run for a considerable time. +- Deduce an upper bound `N` for the degree of `An`, `Sn`. +- Look at some orders and deduce a lower bound for the degree. + - If lower bound > upper bound, then we excluded `An`, `Sn`. + - Otherwise, we continue. +- Monte-Carlo: Try to compute standard generators and degree `n` of largest `An < G` via the algorithm in `[JLNP13]`. +- Try to compute an isomorphism from `G` to `An` or `Sn`. + - If `n < 11`, then use methods from Conder + - Otherwise, we have `n >= 11` and use methods from `SnAnKnownDegree` in `[BLGN+03]` + +## Changes +Here we collect the changes in `SnAnUnknownDegree` compared to the "vanilla" implementation of the algorithm according to the paper `[JLNP13]`. + +- Computation of upper bound `N` done as in `[L84]` and `[KL90]` +- For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for some random elements `r` if `c * c^r` has order `1`, `2`, `3` or `5`. +- Use Conder's Thesis to compute images for degree `n <= 10` From 229ad3980053f91c6899ab627cdc8ab03c7c3260 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Wed, 1 Jun 2022 15:26:09 +0200 Subject: [PATCH 07/25] SnAnUnknownDegree: clean up tests --- .../quick/GenericSnAnUnknownDegree.tst | 119 +++++++----------- 1 file changed, 42 insertions(+), 77 deletions(-) diff --git a/tst/working/quick/GenericSnAnUnknownDegree.tst b/tst/working/quick/GenericSnAnUnknownDegree.tst index ed144103..2d320d20 100644 --- a/tst/working/quick/GenericSnAnUnknownDegree.tst +++ b/tst/working/quick/GenericSnAnUnknownDegree.tst @@ -1,111 +1,76 @@ -#@local d, sets, SdOn2Sets, ri, success, x, slp, db # # HACK to insert the method -gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 58);; -gap> AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.SnAnUnknownDegree, 1070);; -gap> AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnUnknownDegree, 1220);; +gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnSmallUnknownDegree, 58);; +gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 57);; # For each entry (d, k) we construct Sym(d)/Alt(d) acting on k-sets. # For each entry (d, k), we must have 2 * k ^ 2 > d, # otherwise LargeBasePrimitive recognises the group instead of SnAnUnknownDegree. -gap> data := [[7, 2], [8, 3], [9, 3], [10, 3], [11, 3], [12, 3], [13, 3]];; +gap> dataPerm := [[7, 2], [8, 3], [9, 3], [10, 3], [11, 3], [12, 3], [13, 3]];; -# TODO: make sure we really use everything -# SymmetricGroup action on k-sets -gap> SymOnKSets := function(d, k) +# +# PermGroup action on k-sets +gap> PermOnKSets := function(G, k) > local sets; -> sets := Combinations([1 .. d], k);; -> return Action(SymmetricGroup(d), sets, OnSets);; +> sets := Combinations([1 .. NrMovedPoints(G)], k);; +> return Action(G, sets, OnSets);; > end;; - -# -# AlternatingGroup action on 2-sets gap> AltOnKSets := function(d, k) -> local sets; -> sets := Combinations([1 .. d], k);; -> return Action(AlternatingGroup(d), sets, OnSets);; +> return PermOnKSets(AlternatingGroup(d), k); +> end;; +gap> SymOnKSets := function(d, k) +> return PermOnKSets(SymmetricGroup(d), k); > end;; # -gap> altGroups := List(data, entry -> AltOnKSets(entry[1], entry[2]));; -gap> symGroups := List(data, entry -> SymOnKSets(entry[1], entry[2]));; -gap> permMatGroup := function(G, q) return Group(List( +gap> altPermGroups := List(dataPerm, entry -> AltOnKSets(entry[1], entry[2]));; +gap> symPermGroups := List(dataPerm, entry -> SymOnKSets(entry[1], entry[2]));; + +# +gap> dataMat := [[7, 3], [8, 5], [11, 7]];; + +# +# Permutation Matrix Group +gap> PermMatGroup := function(G, q) return Group(List( > GeneratorsOfGroup(G), > x -> ImmutableMatrix(q, PermutationMat(x, NrMovedPoints(G), GF(q))) > )); end;; -gap> altMatGroups := [ -> permMatGroup(AlternatingGroup(11), 7), -> #permMatGroup(AlternatingGroup(5), 7^3), -> ];; +gap> AltMatGroup := function(n, q) return PermMatGroup(AlternatingGroup(n), q); +> end;; +gap> SymMatGroup := function(n, q) return PermMatGroup(SymmetricGroup(n), q); +> end;; + +# +gap> altMatGroups := List(dataMat, entry -> AltMatGroup(entry[1], entry[2]));; +gap> symMatGroups := List(dataMat, entry -> SymMatGroup(entry[1], entry[2]));; + +# gap> nonAltOrSymGroups := [ -> DihedralGroup(IsPermGroup, 10), -> #DihedralGroup(IsPcGroup, 10), -> #DihedralGroup(IsPermGroup, 2000), -> #DihedralGroup(IsPcGroup, 2000), > PSL(3, 5), > SL(3, 5), -> Omega(-1, 4, 5), -> Omega(-1, 4, 3), -> Omega(+1, 4, 5), -> Omega(+1, 4, 3), -> Omega(+1, 8, 5), -> Omega(+1, 8, 3), -> Omega(0, 5, 5), -> Omega(0, 5, 3), > ];; # Test -gap> for i in [1 .. Length(data)] do -> RECOG.TestGroup(altGroups[i], false, Factorial(data[i, 1])/2, rec(), "SnAnUnknownDegree"); -> RECOG.TestGroup(symGroups[i], false, Factorial(data[i, 1]), rec(), "SnAnUnknownDegree"); -> od; -gap> for i in [1 .. Length(altMatGroups)] do -> RECOG.TestGroup(altMatGroups[i], false, Size(altMatGroups[i])); +gap> for i in [1 .. Length(dataPerm)] do +> RECOG.TestGroup(altPermGroups[i], false, Factorial(dataPerm[i, 1])/2, rec()); +> RECOG.TestGroup(symPermGroups[i], false, Factorial(dataPerm[i, 1]), rec()); > od; -gap> for i in [1 .. Length(altMatGroups)] do -> RECOG.TestGroup(altMatGroups[i], true, Size(altMatGroups[i])); +gap> for i in [1 .. Length(dataMat)] do +> RECOG.TestGroup(altMatGroups[i], false, Factorial(dataMat[i, 1])/2, rec()); +> RECOG.TestGroup(symMatGroups[i], false, Factorial(dataMat[i, 1]), rec()); > od; gap> for i in [1 .. Length(nonAltOrSymGroups)] do -> if FindHomMethodsGeneric.SnAnUnknownDegree(RecogNode(nonAltOrSymGroups[i]), nonAltOrSymGroups[i]) = Success then +> ri := RecogNode(nonAltOrSymGroups[i]); +> if FindHomMethodsGeneric.SnAnUnknownDegree(ri, nonAltOrSymGroups[i]) = Success then > Print("ERROR: Recognised group [", i, "] wrongly as Sn/An!\n"); > fi; > od; -# FindHomMethodsGeneric.SnAnUnknownDegree -# Sn -gap> for d in [11] do -> sets := Combinations([1 .. d], 2);; -> SdOn2Sets := Action(SymmetricGroup(d), sets, OnSets);; -> ri := RecogNode(SdOn2Sets);; -> success := FindHomMethodsGeneric.SnAnUnknownDegree(ri, SdOn2Sets); -> if not success or not Size(ri) = Factorial(d) then -> Print("wrong result! degree ", d, "\n"); -> fi; -> od; - -# An -gap> for d in [11] do -> sets := Combinations([1 .. d], 2);; -> SdOn2Sets := Action(AlternatingGroup(d), sets, OnSets);; -> ri := RecogNode(SdOn2Sets);; -> success := FindHomMethodsGeneric.SnAnUnknownDegree(ri, SdOn2Sets); -> if not success or not Size(ri) = Factorial(d)/2 then -> Print("wrong result! degree ", d, "\n"); -> fi; -> od; - -# Check Slp function -gap> ri := RecogNode(SdOn2Sets);; -gap> FindHomMethodsGeneric.SnAnUnknownDegree(ri, SdOn2Sets); -true -gap> x := PseudoRandom(Grp(ri));; -gap> slp := SLPforElement(ri, x);; -gap> x = ResultOfStraightLineProgram(slp, NiceGens(ri)); -true - # # Remove Hacky injection of our method -gap> for db in [FindHomDbPerm, FindHomDbMatrix, FindHomDbProjective] do +gap> for db in [FindHomDbPerm] do +> Remove(db, +> PositionProperty(db, x -> Stamp(x.method) = "SnAnSmallUnknownDegree"));; > Remove(db, > PositionProperty(db, x -> Stamp(x.method) = "SnAnUnknownDegree"));; > od; From e3ac7e3a9c4dc33460caba706fea4cce19444286 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Wed, 1 Jun 2022 16:40:09 +0200 Subject: [PATCH 08/25] SnAnUnknownDegree: remove `SnAnSmallUnknownDegree`. Instead, use the lower bound `M` to ensure that we have a degree `n >= 7`. --- RECOG_SnAn | 20 ++--- gap/generic/SnAnUnknownDegree.gi | 87 ++++++++++--------- gap/projective.gi | 2 +- .../quick/GenericSnAnUnknownDegree.tst | 12 ++- 4 files changed, 62 insertions(+), 59 deletions(-) diff --git a/RECOG_SnAn b/RECOG_SnAn index 76ffbcbe..3b486fdd 100644 --- a/RECOG_SnAn +++ b/RECOG_SnAn @@ -3,21 +3,20 @@ This file describes details of the `SnAnUnknownDegree` implementation in `recog`. The algorithm is based on the paper `[JLNP13]`. ## General Strategy -Here we describe the general strategy that is used to recognise if a group is ismorphic to `An` or `Sn` for `n > 5`. +Here we describe the general strategy that is used to recognise if a group is ismorphic to `An` or `Sn` for `n >= 7`. We have to be careful what we do for small degrees. If we pass an `A5`, `S5`, `A6`, `S6` into `SnAnUnknownDegree`, then it will not recognize it. If the input acts on a space with a large dimension, then this can take forever. - We assume that our input group `G` is a projective irreducible matrix group. -- First try to enumerate a single orbit with upper bound on its order `1440`. - - If we have found an orbit of size at most `1440`, we compute the action homomorphism induced by this orbit. - - Otherwise, if we find more than `1440` elements in an orbit, then we excluded `A5`, `S5`, `A6`, `S6`, `Aut(A6)` and continue. -- Now we can assume for the degree `n >= 7`. We need to ensure this, since `SnAnUnknownDegree` cannot recognize these groups and could run for a considerable time. - Deduce an upper bound `N` for the degree of `An`, `Sn`. -- Look at some orders and deduce a lower bound for the degree. - - If lower bound > upper bound, then we excluded `An`, `Sn`. +- Look at some orders and deduce a lower bound `M` for the degree. + - If `M > N`, then we excluded `An`, `Sn`. + - If `M <= 6`, then we return `TemporaryFailure`. - Otherwise, we continue. +- Now we can assume for the degree `n >= 7`. We need to ensure this, since `SnAnUnknownDegree` cannot recognise `A5`, `S5`, `A6`, `S6` + and could run for a considerable time. - Monte-Carlo: Try to compute standard generators and degree `n` of largest `An < G` via the algorithm in `[JLNP13]`. - Try to compute an isomorphism from `G` to `An` or `Sn`. - If `n < 11`, then use methods from Conder @@ -26,6 +25,7 @@ If the input acts on a space with a large dimension, then this can take forever. ## Changes Here we collect the changes in `SnAnUnknownDegree` compared to the "vanilla" implementation of the algorithm according to the paper `[JLNP13]`. -- Computation of upper bound `N` done as in `[L84]` and `[KL90]` -- For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for some random elements `r` if `c * c^r` has order `1`, `2`, `3` or `5`. -- Use Conder's Thesis to compute images for degree `n <= 10` +- Computation of upper bound `N` done as in `[L84]` and `[KL90]`. +- Computation of lower bound `M` by element orders. +- For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for some random elements `r` if for the element `x := c * c^r` either `x^5 = 1` or `x^6 = 1` holds (The element `x` needs to have order `1`, `2`, `3` or `5`). +- Use Conder's Thesis to compute images for degree `n <= 10`. diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 373fb567..d2fe6033 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -929,36 +929,35 @@ RECOG.RecogniseSnAn := function(ri, eps, N) return TemporaryFailure; end; - -#! @BeginChunk SnAnSmallUnknownDegree -#! This method checks element orders to rule out whether the input group might -#! be A_5, S_5, A_6, S_6, or Aut(A_6). If it wasn't able to rule that out it -#! tries to find a small orbit, which must exist if the input group is -#! isomorphic to one of the groups in question. -#! This method only takes matrix or projective inputs. -#! @EndChunk SnAnSmallUnknownDegree -BindRecogMethod(FindHomMethodsGeneric, "SnAnSmallUnknownDegree", -"find small orbit if the group might be A_5, S_5, A_6, S_6, or Aut(A_6)", -function(ri, G) - local orders, orbit; - orders := Set(List( - [1..30], - i -> RandomElmOrd(ri, "SnAnSmallUnknownDegree", false).order - )); - # Orders which are not in Aut(A_6): - if ForAny(orders, x -> x >= 11) or 7 in orders or 9 in orders then - return NeverApplicable; - fi; - orbit := Orb(G, One(G)[1], OnPoints, rec(storenumbers := true)); - Enumerate(orbit, 1441); - if Length(orbit) <= 1440 then - # Our reduction is that we found a smallish orbit. - SetHomom(ri, OrbActionHomomorphism(G, orbit)); - Setmethodsforimage(ri, FindHomDbPerm); - return Success; - fi; - return NeverApplicable; -end); +# #! @BeginChunk SnAnSmallUnknownDegree +# #! This method checks element orders to rule out whether the input group might +# #! be A_5, S_5, A_6, S_6, or Aut(A_6). If it wasn't able to rule that out it +# #! tries to find a small orbit, which must exist if the input group is +# #! isomorphic to one of the groups in question. +# #! This method only takes matrix or projective inputs. +# #! @EndChunk SnAnSmallUnknownDegree +# BindRecogMethod(FindHomMethodsGeneric, "SnAnSmallUnknownDegree", +# "find small orbit if the group might be A_5, S_5, A_6, S_6, or Aut(A_6)", +# function(ri, G) +# local orders, orbit; +# orders := Set(List( +# [1..30], +# i -> RandomElmOrd(ri, "SnAnSmallUnknownDegree", false).order +# )); +# # Orders which are not in Aut(A_6): +# if ForAny(orders, x -> x >= 11) or 7 in orders or 9 in orders then +# return NeverApplicable; +# fi; +# orbit := Orb(G, One(G)[1], OnPoints, rec(storenumbers := true)); +# Enumerate(orbit, 1441); +# if Length(orbit) <= 1440 then +# # Our reduction is that we found a smallish orbit. +# SetHomom(ri, OrbActionHomomorphism(G, orbit)); +# Setmethodsforimage(ri, FindHomDbPerm); +# return Success; +# fi; +# return NeverApplicable; +# end); RECOG.LowerBoundForDegreeOfSnAnViaOrders := function(ri) local G, orders; @@ -994,22 +993,20 @@ end; #! n = 5 or n = 6, since it uses pre-bolstering elements, #! which need at least 7 moved points. #! If the input group is isomorphic to a symmetric or alternating group of -#! degrees 5 or 6, then this method might not exit quickly. Thus it is -#! recommended to first call the method -#! . +#! degrees 5 or 6, then this method might not exit quickly. #! #! @EndChunk BindRecogMethod(FindHomMethodsGeneric, "SnAnUnknownDegree", "method groups isomorphic to Sn or An with n >= 9", function(ri, G) - local eps, N, p, d, recogData, isoData, degree, swapSLP; - if IsBound(ri!.fhmethsel) and not "SnAnSmallUnknownDegree" - in NamesOfComponents(ri!.fhmethsel.inapplicableMethods) then - ErrorNoReturn("If called via CallMethods then SnAnSmallUnknownDegree must be tried before ", - "SnAnUnknownDegree."); - fi; - # TODO find value for eps + local eps, N, M, p, d, recogData, isoData, degree, swapSLP; + # if IsBound(ri!.fhmethsel) and not "SnAnSmallUnknownDegree" + # in NamesOfComponents(ri!.fhmethsel.inapplicableMethods) then + # ErrorNoReturn("If called via CallMethods then SnAnSmallUnknownDegree must be tried before ", + # "SnAnUnknownDegree."); + # fi; eps := 1 / 10^2; + # N = upper bound for degree # Check magma if IsPermGroup(G) then # We assume that G is primitive and not a giant. @@ -1052,10 +1049,18 @@ function(ri, G) " , Grp() must be an IsPermGroup or an", " IsMatrixGroup"); fi; + # N = lower bound for degree + M := RECOG.LowerBoundForDegreeOfSnAnViaOrders(ri); # Our upper bound is smaller than our lower bound. - if N < RECOG.LowerBoundForDegreeOfSnAnViaOrders(ri) then + if N < M then return NeverApplicable; fi; + # Lower bound does not exclude A5, S5, A6 or S6 + # If the input group is isomorphic to a symmetric or alternating group of + # degrees 5 or 6, then this method might not exit quickly. + if M <= 6 then + return TemporaryFailure; + fi; # Try to find an isomorphism recogData := RECOG.RecogniseSnAn(ri, eps, N); # RECOG.RecogniseSnAn returned NeverApplicable or TemporaryFailure diff --git a/gap/projective.gi b/gap/projective.gi index b3712d9a..1d7d9c51 100644 --- a/gap/projective.gi +++ b/gap/projective.gi @@ -311,7 +311,7 @@ AddMethod(FindHomDbProjective, FindHomMethodsMatrix.ReducibleIso, 1200); AddMethod(FindHomDbProjective, FindHomMethodsProjective.NotAbsolutelyIrred, 1100); -AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnSmallUnknownDegree, 1075); +# AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnSmallUnknownDegree, 1075); AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnUnknownDegree, 1070); diff --git a/tst/working/quick/GenericSnAnUnknownDegree.tst b/tst/working/quick/GenericSnAnUnknownDegree.tst index 2d320d20..71931335 100644 --- a/tst/working/quick/GenericSnAnUnknownDegree.tst +++ b/tst/working/quick/GenericSnAnUnknownDegree.tst @@ -1,12 +1,11 @@ # # HACK to insert the method -gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnSmallUnknownDegree, 58);; -gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 57);; +gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 58);; # For each entry (d, k) we construct Sym(d)/Alt(d) acting on k-sets. # For each entry (d, k), we must have 2 * k ^ 2 > d, # otherwise LargeBasePrimitive recognises the group instead of SnAnUnknownDegree. -gap> dataPerm := [[7, 2], [8, 3], [9, 3], [10, 3], [11, 3], [12, 3], [13, 3]];; +gap> dataPerm := [[5, 2], [7, 2], [8, 3], [9, 3], [10, 3], [11, 3], [12, 3], [13, 3]];; # # PermGroup action on k-sets @@ -27,7 +26,7 @@ gap> altPermGroups := List(dataPerm, entry -> AltOnKSets(entry[1], entry[2]));; gap> symPermGroups := List(dataPerm, entry -> SymOnKSets(entry[1], entry[2]));; # -gap> dataMat := [[7, 3], [8, 5], [11, 7]];; +gap> dataMat := [[5, 4], [7, 3], [8, 5], [11, 7]];; # # Permutation Matrix Group @@ -48,6 +47,7 @@ gap> symMatGroups := List(dataMat, entry -> SymMatGroup(entry[1], entry[2]));; gap> nonAltOrSymGroups := [ > PSL(3, 5), > SL(3, 5), +> Omega(1, 4, 3), > ];; # Test @@ -61,7 +61,7 @@ gap> for i in [1 .. Length(dataMat)] do > od; gap> for i in [1 .. Length(nonAltOrSymGroups)] do > ri := RecogNode(nonAltOrSymGroups[i]); -> if FindHomMethodsGeneric.SnAnUnknownDegree(ri, nonAltOrSymGroups[i]) = Success then +> if FindHomMethodsGeneric.SnAnUnknownDegree(ri, Grp(ri)) = Success then > Print("ERROR: Recognised group [", i, "] wrongly as Sn/An!\n"); > fi; > od; @@ -70,7 +70,5 @@ gap> for i in [1 .. Length(nonAltOrSymGroups)] do # Remove Hacky injection of our method gap> for db in [FindHomDbPerm] do > Remove(db, -> PositionProperty(db, x -> Stamp(x.method) = "SnAnSmallUnknownDegree"));; -> Remove(db, > PositionProperty(db, x -> Stamp(x.method) = "SnAnUnknownDegree"));; > od; From f34d73588e9e1e0b3ee5b10d3573b97bfcac6255 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Thu, 2 Jun 2022 11:52:23 +0200 Subject: [PATCH 09/25] SnAnUnknownDegree: Refactor tests. - Delete `slow/GenericSnAnUnknownDegree.tst`. - Move tests for helper functions to `quick/GenericSnAnUnknownDegreeHelpers.tst`. --- .../quick/GenericSnAnUnknownDegreeHelpers.tst | 103 ++++++ tst/working/slow/GenericSnAnUnknownDegree.tst | 305 ------------------ 2 files changed, 103 insertions(+), 305 deletions(-) create mode 100644 tst/working/quick/GenericSnAnUnknownDegreeHelpers.tst delete mode 100644 tst/working/slow/GenericSnAnUnknownDegree.tst diff --git a/tst/working/quick/GenericSnAnUnknownDegreeHelpers.tst b/tst/working/quick/GenericSnAnUnknownDegreeHelpers.tst new file mode 100644 index 00000000..0d3360f8 --- /dev/null +++ b/tst/working/quick/GenericSnAnUnknownDegreeHelpers.tst @@ -0,0 +1,103 @@ +# RECOG.IsFixedPoint +gap> ri := RecogNode(SymmetricGroup(10));; +gap> g := (1,2,3,4,5,6,7,8);; +gap> c := (1,2,3);; +gap> r := (1,2)(4,5,6);; +gap> RECOG.IsFixedPoint(ri, g,c,r); +true +gap> r := (2,3,4);; +gap> RECOG.IsFixedPoint(ri, g,c,r); +false + +# RECOG.AdjustCycle +gap> ri := RecogNode(SymmetricGroup(10));; +gap> g := (1,2,3,4,5,6,7,8);; +gap> c := (1,2,3);; +gap> r := (4,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,5) +gap> r := (3,4,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,4,5) +gap> r := (2,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,5) +gap> r := (2,4,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,5,4) +gap> r := (2,3,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,4,5) +gap> r := (1,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,5) +gap> r := (1,4,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,5,4) +gap> r := (1,3,4,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,6,4,5) +gap> r := (1,2,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,5,4) +gap> r := (1,2,3,5);; +gap> RECOG.AdjustCycle(ri, g, c, r, 8); +(3,5,4,6) + +# RECOG.BuildCycle +gap> ri := RecogNode(SymmetricGroup(10));; + +# c = (u,v,w) +gap> c := (1,2,3);; + +# Form 1: x = (v,a_1,...,a_alpha) * (w,b_1,....,b_beta) * (...) +# alpha - beta = 0 +gap> x := (2,4,5,6)* (3,7,8,9);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,9), 9 ] + +# alpha - beta = -1 +gap> x := (2,4,5,6)* (3,7,8,9,10);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,9), 9 ] + +# alpha - beta = 1 +gap> x := (2,4,5,6,10)* (3,7,8,9);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,9), 9 ] + +# alpha - beta = -2 +gap> x := (2,4,5) * (3,7,8,9,10);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,10,9), 9 ] + +# alpha - beta = 2 +gap> x := (2,4,5,6,10) * (3,7,8);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,10), 9 ] + +# Form 2: x = (v,a_1,...,a_alpha,w,b_1,....,b_beta) * (...) +# alpha - beta = 0 +gap> x := (2,4,5,6,3,7,8,9);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,9), 9 ] + +# alpha - beta = -1 +gap> x := (2,4,5,6,3,7,8,9,10);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,9), 9 ] + +# alpha - beta = 1 +gap> x := (2,4,5,6,10,3,7,8,9);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,9), 9 ] + +# alpha - beta = -2 +gap> x := (2,4,5,3,7,8,9,10);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,10,9), 9 ] + +# alpha - beta = 2 +gap> x := (2,4,5,6,10,3,7,8);; +gap> RECOG.BuildCycle(ri, c, x, 10); +[ (1,2,3,4,7,5,8,6,10), 9 ] diff --git a/tst/working/slow/GenericSnAnUnknownDegree.tst b/tst/working/slow/GenericSnAnUnknownDegree.tst deleted file mode 100644 index 19a9c17f..00000000 --- a/tst/working/slow/GenericSnAnUnknownDegree.tst +++ /dev/null @@ -1,305 +0,0 @@ -#@local testFunction, IsBolsteringElement, data -#@local altGroups, symGroups, permMatGroup, altMatGroups, nonAltOrSymGroups -#@local ri, g, c, r, i, x, slp -#@local S11On2Sets, d, sets, SdOn2Sets, success, res, isoData, gens, g1, img1, g2, img2 -#@local SymOnKSets, AltOnKSets -#@local db -# -# HACK to insert the method -gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 58);; -gap> AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.SnAnUnknownDegree, 1070);; -gap> AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnUnknownDegree, 1220);; - -# -# testing matrix: -# - isomorphic: yes, no -# different representations: -# - permutation groups: natural, on 2-subsets -# - finitely presented group -# - permutation matrices: compressed matrices, uncompressed matrices -# - projective matrix groups -# -# TODO: Use RECOG.TestGroup? -# TODO: better name for testFunction -# tests RECOG.ThreeCycleCandidatesIterator and RECOG.BolsteringElements -gap> testFunction := function(G, eps, N) -> local ri, iterator, candidate, i, j; -> ri := RecogNode(G); -> iterator := RECOG.ThreeCycleCandidatesIterator(ri, RECOG.ThreeCycleCandidatesConstants(eps, N)); -> for i in [1 .. 4] do -> candidate := iterator(); -> for j in [1 .. 4] do -> if candidate <> NeverApplicable -> and candidate <> TemporaryFailure then -> RECOG.BolsteringElements(ri, candidate, eps, -> N); -> fi; -> od; -> od; -> end;; - -# -# x : permutation -# c : permutation, -# should be a 3-cycle -# -# Returns true, if x is a bolstering element with respect to the 3-cycle c. -# -# Let c = (u, v, w). We call x a bolstering element with respect to c, if -# x = (v, a_1, ..., a_alpha)(w, b_1, ..., b_beta)(...) or -# x = (v, a_1, ..., a_alpha, w, b_1, ..., b_beta)(...) -# with u in fix(x) and alpha, beta >= 2. -gap> IsBolsteringElement := -> function(x, c) -> local suppC, dist, i, k, p, pos1; -> # suppC = [u, v, w] -> suppC := MovedPoints(c); -> if Size(suppC) <> 3 then return false; fi; -> # dist = [k_u, k_v, k_w], -> # where for each p in suppC, k_p is the minimal integer such that p^(x^k) in suppC -> dist := [0, 0, 0]; -> for i in [1..3] do -> k := 1; -> p := suppC[i]^x; -> while not p in suppC do -> k := k + 1; -> p := p ^ x; -> od; -> dist[i] := k; -> od; -> # One point of suppC is fixed by x -> pos1 := PositionProperty(dist, k -> k = 1); -> if pos1 = fail then return false; fi; -> Remove(dist, pos1); -> # The other two points of cuppC have distance at least 2 -> if ForAny(dist, k -> k < 2) then return false; fi; -> return true; -> end;; - -# For each entry (d, k) we construct Sym(d)/Alt(d) acting on k-sets. -# For each entry (d, k), we must have 2 * k ^ 2 > d, -# otherwise LargeBasePrimitive recognises the group instead of SnAnUnknownDegree. -gap> data := [[11, 3], [12, 3], [13, 3]];; - -# TODO: more non-isomorphic examples -# TODO: add projective groups -# -# SymmetricGroup action on k-sets -gap> SymOnKSets := function(d, k) -> local sets; -> sets := Combinations([1 .. d], k);; -> return Action(SymmetricGroup(d), sets, OnSets);; -> end;; - -# -# AlternatingGroup action on 2-sets -gap> AltOnKSets := function(d, k) -> local sets; -> sets := Combinations([1 .. d], k);; -> return Action(AlternatingGroup(d), sets, OnSets);; -> end;; - -# -gap> altGroups := List(data, entry -> AltOnKSets(entry[1], entry[2]));; -gap> symGroups := List(data, entry -> SymOnKSets(entry[1], entry[2]));; -gap> permMatGroup := G -> Group(List( -> GeneratorsOfGroup(G), -> x -> ImmutableMatrix(7, PermutationMat(x, NrMovedPoints(G), GF(7))) -> ));; -gap> altMatGroups := List([11], -> n -> permMatGroup(AlternatingGroup(n)));; -gap> nonAltOrSymGroups := [ -> DihedralGroup(IsPermGroup, 10), -> #DihedralGroup(IsPcGroup, 10), -> #DihedralGroup(IsPermGroup, 2000), -> #DihedralGroup(IsPcGroup, 2000), -> PSL(3, 5), -> SL(3, 5), -> Omega(-1, 4, 5), -> Omega(-1, 4, 3), -> Omega(+1, 4, 5), -> Omega(+1, 4, 3), -> Omega(+1, 8, 5), -> Omega(+1, 8, 3), -> Omega(0, 5, 5), -> Omega(0, 5, 3), -> ];; - -# Test -gap> for i in [1 .. Length(data)] do -> RECOG.TestGroup(altGroups[i], false, Factorial(data[i, 1])/2); -> RECOG.TestGroup(symGroups[i], false, Factorial(data[i, 1])); -> od; -gap> for i in [1 .. Length(altMatGroups)] do -> RECOG.TestGroup(altMatGroups[i], false, Size(altMatGroups[i])); -> od; -gap> for i in [1 .. Length(altMatGroups)] do -> RECOG.TestGroup(altMatGroups[i], true, Size(altMatGroups[i])); -> od; -gap> for i in [1 .. Length(nonAltOrSymGroups)] do -> if FindHomMethodsGeneric.SnAnUnknownDegree(RecogNode(nonAltOrSymGroups[i]), nonAltOrSymGroups[i]) = Success then -> Print("ERROR: Recognised group [", i, "] wrongly as Sn/An!\n"); -> fi; -> od; - -# RECOG.IsFixedPoint -gap> ri := RecogNode(SymmetricGroup(10));; -gap> g := (1,2,3,4,5,6,7,8);; -gap> c := (1,2,3);; -gap> r := (1,2)(4,5,6);; -gap> RECOG.IsFixedPoint(ri, g,c,r); -true -gap> r := (2,3,4);; -gap> RECOG.IsFixedPoint(ri, g,c,r); -false - -# RECOG.AdjustCycle -gap> ri := RecogNode(SymmetricGroup(10));; -gap> g := (1,2,3,4,5,6,7,8);; -gap> c := (1,2,3);; -gap> r := (4,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,5) -gap> r := (3,4,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,4,5) -gap> r := (2,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,5) -gap> r := (2,4,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,5,4) -gap> r := (2,3,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,4,5) -gap> r := (1,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,5) -gap> r := (1,4,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,5,4) -gap> r := (1,3,4,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,6,4,5) -gap> r := (1,2,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,5,4) -gap> r := (1,2,3,5);; -gap> RECOG.AdjustCycle(ri, g, c, r, 8); -(3,5,4,6) - -# RECOG.BuildCycle -gap> ri := RecogNode(SymmetricGroup(10));; - -# c = (u,v,w) -gap> c := (1,2,3);; - -# Form 1: x = (v,a_1,...,a_alpha) * (w,b_1,....,b_beta) * (...) -# alpha - beta = 0 -gap> x := (2,4,5,6)* (3,7,8,9);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,9), 9 ] - -# alpha - beta = -1 -gap> x := (2,4,5,6)* (3,7,8,9,10);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,9), 9 ] - -# alpha - beta = 1 -gap> x := (2,4,5,6,10)* (3,7,8,9);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,9), 9 ] - -# alpha - beta = -2 -gap> x := (2,4,5) * (3,7,8,9,10);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,10,9), 9 ] - -# alpha - beta = 2 -gap> x := (2,4,5,6,10) * (3,7,8);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,10), 9 ] - -# Form 2: x = (v,a_1,...,a_alpha,w,b_1,....,b_beta) * (...) -# alpha - beta = 0 -gap> x := (2,4,5,6,3,7,8,9);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,9), 9 ] - -# alpha - beta = -1 -gap> x := (2,4,5,6,3,7,8,9,10);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,9), 9 ] - -# alpha - beta = 1 -gap> x := (2,4,5,6,10,3,7,8,9);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,9), 9 ] - -# alpha - beta = -2 -gap> x := (2,4,5,3,7,8,9,10);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,10,9), 9 ] - -# alpha - beta = 2 -gap> x := (2,4,5,6,10,3,7,8);; -gap> RECOG.BuildCycle(ri, c, x, 10); -[ (1,2,3,4,7,5,8,6,10), 9 ] - -# Construct isomorphism between S_11 on 2-sets and S_11 in natural -# representation. -gap> sets := Combinations([1 .. 11], 2);; -gap> S11On2Sets := Action(SymmetricGroup(11), sets, OnSets);; -gap> ri := RecogNode(S11On2Sets);; -gap> FindHomMethodsGeneric.SnAnUnknownDegree(ri, S11On2Sets);; -gap> isoData := ri!.SnAnUnknownDegreeIsoData;; -gap> gens := GeneratorsOfGroup(S11On2Sets);; -gap> g1 := gens[1];; -gap> img1 := RECOG.FindImageSn(ri, 11, g1, isoData[1][1], isoData[1][2], -> isoData[2][1], isoData[2][2]);; -gap> CycleStructurePerm(img1); -[ ,,,,,,,,, 1 ] -gap> g2 := gens[2];; -gap> img2 := RECOG.FindImageSn(ri, 11, g2, isoData[1][1], isoData[1][2], -> isoData[2][1], isoData[2][2]);; -gap> CycleStructurePerm(img2); -[ 1 ] - -# FindHomMethodsGeneric.SnAnUnknownDegree -# Sn -gap> for d in [11 .. 14] do -> sets := Combinations([1 .. d], 2);; -> SdOn2Sets := Action(SymmetricGroup(d), sets, OnSets);; -> ri := RecogNode(SdOn2Sets);; -> success := FindHomMethodsGeneric.SnAnUnknownDegree(ri, SdOn2Sets); -> if not success or not Size(ri) = Factorial(d) then -> Print("wrong result! degree ", d, "\n"); -> fi; -> od; - -# An -gap> for d in [11 .. 14] do -> sets := Combinations([1 .. d], 2);; -> SdOn2Sets := Action(AlternatingGroup(d), sets, OnSets);; -> ri := RecogNode(SdOn2Sets);; -> success := FindHomMethodsGeneric.SnAnUnknownDegree(ri, SdOn2Sets); -> if not success or not Size(ri) = Factorial(d)/2 then -> Print("wrong result! degree ", d, "\n"); -> fi; -> od; - -# Check Slp function -gap> ri := RecogNode(S11On2Sets);; -gap> FindHomMethodsGeneric.SnAnUnknownDegree(ri, S11On2Sets); -true -gap> x := PseudoRandom(Grp(ri));; -gap> slp := SLPforElement(ri, x);; -gap> x = ResultOfStraightLineProgram(slp, NiceGens(ri)); -true - -# -# Remove Hacky injection of our method -gap> for db in [FindHomDbPerm, FindHomDbMatrix, FindHomDbProjective] do -> Remove(db, -> PositionProperty(db, x -> Stamp(x.method) = "SnAnUnknownDegree"));; -> od; From 10af7a623e5b02149ccf883befcfe2b367b38f65 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Thu, 2 Jun 2022 16:12:36 +0200 Subject: [PATCH 10/25] SnAnUnknownDegree: Use the same random elements for heuristic three cycle test. This is a big time saver for groups where we have to exceed all attempts. --- gap/generic/SnAnUnknownDegree.gi | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index d2fe6033..412dc3a2 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -43,14 +43,14 @@ end; # Return false if c can not be a three cycle. This uses cheap tests to # determine this. It is based on the Magma function heuristicThreeCycleTest. -RECOG.HeuristicThreeCycleTest := function(ri, c, logInt2N) +RECOG.HeuristicThreeCycleTest := function(ri, c, logInt2N, R) local r, y, yTo5, k; c := StripMemory(c); if not isone(ri)(c ^ 3) then return false; fi; for k in [1 .. logInt2N + 1] do - r := StripMemory(PseudoRandom(Grp(ri))); # TODO: Replace PseudoRandom + r := R[k]; # c * c ^ r is a product of two three-cycles, so it should have order # 1, 2, 3 or 5. y := c * c ^ r; @@ -82,6 +82,8 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) t, # integers, controlling the number of iterations M, B, T, C, logInt2N, + # list of random elements for heuristic three cycle test + R, # counters nrInvolutions, nrTriedConjugates, nrThreeCycleCandidates, # helper functions @@ -96,6 +98,8 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) C := constants.C; logInt2N := constants.logInt2N; + R := List([1 .. logInt2N + 1], k -> StripMemory(RandomElm(ri, "SnAnUnknownDegree", true)!.el)); + # Counters # Counts the constructed involutions t_i in steps 2 & 3. nrInvolutions := 0; @@ -162,7 +166,7 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # three cycle, that is the heuristic can detect whether candidate can # not be a three cycle, e.g. if it does not have order three. nrThreeCycleCandidates := nrThreeCycleCandidates + 1; - if RECOG.HeuristicThreeCycleTest(ri, candidate, logInt2N) then + if RECOG.HeuristicThreeCycleTest(ri, candidate, logInt2N, R) then return candidate; else return fail; From 852db78191281ad516e44f8ef665d82820385f62 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Thu, 2 Jun 2022 16:37:33 +0200 Subject: [PATCH 11/25] SnAnUnknownDegree: Improve upper bound. - Relocate old code for upper bound to `RECOG.SnAnUpperBoundForDegree`. - The is upper bound is not tight enough, since it only uses the dimension of the matrix group. - Use Derek's `RECOG.GuessSnAnDegree` from contrib/Derek --- gap/generic/SnAnUnknownDegree.gi | 207 ++++++++++++++++++++++++++----- 1 file changed, 177 insertions(+), 30 deletions(-) diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 412dc3a2..27c29006 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -980,36 +980,139 @@ RECOG.LowerBoundForDegreeOfSnAnViaOrders := function(ri) )); end; -#! @BeginChunk SnAnUnknownDegree -#! This method tries to determine whether the input group given by ri is -#! isomorphic to a symmetric group Sn or alternating group An with -#! 9 \leq n. -#! It is an implementation of . -#! -#! If Grp(ri) is a permutation group, we assume that it is primitive and -#! not a giant (a giant is Sn or An in natural action). -#! -#! This method can also recognise a symmetric group Sn or alternating group An with -#! n = 7 or n = 8, but is not required to return a result with -#! the specified error probability. -#! -#! This method cannot recognise a symmetric group Sn or alternating group An with -#! n = 5 or n = 6, since it uses pre-bolstering elements, -#! which need at least 7 moved points. -#! If the input group is isomorphic to a symmetric or alternating group of -#! degrees 5 or 6, then this method might not exit quickly. -#! -#! @EndChunk -BindRecogMethod(FindHomMethodsGeneric, "SnAnUnknownDegree", -"method groups isomorphic to Sn or An with n >= 9", -function(ri, G) - local eps, N, M, p, d, recogData, isoData, degree, swapSLP; - # if IsBound(ri!.fhmethsel) and not "SnAnSmallUnknownDegree" - # in NamesOfComponents(ri!.fhmethsel.inapplicableMethods) then - # ErrorNoReturn("If called via CallMethods then SnAnSmallUnknownDegree must be tried before ", - # "SnAnUnknownDegree."); - # fi; - eps := 1 / 10^2; +# Inspired from Magma Code: GuessAltsymDegree, in magma/package/Group/GrpFin/SimpleRecog/altsym.m +# Returns a guess at alternating or symmetric and degree n +# (It won't work for Sym(3) or Sym(6)!) +# +# This function samples projective orders of elements, and attempts to guess +# degree n and whether it is Alternating or Symmetric. +# Returns a record with entries: +# - type : string "Alternating" or "Symmetric" +# - degree : integer n +# Returns fail if n<=6 or maxtries elements are sampled with +# no decision made. +# +# At least Max(mintries,fac*n*Log(n)) random elements are chosen without +# the answer changing, where mintries, fac can be given as an optional +# arguments. +# +# TODO: Investigate why Alt(9) and Sym(8) return fail +# TODO: Might be inspired from +# "Fast Constructive Recognition of a Black Box Group Isomorphic to Sn or An using Goldbach’s Conjecture" +# by Sergey Bratus and Igor Pak, +# in Chapter 9. "What To Do If n is Not Known?" + +RECOG.GuessSnAnDegree := function(ri, optionlist...) + local G, r, options, mintries, maxtries, fac, mindego, mindege, ct, cto, cte, proc, g, o, mindeg, o_fact, mindegforg; + # mindego and mindege will be respectively the smallest possible + # degrees of symmetric groups that contain the elements of odd and + # even orders, in the random sample. + # If mindego > mindege we assume the group is alternating, otherwise + # that it is symmetric. + + G := Grp(ri); + if (IsPermGroup(G) and NrMovedPoints(G) <= 6) + or (IsMatrixGroup(G) and DimensionOfMatrixGroup(G) < 3) then + Print("GuessAltsymDegree works only for degree > 6\n"); + return fail; + fi; + + # Set options + options := rec( + mintries := 100, + maxtries := 5000, + fac := 4 + ); + + if Length(optionlist) > 0 then + for r in RecNames(optionlist[1]) do + if not IsBound(options.(r)) then + ErrorNoReturn("Invalid option to GuessSnAnDegree: ", r); + fi; + options.(r) := optionlist[1].(r); + od; + fi; + + mintries := options.mintries; + maxtries := options.maxtries; + fac := options.fac; + + # Init Loop + mindego := 0; + mindege := 0; + cto := 0; + cte := 0; + ct := 0; + mindeg := 0; + if mintries < 1 then + mintries := 1; + fi; + + # Main Loop + while (ct < Maximum(mintries, fac * mindeg * Int(Ceil(Log(Float(mindeg+1))))) + or mindego = mindege+1) and ct <= maxtries do + # The situation mindego = mindege+1 was responsible for most errors + # in the first version! Alt(n+1) was returned instead of Sym(n). + g := RandomElm(ri, "GuessSnAnDegree", false)!.el;; + o := ri!.order(g); + ct := ct + 1; # counter of loop, as long as no new larger degree was detected + if o = 1 then + continue; + fi; + o_fact := Collected(Factors(o)); + mindegforg := Sum(o_fact, f -> f[1] ^ f[2]); # minimum degree is sum over all prime-powers in factorization + if o mod 2 = 0 then + cte := cte + 1; # counter for even orders + if mindegforg > mindege then + mindege := mindegforg; + if mindege > mindeg then + mindeg := mindege; + fi; + ct := 0; + # vprintf IsAltsym: "New E, E = %o, O = %o, elt order = %o, Randoms = %o\n", mindege, mindego, o_fact, cte+cto; + fi; + else + cto := cto + 1; # counter for odd orders + if mindegforg > mindego then + mindego := mindegforg; + if mindego > mindeg then + mindeg := mindego; + fi; + ct := 0; + # vprintf IsAltsym: "New O, E = %o, O = %o, elt order = %o, Randoms = %o\n", mindege, mindego, o_fact, cte+cto; + fi; + fi; + od; + + if ct > maxtries then + # vprintf IsAltsym: "maxtries exceeded - giving up!"; + return fail; + fi; + + # vprintf IsAltsym: "E = %o, O = %o, Randoms = %o\n", mindege, mindego, cte+cto; + + if mindego > mindege then + if mindego <= 6 then + # vprintf IsAltsym: "GuessAltsymDegree works only for degree > 6"; + return fail; + else + # vprintf IsAltsym: "Alternating of degree %o\n", mindego; + return rec(type := "Alternating", degree := mindego); + fi; + else + if mindege <= 6 then + # vprintf IsAltsym: "GuessAltsymDegree works only for degree > 6"; + return fail; + else + # vprintf IsAltsym: "Symmetric of degree %o\n", mindege; + return rec(type := "Symmetric", degree := mindege); + fi; + fi; +end; + +RECOG.SnAnUpperBoundForDegree := function(ri) + local G, N, p, d, M; + G := Grp(ri); # N = upper bound for degree # Check magma if IsPermGroup(G) then @@ -1065,6 +1168,50 @@ function(ri, G) if M <= 6 then return TemporaryFailure; fi; + + return N; +end; + +#! @BeginChunk SnAnUnknownDegree +#! This method tries to determine whether the input group given by ri is +#! isomorphic to a symmetric group Sn or alternating group An with +#! 9 \leq n. +#! It is an implementation of . +#! +#! If Grp(ri) is a permutation group, we assume that it is primitive and +#! not a giant (a giant is Sn or An in natural action). +#! +#! This method can also recognise a symmetric group Sn or alternating group An with +#! n = 7 or n = 8, but is not required to return a result with +#! the specified error probability. +#! +#! This method cannot recognise a symmetric group Sn or alternating group An with +#! n = 5 or n = 6, since it uses pre-bolstering elements, +#! which need at least 7 moved points. +#! If the input group is isomorphic to a symmetric or alternating group of +#! degrees 5 or 6, then this method might not exit quickly. +#! +#! @EndChunk +BindRecogMethod(FindHomMethodsGeneric, "SnAnUnknownDegree", +"method groups isomorphic to Sn or An with n >= 9", +function(ri, G) + local eps, N, degreeData, recogData, isoData, degree, swapSLP; + # if IsBound(ri!.fhmethsel) and not "SnAnSmallUnknownDegree" + # in NamesOfComponents(ri!.fhmethsel.inapplicableMethods) then + # ErrorNoReturn("If called via CallMethods then SnAnSmallUnknownDegree must be tried before ", + # "SnAnUnknownDegree."); + # fi; + eps := 1 / 10^2; + # N := RECOG.SnAnUpperBoundForDegree(ri); + # if not IsInt(N) then + # return N; + # fi; + # This is usually much smaller than RECOG.SnAnUpperBoundForDegree + degreeData := RECOG.GuessSnAnDegree(ri); + if degreeData = fail then + return TemporaryFailure; + fi; + N := degreeData.degree; # Try to find an isomorphism recogData := RECOG.RecogniseSnAn(ri, eps, N); # RECOG.RecogniseSnAn returned NeverApplicable or TemporaryFailure From f63fb5ca7b5dcadeda2875bb8e9dbf9e82eb3bf2 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Thu, 2 Jun 2022 17:47:24 +0200 Subject: [PATCH 12/25] SnAnUnknownDegree: Use GuessDegree only if degree is too large. --- gap/generic/SnAnUnknownDegree.gi | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 27c29006..57f81290 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -1202,16 +1202,18 @@ function(ri, G) # "SnAnUnknownDegree."); # fi; eps := 1 / 10^2; - # N := RECOG.SnAnUpperBoundForDegree(ri); - # if not IsInt(N) then - # return N; - # fi; + N := RECOG.SnAnUpperBoundForDegree(ri); + if not IsInt(N) then + return N; + fi; # This is usually much smaller than RECOG.SnAnUpperBoundForDegree - degreeData := RECOG.GuessSnAnDegree(ri); - if degreeData = fail then - return TemporaryFailure; + if N > 20 then + degreeData := RECOG.GuessSnAnDegree(ri); + if degreeData = fail then + return TemporaryFailure; + fi; + N := Minimum(N, degreeData.degree); fi; - N := degreeData.degree; # Try to find an isomorphism recogData := RECOG.RecogniseSnAn(ri, eps, N); # RECOG.RecogniseSnAn returned NeverApplicable or TemporaryFailure From 65dc97764f2ad7f1e0a585db232b490825cd1d57 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Fri, 4 Nov 2022 11:03:51 +0800 Subject: [PATCH 13/25] Improvement of SnAnUnknownDegree Compute ThreeCycleCandidates in a "lazy" way, as done in Magma Code GetNextThreeCycle. --- RECOG_SnAn | 5 + gap/generic/SnAnUnknownDegree.gi | 300 ++++++++++++++++++++----------- 2 files changed, 199 insertions(+), 106 deletions(-) diff --git a/RECOG_SnAn b/RECOG_SnAn index 3b486fdd..d229d407 100644 --- a/RECOG_SnAn +++ b/RECOG_SnAn @@ -14,6 +14,7 @@ If the input acts on a space with a large dimension, then this can take forever. - Look at some orders and deduce a lower bound `M` for the degree. - If `M > N`, then we excluded `An`, `Sn`. - If `M <= 6`, then we return `TemporaryFailure`. + - If `N > 20`, use Magma Code `GuessSnAnDegree` to guess the degree by element orders. - Otherwise, we continue. - Now we can assume for the degree `n >= 7`. We need to ensure this, since `SnAnUnknownDegree` cannot recognise `A5`, `S5`, `A6`, `S6` and could run for a considerable time. @@ -28,4 +29,8 @@ Here we collect the changes in `SnAnUnknownDegree` compared to the "vanilla" imp - Computation of upper bound `N` done as in `[L84]` and `[KL90]`. - Computation of lower bound `M` by element orders. - For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for some random elements `r` if for the element `x := c * c^r` either `x^5 = 1` or `x^6 = 1` holds (The element `x` needs to have order `1`, `2`, `3` or `5`). +- `ThreeCycleCandidatesIterator` uses a similar approach to that from the Magma Code `GetNextThreeCycle` and thus computes the elements in a different ordering. For every involution, we first consider only up to `K = 5` random conjugates. If none is successful, we move to the next involution. We save all involutions considered so far. + - If this failed (i.e., for all `B` involutions all `K` random conjugates failed), we proceed in a linear manner: + - We consider each involution in turn, and for each compute `K` new random conjugate. +- Use `RecogniseSnAnLazy` that caches iterators constructed by `ThreeCycleCandidatesIterator` and returns `TemporaryFailure` more quickly. - Use Conder's Thesis to compute images for degree `n <= 10`. diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 57f81290..387d83f9 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -18,6 +18,22 @@ ## ############################################################################# +RECOG.SnAnDebug := false; + +BindGlobal("SnAnTryLater", MakeImmutable("SnAnTryLater")); +BindGlobal("SnAnRepeatImmediately", MakeImmutable("SnAnRepeatImmediately")); + +RECOG.SnAnGetCache := function(ri) + if not IsBound(ri!.SnAnUnknownDegreeCache) then + ri!.SnAnUnknownDegreeCache := rec(); + fi; + return ri!.SnAnUnknownDegreeCache; +end; + +RECOG.SnAnResetCache := function(ri) + ri!.SnAnUnknownDegreeCache := rec(); +end; + # eps : real number, the error bound # N : integer, upper bound for the degree of G # @@ -72,6 +88,7 @@ end; # Creates and returns a function, here called oneThreeCycleCandidate. The # function oneThreeCycleCandidate returns one of the following: # - a three cycle candidate, i.e. an element of G +# - SnAnTryLater, if we tried all involution candidates K times # - TemporaryFailure, if we exhausted all attempts # - NeverApplicable, if we found out that G can't be an Sn or An # @@ -81,41 +98,61 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # involution t, # integers, controlling the number of iterations - M, B, T, C, logInt2N, + M, B, T, C, logInt2N, K, # list of random elements for heuristic three cycle test R, + # list of involution candidates + involutions, # counters - nrInvolutions, nrTriedConjugates, nrThreeCycleCandidates, + nrTriedConjugates, nrThreeCycleCandidates, + # counters + Ki, curInvolutionPos, # helper functions - tryThreeCycleCandidate, oneThreeCycleCandidate; + tryThreeCycleCandidate, oneThreeCycleCandidate, + # used for debugging + cache; # Step 1: Initialization - # The current involution t_i - t := fail; - M := constants.M; B := constants.B; T := constants.T; C := constants.C; logInt2N := constants.logInt2N; + K := 5; + # list containing the constructed involutions t_i in steps 2 & 3 + involutions := EmptyPlist(B); R := List([1 .. logInt2N + 1], k -> StripMemory(RandomElm(ri, "SnAnUnknownDegree", true)!.el)); # Counters - # Counts the constructed involutions t_i in steps 2 & 3. - nrInvolutions := 0; + curInvolutionPos := 1; + Ki := Minimum(K, C); # Counts the elements c in step 4 that we use to conjugate the current # involution t_i. We initialize nrTriedConjugates to C such that "steps 2 # & 3" in tryThreeCycleCandidate immediately construct an involution. - nrTriedConjugates := C; + nrTriedConjugates := []; # counts the size of the set Gamma_i in step 4 for the current involution # t_i - nrThreeCycleCandidates := 0; + nrThreeCycleCandidates := []; + + # Used for Debugging + if RECOG.SnAnDebug then + cache := RECOG.SnAnGetCache(ri); + if not IsBound(cache.iteratorsLocalVars) then + cache.iteratorsLocalVars := []; + fi; + Add(cache.iteratorsLocalVars, rec( + involutions := involutions, + nrTriedConjugates := nrTriedConjugates, + nrThreeCycleCandidates := nrThreeCycleCandidates + )); + fi; # Helper functions # tryThreeCycleCandidate returns one of the following: # - a three cycle candidate, i.e. an element of G - # - fail, if the random conjugate c from step 4 and t commute. Then we have - # to call tryThreeCycleCandidate again + # - TemporaryFailure, if we exhausted all attempts + # - SnAnTryLater, if we tried all involution candidates K times + # - SnAnRepeatImmediately, if we have to call tryThreeCycleCandidate again # - NeverApplicable, if G can not be an Sn or An tryThreeCycleCandidate := function() local @@ -125,11 +162,9 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) r, tPower, tPowerOld, c, # the three cycle candidate candidate; + # Steps 2 & 3: New involution - # Check if we either tried enough conjugates or constructed enough - # three cycle candidates for the current involution t. - # If this is the case, we need to construct the next involution - if nrTriedConjugates >= C or nrThreeCycleCandidates >= T then + if curInvolutionPos > Length(involutions) then r := RandomElm(ri, "SnAnUnknownDegree", true)!.el; # In the paper, we have t = r ^ M. # Invariant: tPower = (r ^ M) ^ (2 ^ a) @@ -147,52 +182,82 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) if not isone(ri)(tPower) then return NeverApplicable; fi; - t := tPowerOld; - nrInvolutions := nrInvolutions + 1; - nrTriedConjugates := 0; - nrThreeCycleCandidates := 0; + involutions[curInvolutionPos] := tPowerOld; + nrTriedConjugates[curInvolutionPos] := 0; + nrThreeCycleCandidates[curInvolutionPos] := 0; + fi; + # Check if we either tried enough conjugates or constructed enough + # three cycle candidates for the current involution t. + # If this is the case, we need to construct the next involution, + # or we have exhausted all attempts + if curInvolutionPos = B + and (nrTriedConjugates[B] >= C or nrThreeCycleCandidates[B] >= T) + then + return TemporaryFailure; + fi; + if nrThreeCycleCandidates[curInvolutionPos] >= T then + curInvolutionPos := curInvolutionPos + 1; + return SnAnRepeatImmediately; + fi; + if nrTriedConjugates[curInvolutionPos] >= Ki then + if curInvolutionPos = B then + Ki := Ki + K; + Ki := Minimum(Ki, C); + curInvolutionPos := 1; + return SnAnTryLater; + else + curInvolutionPos := curInvolutionPos + 1; + return SnAnRepeatImmediately; + fi; fi; # Steps 4 & 5: new three cycle candidate # Try to construct a three cycle candidate via a conjugate of t. See # the comment above this function. - nrTriedConjugates := nrTriedConjugates + 1; + t := involutions[curInvolutionPos]; + nrTriedConjugates[curInvolutionPos] := nrTriedConjugates[curInvolutionPos] + 1; c := t ^ RandomElm(ri, "SnAnUnknownDegree", true)!.el; if isequal(ri)(t * c, c * t) then # we have to call tryThreeCycleCandidate again - return fail; + return SnAnRepeatImmediately; fi; candidate := (t * c) ^ 2; # We now use a one-sided heuristic to test whether candidate can be a # three cycle, that is the heuristic can detect whether candidate can # not be a three cycle, e.g. if it does not have order three. - nrThreeCycleCandidates := nrThreeCycleCandidates + 1; + nrThreeCycleCandidates[curInvolutionPos] := nrThreeCycleCandidates[curInvolutionPos] + 1; if RECOG.HeuristicThreeCycleTest(ri, candidate, logInt2N, R) then return candidate; else - return fail; + return SnAnRepeatImmediately; fi; end; + # construct the iterator oneThreeCycleCandidate := function() local candidate; repeat - if nrInvolutions >= B - and (nrTriedConjugates >= C or nrThreeCycleCandidates >= T) - then - # With probability at least 1 - eps we constructed at least one - # three cycle with this iterator. - return fail; - fi; candidate := tryThreeCycleCandidate(); - if candidate = NeverApplicable then - return NeverApplicable; - fi; - until candidate <> fail; + until candidate <> SnAnRepeatImmediately; + # With probability at least 1 - eps we constructed at least one + # three cycle with this iterator. return candidate; end; + return oneThreeCycleCandidate; end; +RECOG.SnAnCacheIterators := function(ri, T, N) + local cache, constants; + cache := RECOG.SnAnGetCache(ri); + if not IsBound(cache.iterators) then + constants := RECOG.ThreeCycleCandidatesConstants(1 / 4., N); + if RECOG.SnAnDebug then + cache.constants := constants; + fi; + cache.iterators := List([1 .. T], i -> RECOG.ThreeCycleCandidatesIterator(ri, constants)); + fi; +end; + # ri : recognition node with group G # c : element of G, # should be a 3-cycle @@ -891,25 +956,30 @@ RECOG.ConstructSnAnIsomorphism := function(ri, n, stdGensAnWithMemory) slpToStdGens := SLPOfElms(stdGensSnWithMemory)); end; -# This method is an implementation of . It is the main -# function of SnAnUnknownDegree. -# From : -# RECOG.RecogniseSnAn is a one-sided Monte-Carlo algorithm with the following -# properties. It takes as input a black-box group G, a natural number -# N and a real number eps with 0 < eps < 1. If G is -# isomorphic to An or Sn for some 9 <= n <= N, it returns with -# probability at least 1 - eps the degree n and an -# isomorphism from G to An or Sn. -RECOG.RecogniseSnAn := function(ri, eps, N) - local T, foundPreImagesOfStdGens, constants, iterator, c, tmp, recogData, i; - T := Int(Ceil(Log2(1 / Float(eps)))); - foundPreImagesOfStdGens := false; - constants := RECOG.ThreeCycleCandidatesConstants(1. / 4., N); - for i in [1 .. T] do - iterator := RECOG.ThreeCycleCandidatesIterator(ri, constants); +# ri : recognition node with group G, +# T : integer, number of iterators +# N : integer, upper bound for the degree of G +# +# This is the main method used by RECOG.RecogniseSnAn and RECOG.RecogniseSnAnLazy. +# +# This function returns one of the following: +# - an isomorphism from G to Sn or An +# - SnAnTryLater, if we should try this method at a later point again +# - TemporaryFailure, if we exhausted all attempts +# - NeverApplicable, if we found out that G can't be an Sn or An +RECOG.RecogniseSnAnSingleIteration := function(ri, T, N) + local cache, iterators, iterator, c, tmp, recogData; + cache := RECOG.SnAnGetCache(ri); + RECOG.SnAnCacheIterators(ri, T, N); + iterators := cache.iterators; + # each iterator succeeds with probability at least 1/2, + # if we exhaust all attempts + for iterator in iterators do c := iterator(); - while c <> fail do - if c = NeverApplicable then return NeverApplicable; fi; + while not c in [SnAnTryLater, TemporaryFailure] do + if c = NeverApplicable then + return c; + fi; tmp := RECOG.ConstructLongCycle(ri, c, 1. / 8., N); if tmp = fail then c := iterator(); @@ -926,42 +996,32 @@ RECOG.RecogniseSnAn := function(ri, eps, N) # Now tmp contains [g, c, n] where # g, c correspond to standard generators of An recogData := RECOG.ConstructSnAnIsomorphism(ri, tmp[3], tmp{[1,2]}); - if recogData = fail then continue; fi; + if recogData = fail then + continue; + fi; return recogData; od; od; - return TemporaryFailure; + return c; end; -# #! @BeginChunk SnAnSmallUnknownDegree -# #! This method checks element orders to rule out whether the input group might -# #! be A_5, S_5, A_6, S_6, or Aut(A_6). If it wasn't able to rule that out it -# #! tries to find a small orbit, which must exist if the input group is -# #! isomorphic to one of the groups in question. -# #! This method only takes matrix or projective inputs. -# #! @EndChunk SnAnSmallUnknownDegree -# BindRecogMethod(FindHomMethodsGeneric, "SnAnSmallUnknownDegree", -# "find small orbit if the group might be A_5, S_5, A_6, S_6, or Aut(A_6)", -# function(ri, G) -# local orders, orbit; -# orders := Set(List( -# [1..30], -# i -> RandomElmOrd(ri, "SnAnSmallUnknownDegree", false).order -# )); -# # Orders which are not in Aut(A_6): -# if ForAny(orders, x -> x >= 11) or 7 in orders or 9 in orders then -# return NeverApplicable; -# fi; -# orbit := Orb(G, One(G)[1], OnPoints, rec(storenumbers := true)); -# Enumerate(orbit, 1441); -# if Length(orbit) <= 1440 then -# # Our reduction is that we found a smallish orbit. -# SetHomom(ri, OrbActionHomomorphism(G, orbit)); -# Setmethodsforimage(ri, FindHomDbPerm); -# return Success; -# fi; -# return NeverApplicable; -# end); +# This method is an implementation of . +# From : +# RECOG.RecogniseSnAn is a one-sided Monte-Carlo algorithm with the following +# properties. It takes as input a black-box group G, a natural number +# N and a real number eps with 0 < eps < 1. If G is +# isomorphic to An or Sn for some 9 <= n <= N, it returns with +# probability at least 1 - eps the degree n and an +# isomorphism from G to An or Sn. +RECOG.RecogniseSnAn := function(ri, eps, N) + local T, tmp; + T := Int(Ceil(Log2(1 / Float(eps)))); + tmp := SnAnTryLater; + while tmp = SnAnTryLater do + tmp := RECOG.RecogniseSnAnSingleIteration(ri, T, N); + od; + return tmp; +end; RECOG.LowerBoundForDegreeOfSnAnViaOrders := function(ri) local G, orders; @@ -1001,7 +1061,6 @@ end; # "Fast Constructive Recognition of a Black Box Group Isomorphic to Sn or An using Goldbach’s Conjecture" # by Sergey Bratus and Igor Pak, # in Chapter 9. "What To Do If n is Not Known?" - RECOG.GuessSnAnDegree := function(ri, optionlist...) local G, r, options, mintries, maxtries, fac, mindego, mindege, ct, cto, cte, proc, g, o, mindeg, o_fact, mindegforg; # mindego and mindege will be respectively the smallest possible @@ -1054,7 +1113,7 @@ RECOG.GuessSnAnDegree := function(ri, optionlist...) # The situation mindego = mindege+1 was responsible for most errors # in the first version! Alt(n+1) was returned instead of Sym(n). g := RandomElm(ri, "GuessSnAnDegree", false)!.el;; - o := ri!.order(g); + o := OrderFunc(ri)(g); ct := ct + 1; # counter of loop, as long as no new larger degree was detected if o = 1 then continue; @@ -1172,6 +1231,53 @@ RECOG.SnAnUpperBoundForDegree := function(ri) return N; end; +RECOG.SnAnCacheUpperBoundForDegree := function(ri) + local cache, N, degreeData; + cache := RECOG.SnAnGetCache(ri); + if IsBound(cache.N) then + return; + fi; + N := RECOG.SnAnUpperBoundForDegree(ri); + cache.N := N; + if not IsInt(N) then + return; + fi; + # This is usually much smaller than RECOG.SnAnUpperBoundForDegree + if N > 20 then + degreeData := RECOG.GuessSnAnDegree(ri); + if degreeData = fail then + cache.N := TemporaryFailure; + return; + fi; + N := Minimum(N, degreeData.degree); + cache.N := N; + fi; +end; + +# See RECOG.RecogniseSnAn. The difference is, that we give up at an earlier +# point, i.e. we try out other recognition methods, before we continue. +# In order to achieve this, we cache some important values for further +# computations. It is the main function of SnAnUnknownDegree. +RECOG.RecogniseSnAnLazy := function(ri) + local cache, N, tmp; + cache := RECOG.SnAnGetCache(ri); + RECOG.SnAnCacheUpperBoundForDegree(ri); + N := cache.N; + if N = TemporaryFailure then + RECOG.SnAnResetCache(ri); + fi; + if not IsInt(N) then + return N; + fi; + tmp := RECOG.RecogniseSnAnSingleIteration(ri, 1, N); + if tmp = TemporaryFailure then + RECOG.SnAnResetCache(ri); + elif tmp = SnAnTryLater then + tmp := TemporaryFailure; + fi; + return tmp; +end; + #! @BeginChunk SnAnUnknownDegree #! This method tries to determine whether the input group given by ri is #! isomorphic to a symmetric group Sn or alternating group An with @@ -1195,27 +1301,9 @@ end; BindRecogMethod(FindHomMethodsGeneric, "SnAnUnknownDegree", "method groups isomorphic to Sn or An with n >= 9", function(ri, G) - local eps, N, degreeData, recogData, isoData, degree, swapSLP; - # if IsBound(ri!.fhmethsel) and not "SnAnSmallUnknownDegree" - # in NamesOfComponents(ri!.fhmethsel.inapplicableMethods) then - # ErrorNoReturn("If called via CallMethods then SnAnSmallUnknownDegree must be tried before ", - # "SnAnUnknownDegree."); - # fi; - eps := 1 / 10^2; - N := RECOG.SnAnUpperBoundForDegree(ri); - if not IsInt(N) then - return N; - fi; - # This is usually much smaller than RECOG.SnAnUpperBoundForDegree - if N > 20 then - degreeData := RECOG.GuessSnAnDegree(ri); - if degreeData = fail then - return TemporaryFailure; - fi; - N := Minimum(N, degreeData.degree); - fi; + local recogData, isoData, degree, swapSLP, t; # Try to find an isomorphism - recogData := RECOG.RecogniseSnAn(ri, eps, N); + recogData := RECOG.RecogniseSnAnLazy(ri); # RECOG.RecogniseSnAn returned NeverApplicable or TemporaryFailure if not IsRecord(recogData) then return recogData; From af0aba4db1a5138125eede22eb1aa8cf8e57a6c1 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Mon, 8 Dec 2025 15:05:56 +0100 Subject: [PATCH 14/25] Integrate SnAn code into library --- gap/matrix.gi | 6 ++++++ gap/perm.gi | 2 ++ 2 files changed, 8 insertions(+) diff --git a/gap/matrix.gi b/gap/matrix.gi index 40d4df7b..620f4110 100644 --- a/gap/matrix.gi +++ b/gap/matrix.gi @@ -915,7 +915,13 @@ AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.DiagonalMatrices, 1100); AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.KnownStabilizerChain, 1175); +<<<<<<< HEAD AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.FewGensAbelian, 1050); +======= +AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.SnAnUnknownDegree, 1070);; + +AddMethod(FindHomDbPerm, FindHomMethodsGeneric.FewGensAbelian, 1050); +>>>>>>> 7925157 (Integrate SnAn code into library) AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.ReducibleIso, 1000); diff --git a/gap/perm.gi b/gap/perm.gi index ec1dcbd5..47427e01 100644 --- a/gap/perm.gi +++ b/gap/perm.gi @@ -499,6 +499,8 @@ AddMethod(FindHomDbPerm, FindHomMethodsPerm.Imprimitive, 70); AddMethod(FindHomDbPerm, FindHomMethodsPerm.LargeBasePrimitive, 60); +AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 58);; + AddMethod(FindHomDbPerm, FindHomMethodsPerm.StabilizerChainPerm, 55); AddMethod(FindHomDbPerm, FindHomMethodsPerm.StabChain, 50); From 653946878a6ca2d2e0f4cee41d42c52d15e242a0 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Mon, 8 Dec 2025 16:06:44 +0100 Subject: [PATCH 15/25] Remove hack from test file --- tst/working/quick/GenericSnAnUnknownDegree.tst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tst/working/quick/GenericSnAnUnknownDegree.tst b/tst/working/quick/GenericSnAnUnknownDegree.tst index 71931335..294aec2c 100644 --- a/tst/working/quick/GenericSnAnUnknownDegree.tst +++ b/tst/working/quick/GenericSnAnUnknownDegree.tst @@ -1,7 +1,4 @@ # -# HACK to insert the method -gap> AddMethod(FindHomDbPerm, FindHomMethodsGeneric.SnAnUnknownDegree, 58);; - # For each entry (d, k) we construct Sym(d)/Alt(d) acting on k-sets. # For each entry (d, k), we must have 2 * k ^ 2 > d, # otherwise LargeBasePrimitive recognises the group instead of SnAnUnknownDegree. @@ -65,10 +62,3 @@ gap> for i in [1 .. Length(nonAltOrSymGroups)] do > Print("ERROR: Recognised group [", i, "] wrongly as Sn/An!\n"); > fi; > od; - -# -# Remove Hacky injection of our method -gap> for db in [FindHomDbPerm] do -> Remove(db, -> PositionProperty(db, x -> Stamp(x.method) = "SnAnUnknownDegree"));; -> od; From 18b7a184dd08a183575e09c28756b11ff7ffc970 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Mon, 8 Dec 2025 16:40:15 +0100 Subject: [PATCH 16/25] fixed wrong rebase --- doc/howtowritearecogmethod.autodoc | 8 ++------ gap/base/methsel.gi | 6 ++---- gap/base/recognition.gi | 7 ++----- gap/matrix.gi | 4 ---- gap/matrix/classical.gi | 2 +- 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/doc/howtowritearecogmethod.autodoc b/doc/howtowritearecogmethod.autodoc index 11ce8483..1b1c9539 100644 --- a/doc/howtowritearecogmethod.autodoc +++ b/doc/howtowritearecogmethod.autodoc @@ -86,14 +86,10 @@ Namely, many functions for objects with memory assume that, if the elements live Recall that splitting recognition methods produce an epimorphism $\phi:G\to H$ and then delegate the work to the image $H$ and the kernel $N:=\ker(\phi)$. This means that now $N$ and $H$ have to be constructively recognized. Such a -splitting recognition method only needs to -- provide a homomorphism, by calling -`SetHomom(ri, hom);`, and -- optionally update `methodsforimage`, if the representation changed. -However, in practice one will want to provide additional +splitting recognition method only needs to provide a homomorphism, by calling +`SetHomom(ri, hom);`. However, in practice one will want to provide additional data. - We start with an example, similar to a method used in recog. This refers to permutation groups only! diff --git a/gap/base/methsel.gi b/gap/base/methsel.gi index 66538ae9..14485ace 100644 --- a/gap/base/methsel.gi +++ b/gap/base/methsel.gi @@ -56,11 +56,9 @@ InstallGlobalFunction( "CallMethods", function(db, tolerancelimit, methargs...) # Second argument is a number, the tolerance limit. # All other arguments are handed through to the methods. - local ri, i, ms, result, tolerance; + local i, ms, result, tolerance; ms := rec(failedMethods := rec(), inapplicableMethods := rec()); - ri := methargs[1]; - Setfhmethsel(ri, ms); # Initialize record: tolerance := 0; # reuse methods that failed that many times @@ -139,5 +137,5 @@ InstallGlobalFunction( "CallMethods", function(db, tolerancelimit, methargs...) Info(InfoMethSel, 1, "Giving up!"); ms.result := TemporaryFailure; ms.tolerance := tolerance; - return; + return ms; end); diff --git a/gap/base/recognition.gi b/gap/base/recognition.gi index 6e6311e4..a75e9ae1 100644 --- a/gap/base/recognition.gi +++ b/gap/base/recognition.gi @@ -486,7 +486,7 @@ InstallGlobalFunction( RecogniseGeneric, Assert(0, Length(Set(allmethods, m->m.rank)) = Length(allmethods)); # Find a possible homomorphism (or recognise this group as leaf) - CallMethods( allmethods, 10, ri, H ); + Setfhmethsel(ri, CallMethods( allmethods, 10, ri, H )); # TODO: extract the value 10 into a named constant, and / or make it # an option parameter to the func @@ -892,9 +892,6 @@ RECOG.TestGroupOptions := rec( # a supergroup to check inTests := 30, - # InfoLevel used during the call to TestGroup. Useful for debugging. - infoLevel := 0, - # The following options are off by default as they fail on # many examples at present @@ -924,7 +921,7 @@ RECOG.TestGroup := function(g,proj,size, optionlist...) fi; lvl:=InfoLevel(InfoRecog); - SetInfoLevel(InfoRecog, options.infoLevel); + SetInfoLevel(InfoRecog, 0); repeat count := count + 1; #r := Runtime(); diff --git a/gap/matrix.gi b/gap/matrix.gi index 620f4110..4d11668e 100644 --- a/gap/matrix.gi +++ b/gap/matrix.gi @@ -915,13 +915,9 @@ AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.DiagonalMatrices, 1100); AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.KnownStabilizerChain, 1175); -<<<<<<< HEAD -AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.FewGensAbelian, 1050); -======= AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.SnAnUnknownDegree, 1070);; AddMethod(FindHomDbPerm, FindHomMethodsGeneric.FewGensAbelian, 1050); ->>>>>>> 7925157 (Integrate SnAn code into library) AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.ReducibleIso, 1000); diff --git a/gap/matrix/classical.gi b/gap/matrix/classical.gi index 6f68af87..c51ae28c 100644 --- a/gap/matrix/classical.gi +++ b/gap/matrix/classical.gi @@ -2316,7 +2316,7 @@ function( arg ) ); merkinfolevel := InfoLevel(InfoMethSel); SetInfoLevel(InfoMethSel,0); - CallMethods( ClassicalMethDb, nrrandels, recognise, grp ); + ret := CallMethods( ClassicalMethDb, nrrandels, recognise, grp ); SetInfoLevel(InfoMethSel,merkinfolevel); # fail: bedeutet, dass entnervt aufgegeben wurde # true: bedeutet, dass eine Methode "erfolgreich" war From 7fb6ed2cffa8c920b9b80cea969fa6b326d0cfc7 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Tue, 9 Dec 2025 10:25:13 +0100 Subject: [PATCH 17/25] Rename counters to better reflect what they are counting in the algorithm, and highlight the adjustments made to the original algorithm. --- gap/generic/SnAnUnknownDegree.gi | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 387d83f9..6afd3f60 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -98,20 +98,21 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # involution t, # integers, controlling the number of iterations - M, B, T, C, logInt2N, K, + M, B, T, C, logInt2N, K, L, # list of random elements for heuristic three cycle test R, # list of involution candidates involutions, # counters - nrTriedConjugates, nrThreeCycleCandidates, + nrTriedConjugates, nrCommutatingConjugates, nrThreeCycleCandidates, # counters - Ki, curInvolutionPos, + Ki, Li, curInvolutionPos, # helper functions tryThreeCycleCandidate, oneThreeCycleCandidate, # used for debugging cache; # Step 1: Initialization + ######################################################################### M := constants.M; B := constants.B; T := constants.T; @@ -132,6 +133,9 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) nrTriedConjugates := []; # counts the size of the set Gamma_i in step 4 for the current involution # t_i + nrCommutatingConjugates := []; + # counts the actual number of three cycle candidates considered + # which are filtered from the Gamma_i via heuristic order tests. nrThreeCycleCandidates := []; # Used for Debugging @@ -143,6 +147,7 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) Add(cache.iteratorsLocalVars, rec( involutions := involutions, nrTriedConjugates := nrTriedConjugates, + nrCommutatingConjugates := nrCommutatingConjugates, nrThreeCycleCandidates := nrThreeCycleCandidates )); fi; @@ -164,6 +169,7 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) candidate; # Steps 2 & 3: New involution + ######################################################################### if curInvolutionPos > Length(involutions) then r := RandomElm(ri, "SnAnUnknownDegree", true)!.el; # In the paper, we have t = r ^ M. @@ -184,6 +190,7 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) fi; involutions[curInvolutionPos] := tPowerOld; nrTriedConjugates[curInvolutionPos] := 0; + nrCommutatingConjugates[curInvolutionPos] := 0; nrThreeCycleCandidates[curInvolutionPos] := 0; fi; # Check if we either tried enough conjugates or constructed enough @@ -191,11 +198,11 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # If this is the case, we need to construct the next involution, # or we have exhausted all attempts if curInvolutionPos = B - and (nrTriedConjugates[B] >= C or nrThreeCycleCandidates[B] >= T) + and (nrTriedConjugates[B] >= C or nrCommutatingConjugates[B] >= T) then return TemporaryFailure; fi; - if nrThreeCycleCandidates[curInvolutionPos] >= T then + if nrCommutatingConjugates[curInvolutionPos] >= T then curInvolutionPos := curInvolutionPos + 1; return SnAnRepeatImmediately; fi; @@ -211,6 +218,7 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) fi; fi; # Steps 4 & 5: new three cycle candidate + ######################################################################### # Try to construct a three cycle candidate via a conjugate of t. See # the comment above this function. t := involutions[curInvolutionPos]; @@ -220,12 +228,13 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # we have to call tryThreeCycleCandidate again return SnAnRepeatImmediately; fi; + nrCommutatingConjugates[curInvolutionPos] := nrCommutatingConjugates[curInvolutionPos] + 1; candidate := (t * c) ^ 2; # We now use a one-sided heuristic to test whether candidate can be a # three cycle, that is the heuristic can detect whether candidate can # not be a three cycle, e.g. if it does not have order three. - nrThreeCycleCandidates[curInvolutionPos] := nrThreeCycleCandidates[curInvolutionPos] + 1; if RECOG.HeuristicThreeCycleTest(ri, candidate, logInt2N, R) then + nrThreeCycleCandidates[curInvolutionPos] := nrThreeCycleCandidates[curInvolutionPos] + 1; return candidate; else return SnAnRepeatImmediately; From 60894397f67b0e7b9850a60daa27b423f17e586f Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Tue, 9 Dec 2025 12:45:28 +0100 Subject: [PATCH 18/25] Change iterator to work in small batches --- RECOG_SnAn | 1 + gap/generic/SnAnUnknownDegree.gi | 63 +++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/RECOG_SnAn b/RECOG_SnAn index d229d407..f9efb88f 100644 --- a/RECOG_SnAn +++ b/RECOG_SnAn @@ -33,4 +33,5 @@ Here we collect the changes in `SnAnUnknownDegree` compared to the "vanilla" imp - If this failed (i.e., for all `B` involutions all `K` random conjugates failed), we proceed in a linear manner: - We consider each involution in turn, and for each compute `K` new random conjugate. - Use `RecogniseSnAnLazy` that caches iterators constructed by `ThreeCycleCandidatesIterator` and returns `TemporaryFailure` more quickly. +Here we work in batches of at most `L` involutions and batches of at most `K` conjugates per involution. - Use Conder's Thesis to compute images for degree `n <= 10`. diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 6afd3f60..5dc57684 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -53,7 +53,9 @@ RECOG.ThreeCycleCandidatesConstants := function(eps, N) B := Int(Ceil(13 * Log(Float(N)) * Log(3 / Float(eps)))), T := Int(Ceil(3 * Log(3 / Float(eps)))), C := Int(Ceil(Float(3 * N * ~.T / 5))), - logInt2N := LogInt(N, 2) + logInt2N := LogInt(N, 2), + K := 5, # this is an arbitrary number, max number for batch of conjugates per involution + L := 10, # this is an arbitrary number, max number for batch of involutions ); end; @@ -79,7 +81,7 @@ RECOG.HeuristicThreeCycleTest := function(ri, c, logInt2N, R) end; # ri : recognition node with group G -# constants : a record with components M, B, T, C, and logInt2N +# constants : a record with components M, B, T, C, K, L, and logInt2N # # The following algorithm constructs a set of possible 3-cycles. It is based # on the simple observation that the product of two involutions t1, t2, which @@ -88,7 +90,8 @@ end; # Creates and returns a function, here called oneThreeCycleCandidate. The # function oneThreeCycleCandidate returns one of the following: # - a three cycle candidate, i.e. an element of G -# - SnAnTryLater, if we tried all involution candidates K times +# - SnAnTryLater, if we tried a batch of at most L involutions, +# and for each of these we tried K conjugates # - TemporaryFailure, if we exhausted all attempts # - NeverApplicable, if we found out that G can't be an Sn or An # @@ -98,7 +101,9 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # involution t, # integers, controlling the number of iterations - M, B, T, C, logInt2N, K, L, + M, B, T, C, logInt2N, + # integers, making the algorithm give up quicker during iterations + K, L, # list of random elements for heuristic three cycle test R, # list of involution candidates @@ -118,7 +123,8 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) T := constants.T; C := constants.C; logInt2N := constants.logInt2N; - K := 5; + K := constants.K; + L := constants.L; # list containing the constructed involutions t_i in steps 2 & 3 involutions := EmptyPlist(B); @@ -127,9 +133,9 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # Counters curInvolutionPos := 1; Ki := Minimum(K, C); + Li := Minimum(L, B); # Counts the elements c in step 4 that we use to conjugate the current - # involution t_i. We initialize nrTriedConjugates to C such that "steps 2 - # & 3" in tryThreeCycleCandidate immediately construct an involution. + # involution t_i. nrTriedConjugates := []; # counts the size of the set Gamma_i in step 4 for the current involution # t_i @@ -170,6 +176,11 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # Steps 2 & 3: New involution ######################################################################### + # We consider at most B involutions. + if curInvolutionPos > B then + curInvolutionPos := 1; + fi; + # We did not construct yet the involution to consider if curInvolutionPos > Length(involutions) then r := RandomElm(ri, "SnAnUnknownDegree", true)!.el; # In the paper, we have t = r ^ M. @@ -193,30 +204,39 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) nrCommutatingConjugates[curInvolutionPos] := 0; nrThreeCycleCandidates[curInvolutionPos] := 0; fi; - # Check if we either tried enough conjugates or constructed enough - # three cycle candidates for the current involution t. - # If this is the case, we need to construct the next involution, - # or we have exhausted all attempts + # Check if we either tried enough conjugates or already found enough + # commutating conjugates for all involutions t. + # If this is the case, then we have exhausted all attempts. if curInvolutionPos = B - and (nrTriedConjugates[B] >= C or nrCommutatingConjugates[B] >= T) + and ForAll([1 .. B], i -> nrTriedConjugates[i] >= C or nrCommutatingConjugates[i] >= T) then return TemporaryFailure; fi; - if nrCommutatingConjugates[curInvolutionPos] >= T then - curInvolutionPos := curInvolutionPos + 1; - return SnAnRepeatImmediately; - fi; - if nrTriedConjugates[curInvolutionPos] >= Ki then + # If we either considered enough conjugates or already found enough + # commutating conjugates for the current involution t, + # we need to consider the next involution, or reached the end of our batch of involutions. + # Recall, that we work in batches for the lazy version. + # We work in batches of at most L involutions and for these, + # in batches of K conjugates. + if nrTriedConjugates[curInvolutionPos] >= Ki or nrCommutatingConjugates[curInvolutionPos] >= T then if curInvolutionPos = B then + Li := L; + Li := Minimum(Li, B); Ki := Ki + K; Ki := Minimum(Ki, C); curInvolutionPos := 1; return SnAnTryLater; + elif curInvolutionPos = Li then + Li := Li + L; + Li := Minimum(Li, B); + curInvolutionPos := curInvolutionPos + 1; + return SnAnTryLater; else curInvolutionPos := curInvolutionPos + 1; return SnAnRepeatImmediately; fi; fi; + # Steps 4 & 5: new three cycle candidate ######################################################################### # Try to construct a three cycle candidate via a conjugate of t. See @@ -978,8 +998,8 @@ end; # - NeverApplicable, if we found out that G can't be an Sn or An RECOG.RecogniseSnAnSingleIteration := function(ri, T, N) local cache, iterators, iterator, c, tmp, recogData; - cache := RECOG.SnAnGetCache(ri); RECOG.SnAnCacheIterators(ri, T, N); + cache := RECOG.SnAnGetCache(ri); iterators := cache.iterators; # each iterator succeeds with probability at least 1/2, # if we exhaust all attempts @@ -1251,7 +1271,8 @@ RECOG.SnAnCacheUpperBoundForDegree := function(ri) if not IsInt(N) then return; fi; - # This is usually much smaller than RECOG.SnAnUpperBoundForDegree + # This is usually much smaller than RECOG.SnAnUpperBoundForDegree. + # The number to compare N with was chosen arbitrarily as a "large" degree. if N > 20 then degreeData := RECOG.GuessSnAnDegree(ri); if degreeData = fail then @@ -1268,9 +1289,9 @@ end; # In order to achieve this, we cache some important values for further # computations. It is the main function of SnAnUnknownDegree. RECOG.RecogniseSnAnLazy := function(ri) - local cache, N, tmp; - cache := RECOG.SnAnGetCache(ri); + local a, cache, N, tmp; RECOG.SnAnCacheUpperBoundForDegree(ri); + cache := RECOG.SnAnGetCache(ri); N := cache.N; if N = TemporaryFailure then RECOG.SnAnResetCache(ri); From adb2fce7e7ae1344ea8eaf8cde5c0911acc751a1 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Tue, 9 Dec 2025 14:59:01 +0100 Subject: [PATCH 19/25] Update changes text --- RECOG_SnAn | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/RECOG_SnAn b/RECOG_SnAn index f9efb88f..94a0bb4a 100644 --- a/RECOG_SnAn +++ b/RECOG_SnAn @@ -29,9 +29,12 @@ Here we collect the changes in `SnAnUnknownDegree` compared to the "vanilla" imp - Computation of upper bound `N` done as in `[L84]` and `[KL90]`. - Computation of lower bound `M` by element orders. - For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for some random elements `r` if for the element `x := c * c^r` either `x^5 = 1` or `x^6 = 1` holds (The element `x` needs to have order `1`, `2`, `3` or `5`). -- `ThreeCycleCandidatesIterator` uses a similar approach to that from the Magma Code `GetNextThreeCycle` and thus computes the elements in a different ordering. For every involution, we first consider only up to `K = 5` random conjugates. If none is successful, we move to the next involution. We save all involutions considered so far. - - If this failed (i.e., for all `B` involutions all `K` random conjugates failed), we proceed in a linear manner: - - We consider each involution in turn, and for each compute `K` new random conjugate. - Use `RecogniseSnAnLazy` that caches iterators constructed by `ThreeCycleCandidatesIterator` and returns `TemporaryFailure` more quickly. -Here we work in batches of at most `L` involutions and batches of at most `K` conjugates per involution. +- `ThreeCycleCandidatesIterator` uses a similar approach to that from the Magma Code `GetNextThreeCycle` and thus computes the elements in a different ordering. + - We work in batches of at most `L = 10` involutions in a linear manner. We save all involutions considered so far. + - For every involution, we consider only up to `K = 5` random conjugates. If none is successful, we move to the next involution. We save all involutions considered so far. + - If a batch of involutions reaches the last involution, i.e. the `B`-th one, we start with the first involution in the next round. + - After a batch of involutions was completely processed, we return `SnAnTryLater` and exit the recognition method in the lazy variant. + - However, as in the vanilla implementation, we return `TemporaryFailure` if for all `B` involutions + either `C` conjugates have been tested in total or `T` conjugates were proven to commutate with the involution. - Use Conder's Thesis to compute images for degree `n <= 10`. From 458b1b127e198bd7cb782ebb9e533eadcd4a8ea5 Mon Sep 17 00:00:00 2001 From: Friedrich Rober Date: Tue, 9 Dec 2025 16:43:47 +0100 Subject: [PATCH 20/25] Performance fixes: - deactivate guessdegree code - insert SnAnUnknownDegree after deletedpermutationmodule code in projective.gi --- RECOG_SnAn | 11 ++++++----- gap/generic/SnAnUnknownDegree.gi | 20 ++++++++++---------- gap/projective.gi | 4 ++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/RECOG_SnAn b/RECOG_SnAn index 94a0bb4a..0bed08c8 100644 --- a/RECOG_SnAn +++ b/RECOG_SnAn @@ -10,17 +10,18 @@ If we pass an `A5`, `S5`, `A6`, `S6` into `SnAnUnknownDegree`, then it will not If the input acts on a space with a large dimension, then this can take forever. - We assume that our input group `G` is a projective irreducible matrix group. -- Deduce an upper bound `N` for the degree of `An`, `Sn`. +- Deduce an upper bound `N` for the degree of `An`, `Sn` by using bounds provided in `[KL90]`. - Look at some orders and deduce a lower bound `M` for the degree. - If `M > N`, then we excluded `An`, `Sn`. - If `M <= 6`, then we return `TemporaryFailure`. - - If `N > 20`, use Magma Code `GuessSnAnDegree` to guess the degree by element orders. + - TODO: If `N > 20`, use Magma Code `GuessSnAnDegree` to guess the degree by element orders. + - TODO: If `GuessSnAnDegree` was used, then we check if we can found an element of order in the interval `[N - m, N + m]`. - Otherwise, we continue. - Now we can assume for the degree `n >= 7`. We need to ensure this, since `SnAnUnknownDegree` cannot recognise `A5`, `S5`, `A6`, `S6` and could run for a considerable time. - Monte-Carlo: Try to compute standard generators and degree `n` of largest `An < G` via the algorithm in `[JLNP13]`. - Try to compute an isomorphism from `G` to `An` or `Sn`. - - If `n < 11`, then use methods from Conder + - If `n < 11`, then use methods from Jonathan Conder (phd thesis) - Otherwise, we have `n >= 11` and use methods from `SnAnKnownDegree` in `[BLGN+03]` ## Changes @@ -30,9 +31,9 @@ Here we collect the changes in `SnAnUnknownDegree` compared to the "vanilla" imp - Computation of lower bound `M` by element orders. - For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for some random elements `r` if for the element `x := c * c^r` either `x^5 = 1` or `x^6 = 1` holds (The element `x` needs to have order `1`, `2`, `3` or `5`). - Use `RecogniseSnAnLazy` that caches iterators constructed by `ThreeCycleCandidatesIterator` and returns `TemporaryFailure` more quickly. -- `ThreeCycleCandidatesIterator` uses a similar approach to that from the Magma Code `GetNextThreeCycle` and thus computes the elements in a different ordering. +- `ThreeCycleCandidatesIterator` uses a similar approach to that from the Magma Code `GetNextThreeCycle` and thus computes the elements in a different ordering to the paper. - We work in batches of at most `L = 10` involutions in a linear manner. We save all involutions considered so far. - - For every involution, we consider only up to `K = 5` random conjugates. If none is successful, we move to the next involution. We save all involutions considered so far. + - For every involution, we consider only up to `K = 5` random conjugates. If none is successful, we move to the next involution. - If a batch of involutions reaches the last involution, i.e. the `B`-th one, we start with the first involution in the next round. - After a batch of involutions was completely processed, we return `SnAnTryLater` and exit the recognition method in the lazy variant. - However, as in the vanilla implementation, we return `TemporaryFailure` if for all `B` involutions diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 5dc57684..e345f7c7 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -1204,7 +1204,7 @@ RECOG.SnAnUpperBoundForDegree := function(ri) # N = upper bound for degree # Check magma if IsPermGroup(G) then - # We assume that G is primitive and not a giant. + # We assume that G is primitive and not a giant in natural representation. # The smallest non-natural primitive action of Sn or An induces # a large base group. Thus by [L84] its degree is smallest, when the # action is on 2-subsets. Thus its degree is at least n * (n-1) / 2. @@ -1273,15 +1273,15 @@ RECOG.SnAnCacheUpperBoundForDegree := function(ri) fi; # This is usually much smaller than RECOG.SnAnUpperBoundForDegree. # The number to compare N with was chosen arbitrarily as a "large" degree. - if N > 20 then - degreeData := RECOG.GuessSnAnDegree(ri); - if degreeData = fail then - cache.N := TemporaryFailure; - return; - fi; - N := Minimum(N, degreeData.degree); - cache.N := N; - fi; + # if N > 20 then + # degreeData := RECOG.GuessSnAnDegree(ri); + # if degreeData = fail then + # cache.N := TemporaryFailure; + # return; + # fi; + # N := Minimum(N, degreeData.degree); + # cache.N := N; + # fi; end; # See RECOG.RecogniseSnAn. The difference is, that we give up at an earlier diff --git a/gap/projective.gi b/gap/projective.gi index 1d7d9c51..ff9601ef 100644 --- a/gap/projective.gi +++ b/gap/projective.gi @@ -313,8 +313,6 @@ AddMethod(FindHomDbProjective, FindHomMethodsProjective.NotAbsolutelyIrred, 1100 # AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnSmallUnknownDegree, 1075); -AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnUnknownDegree, 1070); - AddMethod(FindHomDbProjective, FindHomMethodsProjective.ClassicalNatural, 1050); AddMethod(FindHomDbProjective, FindHomMethodsProjective.Subfield, 1000); @@ -336,6 +334,8 @@ AddMethod(FindHomDbProjective, FindHomMethodsProjective.D247, 840); AddMethod(FindHomDbProjective, FindHomMethodsProjective.AltSymBBByDegree, 810); +AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnUnknownDegree, 805); + AddMethod(FindHomDbProjective, FindHomMethodsProjective.TensorDecomposable, 800); AddMethod(FindHomDbProjective, FindHomMethodsProjective.FindElmOfEvenNormal, 700); From 01f39bf8b768c2b4306993ca91a2321e761b77d4 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Thu, 11 Dec 2025 22:35:22 +0100 Subject: [PATCH 21/25] Removing duplicate of RECOG.GuessSnAnDegree --- contrib/derek/SnAnKnownDegree.gi | 129 ------------------------------- 1 file changed, 129 deletions(-) delete mode 100644 contrib/derek/SnAnKnownDegree.gi diff --git a/contrib/derek/SnAnKnownDegree.gi b/contrib/derek/SnAnKnownDegree.gi deleted file mode 100644 index 00bc794d..00000000 --- a/contrib/derek/SnAnKnownDegree.gi +++ /dev/null @@ -1,129 +0,0 @@ -# Inspired from Magma Code: GuessAltsymDegree, in magma/package/Group/GrpFin/SimpleRecog/altsym.m -# Returns a guess at alternating or symmetric and degree n -# (It won't work for Sym(3) or Sym(6)!) -# -# This function samples projective orders of elements, and attempts to guess -# degree n and whether it is Alternating or Symmetric. -# Returns a record with entries: -# - type : string "Alternating" or "Symmetric" -# - degree : integer n -# Returns fail if n<=6 or maxtries elements are sampled with -# no decision made. -# -# At least Max(mintries,fac*n*Log(n)) random elements are chosen without -# the answer changing, where mintries, fac can be given as an optional -# arguments. -# -# TODO: Investigate why Alt(9) and Sym(8) return fail -# TODO: Might be inspired from -# "Fast Constructive Recognition of a Black Box Group Isomorphic to Sn or An using Goldbach’s Conjecture" -# by Sergey Bratus and Igor Pak, -# in Chapter 9. "What To Do If n is Not Known?" - -RECOG.GuessSnAnDegree := function(ri, optionlist...) - local G, r, options, mintries, maxtries, fac, mindego, mindege, ct, cto, cte, proc, g, o, mindeg, o_fact, mindegforg; - # mindego and mindege will be respectively the smallest possible - # degrees of symmetric groups that contain the elements of odd and - # even orders, in the random sample. - # If mindego > mindege we assume the group is alternating, otherwise - # that it is symmetric. - - G := Grp(ri); - if (IsPermGroup(G) and NrMovedPoints(G) <= 6) - or (IsMatrixGroup(G) and DimensionOfMatrixGroup(G) < 3) then - Print("GuessAltsymDegree works only for degree > 6\n"); - return fail; - fi; - - # Set options - options := rec( - mintries := 100, - maxtries := 5000, - fac := 4 - ); - - if Length(optionlist) > 0 then - for r in RecNames(optionlist[1]) do - if not IsBound(options.(r)) then - ErrorNoReturn("Invalid option to GuessSnAnDegree: ", r); - fi; - options.(r) := optionlist[1].(r); - od; - fi; - - mintries := options.mintries; - maxtries := options.maxtries; - fac := options.fac; - - # Init Loop - mindego := 0; - mindege := 0; - cto := 0; - cte := 0; - ct := 0; - mindeg := 0; - if mintries < 1 then - mintries := 1; - fi; - - # Main Loop - while (ct < Maximum(mintries, fac * mindeg * Int(Ceil(Log(Float(mindeg+1))))) - or mindego = mindege+1) and ct <= maxtries do - # The situation mindego = mindege+1 was responsible for most errors - # in the first version! Alt(n+1) was returned instead of Sym(n). - g := RandomElm(ri, "GuessSnAnDegree", false)!.el;; - o := ri!.order(g); - ct := ct + 1; # counter of loop, as long as no new larger degree was detected - if o = 1 then - continue; - fi; - o_fact := Collected(Factors(o)); - mindegforg := Sum(o_fact, f -> f[1] ^ f[2]); # minimum degree is sum over all prime-powers in factorization - if o mod 2 = 0 then - cte := cte + 1; # counter for even orders - if mindegforg > mindege then - mindege := mindegforg; - if mindege > mindeg then - mindeg := mindege; - fi; - ct := 0; - # vprintf IsAltsym: "New E, E = %o, O = %o, elt order = %o, Randoms = %o\n", mindege, mindego, o_fact, cte+cto; - fi; - else - cto := cto + 1; # counter for odd orders - if mindegforg > mindego then - mindego := mindegforg; - if mindego > mindeg then - mindeg := mindego; - fi; - ct := 0; - # vprintf IsAltsym: "New O, E = %o, O = %o, elt order = %o, Randoms = %o\n", mindege, mindego, o_fact, cte+cto; - fi; - fi; - od; - - if ct > maxtries then - # vprintf IsAltsym: "maxtries exceeded - giving up!"; - return fail; - fi; - - # vprintf IsAltsym: "E = %o, O = %o, Randoms = %o\n", mindege, mindego, cte+cto; - - if mindego > mindege then - if mindego <= 6 then - # vprintf IsAltsym: "GuessAltsymDegree works only for degree > 6"; - return fail; - else - # vprintf IsAltsym: "Alternating of degree %o\n", mindego; - return rec(type := "Alternating", degree := mindego); - fi; - else - if mindege <= 6 then - # vprintf IsAltsym: "GuessAltsymDegree works only for degree > 6"; - return fail; - else - # vprintf IsAltsym: "Symmetric of degree %o\n", mindege; - return rec(type := "Symmetric", degree := mindege); - fi; - fi; -end; From 0a3e5a9aef2aba3c52189e5d72c3aadb7a21a6e6 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Thu, 11 Dec 2025 22:40:09 +0100 Subject: [PATCH 22/25] Turn RECOG_SnAn into a code comment --- RECOG_SnAn | 41 ---------------------- gap/generic/SnAnUnknownDegree.gi | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 41 deletions(-) delete mode 100644 RECOG_SnAn diff --git a/RECOG_SnAn b/RECOG_SnAn deleted file mode 100644 index 0bed08c8..00000000 --- a/RECOG_SnAn +++ /dev/null @@ -1,41 +0,0 @@ -# SnAnUnknownDegree - -This file describes details of the `SnAnUnknownDegree` implementation in `recog`. The algorithm is based on the paper `[JLNP13]`. - -## General Strategy -Here we describe the general strategy that is used to recognise if a group is ismorphic to `An` or `Sn` for `n >= 7`. - -We have to be careful what we do for small degrees. -If we pass an `A5`, `S5`, `A6`, `S6` into `SnAnUnknownDegree`, then it will not recognize it. -If the input acts on a space with a large dimension, then this can take forever. - -- We assume that our input group `G` is a projective irreducible matrix group. -- Deduce an upper bound `N` for the degree of `An`, `Sn` by using bounds provided in `[KL90]`. -- Look at some orders and deduce a lower bound `M` for the degree. - - If `M > N`, then we excluded `An`, `Sn`. - - If `M <= 6`, then we return `TemporaryFailure`. - - TODO: If `N > 20`, use Magma Code `GuessSnAnDegree` to guess the degree by element orders. - - TODO: If `GuessSnAnDegree` was used, then we check if we can found an element of order in the interval `[N - m, N + m]`. - - Otherwise, we continue. -- Now we can assume for the degree `n >= 7`. We need to ensure this, since `SnAnUnknownDegree` cannot recognise `A5`, `S5`, `A6`, `S6` - and could run for a considerable time. -- Monte-Carlo: Try to compute standard generators and degree `n` of largest `An < G` via the algorithm in `[JLNP13]`. -- Try to compute an isomorphism from `G` to `An` or `Sn`. - - If `n < 11`, then use methods from Jonathan Conder (phd thesis) - - Otherwise, we have `n >= 11` and use methods from `SnAnKnownDegree` in `[BLGN+03]` - -## Changes -Here we collect the changes in `SnAnUnknownDegree` compared to the "vanilla" implementation of the algorithm according to the paper `[JLNP13]`. - -- Computation of upper bound `N` done as in `[L84]` and `[KL90]`. -- Computation of lower bound `M` by element orders. -- For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for some random elements `r` if for the element `x := c * c^r` either `x^5 = 1` or `x^6 = 1` holds (The element `x` needs to have order `1`, `2`, `3` or `5`). -- Use `RecogniseSnAnLazy` that caches iterators constructed by `ThreeCycleCandidatesIterator` and returns `TemporaryFailure` more quickly. -- `ThreeCycleCandidatesIterator` uses a similar approach to that from the Magma Code `GetNextThreeCycle` and thus computes the elements in a different ordering to the paper. - - We work in batches of at most `L = 10` involutions in a linear manner. We save all involutions considered so far. - - For every involution, we consider only up to `K = 5` random conjugates. If none is successful, we move to the next involution. - - If a batch of involutions reaches the last involution, i.e. the `B`-th one, we start with the first involution in the next round. - - After a batch of involutions was completely processed, we return `SnAnTryLater` and exit the recognition method in the lazy variant. - - However, as in the vanilla implementation, we return `TemporaryFailure` if for all `B` involutions - either `C` conjugates have been tested in total or `T` conjugates were proven to commutate with the involution. -- Use Conder's Thesis to compute images for degree `n <= 10`. diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index e345f7c7..d8dc1fd1 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -18,6 +18,65 @@ ## ############################################################################# +## General Strategy +## ---------------- +## +## Here we describe the general strategy that is used to recognise if a group +## is ismorphic to `An` or `Sn` for `n >= 7`. +## +## We have to be careful what we do for small degrees. +## If we pass an `A5`, `S5`, `A6`, `S6` into `SnAnUnknownDegree`, then it will not recognize it. +## If the input acts on a space with a large dimension, then this can take forever. +## +## - We assume that our input group `G` is a projective irreducible matrix group. +## - Deduce an upper bound `N` for the degree of `An`, `Sn` by using bounds provided in [KL90]. +## - Look at some orders and deduce a lower bound `M` for the degree. +## - If `M > N`, then we excluded `An`, `Sn`. +## - If `M <= 6`, then we return `TemporaryFailure`. +## - TODO: If `N > 20`, use Magma Code `GuessSnAnDegree` to guess the degree by element orders. +## - TODO: If `GuessSnAnDegree` was used, then we check if we can found +## an element of order in the interval `[N - m, N + m]`. +## - Otherwise, we continue. +## - Now we can assume for the degree `n >= 7`. We need to ensure this, since +## `SnAnUnknownDegree` cannot recognise `A5`, `S5`, `A6`, `S6` and could run +## for a considerable time. +## - Monte-Carlo: Try to compute standard generators and degree `n` of +## largest `An < G` via the algorithm in [JLNP13]. +## - Try to compute an isomorphism from `G` to `An` or `Sn`. +## - If `n < 11`, then use methods from Jonathan Conder (phd thesis) +## - Otherwise, we have `n >= 11` and use methods from `SnAnKnownDegree` in [BLGN+03] +## +## Changes +## ------- +## Here we collect the changes in `SnAnUnknownDegree` compared to a "vanilla" +## implementation of the algorithm according to the paper [JLNP13]. +## +## - Computation of upper bound `N` done as in `[L84]` and `[KL90]`. +## - Computation of lower bound `M` by element orders. +## - For each ThreeCycleCandidate `c` we check if `c^3 = 1` and we check for +## some random elements `r` if for the element `x := c * c^r` either `x^5 = 1` +## or `x^6 = 1` holds (The element `x` needs to have order `1`, `2`, `3` or `5`). +## - Use `RecogniseSnAnLazy` that caches iterators constructed by +## `ThreeCycleCandidatesIterator` and returns `TemporaryFailure` more +## quickly. +## - `ThreeCycleCandidatesIterator` uses a similar approach to that from the +## Magma Code `GetNextThreeCycle` and thus computes the elements in a +## different ordering to the paper. +## - We work in batches of at most `L = 10` involutions in a linear manner. +## We save all involutions considered so far. +## - For every involution, we consider only up to `K = 5` random +## conjugates. If none is successful, we move to the next involution. +## - If a batch of involutions reaches the last involution, i.e. the `B`-th +## one, we start with the first involution in the next round. +## - After a batch of involutions was completely processed, we return +## `SnAnTryLater` and exit the recognition method in the lazy variant. +## - However, as in the vanilla implementation, we return +## `TemporaryFailure` if for all `B` involutions either `C` conjugates have +## been tested in total or `T` conjugates were proven to commutate with the +## involution. +## - Use Conder's Thesis to compute images for degree `n <= 10`. + + RECOG.SnAnDebug := false; BindGlobal("SnAnTryLater", MakeImmutable("SnAnTryLater")); From a6d26f92842d47642c146756bf6c6b13137224fc Mon Sep 17 00:00:00 2001 From: Max Horn Date: Thu, 11 Dec 2025 22:49:46 +0100 Subject: [PATCH 23/25] Get rid of SnAnRepeatImmediately --- gap/generic/SnAnUnknownDegree.gi | 182 +++++++++++++++---------------- 1 file changed, 85 insertions(+), 97 deletions(-) diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index d8dc1fd1..11dc164a 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -80,7 +80,6 @@ RECOG.SnAnDebug := false; BindGlobal("SnAnTryLater", MakeImmutable("SnAnTryLater")); -BindGlobal("SnAnRepeatImmediately", MakeImmutable("SnAnRepeatImmediately")); RECOG.SnAnGetCache := function(ri) if not IsBound(ri!.SnAnUnknownDegreeCache) then @@ -172,7 +171,7 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # counters Ki, Li, curInvolutionPos, # helper functions - tryThreeCycleCandidate, oneThreeCycleCandidate, + oneThreeCycleCandidate, # used for debugging cache; # Step 1: Initialization @@ -218,13 +217,12 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) fi; # Helper functions - # tryThreeCycleCandidate returns one of the following: + # oneThreeCycleCandidate returns one of the following: # - a three cycle candidate, i.e. an element of G # - TemporaryFailure, if we exhausted all attempts # - SnAnTryLater, if we tried all involution candidates K times - # - SnAnRepeatImmediately, if we have to call tryThreeCycleCandidate again # - NeverApplicable, if G can not be an Sn or An - tryThreeCycleCandidate := function() + oneThreeCycleCandidate := function() local # integer, loop variable a, @@ -233,102 +231,92 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants) # the three cycle candidate candidate; - # Steps 2 & 3: New involution - ######################################################################### - # We consider at most B involutions. - if curInvolutionPos > B then - curInvolutionPos := 1; - fi; - # We did not construct yet the involution to consider - if curInvolutionPos > Length(involutions) then - r := RandomElm(ri, "SnAnUnknownDegree", true)!.el; - # In the paper, we have t = r ^ M. - # Invariant: tPower = (r ^ M) ^ (2 ^ a) - tPower := r ^ M; - # We make a small improvement to the version described in - # . The order of r ^ M is a 2-power. - # It can be at most 2 ^ logInt2N. Thus, if we find an r such that - # (r ^ M) ^ (2 ^ logInt2N) is non-trivial, then we can return - # NeverApplicable. - for a in [1 .. logInt2N] do - tPowerOld := tPower; - tPower := tPower ^ 2; - if isone(ri)(tPower) then break; fi; - od; - if not isone(ri)(tPower) then - return NeverApplicable; - fi; - involutions[curInvolutionPos] := tPowerOld; - nrTriedConjugates[curInvolutionPos] := 0; - nrCommutatingConjugates[curInvolutionPos] := 0; - nrThreeCycleCandidates[curInvolutionPos] := 0; - fi; - # Check if we either tried enough conjugates or already found enough - # commutating conjugates for all involutions t. - # If this is the case, then we have exhausted all attempts. - if curInvolutionPos = B - and ForAll([1 .. B], i -> nrTriedConjugates[i] >= C or nrCommutatingConjugates[i] >= T) - then - return TemporaryFailure; - fi; - # If we either considered enough conjugates or already found enough - # commutating conjugates for the current involution t, - # we need to consider the next involution, or reached the end of our batch of involutions. - # Recall, that we work in batches for the lazy version. - # We work in batches of at most L involutions and for these, - # in batches of K conjugates. - if nrTriedConjugates[curInvolutionPos] >= Ki or nrCommutatingConjugates[curInvolutionPos] >= T then - if curInvolutionPos = B then - Li := L; - Li := Minimum(Li, B); - Ki := Ki + K; - Ki := Minimum(Ki, C); + while true do + # Steps 2 & 3: New involution + ######################################################################### + # We consider at most B involutions. + if curInvolutionPos > B then curInvolutionPos := 1; - return SnAnTryLater; - elif curInvolutionPos = Li then - Li := Li + L; - Li := Minimum(Li, B); - curInvolutionPos := curInvolutionPos + 1; - return SnAnTryLater; - else - curInvolutionPos := curInvolutionPos + 1; - return SnAnRepeatImmediately; fi; - fi; + # We did not construct yet the involution to consider + if curInvolutionPos > Length(involutions) then + r := RandomElm(ri, "SnAnUnknownDegree", true)!.el; + # In the paper, we have t = r ^ M. + # Invariant: tPower = (r ^ M) ^ (2 ^ a) + tPower := r ^ M; + # We make a small improvement to the version described in + # . The order of r ^ M is a 2-power. + # It can be at most 2 ^ logInt2N. Thus, if we find an r such that + # (r ^ M) ^ (2 ^ logInt2N) is non-trivial, then we can return + # NeverApplicable. + for a in [1 .. logInt2N] do + tPowerOld := tPower; + tPower := tPower ^ 2; + if isone(ri)(tPower) then break; fi; + od; + if not isone(ri)(tPower) then + return NeverApplicable; + fi; + involutions[curInvolutionPos] := tPowerOld; + nrTriedConjugates[curInvolutionPos] := 0; + nrCommutatingConjugates[curInvolutionPos] := 0; + nrThreeCycleCandidates[curInvolutionPos] := 0; + fi; + # Check if we either tried enough conjugates or already found enough + # commutating conjugates for all involutions t. + # If this is the case, then we have exhausted all attempts. + if curInvolutionPos = B + and ForAll([1 .. B], i -> nrTriedConjugates[i] >= C or nrCommutatingConjugates[i] >= T) + then + return TemporaryFailure; + fi; + # If we either considered enough conjugates or already found enough + # commutating conjugates for the current involution t, + # we need to consider the next involution, or reached the end of our batch of involutions. + # Recall, that we work in batches for the lazy version. + # We work in batches of at most L involutions and for these, + # in batches of K conjugates. + if nrTriedConjugates[curInvolutionPos] >= Ki or nrCommutatingConjugates[curInvolutionPos] >= T then + if curInvolutionPos = B then + Li := L; + Li := Minimum(Li, B); + Ki := Ki + K; + Ki := Minimum(Ki, C); + curInvolutionPos := 1; + return SnAnTryLater; + elif curInvolutionPos = Li then + Li := Li + L; + Li := Minimum(Li, B); + curInvolutionPos := curInvolutionPos + 1; + return SnAnTryLater; + else + curInvolutionPos := curInvolutionPos + 1; + continue; # we have to start over + fi; + fi; - # Steps 4 & 5: new three cycle candidate - ######################################################################### - # Try to construct a three cycle candidate via a conjugate of t. See - # the comment above this function. - t := involutions[curInvolutionPos]; - nrTriedConjugates[curInvolutionPos] := nrTriedConjugates[curInvolutionPos] + 1; - c := t ^ RandomElm(ri, "SnAnUnknownDegree", true)!.el; - if isequal(ri)(t * c, c * t) then - # we have to call tryThreeCycleCandidate again - return SnAnRepeatImmediately; - fi; - nrCommutatingConjugates[curInvolutionPos] := nrCommutatingConjugates[curInvolutionPos] + 1; - candidate := (t * c) ^ 2; - # We now use a one-sided heuristic to test whether candidate can be a - # three cycle, that is the heuristic can detect whether candidate can - # not be a three cycle, e.g. if it does not have order three. - if RECOG.HeuristicThreeCycleTest(ri, candidate, logInt2N, R) then - nrThreeCycleCandidates[curInvolutionPos] := nrThreeCycleCandidates[curInvolutionPos] + 1; - return candidate; - else - return SnAnRepeatImmediately; - fi; - end; + # Steps 4 & 5: new three cycle candidate + ######################################################################### + # Try to construct a three cycle candidate via a conjugate of t. See + # the comment above this function. + t := involutions[curInvolutionPos]; + nrTriedConjugates[curInvolutionPos] := nrTriedConjugates[curInvolutionPos] + 1; + c := t ^ RandomElm(ri, "SnAnUnknownDegree", true)!.el; + if isequal(ri)(t * c, c * t) then + continue; # we have to start over + fi; + nrCommutatingConjugates[curInvolutionPos] := nrCommutatingConjugates[curInvolutionPos] + 1; + candidate := (t * c) ^ 2; + # We now use a one-sided heuristic to test whether candidate can be a + # three cycle, that is the heuristic can detect whether candidate can + # not be a three cycle, e.g. if it does not have order three. + if RECOG.HeuristicThreeCycleTest(ri, candidate, logInt2N, R) then + nrThreeCycleCandidates[curInvolutionPos] := nrThreeCycleCandidates[curInvolutionPos] + 1; + return candidate; + fi; - # construct the iterator - oneThreeCycleCandidate := function() - local candidate; - repeat - candidate := tryThreeCycleCandidate(); - until candidate <> SnAnRepeatImmediately; - # With probability at least 1 - eps we constructed at least one - # three cycle with this iterator. - return candidate; + # if we get here, we'll loop around and start over + od; end; return oneThreeCycleCandidate; From 7f2e750ac0bdb6549bab06fdcaddacd739e76fa8 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Thu, 11 Dec 2025 23:06:49 +0100 Subject: [PATCH 24/25] trivial tweaks --- gap/generic/SnAnUnknownDegree.gi | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gap/generic/SnAnUnknownDegree.gi b/gap/generic/SnAnUnknownDegree.gi index 11dc164a..aa6465cf 100644 --- a/gap/generic/SnAnUnknownDegree.gi +++ b/gap/generic/SnAnUnknownDegree.gi @@ -1052,7 +1052,7 @@ RECOG.RecogniseSnAnSingleIteration := function(ri, T, N) # if we exhaust all attempts for iterator in iterators do c := iterator(); - while not c in [SnAnTryLater, TemporaryFailure] do + while c <> SnAnTryLater and c <> TemporaryFailure do if c = NeverApplicable then return c; fi; @@ -1072,10 +1072,9 @@ RECOG.RecogniseSnAnSingleIteration := function(ri, T, N) # Now tmp contains [g, c, n] where # g, c correspond to standard generators of An recogData := RECOG.ConstructSnAnIsomorphism(ri, tmp[3], tmp{[1,2]}); - if recogData = fail then - continue; + if recogData <> fail then + return recogData; fi; - return recogData; od; od; return c; From 16bace05c4c91f59429b89e73fd30ea28a8d08b1 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Thu, 11 Dec 2025 23:08:40 +0100 Subject: [PATCH 25/25] restore bug fix --- gap/matrix.gi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gap/matrix.gi b/gap/matrix.gi index 6a10a211..3d5d9b1a 100644 --- a/gap/matrix.gi +++ b/gap/matrix.gi @@ -966,7 +966,7 @@ AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.KnownStabilizerChain, 1175); AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.SnAnUnknownDegree, 1070);; -AddMethod(FindHomDbPerm, FindHomMethodsGeneric.FewGensAbelian, 1050); +AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.FewGensAbelian, 1050); AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.ReducibleIso, 1000);