From 93ddb13ad040a8a02c1d78e6dac460670b844159 Mon Sep 17 00:00:00 2001 From: Stevell Muller <78619134+StevellM@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:31:15 +0200 Subject: [PATCH] `QuadFormWithIsom` Patch 2: towards better time in CI (#2825) --- .../QuadFormAndIsom/src/embeddings.jl | 344 +++++++++--------- .../QuadFormAndIsom/src/enumeration.jl | 74 ++-- .../src/hermitian_miranda_morrison.jl | 51 +-- .../src/lattices_with_isometry.jl | 68 +++- src/GAP/gap_to_oscar.jl | 4 +- src/Groups/abelian_aut.jl | 2 +- src/Groups/gsets.jl | 12 +- src/Groups/matrices/MatGrp.jl | 2 +- 8 files changed, 290 insertions(+), 267 deletions(-) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index bb85f6518515..c60691cb1091 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -13,9 +13,9 @@ # together with the embeddings O(A) \to O(D) and O(B) \to O(D) function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModule) D = A+B - AinD = hom(A, D, TorQuadModuleElem[D(lift(a)) for a in gens(A)]) - BinD = hom(B, D, TorQuadModuleElem[D(lift(b)) for b in gens(B)]) - @assert all(AinD(a)*BinD(b) == 0 for a in gens(A), b in gens(B)) + AinD = hom(A, D, elem_type(D)[D(lift(a)) for a in gens(A)]) + BinD = hom(B, D, elem_type(D)[D(lift(b)) for b in gens(B)]) + @hassert :ZZLatWithIsom 1 all(AinD(a)*BinD(b) == 0 for a in gens(A), b in gens(B)) OD = orthogonal_group(D) OA = orthogonal_group(A) OB = orthogonal_group(B) @@ -25,7 +25,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu for f in gens(OA) imgf = data.(union!(AinD.(f.(gens(A))), BinD.(gens(B)))) fab = hom(gene, imgf) - fD = OD(hom(D, D, fab.map)) + fD = OD(hom(D, D, fab.map); check = false) push!(geneOAinOD, fD) end @@ -33,7 +33,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu for f in gens(OB) imgf = data.(union!(AinD.(gens(A)), BinD.(f.(gens(B))))) fab = hom(gene, imgf) - fD = OD(hom(D, D, fab.map)) + fD = OD(hom(D, D, fab.map); check = false) push!(geneOBinOD, fD) end OAtoOD = hom(OA, OD, geneOAinOD; check = false) @@ -85,11 +85,11 @@ end # This object is defined in Algorithm 2 of [BH23], the glue kernel we aim to use # for gluing lattices in a p-admissible triples are actually submodules of these # V's. -function _get_V(fq::TorQuadModuleMor, mu, p::IntegerUnion) +function _get_V(fq::TorQuadModuleMor, mu::PolyRingElem, p::IntegerUnion) q = domain(fq) V, _ = primary_part(q, p) _, Vinq = sub(q, elem_type(q)[q(lift(divexact(order(g), p)*g)) for g in gens(V) if !(order(g)==1)]) - fpV = restrict_endomorphism(fq, Vinq) + fpV = restrict_endomorphism(fq, Vinq; check = false) fpV = evaluate(mu, fpV) V, _ = kernel(fpV) Vinq = hom(V, q, elem_type(q)[q(lift(a)) for a in gens(V)]) @@ -98,7 +98,7 @@ function _get_V(fq::TorQuadModuleMor, mu, p::IntegerUnion) end # This is the rho function as defined in Definition 4.8 of BH23. -function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}; quad::Bool = (p == 2)) +function _rho_functor(q::TorQuadModule, p::IntegerUnion, l::IntegerUnion; quad::Bool = (p == 2)) pq, pqtoq = primary_part(q, p) pq = rescale(pq, QQ(p)^(l-1)) Nv = cover(pq) @@ -127,12 +127,12 @@ end # A finite bilinear module over the 2-adic integers is even if all square are # zeros. -function _is_even(T, p, l) +function _is_even(T::TorQuadModule, p::IntegerUnion, l::IntegerUnion) B = gram_matrix_bilinear(_rho_functor(T, p, l; quad = false)) return is_empty(B) || (all(is_zero, diagonal(B)) && all(is_integral, 2*B)) end -function _is_free(T, p, l) +function _is_free(T::TorQuadModule, p::IntegerUnion, l::IntegerUnion) return _is_even(T, p, l-1) && _is_even(T, p, l+1) end @@ -169,24 +169,24 @@ function _overlattice(gamma::TorQuadModuleMor, z = zero_matrix(QQ, 0, degree(A)) glue = reduce(vcat, QQMatrix[matrix(QQ, 1, degree(A), g) for g in _glue]; init=z) glue = vcat(basis_matrix(A+B), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + Fakeglue = FakeFmpqMat(glue) + _FakeB = hnf(Fakeglue) + _B = QQ(1, denominator(Fakeglue))*change_base_ring(QQ, numerator(_FakeB)) C = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) - fC = block_diagonal_matrix([fA, fB]) + fC = block_diagonal_matrix(QQMatrix[fA, fB]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C)) fC = _B*fC*inv(_B) else _glue = Vector{QQFieldElem}[lift(HAinD(a)) + lift(HBinD(gamma(a))) for a in gens(domain(gamma))] z = zero_matrix(QQ, 0, degree(cover(D))) glue = reduce(vcat, QQMatrix[matrix(QQ, 1, degree(cover(D)), g) for g in _glue]; init=z) - glue = vcat(block_diagonal_matrix(basis_matrix.([A, B])), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + glue = vcat(block_diagonal_matrix(basis_matrix.(ZZLat[A, B])), glue) + Fakeglue = FakeFmpqMat(glue) + _FakeB = hnf(Fakeglue) + _B = QQ(1, denominator(Fakeglue))*change_base_ring(QQ, numerator(_FakeB)) C = lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) - fC = block_diagonal_matrix([fA, fB]) - _B = solve_left(block_diagonal_matrix(basis_matrix.([A, B])), basis_matrix(C)) + fC = block_diagonal_matrix(QQMatrix[fA, fB]) + _B = solve_left(block_diagonal_matrix(basis_matrix.(ZZLat[A, B])), basis_matrix(C)) fC = _B*fC*inv(_B) end @hassert :ZZLatWithIsom 1 fC*gram_matrix(C)*transpose(fC) == gram_matrix(C) @@ -220,7 +220,7 @@ end function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) - gene = TorQuadModuleElem[g(q(lift(t))) for t in gens(T)] + gene = elem_type(q)[g(q(lift(t))) for t in gens(T)] return sub(q, gene)[1] end @@ -231,7 +231,7 @@ function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) q = domain(O) N, _ = sub(q, i.(gens(domain(i)))) stab, _ = stabilizer(O, N, _on_subgroup_automorphic) - return sub(O, O.([h.X for h in gens(stab)])) + return sub(O, elem_type(O)[O(h) for h in gens(stab)]) end function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, @@ -248,7 +248,7 @@ function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor return res end - fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) + fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq; check = false) : restrict_endomorphism(hom(f), Vinq; check = false) if ord == -1 subs = collect(stable_submodules(V, TorQuadModuleMor[fV])) else @@ -294,7 +294,7 @@ function _cokernel_as_Fp_vector_space(HinV::TorQuadModuleMor, p::IntegerUnion) function _VptoV(v::ModuleElem{FpFieldElem}) x = lift.(v.v) - return sum(TorQuadModuleElem[x[i]*V[i] for i in 1:ngens(V)]) + return sum(x[i]*V[i] for i in 1:n) end VtoVp = Hecke.MapFromFunc(V, Vp, _VtoVp, _VptoV) @@ -308,15 +308,15 @@ end # Almost duplicate of an existing function: we do not always want to compute # stabilizers but just some orbit representatives function _orbit_representatives(G::MatrixGroup{E}, k::Int, O::AutomorphismGroup{TorQuadModule}) where E <: FinFieldElem - F = G.ring - n = G.deg + F = base_ring(G) + n = degree(G) q = GAP.Obj(order(F)) V = VectorSpace(F, n) orbs = GAP.Globals.Orbits(G.X, GAP.Globals.Subspaces(GAP.Globals.GF(q)^n, k)) - orbs = [GAP.Globals.BasisVectors(GAP.Globals.Basis(orb[1])) for orb in orbs] - orbs = [[[F(x) for x in v] for v in bas] for bas in orbs] - orbs = [sub(V, [V(v) for v in bas])[1] for bas in orbs] - return [(orbs[i], O) for i in 1:length(orbs)] + orbs1 = [GAP.Globals.BasisVectors(GAP.Globals.Basis(orb[1])) for orb in orbs] + orbs2 = [[[F(x) for x in v] for v in bas] for bas in orbs1]::Vector{Vector{Vector{elem_type(F)}}} + orbs3 = [sub(V, [V(v) for v in bas])[1] for bas in orbs2] + return [(orbs3[i], O) for i in 1:length(orbs3)] end # Given an abelian group injection V \to q where the group structure on V is @@ -366,7 +366,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu # In theory, V should contain H0 := p^l*pq where pq is the p-primary part of q all(a -> has_preimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) || return res - H0, H0inq = sub(q, TorQuadModuleElem[q(lift((p^l)*a)) for a in gens(pq)]) + H0, H0inq = sub(q, elem_type(q)[q(lift((p^l)*a)) for a in gens(pq)]) @hassert :ZZLatWithIsom 1 is_invariant(f, H0inq) # H0 should be contained in the group we want. So either H0 is the only one @@ -377,7 +377,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end - H0inV = hom(H0, V, TorQuadModuleElem[V(lift(a)) for a in gens(H0)]) + H0inV = hom(H0, V, elem_type(V)[V(lift(a)) for a in gens(H0)]) @hassert :ZZLatWithIsom 1 is_injective(H0inV) # Since V and H0 are elementary p-groups, they can be seen as finite @@ -391,7 +391,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu dim(Qp) == 0 && return res # We descend G to V for computing stabilizers later on - GV, GtoGV = restrict_automorphism_group(G, Vinq) + GV, GtoGV = restrict_automorphism_group(G, Vinq; check = false) if compute_stab satV, j = kernel(GtoGV) end @@ -421,20 +421,20 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu for (orb, stab) in orb_and_stab i = orb.map - gene_orb = elem_type(Qp)[Qp(vec(collect(i(v).v))) for v in gens(domain(i))] - gene_orb = elem_type(Vp)[preimage(VptoQp, v) for v in gene_orb] + gene_orbQp = elem_type(Qp)[Qp(vec(collect(i(v).v))) for v in gens(domain(i))] + gene_orbVp = elem_type(Vp)[preimage(VptoQp, v) for v in gene_orbQp] - gene_orb = vcat(gene_orb, gene_H0p) - gene_orb = elem_type(V)[preimage(VtoVp, Vp(v)) for v in gene_orb] - gene_orb = elem_type(q)[image(Vinq, v) for v in gene_orb] - orbq, orbqinq = sub(q, gene_orb) + gene_orbVp = vcat(gene_orbVp, gene_H0p) + gene_orbV = elem_type(V)[preimage(VtoVp, Vp(v)) for v in gene_orbVp] + gene_orbq = elem_type(q)[image(Vinq, v) for v in gene_orbV] + orbq, orbqinq = sub(q, gene_orbq) @hassert :ZZLatWithIsom 1 order(orbq) == ord # We keep only f-stable subspaces is_invariant(f, orbqinq) || continue if compute_stab - stabq = AutomorphismGroupElem{TorQuadModule}[GtoMGp\(s) for s in gens(stab)] - stabq, _ = sub(G, union!(stabq, gens(satV))) + stabq_gen = elem_type(G)[GtoMGp\(s) for s in gens(stab)] + stabq, _ = sub(G, union!(stabq_gen, gens(satV))) # Stabilizers should preserve the actual subspaces, by definition. so if we # have lifted since properly, this should hold.. @hassert :ZZLatWithIsom 1 is_invariant(stabq, orbqinq) @@ -472,7 +472,7 @@ function _classes_automorphic_subgroups(q::TorQuadModule, # primary part of H. # # First, we cut q as an orthogonal direct sum of its primary parts - pds = sort(prime_divisors(order(q))) + pds = sort!(prime_divisors(order(q))) if compute_stab blocks = TorQuadModuleMor[primary_part(q, pds[1])[2]] ni = Int[ngens(domain(blocks[1]))] @@ -489,7 +489,8 @@ function _classes_automorphic_subgroups(q::TorQuadModule, blocks = TorQuadModuleMor[primary_part(q, p)[2] for p in pds] D, inj, proj = biproduct(domain.(blocks)) end - _, phi = is_isometric_with_isometry(D, q) + phi = hom(D, q, TorQuadModuleElem[sum([blocks[i](proj[i](a)) for i in 1:length(pds)]) for a in gens(D)]) + @hassert :ZZLatWithIsom 1 is_isometry(phi) list_can = Vector{Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}}[] # We collect the possible subgroups for each primary part, with the stabilizer @@ -498,8 +499,8 @@ function _classes_automorphic_subgroups(q::TorQuadModule, qpinq = blocks[i] qp = domain(qpinq) T, _ = primary_part(H, p) - Oqp = restrict_automorphism_group(O, qpinq)[1] - fqp = restrict_endomorphism(f, qpinq) + Oqp, _ = restrict_automorphism_group(O, qpinq; check = false) + fqp = restrict_endomorphism(f, qpinq; check = false) if is_elementary(T, p) _, j = _get_V(id_hom(qp), minimal_polynomial(identity_matrix(QQ, 1)), p) sors = _subgroups_orbit_representatives_and_stabilizers_elementary(j, Oqp, order(T), fqp; compute_stab) @@ -520,7 +521,7 @@ function _classes_automorphic_subgroups(q::TorQuadModule, embs = TorQuadModuleMor[l[1] for l in lis] embs = TorQuadModuleMor[hom(domain(embs[i]), q, TorQuadModuleElem[blocks[i](domain(blocks[i])(lift(embs[i](a)))) for a in gens(domain(embs[i]))]) for i in 1:length(lis)] H2, _proj = direct_product(domain.(embs)...) - _, H2inq = sub(q, TorQuadModuleElem[sum([embs[i](_proj[i](g)) for i in 1:length(lis)]) for g in gens(H2)]) + _, H2inq = sub(q, elem_type(q)[sum([embs[i](_proj[i](g)) for i in 1:length(lis)]) for g in gens(H2)]) if compute_stab stabs = AutomorphismGroup{TorQuadModule}[l[2] for l in lis] genestab = ZZMatrix[] @@ -600,11 +601,11 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, HM = domain(HMinqM) OHM = orthogonal_group(HM) - actN = hom(stabN, OHN, elem_type(OHN)[OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) - actM = hom(stabM, OHM, elem_type(OHM)[OHM(restrict_automorphism(x, HMinqM)) for x in gens(stabM)]) + actN = hom(stabN, OHN, elem_type(OHN)[OHN(restrict_automorphism(x, HNinqN; check = false); check = false) for x in gens(stabN)]) + actM = hom(stabM, OHM, elem_type(OHM)[OHM(restrict_automorphism(x, HMinqM; check = false); check = false) for x in gens(stabM)]) imM, _ = image(actM) - stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] + stabNphi = elem_type(OHM)[OHM(compose(inv(phi), compose(hom(actN(g)), phi)); check = false) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) if is_elementary_with_prime(HM)[1] @@ -862,7 +863,7 @@ function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :su # $M\perp L2 \subset T \subset Mv\perp L1$. # # The upshot is that both $(T\cap S)/(M\perp L2)$ and - # $(T\cap Sv)/(M\perpL2) lies in the embedding of $qM \to D$. Their + # $(T\cap Sv)/(M\perp L2) lies in the embedding of $qM \to D$. Their # quotient is isometric to the quotient of their preimage. # The quotient of their preimage is precisely the subgroup of `qM` # we look for. @@ -961,8 +962,8 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, if g == 0 # Needed to compute the image of the stabilizer of the isometry we construct # (in the orthogonal group of the discriminant group of the new lattice). - geneA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(OqA(a.X)) for a in gens(GA)] - geneB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(OqB(b.X)) for b in gens(GB)] + geneA = elem_type(OD)[OqAinOD(OqA(a.X)) for a in gens(GA)] + geneB = elem_type(OD)[OqBinOD(OqB(b.X)) for b in gens(GB)] union!(geneA, geneB) # We compute the overlattice in this context @@ -974,7 +975,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) - phi2 = hom(qC2, D, TorQuadModuleElem[D(lift(x)) for x in gens(qC2)]) + phi2 = hom(qC2, D, elem_type(D)[D(lift(x)) for x in gens(qC2)]) @hassert :ZZLatWithIsom 1 is_isometry(phi2) # This is the new image of the stabilizer, just a direct product of @@ -1029,35 +1030,23 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, for H1 in subsA, H2 in subsB ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) !ok && continue + SAinqA, stabA = H1 SA = domain(SAinqA) SAinD = compose(SAinqA, qAinD) OSA = orthogonal_group(SA) + fSA = OSA(restrict_automorphism(fqA, SAinqA; check = false); check = false) SBinqB, stabB = H2 SB = domain(SBinqB) SBinD = compose(SBinqB, qBinD) OSB = orthogonal_group(SB) - + fSB = OSB(restrict_automorphism(fqB, SBinqB; check = false); check = false) + # we need a first admissible gluing. We know that such gluing exists because # we have an admissible triple as input and the glue kernels have been # chosen in such a way that their exist an admissible gluing between them. - phi = _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) - - # we compute the image of the stabilizers in the respective OS* and we keep track - # of the elements of the stabilizers acting trivially in the respective S* - # (there are in the ker*). - actA = hom(stabA, OSA, elem_type(OSA)[OSA(restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) - imA, _ = image(actA) - kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(kernel(actA)[1])] - push!(kerA, OqAinOD(one(OqA))) - fSA = OSA(restrict_automorphism(fqA, SAinqA)) - - actB = hom(stabB, OSB, elem_type(OSB)[OSB(restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) - imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(kernel(actB)[1])] - push!(kerB, OqBinOD(one(OqB))) - fSB = OSB(restrict_automorphism(fqB, SBinqB)) + phi = _find_admissible_gluing(SAinqA, SBinqB, phi, l, p, spec) # We want all isometries of SB which preserves p^l*q_B and such that they # define isometries of rho_{l+1}(B). If `spec == true`, then rho_{l+1}(B) is @@ -1070,7 +1059,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # phi might not "send" the restriction of fA to this of fB, but at least phi*fA*phi^-1 # should be conjugate to fB inside O(SB, rho_l(qB)) for the gluing. # If not, we try the next potential pair. - fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi)); check = false) @hassert :ZZLatWithIsom 1 fSAinOSB in OSBrB # Same as before, since phi is admissible, then the image of fSA should preserve rho_{l+1}(B) bool, g0 = is_conjugate_with_data(OSBrB, OSBrB(fSAinOSB), fSB) bool || continue @@ -1078,13 +1067,22 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # The new phi is "sending" the restriction of fA to this of fB # and it is still admissible. So we can glue SA and SB as wanted. phi = compose(phi, hom(OSB(g0))) - @hassert :ZZLatWithIsom OSBrB(compose(inv(phi), compose(hom(fSA), phi))) == fSB + @hassert :ZZLatWithIsom 1 OSBrB(compose(inv(phi), compose(hom(fSA), phi)); check = false) == fSB + + # we compute the image of the stabilizers in the respective OS* and we keep track + # of the elements of the stabilizers acting trivially in the respective S* + # (there are in the ker*). + actA = hom(stabA, OSA, elem_type(OSA)[OSA(restrict_automorphism(x, SAinqA; check = false); check = false) for x in gens(stabA)]) + imA, _ = image(actA) + + actB = hom(stabB, OSB, elem_type(OSB)[OSB(restrict_automorphism(x, SBinqB; check = false); check = false) for x in gens(stabB)]) + imB, _ = image(actB) # Now it is time to compute generators for O(SB, rho_l(qB), fB), and the induced # images of stabA|stabB for taking the double cosets next center, _ = centralizer(OSBrB, fSB) center, _ = sub(OSB, elem_type(OSB)[OSB(c) for c in gens(center)]) - stabSAphi, _ = sub(OSB, elem_type(OSB)[OSB(compose(inv(phi), compose(hom(g), phi))) for g in gens(imA)]) + stabSAphi, _ = sub(OSB, elem_type(OSB)[OSB(compose(inv(phi), compose(hom(g), phi)); check = false) for g in gens(imA)]) stabSAphi, _ = intersect(center, stabSAphi) stabSB, _ = intersect(center, imB) @@ -1108,29 +1106,13 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # This is the type requirement: somehow, we want `(C2, fC2)` to be a "q-th root" of `(C, fC)`. !is_of_type(C2fC2^q, type(C)) && continue - # By the theory of primitive extensions, the discriminant group qC2 of C2 - # is equal to H^{perp}/H where H is the graph of phig in D = qA + qB. We need - # to treat both at the same time to compute the image of the centralizer - # O(C2, fC2) in O(qC2, fqC2) using GA and GB. - ext = domain(extinD) - perp, j = orthogonal_submodule(D, ext) - disc = torsion_quadratic_module(cover(perp), cover(ext); modulus = modulus_bilinear_form(perp), - modulus_qf = modulus_quadratic_form(perp)) + disc, stab = _glue_stabilizers(phig, actA, actB, OqAinOD, OqBinOD, extinD) + qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) - phi2 = hom(qC2, disc, TorQuadModuleElem[disc(lift(x)) for x in gens(qC2)]) + phi2 = hom(qC2, disc, elem_type(disc)[disc(lift(x)) for x in gens(qC2)]) @hassert :ZZLatWithIsom 1 is_isometry(phi2) # In fact they are the same module so phi2, mathematically, is the identity. - # So now this new integer lattice with isometry `(C2, fC2)` is a good - # output. Just remain to compute GC2 in a smart way. - geneOSA = AutomorphismGroupElem{TorQuadModule}[OSA(compose(phig, compose(hom(g1), inv(phig)))) for g1 in unique(gens(imB))] - im2_phi, _ = sub(OSA, geneOSA) - im3, _, _ = intersect(imA, im2_phi) - stab = AutomorphismGroupElem{TorQuadModule}[OqAinOD(actA\x) * OqBinOD(actB\(imB(compose(inv(phig), compose(hom(x), phig))))) for x in gens(im3)] - union!(stab, kerA) - union!(stab, kerB) - stab = TorQuadModuleMor[restrict_automorphism(g, j) for g in stab] - stab = TorQuadModuleMor[hom(disc, disc, [disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] stab = sub(OqC2, elem_type(OqC2)[OqC2(compose(phi2, compose(g, inv(phi2))); check = false) for g in stab]) # If we have done good things, the action of fC2 on qC2 should centralize @@ -1189,10 +1171,10 @@ function _compute_double_stabilizer(SBinqB::TorQuadModuleMor, l::IntegerUnion, s OSB = orthogonal_group(SB) p = elementary_divisors(SB)[1] rB = _rho_functor(qB, p, l+1) - rBtoSB = hom(rB, SB, TorQuadModuleElem[SB(QQ(p^l)*lift(a)) for a in gens(rB)]) + rBtoSB = hom(rB, SB, elem_type(SB)[SB(QQ(p^l)*lift(a)) for a in gens(rB)]) HB, HBinSB = sub(SB, rBtoSB.(gens(rB))) OSBHB, _ = stabilizer(OSB, HBinSB) - OHB, OSBHBtoOHB = restrict_automorphism_group(OSBHB, HBinSB) + OHB, OSBHBtoOHB = restrict_automorphism_group(OSBHB, HBinSB; check = false) K, _ = kernel(OSBHBtoOHB) if spec OHBrB, _ = stabilizer(OHB, gram_matrix_quadratic(rB), _on_modular_matrix_quad) @@ -1203,56 +1185,6 @@ function _compute_double_stabilizer(SBinqB::TorQuadModuleMor, l::IntegerUnion, s return OSBrB end - -############################################################################### -# -# Isometries between finite bilinear modules -# -############################################################################### - -# Test whether an abelian group isomorphism respect the finite bilinear product -# of its domain (of type `TorQuadModule`). -function _is_isometry_bilinear(f::TorQuadModuleMor) - !is_bijective(f) && return false - for a in gens(domain(f)) - for b in gens(domain(f)) - if f(a)*f(b) != a*b - return false - end - end - end - return true -end - -# Test whether an abelian group isomorphism defines an anti isometry of the -# finite bilinear product defined on its domain (of type `TorQuadModule`) -function _is_anti_isometry_bilinear(f::TorQuadModuleMor) - !is_bijective(f) && return false - for a in gens(domain(f)) - for b in gens(domain(f)) - if f(a)*f(b) != -a*b - return false - end - end - end - return true -end - -# Compute an anti-isometry between the two finite bilinear modules r1 and r2. -function _anti_isometry_bilinear(r1::TorQuadModule, r2::TorQuadModule) - @hassert :ZZLatWithIsom is_semi_regular(r1) === is_semi_regular(r2) === true - hz = hom(r1, r2, zero_matrix(ZZ, ngens(r1), ngens(r2))) - r2m = rescale(r2, -1) - r2tor2m = hom(r2, r2m, identity_matrix(ZZ, ngens(r2))) - r1N, r1tor1N = normal_form(r1) - r2mN, r2mtor2mN = normal_form(r2m) - gram_matrix_bilinear(r1N) == gram_matrix_bilinear(r2mN) || return false, hz - T = hom(r1N, r2mN, identity_matrix(ZZ, ngens(r1N))) - T = compose(r1tor1N, compose(T, compose(inv(r2mtor2mN), inv(r2tor2m)))) - @hassert :ZZLatWithIsom _is_anti_isometry_bilinear(T) - return true, T -end - ############################################################################### # # Admissible gluings @@ -1267,49 +1199,105 @@ function _find_admissible_gluing(SAinqA::TorQuadModuleMor, SBinqB::TorQuadModuleMor, phi::TorQuadModuleMor, l::IntegerUnion, + p::IntegerUnion, spec::Bool) SA = domain(SAinqA) SB = domain(SBinqB) - p = elementary_divisors(SA)[1] qA = codomain(SAinqA) qB = codomain(SBinqB) - pqA, pqAtoqA = primary_part(qA, p) - pqB, pqBtoqB = primary_part(qB, p) - rA = _rho_functor(qA, p, l+1) - rB = _rho_functor(qB, p, l+1) - rAtoSA = hom(rA, SA, TorQuadModuleElem[SA(QQ(p^l)*lift(a)) for a in gens(rA)]) - HA, _ = sub(SA, rAtoSA.(gens(rA))) - rBtoSB = hom(rB, SB, TorQuadModuleElem[SB(QQ(p^l)*(lift(b))) for b in gens(rB)]) + rA = _rho_functor(qA, p, l+1; quad = spec) + rB = _rho_functor(qB, p, l+1; quad = spec) + @hassert :ZZLatWithIsom modulus_quadratic_form(rA) == modulus_quadratic_form(rB) + + rAtoSA = hom(rA, SA, elem_type(SA)[SA(QQ(p^l)*lift(a)) for a in gens(rA)]) + HA, HAinSA = sub(SA, rAtoSA.(gens(rA))) + rAtoHA = hom(rA, HA, elem_type(HA)[HAinSA\(rAtoSA(a)) for a in gens(rA)]) + + rBtoSB = hom(rB, SB, elem_type(SB)[SB(QQ(p^l)*(lift(b))) for b in gens(rB)]) HB, HBinSB = sub(SB, rBtoSB.(gens(rB))) - if spec - ok, phi_0 = is_anti_isometric_with_anti_isometry(rA, rB) - @hassert :ZZLatWithIsom 1 ok - @hassert :ZZLatWithIsom 1 is_anti_isometry(phi_0) - else - ok, phi_0 = _anti_isometry_bilinear(rA, rB) - @hassert :ZZLatWithIsom 1 ok - @hassert :ZZLatWithIsom 1 _is_anti_isometry_bilinear(phi_0) - end - phiHA, _ = sub(SB, TorQuadModuleElem[phi(SA(lift(a))) for a in gens(HA)]) + rBtoHB = hom(rB, HB, elem_type(HB)[HBinSB\(rBtoSB(b)) for b in gens(rB)]) + + # We construct an abstract isometry between the rho functors: since they have + # the same modulus quadratic, either we see them as finite bilinear modules + # or both as finite quadratic modules depending on the value of spec + # + # Our goal would be to massage phi such that it maps HA to HB, and the + # restriction to rA and rB agrees with phi_0 + ok, phi_0 = is_anti_isometric_with_anti_isometry(rA, rB) + @hassert :ZZLatWithIsom 1 ok + @hassert :ZZLatWithIsom 1 is_anti_isometry(phi_0) + + # We first massage phi such that it maps HA to HB + phiHA, _ = sub(SB, elem_type(SB)[phi(SA(lift(a))) for a in gens(HA)]) OSB = orthogonal_group(SB) - g = one(OSB) - for f in OSB - g = f - if cover(_on_subgroup_automorphic(phiHA, g)) == cover(HB) - break - end - end + G = GSetByElements(OSB, _on_subgroup_automorphic, TorQuadModule[HB]) + ok, g = representative_action(G, phiHA, HB) + @hassert :ZZLatWithIsom 1 ok phi_1 = compose(phi, hom(g)) + @hassert :ZZLatWithIsom 1 sub(SB, elem_type(SB)[phi_1(SA(lift(a))) for a in gens(HA)])[1] == HB + + # Now we look at the restriction to rA and rB, and we modify a bit phi_1 by an + # element of OSBHB, which in particular preserves rB, in order to make phig + # and phi_0 agree + phi_1_res = hom(HA, HB, elem_type(HB)[HBinSB\(phi_1(HAinSA(a))) for a in gens(HA)]) + phi_0_res = compose(inv(rAtoHA), compose(phi_0, rBtoHB)) OSBHB, _ = stabilizer(OSB, HBinSB) - g = one(OSBHB) - for f in OSBHB - g = f - phif = compose(phi_1, hom(f)) - psi = hom(rA, rB, TorQuadModuleElem[rBtoSB\(phif(rAtoSA(a))) for a in gens(rA)]) - if matrix(psi) == matrix(phi_0) - break - end - end + OHB, OSBHBtoOHB = restrict_automorphism_group(OSBHB, HBinSB; check = false) + g = OSBHBtoOHB\(OHB(compose(inv(phi_1_res), phi_0_res); check = false)) phig = compose(phi_1, hom(g)) + @hassert :ZZLatWithIsom 1 matrix(hom(rA, rB, elem_type(rB)[rBtoSB\(phig(rAtoSA(a))) for a in gens(rA)])) == matrix(phi_0) + @hassert :ZZLatWithIsom 1 sub(SB, elem_type(SB)[phig(SA(lift(a))) for a in gens(HA)])[1] == HB return phig end + +############################################################################### +# +# Extend stabilizers along equivariant primitive extensions +# +############################################################################### + +# Given an equivariant gluing phi between stable anti-isometric subgroups SA and +# SB of qA and qB respectively, we know that the image of the centralizer of the +# given equivariant primitive extensions in `O(D)` is given as a certain +# product. +# +# Here actA and actB are respectively the orthogonal representations of the +# stabilizers of SA and SB on SA and SB respectively. Then an isometry of OD +# centralizing the extensions and comes from a global isometry if and only if it +# can be written `(a, b)` where a and b are respectively centralizing elements +# of the isometries of the two sublattices which preserve the glue groups, i.e. +# elements of the domain of actA and actB respectively. We need to take care of +# the kernel of such maps though, and compose the generators of their images. +function _glue_stabilizers(phi::TorQuadModuleMor, + actA::GAPGroupHomomorphism, + actB::GAPGroupHomomorphism, + OqAinOD::GAPGroupHomomorphism, + OqBinOD::GAPGroupHomomorphism, + graph::TorQuadModuleMor) + OD = codomain(OqAinOD) + OqA = domain(OqAinOD) + imA, _ = image(actA) + kerA = elem_type(OD)[OqAinOD(x) for x in gens(kernel(actA)[1])] + push!(kerA, OqAinOD(one(OqA))) + + OqB = domain(OqBinOD) + imB, _ = image(actB) + kerB = elem_type(OD)[OqBinOD(x) for x in gens(kernel(actB)[1])] + push!(kerB, OqBinOD(one(OqB))) + + ext = domain(graph) + perp, j = orthogonal_submodule(codomain(graph), ext) + disc = torsion_quadratic_module(cover(perp), cover(ext); modulus = QQ(1), modulus_qf = QQ(2)) + + OSA = codomain(actA) + geneOSA = elem_type(OSA)[OSA(compose(phi, compose(hom(g1), inv(phi))); check = false) for g1 in unique(gens(imB))] + im2_phi, _ = sub(OSA, geneOSA) + im3, _, _ = intersect(imA, im2_phi) + stab = elem_type(OD)[OqAinOD(actA\x) * OqBinOD(actB\(imB(compose(inv(phi), compose(hom(x), phi)); check = false))) for x in gens(im3)] + union!(stab, kerA) + union!(stab, kerB) + stab = TorQuadModuleMor[restrict_automorphism(g, j; check = false) for g in stab] + stab = TorQuadModuleMor[hom(disc, disc, elem_type(disc)[disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] + + return disc, stab +end diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 3892cd6058f0..97f6f702b87f 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -1,4 +1,4 @@ -################################################################################## +################################################################################# # # This is an import to Oscar of the methods written following the paper [BH23] on # "Finite subgroups of automorphisms of K3 surfaces". @@ -101,7 +101,7 @@ julia> is_admissible_triple(genus(F), genus(C), genus(Lf), 5) true ``` """ -function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) +function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::IntegerUnion) zg = genus(integer_lattice(; gram = matrix(QQ, 0, 0, []))) AperpB = direct_sum(A, B) (signature_tuple(AperpB) == signature_tuple(C)) || (return false) @@ -122,7 +122,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) end # A+B and C must agree locally at every primes except p - for q in filter(qq -> qq != p, union!([2], primes(AperpB), primes(C))) + for q in filter(qq -> qq != p, union!(ZZRingElem[2], primes(AperpB), primes(C))) if local_symbol(AperpB, q) != local_symbol(C, q) return false end @@ -223,18 +223,14 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) return false end - qA, qB, qC = discriminant_group.([A, B, C]) + qC = discriminant_group(C) spec = (p == 2) && (_is_free(qA, p, l+1)) && (_is_free(qB, p, l+1)) && (_is_even(qC, p, l)) - rA = _rho_functor(qA, p, l+1) - rB = _rho_functor(qB, p, l+1) - if spec - return is_anti_isometric_with_anti_isometry(rA, rB)[1] - else - return _anti_isometry_bilinear(rA, rB)[1] - end + rA = _rho_functor(qA, p, l+1; quad = spec) + rB = _rho_functor(qB, p, l+1; quad = spec) + return is_anti_isometric_with_anti_isometry(rA, rB)[1] end -function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZZLat, ZZLatWithIsom} +function is_admissible_triple(A::T, B::T, C::T, p::IntegerUnion) where T <: Union{ZZLat, ZZLatWithIsom} return is_admissible_triple(genus(A), genus(B), genus(C), p) end @@ -273,7 +269,7 @@ julia> admissible_triples(g, 2) (Genus symbol: II_(0, 0), Genus symbol: II_(5, 0) 2^-1_3 3^1) ``` """ -function admissible_triples(G::ZZGenus, p::Integer; pA::Int = -1, pB::Int = -1) +function admissible_triples(G::ZZGenus, p::IntegerUnion; pA::Int = -1, pB::Int = -1) @req is_prime(p) "p must be a prime number" @req is_integral(G) "G must be a genus of integral lattices" rG = rank(G) @@ -319,7 +315,7 @@ function admissible_triples(G::ZZGenus, p::Integer; pA::Int = -1, pB::Int = -1) return L end -admissible_triples(L::T, p::Integer; pA::Int = -1, pB::Int = -1) where T <: Union{ZZLat, ZZLatWithIsom} = admissible_triples(genus(L), p; pA, pB) +admissible_triples(L::T, p::IntegerUnion; pA::Int = -1, pB::Int = -1) where T <: Union{ZZLat, ZZLatWithIsom} = admissible_triples(genus(L), p; pA, pB) ################################################################################## # @@ -330,12 +326,13 @@ admissible_triples(L::T, p::Integer; pA::Int = -1, pB::Int = -1) where T <: Unio # we compute ideals of E/K whose absolute norm is equal to d function _ideals_of_norm(E::Field, d::QQFieldElem) + OE = maximal_order(E) if denominator(d) == 1 return _ideals_of_norm(E, numerator(d)) elseif numerator(d) == 1 - return [inv(I) for I in _ideals_of_norm(E, denominator(d))] + return Hecke.fractional_ideal_type(OE)[inv(I) for I in _ideals_of_norm(E, denominator(d))] else - return [I*inv(J) for (I, J) in Hecke.cartesian_product_iterator([_ideals_of_norm(E, numerator(d)), _ideals_of_norm(E, denominator(d))]; inplace=false)] + return Hecke.fractional_ideal_type(OE)[I*inv(J) for (I, J) in Hecke.cartesian_product_iterator(Vector{Hecke.fractional_ideal_type(OE)}[_ideals_of_norm(E, numerator(d)), _ideals_of_norm(E, denominator(d))]; inplace=false)] end end @@ -498,19 +495,19 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) @vprintln :ZZLatWithIsom 1 "$H" M, fM = trace_lattice_with_isometry(H) det(M) == d || continue - M = integer_lattice_with_isometry(M, fM) - @hassert :ZZLatWithIsom 1 is_of_hermitian_type(M) - @hassert :ZZLatWithIsom 1 order_of_isometry(M) == n*m + MfM = integer_lattice_with_isometry(M, fM; check = false) + @hassert :ZZLatWithIsom 1 is_of_hermitian_type(MfM) + @hassert :ZZLatWithIsom 1 order_of_isometry(MfM) == n*m if is_even(M) != is_even(Lf) continue end - if !is_of_same_type(M^m, Lf) + if !is_of_same_type(MfM^m, Lf) continue end gr = genus_representatives(H) for HH in gr M, fM = trace_lattice_with_isometry(HH) - push!(reps, integer_lattice_with_isometry(M, fM)) + push!(reps, integer_lattice_with_isometry(M, fM; check = false)) end end return reps @@ -552,7 +549,7 @@ julia> is_of_same_type(Lf, reps[2]^2) true ``` """ -function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = -1, pB::Int = -1) +function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion; pA::Int = -1, pB::Int = -1) rank(Lf) == 0 && return ZZLatWithIsom[Lf] @req is_prime(p) "p must be a prime number" @@ -615,7 +612,7 @@ julia> splitting_of_prime_power(Lf, 3, 1) Integer lattice with isometry of finite order 3 ``` """ -function splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) +function splitting_of_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion, b::Int = 0) if rank(Lf) == 0 (b == 0) && return ZZLatWithIsom[Lf] return ZZLatWithIsom[] @@ -668,29 +665,32 @@ of $(L, f)$. Note that `e` can be 0, while `d` has to be positive. """ -function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) +function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, _p::IntegerUnion) rank(Lf) == 0 && return ZZLatWithIsom[Lf] + n = order_of_isometry(Lf) + + @req is_finite(n) "Isometry must be of finite order" + + p = typeof(n)(_p) @req is_prime(p) "p must be a prime number" - @req is_finite(order_of_isometry(Lf)) "Isometry must be of finite order" - n = order_of_isometry(Lf) pd = prime_divisors(n) @req 1 <= length(pd) <= 2 && p in pd "Order must be divisible by p and have at most 2 prime divisors" if length(pd) == 2 q = pd[1] == p ? pd[2] : pd[1] - d = valuation(n, p) - e = valuation(n, q) + d = valuation(n, p)::Int # Weird ? Valuation is type stable and returns Int but it bugs here + e = valuation(n, q)::Int else - q = 1 - d = valuation(n, p) - e = 0 + q = one(n) + d = valuation(n, p)::Int + e = zero(n) end phi = minimal_polynomial(Lf) - chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) + chi = prod(cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e; init = zero(phi)) @req is_divisible_by(chi, phi) "Minimal polynomial is not of the correct form" @@ -763,7 +763,7 @@ julia> all(LL -> is_of_same_type(Lf, LL^2), reps) true ``` """ -function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) +function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion, b::Int = 1) if rank(Lf) == 0 b == 0 && return ZZLatWithIsom[Lf] return ZZLatWithIsom[] @@ -788,7 +788,7 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) x = gen(parent(minimal_polynomial(Lf))) B0 = kernel_lattice(Lf, x^(divexact(n, p)) - 1) - A0 = kernel_lattice(Lf, prod([cyclotomic_polynomial(p^d*q^i) for i in 0:e])) + A0 = kernel_lattice(Lf, prod(cyclotomic_polynomial(p^d*q^i) for i in 0:e)) A = splitting_of_pure_mixed_prime_power(A0, p) isempty(A) && return reps B = splitting_of_mixed_prime_power(B0, p, 0) @@ -817,13 +817,15 @@ Note that currently we support only orders which admit at most 2 prime divisors. function enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::IntegerUnion) @req is_finite(order) && order >= 1 "order must be positive and finite" if order == 1 - return representatives_of_hermitian_type(integer_lattice_with_isometry(L)) + reps = representatives_of_hermitian_type(integer_lattice_with_isometry(L)) + return reps end pd = prime_divisors(order) @req length(pd) in [1,2] "order must have at most two prime divisors" if length(pd) == 1 v = valuation(order, pd[1]) - return _enumerate_prime_power(L, pd[1], v) + reps = _enumerate_prime_power(L, pd[1], v) + return reps end p, q = sort!(pd) vp = valuation(order, p) diff --git a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index 6050908ddf19..950122405285 100644 --- a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl +++ b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl @@ -214,7 +214,7 @@ function _get_product_quotient(E::Hecke.NfRel, Fac::Vector{Tuple{NfOrdIdl, Int}} if length(Fac) == 0 A = abelian_group() - function dlog_0(x::Vector); return id(A); end; + function dlog_0(x::Vector{<:Hecke.NfRelElem}); return id(A); end; function exp_0(x::GrpAbFinGenElem); return one(E); end; return A, dlog_0, exp_0 end @@ -230,25 +230,25 @@ function _get_product_quotient(E::Hecke.NfRel, Fac::Vector{Tuple{NfOrdIdl, Int}} G, proj, inj = biproduct(groups...) - function dlog(x::Vector) + function dlog(x::Vector{<:Hecke.NfRelElem}) if length(x) == 1 - return sum([inj[i](dlogs[i](x[1])) for i in 1:length(Fac)]) + return sum(inj[i](dlogs[i](x[1])) for i in 1:length(Fac)) else @hassert :ZZLatWithIsom 1 length(x) == length(Fac) - return sum([inj[i](dlogs[i](x[i])) for i in 1:length(Fac)]) + return sum(inj[i](dlogs[i](x[i])) for i in 1:length(Fac)) end end function exp(x::GrpAbFinGenElem) - v = Hecke.NfRelElem[exps[i](proj[i](x)) for i in 1:length(Fac)] + v = elem_type(E)[exps[i](proj[i](x)) for i in 1:length(Fac)] @hassert :ZZLatWithIsom 1 dlog(v) == x return v end - for i in 1:10 - a = rand(G) - @hassert :ZZLatWithIsom 1 dlog(exp(a)) == a - end + @v_do :ZZLatWithIsom 1 for i in 1:10 + a = rand(G) + @hassert :ZZLatWithIsom 1 dlog(exp(a)) == a + end return G, dlog, exp end @@ -314,14 +314,15 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) if rank(Lf) != degree(Lf) - Lf2 = integer_lattice_with_isometry(integer_lattice(gram = gram_matrix(Lf)), isometry(Lf); ambient_representation = false) + Lf2 = integer_lattice_with_isometry(integer_lattice(gram = gram_matrix(Lf)), isometry(Lf); ambient_representation = false, check = false) qL2, fqL2 = discriminant_group(Lf2) OqL2 = orthogonal_group(qL2) ok, phi12 = is_isometric_with_isometry(qL, qL2) @hassert :ZZLatWithIsom 1 ok - ok, g0 = is_conjugate_with_data(OqL, fqL, OqL(compose(phi12, compose(hom(fqL2), inv(phi12))))) + ok, g0 = is_conjugate_with_data(OqL, OqL(compose(phi12, compose(hom(fqL2), inv(phi12))); check = false), fqL) @hassert :ZZLatWithIsom 1 ok phi12 = compose(hom(OqL(g0)), phi12) + #@hassert :ZZLatWithIsom 1 matrix(compose(hom(fqL), phi12)) == matrix(compose(phi12, hom(fqL2))) @hassert :ZZLatWithIsom 1 is_isometry(phi12) else Lf2 = Lf @@ -333,8 +334,11 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # fqL, G is the group where we want to compute the image of O(L, f). This # group G corresponds to U(D_L) in the notation of BH23. G2, _ = centralizer(OqL2, fqL2) - G, _ = sub(OqL, elem_type(OqL)[OqL(compose(phi12, compose(hom(g), inv(phi12)))) for g in gens(G2)]) - GtoG2 = hom(G, G2, gens(G), gens(G2)) + gensG2 = gens(G2) + gensG = elem_type(OqL)[OqL(compose(phi12, compose(hom(g), inv(phi12))); check = false) for g in gensG2] + G, GinOqL = sub(OqL, gensG) + @hassert :ZZLatWithIsom 1 fqL in G + GtoG2 = hom(G, G2, gensG, gensG2; check = false) # This is the associated hermitian O_E-lattice to (L, f): we want to make qL # (aka D_L) correspond to the quotient D^{-1}H^#/H by the trace construction, @@ -407,7 +411,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) RmodF, Flog, _ = _get_product_quotient(E, Fdata) A = elem_type(RmodF)[Flog(Fsharpexp(g)) for g in gens(RmodFsharp)] - f = hom(RmodFsharp, RmodF, A) + f = hom(RmodFsharp, RmodF, A; check = false) FmodFsharp, j = kernel(f) # Now according to Theorem 6.15 of BH23, it remains to quotient out the image @@ -418,10 +422,10 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) OK = base_ring(OE) UOK, mUOK = unit_group(OK) - fU = hom(UOEabs, UOK, elem_type(UOK)[mUOK\norm(OE(mUOK(m))) for m in gens(UOK)]) + fU = hom(UOEabs, UOK, elem_type(UOK)[mUOK\norm(OE(mUOK(m))) for m in gens(UOK)]; check = false) KU, jU = kernel(fU) - gene_norm_one = Hecke.NfRelElem[EabstoE(Eabs(mUOEabs(jU(k)))) for k in gens(KU)] + gene_norm_one = elem_type(E)[EabstoE(Eabs(mUOEabs(jU(k)))) for k in gens(KU)] # Now according to Theorem 6.15 of BH23, it remains to quotient out FOEmodFsharp, m = sub(RmodFsharp, elem_type(RmodFsharp)[Fsharplog(typeof(x)[x for i in 1:length(S)]) for x in gene_norm_one]) @@ -445,7 +449,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # consider up to now, and then map the corresponding determinant adeles inside # Q. Since our matrices were approximate lifts of the generators of G, we can # create the map we wanted from those data. - for g in gens(G2) + for g in gensG2 ds = elem_type(E)[] for p in S if !_is_special(H, p) @@ -464,10 +468,11 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) end GSQ, SQtoGSQ, _ = Oscar._isomorphic_gap_group(SQ) - f2 = hom(G2, GSQ, gens(G2), SQtoGSQ.(imgs); check = false) + f2 = hom(G2, GSQ, gensG2, SQtoGSQ.(imgs); check = false) f = compose(GtoG2, f2) - return f + @hassert :ZZLatWithIsom 1 isone(f(fqL)) + return f, GinOqL # Needs the second map to map the kernel of f into OqL end # We check whether for the prime ideal p E_O(L_p) != F(L_p). @@ -506,7 +511,7 @@ function _is_special(L::HermLat, p::NfOrdIdl) u = elem_in_nf(uniformizer(P)) s = involution(L) su = s(u) - H = block_diagonal_matrix([matrix(E, 2, 2, [0 u^(S[i]); su^(S[i]) 0]) for i in 1:length(S)]) + H = block_diagonal_matrix(dense_matrix_type(E)[matrix(E, 2, 2, [0 u^(S[i]); su^(S[i]) 0]) for i in 1:length(S)]) return is_locally_isometric(L, hermitian_lattice(E; gram = H), p) end @@ -677,7 +682,7 @@ function _approximate_isometry(H::HermLat, H2::HermLat, g::AutomorphismGroupElem Bps = _local_basis_modular_submodules(H2, minimum(P), a, res) Bp = reduce(vcat, Bps) Gp = Bp*gram_matrix(ambient_space(H))*map_entries(involution(E), transpose(Bp)) - Fp = block_diagonal_matrix([_transfer_discriminant_isometry(res, g, Bps[i], P, BHp_inv) for i in 1:length(Bps)]) + Fp = block_diagonal_matrix(typeof(Gp)[_transfer_discriminant_isometry(res, g, Bps[i], P, BHp_inv) for i in 1:length(Bps)]) # This is the local defect. By default, it should have scale P-valuations -a # and norm P-valuation e-1-a Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) @@ -717,7 +722,7 @@ function _local_basis_modular_submodules(H::HermLat, p::NfOrdIdl, a::Int, res::A L2 = restrict_scalars(H2, res) L2 = intersect(L, L2) B2 = basis_matrix(L2) - gene = [res(vec(collect(B2[i, :]))) for i in 1:nrows(B2)] + gene = Vector{elem_type(base_field(H))}[res(vec(collect(B2[i, :]))) for i in 1:nrows(B2)] H2 = lattice(ambient_space(H), gene) b = local_basis_matrix(H2, p; type = :submodule) end @@ -736,7 +741,7 @@ end # exactly what we look for (a unit at P with trace 1); # - p is ramified, and then we cook up a good element. The actual code from # that part is taken from the Sage implementation of Simon Brandhorst -function _find_rho(P::Hecke.NfRelOrdIdl, e) +function _find_rho(P::Hecke.NfRelOrdIdl, e::Int) OE = order(P) E = nf(OE) lp = prime_decomposition(OE, minimum(P)) diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index bfce40849487..ea1a0e2ee3a9 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -614,7 +614,7 @@ function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true @hassert :ZZLatWithIsom 1 basis_matrix(L)*f_ambient == f*basis_matrix(L) end - return ZZLatWithIsom(Vf, L, f, n)::ZZLatWithIsom + return ZZLatWithIsom(Vf, L, f, n) end @doc raw""" @@ -646,7 +646,7 @@ function integer_lattice_with_isometry(L::ZZLat; neg::Bool = false) if neg f = -f end - return integer_lattice_with_isometry(L, f; check = false, ambient_representation = true)::ZZLatWithIsom + return integer_lattice_with_isometry(L, f; check = false, ambient_representation = true) end @doc raw""" @@ -684,7 +684,7 @@ Integer lattice of rank 2 and degree 2 [0 -1] ``` """ -lattice(Vf::QuadSpaceWithIsom) = ZZLatWithIsom(Vf, lattice(space(Vf)), isometry(Vf), order_of_isometry(Vf))::ZZLatWithIsom +lattice(Vf::QuadSpaceWithIsom) = ZZLatWithIsom(Vf, lattice(space(Vf)), isometry(Vf), order_of_isometry(Vf)) @doc raw""" lattice(Vf::QuadSpaceWithIsom, B::MatElem{<:RationalUnion}; @@ -1437,7 +1437,7 @@ function discriminant_group(Lf::ZZLatWithIsom) q = discriminant_group(L) f = hom(q, q, elem_type(q)[q(lift(t)*f) for t in gens(q)]) f = gens(Oscar._orthogonal_group(q, ZZMatrix[matrix(f)]; check = false))[1] - return (q, f)::Tuple{TorQuadModule, AutomorphismGroupElem{TorQuadModule}} + return (q, f) end @doc raw""" @@ -1471,29 +1471,43 @@ julia> order(G) @req is_integral(Lf) "Underlying lattice must be integral" n = order_of_isometry(Lf) L = lattice(Lf) - f = ambient_isometry(Lf) if (n in [1, -1]) || (isometry(Lf) == -identity_matrix(QQ, rank(L))) + # Trivial cases: the lattice has rank 0, or is endowed with +- identity return image_in_Oq(L) - elseif is_definite(L) + elseif rank(L) == euler_phi(n) + # this should also cover the case of rank 1 + # The image of -id and multiplication by primitive root of unity + qL, fqL = discriminant_group(Lf) + OqL = orthogonal_group(qL) + UL = ZZMatrix[-matrix(one(OqL)), matrix(fqL)] + UL = elem_type(OqL)[OqL(m; check = false) for m in UL] + return sub(OqL, unique!(UL)) + elseif is_definite(L) || rank(L) == 2 + # We can compute the orthogonal groups and centralizer with GAP OL = orthogonal_group(L) - f = OL(f) - UL = QQMatrix[matrix(OL(s)) for s in gens(centralizer(OL, f)[1])] + f = isometry(Lf) + V = ambient_space(L) + B = basis_matrix(L) + B2 = orthogonal_complement(V, B) + C = vcat(B, B2) + f = block_diagonal_matrix(QQMatrix[f, identity_matrix(QQ, nrows(B2))]) + f = inv(C)*f*C + f = OL(f; check = false) + UL = QQMatrix[matrix(s) for s in gens(centralizer(OL, f)[1])] qL = discriminant_group(L) UL = ZZMatrix[matrix(hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)])) for g in UL] unique!(UL) OqL = orthogonal_group(qL) UL = elem_type(OqL)[OqL(m; check = false) for m in UL] return sub(OqL, UL) - elseif rank(L) == euler_phi(n) - qL = discriminant_group(L) - UL = ZZMatrix[matrix(hom(qL, qL, elem_type(qL)[qL(-lift(t)) for t in gens(qL)]))] - OqL = orthogonal_group(qL) - UL = elem_type(OqL)[OqL(m; check = false) for m in UL] - return sub(OqL, UL) else @req is_of_hermitian_type(Lf) "Not yet implemented for indefinite lattices with isometry which are not of hermitian type" - dets = Oscar._local_determinants_morphism(Lf) - return kernel(dets) + # indefinite of rank bigger or equal to 3 + # We use Hermitian Miranda-Morrison (see [BH23, Part 6]) + dets, j = Oscar._local_determinants_morphism(Lf) + _, jj = kernel(dets) + jj = compose(jj, j) + return image(jj) end end @@ -1812,11 +1826,18 @@ true function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) F = invariant_lattice(L, G; ambient_representation) C = orthogonal_submodule(L, F) + if !ambient_representation + V = ambient_space(L) + B = basis_matrix(L) + B2 = orthogonal_complement(V, B) + B3 = vcat(B, B2) + end gene = QQMatrix[] for g in gens(G) if !ambient_representation - m = solve(basis_matrix(L), matrix(g)*basis_matrix(L)) - m = solve_left(basis_matrix(C), basis_matrix(C)*m) + g_ambient = block_diagonal_matrix(QQMatrix[matrix(g), identity_matrix(QQ, nrows(B2))]) + g_ambient = inv(B3)*g_ambient*B3 + m = solve_left(basis_matrix(C), basis_matrix(C)*g_ambient) push!(gene, m) else push!(gene, matrix(g)) @@ -1864,11 +1885,18 @@ with gram matrix function invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) F = invariant_lattice(L, G; ambient_representation) C = orthogonal_submodule(L, F) + if !ambient_representation + V = ambient_space(L) + B = basis_matrix(L) + B2 = orthogonal_complement(V, B) + B3 = vcat(B, B2) + end gene = QQMatrix[] for g in gens(G) if !ambient_representation - m = solve(basis_matrix(L), matrix(g)*basis_matrix(L)) - m = solve_left(basis_matrix(C), basis_matrix(C)*m) + g_ambient = block_diagonal_matrix(QQMatrix[matrix(g), identity_matrix(QQ, nrows(B2))]) + g_ambient = inv(B3)*g_ambient*B3 + m = solve_left(basis_matrix(C), basis_matrix(C)*g_ambient) push!(gene, m) else push!(gene, matrix(g)) diff --git a/src/GAP/gap_to_oscar.jl b/src/GAP/gap_to_oscar.jl index 8837b3326eac..cc293a1502f5 100644 --- a/src/GAP/gap_to_oscar.jl +++ b/src/GAP/gap_to_oscar.jl @@ -45,13 +45,13 @@ function (F::FinField)(x::GAP.FFE) # value fits into an Int; telling Julia about this via a type assertion # results in slightly better code val = GAPWrap.INT_FFE_DEFAULT(x) - return F(val) + return F(val)::elem_type(F) end # Use `iso_oscar_gap` not `iso_gap_oscar` in order to make sure # that the result is in `F`, and in order to cache the isomorphism in `F`. iso = iso_oscar_gap(F) - return preimage(iso, x) + return preimage(iso, x)::elem_type(F) end # test code for producing a gap finite field element not stored as FFE: `GAP.Globals.Z(65537)` diff --git a/src/Groups/abelian_aut.jl b/src/Groups/abelian_aut.jl index 7e1ac3fc9a4e..755763b5e566 100644 --- a/src/Groups/abelian_aut.jl +++ b/src/Groups/abelian_aut.jl @@ -42,7 +42,7 @@ Base.:^(x::AbTorElem,f::AutGrpAbTorElem) = apply_automorphism(f, x, true) # the _as_subgroup function needs a redefinition # to pass on the to_gap and to_oscar attributes to the subgroup function _as_subgroup(aut::AutomorphismGroup{S}, subgrp::GapObj) where S <: Union{TorQuadModule,GrpAbFinGen} - function img(x::S) + function img(x::AutomorphismGroupElem{S}) return group_element(aut, x.X) end to_gap = get_attribute(aut, :to_gap) diff --git a/src/Groups/gsets.jl b/src/Groups/gsets.jl index 03e89f6251c4..a17c1b9f745d 100644 --- a/src/Groups/gsets.jl +++ b/src/Groups/gsets.jl @@ -947,14 +947,14 @@ ZZRingElem[12, 12, 16] ``` """ function orbit_representatives_and_stabilizers(G::MatrixGroup{E}, k::Int) where E <: FinFieldElem - F = G.ring - n = G.deg + F = base_ring(G) + n = degree(G) q = GAP.Obj(order(F)) V = VectorSpace(F, n) orbs = GAP.Globals.Orbits(G.X, GAP.Globals.Subspaces(GAP.Globals.GF(q)^n, k)) orbreps = [GAP.Globals.BasisVectors(GAP.Globals.Basis(orb[1])) for orb in orbs] - stabs = [Oscar._as_subgroup_bare(G, GAP.Globals.Stabilizer(G.X, v, GAP.Globals.OnSubspacesByCanonicalBasis)) for v in orbreps] - orbreps = [[[F(x) for x in v] for v in bas] for bas in orbreps] - orbreps = [sub(V, [V(v) for v in bas])[1] for bas in orbreps] - return [(orbreps[i], stabs[i]) for i in 1:length(stabs)] + stabs = [Oscar._as_subgroup_bare(G, GAP.Globals.Stabilizer(G.X, v, GAP.Globals.OnSubspacesByCanonicalBasis)) for v in orbreps]::Vector{typeof(G)} + orbreps1 = [[[F(x) for x in v] for v in bas] for bas in orbreps]::Vector{Vector{Vector{elem_type(F)}}} + orbreps2 = [sub(V, [V(v) for v in bas])[1] for bas in orbreps1] + return [(orbreps2[i], stabs[i]) for i in 1:length(stabs)] end diff --git a/src/Groups/matrices/MatGrp.jl b/src/Groups/matrices/MatGrp.jl index 03ea64d12404..67ddcc34fce3 100644 --- a/src/Groups/matrices/MatGrp.jl +++ b/src/Groups/matrices/MatGrp.jl @@ -467,7 +467,7 @@ transpose(x::MatrixGroupElem) = MatrixGroupElem(x.parent, transpose(x.elm)) Return the base ring of the matrix group `G`. """ -base_ring(G::MatrixGroup) = G.ring +base_ring(G::MatrixGroup{RE}) where RE <: RingElem = G.ring::parent_type(RE) """ degree(G::MatrixGroup)