@@ -475,3 +475,359 @@ RECOG.FindStdGensUsingBSGS := function(g,stdgens,projective,large)
475475 od ;
476476 return SLPOfElms(gens);
477477end ;
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