Skip to content

Commit 277e1ac

Browse files
author
Till Müller
committed
Add Frank Lübeck SL2 recognition algorithm
1 parent 5120658 commit 277e1ac

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed

gap/projective/constructive_recognition/SL/BaseCase.gi

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,359 @@ RECOG.FindStdGensUsingBSGS := function(g,stdgens,projective,large)
475475
od;
476476
return SLPOfElms(gens);
477477
end;
478+
479+
480+
###################################################################################################
481+
###################################################################################################
482+
######## Algorithm provided by Frank Lübeck #######################################################
483+
###################################################################################################
484+
###################################################################################################
485+
486+
487+
488+
#################### (c) 2025 Frank Lübeck ##########################
489+
##
490+
## G must be SL(2,q) generated by 2x2 matrices over GF(q).
491+
## Returns [slps, mat] where slps is list of SLPs in given generators of G
492+
## to elements which are conjugate by mat to standard/nice generators:
493+
## t, u1, u2 = diag(1/Z(q),Z(q)) [[1,0],[1,1]], [[1,1],[0,1]]
494+
##
495+
## We construct elements with the strategy of Section 2. from the article
496+
## Conder, Leedham-Green, Fast recognition of classical groups
497+
## over large fields, Groups and Computation III, 2001.
498+
## and then adjust them to the form described above.
499+
##
500+
## Some comments:
501+
## - To find short SLP to the nice generators we avoid 'PseudoRandom(g)'
502+
## and instead just start with the trivial element and multiply random
503+
## generators to it.
504+
## This should be good enough because in both cases (element xm and twice
505+
## element cm in the code) almost half of the elements in G are suitable.
506+
##
507+
## - The list of SLPs in the result is probably what is needed for the
508+
## SLPforNiceGens for the RecogNode.
509+
##
510+
## - To find for a given element A in G an SLP in the nice generators compute
511+
## Astd := A^mat and use the function 'SLPinStandardSL2' from the file
512+
## "SL2.g".
513+
##
514+
## - If q = 2^m with odd m then the computation of the eigenvalues of xm
515+
## needs the quadratic extension GF(2^(2m)).
516+
##
517+
##
518+
## Example (takes about 15 seconds on my notebook):
519+
## Size(SL(2,23^15));
520+
## G:=Group(List([1..4],i->PseudoRandom(SL(2,23^15))));
521+
## l:=RecogNaturalSL2(G,23^15);
522+
## nicegens:=List(l[1],a->ResultOfStraightLineProgram(a,GeneratorsOfGroup(G)));
523+
## List(last, a->a^l[2]);
524+
##
525+
RecogNaturalSL2 := function(G, q)
526+
local GM, one, zero, qm1fac, c, m, gens, xm, x, pol, v, z, exp, a,
527+
mat, tm, ym, y, ymat, tr, d, cm, r1, r2, r, log, i, trupm,
528+
smm, trlowm, F, a2, bas, e, l, emax, tmp;
529+
GM := GroupWithMemory(G);
530+
one := One(G.1[1,1]);
531+
zero := Zero(one);
532+
533+
# find element x of order q-1
534+
# (a power of x will become the nice generator t later)
535+
qm1fac := Factors(q-1);
536+
c := Product(Set(qm1fac));
537+
m := (q-1)/c;
538+
gens := GeneratorsOfGroup(GM);
539+
xm := One(GM);
540+
repeat
541+
#xm := PseudoRandom(GM);
542+
xm := xm*Random(gens);
543+
x := StripMemory(xm);
544+
pol := [one, -x[1,1]-x[2,2], one];
545+
v := [zero, one];
546+
v := PowerModCoeffs(v, m, pol);
547+
until PowerModCoeffs(v, c, pol) = [one] and
548+
ForAll(qm1fac, p-> PowerModCoeffs(v, c/p, pol) <> [one]);
549+
# eigenvalues and eigenvectors of x (zeroes of pol)
550+
# we use Cantor-Zassenhaus
551+
z := Z(q);
552+
if q mod 2 = 0 then
553+
if q mod 3 = 1 then
554+
exp := (q-1)/3;
555+
else
556+
exp := (q^2-1)/3;
557+
z := Z(q^2);
558+
fi;
559+
else
560+
exp := (q-1)/2;
561+
fi;
562+
repeat
563+
v := [z^Random(0,q-1), one];
564+
v := PowerModCoeffs(v, exp, pol);
565+
until Length(v) = 2 and ValuePol(pol, (-v[1]+one)/v[2]) = zero;
566+
a := (-v[1]+one)/v[2];
567+
# colums of mat are eigenvectors for a and 1/a (x^mat is diagonal)
568+
mat := [[-x[1,2], -x[1,2]], [x[1,1]-a, x[1,1]-1/a]];
569+
if mat[1,1] = zero and mat[2,1] = zero then
570+
mat[1,1] := x[2,2]-a; mat[2,1] := -x[2,1];
571+
fi;
572+
if mat[1,2] = zero and mat[2,2] = zero then
573+
mat[1,2] := x[2,2]-1/a; mat[2,2] := -x[2,1];
574+
fi;
575+
576+
577+
# find conjugate of x with different eigenspaces
578+
# (almost all conjugates will do)
579+
tm := One(GM);
580+
repeat
581+
tm := tm * Random(gens);
582+
ym := tm*xm*tm^-1;
583+
y := StripMemory(ym);
584+
ymat := y*mat;
585+
until ymat[1,1]*mat[2,1]-ymat[2,1]*mat[1,1] <> zero and
586+
ymat[1,2]*mat[2,2]-ymat[2,2]*mat[1,2] <> zero;
587+
# now y^(tm * mat) = diag(a, a^-1)
588+
tr := tm*mat;
589+
590+
# a-eigenvector of x in new basis
591+
d := tr^-1 * [mat[1,1],mat[2,1]];
592+
# can be scaled to [1,d]
593+
d := d[2]/d[1];
594+
cm := One(GM);
595+
repeat
596+
# look for cm with non-trivial conditions (i <> 0, (q-1)/2)
597+
repeat
598+
cm := cm*Random(gens);
599+
c := StripMemory(cm)^tr;
600+
r1 := c[2,1]+d*c[2,2];
601+
r2 := d^2*c[1,2]+d*c[1,1];
602+
until r2 <> zero and r1 <> zero and r1 <> r2 and r1 <> -r2;;
603+
r := r1 / r2;
604+
log := DLog(a, r, qm1fac);
605+
i := false;
606+
if log mod 2 = 0 then
607+
i := log/2;
608+
elif q mod 2 = 0 then
609+
i := (q-1-log)/2;
610+
fi;
611+
if IsInt(i) then
612+
# this will in most cases be a transvection normalized by x
613+
trupm := Comm(xm, ym^i*cm);
614+
smm := trupm^mat;
615+
if smm[1,2] = zero then
616+
i := false;
617+
else
618+
# rescale first column of mat such that trupm^mat = [[1,1],[0,1]]
619+
mat[1,1] := mat[1,1]*smm[1,2];
620+
mat[2,1] := mat[2,1]*smm[1,2];
621+
tr := tm*mat;
622+
fi;
623+
fi;
624+
until IsInt(i);
625+
626+
# same for the other eigenvector of x:
627+
# 1/a-eigenvector of x in new basis
628+
d := tr^-1 * [mat[1,2],mat[2,2]];
629+
# can be scaled to [1,d]
630+
d := d[2]/d[1];
631+
cm := One(GM);
632+
repeat
633+
# look for cm with non-trivial conditions (i <> 0, (q-1)/2)
634+
repeat
635+
cm := cm*Random(gens);
636+
c := StripMemory(cm)^tr;
637+
r1 := c[2,1]+d*c[2,2];
638+
r2 := d^2*c[1,2]+d*c[1,1];
639+
until r2 <> zero and r1 <> zero and r1 <> r2 and r1 <> -r2;;
640+
r := r1 / r2;
641+
log := DLog(a, r, qm1fac);
642+
i := false;
643+
if log mod 2 = 0 then
644+
i := log/2;
645+
elif q mod 2 = 0 then
646+
i := (q-1-log)/2;
647+
fi;
648+
if IsInt(i) then
649+
# in most cases a transvection which becomes conjugated by mat
650+
# lower triangular (here it is more difficult to rescale such
651+
# that the conjugate matrix is [[1,0],[1,1]]).
652+
trlowm := Comm(xm, ym^i*cm);
653+
smm := trlowm^mat;
654+
if smm[2,1] = zero then
655+
i := false;
656+
fi;
657+
fi;
658+
until IsInt(i);
659+
660+
# adjust lower left entry of trlowm^mat to one
661+
# (we use F_p linear algebra in F_q to find the nice element
662+
# of products of trlowm^(x^i) for some small i)
663+
if smm[2,1] <> one then
664+
F := GF(q);
665+
a2 := a^2;
666+
bas := [smm[2,1]];
667+
e := DegreeOverPrimeField(F);
668+
for i in [1..e-1] do
669+
Add(bas, bas[i]*a2);
670+
od;
671+
bas := Basis(F, bas);
672+
l := List(Coefficients(bas, one), IntFFE);
673+
emax := e;
674+
while l[emax] = 0 do
675+
emax := emax-1;
676+
od;
677+
tmp := trlowm;
678+
if l[1] = 0 then
679+
trlowm := One(GM);
680+
else
681+
trlowm := tmp^l[1];
682+
fi;
683+
for i in [2..emax] do
684+
tmp := tmp^xm;
685+
if l[i] <> 0 then
686+
trlowm := trlowm*tmp^l[i];
687+
fi;
688+
od;
689+
fi;
690+
691+
# finally power x to change a to 1/Z(q)
692+
if a <> 1/Z(q) then
693+
log := DLog(a, 1/Z(q), qm1fac);
694+
xm := xm^log;
695+
fi;
696+
697+
# return SLPs of elements mapped by mat to
698+
# diag(1/Z(q),Z(q))[[1,0],[1,1]], [[1,1],[0,1]],
699+
# and mat
700+
return [List([xm, trlowm, trupm], SLPOfElm), mat];
701+
end;
702+
703+
## The following code is provided by Till Eisenbrand.
704+
## It integrates the algorithm above to produce the same output format
705+
## as RECOG.RecogniseSL2NaturalEvenChar and
706+
## RECOG.RecogniseSL2NaturalOddCharUsingBSGS.
707+
## G must be SL(2,q) generated by 2x2 matrices over GF(q).
708+
## Note: does not work for q = 2, 3, 5.
709+
ConRecogNaturalSL2 := function(G, f)
710+
local q, res, nicegens, diag, u1, u2, umat, lmat, k, j, i, el, result, bas, true_diag, true_u1, true_u2, basi, p, basis, coeffs, m, c, l;
711+
q := Size(f);
712+
p := Characteristic(f);
713+
j := DegreeOverPrimeField(GF(q));
714+
## standard generators of SL(2,q) in the natural representation
715+
true_diag := [[Z(q)^(-1),0*Z(q)],[0*Z(q),Z(q)]];
716+
true_u1 := [[Z(q)^0,0*Z(q)],[Z(q)^0,Z(q)^0]];
717+
true_u2 := [[Z(q)^0,Z(q)^0],[0*Z(q),Z(q)^0]];
718+
## retry until RecogNaturalSL2 returns generators matching the standard form
719+
for i in [1..100] do
720+
res := RecogNaturalSL2(G,q);
721+
nicegens :=List(res[1],a->ResultOfStraightLineProgram(a,GeneratorsOfGroup(G)));
722+
diag := nicegens[1];
723+
u1 := nicegens[2];
724+
u2 := nicegens[3];
725+
## check if we found the correct generators
726+
if diag^res[2] = true_diag and u1^res[2] = true_u1 and u2^res[2] = true_u2 then
727+
break;
728+
fi;
729+
od;
730+
Print("i = " , i ,"\n");
731+
if IsEvenInt(q) then
732+
## even characteristic: conjugation by diag generates all of GF(q)* directly
733+
lmat := [];
734+
for k in [0..j-1] do
735+
i := (q-1-k)*Int(2)^-1 mod (q-1);
736+
el := u1^(diag^i);
737+
Add(lmat,el);
738+
od;
739+
umat := [];
740+
for k in [0..j-1] do
741+
i := k * Int(2)^-1 mod (q-1);
742+
el := u2^(diag^i);
743+
Add(umat, el);
744+
od;
745+
else
746+
## odd characteristic: conjugation only yields squares, so express z^l
747+
## in the Fp-basis {z^0, z^2, ..., z^(2(j-1))} and multiply conjugates
748+
basis := List([0..j-1], i -> Z(q)^(2*i));
749+
lmat := [];
750+
for l in [0..j-1] do
751+
coeffs := Coefficients(Basis(GF(q), basis), Z(q)^l);
752+
m := u1^0;
753+
for i in [0..j-1] do
754+
c := IntFFE(coeffs[i+1]);
755+
m := m * (diag^i * u1 * diag^(-i))^c;
756+
od;
757+
Add(lmat, m);
758+
od;
759+
umat := [];
760+
for l in [0..j-1] do
761+
coeffs := Coefficients(Basis(GF(q), basis), Z(q)^l);
762+
m := u2^0;
763+
for i in [0..j-1] do
764+
c := IntFFE(coeffs[i+1]);
765+
m := m * (diag^(-i) * u2 * diag^i)^c;
766+
od;
767+
Add(umat, m);
768+
od;
769+
fi;
770+
basi := res[2];
771+
bas := basi^(-1);
772+
Print("umats: \n");
773+
for i in [1..Length(umat)] do
774+
Display(umat[i]^basi);
775+
od;
776+
Print("lmats: \n");
777+
for i in [1..Length(lmat)] do
778+
Display(lmat[i]^basi);
779+
od;
780+
result := rec( g := G, t := lmat, s := umat, bas := bas, basi := basi,
781+
one := One(f), a := umat[1]*lmat[1]*umat[1], b := One(umat[1]),
782+
One := One(umat[1]), f := f, q := q, p := Characteristic(f), ext := j,
783+
d := 2 );
784+
result.all := Concatenation(result.s,result.t,[result.a],[result.b]);
785+
return result;
786+
end;
787+
788+
789+
## test function: compares ConRecogNaturalSL2 against the built-in recognition
790+
## note: does not work for q = 2, 3, 5
791+
## Input: either a list of prime powers or the empty list
792+
## (then a preset list of prime powers is tested).
793+
test_ConRecogNaturalSL2 := function(input)
794+
local i, G, list, qlist, res_old, res, f, q, valid;
795+
if input = [] then
796+
qlist := [2^3, 2^5, 3^4, 25, 17^3, 9967];
797+
elif Size(input)>0 then
798+
qlist := [];
799+
for q in input do
800+
if IsPrimePowerInt(q) then
801+
Add(qlist, q);
802+
fi;
803+
od;
804+
fi;
805+
valid := true;
806+
for q in qlist do
807+
Print("testing q = ", q, "\n");
808+
f := GF(q);
809+
list := [];
810+
for i in [1..5] do
811+
Add(list, Random(SL(2,q)));
812+
od;
813+
G := GroupWithGenerators(list);
814+
if IsEvenInt(q) then
815+
res_old := RECOG.RecogniseSL2NaturalEvenChar(G,f,false);
816+
else
817+
res_old := RECOG.RecogniseSL2NaturalOddCharUsingBSGS(G,f);
818+
fi;
819+
res := ConRecogNaturalSL2(G,f);
820+
## compare all generators after change of basis
821+
for i in [1..Length(res.all)] do
822+
if res.all[i]^res.basi <> res_old.all[i]^res_old.basi then
823+
Print("Test failed for q = ", q, ", index i = ", i, "\n");
824+
valid := false;
825+
fi;
826+
od;
827+
od;
828+
if valid then
829+
Print("All tests passed.\n");
830+
fi;
831+
end;
832+
833+

0 commit comments

Comments
 (0)