//////////////////////////////////////////////////////////////////////////////80 // // Last update : 2022-04-19 (SHW) // // Magma code. // // This code produces, for each rank n from 1 to nMax, a collection of one // representative group from each Z-conjugacy class of finite subgroup of // GL(n,Z). As of the last update, this code // 1. returns the expected number of Z-conjugacy classes of finite subgroups // for ranks n ≤ 3. // 2. does not return the expected number for ranks n ≥ 4. // // For each rank n, // 1. Identification of irreducible subgroups uses Magma's IntegralMatrix- // GroupDatabase to identify all maximal irreducible finite subgroups of // GL(n,Z), tests all subgroups of each for irreducibility, then prunes // Z-conjugates from the sequence (list) of subgroups collected. // 2. Computation of reducible subgroups is based on the algorithm described // in Brown, Neubuser, and Zassenhaus (1972), "On integral groups: I. The // reducible case". // // N.B. The parameter nMin can be changed to a value other than 2. However, to // work correctly, the code requires one representative from each Z-conjugacy // class of finite subgroup of GL(n,Z) from rank 1 to rank nMin - 1, properly // encoded and saved in the variable FiniteSubgroupsGLnZ. // //////////////////////////////////////////////////////////////////////////////// // // TO DO // // (00) The following to do items copied from earlier code. Some items may no // longer be relevant. Delete those. // (01) Generalize this code to analyze all cases. // (02) Determine whether to use list or set for each variable, function, etc. // (and whether it even matters). // (03) Restructure code, defining functions for compartmentalizable steps. See // also item (08), related, below. // (04) Which is faster: Identity(G) vs ScalarMatrix(ni,1)? Delete the slower. // (05) In the construction of generators for GStarGenList, we initially used // DiagonalJoin(g1,I2) and DiagonalJoin(I1,g2) to produce (n1 + n2) square // matrix representations of each generator. However, it seems only the // diagonal blocks are used in subsequent computations, so it's more effi- // cient to save each generator as a 2-sequence [g1,I2] or [I1,g2]. With // the matrix approach, we also included the command // GStar := sub; // again, not needed in the sequel. (Once confirmed, relocate to NOTES.) // (06) Computation of Y in YList incorrect! !!! Need to explicitly include the // denominators coming from EDV. Do it !!! // (07) In the function OrderedWeightedPartitions(), choose the ground ring // that yields the fastest computations. (It may not matter.) // (08) Move all functions and procedures to external files. Add AttachSpec // call. See Project002 > ComputerCode > ActiveCode > spec and dP1. // (09) Fold the function OrderedPartitions into OrderedWeightedPartitions by // polymorphism, using the former if argument 1 is a positive integer, and // the latter if argument 1 is a sequence. // (10) Move the construction of \mathfrak{U} (involving RationalMatrixGroup- // Database) to separate file. Save the results of that file as a data- // base. Import the database in this code when needed. // (11) Find more efficient way to lift g2N2 to g2? // (12) Verify (theoretically) new initialized value of GStarGens. // (13) Contact Magma re documentation of quotient group construction : The // constructor G/N, like the constructor quo, yields a projection // homomorphism (from G to a permutation group encoding the cosets in the // quotient). See // file:///Applications/Magma/doc/html/text663.htm\ // (14) Determine whether to use sequence of pairs (e.g., GPair) or 2 separate // variables (e.g., G1 and G2). Standardize all code accordingly. // (15) Verify, condense explanatory note to initialization of GStarGens. // (16) After constructing GStarGens, determine whether to construct the // associated subgroup of G1 x G2 (and possibly extract a smaller set of // generators, again in the form [g1,g2]). // (17) In creating the list of normal subgroups of G1 and G2, the algorithm // calls for exactly one representative per conjugacy class in the norm- // alizer of the group in its parent general linear matrix group. Deter- // mine whether Magma's default conjugacy approach in NormalSubgroups() // and Subgroups() reduces the number of conjugacy classes, and if so, how // to correct this. // (18) Move the computation of the sequence of sequences of irreducible finite // subgroups of GL(n,Z) to a separate program. Run once to create data- // base. Import that database in this code. // (19) Add dynamic feature to EncodeSeqToInt and DecodeIntToSeq, that extends // the length of the sequence of primes automatically as needed. // (20) Write code to pre-create a database of integer sequences corresponding // to all possible choices of r irreducible constituents of size s x s. // Given nMax, s = 1 : r in [1..nMax]; s = 2 : r in [1..(nMax div 2)], // etc. // (21) Add short descriptions to decoding and encoding functions. // (22) Clean up (i.e. shorten) decode function, if possible. // (23) Save BoolToInt as stand-alone intrinsic. However, if not used by this // program, do not include it in this program's spec. (BoolToInt was used // in an earlier version of this program, but not as of 2021-09-30.) // (24) Write two versions of the Decode function : The given one, when argu- // ment 1 is an integer; and a second one, when argument 1 is a rational // number. N.B. The rational version is not required for this program. It // is just a natural extension that would be cool to have. // (25) Verify the IsGLQConjugate() function works as expected. Conjugate over // Q or over Z? !!! See procedure PruneQConjugates. // (26) Expand procedure PruneQConjugates to other natural inputs (e.g., Z con- // jugates) if little or no computational loss involved (e.g., depending // on the input, set the function to use in the rest of the procedure -- // do not run repeated if|then tests!). Otherwise, write separate related // functions? // (27) In the initial construction of the sequence of irreducible subgroups // of GL(n,Q) of each rank, is it faster to make the initial construction // with irrSubs as a set (rather than a sequence, as it is presently de- // fined), then just before applying PruneQConjugates, convert it to a se- // quence? // (28) IrreducibleFiniteSubgroupsGLnQ : If there is a way to avoid having to // test each group for being soluble, delete that aspect of the code. At // present, the code encounters a non-soluble group in rank n = 4, and the // IrreducibleModules function fails and crashes the program. Note : The // documentation for IrreducibleModules seems to claim it applies to non- // soluble finite groups for modules are over Q. Both hypotheses are sat- // isfied here. Figure out why the code fails! // (29) IsIrreducibleRationalMatrixGroup : Limit to rational matrices, to avoid // possible rounding errors when working with real numbers? This does not // affect the current application, but may mislead future applications. // (30) Remove the ParallelSort() from PruneQConjugates (and PruneZConjugates, // if written) -- better, make it an optional argument, with default value // true -- if it not needed in the rewritten program. (In the rewritten // program, it is applied to sequences of groups coming from a fixed rank // n, so it is not needed.) // (31) Circa line 400 : Faster method than invoking Sort() to analyze only // encoded irreducible constituent sequences that are "in order"? // (32) Circa line 415 : Use dictionary structure for FiniteSubgroupsGLnZ, if // Magma permits? If so, rewrite this "look-up" portion accordingly. // (33) Add description for function ReducibleFiniteSubgroupsG1G2. // (34) In the function VMatrix, make n1, n2 optional parameters. If not speci- // fied in function call, compute using the first 3 lines of current code. // (35) Incorporate the functions SchurIndicatorOfGroup and RepNormOfGroup. If // possible, rename Schur and Norm, and use function overloading. Add // description and signature data for each function. // (36) Remove isSortNeeded parameter from PruneZConjugates? or add it to // PruneQConjugates? // //////////////////////////////////////////////////////////////////////////////// // // NOTES // // (*) When constructing NSeqPair, deleted an earlier approach using nested for // loops in favor of the current construction permitting dynamic length of // NSeq. (It is natural to want to delete elements of NSeq during this con- // struction. This makes looping a pre-determined number of times based on // the initial length of NSeq problematic. One can use a nested for loop // structure if one proceeds by building a (partial) copy of NSeq.) // //////////////////////////////////////////////////////////////////////////////// // For debugging only. outputDebug := true; // Parameters (USER MAY ADJUST). // // The values nMin and nMax specify the starting rank and ending rank, // respectively, for which this code will compute a representative of each Z- // class of finite subgroup of GL(n,Z). To work correctly, the code requires a // collection of one representative group from each Z-conjugacy class of finite // subgroup of GL(n,Z) from rank 1 to rank nMin - 1, properly encoded and saved // in the variable FiniteSubgroupsGLnZ. nMin := 2; // Allowed values : Any integer in [2,...,nMax]. nMax := 6; // Allowed values : Any integer in [nMin,...,7]. // Constants (DO NOT ADJUST). DBQ := RationalMatrixGroupDatabase(); DBZ := IntegralMatrixGroupDatabase(); Q := RationalField(); Z := IntegerRing(); // Function and procedures. //////////////////////////////////////////////////////////////////////////////// // // Function : GeneratorsNonemptySet // // Arguments // 1. G : A group to which Magma's Generators() intrinsic applies. // Returns // 1. A set of generators of G. If G is the trivial group, then the set con- // taining only the identity element of G is returned. // Description // This function is used to avoid the default behavior of Magma's built-in // Generators function, which returns the empty set when it is passed the // trivial group as its argument. GeneratorsNonemptySet := function(G) if (IsTrivial(G)) then return {Identity(G)}; else return Generators(G); end if; end function; RepNormSquaredOfGroup := function(G) sum := 0; for g in G do sum +:= Trace(g) * ComplexConjugate(Trace(g)); end for; return sum/#(G); end function; SchurIndicatorOfGroup := function(G) sum := 0; for g in G do sum +:= Trace(g^2); end for; return sum/#(G); end function; //////////////////////////////////////////////////////////////////////////////// // // Function : IsIrreducibleRationalMatrixGroup // // Arguments // 1. G : A finite group of real matrices. // Returns // 1. true or false, according to whether G is irreducible or not, over Q. // Description // This function uses the orthogonality of irreducible complex representa- // tions to determine whether a given finite group of rational matrices is // irreducible. // // N.B. This same function applies to finite groups of real matrices (for // complex matrices, one needs to use the complex conjugate in defining the // inner product that leads to the trace formula). However, I suspect that // in computer applications, rounding of real numbers would cause this // function as written to fail. If one wishes to use this function for real // matrices, one could attempt to rewrite it using a tolerance, rather than // exact equality, in the "if" statement. IsIrreducibleRationalMatrixGroup := function(G) if (RepNormSquaredOfGroup(G) + SchurIndicatorOfGroup(G) eq 2) then return true; else return false; end if; end function; //////////////////////////////////////////////////////////////////////////////// // // Function : IsDecreasingSequence // // Arguments // 1. s : A sequence whose elements belong to a totally ordered set. // Returns // 1. true or false, according to whether the elements of s are (weakly) de- // creasing. IsDecreasingSequence := function(s) boo := true; if (#(s) gt 1) then for i in [1..(#(s) - 1)] do if (s[i] lt s[i + 1]) then boo := false; break; end if; end for; end if; return boo; end function; //////////////////////////////////////////////////////////////////////////////// // // Function : IsIncreasingSequence // // Arguments // 1. s : A sequence whose elements belong to a totally ordered set. // Returns // 1. true or false, according to whether the elements of s are (weakly) in- // creasing. IsIncreasingSequence := function(s) boo := true; if (#(s) gt 1) then for i in [1..(#(s) - 1)] do if (s[i] gt s[i + 1]) then boo := false; break; end if; end for; end if; return boo; end function; //////////////////////////////////////////////////////////////////////////////// // // Procedure : PruneQConjugates // // Arguments // 1. GSeq : A nonempty sequence of subgroups of GL(n,Q). Subgroups of another parent // that is coercible into GL(n,Q) for some n (e.g., GL(n,Z)) are allowed. // The value of n is allowed to vary from subgroup to subgroup. // Description // At the end of this procedure, the sequence referenced by GSeq here is // ordered by increasing order of subgroups, and exactly one representative // from each Q-conjugacy class of subgroups in the original sequence re- // mains. // // N.B. This procedure uses ParallelSort, which proved much faster than // other sorting methods, e.g., // Sort(~GSeq,func); PruneQConjugates := procedure(~GSeq) S := [#(G) : G in GSeq]; ParallelSort(~S,~GSeq); i := 1; while (i lt #(GSeq)) do nGi := #(GSeq[i]); j := i + 1; while ((j ne #(GSeq) + 1) and (#GSeq[j] eq nGi)) do if (IsGLQConjugate(GSeq[i],GSeq[j])) then Remove(~GSeq,j); else j +:= 1; end if; end while; // End while loop in j. i +:= 1; end while; // End while loop in i. end procedure; //////////////////////////////////////////////////////////////////////////////// // // Procedure : PruneZConjugates // // Arguments // 1. GSeq : A nonempty sequence of finite subgroups of GL(n,Z). Subgroups of // another parent that is coercible into GL(n,Z) for some n are allowed. // The value of n is allowed to vary from subgroup to subgroup. // 2. isSortNeeded : A boolean. In the case that groups in GSeq are listed in // order of increasing order, setting the isSortNeeded parameter to false // speeds up computation. // Description // At the end of this procedure, the sequence referenced by GSeq here is // ordered by increasing order of subgroups, and exactly one representative // from each Z-conjugacy class of subgroups in the original sequence re- // mains. // // N.B. This procedure uses ParallelSort, which proved much faster than // other sorting methods, e.g., // Sort(~GSeq,func); PruneZConjugates := procedure(~GSeq,isSortNeeded) if (isSortNeeded) then S := [#(G) : G in GSeq]; ParallelSort(~S,~GSeq); end if; i := 1; while (i lt #(GSeq)) do nGi := #(GSeq[i]); j := i + 1; while ((j ne #(GSeq) + 1) and (#GSeq[j] eq nGi)) do if (IsGLZConjugate(GSeq[i],GSeq[j])) then Remove(~GSeq,j); else j +:= 1; end if; end while; // End while loop in j. i +:= 1; end while; // End while loop in i. end procedure; //////////////////////////////////////////////////////////////////////////////// // // Function : IrreducibleFiniteSubgroupsGLnQ // // Arguments // 1. n : A positive integer. From Magma v2.8 onward, and as of v2.23-1, // the maximum allowed value is 31. // Returns // 1. A sequence with exactly one representative from each Q-class of // irreducible finite subgroup of GL(n,Q). // Description // This function uses Magma's RationalMatrixGroupDatabase. IrreducibleFiniteSubgroupsGLnQ := function(n) ifsQ := []; for i in [1..NumberOfGroups(DBQ,n)] do for G in [G`subgroup : G in Subgroups(Group(DBQ,n,i))] do if (IsIrreducibleRationalMatrixGroup(G)) then Append(~ifsQ,G); end if; end for; end for; PruneQConjugates(~ifsQ); return ifsQ; end function; //////////////////////////////////////////////////////////////////////////////// // // Procedure : PrintHeading // // Arguments // 1. string : A string. // Description // A procedure to print headings. PrintHeading := procedure(string) padding := 3; print ""; print "*"^(#(string) + 2 + (2 * padding)); print "*" cat " "^(2 * padding + #(string)) cat "*"; printf "*"; printf " "^(padding); printf string; printf " "^(padding); printf "*\n"; print "*" cat " "^(2 * padding + #(string)) cat "*"; print "*"^(#(string) + 2 + (2 * padding)); print ""; end procedure; //////////////////////////////////////////////////////////////////////////////// // // Function : VMatrix // // Arguments // 1. GGens : A nonempty sequence of generators for a subgroup of a direct // product G_{1} x G_{2}, where G_{i} is a subgroup of GL(n_{i},Z). Each // generator has the form [g_{1,j},g_{2,j}] (so each g_{i,j} is an n_{i} x // n_{i} invertible square matrix). // 2. n1 : A positive integer. The rank of matrices in G_{1}. // 3. n2 : A positive integer. The rank of matrices in G_{2}. // Returns // 1. An (n_{1} n_{2} z) x (n_{1} n_{2}) matrix with integer entries, where // z = #(GGens) is the number of pairs of generators passed in the argu- // ment. // Description // See Brown et al. (1972), pages 392-393. VMatrix := function(GGens,n1,n2) ZY := PolynomialRing(IntegerRing(),n1 * n2); Y := Matrix(n1,n2,[ZY.i : i in [1..Rank(ZY)]]); V := []; for g in GGens do // !!! g[1],g[2] coerced into ZY to suppress Magma error !!! g1YmYg2 := ElementToSequence(ChangeRing(g[1],ZY) * Y - Y * ChangeRing(g[2],ZY)); for v in g1YmYg2 do for i in [1..Rank(ZY)] do Append(~V,Coefficient(v,ZY.i,1)); end for; end for; end for; return Matrix(IntegerRing(),n1 * n2,V); end function; //////////////////////////////////////////////////////////////////////////////// // // Function : ConnectingFunctions // // Arguments // 1. GStarGens : A nonempty sequence of generators for a subgroup of a direct // product G_{1} x G_{2}, where G_{i} is a subgroup of GL(n_{i},Z). Each // generator has the form [g_{1,j},g_{2,j}] (so each g_{i,j} is an n_{i} x // n_{i} invertible square matrix). // Returns // 1. A sequence of n_{1} x n_{2} matrices representing all connecting func- // tions for the groups G_{1} and G_{2}. // Description // See Brown et al. (1972), page 390. !!! Add explanation (move comment // from ReducibleSubgroupsGLnZ -- a separate file -- here?) !!! ConnectingFunctions := function(GStarGens) g := GStarGens[1]; n1 := NumberOfRows(g[1]); n2 := NumberOfRows(g[2]); V := VMatrix(GStarGens,n1,n2); EDV := ElementaryDivisors(V); if (EDV eq []) then EDVProduct := 1; else EDVProduct := &*EDV; // Product of all elements in EDV. end if; YList := []; // It seems we don't use the variable lambdaList. If correct, delete !!! // > lambdaList may be used to compute the orbits, with PGStarGenSet. lambdaList := []; if (EDVProduct eq 1) then Append(~YList,ZeroMatrix(Z,n1,n2)); // Same def for CFList !!! Append(~lambdaList,[0 : i in [1..(n1 * n2)]]); // The next line exists only because Magma complains if QMat is not // defined in all cases. !!! QMat := ScalarMatrix(n1 * n2,1); else _,_,QMat := SmithForm(V); AbGp := AbelianGroup(EDV); ZeroEntries := [0 : i in [1..(n1 * n2 - #(EDV))]]; // !!! simplify the following for loop !!! for lambda in AbGp do lambdaSeq := ElementToSequence(lambda); Append(~lambdaList,lambdaSeq); lambdaSeq := [lambdaSeq[i]/EDV[i] : i in [1..#(lambdaSeq)]] cat ZeroEntries; // Previous attempt !!! // lambdaSeq := [(EDVProduct div EDV[i]) * lambdaSeq[i] : i in [1..#(lambdaSeq)]] cat ZeroEntries; Append(~YList,Matrix(n1,n2,ElementToSequence(ChangeRing(QMat,RationalField()) * Matrix(n1 * n2,1,lambdaSeq)))); end for; // !!! code block << PGStarGenSet >> goes here !!! end if; return YList; end function; //////////////////////////////////////////////////////////////////////////////// // // Function : ReducibleFiniteSubgroupsG1G2 // // Arguments // 1. G1,G2 : Finite subgroups of GL(n_{i},Z), for i in {1,2}. n_{1} and n_{2} // may or may not be equal. // Returns // 1. A sequence of groups. // Description // !!! add description !!! ReducibleFiniteSubgroupsG1G2 := function(G1,G2) n1 := NumberOfRows(Random(G1)); n2 := NumberOfRows(Random(G2)); I1 := ScalarMatrix(n1,1); I2 := ScalarMatrix(n2,1); // Magma's NormalSubgroups intrinsic returns a record of conjugacy classes // of normal subgroups, with exactly one representative group per class. // Note that conjugacy is tested in the full parent general linear matrix // group (!!! verify !!!), not the normalizer Norm_{GL(n_{i},Z)}(Gi). This // may be a problem for our algorithm !!! See Brown et al. (1972), p 392, // point b), paragraph 2. NRecPair := [NormalSubgroups(G1),NormalSubgroups(G2)]; NormalizerPair := [NormalizerGLZ(G1),NormalizerGLZ(G2)]; NOrderSeqPair := [[#(N`subgroup) : N in NRecPair[i]] : i in [1,2]]; nNPair := [#(NSeq) : NSeq in NOrderSeqPair]; // Because G* depends on the isomorphism sigma, in the following chunk of // code we loop over normal subgroups N_{1} of G_{1} and attempt to find // isomorphisms sigma : G_{1}/N_{1} --> G_{2}/N_{2}, analyzing each such // (class of) isomorphisms as it is found. // // Determine whether this is the most effective way to organize analysis!!! // Regardless, we'll probably want to group the substeps into functions !!! groups := []; for N1Rec in NRecPair[1] do N1 := N1Rec`subgroup; // For each normal subgroup N_{1} of G_{1}, first we check whether a // normal subgroup N_{2} of G_{2} of the required order for a possible // isomorphism G_{1}/N_{1} --> G_{2}/N_{2} exists, by querying // NOrderSeqPair. If the required order is found, the index of its // first occurence in NOrderSeqPair is returned. Otherwise, 0 is // returned. N2Order := #(G2)/(#(G1)/#(N1)); if (IsIntegral(N2Order)) then i := Index(NOrderSeqPair[2],N2Order); else i := 0; end if; if (i ne 0) then while ((i le nNPair[2]) and (NOrderSeqPair[2,i] eq N2Order)) do N2 := NRecPair[2,i]`subgroup; // Compute quotient groups (as permutation groups) and corres- // ponding natural projections. Q1,pi1 := G1/N1; Q2,pi2 := G2/N2; isIso,iso0 := IsIsomorphic(Q1,Q2); if (isIso) then // sigmaList : Construct a list of representative isomorphisms // G1/N1 --> G2/N2 in all cases (N1,N2) where these quotients // are isomorphic. Needed? How to do? Conjugate iso0 by all // pairs of automorphisms (of G1 fixing N1 and G2 fixing N2, // respectively)? !!! // If the single isomorphism in iso0 is sufficient, then delete // the following line, and rename the variable iso0 above to // sigma. // SHW (2022-02-23) : Following meeting with DL, AVA, // suspect error here!!! Conjugate iso0 on one side only // with all possible automorphisms of Q1 (or of Q2, on the // other side) to generate all possible sigma. // !!! clean up the following work-around !!! phiQ1AutToPerm,AutQ1AsPermRep := PermutationRepresentation(AutomorphismGroup(Q1)); sigmaList := [(Inverse(phiQ1AutToPerm))(perm) * iso0 : perm in AutQ1AsPermRep]; // sigmaList := [iso0]; // Naive approach (fix one iso Q1 Q2). // N.B. The initial value of GStarGens is not empty! Rather, // we define it to be the set of all pairs [I1,n2], where I1 is // the identity element of G1, and n2 runs over all elements in // a set of generators of N2. This ensures that all elements of // the form [I1,n2] for all n2 in N2 will be in GStar. (All // such elements are indeed in GStar, for all isomorphisms // sigma : sigma(pi1(I1)) = N2, so each n2 in N2 is a lift // under pi2.) Furthermore, this ensures that once one repre- // sentative [g1 n1,g2 n2] of [g1 N1,g2 N2] is in GStarGens, // all #(N2) elements of [g1,g2 N2] in G1 x G2 will be in GStar // (elements of the form [I1,n2] allow us to multiply by n2 in // the second factor of G1 x G2) and hence all #(N1) #(N2) ele- // ments of [g1 N1,g2 N2] in G1 x G2 will be in GStar. for sigma in sigmaList do GStarGens := [* [* I1,n2 *] : n2 in GeneratorsNonemptySet(N2) *]; for g1 in GeneratorsNonemptySet(G1) do g2N2 := sigma(pi1(g1)); for g2 in G2 do // We test elements of G2 until we find one that maps // to g2N2 under the natural projection map pi2. // Find more efficient way to lift g2 from g2N2? !!! if (pi2(g2) eq g2N2) then g2N2Rep := g2; break; // Breaks g2 for loop. end if; end for; for n2 in GeneratorsNonemptySet(N2) do Append(~GStarGens,[* g1,g2N2Rep * n2 *]); end for; end for; // The following code creates subgroup of G1 x G2 corres- // ponding to GStarGens, then extracts a set of genera- // tors, each in the form [g1,g2] (i.e. as a sequence of 2 // matrices, not as a single matrix). GStar := sub; GStarGens := [* [* ExtractBlock(g,1,1,n1,n1),ExtractBlock(g,n1 + 1,n1 + 1,n2,n2) *] : g in GeneratorsNonemptySet(GStar) *]; CFs := ConnectingFunctions(GStarGens); // If one can speed computation by collecting subsets of // groups (possibly over multiple values of normal sub- // groups n1 and n2) and analyzing within those subsets for // GLZ conjugacy, do so. (This is the idea of the variable // groupsNew below.) // groupsNew := []; for Y in CFs do YQ := ChangeRing(Y,RationalField()); Append(~groups,sub); end for; // End Y for loop. end for; // End sigma for loop. end if; // End isIso if. i +:= 1; end while; end if; end for; PruneZConjugates(~groups,true); return groups; /* // !!! I believe we can omit the following code, if desired, if we // simply PruneZConjugates from the above (or accept redundancy) !!! // !!! code block << PGStarGenSet >> !!! PGStarGenSet := {}; for g in GeneratorsNonemptySet(NormalizerPair[1]) do Include(~PGStarGenSet,[g,I2]); end for; for g in GeneratorsNonemptySet(NormalizerPair[2]) do Include(~PGStarGenSet,[I1,g]); end for; CSeq := []; lambdaPrimeSeq := []; for g in PGStarGenSet do for Y in YList do CVector := QMat^(-1) * Matrix(n1 * n2,1,ElementToSequence(g[1]^(-1) * Y * g[2])); Append(~CSeq,CVector); // Verify integer division by EDVProduct next line is correct !!! Append(~lambdaPrimeSeq,[((EDV[i] * CVector[i][1]) div EDVProduct) mod EDV[i] : i in [1..#(EDV)]]); end for; end for; // Build each group from the component blocks (g1 in top // left, g2 in bottom right, CF in top right, 0 in bottom // left), save to temp list. Prune temp list of Z-conju- // gates. Return pruned list. end if; // End isIso if. i +:= 1; end while; end if; end for; */ // return [DirectProduct(G1,G2)]; // !!! for testing only !!! end function; // Begin main. FiniteSubgroupsGLnZ := [ [[*[[1,1]],[MatrixGroup<1,Z | [1]>]*],[*[[1,2]],[MatrixGroup<1,Z | [-1]>]*]] ]; nICQ := [2]; nUnassignedIrreducibleGLnZ := [0]; // !!! for debug only !!! nRIrr := [2]; // !!! for debug only !!! nBuilt := [0]; // !!! for debug only !!! nNotRIrrNotBuilt := [0]; // !!! for debug only !!! for n in [nMin..nMax] do // Compute sequence of 1 representative for each Q-class of irreducible // finite subgroup of GL(n,Z). ifsQ := IrreducibleFiniteSubgroupsGLnQ(n); Append(~nICQ,#(ifsQ)); // Initialize a list of finite subgroups of GL(n,Z) for this rank. This // initialization creates entries of the form // [ key , sequence of groups ] // one for each Q-class of irreducible finite subgroup in GL(n,Q). fsGLnZ := [ [* [[n,i]],[] *] : i in [1..nICQ[n]] ]; subsNotRIrr := []; // !!! for debug only !!! nUnassignedIrrs := 0; // !!! for debug only !!! // Loop over all partitions (of positive integers) of n. Elements of the // partition correspond to the ranks of the irreducible constituents of the // groups we will construct. The partition [ n ] of n corresponds to irre- // ducible subgroups of rank n. // // N.B. We use Reverse() to put the elements in each partition in in- // creasing order. This allows us to follow the algorithm in Brown et al. // (1972) more straightforwardly. for p in [Reverse(p) : p in Partitions(n)] do if (#(p) eq 1) then // Compute 1 representative for each Z-class of irreducible sub- // group of rank n. for j in [1..NumberOfGroups(DBZ,n)] do for G in [G`subgroup : G in Subgroups(Group(DBZ,n,j))] do if (IsIrreducibleRationalMatrixGroup(G)) then noMatch := true; // !!! for debug only !!! for i in [1..nICQ[n]] do if (IsGLQConjugate(G,ifsQ[i])) then noMatch := false; // !!! for debug only !!! Append(~fsGLnZ[i,2],G); break; end if; end for; // !!! following if for debug only !!! if (noMatch) then nUnassignedIrrs +:= 1; end if; else Append(~subsNotRIrr,G); // !!! for debug only !!! end if; end for; // End G for loop. end for; // End j for loop. for i in [1..nICQ[n]] do PruneZConjugates(~fsGLnZ[i,2],false); end for; PruneZConjugates(~subsNotRIrr,true);// !!! for debug only !!! else // The "else" branch executes iff the partition p is nontrivial, // i.e. contains more than one positive integer. // Compute 1 representative for each Z-class of reducible subgroup // of rank n. // !!! consolidate the following for loops : write a function that // takes partition p as input and returns sequence or set of all // "split" sequences whose entries are "weakly decreasing". See // Carnet 5 p 93 !!! for ic in CartesianProduct([[1..nICQ[i]] : i in p]) do icSig0 := [[p[i],ic[i]] : i in [1..#(p)]]; // Analyze only sequences whose irreducible constituents // (encoded [rank,index]) are "in order", where indices are as // ordered in ifsQ. (By construction of the partition p, the // ranks will be in order.) Sequences whose irreducible con- // stituents are "out of order" are accounted for by the cor- // responding "in order" sequence. if (IsIncreasingSequence(icSig0)) then // Split the sequence, as described in Brown et al. (1972). if (icSig0[1] eq icSig0[#(icSig0)]) then r := #(icSig0) - 1; else for i in [(#(icSig0) - 1)..1 by -1] do if (icSig0[i] ne icSig0[i + 1]) then r := i; break; end if; end for; end if; icSig := [[icSig0[i] : i in [1..r]], [icSig0[i] : i in [(r + 1)..#(icSig0)]]]; // The following chunk of code searches the "dictionary" // of irreducible constituents for a key match. When // found, save the corresponding sequence of groups with // that key (i.e. collection of irreducible constituents) // as L1 or L2, as appropriate. GiRanks := []; LPair := []; for i in [1..2] do Append(~GiRanks,&+[ic[1] : ic in icSig[i]]); for GRec in FiniteSubgroupsGLnZ[GiRanks[i]] do if (icSig[i] eq GRec[1]) then Append(~LPair,GRec[2]); break; end if; end for; // End GRec for loop. end for; // End i for loop. n1 := GiRanks[1]; n2 := GiRanks[2]; I1 := ScalarMatrix(n1,1); I2 := ScalarMatrix(n2,1); // !!! use CartesianProduct()? tradeoffs? !!! rfsL1L2 := []; for G1 in LPair[1] do for G2 in LPair[2] do rfsL1L2 cat:= ReducibleFiniteSubgroupsG1G2(G1,G2); end for; // End G2 for loop. end for; // End G1 for loop. // Append [icSig,seqOfGroups] to fsGLnZ. PruneZConjugates(~rfsL1L2,true); Append(~fsGLnZ,[* icSig0,rfsL1L2 *]); end if; end for; // End ic for loop. end if; end for; // End p for loop. // !!! next chunk of code for debug only !!! subsNotRIrrNotBuilt := []; for G in subsNotRIrr do matchFound := false; for rec in fsGLnZ do for G0 in rec[2] do if (IsGLZConjugate(G,G0)) then matchFound := true; break rec; end if; end for; end for;// End rec for loop. if not(matchFound) then Append(~subsNotRIrrNotBuilt,G); end if; end for; printf "Rank %o : Number of subgroups of maximal Q-irreducible subgroups which are not R-irreducible AND were not constructed (as reducible) by our algorithm : %o\n",n,#(subsNotRIrrNotBuilt); // !!! end for debug only !!! // !!! next chunk of code for debug only !!! // !!! delete when irreducibility detection corrected !!! Append(~nRIrr,&+[#(rec[2]) : rec in fsGLnZ | #(rec[1]) eq 1]); Append(~nBuilt,&+[#(rec[2]) : rec in fsGLnZ | #(rec[1]) gt 1]); count := &+[1 : rec in fsGLnZ | #(rec[1]) eq 1]; printf "count = %o\n",count; for G in subsNotRIrrNotBuilt do count +:= 1; Insert(~fsGLnZ,count,[*[[n,count]],[G]*]); end for; // !!! end for debug only !!! Append(~FiniteSubgroupsGLnZ,fsGLnZ); Append(~nUnassignedIrreducibleGLnZ,nUnassignedIrrs); // !!! for debug only !!! Append(~nNotRIrrNotBuilt,#(subsNotRIrrNotBuilt)); // !!! for debug only !!! nICQ[n] +:= nNotRIrrNotBuilt[n]; // !!! for debug only !!! // !!! for debug only begins here !!! nExpected := [2,13,73,710,6079,85308]; print " | #(Z-conj classes fin subs) | ...of which, among the subs found,... "; print " | | | | | #(not R-irred "; print " n | expected | found | #(R-irred) | #(built) | not built) "; print "---+-------------+--------------+------------+----------+---------------"; nSubs := 0; for rec in FiniteSubgroupsGLnZ[n] do nSubs +:= #(rec[2]); end for; printf "%2o | %7o | %7o = %7o + %6o + %8o\n", n,nExpected[n],nSubs,nRIrr[n],nBuilt[n],nNotRIrrNotBuilt[n]; // !!! for debug only ends here !!! end for; // Build sequence of sequences of subgroups only. One sequence per rank. // !!! rename, using full name from above, and changing above !!! fsGLnZ := []; for n in [1..#(FiniteSubgroupsGLnZ)] do Append(~fsGLnZ,&cat[rec[2] : rec in FiniteSubgroupsGLnZ[n]]); end for; // Check number of Z-conjugacy classes of finite subgroups of GL(n,Z). PrintHeading("Summary of results"); nExpected := [2,13,73,710,6079,85308]; print " | #(Z-conj classes fin subs) | ...of which, among the subs found,... "; print " | | | | | #(not R-irred "; print " n | expected | found | #(R-irred) | #(built) | not built) "; print "---+-------------+--------------+------------+----------+---------------"; for n in [1..#(FiniteSubgroupsGLnZ)] do nSubs := 0; // nRIrr := 0; for rec in FiniteSubgroupsGLnZ[n] do nSubs +:= #(rec[2]); // if (#(rec[1]) eq 1) then // nRIrr +:= #(rec[2]); // end if; end for; printf "%2o | %7o | %7o = %7o + %6o + %8o\n", n,nExpected[n],nSubs,nRIrr[n],nBuilt[n],nNotRIrrNotBuilt[n]; end for; save "fsGLnZ.ws"; rankMax := #(fsGLnZ); // If set to true, the following data will be displayed for each subgroup: // (*) Index (position) of subgroup in the rank-n sequence in fsGLnZ // (*) Generators (as matrices) of the subgroup // (*) Whether the subgroup is cyclic // (*) The order of the subgroup // (*) Whether the subgroup is admissible as the Galois action on an algebraic K3 surface // (*) The subgroup's first Galois cohomology displayEachSubgroupData := false; // !save functions and procedures to separate, stand-alone files!!! // !create spec file, load spec!!! //////////////////////////////////////////////////////////////////////////////// // // Function : CountGLZConjugates // // Arguments // 1. glSeq : A sequence of finite (or rank-2) subgroups of a general linear // matrix group. All matrices must have integer or rational entries. // Returns // 1. The number of GL(n,Z)-conjugate pairs in the input sequence. // 2. A sequence of sequence pairs [i,j] recording the indices, in the input // sequence, of GL(n,Z)-conjugate subgroups. // Description // Checks for pairwise GL(n,Z)-conjugacy among all groups in the input // sequence. If a pair of GL(n,Z)-conjugate subgroups is found, the indices // (in the input list) of the subgroups are recorded. function CountGLZConjugates(glzSeq) indCP := []; nSubs := #(glzSeq); for i in [1..nSubs] do for j in [(i + 1)..nSubs] do printf "(i,j) = (%2o,%2o)\n",i,j;// !for debug only!!! if (IsGLZConjugate(glzSeq[i],glzSeq[j])) then Append(~indCP,[i,j]); end if; end for; end for; return #(indCP),indCP; end function; //////////////////////////////////////////////////////////////////////////////// // // Function : IsK3Admissible // // Arguments // 1. G : A finite subgroup of GL(n,Z) for some n in {1,...,20}. // Returns // 1. boolean (true or false). // Description // !!! function IsK3Admissible(G) k3Adm := false; // !decide default: false or true!!! if (IsTrivial(G)) then k3Adm := true; else n := NumberOfRows(Random(G)); // ?better method??? // Check: Nontrivial eigenspace for eigenvalue 1 CM := CohomologyModule(G,GModule(G)); // ?better method??? eigenspace1 := VectorSpace(RationalField(),n); for g in Generators(G) do eigenspace1 meet:= Eigenspace(ChangeRing(MatrixOfElement(CM,g),RationalField()),1); end for; if (Dimension(eigenspace1) gt 0) then k3Adm := true; end if; end if; return k3Adm; end function; // Report subgroup attributes and first cohomology. for n in [1..rankMax] do PrintHeading("Rank " cat IntegerToString(n) cat " : Finite subgroups"); setOfH1 := {**}; setOfH1Cyc := {**}; setOfH1K3Adm := {**}; setOfH1K3AdmCyc := {**}; for i in [1..#(fsGLnZ[n])] do // printf "n = %o | i = %o\n",n,i; // !for debug only!!! // G := fsGLnZ[n,i]; // This line fails for some subs. Replaced by line below. G := sub; // !determine if, why this coercion necessary!!! isCyclic := IsCyclic(G); isK3Adm := IsK3Admissible(G); // Compute first cohomology if (IsTrivial(G)) then CM := RSpace(Z,0); // !verify this is OK!!! CG := quo; // !verify this is OK!!! CGModuli := Moduli(CG); // ?better method??? else CM := CohomologyModule(G,GModule(G)); CG := CohomologyGroup(CM,1); CGModuli := Moduli(CG); end if; // ?should we also report the degree of the moduli space??? if (displayEachSubgroupData) then printf "Subgroup %o of GL(%o,Z)\n",i,n; printf "Generators:\n%o\n",Generators(G); printf "Cyclic ? %o\n",isCyclic; printf "Order : %o\n",#(G); printf "K3-admissible ? %o\n",isK3Adm; printf "H^1 moduli : %o\n",CGModuli; print ""; end if; // Record moduli in appropriate counter sets. Include(~setOfH1,CGModuli); if (isCyclic) then Include(~setOfH1Cyc,CGModuli); end if; if (isK3Adm) then Include(~setOfH1K3Adm,CGModuli); if (isCyclic) then Include(~setOfH1K3AdmCyc,CGModuli); end if; end if; end for; // End for loop : i (finite subgroups of GL(n,Z)) printf "Set of H^1 :\n%o\n\n",setOfH1; printf "Set of H^1 (cyclic) :\n%o\n\n",setOfH1Cyc; printf "Set of H^1 (cyclic) = Set of H^1? %o\n\n",MultisetToSet(setOfH1) eq MultisetToSet(setOfH1Cyc); printf "Set of H^1 (K3-admissible) :\n%o\n\n",setOfH1K3Adm; printf "Set of H^1 (K3-admissible and cyclic) :\n%o\n\n",setOfH1K3AdmCyc; printf "Set of H^1 (K3-adm, cyc) = Set of H^1 (K3-adm)? %o\n\n",MultisetToSet(setOfH1K3AdmCyc) eq MultisetToSet(setOfH1K3Adm); end for; // End for loop : n (rank) save "fsGLnZwH1.ws"; //exit;