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..aa6465cf 100644
--- a/gap/generic/SnAnUnknownDegree.gi
+++ b/gap/generic/SnAnUnknownDegree.gi
@@ -17,7 +17,81 @@
## .
##
#############################################################################
-#
+
+## 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"));
+
+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
#
@@ -37,12 +111,35 @@ 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;
+# 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, 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 := 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;
+ 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
+# 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
@@ -51,6 +148,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 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
#
@@ -61,103 +160,180 @@ RECOG.ThreeCycleCandidatesIterator := function(ri, constants)
t,
# integers, controlling the number of iterations
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
+ involutions,
+ # counters
+ nrTriedConjugates, nrCommutatingConjugates, nrThreeCycleCandidates,
# counters
- nrInvolutions, nrTriedConjugates, nrThreeCycleCandidates,
+ Ki, Li, curInvolutionPos,
# helper functions
- tryThreeCycleCandidate, oneThreeCycleCandidate;
+ 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 := constants.K;
+ L := constants.L;
+ # 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);
+ 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.
- nrTriedConjugates := C;
+ # involution t_i.
+ nrTriedConjugates := [];
# counts the size of the set Gamma_i in step 4 for the current involution
# t_i
- nrThreeCycleCandidates := 0;
+ 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
+ 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,
+ nrCommutatingConjugates := nrCommutatingConjugates,
+ nrThreeCycleCandidates := nrThreeCycleCandidates
+ ));
+ 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
- # - 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
# - NeverApplicable, if G can not be an Sn or An
- tryThreeCycleCandidate := function()
+ oneThreeCycleCandidate := function()
local
# integer, loop variable
a,
# elements, in G
- r, tPower, tPowerOld, c;
- # 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;
- # Invariant: tPower = (r ^ M) ^ (2 ^ a)
- # 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;
+ r, tPower, tPowerOld, c,
+ # the three cycle candidate
+ candidate;
+
+ while true do
+ # Steps 2 & 3: New involution
+ #########################################################################
+ # We consider at most B involutions.
+ if curInvolutionPos > B then
+ curInvolutionPos := 1;
fi;
- t := tPowerOld;
- nrInvolutions := nrInvolutions + 1;
- nrTriedConjugates := 0;
- nrThreeCycleCandidates := 0;
- 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;
- c := t ^ RandomElm(ri, "SnAnUnknownDegree", true)!.el;
- if not isequal(ri)(t * c, c * t) then
- nrThreeCycleCandidates := nrThreeCycleCandidates + 1;
- return (t * c) ^ 2;
- else
- # we have to call tryThreeCycleCandidate again
- return fail;
- fi;
- end;
- # construct the iterator
- oneThreeCycleCandidate := function()
- local candidate;
- repeat
- if nrInvolutions >= B
- and (nrTriedConjugates >= C or nrThreeCycleCandidates >= T)
+ # 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
- # With probability at least 1 - eps we constructed at least one
- # three cycle with this iterator.
- return fail;
+ return TemporaryFailure;
fi;
- candidate := tryThreeCycleCandidate();
- if candidate = NeverApplicable then
- return NeverApplicable;
+ # 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
+ 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;
- until candidate <> fail;
- return candidate;
+
+ # if we get here, we'll loop around and start over
+ od;
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
@@ -604,7 +780,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;
@@ -641,32 +817,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 +990,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,49 +1009,52 @@ 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.
+# ri : recognition node with group G,
+# T : integer, number of iterators
+# N : integer, upper bound for the degree of G
#
-# 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);
+# 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;
+ 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
+ for iterator in iterators do
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;
+ while c <> SnAnTryLater and c <> TemporaryFailure do
+ if c = NeverApplicable then
+ return c;
fi;
tmp := RECOG.ConstructLongCycle(ri, c, 1. / 8., N);
if tmp = fail then
@@ -752,7 +1064,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;
@@ -760,33 +1072,185 @@ 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;
- return recogData;
+ if recogData <> fail then
+ return recogData;
+ fi;
od;
od;
- return TemporaryFailure;
+ return c;
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.
-#! 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).
-#!
-#! @EndChunk
-BindRecogMethod(FindHomMethodsGeneric, "SnAnUnknownDegree",
-"method groups isomorphic to Sn or An with n >= 11",
-function(ri, G)
- local eps, N, p, d, recogData, isoData, degree, swapSLP;
- #G := Grp(ri);
- # TODO find value for eps
- eps := 1 / 10^2;
+# 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;
+ 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;
+
+# 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 := OrderFunc(ri)(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
- # 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.
@@ -826,8 +1290,96 @@ 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 < 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;
+
+ 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.
+ # 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;
+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 a, cache, N, tmp;
+ RECOG.SnAnCacheUpperBoundForDegree(ri);
+ cache := RECOG.SnAnGetCache(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
+#! 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 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;
@@ -851,13 +1403,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/matrix.gi b/gap/matrix.gi
index 9d439291..3d5d9b1a 100644
--- a/gap/matrix.gi
+++ b/gap/matrix.gi
@@ -964,6 +964,8 @@ AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.DiagonalMatrices, 1100);
AddMethod(FindHomDbMatrix, FindHomMethodsMatrix.KnownStabilizerChain, 1175);
+AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.SnAnUnknownDegree, 1070);;
+
AddMethod(FindHomDbMatrix, FindHomMethodsGeneric.FewGensAbelian, 1050);
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);
diff --git a/gap/projective.gi b/gap/projective.gi
index d46910c3..ff9601ef 100644
--- a/gap/projective.gi
+++ b/gap/projective.gi
@@ -311,6 +311,8 @@ AddMethod(FindHomDbProjective, FindHomMethodsMatrix.ReducibleIso, 1200);
AddMethod(FindHomDbProjective, FindHomMethodsProjective.NotAbsolutelyIrred, 1100);
+# AddMethod(FindHomDbProjective, FindHomMethodsGeneric.SnAnSmallUnknownDegree, 1075);
+
AddMethod(FindHomDbProjective, FindHomMethodsProjective.ClassicalNatural, 1050);
AddMethod(FindHomDbProjective, FindHomMethodsProjective.Subfield, 1000);
@@ -332,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);
diff --git a/tst/working/quick/GenericSnAnUnknownDegree.tst b/tst/working/quick/GenericSnAnUnknownDegree.tst
index 3848d74b..294aec2c 100644
--- a/tst/working/quick/GenericSnAnUnknownDegree.tst
+++ b/tst/working/quick/GenericSnAnUnknownDegree.tst
@@ -1,45 +1,64 @@
-#@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);;
+# 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 := [[5, 2], [7, 2], [8, 3], [9, 3], [10, 3], [11, 3], [12, 3], [13, 3]];;
-# 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;
+#
+# PermGroup action on k-sets
+gap> PermOnKSets := function(G, k)
+> local sets;
+> sets := Combinations([1 .. NrMovedPoints(G)], k);;
+> return Action(G, sets, OnSets);;
+> end;;
+gap> AltOnKSets := function(d, k)
+> return PermOnKSets(AlternatingGroup(d), k);
+> end;;
+gap> SymOnKSets := function(d, k)
+> return PermOnKSets(SymmetricGroup(d), k);
+> end;;
-# 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;
+#
+gap> altPermGroups := List(dataPerm, entry -> AltOnKSets(entry[1], entry[2]));;
+gap> symPermGroups := List(dataPerm, entry -> SymOnKSets(entry[1], entry[2]));;
+
+#
+gap> dataMat := [[5, 4], [7, 3], [8, 5], [11, 7]];;
-# 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
+#
+# Permutation Matrix Group
+gap> PermMatGroup := function(G, q) return Group(List(
+> GeneratorsOfGroup(G),
+> x -> ImmutableMatrix(q, PermutationMat(x, NrMovedPoints(G), GF(q)))
+> )); end;;
+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]));;
#
-# Remove Hacky injection of our method
-gap> for db in [FindHomDbPerm, FindHomDbMatrix, FindHomDbProjective] do
-> Remove(db,
-> PositionProperty(db, x -> Stamp(x.method) = "SnAnUnknownDegree"));;
+gap> nonAltOrSymGroups := [
+> PSL(3, 5),
+> SL(3, 5),
+> Omega(1, 4, 3),
+> ];;
+
+# Test
+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(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
+> ri := RecogNode(nonAltOrSymGroups[i]);
+> if FindHomMethodsGeneric.SnAnUnknownDegree(ri, Grp(ri)) = Success then
+> Print("ERROR: Recognised group [", i, "] wrongly as Sn/An!\n");
+> fi;
> od;
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;