diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 886708c11..8bf7db9a6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -29,7 +29,6 @@ jobs: - '4.15' - '4.14' - '4.13' - - '4.12' steps: - uses: actions/checkout@v6 diff --git a/PackageInfo.g b/PackageInfo.g index c9c6fafbb..936ab5fa3 100644 --- a/PackageInfo.g +++ b/PackageInfo.g @@ -266,7 +266,7 @@ PackageDoc := rec( ), Dependencies := rec( - GAP := ">=4.12", + GAP := ">=4.13", NeededOtherPackages := [ ["AtlasRep", ">= 1.4.0"], ["FactInt", ">= 1.5.2"], diff --git a/contrib/frank/sl2/NatSL2q.g b/contrib/frank/sl2/NatSL2q.g deleted file mode 100644 index 7e5c6504f..000000000 --- a/contrib/frank/sl2/NatSL2q.g +++ /dev/null @@ -1,222 +0,0 @@ -#################### (c) 2025 Frank Lübeck ########################## -## -## G must be SL(2,q) generated by 2x2 matrices over GF(q). -## Returns [slps, mat] where slps is list of SLPs in given generators of G -## to elements which are conjugate by mat to standard/nice generators: -## t, u1, u2 = diag(1/Z(q),Z(q)) [[1,0],[1,1]], [[1,1],[0,1]] -## -## We construct elements with the strategy of Section 2. from the article -## Conder, Leedham-Green, Fast recognition of classical groups -## over large fields, Groups and Computation III, 2001. -## and then adjust them to the form described above. -## -## Some comments: -## - To find short SLP to the nice generators we avoid 'PseudoRandom(g)' -## and instead just start with the trivial element and multiply random -## generators to it. -## This should be good enough because in both cases (element xm and twice -## element cm in the code) almost half of the elements in G are suitable. -## -## - The list of SLPs in the result if probably what is needed for the -## SLPforNiceGens for the RecogNode. -## -## - To find for a given element A in G an SLP in the nice generators compute -## Astd := A^mat and use the function 'SLPinStandardSL2' from the file -## "SL2.g". -## -## - If q = 2^m with odd m then the computation of the eigenvalues of xm -## needs the quadratic extension GF(2^(2m)). -## -## -## Example (takes about 15 seconds on my notebook): -## Size(SL(2,23^15)); -## G:=Group(List([1..4],i->PseudoRandom(SL(2,23^15)))); -## l:=RecogNaturalSL2(G,23^15); -## nicegens:=List(l[1],a->ResultOfStraightLineProgram(a,GeneratorsOfGroup(G))); -## List(last, a->a^l[2]); -## -RecogNaturalSL2 := function(G, q) - local GM, one, zero, qm1fac, c, m, gens, xm, x, pol, v, z, exp, a, - mat, tm, ym, y, ymat, tr, d, cm, r1, r2, r, log, i, trupm, - smm, trlowm, F, a2, bas, e, l, emax, tmp; - GM := GroupWithMemory(G); - one := OneOfBaseDomain(G.1); - zero := Zero(one); - - # find element x of order q-1 - # (a power of x will become the nice generator t later) - qm1fac := Factors(q-1); - c := Product(Set(qm1fac)); - m := (q-1)/c; - gens := GeneratorsOfGroup(GM); - xm := One(GM); - repeat - #xm := PseudoRandom(GM); - xm := xm*Random(gens); - x := StripMemory(xm); - pol := [one, -x[1,1]-x[2,2], one]; - v := [zero, one]; - v := PowerModCoeffs(v, m, pol); - until PowerModCoeffs(v, c, pol) = [one] and - ForAll(qm1fac, p-> PowerModCoeffs(v, c/p, pol) <> [one]); - # eigenvalues and eigenvectors of x (zeroes of pol) - # we use Cantor-Zassenhaus - z := Z(q); - if q mod 2 = 0 then - if q mod 3 = 1 then - exp := (q-1)/3; - else - exp := (q^2-1)/3; - z := Z(q^2); - fi; - else - exp := (q-1)/2; - fi; - repeat - v := [z^Random(0,q-1), one]; - v := PowerModCoeffs(v, exp, pol); - until Length(v) = 2 and ValuePol(pol, (-v[1]+one)/v[2]) = zero; - a := (-v[1]+one)/v[2]; - # colums of mat are eigenvectors for a and 1/a (x^mat is diagonal) - mat := [[-x[1,2], -x[1,2]], [x[1,1]-a, x[1,1]-1/a]]; - if mat[1,1] = zero and mat[2,1] = zero then - mat[1,1] := x[2,2]-a; mat[2,1] := -x[2,1]; - fi; - if mat[1,2] = zero and mat[2,2] = zero then - mat[1,2] := x[2,2]-1/a; mat[2,2] := -x[2,1]; - fi; - - - # find conjugate of x with different eigenspaces - # (almost all conjugates will do) - tm := One(GM); - repeat - tm := tm * Random(gens); - ym := tm*xm*tm^-1; - y := StripMemory(ym); - ymat := y*mat; - until ymat[1,1]*mat[2,1]-ymat[2,1]*mat[1,1] <> zero and - ymat[1,2]*mat[2,2]-ymat[2,2]*mat[1,2] <> zero; - # now y^(tm * mat) = diag(a, a^-1) - tr := tm*mat; - - Assert(3, y^(tm * mat) = DiagonalMat([a, a^-1]) ); - - # a-eigenvector of x in new basis - d := tr^-1 * [mat[1,1],mat[2,1]]; - # can be scaled to [1,d] - d := d[2]/d[1]; - cm := One(GM); - repeat - # look for cm with non-trivial conditions (i <> 0, (q-1)/2) - repeat - cm := cm*Random(gens); - c := StripMemory(cm)^tr; - r1 := c[2,1]+d*c[2,2]; - r2 := d^2*c[1,2]+d*c[1,1]; - until r2 <> zero and r1 <> zero and r1 <> r2 and r1 <> -r2;; - r := r1 / r2; - log := DLog(a, r, qm1fac); - i := false; - if log mod 2 = 0 then - i := log/2; - elif q mod 2 = 0 then - i := (q-1-log)/2; - fi; - if IsInt(i) then - # this will in most cases be a transvection normalized by x - trupm := Comm(xm, ym^i*cm); - smm := trupm^mat; - if smm[1,2] = zero or smm[2,1] <> zero then - i := false; - else - # rescale first column of mat such that trupm^mat = [[1,1],[0,1]] - mat[1,1] := mat[1,1]*smm[1,2]; - mat[2,1] := mat[2,1]*smm[1,2]; - tr := tm*mat; - fi; - fi; - until IsInt(i); - - Assert(3, trupm^mat = [[1,1],[0,1]] * one); - - # same for the other eigenvector of x: - # 1/a-eigenvector of x in new basis - d := tr^-1 * [mat[1,2],mat[2,2]]; - # can be scaled to [1,d] - d := d[2]/d[1]; - cm := One(GM); - repeat - # look for cm with non-trivial conditions (i <> 0, (q-1)/2) - repeat - cm := cm*Random(gens); - c := StripMemory(cm)^tr; - r1 := c[2,1]+d*c[2,2]; - r2 := d^2*c[1,2]+d*c[1,1]; - until r2 <> zero and r1 <> zero and r1 <> r2 and r1 <> -r2;; - r := r1 / r2; - log := DLog(a, r, qm1fac); - i := false; - if log mod 2 = 0 then - i := log/2; - elif q mod 2 = 0 then - i := (q-1-log)/2; - fi; - if IsInt(i) then - # in most cases a transvection which becomes conjugated by mat - # lower triangular (here it is more difficult to rescale such - # that the conjugate matrix is [[1,0],[1,1]]). - trlowm := Comm(xm, ym^i*cm); - smm := trlowm^mat; - if smm[2,1] = zero or smm[1,2] <> zero then - i := false; - fi; - fi; - until IsInt(i); - - # adjust lower left entry of trlowm^mat to one - # (we use F_p linear algebra in F_q to find the nice element - # of products of trlowm^(x^i) for some small i) - if smm[2,1] <> one then - F := GF(q); - a2 := a^2; - bas := [smm[2,1]]; - e := DegreeOverPrimeField(F); - for i in [1..e-1] do - Add(bas, bas[i]*a2); - od; - bas := Basis(F, bas); - l := List(Coefficients(bas, one), IntFFE); - emax := e; - while l[emax] = 0 do - emax := emax-1; - od; - tmp := trlowm; - if l[1] = 0 then - trlowm := One(GM); - else - trlowm := tmp^l[1]; - fi; - for i in [2..emax] do - tmp := tmp^xm; - if l[i] <> 0 then - trlowm := trlowm*tmp^l[i]; - fi; - od; - fi; - - Assert(3, trlowm^mat = [[1,0],[1,1]] * one); - - # finally power x to change a to 1/Z(q) - if a <> 1/Z(q) then - log := DLog(a, 1/Z(q), qm1fac); - xm := xm^log; - fi; - - Assert(3, xm^mat = DiagonalMat([Z(q)^-1, Z(q)])); - - # return SLPs of elements mapped by mat to - # diag(1/Z(q),Z(q))[[1,0],[1,1]], [[1,1],[0,1]], - # and mat - return [List([xm, trlowm, trupm], SLPOfElm), mat]; -end; diff --git a/gap/projective/classicalnatural.gi b/gap/projective/classicalnatural.gi index c24057070..9b4fe8e01 100644 --- a/gap/projective/classicalnatural.gi +++ b/gap/projective/classicalnatural.gi @@ -926,13 +926,8 @@ function(ri) # This is (P)SL2, lets set up the recognition: Info(InfoRecog,2,"ClassicalNatural: this is PSL_2!"); - if IsEvenInt(q) then - std := RECOG.RecogniseSL2NaturalEvenChar(gm,f,false); - ri!.comment := "PSL2Even"; - else - std := RECOG.RecogniseSL2NaturalOddCharUsingBSGS(gm,f); - ri!.comment := "PSL2Odd"; - fi; + std := RECOG.RecogniseSL2Natural(gm,f); + ri!.comment := "PSL2"; Setslptonice(ri,SLPOfElms(std.all)); ri!.nicebas := std.bas; ri!.nicebasi := std.basi; diff --git a/gap/projective/sl.gi b/gap/projective/sl.gi index ea83f590a..41cac6eb8 100644 --- a/gap/projective/sl.gi +++ b/gap/projective/sl.gi @@ -72,11 +72,7 @@ RECOG.FindStdGens_SL := function(sld) Info(InfoRecog,2, "Recognising this SL2 constructively in 2 dimensions..."); sl2genss := GeneratorsWithMemory(sl2genss); - if IsEvenInt(q) then - resl2 := RECOG.RecogniseSL2NaturalEvenChar(Group(sl2genss),f,false); - else - resl2 := RECOG.RecogniseSL2NaturalOddCharUsingBSGS(Group(sl2genss),f); - fi; + resl2 := RECOG.RecogniseSL2Natural(Group(sl2genss),f); slpsl2std := SLPOfElms(resl2.all); bas := resl2.bas * bas; # We need the actual transvections: diff --git a/gap/projective/sl2_natural.gi b/gap/projective/sl2_natural.gi new file mode 100644 index 000000000..de87aa3ed --- /dev/null +++ b/gap/projective/sl2_natural.gi @@ -0,0 +1,636 @@ +############################################################################# +## +## This file provides recognition of SL(2,q) in natural representation. +## +## Main function: +## +## RECOG.RecogniseSL2Natural(G, f) +## +## G must be SL(2,q) generated by 2x2 matrices over GF(q) = f. +## Dispatches to the appropriate helper function depending on q: +## q = 2 -> RECOG.RecogniseSL2Natural2 +## q = 3,5 -> RECOG.RecogniseSL2NaturalSmallPrimes +## q = 4 -> RECOG.RecogniseSL2Natural4 +## q else -> RECOG.RecogniseSL2NaturalGeneric (Frank Lübeck 2025) +## +## Returns a rec with entries: +## s -- list of upper transvections, i.e. s[i]^basi = [[1,Z(q)^(i-1)],[0,1]] +## t -- list of lower transvections, i.e. t[i]^basi = [[1,0],[Z(q)^(i-1),1]] +## where j = DegreeOverPrimeField(GF(q)), for i=1,...,j +## bas -- base change matrix +## basi -- inverse base change matrix +## a -- an element conjugate to [[0,-1],[1,0]] via basi +## b -- identity matrix +## all -- concatenation of s, t, [a], [b] +## one -- One(f) +## g -- G + + +############################################################################# +## +## RECOG.RecogniseSL2Natural +## +RECOG.RecogniseSL2Natural := function(G, f) + local q, res, nicegens, diag, u1, u2, umat, lmat, k, j, i, el, result, bas, basi, + basis, coeffs, m, c, l, a, b; + q := Size(f); + + ## if q = 2,3,4 then RecogNaturalSL2 does not work + if q = 2 then + return RECOG.RecogniseSL2Natural2(G,f); + elif q = 4 then + return RECOG.RecogniseSL2Natural4(G,f); + elif q = 3 or q = 5 then + return RECOG.RecogniseSL2NaturalSmallPrimes(G,f); + fi; + + #TODO: for 2^l with l large enough, RECOG.RecogniseSL2NaturalEvenChar becomes faster than + # RecogniseSL2NaturalGeneric, for example q = 2^40. + res := RECOG.RecogniseSL2NaturalGeneric(G,q); + nicegens :=List(res[1],a->ResultOfStraightLineProgram(a,GeneratorsOfGroup(G))); + diag := nicegens[1]; + u1 := nicegens[2]; + u2 := nicegens[3]; + j := DegreeOverPrimeField(GF(q)); + if IsEvenInt(q) then + ## even characteristic: conjugation by diag generates all of GF(q)* directly + lmat := []; + for k in [0..j-1] do + i := (q-1-k)*Int(2)^-1 mod (q-1); + el := u1^(diag^i); + Add(lmat,el); + od; + umat := []; + for k in [0..j-1] do + i := k * Int(2)^-1 mod (q-1); + el := u2^(diag^i); + Add(umat, el); + od; + else + # In odd characteristic conjugation only yields squares. + # Express z^l in the Fp-basis {z^0, z^2, ..., z^(2(j-1))} and multiply conjugates. + # Then create the lower/upper matrices with z^0, z^1, ..., z^(j-1) as entries + basis := List([0..j-1], i -> Z(q)^(2*i)); + basis := Basis(GF(q), basis); + lmat := []; + for l in [0..j-1] do + coeffs := Coefficients(basis, Z(q)^l); + m := u1^0; + for i in [0..j-1] do + c := IntFFE(coeffs[i+1]); + m := m * (diag^i * u1 * diag^(-i))^c; + od; + Add(lmat, m); + od; + umat := []; + for l in [0..j-1] do + coeffs := Coefficients(basis, Z(q)^l); + m := u2^0; + for i in [0..j-1] do + c := IntFFE(coeffs[i+1]); + m := m * (diag^(-i) * u2 * diag^i)^c; + od; + Add(umat, m); + od; + fi; + basi := res[2]; + bas := basi^(-1); + a := umat[1]^(-1)*lmat[1]*umat[1]^(-1); + b := One(umat[1]); + result := rec( g := G, t := lmat, s := umat, bas := bas, basi := basi, + one := One(f), a := a, b := b, + all := Concatenation(umat,lmat,[a],[b])); + return result; +end; + + + + + + + + + +############################################################################# +## +## RECOG.RecogniseSL2NaturalGeneric +## +## 2025 Frank Lübeck +## +## Returns [slps, mat] where slps is list of SLPs in given generators of G +## to elements which are conjugate by mat to standard/nice generators: +## t, u1, u2 = diag(1/Z(q),Z(q)) [[1,0],[1,1]], [[1,1],[0,1]] +## +## We construct elements with the strategy of Section 2. from the article +## Conder, Leedham-Green, Fast recognition of classical groups +## over large fields, Groups and Computation III, 2001. +## and then adjust them to the form described above. +## +## Some comments: +## - Does not work for q = 2,3,4 +## - To find short SLP to the nice generators we avoid 'PseudoRandom(g)' +## and instead just start with the trivial element and multiply random +## generators to it. +## This should be good enough because in both cases (element xm and twice +## element cm in the code) almost half of the elements in G are suitable. +## - The list of SLPs in the result is probably what is needed for the +## SLPforNiceGens for the RecogNode. +## - To find for a given element A in G an SLP in the nice generators compute +## Astd := A^mat and use the function 'SLPinStandardSL2' from the file +## "SL2.g". +## - If q = 2^m with odd m then the computation of the eigenvalues of xm +## needs the quadratic extension GF(2^(2m)). + +RECOG.RecogniseSL2NaturalGeneric := function(G, q) + local GM, one, zero, qm1fac, c, m, gens, xm, x, pol, v, z, exp, a, + mat, tm, ym, y, ymat, tr, d, cm, r1, r2, r, log, i, trupm, + smm, trlowm, F, a2, bas, e, l, emax, tmp; + + if q < 5 then + Error("Prime power q must be at least 5."); + fi; + + GM := GroupWithMemory(G); + one := OneOfBaseDomain(G.1); + zero := Zero(one); + + # find element x of order q-1 + # (a power of x will become the nice generator t later) + qm1fac := Factors(q-1); + c := Product(Set(qm1fac)); + m := (q-1)/c; + gens := GeneratorsOfGroup(GM); + xm := One(GM); + repeat + #xm := PseudoRandom(GM); + xm := xm*Random(gens); + x := StripMemory(xm); + pol := [one, -x[1,1]-x[2,2], one]; + v := [zero, one]; + v := PowerModCoeffs(v, m, pol); + until PowerModCoeffs(v, c, pol) = [one] and + ForAll(qm1fac, p-> PowerModCoeffs(v, c/p, pol) <> [one]); + # eigenvalues and eigenvectors of x (zeroes of pol) + # we use Cantor-Zassenhaus + z := Z(q); + if q mod 2 = 0 then + if q mod 3 = 1 then + exp := (q-1)/3; + else + exp := (q^2-1)/3; + z := Z(q^2); + fi; + else + exp := (q-1)/2; + fi; + repeat + v := [z^Random(0,q-1), one]; + v := PowerModCoeffs(v, exp, pol); + until Length(v) = 2 and ValuePol(pol, (-v[1]+one)/v[2]) = zero; + a := (-v[1]+one)/v[2]; + # colums of mat are eigenvectors for a and 1/a (x^mat is diagonal) + mat := [[-x[1,2], -x[1,2]], [x[1,1]-a, x[1,1]-1/a]]; + if mat[1,1] = zero and mat[2,1] = zero then + mat[1,1] := x[2,2]-a; mat[2,1] := -x[2,1]; + fi; + if mat[1,2] = zero and mat[2,2] = zero then + mat[1,2] := x[2,2]-1/a; mat[2,2] := -x[2,1]; + fi; + + + # find conjugate of x with different eigenspaces + # (almost all conjugates will do) + tm := One(GM); + repeat + tm := tm * Random(gens); + ym := tm*xm*tm^-1; + y := StripMemory(ym); + # in this basis x is diagonal, so different eigenspaces means all + # entries non-zero + ymat := y^mat; + until ymat[1,1] <> zero and ymat[1,2] <> zero and ymat[2,1] <> zero + and ymat[2,2] <> zero; + # now y^(tm * mat) = diag(a, a^-1) + tr := tm*mat; + + Assert(3, y^(tm * mat) = DiagonalMat([a, a^-1]) ); + + # a-eigenvector of x in new basis + d := tr^-1 * [mat[1,1],mat[2,1]]; + # can be scaled to [1,d] + d := d[2]/d[1]; + cm := One(GM); + repeat + # look for cm such that [1,d] is also eigenvector of (y^i cm)^tr + repeat + cm := cm*Random(gens); + c := StripMemory(cm)^tr; + r1 := c[2,1]+d*c[2,2]; + r2 := d^2*c[1,2]+d*c[1,1]; + until r2 <> zero and r1 <> zero; + r := r1 / r2; + log := DLog(a, r, qm1fac); + i := false; + if log mod 2 = 0 then + i := log/2; + elif q mod 2 = 0 then + # in char two r is always a square + i := log/2 mod (q-1); + fi; + if IsInt(i) then + # this will in most cases be a transvection normalized by x + trupm := Comm(xm, ym^i*cm); + smm := trupm^mat; + if smm[1,2] = zero or smm[2,1] <> zero then + i := false; + else + # rescale first column of mat such that trupm^mat = [[1,1],[0,1]] + mat[1,1] := mat[1,1]*smm[1,2]; + mat[2,1] := mat[2,1]*smm[1,2]; + tr := tm*mat; + fi; + fi; + until IsInt(i); + + Assert(3, trupm^mat = [[1,1],[0,1]] * one); + + # same for the other eigenvector of x: + # 1/a-eigenvector of x in new basis + d := tr^-1 * [mat[1,2],mat[2,2]]; + # can be scaled to [1,d] + d := d[2]/d[1]; + cm := One(GM); + repeat + # look for cm such that [1,d] is also eigenvector of (y^i cm)^tr + repeat + cm := cm*Random(gens); + c := StripMemory(cm)^tr; + r1 := c[2,1]+d*c[2,2]; + r2 := d^2*c[1,2]+d*c[1,1]; + until r2 <> zero and r1 <> zero; + r := r1 / r2; + log := DLog(a, r, qm1fac); + i := false; + if log mod 2 = 0 then + i := log/2; + elif q mod 2 = 0 then + # in char two r is always a square + i := log/2 mod (q-1); + fi; + if IsInt(i) then + # in most cases a transvection which becomes conjugated by mat + # lower triangular (here it is more difficult to rescale such + # that the conjugate matrix is [[1,0],[1,1]]). + trlowm := Comm(xm, ym^i*cm); + smm := trlowm^mat; + if smm[2,1] = zero or smm[1,2] <> zero then + i := false; + fi; + fi; + until IsInt(i); + + Assert(3, IsZero((trlowm^mat)[1,2]) and IsOne((trlowm^mat)[1,1])); + + # adjust lower left entry of trlowm^mat to one + # (we use F_p linear algebra in F_q to find the nice element + # of products of trlowm^(x^i) for some small i) + if smm[2,1] <> one then + F := GF(q); + a2 := a^2; + bas := [smm[2,1]]; + e := DegreeOverPrimeField(F); + for i in [1..e-1] do + Add(bas, bas[i]*a2); + od; + bas := Basis(F, bas); + l := List(Coefficients(bas, one), IntFFE); + emax := e; + while l[emax] = 0 do + emax := emax-1; + od; + tmp := trlowm; + if l[1] = 0 then + trlowm := One(GM); + else + trlowm := tmp^l[1]; + fi; + for i in [2..emax] do + tmp := tmp^xm; + if l[i] <> 0 then + trlowm := trlowm*tmp^l[i]; + fi; + od; + fi; + + Assert(3, trlowm^mat = [[1,0],[1,1]] * one); + + # finally power x to change a to 1/Z(q) + if a <> 1/Z(q) then + log := DLog(a, 1/Z(q), qm1fac); + xm := xm^log; + fi; + + Assert(3, xm^mat = DiagonalMat([Z(q)^-1, Z(q)])); + + # return SLPs of elements mapped by mat to + # diag(1/Z(q),Z(q))[[1,0],[1,1]], [[1,1],[0,1]], + # and mat + return [List([xm, trlowm, trupm], SLPOfElm), mat]; +end; + + + + + + + +############################################################################# +## +## Given a list of generators of SL(2,2), finds a generating pair +## consisting of either a 2-cycle and a 3-cycle, or two distinct 2-cycles. +## Returns [e2, 2, e3, o3] where e2 is always the 2-cycle and o3 is the +## order of e3 (2 or 3). Orders are determined directly from matrix entries: +## a non-identity element has order 2 iff its trace is zero. +## Returns fail if no such pair is found. + +RECOG.findGenPairSL22 := function(gens) + local m1, m2, o1, o2, i, j, n; + n := Length(gens); + for i in [1..n] do + m1 := StripMemory(gens[i]); + if IsOne(m1) then continue; fi; + if m1[1,1] = m1[2,2] then o1 := 2; else o1 := 3; fi; + for j in [i+1..n] do + m2 := StripMemory(gens[j]); + if IsOne(m2) or m1 = m2 then continue; fi; + if m2[1,1] = m2[2,2] then o2 := 2; else o2 := 3; fi; + if o1 = 2 then + return [gens[i], o1, gens[j], o2]; + elif o2 = 2 then + return [gens[j], o2, gens[i], o1]; + fi; + od; + od; + return fail; +end; + + + + + + + +############################################################################# +## +## RECOG.RecogniseSL2Natural2 +## +## G must be SL(2,2) generated by 2x2 matrices over GF(2) = f. +## +## +## Strategy: use RECOG.findGenPairSL22 to find a generating pair (e2, e3) +## where e2 has order 2 and e3 has order 2 or 3. Then compute the +## two transvections directly by explicit matrix multiplication, +## exploiting the fact that SL(2,2) has only 6 elements and GF(2)*={1}. + +RECOG.RecogniseSL2Natural2 := function(G, f) + local one, zero, gens, pair, e2, e3, o3, + m1, m2, lower, upper, + bas, s, t, res; + + one := One(f); + zero := Zero(f); + gens := GeneratorsOfGroup(G); + + # --- Step 1: find a generating pair --- + pair := RECOG.findGenPairSL22(gens); + if pair = fail then + Error("ConRecogniseSL2NaturalQ22: could not find generating pair - is G really SL(2,2)?"); + fi; + e2 := pair[1]; + o3 := pair[4]; + e3 := pair[3]; + + # --- Step 2: compute transvections directly --- + m1 := StripMemory(e2); + m2 := StripMemory(e3); + + if o3 = 3 then + if m1[1,1] = zero then + if m2[1,1] = zero then + upper := e2 * e3; lower := e3 * e2; + else + lower := e2 * e3; upper := e3 * e2; + fi; + elif m1[1,2] = zero then + if m2[1,1] = zero then + upper := e3 * e2; lower := e2; + else + upper := e2 * e3; lower := e2; + fi; + else + if m2[1,1] = zero then + lower := e2 * e3; upper := e2; + else + lower := e3 * e2; upper := e2; + fi; + fi; + else + if m1[1,1] = zero then + if m2[1,2] = zero then + lower := e3; upper := e2 * e3 * e2; + else + upper := e3; lower := e2 * e3 * e2; + fi; + elif m2[1,1] = zero then + if m1[1,2] = zero then + lower := e2; upper := e3 * e2 * e3; + else + upper := e2; lower := e3 * e2 * e3; + fi; + else + if m1[1,2] = zero then + lower := e2; upper := e3; + else + upper := e2; lower := e3; + fi; + fi; + fi; + + bas := IdentityMat(2, f); + s := [ upper ]; + t := [ lower ]; + res := rec( g := G, t := t, s := s, bas := bas, basi := bas^-1, + one := one, a := s[1]*t[1]*s[1], b := One(s[1]) ); + res.all := Concatenation(res.s, res.t, [res.a], [res.b]); + return res; +end; + + + + + + + + +############################################################################# +## +## RECOG.RecogniseSL2Natural4 +## +## G must be SL(2,4) generated by 2x2 matrices over GF(4) = f. +## +## Strategy: +## 1. Find element xm of order 3: these are diagonalizable with +## eigenvalues Z(4) and 1/Z(4). +## 2. Construct base change matrix mat from the eigenvectors of xm. +## 3. Find one upper and one lower transvections via random walk +## after base change. +## 4. Conjugate transvections with powers of xm to obtain two lower +## transvections and two upper transvections with off-diagonal +## entries Z(4)^0 and Z(4). + +RECOG.RecogniseSL2Natural4 := function(G, f) + local GM, one, zero, gens, xm, x, lambda, mat, upper, lower, + cur, c, smm, val, s, t, res, s1, s2, t1, t2, i, target; + + GM := GroupWithMemory(G); + one := One(f); + zero := Zero(f); + gens := GeneratorsOfGroup(GM); + lambda := Z(4); + + # Find element xm of order 3 via trace condition (Tr=1 <=> order 3 in SL(2,4)) + xm := One(GM); + repeat + xm := xm * Random(gens); + x := StripMemory(xm); + until x[1,1] + x[2,2] = one; + + # Diagonalize x: eigenvectors for lambda and lambda^2 = 1/lambda. + # The columns of mat are the right eigenvectors of x, + # i.e. (x - λI) * v^T = 0. The first row gives: + # (x[1,1] - λ)*v[1] + x[1,2]*v[2] = 0, yielding v = [-x[1,2], x[1,1] - λ]. + # If this vector is zero. we fall back to the second row: + # x[2,1]*v[1] + (x[2,2] - λ)*v[2] = 0, yielding v = [x[2,2] - λ, -x[2,1]]. + mat := [[x[1,2], x[1,2]], [x[1,1]+lambda, x[1,1]+lambda^2]]; + if mat[1,1] = zero and mat[2,1] = zero then + mat[1,1] := x[2,2]+lambda; mat[2,1] := x[2,1]; + fi; + if mat[1,2] = zero and mat[2,2] = zero then + mat[1,2] := x[2,2]+lambda^2; mat[2,2] := x[2,1]; + fi; + + # Find one upper and one lower transvection (any off-diagonal value). + upper := fail; + lower := fail; + cur := One(GM); + while upper = fail or lower = fail do + cur := cur * Random(gens); + c := StripMemory(cur); + smm := c^mat; + if smm[1,1] = one and smm[2,2] = one then + if smm[2,1] = zero and smm[1,2] <> zero and upper = fail then + upper := cur; + elif smm[1,2] = zero and smm[2,1] <> zero and lower = fail then + lower := cur; + fi; + fi; + od; + + # If upper has off-diagonal value v = lambda^k, then upper^(xm^i) has value lambda^(k+i). + # We want s[1] with value 1 and s[2] with value lambda. + # So we conjugate by xm^i where i is chosen such that lambda^(k+i) = 1 resp. lambda. + val := (StripMemory(upper)^mat)[1,2]; + if val = one then + s := [ upper, upper^xm ]; + elif val = lambda then + s := [ upper^(xm^2), upper ]; + else + s := [ upper^xm, upper^(xm^2) ]; + fi; + + # If lower has off-diagonal value v = lambda^k, then lower^(xm^i) has value lambda^(k + 2i) mod 3. + # val=1 (k=0): xm^0 -> 1, xm^1 -> lambda^2, xm^2 -> lambda => t := [xm^0, xm^2] + # val=lambda (k=1): xm^0 -> lambda, xm^1 -> 1, xm^2 -> lambda^2 => t := [xm^1, xm^0] + # val=lambda^2 (k=2): xm^0 -> lambda^2, xm^1 -> lambda, xm^2 -> 1 => t := [xm^2, xm^1] + val := (StripMemory(lower)^mat)[2,1]; + if val = one then + t := [ lower, lower^(xm^2) ]; + elif val = lambda then + t := [ lower^xm, lower ]; + else + t := [ lower^(xm^2), lower^xm ]; + fi; + + res := rec( g := G, t := t, s := s, bas := mat^-1, basi := mat, + one := one, a := s[1]*t[1]*s[1], b := One(s[1]) ); + res.all := Concatenation(res.s, res.t, [res.a], [res.b]); + return res; +end; + + + + + + + + +############################################################################# +## +## RECOG.RecogniseSL2NaturalSmallPrimes +## +## G must be SL(2,p) generated by 2x2 matrices over GF(p) = f, where p is prime. +## +## Strategy: +## Since p is a prime, SL(2,p) is generated by two transvections alone. +## We directly search for upper and lower triangular transvections +## via a random walk in the standard basis. +## +## Another Approach would be to find an element of order p, +## which is unipotent (eigenvalue 1 with multiplicity 2), and hence +## conjugate to an upper or lower triangular matrix. A base change would +## then directly yield one transvection, and only the other one would +## need to be found via random walk. However, in our experiments this +## was slower. + +RECOG.RecogniseSL2NaturalSmallPrimes := function(G,f) +local GM, one, zero, gens, xm, x, o, trupm, trlowm, + cur, c, k, s, t, res; + GM := GroupWithMemory(G); + one := One(f); + zero := Zero(f); + gens := GeneratorsOfGroup(GM); + + # Find transvections via random walk directly in standard basis. + # (No base change needed since SL(2,3) has no useful order-2 element + # outside -I to build a base change matrix from.) + trupm := fail; + trlowm := fail; + cur := One(GM); + while trupm = fail or trlowm = fail do + cur := cur * Random(gens); + c := StripMemory(cur); + if c[2,1] = zero and c[1,2] <> zero and trupm = fail then + if c[1,1] = c[2,2] and c[1,1] = one then + trupm := cur; + fi; + elif c[1,2] = zero and c[2,1] <> zero and trlowm = fail then + if c[1,1] = c[2,2] and c[1,1] = one then + trlowm := cur; + fi; + fi; + od; + + # Rescale transvections to standard form [[1,1],[0,1]] and [[1,0],[1,1]]. + k := IntFFE((trupm)[1,2]^-1); + trupm := trupm^k; + k := IntFFE((trlowm)[2,1]^-1); + trlowm := trlowm^k; + + s := [ trupm ]; + t := [ trlowm ]; + res := rec( g := G, t := t, s := s, + bas := One(G), basi := One(G), + one := one, a := s[1]^-1*t[1]*s[1]^-1, b := One(s[1]) ); + res.all := Concatenation(res.s, res.t, [res.a], [res.b]); + return res; +end; diff --git a/read.g b/read.g index a6ae03089..93ae97635 100644 --- a/read.g +++ b/read.g @@ -53,6 +53,7 @@ ReadPackage("recog","gap/projective/almostsimple.gi"); ReadPackage("recog","gap/projective/almostsimple/lietype.gi"); ReadPackage("recog","gap/projective/almostsimple/hints.gi"); ReadPackage("recog","gap/projective/classicalnatural.gi"); +ReadPackage("recog","gap/projective/sl2_natural.gi"); ReadPackage("recog","gap/projective/sl.gi"); ReadPackage("recog","gap/projective/AnSnOnFDPM.gi"); diff --git a/tst/working/quick/bugfix.tst b/tst/working/quick/bugfix.tst index 2d9c05968..b100935a5 100644 --- a/tst/working/quick/bugfix.tst +++ b/tst/working/quick/bugfix.tst @@ -31,25 +31,25 @@ gap> RECOG.TestGroup(SymmetricGroup(11), false, Factorial(11)); # See https://github.com/gap-packages/recog/issues/65 gap> RecogniseGroup(SL(2,2)); + F: K: gap> RecogniseGroup(SL(2,3)); + F: K: K:> gap> RecogniseGroup(SL(2,4)); + F: K: gap> RecogniseGroup(SL(2,5)); + F: K: K:> diff --git a/tst/working/quick/sl2.tst b/tst/working/quick/sl2.tst new file mode 100644 index 000000000..b585d12c4 --- /dev/null +++ b/tst/working/quick/sl2.tst @@ -0,0 +1,21 @@ +gap> START_TEST("sl2.tst"); +gap> testRecogniseSL2Natural := function(q) +> local G, res, f, i, std; +> f := GF(q); +> G := GroupWithGenerators(List([1..10], i->Random(SL(2,q)))); +> res := RECOG.RecogniseSL2Natural(G,f); +> std := RECOG.MakeSL_StdGens(Characteristic(f),DegreeOverPrimeField(f),2,2); +> for i in [1..Length(res.all)] do +> if res.all[i]^res.basi <> std.all[i] then +> return false; +> fi; +> od; +> return true; +> end;; +gap> list := Filtered([2..1000], IsPrimePowerInt);; +gap> for q in list do +> if not testRecogniseSL2Natural(q) then +> Print("FAILED for q = ", q, "\n"); +> fi; +> od; +gap> STOP_TEST("sl2.tst");