From 937e80634f800f5bff982c16a579a8affe518932 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 12 Aug 2022 12:10:57 +0200 Subject: [PATCH 01/76] first step --- .../LatticesWithIsometry.jl | 252 ++++++++++++++ .../LatticesWithIsometry/TraceEquivalence.jl | 314 ++++++++++++++++++ .../LatticesWithIsometry/Types.jl | 43 +++ src/Oscar.jl | 3 + 4 files changed, 612 insertions(+) create mode 100644 src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl create mode 100644 src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl create mode 100644 src/NumberTheory/LatticesWithIsometry/Types.jl diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl new file mode 100644 index 000000000000..c1f0c761c22b --- /dev/null +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -0,0 +1,252 @@ +export admissible_triples, is_admissble_triple + +################################################################################## +# +# This is an import to Oscar of the methods written following the paper [BH22] on +# "Finite subgroups of automorphisms of K3 surfaces". +# +################################################################################## + +# The tuples in output are pairs of positive integers! +function _tuples_divisors(d::fmpz) + div = divisors(d) + return [(dd,abs(divexact(d,dd))) for dd in div] +end + +# This is line 8 of Algorithm 1, they correspond to the possible +# discriminant for the genera A and B to glue to fit in C. d is +# the determinant of C, m the maximal p-valuation of the gcd of +# d1 and dp. +function _find_D(d::fmpz, m::Integer, p::Integer) + @req is_prime(p) "p must be a prime number" + @req d != 0 "The discriminant of a non-degenerate form is non-zero" + + # If m == 0, there are no conditions on the gcd of d1 and dp + if m == 0 + return _tuples_divisors(d) + end + + D = Tuple{Int64,Int64}[] + # We try all the values of g possible, from 1 to p^m + for j=0:m + g = p^j + dj = _tuples_divisors(d*g^2) + for (d1,dp) in dj + if mod(d1,g) == mod(dp,g) == 0 + push!(D,(d1,dp)) + end + end + end + return D +end + +# This is line 10 of Algorithm 1. We need the condition on the even-ness of +# C since subgenera of an even genus are even too. r is the rank of +# the subgenus, d its determinant, s and l the scale and level of C +function _find_L(r::Integer, d, s::fmpz, l::fmpz, p::Integer, even = true) + L = ZGenus[] + for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] + gen = genera((s1,s2), d, even=even) + filter!(G -> divides(scale(G), s)[1], gen) + filter!(G -> divides(p*l, level(G))[1], gen) + append!(L, gen) + end + return L +end + +primes(G::ZGenus) = prime.(local_symbols(G)) + +@doc Markdown.doc""" + is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) -> Bool + +Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such +that the rank of `B` is divisible by $p-1$ and the level of `C` is a power +of `p`, return whether `(A,B,C)` is `p`-admissible in the sense of +Definition 4.13. [BH22] +""" +function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) + zg = genus(Zlattice(gram = matrix(QQ, 0, 0, []))) + if ((A == zg) && (B == C)) || ((B == zg) && (A == C)) + # C can be always glued with the empty genus to obtain C + return true + elseif (A == zg) || (B == zg) + # If A or B is empty but the other is not C, then there is no glueing + return false + end + + @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" + @req prime_divisors(level(C)) == [p] || level(C) == one(fmpq) "The level of C must be a power of p" + AperpB = orthogonal_sum(A,B) + # If A and B glue to C, the sum of their ranks must match the one of C + if rank(AperpB) != rank(C) + return false + end + + lA = ngens(discriminant_group(A)) + lB = ngens(discriminant_group(B)) + if all(g -> abs(det(AperpB)) != p^(2*g)*abs(det(C)), 0:min(lA, lB, divexact(rank(B), p-1))) + return false + 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))) + if local_symbol(AperpB,q) != local_symbol(C,q) + return false + end + end + + g = divexact(valuation(div(det(AperpB), det(C)), p), 2) + # If the determinants agree, A+B = C and, equivalently, they agree locally at p too. + # Otherwise, their localisations at p must be rationally equal. + if g == 0 + return local_symbol(AperpB, p) == local_symbol(C, p) + elseif excess(local_symbol(AperpB, p)) != excess(local_symbol(C, p)) + return false + end + + if !(divides(scale(AperpB), scale(C))[1] && divides(p*level(C), level(AperpB))[1]) + return false + end + + # At this point, if C is unimodular at p, the glueing condition is equivalent to have + # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B + if !divides(det(C), p)[1] + qA = gram_matrix_quadratic(normal_form(primary_part(discriminant_group(A), p)[1])[1]) + qB = gram_matrix_quadratic(normal_form(rescale(primary_part(discriminant_group(B), p)[1], -1))[1]) + return qA == qB + end + + l = valuation(level(C), p) + Ap = symbol(local_symbol(A, p)) + Bp = symbol(local_symbol(B,p)) + a_max = sum(Int64[s[2] for s in Ap if s[1] == l+1]) + b_max = sum(Int64[s[2] for s in Bp if s[1] == l+1]) + # For the glueing, rho_{l+1}(A) and rho_{l+1}(B) are anti-isometric, so they must have the + # same order + if a_max != b_max + return false + end + + # Since p^l*A^\vee/A is in the glue, its order is less than the order of the glue + if g < a_max + return false + end + + a1 = sum(Int64[s[2] for s in Ap if s[1] == 1]) + a2 = sum(Int64[s[2] for s in Ap if s[1] >= 2]) + b1 = sum(Int64[s[2] for s in Bp if s[1] == 1]) + b2 = sum(Int64[s[2] for s in Bp if s[1] >= 2]) + + if l != 0 + ker_min = max(g-a1, g-b1, a_max) + else + ker_min = max(g-a1, g-b1) + end + ker_max = min(a2+div(a1,2), b2+div(b1,2), g) + + if ker_max < ker_min + return false + end + + ABp = symbol(local_symbol(AperpB, p)) + Cp = symbol(local_symbol(C, p)) + + if a_max == g + if length(Ap) > 1 + Ar = ZpGenus(p, Ap[1:end-1]) + else + Ar = genus(matrix(ZZ,0,0,[]), p) + end + + if length(Bp) > 1 + Br = ZpGenus(p, Bp[1:end-1]) + else + Br = genus(matrix(ZZ, 0, 0, []), p) + end + + ABr = orthogonal_sum(Ar, Br) + + for i = 1:l + s1 = symbol(ABr)[i] + s2 = symbol(Cp)[i] + if s1[2] != s2[2] + return false + end + if p == 2 && s1[4] > s2[4] + return false + elseif p != 2 && s1[3] != s2[3] + return false + end + end + end + + s3 = symbol(local_symbol(C, 2))[end] + if p == 2 + s1 = symbol(Ap)[s3[1]+1] + s2 = symbol(Bp)[s3[1]+1] + if s1[3] != s2[3] + return false + end + end + + for s in Cp + s[1] += 2 + end + Cp = ZpGenus(p, Cp) + + if !represented(local_symbol(AperpB, p), Cp) + return false + end + if !represents(C, AperpB) + return false + end + + return true +end + +function is_admissible_triple(A::Union{ZLat, ZGenus}, B::Union{ZLat, ZGenus}, C::Union{ZLat, ZGenus}, p::Integer) + L = ZGenus[typeof(D) <: ZLat ? genus(D) : D for D = (A, B, C)] + return is_admissible_triple(L[1], L[2], L[3], p) +end + +@doc Markdown.doc""" + admissible_triples(C::ZGenus, p::Integer) -> Vector{Tuple{ZGenus, ZGenus}} + +Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of +$\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and +`B` is of rank divisible by $p-1$. See Algorithm 1 [BH22]. +""" +function admissible_triples(G::ZGenus, p::Int64) + @req is_prime(p) "p must be a prime number" + n = rank(G) + d = det(G) + even = iseven(G) + L = Tuple{ZGenus, ZGenus}[] + for ep in 0:div(n, p-1) + rp = (p-1)*ep + r1 = n - rp + m = min(ep, r1) + D = _find_D(d, m, p) + for (d1, dp) in D + L1 = _find_L(r1, d1, scale(G), level(G), p, even) + Lp = _find_L(rp, dp, scale(G), level(G), p, even) + for (A, B) in [(A, B) for A in L1 for B in Lp] + if is_admissible_triple(A, B, G, p) + push!(L, (A, B)) + end + end + end + end + return L +end + +admissible_triples(L::ZLat, p::Integer) = admissible_triple(genus(L), p) + + +function primitive_extensions(A::ZLat, B::ZLat, C::ZLat, p::Integer) + @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" + L = [] + g = valuation(divexact(det(A)*det(B), det(C)), p) + + +end diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl new file mode 100644 index 000000000000..d9bd35a917a3 --- /dev/null +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -0,0 +1,314 @@ +export trace_lattice, inverse_trace_lattice + +function _cyclotomic_tower(n::Integer) + K,a = CyclotomicRealSubfield(n, cached=false) + Kt, t = PolynomialRing(K, "t", cached=false) + E,b = number_field(t^2-a*t+1, "b", cached=false) + set_attribute!(E, :cyclo, ZZ(n)) + return E, b +end + +function _is_cyclotomic_field(E::Oscar.Field) + f = get_attribute(E, :cyclo) + if f === nothing + return (false, fmpz(1)) + else + return (true, f) + end +end + +function trace_lattice(L::Hecke.AbsLat, order::Integer = 2) + #@req order > 0 "The order must be positive" + n = degree(L) + E = base_field(L) + G = gram_matrix(rational_span(L)) + + if E == QQ || E == ZZ + if order == 1 + f = identity_matrix(E, n) + elseif order == 2 + f = -identity_matrix(E, n) + else + error("For ZLat the order must be 1 or 2") + end + return L,f + end + + bool, m = _is_cyclotomic_field(E) + + #@req bool "Base field must be cyclotomic" + + Eabs, EabstoE = Hecke.absolute_simple_field(E) + EtoEabs = pseudo_inv(EabstoE) + d = degree(Eabs) + Gabs = map_entries(EtoEabs, G) + z = gen(Eabs) + chi = minpoly(z) + Mchi = companion_matrix(chi) + f = block_diagonal_matrix([Mchi for i=1:n]) + + coeffs = fmpq[] + for i=1:n + for iz=1:d + for j=1:n + for jz=1:d + g = z^(iz-jz)*Gabs[i,j] + push!(coeffs, trace(g)) + end + end + end + end + gram = matrix(QQ, d*n, d*n, coeffs) + @assert f*gram*transpose(f) == gram + return Zlattice(gram = 1//d*gram), f +end + +function _Sq(q::Integer) + @assert q > 1 + Sq = [k for k=1:q if gcd(k,q) == 1] + @assert length(Sq) == euler_phi(q) + return Sq +end + +function _order_max(rank::Integer) + k = iseven(rank) ? rank : rank+1 + order_max = maximum(euler_phi_inv(k)) + order_max = maximum(filter(n -> divides(ZZ(k), euler_phi(n))[1], 1:order_max)) + return order_max +end + +function inverse_trace_lattice(L::ZLat, f::fmpq_mat) + n = findfirst(n -> is_one(f^n), 1:_order_max(rank(L))) + #@req ((n !== nothing) || (divides(rank(L), euler_phi(n))[1])) "(L, f) is not the trace lattice of a hermitian lattice over a cyclotomic field" + + E,b = _cyclotomic_tower(n) + + m = divexact(rank(L), euler_phi(n)) + # We choose as basis for the hermitian lattice Lh the identity matrix + gram = matrix(zeros(E, m,m)) + G = gram_matrix(L) + Sq = _Sq(n) + + for i=1:m + for j=1:m + alpha = sum([G[1+(i-1)*euler_phi(n),:]*transpose(f^k)[:,1+(j-1)*euler_phi(n)] for k in Sq])[1] + gram[i,j] = alpha + end + end + + s = involution(E) + @assert transpose(map_entries(s, gram)) == gram + + Lh = hermitian_lattice(E, gram = gram) + return Lh +end + +#function _induced_image_in_Oq(qL::Hecke.TorQuadMod, f::fmpq_mat) +# imgs = elem_type(qL)[] +# d = denominator(f) +# m = order(qL) +# dinv = invmod(d,m) +# for g in gens(qL) +# x = lift(g)*f +# x = QQ(dinv*d)*x +# push!(imgs, qL(x)) +# end +# return hom(qL, qL, imgs) +#end + +function _eichler_isometry(G, u, v, y, mu) + n = ncols(G) + d = absolute_degree(base_ring(G)) + s = involution(base_ring(G)) + @assert u*G*transpose(map_entries(s,u)) == 0 + @assert u*G*transpose(map_entries(s,y)) == 0 + @assert v*G*transpose(map_entries(s,y)) == 0 + V = VectorSpace(base_ring(G) ,n) + uv = u*G*transpose(map_entries(s,v))[1] + @assert mu*uv+s(mu*uv) == (-y*G*transpose(map_entries(s,y)))[1] + imgs = [e + ((e*G*transpose(map_entries(s,u)))[1]//s(uv))*y + ((mu*(e*G*transpose(map_entries(s,u)))[1])//(-s(uv)) - (e*G*transpose(map_entries(s,y)))[1]//uv)*u for e in gens(V)] + f = matrix(reduce(vcat,imgs)) + @assert G == f*G*transpose(map_entries(s,f)) + return _restrict_scalars(f), one(base_ring(G)), f +end + +function _restrict_scalars(M) + n = ncols(M) + d = absolute_degree(base_ring(M)) + N = matrix(zeros(QQ, d*n, d*n)) + bas = absolute_basis(base_ring(M)) + for i=0:n-1 + for j=0:n-1 + F[d*i+1:(i+1)*d+1, d*j+1:(j+1)*d+1] = matrix(absolute_coordinates.(M[i,j].*(bas))) + end + end + return F +end + +function _skew_element_cyclo(E) + K = base_field(E) + b = gen(E) + s = involution(E) + M = matrix(K, 2,1,[2, b+s(b)]) + r,k = left_kernel(M) + @assert r == 1 + c = E(k[1]+b*k[2]) + @assert iszero(c+s(c)) + return c +end + +function _symmetry(G, v, sigma) + s = involution(parent(sigma)) + @assert v*G*transpose(map_entries(s,v)) == sigma+s(sigma) + n = ncols(G) + V = VectorSpace(base_ring(G), n) + imgs = [e - (e*G*transpose(map_entries(s,v)))*inv(sigma)*v for e in gens(V)] + g = matrix(reduce(vcat, imgs)) + return _restrict_scalars(g), det(g), g +end + +function _my_rand_QQ(k::Oscar.IntegerUnion = 64) + p = ZZ(rand(-k:k)) + q = ZZ(rand(-k:k)) + while q == 0 + q = ZZ(rand(-k:k)) + end + return p//q +end + +function _my_rand(K::AnticNumberField, k::Oscar.IntegerUnion = 64) + d = degree(K) + a = gen(K) + pows = powers(a, d-1) + z = K(0) + for zz in pows + z += zz*my_rand_QQ(k) + end + return z +end + +function _my_rand(VOE::AbstractAlgebra.Generic.FreeModule{Hecke.NfRelOrdElem{nf_elem, Hecke.NfRelElem{nf_elem}}}, k::Oscar.IntegerUnion = 5) + n = rank(VOE) + OE = base_ring(VOE) + v = vec(zeros(OE, 1, n)) + for i=1:n + v[i] = rand(OE, k) + end + return VOE(v) +end + +function _vecQQ_to_VE(VE::VE::AbstractAlgebra.Generic.FreeModule{Hecke.NfRelElem{nf_elem}}, e::Vector{fmpq) + n = size(e)[2] + E = base_ring(VE) + d = absolute_degree(E) + @assert divides(n,d)[1] + @assert divexact(n,d) == rank(VE) + Eabs, EbastoE = absolute_simple_field(E) + v = vec(zeros(E, 1, divexact(n,d))) + for i=0:rank(VE)-1 + v[i] = EabstoE(Eabs(e[i*d:(i+1)*d])) + end + return VE(v) +end + +function image_centralizer_in_Oq(L::ZLat, f::fmpq_mat) + Lh = inverse_trace_lattice(L, f) + G = gram_matrix(rational_span(Lh)) + n = degree(Lh) + @assert n > 1 + E = base_field(Lh) + invo = involution(E) + b = gen(E) + ord = get_attribute(E, :cyclo) + @req ord >= 3 "f must of order at least 3" + prime = false + + if length(prime_divisors(ord)) == 1 + prime = true + OE = maximal_order(E) + D = different(OE) + P = prime_decomposition(OE, minimum(D))[1][1] + end + + qL = discriminant_group(L) + OqL = orthogonal_group(qL) + MGf = matrix_group([f]) + fqL = OqL(gens(MGf)[1]) + Oqf, OqftoOqL = centralizer(OqL, fqL) + K = base_field(E) + + VO = FreeModule(O(E), n) + VE = VectorSpace(E, n) + gens = [fqL, OqL(gens(matrix_group([-f^0]))[1])] + dets = [b^n, E(-1)^n] + S = subgroup(Oqf, gens) + w = _skew_element_cyclo(E) + count = 0 + ind = 1 + flag = true + + M = gram_matrix(lll_reduction(L)) + extra = [M[:,j] for j=1:ncols(M)] + while order(S) != order(Oqf) + count +=1 + if flag + iterate(qL, ind) === nothing && flag = false && @vprint :LWI 1 "Done enumerating the discriminant group\n" && continue + s, ind = iterate(qL, ind) + elseif length(extra) != 0 + e = pop!(extra) + s = _vecQQ_to_VE(VE, e).v + else + s = _my_rand(VO).v + end + if iszero(s) + continue + end + sigma = (s*G*transpose(map_entries(invo, s)))[1]//2 + @vprint :LWI 1 "Computing spinor norms of Oqd. Total: $(order(Oqf)). Remaining: $(order(Oqf)//order(S)). Number of tries: $(count). \n" + GC.gc() + if divides(order(qL), 2)[1] && sigma == 0 + sg = s*G + (m,i) = minimum([(norm(val), index) for (index, val) in enumerate(sg) if val != 0]) + v = gen(VE, i).v + dimker, ker = left_kernel((G*transpose(map_entries(invo, vcat(s, v))))) + for i=1:dimker + y = ker[i,:] + mu = (-y*G*transpose(map_entries(invo, y)))[1]//(2*(s*G*transpose(map_entries(invo,v)))[1]) + T, det, TE = _eichler_isometry(G, s, v, y, mu) + if gcd(denominator(T), order(qL)) == 1 + Tbar = OqL(T) + Tbar = Oqf(Tbar) + if Tbar notin S + push!(gens, Tbar) + push!(dets, E(1)) + S = sub(Oqf, gens) + end + end + end + end + if prime + I = D*fractional_ideal(OE, s*G) + if valuation(I, P) <= 0 + continue + end + end + for i in 1:100 + sigma1 = sigma + E(_my_rand(K))*omega + if sigma1 == 0 + continue + end + tau, determ, tauE = _symmetry(G, s, sigma1) + if gcd(denominator(tau), order(qL)) != 1 + continue + end + taubar = OqL(tau) + taubar = Oqf(taubar) + if taubar notin S + push!(gens, taubar) + push!(dets, determ) + S = sub(Oqf, gens) + end + end + end + Lhn = hermitian_lattice(E, (1//ord)*G) +end diff --git a/src/NumberTheory/LatticesWithIsometry/Types.jl b/src/NumberTheory/LatticesWithIsometry/Types.jl new file mode 100644 index 000000000000..cca4db9e2706 --- /dev/null +++ b/src/NumberTheory/LatticesWithIsometry/Types.jl @@ -0,0 +1,43 @@ +@attributes mutable struct LatticeWithIsometry{S, T} <: AbsLat{S} + Lb::AbsLat{S} + f::T + GL + Lh::HermLat + n::Int + + function LatticeWithIsometry{S, T}(Lb::AbsLat{S}, f::T, GL, Lh::HermLat, n::Int) where {S, T} + z = new{S, T}(Lb, f, GL, Lh, n) + return z + end + + function LatticeWithIsometry{S, T}(Lb::AbsLat{S}, f::T, n::Int) where {S, T} + z = new{S, T}() + z.Lb = Lb + z.f = f + z.n = n + Lh = Hecke.inverse_trace_lattice(Lb, f) + z.Lh = Lh + return z + end +end + +lattice(Lf::LatticeWithIsometry) = Lf.Lb + +isometry(Lf::LatticeWithIsometry) = Lf.f + +hermitian_structure(Lf::LatticeWithIsometry) = Lf.Lh + +rank(Lf::LatticeWithIsometry) = rank(Lf.Lb) + +degree(Lf::LatticeWithIsometry) = degree(Lf.Lb) + +genus(Lf::LatticeWithIsometry) = genus(Lf.Lb) + +minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf)) + +charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf)) + +image_centralizer_in_Oq(Lf::LatticeWithIsometry) = Lf.GL + +order_isometry(Lf::LatticeWithIsometry) = Lf.n + diff --git a/src/Oscar.jl b/src/Oscar.jl index 8ab671cb6a76..d302ee762de1 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -378,6 +378,9 @@ include("Modules/module-localizations.jl") include("Geometry/basics.jl") include("NumberTheory/NmbThy.jl") +include("NumberTheory/LatticesWithIsometry/TraceEquivalence.jl") +include("NumberTheory/LatticesWithIsometry/Types.jl") +include("NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl") include("PolyhedralGeometry/main.jl") From 5ca1905ef3c47a0adec835a6e0147d076b160512 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 26 Aug 2022 09:09:41 +0200 Subject: [PATCH 02/76] first step --- src/NumberTheory/LWI.jl | 4 + .../LatticesWithIsometry/Enumeration.jl | 250 +++++++++ .../LatticesWithIsometry.jl | 475 ++++++++++-------- src/NumberTheory/LatticesWithIsometry/Misc.jl | 48 ++ .../LatticesWithIsometry/TraceEquivalence.jl | 309 +++--------- .../LatticesWithIsometry/Types.jl | 41 +- src/Oscar.jl | 4 +- 7 files changed, 638 insertions(+), 493 deletions(-) create mode 100644 src/NumberTheory/LWI.jl create mode 100644 src/NumberTheory/LatticesWithIsometry/Enumeration.jl create mode 100644 src/NumberTheory/LatticesWithIsometry/Misc.jl diff --git a/src/NumberTheory/LWI.jl b/src/NumberTheory/LWI.jl new file mode 100644 index 000000000000..9c23f70bd240 --- /dev/null +++ b/src/NumberTheory/LWI.jl @@ -0,0 +1,4 @@ +include("LatticesWithIsometry/Misc.jl") +include("LatticesWithIsometry/Types.jl") +include("LatticesWithIsometry/TraceEquivalence.jl") +include("LatticesWithIsometry/LatticesWithIsometry.jl") diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl new file mode 100644 index 000000000000..83d7329c994e --- /dev/null +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -0,0 +1,250 @@ +export admissible_triples, is_admissble_triple + +################################################################################## +# +# This is an import to Oscar of the methods written following the paper [BH22] on +# "Finite subgroups of automorphisms of K3 surfaces". +# +################################################################################## + +# The tuples in output are pairs of positive integers! +function _tuples_divisors(d::fmpz) + div = divisors(d) + return [(dd,abs(divexact(d,dd))) for dd in div] +end + +# This is line 8 of Algorithm 1, they correspond to the possible +# discriminant for the genera A and B to glue to fit in C. d is +# the determinant of C, m the maximal p-valuation of the gcd of +# d1 and dp. +function _find_D(d::fmpz, m::Integer, p::Integer) + @req is_prime(p) "p must be a prime number" + @req d != 0 "The discriminant of a non-degenerate form is non-zero" + + # If m == 0, there are no conditions on the gcd of d1 and dp + if m == 0 + return _tuples_divisors(d) + end + + D = Tuple{Int64,Int64}[] + # We try all the values of g possible, from 1 to p^m + for j=0:m + g = p^j + dj = _tuples_divisors(d*g^2) + for (d1,dp) in dj + if mod(d1,g) == mod(dp,g) == 0 + push!(D,(d1,dp)) + end + end + end + return D +end + +# This is line 10 of Algorithm 1. We need the condition on the even-ness of +# C since subgenera of an even genus are even too. r is the rank of +# the subgenus, d its determinant, s and l the scale and level of C +function _find_L(r::Integer, d, s::fmpz, l::fmpz, p::Integer, even = true) + L = ZGenus[] + for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] + gen = genera((s1,s2), d, even=even) + filter!(G -> divides(scale(G), s)[1], gen) + filter!(G -> divides(p*l, level(G))[1], gen) + append!(L, gen) + end + return L +end + +@doc Markdown.doc""" + is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) -> Bool + +Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such +that the rank of `B` is divisible by $p-1$ and the level of `C` is a power +of `p`, return whether `(A,B,C)` is `p`-admissible in the sense of +Definition 4.13. [BH22] +""" +function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) + zg = genus(Zlattice(gram = matrix(QQ, 0, 0, []))) + if ((A == zg) && (B == C)) || ((B == zg) && (A == C)) + # C can be always glued with the empty genus to obtain C + return true + elseif (A == zg) || (B == zg) + # If A or B is empty but the other is not C, then there is no glueing + return false + end + + @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" + @req prime_divisors(level(C)) == [p] || level(C) == one(fmpq) "The level of C must be a power of p" + AperpB = orthogonal_sum(A,B) + # If A and B glue to C, the sum of their ranks must match the one of C + if rank(AperpB) != rank(C) + return false + end + + lA = ngens(discriminant_group(A)) + lB = ngens(discriminant_group(B)) + if all(g -> abs(det(AperpB)) != p^(2*g)*abs(det(C)), 0:min(lA, lB, divexact(rank(B), p-1))) + return false + 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))) + if local_symbol(AperpB,q) != local_symbol(C,q) + return false + end + end + + g = divexact(valuation(div(det(AperpB), det(C)), p), 2) + # If the determinants agree, A+B = C and, equivalently, they agree locally at p too. + # Otherwise, their localisations at p must be rationally equal. + if g == 0 + return local_symbol(AperpB, p) == local_symbol(C, p) + elseif excess(local_symbol(AperpB, p)) != excess(local_symbol(C, p)) + return false + end + + if !(divides(scale(AperpB), scale(C))[1] && divides(p*level(C), level(AperpB))[1]) + return false + end + + # At this point, if C is unimodular at p, the glueing condition is equivalent to have + # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B + if !divides(det(C), p)[1] + qA = gram_matrix_quadratic(normal_form(primary_part(discriminant_group(A), p)[1])[1]) + qB = gram_matrix_quadratic(normal_form(rescale(primary_part(discriminant_group(B), p)[1], -1))[1]) + return qA == qB + end + + l = valuation(level(C), p) + Ap = symbol(local_symbol(A, p)) + Bp = symbol(local_symbol(B,p)) + a_max = sum(Int64[s[2] for s in Ap if s[1] == l+1]) + b_max = sum(Int64[s[2] for s in Bp if s[1] == l+1]) + # For the glueing, rho_{l+1}(A) and rho_{l+1}(B) are anti-isometric, so they must have the + # same order + if a_max != b_max + return false + end + + # Since p^l*A^\vee/A is in the glue, its order is less than the order of the glue + if g < a_max + return false + end + + a1 = sum(Int64[s[2] for s in Ap if s[1] == 1]) + a2 = sum(Int64[s[2] for s in Ap if s[1] >= 2]) + b1 = sum(Int64[s[2] for s in Bp if s[1] == 1]) + b2 = sum(Int64[s[2] for s in Bp if s[1] >= 2]) + + if l != 0 + ker_min = max(g-a1, g-b1, a_max) + else + ker_min = max(g-a1, g-b1) + end + ker_max = min(a2+div(a1,2), b2+div(b1,2), g) + + if ker_max < ker_min + return false + end + + ABp = symbol(local_symbol(AperpB, p)) + Cp = symbol(local_symbol(C, p)) + + if a_max == g + if length(Ap) > 1 + Ar = ZpGenus(p, Ap[1:end-1]) + else + Ar = genus(matrix(ZZ,0,0,[]), p) + end + + if length(Bp) > 1 + Br = ZpGenus(p, Bp[1:end-1]) + else + Br = genus(matrix(ZZ, 0, 0, []), p) + end + + ABr = orthogonal_sum(Ar, Br) + + for i = 1:l + s1 = symbol(ABr)[i] + s2 = symbol(Cp)[i] + if s1[2] != s2[2] + return false + end + if p == 2 && s1[4] > s2[4] + return false + elseif p != 2 && s1[3] != s2[3] + return false + end + end + end + + s3 = symbol(local_symbol(C, 2))[end] + if p == 2 + s1 = symbol(Ap)[s3[1]+1] + s2 = symbol(Bp)[s3[1]+1] + if s1[3] != s2[3] + return false + end + end + + for s in Cp + s[1] += 2 + end + Cp = ZpGenus(p, Cp) + + if !represented(local_symbol(AperpB, p), Cp) + return false + end + if !represents(C, AperpB) + return false + end + + return true +end + +function is_admissible_triple(A::Union{ZLat, ZGenus}, B::Union{ZLat, ZGenus}, C::Union{ZLat, ZGenus}, p::Integer) + L = ZGenus[typeof(D) <: ZLat ? genus(D) : D for D = (A, B, C)] + return is_admissible_triple(L[1], L[2], L[3], p) +end + +@doc Markdown.doc""" + admissible_triples(C::ZGenus, p::Integer) -> Vector{Tuple{ZGenus, ZGenus}} + +Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of +$\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and +`B` is of rank divisible by $p-1$. See Algorithm 1 [BH22]. +""" +function admissible_triples(G::ZGenus, p::Int64) + @req is_prime(p) "p must be a prime number" + n = rank(G) + d = det(G) + even = iseven(G) + L = Tuple{ZGenus, ZGenus}[] + for ep in 0:div(n, p-1) + rp = (p-1)*ep + r1 = n - rp + m = min(ep, r1) + D = _find_D(d, m, p) + for (d1, dp) in D + L1 = _find_L(r1, d1, scale(G), level(G), p, even) + Lp = _find_L(rp, dp, scale(G), level(G), p, even) + for (A, B) in [(A, B) for A in L1 for B in Lp] + if is_admissible_triple(A, B, G, p) + push!(L, (A, B)) + end + end + end + end + return L +end + +admissible_triples(L::ZLat, p::Integer) = admissible_triple(genus(L), p) + + +function primitive_extensions(A::ZLat, B::ZLat, C::ZLat, p::Integer) + @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" + L = [] + g = valuation(divexact(det(A)*det(B), det(C)), p) + + +end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index c1f0c761c22b..feaed22cf2b9 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -1,252 +1,307 @@ -export admissible_triples, is_admissble_triple - -################################################################################## +export hermitian_structure, isometry, lattice_with_isometry, order_of_isometry, + type + +############################################################################### # -# This is an import to Oscar of the methods written following the paper [BH22] on -# "Finite subgroups of automorphisms of K3 surfaces". +# String I/O # -################################################################################## +############################################################################### -# The tuples in output are pairs of positive integers! -function _tuples_divisors(d::fmpz) - div = divisors(d) - return [(dd,abs(divexact(d,dd))) for dd in div] +function Base.show(io::IO, Lf::LatticeWithIsometry) + println(io, "Lattice of rank $(rank(Lf)) with isometry of order $(order_of_isometry(Lf))") + println(io, "given by") + print(IOContext(io, :compact => true), isometry(Lf)) end -# This is line 8 of Algorithm 1, they correspond to the possible -# discriminant for the genera A and B to glue to fit in C. d is -# the determinant of C, m the maximal p-valuation of the gcd of -# d1 and dp. -function _find_D(d::fmpz, m::Integer, p::Integer) - @req is_prime(p) "p must be a prime number" - @req d != 0 "The discriminant of a non-degenerate form is non-zero" - - # If m == 0, there are no conditions on the gcd of d1 and dp - if m == 0 - return _tuples_divisors(d) - end - - D = Tuple{Int64,Int64}[] - # We try all the values of g possible, from 1 to p^m - for j=0:m - g = p^j - dj = _tuples_divisors(d*g^2) - for (d1,dp) in dj - if mod(d1,g) == mod(dp,g) == 0 - push!(D,(d1,dp)) - end - end - end - return D -end +############################################################################### +# +# Attributes +# +############################################################################### -# This is line 10 of Algorithm 1. We need the condition on the even-ness of -# C since subgenera of an even genus are even too. r is the rank of -# the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Integer, d, s::fmpz, l::fmpz, p::Integer, even = true) - L = ZGenus[] - for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] - gen = genera((s1,s2), d, even=even) - filter!(G -> divides(scale(G), s)[1], gen) - filter!(G -> divides(p*l, level(G))[1], gen) - append!(L, gen) - end - return L -end +@doc Markdown.doc""" + lattice(Lf::LatticeWithIsometry) -> ZLat -primes(G::ZGenus) = prime.(local_symbols(G)) +Given a lattice with isometry `Lf`, return the underlying lattice `L`. +""" +lattice(Lf::LatticeWithIsometry) = Lf.Lb @doc Markdown.doc""" - is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) -> Bool + isometry(Lf::LatticeWithIsometry) -> fmpq_mat -Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such -that the rank of `B` is divisible by $p-1$ and the level of `C` is a power -of `p`, return whether `(A,B,C)` is `p`-admissible in the sense of -Definition 4.13. [BH22] +Given a lattice with isometry `Lf`, return the underlying isometry `f`. """ -function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) - zg = genus(Zlattice(gram = matrix(QQ, 0, 0, []))) - if ((A == zg) && (B == C)) || ((B == zg) && (A == C)) - # C can be always glued with the empty genus to obtain C - return true - elseif (A == zg) || (B == zg) - # If A or B is empty but the other is not C, then there is no glueing - return false - end +isometry(Lf::LatticeWithIsometry) = Lf.f - @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" - @req prime_divisors(level(C)) == [p] || level(C) == one(fmpq) "The level of C must be a power of p" - AperpB = orthogonal_sum(A,B) - # If A and B glue to C, the sum of their ranks must match the one of C - if rank(AperpB) != rank(C) - return false - end +@doc Markdown.doc""" + order_of_isometry(Lf::LatticeWithIsometry) -> Integer - lA = ngens(discriminant_group(A)) - lB = ngens(discriminant_group(B)) - if all(g -> abs(det(AperpB)) != p^(2*g)*abs(det(C)), 0:min(lA, lB, divexact(rank(B), p-1))) - return false - end +Given a lattice with isometry `Lf`, return the order of the underlying +isometry `f`. +""" +order_of_isometry(Lf::LatticeWithIsometry) = Lf.n - # 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))) - if local_symbol(AperpB,q) != local_symbol(C,q) - return false - end - end +############################################################################### +# +# Predicates +# +############################################################################### - g = divexact(valuation(div(det(AperpB), det(C)), p), 2) - # If the determinants agree, A+B = C and, equivalently, they agree locally at p too. - # Otherwise, their localisations at p must be rationally equal. - if g == 0 - return local_symbol(AperpB, p) == local_symbol(C, p) - elseif excess(local_symbol(AperpB, p)) != excess(local_symbol(C, p)) - return false - end +@doc Markdown.doc""" + rank(Lf::LatticeWithIsometry) -> Integer - if !(divides(scale(AperpB), scale(C))[1] && divides(p*level(C), level(AperpB))[1]) - return false - end +Given a lattice with isometry `Lf`, return the rank of the underlying lattice +`L`. +""" +rank(Lf::LatticeWithIsometry) = rank(lattice(Lf)) - # At this point, if C is unimodular at p, the glueing condition is equivalent to have - # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B - if !divides(det(C), p)[1] - qA = gram_matrix_quadratic(normal_form(primary_part(discriminant_group(A), p)[1])[1]) - qB = gram_matrix_quadratic(normal_form(rescale(primary_part(discriminant_group(B), p)[1], -1))[1]) - return qA == qB - end +@doc Markdown.doc""" + charpoly(Lf::LatticeWithIsometry) -> fmpq_poly - l = valuation(level(C), p) - Ap = symbol(local_symbol(A, p)) - Bp = symbol(local_symbol(B,p)) - a_max = sum(Int64[s[2] for s in Ap if s[1] == l+1]) - b_max = sum(Int64[s[2] for s in Bp if s[1] == l+1]) - # For the glueing, rho_{l+1}(A) and rho_{l+1}(B) are anti-isometric, so they must have the - # same order - if a_max != b_max - return false - end - - # Since p^l*A^\vee/A is in the glue, its order is less than the order of the glue - if g < a_max - return false - end +Given a lattice with isometry `Lf`, return the characteristic polynomial of the +underlying isometry `f`. +""" +charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf)) - a1 = sum(Int64[s[2] for s in Ap if s[1] == 1]) - a2 = sum(Int64[s[2] for s in Ap if s[1] >= 2]) - b1 = sum(Int64[s[2] for s in Bp if s[1] == 1]) - b2 = sum(Int64[s[2] for s in Bp if s[1] >= 2]) +@doc Markdown.doc""" + minpoly(Lf::LatticeWithIsometry) -> fmpq_poly - if l != 0 - ker_min = max(g-a1, g-b1, a_max) - else - ker_min = max(g-a1, g-b1) - end - ker_max = min(a2+div(a1,2), b2+div(b1,2), g) +Given a lattice with isometry `Lf`, return the minimal polynomial of the +underlying isometry `f`. +""" +minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf)) - if ker_max < ker_min - return false - end +@doc Markdown.doc""" + genus(Lf::LatticeWithIsometry) -> ZGenus - ABp = symbol(local_symbol(AperpB, p)) - Cp = symbol(local_symbol(C, p)) - - if a_max == g - if length(Ap) > 1 - Ar = ZpGenus(p, Ap[1:end-1]) - else - Ar = genus(matrix(ZZ,0,0,[]), p) - end - - if length(Bp) > 1 - Br = ZpGenus(p, Bp[1:end-1]) - else - Br = genus(matrix(ZZ, 0, 0, []), p) - end - - ABr = orthogonal_sum(Ar, Br) - - for i = 1:l - s1 = symbol(ABr)[i] - s2 = symbol(Cp)[i] - if s1[2] != s2[2] - return false - end - if p == 2 && s1[4] > s2[4] - return false - elseif p != 2 && s1[3] != s2[3] - return false - end - end +Given a lattice with isometry `Lf`, return the genus of the underlying +lattice `L`. + +For now, in order for the genus to exist, the lattice must be integral. +""" +genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus(L) : error("Underlying lattice must be integral"); end + + +############################################################################### +# +# Constructor +# +############################################################################### + +@doc Markdown.doc""" + lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true) + -> LatticeWithIsometry + +Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry +of `L` of finite order `n`, return the corresponding lattice with isometry +pair $(L, f)$. +""" +function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true) + if rank(L) == 0 + return LatticewithIsometry(L, matrix(QQ,0,0,[]), -1) end - s3 = symbol(local_symbol(C, 2))[end] - if p == 2 - s1 = symbol(Ap)[s3[1]+1] - s2 = symbol(Bp)[s3[1]+1] - if s1[3] != s2[3] - return false - end + if check + @req det(f) != 0 "f is not invertible" + G = gram_matrix(L) + @req f*G*transpose(f) == G "f does not define an isometry of L" + m = Oscar._exponent(f) + @req m > 0 "f is not of finite exponent" + @req n == m "The order of f is not equal to n" end - for s in Cp - s[1] += 2 + return LatticeWithIsometry(L, f, n) +end + +@doc Markdown.doc""" + lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true) + -> LatticeWithIsometry + +Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry +of `L` of finite order, return the corresponding lattice with isometry +pair $(L, f)$. +""" +function lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true) + if rank(L) == 0 + return LatticeWithIsometry(L, matrix(QQ,0,0,[]), -1) end - Cp = ZpGenus(p, Cp) - - if !represented(local_symbol(AperpB, p), Cp) - return false + + if check + @req det(f) != 0 "f is not invertibe" + G = gram_matrix(L) + @req f*G*transpose(f) == G "f does not define an isometry of L" + m = Oscar._exponent(f) + @req m > 0 "f is not of finite exponent" end - if !represents(C, AperpB) - return false - end - - return true + + n = Oscar._exponent(f) + return lattice_with_isometry(L, f, Int(n), check = false) end -function is_admissible_triple(A::Union{ZLat, ZGenus}, B::Union{ZLat, ZGenus}, C::Union{ZLat, ZGenus}, p::Integer) - L = ZGenus[typeof(D) <: ZLat ? genus(D) : D for D = (A, B, C)] - return is_admissible_triple(L[1], L[2], L[3], p) +@doc Markdown.doc""" + lattice_with_isometry(L::ZLat) -> LatticeWithIsometry + +Given a $\mathbb Z$-lattice `L` return the lattice with isometry pair $(L, f)$, +where `f` corresponds to the identity mapping of `L`. +""" +function lattice_with_isometry(L::ZLat) + r = rank(L) + f = identity_matrix(QQ, r) + return lattice_with_isometry(L, f, check = false) end +############################################################################### +# +# Hermitian structure +# +############################################################################### + @doc Markdown.doc""" - admissible_triples(C::ZGenus, p::Integer) -> Vector{Tuple{ZGenus, ZGenus}} + hermitian_structure(Lf::LatticeWithIsometry; check::Bool = true) -> HermLat -Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of -$\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and -`B` is of rank divisible by $p-1$. See Algorithm 1 [BH22]. +Given a lattice with isometry `Lf` such that the minimal polynomial of the +underlying isometry `f` is irreducible and cyclotomic, return the +hermitian structure of the underlying lattice `L` over the $n$th cyclotomic +field, where $n$ is the order of `f`. + +If it exists, the hermitian structure is cached. +""" +@attr function hermitian_structure(Lf::LatticeWithIsometry) + @req rank(Lf) > 0 "Lf must be of positive rank" + f = isometry(Lf) + n = order_of_isometry(Lf) + + @req n >= 3 "No hermitian structures for n smaller than 3" + + return inverse_trace_lattice(lattice(Lf), f, n = n, check = true) +end + +############################################################################### +# +# Image centralizer in discriminant group +# +############################################################################### + +@doc Markdown.doc""" + discriminant_group(Lf::LatticeWithIsometry) -> TorQuadMod, TorQuadModMor + +Given an integral lattice with isometry `Lf`, return the discriminant group `q` +of the underlying lattice `L` as well as this image of the underlying isometry +`f` inside $O(q)$. """ -function admissible_triples(G::ZGenus, p::Int64) - @req is_prime(p) "p must be a prime number" - n = rank(G) - d = det(G) - even = iseven(G) - L = Tuple{ZGenus, ZGenus}[] - for ep in 0:div(n, p-1) - rp = (p-1)*ep - r1 = n - rp - m = min(ep, r1) - D = _find_D(d, m, p) - for (d1, dp) in D - L1 = _find_L(r1, d1, scale(G), level(G), p, even) - Lp = _find_L(rp, dp, scale(G), level(G), p, even) - for (A, B) in [(A, B) for A in L1 for B in Lp] - if is_admissible_triple(A, B, G, p) - push!(L, (A, B)) - end - end - end +function discriminant_group(Lf::LatticeWithIsometry) + L = lattice(Lf) + f = isometry(Lf) + @req is_integral(L) "Underlying lattice must be integral" + q = discriminant_group(L) + Oq = orthogonal_group(q) + return q, Oq(gens(matrix_group(f))[1]) +end + +############################################################################### +# +# Signatures +# +############################################################################### + +function _real_kernel_signatures(L::ZLat, M) + C = base_ring(M) + bL = basis_matrix(L) + GL = gram_matrix(ambient_space(L)) + bLC = change_base_ring(C, bL) + GLC = change_base_ring(C, GL) + k, KC = left_kernel(M) + newGC = KC*bLC*GLC*transpose(KC*bLC) + + newGC = Hecke._gram_schmidt(newGC, C)[1] + diagC = diagonal(newGC) + + @assert all(z -> isreal(z), diagC) + @assert all(z -> !iszero(z), diagC) + + k1 = count(z -> z > 0, diagC) + k2 = length(diagC) - k1 + + return (k1, k2) +end + +function signatures(Lf::LatticeWithIsometry) + @req rank(Lf) != 0 "Signatures non available for the empty lattice" + L = lattice(Lf) + f = isometry(Lf) + @req Oscar._is_cyclotomic_polynomial(minpoly(f)) "Minimal polynomial must be irreducible and cyclotomic" + n = order_of_isometry(Lf) + @req divides(rank(L), euler_phi(n))[1] "The totient of the order of the underlying isometry must divide the rank of the underlying lattice" + C = CalciumField() + eig = eigenvalues(f, QQBar) + j = findfirst(z -> findfirst(k -> isone(z^k), 1:n) == n, eig) + lambda = C(eig[j]) + Sq = [i for i in 1:div(n,2) if gcd(i,n) == 1] + D = Dict{Integer, Tuple{Int64, Int64}}() + fC = change_base_ring(C, f) + for i in Sq + M = fC + inv(fC) - lambda^i - lambda^(-i) + D[i] = _real_kernel_signatures(L, M) end - return L + return D end -admissible_triples(L::ZLat, p::Integer) = admissible_triple(genus(L), p) +############################################################################### +# +# Kernel sublattices +# +############################################################################### +@doc Markdown.doc""" + kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) -> LatticeWithIsometry -function primitive_extensions(A::ZLat, B::ZLat, C::ZLat, p::Integer) - @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" - L = [] - g = valuation(divexact(det(A)*det(B), det(C)), p) - +Given a lattice with isometry `Lf` and a polynomial `p` with rational +coefficients, return the sublattice $M := \ker(p(f))$ of the underlying lattice +`L` with isometry `f`, together with the restriction $f_{\mid M}$. +""" +function kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) + n = order_of_isometry(Lf) + L = lattice(Lf) + f = isometry(Lf) + M = p(f) + k, K = left_kernel(change_base_ring(ZZ, M)) + L2 = Zlattice(gram = K*gram_matrix(L)*transpose(K)) + f2 = solve_left(change_base_ring(QQ, K), K*f) + @assert f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) + chi = parent(p)(collect(coefficients(minpoly(f2)))) + chif = parent(p)(collect(coefficients(minpoly(Lf)))) + _chi = gcd(p, chif) + @assert (rank(L2) == 0) || (chi == _chi) + return lattice_with_isometry(L2, f2, check = false) +end + +kernel_lattice(Lf::LatticeWithIsometry, p::fmpz_poly) = kernel_lattice(Lf, change_base_ring(QQ, p)) + +function kernel_lattice(Lf::LatticeWithIsometry, l::Integer) + @req divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" + p = Oscar._cyclotomic_polynomial(l) + return kernel_lattice(Lf, p) +end + +############################################################################### +# +# Type +# +############################################################################### +@attr function type(Lf::LatticeWithIsometry) + L = lattice(Lf) + f = isometry(Lf) + n = order_of_isometry(Lf) + divs = divisors(n) + Qx = Hecke.Globals.Qx + x = gen(Qx) + t = Dict{Integer, Tuple{LatticeWithIsometry, LatticeWithIsometry}}() + for l in divs + Hl = kernel_lattice(Lf, Oscar._cyclotomic_polynomial(l)) + Al = kernel_lattice(Lf, x^l-1) + t[l] = (Hl, Al) + end + return t end + diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl new file mode 100644 index 000000000000..4df6ef8821b6 --- /dev/null +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -0,0 +1,48 @@ + +############################################################################### +# +# Cyclotomic polynomials +# +############################################################################### + +function _cyclotomic_polynomial(n::Int64) + @assert n > 0 + _, x = QQ["x"] + return Hecke.cyclotomic(n, x) +end + +function _is_cyclotomic_polynomial(p::Union{fmpz_poly, fmpq_poly}) + n = degree(p) + R = parent(p) + x = gen(R) + list_cyc = union(Int64[k for k in euler_phi_inv(n)], [1]) + list_poly = [Hecke.cyclotomic(k, x) for k in list_cyc] + return any(q -> R(collect(coefficients(q))) == p, list_poly) +end + +############################################################################### +# +# Exponent of fmpq/fmpz_mat +# +############################################################################### + +function _is_of_finite_exponent(f::Union{fmpq_mat, fmpz_mat}) + !Hecke.is_squarefree(minpoly(f)) && return false + chi = charpoly(f) + fact = collect(factor(chi)) + return all(p -> _is_cyclotomic_polynomial(p[1]), fact) +end + +function _exponent(f::Union{fmpq_mat, fmpz_mat}) + !_is_of_finite_exponent(f) && return -1 + degs = unique(degree.([p[1] for p in collect(factor(minpoly(f)))])) + exps = euler_phi_inv(degs[1])::Vector{fmpz} + for i in 2:length(degs) + union!(exps, euler_phi_inv(degs[i])) + end + maxdeg = lcm(exps) + divmd = divisors(maxdeg) + n = findfirst(k -> isone(f^k), divmd) + @assert n !== nothing + return return divmd[n] +end diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index d9bd35a917a3..cd30ee4dac69 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -1,24 +1,22 @@ export trace_lattice, inverse_trace_lattice -function _cyclotomic_tower(n::Integer) - K,a = CyclotomicRealSubfield(n, cached=false) - Kt, t = PolynomialRing(K, "t", cached=false) - E,b = number_field(t^2-a*t+1, "b", cached=false) - set_attribute!(E, :cyclo, ZZ(n)) - return E, b -end - -function _is_cyclotomic_field(E::Oscar.Field) - f = get_attribute(E, :cyclo) - if f === nothing - return (false, fmpz(1)) - else - return (true, f) - end -end - -function trace_lattice(L::Hecke.AbsLat, order::Integer = 2) - #@req order > 0 "The order must be positive" +@doc Markdown.doc""" + trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T -> LatticeWithIsometry + +Given a lattice `L` which is either a $\mathbb Z$-lattice or a hermitian lattice +over the $E/K$ with `E` a cyclotomic field and `K` a maximal real subfield of +`E`, return the trace lattice `Lf` whose underlying lattice is the trace lattice +of `L` and the underlying map `f` is: + - the identity if `L` is a `ZLat` and `order == 1`; + - the opposite of the identity if `L` is a `ZLat` and `order == 2`; + - given by the multiplication by a $n$-th root of the unity if `L` is a + `HermLat`, where `n` is the order a primitive element in `E`. + +Note that the optional argument `order` has no effect if `L` is not a +$\mathbb Z$-lattice. +""" +function trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T + @req order > 0 "The order must be positive" n = degree(L) E = base_field(L) G = gram_matrix(rational_span(L)) @@ -31,12 +29,12 @@ function trace_lattice(L::Hecke.AbsLat, order::Integer = 2) else error("For ZLat the order must be 1 or 2") end - return L,f + return lattice_with_isometry(L, f, order, check = false) end + + bool, m = Hecke.is_cyclotomic_type(E) - bool, m = _is_cyclotomic_field(E) - - #@req bool "Base field must be cyclotomic" + @req bool "Base field must be cyclotomic" Eabs, EabstoE = Hecke.absolute_simple_field(E) EtoEabs = pseudo_inv(EabstoE) @@ -60,38 +58,52 @@ function trace_lattice(L::Hecke.AbsLat, order::Integer = 2) end gram = matrix(QQ, d*n, d*n, coeffs) @assert f*gram*transpose(f) == gram - return Zlattice(gram = 1//d*gram), f + LL = Zlattice(gram = 1//m*gram) + return lattice_with_isometry(LL, f, m, check = false) end -function _Sq(q::Integer) - @assert q > 1 - Sq = [k for k=1:q if gcd(k,q) == 1] - @assert length(Sq) == euler_phi(q) - return Sq -end - -function _order_max(rank::Integer) - k = iseven(rank) ? rank : rank+1 - order_max = maximum(euler_phi_inv(k)) - order_max = maximum(filter(n -> divides(ZZ(k), euler_phi(n))[1], 1:order_max)) - return order_max -end - -function inverse_trace_lattice(L::ZLat, f::fmpq_mat) - n = findfirst(n -> is_one(f^n), 1:_order_max(rank(L))) - #@req ((n !== nothing) || (divides(rank(L), euler_phi(n))[1])) "(L, f) is not the trace lattice of a hermitian lattice over a cyclotomic field" +@doc Markdown.doc""" + inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, + check::Bool = true) -> HermLat + +Given a $\mathbb Z$-lattice `L` and a matrix with rational entries `f`, +representing an isometry of `L` of order $n \geq 3$, such that the totient of `n` +divides the rank of `L` and the minimal polynomial of `f` is the `n`th cyclotomic +polynomial, return the corresponding hermitian lattice over $E/K$ where `E` is the +`n`th cyclotomic field and `K` is a maximal real subfield of `E`, where the +multiplication by the `n`th root of unity correspond to the mapping via `f`. +""" +function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Bool = true) + if n <= 0 + Oscar._exponent(f) + end + + @req n > 0 "f is not of finite exponent" + + if check + @req n >= 3 "No hermitian inverse trace lattice for order less than 3" + G = gram_matrix(L) + @req Oscar._is_cyclotomic_polynomial(minpoly(f)) "The minimal polynomial of f must be irreducible and cyclotomic" + @req f*G*transpose(f) == G "f does not define an isometry of L" + @req Oscar._exponent(f) == n "The order of f should be equal to n" + @req divides(rank(L), euler_phi(n))[1] "The totient of n must divides the rank of L" + end - E,b = _cyclotomic_tower(n) + E,b = cyclotomic_field_as_cm_extension(n, cached = false) m = divexact(rank(L), euler_phi(n)) # We choose as basis for the hermitian lattice Lh the identity matrix gram = matrix(zeros(E, m,m)) G = gram_matrix(L) - Sq = _Sq(n) + v = zero_matrix(QQ, 1, degree(L)) for i=1:m for j=1:m - alpha = sum([G[1+(i-1)*euler_phi(n),:]*transpose(f^k)[:,1+(j-1)*euler_phi(n)] for k in Sq])[1] + vi = deepcopy(v) + vi[1,1+(i-1)*euler_phi(n)] = one(QQ) + vj = deepcopy(v) + vj[1,1+(j-1)*euler_phi(n)] = one(QQ) + alpha = sum([(vi*G*transpose(vj*f^k))[1]*b^k for k in 0:n-1]) gram[i,j] = alpha end end @@ -103,212 +115,17 @@ function inverse_trace_lattice(L::ZLat, f::fmpq_mat) return Lh end -#function _induced_image_in_Oq(qL::Hecke.TorQuadMod, f::fmpq_mat) -# imgs = elem_type(qL)[] -# d = denominator(f) -# m = order(qL) -# dinv = invmod(d,m) -# for g in gens(qL) -# x = lift(g)*f -# x = QQ(dinv*d)*x -# push!(imgs, qL(x)) -# end -# return hom(qL, qL, imgs) -#end - -function _eichler_isometry(G, u, v, y, mu) - n = ncols(G) - d = absolute_degree(base_ring(G)) - s = involution(base_ring(G)) - @assert u*G*transpose(map_entries(s,u)) == 0 - @assert u*G*transpose(map_entries(s,y)) == 0 - @assert v*G*transpose(map_entries(s,y)) == 0 - V = VectorSpace(base_ring(G) ,n) - uv = u*G*transpose(map_entries(s,v))[1] - @assert mu*uv+s(mu*uv) == (-y*G*transpose(map_entries(s,y)))[1] - imgs = [e + ((e*G*transpose(map_entries(s,u)))[1]//s(uv))*y + ((mu*(e*G*transpose(map_entries(s,u)))[1])//(-s(uv)) - (e*G*transpose(map_entries(s,y)))[1]//uv)*u for e in gens(V)] - f = matrix(reduce(vcat,imgs)) - @assert G == f*G*transpose(map_entries(s,f)) - return _restrict_scalars(f), one(base_ring(G)), f -end - -function _restrict_scalars(M) - n = ncols(M) - d = absolute_degree(base_ring(M)) - N = matrix(zeros(QQ, d*n, d*n)) - bas = absolute_basis(base_ring(M)) - for i=0:n-1 - for j=0:n-1 - F[d*i+1:(i+1)*d+1, d*j+1:(j+1)*d+1] = matrix(absolute_coordinates.(M[i,j].*(bas))) - end - end - return F -end - -function _skew_element_cyclo(E) - K = base_field(E) - b = gen(E) - s = involution(E) - M = matrix(K, 2,1,[2, b+s(b)]) - r,k = left_kernel(M) - @assert r == 1 - c = E(k[1]+b*k[2]) - @assert iszero(c+s(c)) - return c -end - -function _symmetry(G, v, sigma) - s = involution(parent(sigma)) - @assert v*G*transpose(map_entries(s,v)) == sigma+s(sigma) - n = ncols(G) - V = VectorSpace(base_ring(G), n) - imgs = [e - (e*G*transpose(map_entries(s,v)))*inv(sigma)*v for e in gens(V)] - g = matrix(reduce(vcat, imgs)) - return _restrict_scalars(g), det(g), g -end - -function _my_rand_QQ(k::Oscar.IntegerUnion = 64) - p = ZZ(rand(-k:k)) - q = ZZ(rand(-k:k)) - while q == 0 - q = ZZ(rand(-k:k)) - end - return p//q -end - -function _my_rand(K::AnticNumberField, k::Oscar.IntegerUnion = 64) - d = degree(K) - a = gen(K) - pows = powers(a, d-1) - z = K(0) - for zz in pows - z += zz*my_rand_QQ(k) - end - return z -end - -function _my_rand(VOE::AbstractAlgebra.Generic.FreeModule{Hecke.NfRelOrdElem{nf_elem, Hecke.NfRelElem{nf_elem}}}, k::Oscar.IntegerUnion = 5) - n = rank(VOE) - OE = base_ring(VOE) - v = vec(zeros(OE, 1, n)) - for i=1:n - v[i] = rand(OE, k) - end - return VOE(v) -end - -function _vecQQ_to_VE(VE::VE::AbstractAlgebra.Generic.FreeModule{Hecke.NfRelElem{nf_elem}}, e::Vector{fmpq) - n = size(e)[2] - E = base_ring(VE) - d = absolute_degree(E) - @assert divides(n,d)[1] - @assert divexact(n,d) == rank(VE) - Eabs, EbastoE = absolute_simple_field(E) - v = vec(zeros(E, 1, divexact(n,d))) - for i=0:rank(VE)-1 - v[i] = EabstoE(Eabs(e[i*d:(i+1)*d])) - end - return VE(v) -end - -function image_centralizer_in_Oq(L::ZLat, f::fmpq_mat) - Lh = inverse_trace_lattice(L, f) - G = gram_matrix(rational_span(Lh)) - n = degree(Lh) - @assert n > 1 - E = base_field(Lh) - invo = involution(E) - b = gen(E) - ord = get_attribute(E, :cyclo) - @req ord >= 3 "f must of order at least 3" - prime = false - - if length(prime_divisors(ord)) == 1 - prime = true - OE = maximal_order(E) - D = different(OE) - P = prime_decomposition(OE, minimum(D))[1][1] +function image_centralizer_in_Oq(L::ZLat, f::fmpq_mat; check::Bool = true) + if check + @req is_integral(L) "Lattice L must be integral" + @req Oscar._order(f) >0 "f is not of finite exponent" + @req f*gram_matrix(L)*transpose(f) == gram_matrix(L) "f does not define an isometry of L" end qL = discriminant_group(L) OqL = orthogonal_group(qL) MGf = matrix_group([f]) fqL = OqL(gens(MGf)[1]) - Oqf, OqftoOqL = centralizer(OqL, fqL) - K = base_field(E) - - VO = FreeModule(O(E), n) - VE = VectorSpace(E, n) - gens = [fqL, OqL(gens(matrix_group([-f^0]))[1])] - dets = [b^n, E(-1)^n] - S = subgroup(Oqf, gens) - w = _skew_element_cyclo(E) - count = 0 - ind = 1 - flag = true - - M = gram_matrix(lll_reduction(L)) - extra = [M[:,j] for j=1:ncols(M)] - while order(S) != order(Oqf) - count +=1 - if flag - iterate(qL, ind) === nothing && flag = false && @vprint :LWI 1 "Done enumerating the discriminant group\n" && continue - s, ind = iterate(qL, ind) - elseif length(extra) != 0 - e = pop!(extra) - s = _vecQQ_to_VE(VE, e).v - else - s = _my_rand(VO).v - end - if iszero(s) - continue - end - sigma = (s*G*transpose(map_entries(invo, s)))[1]//2 - @vprint :LWI 1 "Computing spinor norms of Oqd. Total: $(order(Oqf)). Remaining: $(order(Oqf)//order(S)). Number of tries: $(count). \n" - GC.gc() - if divides(order(qL), 2)[1] && sigma == 0 - sg = s*G - (m,i) = minimum([(norm(val), index) for (index, val) in enumerate(sg) if val != 0]) - v = gen(VE, i).v - dimker, ker = left_kernel((G*transpose(map_entries(invo, vcat(s, v))))) - for i=1:dimker - y = ker[i,:] - mu = (-y*G*transpose(map_entries(invo, y)))[1]//(2*(s*G*transpose(map_entries(invo,v)))[1]) - T, det, TE = _eichler_isometry(G, s, v, y, mu) - if gcd(denominator(T), order(qL)) == 1 - Tbar = OqL(T) - Tbar = Oqf(Tbar) - if Tbar notin S - push!(gens, Tbar) - push!(dets, E(1)) - S = sub(Oqf, gens) - end - end - end - end - if prime - I = D*fractional_ideal(OE, s*G) - if valuation(I, P) <= 0 - continue - end - end - for i in 1:100 - sigma1 = sigma + E(_my_rand(K))*omega - if sigma1 == 0 - continue - end - tau, determ, tauE = _symmetry(G, s, sigma1) - if gcd(denominator(tau), order(qL)) != 1 - continue - end - taubar = OqL(tau) - taubar = Oqf(taubar) - if taubar notin S - push!(gens, taubar) - push!(dets, determ) - S = sub(Oqf, gens) - end - end - end - Lhn = hermitian_lattice(E, (1//ord)*G) + return centralizer(OqL, fqL) end + diff --git a/src/NumberTheory/LatticesWithIsometry/Types.jl b/src/NumberTheory/LatticesWithIsometry/Types.jl index cca4db9e2706..a431916865e9 100644 --- a/src/NumberTheory/LatticesWithIsometry/Types.jl +++ b/src/NumberTheory/LatticesWithIsometry/Types.jl @@ -1,43 +1,16 @@ -@attributes mutable struct LatticeWithIsometry{S, T} <: AbsLat{S} - Lb::AbsLat{S} - f::T - GL - Lh::HermLat - n::Int +export LatticeWithIsometry - function LatticeWithIsometry{S, T}(Lb::AbsLat{S}, f::T, GL, Lh::HermLat, n::Int) where {S, T} - z = new{S, T}(Lb, f, GL, Lh, n) - return z - end +@attributes mutable struct LatticeWithIsometry + Lb::ZLat + f::fmpq_mat + n::Integer - function LatticeWithIsometry{S, T}(Lb::AbsLat{S}, f::T, n::Int) where {S, T} - z = new{S, T}() + function LatticeWithIsometry(Lb::ZLat, f::fmpq_mat, n::Integer) + z = new() z.Lb = Lb z.f = f z.n = n - Lh = Hecke.inverse_trace_lattice(Lb, f) - z.Lh = Lh return z end end -lattice(Lf::LatticeWithIsometry) = Lf.Lb - -isometry(Lf::LatticeWithIsometry) = Lf.f - -hermitian_structure(Lf::LatticeWithIsometry) = Lf.Lh - -rank(Lf::LatticeWithIsometry) = rank(Lf.Lb) - -degree(Lf::LatticeWithIsometry) = degree(Lf.Lb) - -genus(Lf::LatticeWithIsometry) = genus(Lf.Lb) - -minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf)) - -charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf)) - -image_centralizer_in_Oq(Lf::LatticeWithIsometry) = Lf.GL - -order_isometry(Lf::LatticeWithIsometry) = Lf.n - diff --git a/src/Oscar.jl b/src/Oscar.jl index 43546f011df4..d47d5b31570e 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -379,9 +379,7 @@ include("Modules/module-localizations.jl") include("Geometry/basics.jl") include("NumberTheory/NmbThy.jl") -include("NumberTheory/LatticesWithIsometry/TraceEquivalence.jl") -include("NumberTheory/LatticesWithIsometry/Types.jl") -include("NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl") +include("NumberTheory/LWI.jl") include("PolyhedralGeometry/main.jl") From fb6378b8f0cf2d4c2e8220be46bd788da3b0b0c2 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 30 Aug 2022 17:13:08 +0200 Subject: [PATCH 03/76] some more --- src/NumberTheory/LatticesWithIsometry/Enumeration.jl | 8 +++++--- .../LatticesWithIsometry/LatticesWithIsometry.jl | 9 ++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 83d7329c994e..ab41d3818e69 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -241,10 +241,12 @@ end admissible_triples(L::ZLat, p::Integer) = admissible_triple(genus(L), p) -function primitive_extensions(A::ZLat, B::ZLat, C::ZLat, p::Integer) +function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer) + A, B, C = lattice.([Afa, Bfb, Cfc]) @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" L = [] - g = valuation(divexact(det(A)*det(B), det(C)), p) + g = div(valuation(divexact(det(A)*det(B), det(C)), p),2) + fA, fB, fC = isometry.([Afa, Bfb, Cfc]) + mua, mub = minpoly.([fA, fB]) - end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index feaed22cf2b9..f1a1380b01b7 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -166,7 +166,7 @@ field, where $n$ is the order of `f`. If it exists, the hermitian structure is cached. """ -@attr function hermitian_structure(Lf::LatticeWithIsometry) +@attr HermLat function hermitian_structure(Lf::LatticeWithIsometry) @req rank(Lf) > 0 "Lf must be of positive rank" f = isometry(Lf) n = order_of_isometry(Lf) @@ -296,11 +296,14 @@ end divs = divisors(n) Qx = Hecke.Globals.Qx x = gen(Qx) - t = Dict{Integer, Tuple{LatticeWithIsometry, LatticeWithIsometry}}() + t = Dict{Integer, Tuple}() for l in divs Hl = kernel_lattice(Lf, Oscar._cyclotomic_polynomial(l)) + if !(order_of_isometry(Hl) in [-1,1,2]) + Hl = inverse_trace_lattice(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false) + end Al = kernel_lattice(Lf, x^l-1) - t[l] = (Hl, Al) + t[l] = (genus(Hl), genus(Al)) end return t end From 6ad38198ba34f5174688284c39ec76856ea9f81d Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 11 Oct 2022 14:10:25 +0200 Subject: [PATCH 04/76] temporary --- .../LatticesWithIsometry/Enumeration.jl | 112 +++++++-- .../LatticesWithIsometry.jl | 38 +++ src/NumberTheory/LatticesWithIsometry/Misc.jl | 233 +++++++++++++++++- .../LatticesWithIsometry/TraceEquivalence.jl | 14 -- 4 files changed, 367 insertions(+), 30 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index ab41d3818e69..d615813db7c6 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -135,16 +135,16 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) b1 = sum(Int64[s[2] for s in Bp if s[1] == 1]) b2 = sum(Int64[s[2] for s in Bp if s[1] >= 2]) - if l != 0 - ker_min = max(g-a1, g-b1, a_max) - else - ker_min = max(g-a1, g-b1) - end - ker_max = min(a2+div(a1,2), b2+div(b1,2), g) + #if l != 0 + # ker_min = max(g-a1, g-b1, a_max) + #else + # ker_min = max(g-a1, g-b1) + #end + #ker_max = min(a2+div(a1,2), b2+div(b1,2), g) - if ker_max < ker_min - return false - end + #if ker_max < ker_min + # return false + #end ABp = symbol(local_symbol(AperpB, p)) Cp = symbol(local_symbol(C, p)) @@ -238,15 +238,97 @@ function admissible_triples(G::ZGenus, p::Int64) return L end -admissible_triples(L::ZLat, p::Integer) = admissible_triple(genus(L), p) - +admissible_triples(L::ZLat, p::Integer) = admissible_triples(genus(L), p) function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer) + @req is_prime(p) "p must be a prime number" A, B, C = lattice.([Afa, Bfb, Cfc]) @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" - L = [] - g = div(valuation(divexact(det(A)*det(B), det(C)), p),2) - fA, fB, fC = isometry.([Afa, Bfb, Cfc]) - mua, mub = minpoly.([fA, fB]) + results = LatticeWithIsometry[] + g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) + fA, fB = isometry.([Afa, Bfb]) + nB = order_of_isometry(Bfb) + qA, fqA = discriminant_group(Afa) + qB, fqB = discriminant_group(Bfb) + GA = image_centralizer_in_Oq(Afa) + GB = image_centralizer_in_Oq(Bfb) + _VA = intersect(lattice_in_same_ambient_space(A, inv(fA^nB - fA^0)), dual(A)) + VA, VAinqA = sub(qA, [qA(vec(collect(basis_matrix(_VA)[j,:]))) for j in 1:nrows(basis_matrix(_VA))]) + VB, VBinqB = sub(qB, [g*divexact(order(g), p) for g in gens(primary_part(qB, p))]) + + if min(order(VA), order(VB)) < g + return results + end + + l = level(genus(C)) + H10 = rescale(qA, l) + @assert is_normal(VA, H10) && issubset(H10, VA) + HA, VAtoHA = quo(VA, H10) + H20 = rescale(qB, l) + @assert is_normal(VB, H20) && issubset(H20, VB) + HB, VBtoHB = quo(VB, H20) + + D, qAinD, qBinD = orthogonal_sum(qA, qB) + OD = orthogonal_group(D) + prim_e:wqxt = Tuple{TorQuadMod, AutomorphismGroup}[] + OqAinOD, OqBinOD = _embedding_orthogonal_group(qAinD, qBinD) + + if g == 0 + geneA = OqAinOD.(gens(GA)) + geneB = OqBinOD.(gens(GB)) + gene = vcat(geneA, geneB) + push!(prim_ext, (sub(D, elem_type(D)[])[1], sub(OD, gene)[1])) + @goto exit + end + + if (order(VA) == 1) || (order(VB) == 1) + @goto exit + end + + glue_order = p^g + subsA = _subgroups_representatives(HA, GA, glue_order, g = fqA) + subsA = [(VAtoHA\s[1], s[2]) for s in subsA] + subsB = _subgroups_representatives(HB, GB, glue_order, g = fqB) + subsB = [(VBtoHB\s[1], s[2]) for s in subsB] + for (SA, stabSA) in subsA + NSA, SAtoNSA = normal_form(SA) + OSA = orthogonal_sum(SA) + actSA = hom(stabSA, OSA, [OSA(lift(x)) for x in gens(stabSA)]) + imSA = image(actSA, stabSA)[1] + kerSA = image(OqAinOD, kernel(actSA)) + fSA = OSA(fqA) + for (SB, stabSB) in subsB + bool, phi = is_anti_isometric_with_anti_isometry(SA, SB) + bool || continue + NSB, NSBtoSB = normal_form(SB) + OSB = orthogonal_group(SB) + actSB = hom(stabSB, OSB, [OSB(lift(x)) for x in gens(stabSB)]) + imSB = image(actSB, stabSB)[1] + kerSB = image(OqBinOD, kernel(actSB)) + fSB = OSB(fqB) + fSAinOSB = OSB(compose(inv(phi), compose(fSA, phi))) + bool, g0 = representative_action(OSB, fSAinOSB, fSB) + bool || continue + phi = compose(phi, g0) + fSAinOSB = OSB(compose(inv(phi), compose(fSA, phi))) + @assert fSAinOSB == fSB + center = centralizer(OSB, fSB) + stabSAphi = [OSB(compose(inv(phi), compose(g, phi))) for g in gens(stabSA)] + stabSAphi = sub(center, stabSAphi)[1] + stabSB = sub(center, gens(stabSB))[1] + reps = double_cosets(center, stabSAphi, stabSB) + for g in reps + g = representative(g) + phig = compose(phi, g) + g0g = OSB(compose(g0, g)) + gene = [qAtoD(SAtoNSA\NSA[i]) + qBtoD(g0g(SBtoNSB\NSB[i])) for i in 1:ngens(NSA)] + ext = sub(D, gene)[1] + perp = orthogonal_submodule(D, ext)[1] + + + + + + @label exit end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index f1a1380b01b7..4cbbf416cdc3 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -81,6 +81,7 @@ For now, in order for the genus to exist, the lattice must be integral. """ genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus(L) : error("Underlying lattice must be integral"); end +ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf)) ############################################################################### # @@ -198,6 +199,31 @@ function discriminant_group(Lf::LatticeWithIsometry) return q, Oq(gens(matrix_group(f))[1]) end +@attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) + n = order_of_isometry(Lf) + L = lattice(Lf) + f = isometry(Lf) + @req is_integral(L) "Underlying lattice must be integral" + if n in [1,2] + return image_in_Oq(L) + elseif is_definite(L) + OL = orthogonal_group(L) + f = G(f) + UL = [OL(s.X) for s in gens(centralizer(OL, f)[1])] + OqL = orthogonal_group(discriminant_group(L)) + return sub(OqL, [OqL(g) for f in UL])[1] + elseif rank(L) == euler_phi(n) + gene = matrix_group([-f^0, f]) + OqL = orthogonal_group(discriminant_group(L)) + return sub(OqL, [OqL(g.X) for g in gens(gene)])[1] + else + qL, fqL = discriminant_group(Lf) + OqL = orthogonal_group(qL) + CdL, _ = centralizer(OqL, fqL) + return sub(OqL, [OqL(s.X) for s in CdL])[1] + end +end + ############################################################################### # # Signatures @@ -283,6 +309,18 @@ function kernel_lattice(Lf::LatticeWithIsometry, l::Integer) return kernel_lattice(Lf, p) end +invariant_lattice(Lf::LatticeWithIsometry) = kernel_lattice(Lf, 1) + +function coinvariant_lattice(Lf::LatticeWithIsometry) + chi = minpoly(Lf) + if chi(1) == 0 + R = parent(chi) + x = gen(R) + chi = divexact(chi, x-1) + end + return kernel_lattice(Lf, chi) +end + ############################################################################### # # Type diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 4df6ef8821b6..dbdbe9ed070a 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -1,3 +1,4 @@ +GG = GAP.Globals ############################################################################### # @@ -44,5 +45,235 @@ function _exponent(f::Union{fmpq_mat, fmpz_mat}) divmd = divisors(maxdeg) n = findfirst(k -> isone(f^k), divmd) @assert n !== nothing - return return divmd[n] + return divmd[n] end + +############################################################################## +# +# Group functionalities +# +##############################################################################:q + +function _embedding_orthogonal_group(i1, i2) + D = codomain(i1) + A = domain(i1) + B = domain(i2) + gene = vcat(i1.(gens(A)), i2.gens(B)) + n = ngens(A) + OD, OA, OB = orthogonal_group.([D, A, B]) + + geneOA = elem_type(OD)[] + for f in gens(OA) + imgs = [i1(f(a)) for a in gens(A)] + imgs = vcat(imgs, gene[n+1:end]) + _f = hom(lift.(gene), lift.(imgs)) + f = TorQuadModMor(D, D, _f) + push!(geneOA, f) + end + geneOB = elem_type(OD)[] + for f in gens(OB) + imgs = [i2(f(a)) for a in gens(B)] + imgs = vcat(imgs, gene[1:n]) + _f = hom(lift.(gene), lift.(imgs)) + f = TorQuadModMor(D, D, _f) + push!(geneOB, f) + end + + OAtoOD = hom(OA, OD, gens(OA), geneOA) + OBtoOD = hom(OB, OD, gens(OB), geneOB) + return OAtoOD, OBtoOD + end + +function __orbits_and_stabilizers_elementary_abelian_subgroups(G, aut, gens_aut, gens_act, min_order, max_order) + Gap = G.X + pcgs = GG.Pcgs(Ggap) + p = GG.Order(pcgs[1]) + if p == 2 + act = GG.OnSubspacesByCanonicalBasisGF2 + else + act = GG.OnSubspacesByCanonicalBasis + end + F = GF(p) + Fgap = GG.GF(p) + function matrix_representation(f, pcgs) + return [[GG.ExponentsOfPcElements(pcgs, GG.Image(f,i))[1]*GG.One(Fgap)] for i in pcgs] + end + + gens_mats = GAP.julia_to_gap([matrix_representation(f, pcgs) for f in gens_act], recursive=true) + + n = length(pcgs) + V = VectorSpace(F, n) + valmin = valuation(min_order, p) + valmax = valuation(max_order, p) + valmax = max(valmax, n) + + if minval == 0 + subs = [(sub(G, elem_type(G)[])[1], aut)] + minval = 1 + else + subs = [] + end + + for k in valmin:valmax + it = Hecke.subsets(n, k) + b = basis(V) + _subs = GAP.julia_to_gap([GG.LinearCombination(b, GAP.julia_to_gap(vec(collect(lift.(b[l].v))), recursive=true)) for l in it], recursive=true) + orbs = GG.OrbitsDomain(aut, _subs, gens_aut, gens_mats, act) + + for orb in orbs + orb = orb[1] + stab = GG.Stabilizer(aut, orb, gens_aut, gens_mats, act) + gene = [GG.PcElementByExponents(pcgs, i) for i in orb] + gene = G.(gene) + subgrp = sub(G, gene)[1] + push!(subs, (subgrp, stab)) + end + end + return subs +end + +function __orbits_and_stabilizers_elementary_abelian_subgroups_equivariant(G, aut, gens_aut, gens_act, min_order, max_order, g) + @assert g.X in GG.Center(aut.X) + + Gap = G.X + pcgs = GG.Pcgs(Ggap) + p = GG.Order(pcgs[1]) + if p == 2 + act = GG.OnSubspacesByCanonicalBasisGF2 + else + act = GG.OnSubspacesByCanonicalBasis + end + F = GF(p) + Fgap = GG.GF(p) + function matrix_representation(f, pcgs) + return [[GG.ExponentsOfPcElements(pcgs, GG.Image(f,i))[1]*GG.One(Fgap)] for i in pcgs] + end + + gens_mats = GAP.julia_to_gap([GAPmatrix_representation(f, pcgs) for f in gens_act], recursive=true) + + n = length(pcgs) + V = VectorSpace(F, n) + valmin = valuation(min_order, p) + valmax = valuation(max_order, p) + valmax = max(valmax, n) + + if minval == 0 + subs = [(sub(G, elem_type(G)[])[1], aut)] + minval = 1 + else + subs = [] + end + + for k in valmin:valmax + it = Hecke.subsets(n, k) + b = basis(V) + _subs = [GG.LinearCombination(b, GAP.julia_to_gap(vec(collect(lift.(b[l].v))), recursive=true)) for l in it] + _subs = GAP.julia_to_gap(filter!(i -> act(i, g) == i, _subs), recursive=true) + orbs = GG.OrbitsDomain(aut, _subs, gens_aut, gens_mats, act) + + for orb in orbs + orb = orb[1] + stab = GG.Stabilizer(aut, orb, gens_aut, gens_mats, act) + gene = [GG.PcElementByExponents(pcgs, i) for i in orb] + gene = G.(gene) + subgrp = sub(G, gene)[1] + push!(subs, (subgrp, stab)) + end + end + return subs +end + +function __subgroup_representatives1(epi, aut, gens_aut, gens_act, max_order) + G0 = domain(epi) + G1 = codomain(epi) + N = GG.InvariantElementaryAbelianSeries(G1.X, gens_act, G1.X, true) + N = N[end-1] + invs = GG.InvariantSubgroupsElementaryAbelianGroup(N, gens_act) + filter!(i -> GG.IsNonTrivial(i), invs) + m = minimum([GG.Size(i) for i in invs]) + for N in invs + if GG.Size(N) == m + break + end + end + subgrp_reps = __orbits_and_stabilizers_elementary_abelian_subgroups(N, aut, gens_aut, gens_act, 1, max_order) + + if GG.Size(N) == order(G1) + return subgrp_reps + end + + epi_mod = GG.NaturalHomomorphismByNormalSubgroupNC(G1.X, N) + GmodN = GG.Image(epi_mod) + aut_on_GmodN = GAP.julia_to_gap([GG.InducedAutomorphism(epi_mod, i) for i in gens_act], recursive=true) + epi_new = GG.CompositionMapping(epi_mod, epi) + Alist = __subgroup_representatives1(epi_new, aut, gens_aut, aut_on_GmodN, max_order) + for j in 1:length(Alist) + A = Alist[j] + repr = GG.PreImage(epi_mod, A[1].X) + repr = Oscar._oscar_group(repr, A[1]) + Alist[j] = (repr, A[1]) + end + @assert order(Alist[1][1]) == GG.Size(N) + popfirst!(Alist) + subgrp_reps = vcat(subgrp_reps, Alist) + + for A in Alist + stab = A[2] + A = A[1] + act = GAP.julia_to_gap([GG.InducedAutomorphism(epi, i) for i in GG.GeneratorsOfGroup(stab)], recursive=true) + Blist_A = __orbits_and_stabilizers_elementary_abelian_subgroups(N, stab, GG.GeneratorsOfGroup(stab), act, 1, max_order) + if order(Blist_A[end]) == order(N) + pop!(Blist_A) + end + for B in Blist_A + C_AB = B[2] + B = B[1] + epiB = GG.NaturalHomomorphismByNormalSubgroupNC(A.X, B.X) + AmodB = GG.Image(epiB) + NmodB = GG.Image(epiB, N.X) + UmodBList = GG.ComplementClassesRepresentatives(AmodB, NmodB) + gens_C_AB = GG.GeneratorsOfGroup(C_AB) + act_C_AB = GAP.julia_to_gap([GG.InducedAutomorphism(epi, i) for i in gens_C_AB], recursive=true) + act_C_AB = GAP.julia_to_gap([GG.InducedAutomorphism(epiB, i) for i in act_C_AB], recursive=true) + complement_reps = GG.ExternalOrbitsStabilisers(C_AB, UmodBList, gens_C_AB, act_C_AB, GAP.evalstr("function(x,g); return Image(g,x);end;")) + complement_reps = [(Oscar._oscar_group(GG.PreImage(epiB, GG.Representative(i)), A), GG.StabilizerOfExternalSet(i)) for i in complement_reps] + subgrp_reps = vcat(subgrp_reps, complement_reps) + end + end + sort!(subgrp_reps, s -> order(s[1])) + return subgrp_reps +end + +function __subgroup_representatives(G, aut, max_order) + gens_aut = GG.GeneratorsOfGroup(aut) + epi = GG.GroupHomomorphismByImages(G.X, G.X, GG.GeneratorsOfGroup(G.X), GG.GeneratorsOfGroup(G.X)) + return __subgroup_representatives1(epi, aut, gens_aut, gens_aut, max_order) +end + +function __subgroup_representatives_elementary(G, aut, order) + gens_aut = GG.GeneratorsOfGroup(aut) + return __orbits_and_stabilizers_elementary_abelian_subgroups(G, aut, gens_aut, gens_aut, order, order) +end + +function __subgroup_representatives_elementary_equivariant(G, aut, order, g) + gens_aut = GG.GeneratorsOfGroup(aut) + return __orbits_and_stabilizers_elementary_abelian_subgroups_equivariant(G, aut, gens_aut, gens_aut, order, order, g) +end + +function _subgroups_representatives(H, G, order, g = 1) + g = G(g) + if order(H) == 1 + return [(H, G.X)] + end + + if !is_prime(elementary_divisors(H)[1]) + subgrp_reps = __subgroup_representatives(H, G.X, order) + subgrp_reps = [S for S in subgrp_reps if order(S[1]) == order] + elseif order(H) < ZZ(2^8) + subgrp_reps = __subgroup_representatives_elementary_equivariant(H, G.X, order, g.X) + subgrp_reps = [S for S in subgrp_reps if order(S[1]) == order] + else + a=1 + end + end + diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index cd30ee4dac69..66a6941b6681 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -115,17 +115,3 @@ function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Boo return Lh end -function image_centralizer_in_Oq(L::ZLat, f::fmpq_mat; check::Bool = true) - if check - @req is_integral(L) "Lattice L must be integral" - @req Oscar._order(f) >0 "f is not of finite exponent" - @req f*gram_matrix(L)*transpose(f) == gram_matrix(L) "f does not define an isometry of L" - end - - qL = discriminant_group(L) - OqL = orthogonal_group(qL) - MGf = matrix_group([f]) - fqL = OqL(gens(MGf)[1]) - return centralizer(OqL, fqL) -end - From 7ef9b9c4ea750ab22d4a3d358f224b580ad2a01b Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 17 Oct 2022 10:37:40 +0200 Subject: [PATCH 05/76] temporary --- .../LatticesWithIsometry/Enumeration.jl | 51 ++++++-------- src/NumberTheory/LatticesWithIsometry/Misc.jl | 66 +++++++++++-------- 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index d615813db7c6..20adecb80ab5 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -17,16 +17,16 @@ end # discriminant for the genera A and B to glue to fit in C. d is # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. -function _find_D(d::fmpz, m::Integer, p::Integer) - @req is_prime(p) "p must be a prime number" - @req d != 0 "The discriminant of a non-degenerate form is non-zero" +function _find_D(d::fmpz, m::Int, p::Int) + @assert is_prime(p) + @assert d != 0 # If m == 0, there are no conditions on the gcd of d1 and dp if m == 0 return _tuples_divisors(d) end - D = Tuple{Int64,Int64}[] + D = Tuple{Int,Int}[] # We try all the values of g possible, from 1 to p^m for j=0:m g = p^j @@ -43,7 +43,7 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Integer, d, s::fmpz, l::fmpz, p::Integer, even = true) +function _find_L(r::Int, d, s::fmpz, l::fmpz, p::Int, even = true) L = ZGenus[] for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] gen = genera((s1,s2), d, even=even) @@ -109,17 +109,15 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) # At this point, if C is unimodular at p, the glueing condition is equivalent to have # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B if !divides(det(C), p)[1] - qA = gram_matrix_quadratic(normal_form(primary_part(discriminant_group(A), p)[1])[1]) - qB = gram_matrix_quadratic(normal_form(rescale(primary_part(discriminant_group(B), p)[1], -1))[1]) - return qA == qB + return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1]) end l = valuation(level(C), p) Ap = symbol(local_symbol(A, p)) - Bp = symbol(local_symbol(B,p)) - a_max = sum(Int64[s[2] for s in Ap if s[1] == l+1]) - b_max = sum(Int64[s[2] for s in Bp if s[1] == l+1]) - # For the glueing, rho_{l+1}(A) and rho_{l+1}(B) are anti-isometric, so they must have the + Bp = symbol(local_symbol(B, p)) + a_max = sum(Int[s[2] for s in Ap if s[1] == l+1]) + b_max = sum(Int[s[2] for s in Bp if s[1] == l+1]) + # For the glueing, rho_{l+1}(A_p) and rho_{l+1}(B_p) are anti-isometric, so they must have the # same order if a_max != b_max return false @@ -130,21 +128,10 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) return false end - a1 = sum(Int64[s[2] for s in Ap if s[1] == 1]) - a2 = sum(Int64[s[2] for s in Ap if s[1] >= 2]) - b1 = sum(Int64[s[2] for s in Bp if s[1] == 1]) - b2 = sum(Int64[s[2] for s in Bp if s[1] >= 2]) - - #if l != 0 - # ker_min = max(g-a1, g-b1, a_max) - #else - # ker_min = max(g-a1, g-b1) - #end - #ker_max = min(a2+div(a1,2), b2+div(b1,2), g) - - #if ker_max < ker_min - # return false - #end + a1 = sum(Int[s[2] for s in Ap if s[1] == 1]) + a2 = sum(Int[s[2] for s in Ap if s[1] >= 2]) + b1 = sum(Int[s[2] for s in Bp if s[1] == 1]) + b2 = sum(Int[s[2] for s in Bp if s[1] >= 2]) ABp = symbol(local_symbol(AperpB, p)) Cp = symbol(local_symbol(C, p)) @@ -192,7 +179,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) end Cp = ZpGenus(p, Cp) - if !represented(local_symbol(AperpB, p), Cp) + if !represents(local_symbol(AperpB, p), Cp) return false end if !represents(C, AperpB) @@ -247,14 +234,14 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry results = LatticeWithIsometry[] g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) fA, fB = isometry.([Afa, Bfb]) - nB = order_of_isometry(Bfb) + nA = order_of_isometry(Afa) qA, fqA = discriminant_group(Afa) qB, fqB = discriminant_group(Bfb) GA = image_centralizer_in_Oq(Afa) GB = image_centralizer_in_Oq(Bfb) - _VA = intersect(lattice_in_same_ambient_space(A, inv(fA^nB - fA^0)), dual(A)) - VA, VAinqA = sub(qA, [qA(vec(collect(basis_matrix(_VA)[j,:]))) for j in 1:nrows(basis_matrix(_VA))]) - VB, VBinqB = sub(qB, [g*divexact(order(g), p) for g in gens(primary_part(qB, p))]) + VA, VAinqA = sub(qA, [g*divexact(order(g), p) for g in gens(primary_part(qA, p))]) + _VB = intersect(lattice_in_same_ambient_space(B, inv(fB^nA - fB^0)), dual(B)) + VB, VBinqB = sub(qB, [qB(vec(collect(basis_matrix(_VB)[j,:]))) for j in 1:nrows(basis_matrix(_VB))]) if min(order(VA), order(VB)) < g return results diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index dbdbe9ed070a..8732ccdf71d4 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -48,41 +48,49 @@ function _exponent(f::Union{fmpq_mat, fmpz_mat}) return divmd[n] end +function _image(p::fmpz_poly, f::TorQuadModMor) + M = f.map_ab.map + M = p(M) + fab = hom(domain(f.map_ab), codomain(f.map_ab), M) + pf = hom(domain(f), codomain(f), fab) + return pf +end + ############################################################################## # # Group functionalities # -##############################################################################:q +############################################################################## -function _embedding_orthogonal_group(i1, i2) - D = codomain(i1) - A = domain(i1) - B = domain(i2) - gene = vcat(i1.(gens(A)), i2.gens(B)) - n = ngens(A) - OD, OA, OB = orthogonal_group.([D, A, B]) - - geneOA = elem_type(OD)[] - for f in gens(OA) - imgs = [i1(f(a)) for a in gens(A)] - imgs = vcat(imgs, gene[n+1:end]) - _f = hom(lift.(gene), lift.(imgs)) - f = TorQuadModMor(D, D, _f) - push!(geneOA, f) - end - geneOB = elem_type(OD)[] - for f in gens(OB) - imgs = [i2(f(a)) for a in gens(B)] - imgs = vcat(imgs, gene[1:n]) - _f = hom(lift.(gene), lift.(imgs)) +function embedding_orthogonal_group(i1, i2) + D = codomain(i1) + A = domain(i1) + B = codomain(i2) + gene = vcat(i1.(gens(A)), i2.(gens(B))) + n = ngens(A) + OD, OA, OB = orthogonal_group.([D, A, B]) + + geneOA = elem_type(OD)[] + for f in gens(OA) + imgs = [i1(f(a)) for a in gens(A)] + imgs = vcat(imgs, gene[n+1:end]) + _f = hom(lift.(gene), lift.(imgs)) f = TorQuadModMor(D, D, _f) - push!(geneOB, f) - end - - OAtoOD = hom(OA, OD, gens(OA), geneOA) - OBtoOD = hom(OB, OD, gens(OB), geneOB) - return OAtoOD, OBtoOD - end + push!(geneOA, f) + end + geneOB = elem_type(OD)[] + for f in gens(OB) + imgs = [i2(f(a)) for a in gens(B)] + imgs = vcat(gene[1:n], imgs) + _f = hom(lift.(gene), lift.(imgs)) + f = TorQuadModMor(D, D, _f) + push!(geneOB, f) + end + + OAtoOD = hom(OA, OD, gens(OA), geneOA) + OBtoOD = hom(OB, OD, gens(OB), geneOB) + return OAtoOD, OBtoOD + end function __orbits_and_stabilizers_elementary_abelian_subgroups(G, aut, gens_aut, gens_act, min_order, max_order) Gap = G.X From 2e9fc2ce794fb900abe14d420833571e0a6aee46 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 18 Oct 2022 15:11:02 +0200 Subject: [PATCH 06/76] temporary --- src/NumberTheory/LWI.jl | 1 + .../LatticesWithIsometry/Enumeration.jl | 78 +++-- .../LatticesWithIsometry.jl | 6 +- src/NumberTheory/LatticesWithIsometry/Misc.jl | 288 +++++++----------- 4 files changed, 160 insertions(+), 213 deletions(-) diff --git a/src/NumberTheory/LWI.jl b/src/NumberTheory/LWI.jl index 9c23f70bd240..b8b80764cafe 100644 --- a/src/NumberTheory/LWI.jl +++ b/src/NumberTheory/LWI.jl @@ -2,3 +2,4 @@ include("LatticesWithIsometry/Misc.jl") include("LatticesWithIsometry/Types.jl") include("LatticesWithIsometry/TraceEquivalence.jl") include("LatticesWithIsometry/LatticesWithIsometry.jl") +include("LatticesWithIsometry/Enumeration.jl") diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 20adecb80ab5..2baa91cb23c4 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -1,4 +1,4 @@ -export admissible_triples, is_admissble_triple +export admissible_triples, is_admissible_triple ################################################################################## # @@ -227,38 +227,37 @@ end admissible_triples(L::ZLat, p::Integer) = admissible_triples(genus(L), p) +function _get_V(q, fq, mu, p) + pq, pqinq = primary_part(q, p) + pq, pqinq = sub(q, pqinq.([g*divexact(order(g), p) for g in gens(pq)])) + fpq = _image(mu, _restrict(fq, pqinq)) + ker, kerinpq = kernel(fpq.map_ab) + V, Vinq = sub(q, [pqinq(pq(kerinpq(a))) for a in gens(ker)]) + fV = _restrict(fq, Vinq) + return V, Vinq, FV +end + function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer) @req is_prime(p) "p must be a prime number" + A, B, C = lattice.([Afa, Bfb, Cfc]) + @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" + results = LatticeWithIsometry[] + g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) + fA, fB = isometry.([Afa, Bfb]) - nA = order_of_isometry(Afa) qA, fqA = discriminant_group(Afa) qB, fqB = discriminant_group(Bfb) GA = image_centralizer_in_Oq(Afa) GB = image_centralizer_in_Oq(Bfb) - VA, VAinqA = sub(qA, [g*divexact(order(g), p) for g in gens(primary_part(qA, p))]) - _VB = intersect(lattice_in_same_ambient_space(B, inv(fB^nA - fB^0)), dual(B)) - VB, VBinqB = sub(qB, [qB(vec(collect(basis_matrix(_VB)[j,:]))) for j in 1:nrows(basis_matrix(_VB))]) - - if min(order(VA), order(VB)) < g - return results - end - l = level(genus(C)) - H10 = rescale(qA, l) - @assert is_normal(VA, H10) && issubset(H10, VA) - HA, VAtoHA = quo(VA, H10) - H20 = rescale(qB, l) - @assert is_normal(VB, H20) && issubset(H20, VB) - HB, VBtoHB = quo(VB, H20) - D, qAinD, qBinD = orthogonal_sum(qA, qB) OD = orthogonal_group(D) - prim_e:wqxt = Tuple{TorQuadMod, AutomorphismGroup}[] - OqAinOD, OqBinOD = _embedding_orthogonal_group(qAinD, qBinD) + prim_ext = Tuple{TorQuadMod, AutomorphismGroup}[] + OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) if g == 0 geneA = OqAinOD.(gens(GA)) @@ -268,20 +267,34 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry @goto exit end - if (order(VA) == 1) || (order(VB) == 1) - @goto exit + VA, VAinqA, fVA = _get_V(qA, fqA, minpoly(Bfb), p) + gene_GVA = [_restrict(g, VAinqA) for g in gens(GA)] + GVA = sub(orthogonal_group(VA), gene_GVA) + @assert fVA in GVA + GVAinGA = hom(GVA, GA, gens(GVA), gens(GA)) + VB, VBinqB, fVB = _get_V(qB, fqB, minpoly(Afa), p) + gene_GVB = [_restrict(g, VBinqB) for g in gens(GB)] + GVB = sub(orthogonal_group(VB), gene_GVB) + @assert fVB in GVB + GVBinGB = hom(GVB, GB, gens(GVB), gens(GB)) + if min(order(VA), order(VB)) < p^g + return results end - glue_order = p^g - subsA = _subgroups_representatives(HA, GA, glue_order, g = fqA) - subsA = [(VAtoHA\s[1], s[2]) for s in subsA] - subsB = _subgroups_representatives(HB, GB, glue_order, g = fqB) - subsB = [(VBtoHB\s[1], s[2]) for s in subsB] + l = level(genus(G)) - for (SA, stabSA) in subsA - NSA, SAtoNSA = normal_form(SA) - OSA = orthogonal_sum(SA) - actSA = hom(stabSA, OSA, [OSA(lift(x)) for x in gens(stabSA)]) + subsA = _subgroups_representatives(VAinqA, GA, g, fVA, l) + subsB = _subgroups_representatives(VBinqB, GB, g, fVB, l) + + R = [(H1, H2) for H1 in subsA for H2 in subsB if is_anti_isometric_with_anti_isometry(H1[1], H2[1])[1]] + + for (H1, H2) in R + SA, stabA = H1 + SB, stabB = H2 + ok, phi = is_anti_isometric_with_anti_isometry(SA, SB) + @assert ok + OSA = orthogonal_group(SA) + actSA = hom(stabSA, OSA, [OSA(matrix(x)) for x in gens(stabSA)]) imSA = image(actSA, stabSA)[1] kerSA = image(OqAinOD, kernel(actSA)) fSA = OSA(fqA) @@ -312,8 +325,9 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry gene = [qAtoD(SAtoNSA\NSA[i]) + qBtoD(g0g(SBtoNSB\NSB[i])) for i in 1:ngens(NSA)] ext = sub(D, gene)[1] perp = orthogonal_submodule(D, ext)[1] - - + end + end + end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 4cbbf416cdc3..926e8abbd196 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -208,10 +208,10 @@ end return image_in_Oq(L) elseif is_definite(L) OL = orthogonal_group(L) - f = G(f) - UL = [OL(s.X) for s in gens(centralizer(OL, f)[1])] + f = OL(f) + UL = [OL(s) for s in gens(centralizer(OL, f)[1])] OqL = orthogonal_group(discriminant_group(L)) - return sub(OqL, [OqL(g) for f in UL])[1] + return sub(OqL, [OqL(g) for g in UL])[1] elseif rank(L) == euler_phi(n) gene = matrix_group([-f^0, f]) OqL = orthogonal_group(discriminant_group(L)) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 8732ccdf71d4..5334e5702c70 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -48,14 +48,37 @@ function _exponent(f::Union{fmpq_mat, fmpz_mat}) return divmd[n] end -function _image(p::fmpz_poly, f::TorQuadModMor) +function _image(p::Union{fmpz_poly, fmpq_poly}, f::TorQuadModMor) + p = change_base_ring(ZZ, p) M = f.map_ab.map M = p(M) - fab = hom(domain(f.map_ab), codomain(f.map_ab), M) - pf = hom(domain(f), codomain(f), fab) + pf = hom(domain(f), codomain(f), M) return pf end +function _image(p::Union{fmpz_poly, fmpz_poly}, f::AutomorphismGroupElem{TorQuadMod}) + D = domain(f) + M = matrix(f) + return _image(p, hom(D, D, M)) +end + +function _restrict(f::TorQuadModMor, i::TorQuadModMor) + imgs = TorQuadModElem[] + V = domain(i) + for g in gens(V) + h = f(i(g)) + hV = preimage(i, h) + push!(imgs, hV) + end + return hom(V, V, imgs) +end + +function _restrict(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) + D = domain(f) + M = matrix(f) + return _restrict(hom(D, D, M), i) +end + ############################################################################## # # Group functionalities @@ -92,196 +115,105 @@ function embedding_orthogonal_group(i1, i2) return OAtoOD, OBtoOD end -function __orbits_and_stabilizers_elementary_abelian_subgroups(G, aut, gens_aut, gens_act, min_order, max_order) - Gap = G.X - pcgs = GG.Pcgs(Ggap) - p = GG.Order(pcgs[1]) - if p == 2 - act = GG.OnSubspacesByCanonicalBasisGF2 - else - act = GG.OnSubspacesByCanonicalBasis +function _as_Fp_vector_space_quotient(HinV, p, f) + i = HinV.map_ab + H = domain(HinV) + Hab = domain(i) + Hs, HstoHab = snf(Hab) + f = _restrict(f, HinV) + V = codmain(HinV) + Vab = codomain(i) + Vs, VstoVab = snf(Vab) + + function _VtoVs(x::TorQuadModElem) + return inv(VstoVab)(data(x)) end - F = GF(p) - Fgap = GG.GF(p) - function matrix_representation(f, pcgs) - return [[GG.ExponentsOfPcElements(pcgs, GG.Image(f,i))[1]*GG.One(Fgap)] for i in pcgs] - end - - gens_mats = GAP.julia_to_gap([matrix_representation(f, pcgs) for f in gens_act], recursive=true) - - n = length(pcgs) - V = VectorSpace(F, n) - valmin = valuation(min_order, p) - valmax = valuation(max_order, p) - valmax = max(valmax, n) - if minval == 0 - subs = [(sub(G, elem_type(G)[])[1], aut)] - minval = 1 - else - subs = [] + function _VstoV(x::GrpAbFinGenElem) + return V(VstoVab(x)) end - for k in valmin:valmax - it = Hecke.subsets(n, k) - b = basis(V) - _subs = GAP.julia_to_gap([GG.LinearCombination(b, GAP.julia_to_gap(vec(collect(lift.(b[l].v))), recursive=true)) for l in it], recursive=true) - orbs = GG.OrbitsDomain(aut, _subs, gens_aut, gens_mats, act) + VtoVs = Hecke.MapFromFunc(_VtoVs, _VstoV, V, Vs) - for orb in orbs - orb = orb[1] - stab = GG.Stabilizer(aut, orb, gens_aut, gens_mats, act) - gene = [GG.PcElementByExponents(pcgs, i) for i in orb] - gene = G.(gene) - subgrp = sub(G, gene)[1] - push!(subs, (subgrp, stab)) - end - end - return subs -end - -function __orbits_and_stabilizers_elementary_abelian_subgroups_equivariant(G, aut, gens_aut, gens_act, min_order, max_order, g) - @assert g.X in GG.Center(aut.X) - - Gap = G.X - pcgs = GG.Pcgs(Ggap) - p = GG.Order(pcgs[1]) - if p == 2 - act = GG.OnSubspacesByCanonicalBasisGF2 - else - act = GG.OnSubspacesByCanonicalBasis - end + n = ngens(Vs) F = GF(p) - Fgap = GG.GF(p) - function matrix_representation(f, pcgs) - return [[GG.ExponentsOfPcElements(pcgs, GG.Image(f,i))[1]*GG.One(Fgap)] for i in pcgs] - end - - gens_mats = GAP.julia_to_gap([GAPmatrix_representation(f, pcgs) for f in gens_act], recursive=true) - - n = length(pcgs) - V = VectorSpace(F, n) - valmin = valuation(min_order, p) - valmax = valuation(max_order, p) - valmax = max(valmax, n) - - if minval == 0 - subs = [(sub(G, elem_type(G)[])[1], aut)] - minval = 1 - else - subs = [] - end - - for k in valmin:valmax - it = Hecke.subsets(n, k) - b = basis(V) - _subs = [GG.LinearCombination(b, GAP.julia_to_gap(vec(collect(lift.(b[l].v))), recursive=true)) for l in it] - _subs = GAP.julia_to_gap(filter!(i -> act(i, g) == i, _subs), recursive=true) - orbs = GG.OrbitsDomain(aut, _subs, gens_aut, gens_mats, act) + MVs = matrix(compose(inv(VstoVab), compose(f.map_ab, VstoVab))) + Vp = VectorSpace(F, n) - for orb in orbs - orb = orb[1] - stab = GG.Stabilizer(aut, orb, gens_aut, gens_mats, act) - gene = [GG.PcElementByExponents(pcgs, i) for i in orb] - gene = G.(gene) - subgrp = sub(G, gene)[1] - push!(subs, (subgrp, stab)) - end + function _VstoVp(x::GrpAbFinGenElem) + v = x.coeff + return Vp(vec(collect(v))) end - return subs -end -function __subgroup_representatives1(epi, aut, gens_aut, gens_act, max_order) - G0 = domain(epi) - G1 = codomain(epi) - N = GG.InvariantElementaryAbelianSeries(G1.X, gens_act, G1.X, true) - N = N[end-1] - invs = GG.InvariantSubgroupsElementaryAbelianGroup(N, gens_act) - filter!(i -> GG.IsNonTrivial(i), invs) - m = minimum([GG.Size(i) for i in invs]) - for N in invs - if GG.Size(N) == m - break - end - end - subgrp_reps = __orbits_and_stabilizers_elementary_abelian_subgroups(N, aut, gens_aut, gens_act, 1, max_order) - - if GG.Size(N) == order(G1) - return subgrp_reps + function _VptoVs(v::ModuleElem{gfp_elem}) + x = lift.(v.v) + return Vs(vec(collect(x))) end - epi_mod = GG.NaturalHomomorphismByNormalSubgroupNC(G1.X, N) - GmodN = GG.Image(epi_mod) - aut_on_GmodN = GAP.julia_to_gap([GG.InducedAutomorphism(epi_mod, i) for i in gens_act], recursive=true) - epi_new = GG.CompositionMapping(epi_mod, epi) - Alist = __subgroup_representatives1(epi_new, aut, gens_aut, aut_on_GmodN, max_order) - for j in 1:length(Alist) - A = Alist[j] - repr = GG.PreImage(epi_mod, A[1].X) - repr = Oscar._oscar_group(repr, A[1]) - Alist[j] = (repr, A[1]) - end - @assert order(Alist[1][1]) == GG.Size(N) - popfirst!(Alist) - subgrp_reps = vcat(subgrp_reps, Alist) + VstoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) + VtoVp = compose(VtoVs, VstoVp) + M = reduce(vcat, [(i(HstoHab(a))).coeff for a in gens(Hs)]) + subgene = [Vp(M*v.v) for v in gens(Vp)] + Hp, _ = sub(Vp, subgene) + Qp, VptoQp = quo(Vp, Hp) + fVp = change_base_ring(F, matrix(MVs)) + ok, fQp = can_solve_with_solution(transpose(VptoQp.matrix), transpose(Vp.toQp.matrix)*fVp) + @assert ok - for A in Alist - stab = A[2] - A = A[1] - act = GAP.julia_to_gap([GG.InducedAutomorphism(epi, i) for i in GG.GeneratorsOfGroup(stab)], recursive=true) - Blist_A = __orbits_and_stabilizers_elementary_abelian_subgroups(N, stab, GG.GeneratorsOfGroup(stab), act, 1, max_order) - if order(Blist_A[end]) == order(N) - pop!(Blist_A) - end - for B in Blist_A - C_AB = B[2] - B = B[1] - epiB = GG.NaturalHomomorphismByNormalSubgroupNC(A.X, B.X) - AmodB = GG.Image(epiB) - NmodB = GG.Image(epiB, N.X) - UmodBList = GG.ComplementClassesRepresentatives(AmodB, NmodB) - gens_C_AB = GG.GeneratorsOfGroup(C_AB) - act_C_AB = GAP.julia_to_gap([GG.InducedAutomorphism(epi, i) for i in gens_C_AB], recursive=true) - act_C_AB = GAP.julia_to_gap([GG.InducedAutomorphism(epiB, i) for i in act_C_AB], recursive=true) - complement_reps = GG.ExternalOrbitsStabilisers(C_AB, UmodBList, gens_C_AB, act_C_AB, GAP.evalstr("function(x,g); return Image(g,x);end;")) - complement_reps = [(Oscar._oscar_group(GG.PreImage(epiB, GG.Representative(i)), A), GG.StabilizerOfExternalSet(i)) for i in complement_reps] - subgrp_reps = vcat(subgrp_reps, complement_reps) - end - end - sort!(subgrp_reps, s -> order(s[1])) - return subgrp_reps -end - -function __subgroup_representatives(G, aut, max_order) - gens_aut = GG.GeneratorsOfGroup(aut) - epi = GG.GroupHomomorphismByImages(G.X, G.X, GG.GeneratorsOfGroup(G.X), GG.GeneratorsOfGroup(G.X)) - return __subgroup_representatives1(epi, aut, gens_aut, gens_aut, max_order) -end - -function __subgroup_representatives_elementary(G, aut, order) - gens_aut = GG.GeneratorsOfGroup(aut) - return __orbits_and_stabilizers_elementary_abelian_subgroups(G, aut, gens_aut, gens_aut, order, order) -end -function __subgroup_representatives_elementary_equivariant(G, aut, order, g) - gens_aut = GG.GeneratorsOfGroup(aut) - return __orbits_and_stabilizers_elementary_abelian_subgroups_equivariant(G, aut, gens_aut, gens_aut, order, order, g) + return Qp, VtoVp, VptoQp, fQp end -function _subgroups_representatives(H, G, order, g = 1) - g = G(g) - if order(H) == 1 - return [(H, G.X)] +function _subgroups_representatives(Vinq::TorQuadModMor, G, g, f = one(G), l::Int = 0) + V = domain(Vinq) + q = codomain(Vinq) + p = elementary_divisors(V)[1] + @req all(a -> haspreimage(Vtoq.map_ab, data(l*a))[1], gens(q)) "l*q is not contained in V" + H0, H0inV = sub(V, [preimage(Vtoq, (l*a)) for a in gens(q)]) + Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) + + gene_GV = [_restrict(g, Vinq) for g in gens(G)] + GV = sub(orthogonal_group(V), gene_GV) + @assert f in GV + GVinG = hom(GV, G, gens(GV), gens(G)) + + act_GV_Vp = [change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] + act_GV_Qp = [solve_left(transpose(VptoQp).matrix, transpose(VptoQp).matrix*g) for g in act_GV_Vp] + MGp = matrix_group(act_GV_Qp) + @assert fQp in MGp + MGptoGV = hom(MGp, GV, gens(GV)) + MGptoG = compose(MGptoGV, GVtoG) + + res = [] + if g-ngens(snf(abelian_group(H10))) > dim(Qp) + return res end - - if !is_prime(elementary_divisors(H)[1]) - subgrp_reps = __subgroup_representatives(H, G.X, order) - subgrp_reps = [S for S in subgrp_reps if order(S[1]) == order] - elseif order(H) < ZZ(2^8) - subgrp_reps = __subgroup_representatives_elementary_equivariant(H, G.X, order, g.X) - subgrp_reps = [S for S in subgrp_reps if order(S[1]) == order] - else - a=1 + F = base_ring(Qp) + k, K = kernel(VptoQp.matrix, side = :left) + gene_H0p = [Vp(vec(collect(K[i,:]))) for i in 1:k] + orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) + for (orb, stab) in orb_and_stab + i = orb.map + _V = codomain(i) + for v in gens(ord) + vv = _V(transpose(fQp*i(v))) + try + preimage(i, vv) + catch + @goto non_fixed + end + end + gene_orb_in_Qp = [Qp(vec(collect(i(v).v))) for v in gens(orb)] + gene_orb_in_Vp = [preimage(VptoQp, v) for v in gene_orb_in_Qp] + gene_submod_in_Vp = vcat(gene_ord_in_Vp, gene_H0p) + gene_submod_in_V = [preimage(VtoVp, v) for v in gene_submod_in_Vp] + gene_submod_in_q = [image(Vinq, v) for v in gene_submod_in_V] + orbq, _ = sub(q, gene_submod_in_q) + @assert order(orb) == p^g + stabq = image(MGptoG, stab) + push!(res, (orbq, stabq)) + @label non_fixed end - end + return res +end From a1e8e7122e5a8239f9ef9bf98405780568532796 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 19 Oct 2022 17:59:34 +0200 Subject: [PATCH 07/76] some more --- .../LatticesWithIsometry/Enumeration.jl | 15 ++--- .../LatticesWithIsometry.jl | 9 +-- src/NumberTheory/LatticesWithIsometry/Misc.jl | 66 ++++++++++--------- .../LatticesWithIsometry/TraceEquivalence.jl | 6 +- 4 files changed, 47 insertions(+), 49 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 2baa91cb23c4..58a47687164a 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -108,8 +108,10 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) # At this point, if C is unimodular at p, the glueing condition is equivalent to have # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B + qA = discriminant_group(A) + qB = discriminant_group(B) if !divides(det(C), p)[1] - return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1]) + return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1])[1] end l = valuation(level(C), p) @@ -234,7 +236,7 @@ function _get_V(q, fq, mu, p) ker, kerinpq = kernel(fpq.map_ab) V, Vinq = sub(q, [pqinq(pq(kerinpq(a))) for a in gens(ker)]) fV = _restrict(fq, Vinq) - return V, Vinq, FV + return V, Vinq, fV end function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer) @@ -268,15 +270,8 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry end VA, VAinqA, fVA = _get_V(qA, fqA, minpoly(Bfb), p) - gene_GVA = [_restrict(g, VAinqA) for g in gens(GA)] - GVA = sub(orthogonal_group(VA), gene_GVA) - @assert fVA in GVA - GVAinGA = hom(GVA, GA, gens(GVA), gens(GA)) VB, VBinqB, fVB = _get_V(qB, fqB, minpoly(Afa), p) - gene_GVB = [_restrict(g, VBinqB) for g in gens(GB)] - GVB = sub(orthogonal_group(VB), gene_GVB) - @assert fVB in GVB - GVBinGB = hom(GVB, GB, gens(GVB), gens(GB)) + if min(order(VA), order(VB)) < p^g return results end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 926e8abbd196..57d3c9c983d5 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -205,23 +205,24 @@ end f = isometry(Lf) @req is_integral(L) "Underlying lattice must be integral" if n in [1,2] - return image_in_Oq(L) + GL, _ = image_in_Oq(L) elseif is_definite(L) OL = orthogonal_group(L) f = OL(f) UL = [OL(s) for s in gens(centralizer(OL, f)[1])] OqL = orthogonal_group(discriminant_group(L)) - return sub(OqL, [OqL(g) for g in UL])[1] + GL, _ = sub(OqL, [OqL(g) for g in UL]) elseif rank(L) == euler_phi(n) gene = matrix_group([-f^0, f]) OqL = orthogonal_group(discriminant_group(L)) - return sub(OqL, [OqL(g.X) for g in gens(gene)])[1] + GL, _ = sub(OqL, [OqL(g.X) for g in gens(gene)]) else qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) CdL, _ = centralizer(OqL, fqL) - return sub(OqL, [OqL(s.X) for s in CdL])[1] + GL, _ = sub(OqL, [OqL(s.X) for s in CdL]) end + return GL end ############################################################################### diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 5334e5702c70..570291ec94a3 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -74,9 +74,7 @@ function _restrict(f::TorQuadModMor, i::TorQuadModMor) end function _restrict(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) - D = domain(f) - M = matrix(f) - return _restrict(hom(D, D, M), i) + return _restrict(hom(f), i) end ############################################################################## @@ -88,7 +86,7 @@ end function embedding_orthogonal_group(i1, i2) D = codomain(i1) A = domain(i1) - B = codomain(i2) + B = domain(i2) gene = vcat(i1.(gens(A)), i2.(gens(B))) n = ngens(A) OD, OA, OB = orthogonal_group.([D, A, B]) @@ -97,17 +95,19 @@ function embedding_orthogonal_group(i1, i2) for f in gens(OA) imgs = [i1(f(a)) for a in gens(A)] imgs = vcat(imgs, gene[n+1:end]) - _f = hom(lift.(gene), lift.(imgs)) + _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) + _f = hom(D.ab_grp, D.ab_grp, _f) f = TorQuadModMor(D, D, _f) - push!(geneOA, f) + push!(geneOA, OD(f)) end geneOB = elem_type(OD)[] for f in gens(OB) imgs = [i2(f(a)) for a in gens(B)] imgs = vcat(gene[1:n], imgs) - _f = hom(lift.(gene), lift.(imgs)) + _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) + _f = hom(D.ab_grp, D.ab_grp, _f) f = TorQuadModMor(D, D, _f) - push!(geneOB, f) + push!(geneOB, OD(f)) end OAtoOD = hom(OA, OD, gens(OA), geneOA) @@ -120,8 +120,7 @@ function _as_Fp_vector_space_quotient(HinV, p, f) H = domain(HinV) Hab = domain(i) Hs, HstoHab = snf(Hab) - f = _restrict(f, HinV) - V = codmain(HinV) + V = codomain(HinV) Vab = codomain(i) Vs, VstoVab = snf(Vab) @@ -153,61 +152,64 @@ function _as_Fp_vector_space_quotient(HinV, p, f) VstoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) VtoVp = compose(VtoVs, VstoVp) M = reduce(vcat, [(i(HstoHab(a))).coeff for a in gens(Hs)]) - subgene = [Vp(M*v.v) for v in gens(Vp)] + subgene = [Vp(v.v*M) for v in gens(Vp)] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) - fVp = change_base_ring(F, matrix(MVs)) - ok, fQp = can_solve_with_solution(transpose(VptoQp.matrix), transpose(Vp.toQp.matrix)*fVp) + fVp = change_base_ring(F, MVs) + ok, fQp = can_solve_with_solution(VptoQp.matrix, fVp*VptoQp.matrix) @assert ok return Qp, VtoVp, VptoQp, fQp end -function _subgroups_representatives(Vinq::TorQuadModMor, G, g, f = one(G), l::Int = 0) +function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, g::Int, f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), l::Union{Int, fmpz} = 0) V = domain(Vinq) q = codomain(Vinq) p = elementary_divisors(V)[1] - @req all(a -> haspreimage(Vtoq.map_ab, data(l*a))[1], gens(q)) "l*q is not contained in V" - H0, H0inV = sub(V, [preimage(Vtoq, (l*a)) for a in gens(q)]) + @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" + H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) - gene_GV = [_restrict(g, Vinq) for g in gens(G)] - GV = sub(orthogonal_group(V), gene_GV) - @assert f in GV + if dim(Qp) == 0 + return Tuple{TorQuadMod, AutomorphismGroup{TorQuadMod}}[(V, G)] + end + + gene_GV = TorQuadModMor[_restrict(g, Vinq) for g in gens(G)] + OV = orthogonal_group(V) + GV = sub(OV, OV.(gene_GV))[1] + @assert GV(f) in GV GVinG = hom(GV, G, gens(GV), gens(G)) - act_GV_Vp = [change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] - act_GV_Qp = [solve_left(transpose(VptoQp).matrix, transpose(VptoQp).matrix*g) for g in act_GV_Vp] + act_GV_Vp = MatElem{gfp_elem}[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] + act_GV_Qp = MatElem{gfp_elem}[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(act_GV_Qp) @assert fQp in MGp MGptoGV = hom(MGp, GV, gens(GV)) MGptoG = compose(MGptoGV, GVtoG) - res = [] + res = Tuple{TorQuadMod, AutomorphismGroup{TorQuadMod}}[] if g-ngens(snf(abelian_group(H10))) > dim(Qp) return res end F = base_ring(Qp) k, K = kernel(VptoQp.matrix, side = :left) - gene_H0p = [Vp(vec(collect(K[i,:]))) for i in 1:k] - orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) + gene_H0p = ModuleElem{gfp_elem}[Vp(vec(collect(K[i,:]))) for i in 1:k] + orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k)::Vector{Tuple{AbstractAlgebra.Generic.Submodule{gfp_elem}}, MatrixGroup{gfp_elem, gfp_mat}} for (orb, stab) in orb_and_stab i = orb.map _V = codomain(i) for v in gens(ord) - vv = _V(transpose(fQp*i(v))) - try - preimage(i, vv) - catch + vv = _V(i(v)*fQp) + if !can_solve_with_solution(i.matrix, vv.v, side = :left)[1] @goto non_fixed end end - gene_orb_in_Qp = [Qp(vec(collect(i(v).v))) for v in gens(orb)] - gene_orb_in_Vp = [preimage(VptoQp, v) for v in gene_orb_in_Qp] + gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{gfp_elem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] + gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{gfp_elem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] gene_submod_in_Vp = vcat(gene_ord_in_Vp, gene_H0p) - gene_submod_in_V = [preimage(VtoVp, v) for v in gene_submod_in_Vp] - gene_submod_in_q = [image(Vinq, v) for v in gene_submod_in_V] + gene_submod_in_V = TorQuadModElem[preimage(VtoVp, v) for v in gene_submod_in_Vp] + gene_submod_in_q = TorQuadModElem[image(Vinq, v) for v in gene_submod_in_V] orbq, _ = sub(q, gene_submod_in_q) @assert order(orb) == p^g stabq = image(MGptoG, stab) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index 66a6941b6681..0ab5951bb476 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -50,9 +50,9 @@ function trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T for iz=1:d for j=1:n for jz=1:d - g = z^(iz-jz)*Gabs[i,j] - push!(coeffs, trace(g)) - end + g = z^(iz-jz)*Gabs[i,j] + push!(coeffs, trace(g)) + end end end end From b5aa4d6e0eaeb677e4010d1d1b36d6685f7058dc Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 24 Oct 2022 22:54:22 +0200 Subject: [PATCH 08/76] some more --- .../LatticesWithIsometry/Enumeration.jl | 226 +++++++++++++----- .../LatticesWithIsometry.jl | 8 +- src/NumberTheory/LatticesWithIsometry/Misc.jl | 82 ++++--- 3 files changed, 218 insertions(+), 98 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 58a47687164a..2de08c54266e 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -111,7 +111,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) qA = discriminant_group(A) qB = discriminant_group(B) if !divides(det(C), p)[1] - return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1])[1] + return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1])[1] end l = valuation(level(C), p) @@ -169,8 +169,8 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) s3 = symbol(local_symbol(C, 2))[end] if p == 2 - s1 = symbol(Ap)[s3[1]+1] - s2 = symbol(Bp)[s3[1]+1] + s1 = Ap[s3[1]+1] + s2 = Bp[s3[1]+1] if s1[3] != s2[3] return false end @@ -229,25 +229,55 @@ end admissible_triples(L::ZLat, p::Integer) = admissible_triples(genus(L), p) -function _get_V(q, fq, mu, p) - pq, pqinq = primary_part(q, p) - pq, pqinq = sub(q, pqinq.([g*divexact(order(g), p) for g in gens(pq)])) - fpq = _image(mu, _restrict(fq, pqinq)) - ker, kerinpq = kernel(fpq.map_ab) - V, Vinq = sub(q, [pqinq(pq(kerinpq(a))) for a in gens(ker)]) - fV = _restrict(fq, Vinq) - return V, Vinq, fV +function _get_V(q, f, fq, mu, p) + L = relations(q) + f_ambient = inv(basis_matrix(L))*f*basis_matrix(L) + @assert f*gram_matrix(L)*transpose(f) == gram_matrix(L) + f_mu = mu(f_ambient) + if !is_zero(f_mu) + @assert det(f_mu) != 0 + L_sub = intersect(lattice_in_same_ambient_space(L, inv(f_mu)), dual(L)) + B = basis_matrix(L_sub) + V, Vinq = sub(q, q.([vec(collect(B[i,:])) for i in 1:nrows(B)])) + else + V, Vinq = q, id_hom(q) + end + pV, pVinV = primary_part(V, p) + pV, pVinV = sub(V, pVinV.([divexact(order(g), p)*g for g in gens(pV) if !(order(g)==1)])) + pVinq = compose(pVinV, Vinq) + fpV = _restrict(fq, pVinq) + return pV, pVinq, fpV +end + +function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) + N = relations(q) + if l == 0 + Gl = N + Gm = intersect(1//p*N, dual(N)) + rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) + gene = [q(lift(g)) for g in gens(rholN)] + return sub(q, gene)[2] + end + k = l-1 + m = l+1 + Gk = intersect(1//p^k*N, dual(N)) + Gl = intersect(1//p^l*N, dual(N)) + Gm = intersect(1//p^m*N, dual(N)) + rholN = torsion_quadratic_module(Gl, Gk+p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) + gene = [q(lift(g)) for g in gens(rholN)] + return sub(q, gene)[2] end function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer) - @req is_prime(p) "p must be a prime number" - + # requirement for the algorithm of BH22 + @req is_prime(p) "p must be a prime number" A, B, C = lattice.([Afa, Bfb, Cfc]) - @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" - - results = LatticeWithIsometry[] + # we need to compare the type of the output with the one of (C, f_c^p) + t = type(lattice_with_isometry(C, isometry(Cfc)^p)) + results = LatticeWithIsometry[] + # this is the glue valuation: it is well-defined because the triple in input is admissible g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) fA, fB = isometry.([Afa, Bfb]) @@ -256,75 +286,153 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry GA = image_centralizer_in_Oq(Afa) GB = image_centralizer_in_Oq(Bfb) + # this is where we will perform the glueing D, qAinD, qBinD = orthogonal_sum(qA, qB) OD = orthogonal_group(D) prim_ext = Tuple{TorQuadMod, AutomorphismGroup}[] OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) + # if the glue valuation is zero, then we glue along the trivial group and we don't + # have much more to do. Since the triple is p-admissible, A+B = C if g == 0 geneA = OqAinOD.(gens(GA)) geneB = OqBinOD.(gens(GB)) gene = vcat(geneA, geneB) - push!(prim_ext, (sub(D, elem_type(D)[])[1], sub(OD, gene)[1])) + GC2 = sub(OD, gene)[1] + C2 = orthogonal_sum(A, B)[1] + fC2 = block_diagonal_matrix(fA, fB) + C2fC2 = lattice_with_isometry(C2, fC2) + if type(C2fC2) == t + set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) + push!(results, C2fC2) + end @goto exit end - VA, VAinqA, fVA = _get_V(qA, fqA, minpoly(Bfb), p) - VB, VBinqB, fVB = _get_V(qB, fqB, minpoly(Afa), p) + # these are GA/GM-invariant, fA/fB-stable, and should contain the kernels of any glue map + VA, VAinqA, fVA = _get_V(qA, fA, fqA, minpoly(Bfb), p) + VB, VBinqB, fVB = _get_V(qB, fB, fqB, minpoly(Afa), p) + # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g return results end - l = level(genus(G)) - + # scale of the dual: any glue kernel must contain the multiple of l of the respective + # discriminant groups + l = level(genus(C)) + + # We look for the GA/GB-invariant and fA/fB-stable subgroups of VA/VB which respectively + # contained lqA/lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) + # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones + # are fA-stable (resp. fB-stable) subsA = _subgroups_representatives(VAinqA, GA, g, fVA, l) subsB = _subgroups_representatives(VBinqB, GB, g, fVB, l) - - R = [(H1, H2) for H1 in subsA for H2 in subsB if is_anti_isometric_with_anti_isometry(H1[1], H2[1])[1]] - + # once we have the potential kernels, we create pairs of anti-isometric groups since glue + # maps are anti-isometry + R = [(H1, H2) for H1 in subsA for H2 in subsB if is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1]))[1]] + # now, for each pair of anti-isometric potential kernels, we need to see whether + # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the + # corresponding overlattice and check whether it satisfies the type condition for (H1, H2) in R - SA, stabA = H1 - SB, stabB = H2 + #return H1, H2 + SAinqA, stabA = H1 + SA = domain(SAinqA) + # SA might not be in normal form + SBinqB, stabB = H2 + SB = domain(SBinqB) + # we already know they are but now we get the map ok, phi = is_anti_isometric_with_anti_isometry(SA, SB) @assert ok OSA = orthogonal_group(SA) - actSA = hom(stabSA, OSA, [OSA(matrix(x)) for x in gens(stabSA)]) - imSA = image(actSA, stabSA)[1] - kerSA = image(OqAinOD, kernel(actSA)) - fSA = OSA(fqA) - for (SB, stabSB) in subsB - bool, phi = is_anti_isometric_with_anti_isometry(SA, SB) - bool || continue - NSB, NSBtoSB = normal_form(SB) - OSB = orthogonal_group(SB) - actSB = hom(stabSB, OSB, [OSB(lift(x)) for x in gens(stabSB)]) - imSB = image(actSB, stabSB)[1] - kerSB = image(OqBinOD, kernel(actSB)) - fSB = OSB(fqB) - fSAinOSB = OSB(compose(inv(phi), compose(fSA, phi))) - bool, g0 = representative_action(OSB, fSAinOSB, fSB) - bool || continue - phi = compose(phi, g0) - fSAinOSB = OSB(compose(inv(phi), compose(fSA, phi))) - @assert fSAinOSB == fSB - center = centralizer(OSB, fSB) - stabSAphi = [OSB(compose(inv(phi), compose(g, phi))) for g in gens(stabSA)] - stabSAphi = sub(center, stabSAphi)[1] - stabSB = sub(center, gens(stabSB))[1] - reps = double_cosets(center, stabSAphi, stabSB) - for g in reps - g = representative(g) - phig = compose(phi, g) - g0g = OSB(compose(g0, g)) - gene = [qAtoD(SAtoNSA\NSA[i]) + qBtoD(g0g(SBtoNSB\NSB[i])) for i in 1:ngens(NSA)] - ext = sub(D, gene)[1] - perp = orthogonal_submodule(D, ext)[1] + # we compute the image of the stabalizer in OSA + actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) + imA = image(actA, stabA)[1] + # we keep track of the element of the stabilizer acting trivially on SA + kerA = [OqAinOD(x) for x in gens(kernel(actA)[1])] + fSA = _restrict(fqA, SAinqA) + fSA = OSA(fSA) + OSB = orthogonal_group(SB) + actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) + imB = image(actB, stabB) + kerB = [OqBinOD(x) for x in gens(kernel(actB)[1])] + fSB = _restrict(fqB, SBinqB) + fSB = OSB(fSB) + # we get all the elements of qB of order exactly p^l, which are not mutiple of an + # element of order p^{l+1}. In theory, glue maps are classified by the orbit of phi + # under the action of O(SB, rho_l(qB), fB) + rBinqB = _rho_functor(qB, p, valuation(l, p)) + @assert Oscar._is_invariant(stabB, rBinqB) # otherwise there is something wrong! + #return rBinqB, SBinqB + rBinSB = hom(domain(rBinqB), SB, [SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) + @assert is_injective(rBinSB) # we indeed have rho_l(qB) which is a subgroup of SB + # We compute the generators of O(SB, rho_l(qB)) + stabrB = [g for g in collect(OSB) if Oscar._is_invariant(g, rBinSB)] + OSBrB, _ = sub(OSB, stabrB) + @assert fSB in OSBrB # otherwise there is something wrong + # 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 glueing. + # If not, we try the next potential pair. + fSAinOSBrB = OSBrB(compose(inv(phi), compose(hom(fSA), phi))) + bool, g0 = representative_action(OSBrB, fSAinOSBrB, OSBrB(fSB)) + bool || continue + phi = compose(phi, hom(OSB(g0))) + fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + # Now the new phi is "sending" the restriction of fA to this of fB. + # So we can glue SA and SB. + @assert fSAinOSB == fSB + # Now it is time to compute generators for O(SB, rho_l(qB), fB), and the induced + # image of stabA for taking the double cosets next + center, _ = centralizer(OSBrB, OSBrB(fSB)) + center, _ = sub(OSB, OSB.(gens(center))) + stabSAphi = [OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] + stabSAphi, _ = sub(center, stabSAphi) + stabSB, _ = sub(center, actB.(gens(stabB))) + reps = double_cosets(center, stabSAphi, stabSB) + # now we iterate over all double cosets and for each representative, we compute the + # corresponding overlattice in the glueing. If it has the wanted type, we compute + # the image of the centralizer in OD from the stabA ans stabB. + for g in reps + g = representative(g) + phig = compose(phi, hom(g)) + # problem of lattices that are not in the same ambient space... + # TODO: fix `overlattice` or change something somewhere... + S = relations(domain(phig)) + R = relations(codomain(phig)) + VS = ambient_space(S) + diag, trafo = Hecke._gram_schmidt(gram_matrix(S), identity) + V1 = quadratic_space(QQ, diag) + S1 = lattice(V1, basis_matrix(S)*inv(trafo)) + VR = ambient_space(R) + b,T = isisometric_with_isometry(VR, V1) + @assert b + R1 = lattice(V1, T) + glue = [lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] + z = zero_matrix(QQ,0,degree(S)+degree(R)) + glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in glue], init=z) + glue = vcat(block_diagonal_matrix([basis_matrix(S1), basis_matrix(R1)]), glue) + glue = FakeFmpqMat(glue) + B = hnf(glue) + B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(B)) + C2 = lattice(, B[end-rank(S)-rank(R)+1:end, :]) + fC2 = block_diagonal_matrix([fA, fB]) + return C2, fC2 + C2fC2 = lattice_with_isometry(C2, fC2) + if type(C2fC2) != t + continue end + im2_phi = sub(OSA, [compose(phig, compose(g1, inv(phig))) for g1 in gens(imB)]) + im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) + stab = [(x, imB(compose(inv(phig), compose(x, phig)))) for x in gens(im3)] + stab = [OqAinOD(x[1])*OqBinOD(x[2]) for x in stab] + stab = union(stab, kerA) + stab = union(stab, kerB) + stab = Oscar._orthogonal_group(discriminant_group(C2), matrix.(stab)) + set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) + push!(results, C2fC2) end end - - @label exit + return results end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 57d3c9c983d5..c22201637672 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -196,15 +196,17 @@ function discriminant_group(Lf::LatticeWithIsometry) @req is_integral(L) "Underlying lattice must be integral" q = discriminant_group(L) Oq = orthogonal_group(q) - return q, Oq(gens(matrix_group(f))[1]) + f_ambient = inv(basis_matrix(L))*f*basis_matrix(L) + return q, Oq(gens(matrix_group(f_ambient))[1]) end @attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) n = order_of_isometry(Lf) L = lattice(Lf) f = isometry(Lf) + f = inv(basis_matrix(L))*f*basis_matrix(L) @req is_integral(L) "Underlying lattice must be integral" - if n in [1,2] + if n == 1 GL, _ = image_in_Oq(L) elseif is_definite(L) OL = orthogonal_group(L) @@ -339,7 +341,7 @@ end for l in divs Hl = kernel_lattice(Lf, Oscar._cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = inverse_trace_lattice(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false) + Hl = inverse_trace_lattice(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false) end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 570291ec94a3..e03f6d745a13 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -48,20 +48,6 @@ function _exponent(f::Union{fmpq_mat, fmpz_mat}) return divmd[n] end -function _image(p::Union{fmpz_poly, fmpq_poly}, f::TorQuadModMor) - p = change_base_ring(ZZ, p) - M = f.map_ab.map - M = p(M) - pf = hom(domain(f), codomain(f), M) - return pf -end - -function _image(p::Union{fmpz_poly, fmpz_poly}, f::AutomorphismGroupElem{TorQuadMod}) - D = domain(f) - M = matrix(f) - return _image(p, hom(D, D, M)) -end - function _restrict(f::TorQuadModMor, i::TorQuadModMor) imgs = TorQuadModElem[] V = domain(i) @@ -77,6 +63,25 @@ function _restrict(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) return _restrict(hom(f), i) end +function _is_invariant(f::TorQuadModMor, i::TorQuadModMor) + fab = f.map_ab + V = domain(i) + bool = true + for a in gens(V) + b = f(i(a)) + bool &= haspreimage(fab, data(b))[1] + end + return bool +end + +function _is_invariant(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) + return _is_invariant(hom(f), i) +end + +function _is_invariant(aut::AutomorphismGroup{TorQuadMod}, i::TorQuadModMor) + return all(g -> _is_invariant(g, i), gens(aut)) +end + ############################################################################## # # Group functionalities @@ -136,7 +141,7 @@ function _as_Fp_vector_space_quotient(HinV, p, f) n = ngens(Vs) F = GF(p) - MVs = matrix(compose(inv(VstoVab), compose(f.map_ab, VstoVab))) + MVs = matrix(compose(VstoVab, compose(f.map_ab, inv(VstoVab)))) Vp = VectorSpace(F, n) function _VstoVp(x::GrpAbFinGenElem) @@ -144,15 +149,15 @@ function _as_Fp_vector_space_quotient(HinV, p, f) return Vp(vec(collect(v))) end - function _VptoVs(v::ModuleElem{gfp_elem}) + function _VptoVs(v::ModuleElem{gfp_fmpz_elem}) x = lift.(v.v) return Vs(vec(collect(x))) end VstoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) VtoVp = compose(VtoVs, VstoVp) - M = reduce(vcat, [(i(HstoHab(a))).coeff for a in gens(Hs)]) - subgene = [Vp(v.v*M) for v in gens(Vp)] + gene = gfp_fmpz_mat[matrix(F.((i(HstoHab(a))).coeff)) for a in gens(Hs)] + subgene = [Vp(vec(collect(transpose(v)))) for v in gene] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) fVp = change_base_ring(F, MVs) @@ -170,50 +175,55 @@ function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{To @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) + Vp = codomain(VtoVp) if dim(Qp) == 0 - return Tuple{TorQuadMod, AutomorphismGroup{TorQuadMod}}[(V, G)] + if order(V) == p^g + return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(Vinq, G)] + end + return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] end gene_GV = TorQuadModMor[_restrict(g, Vinq) for g in gens(G)] - OV = orthogonal_group(V) - GV = sub(OV, OV.(gene_GV))[1] + GV = Oscar._orthogonal_group(V, [m.map_ab.map for m in gene_GV]) @assert GV(f) in GV GVinG = hom(GV, G, gens(GV), gens(G)) - act_GV_Vp = MatElem{gfp_elem}[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] - act_GV_Qp = MatElem{gfp_elem}[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] + act_GV_Vp = gfp_fmpz_mat[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] + act_GV_Qp = gfp_fmpz_mat[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(act_GV_Qp) @assert fQp in MGp MGptoGV = hom(MGp, GV, gens(GV)) - MGptoG = compose(MGptoGV, GVtoG) + MGptoG = compose(MGptoGV, GVinG) + + res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] - res = Tuple{TorQuadMod, AutomorphismGroup{TorQuadMod}}[] - if g-ngens(snf(abelian_group(H10))) > dim(Qp) + if g-ngens(snf(abelian_group(H0))[1]) > dim(Qp) return res end + F = base_ring(Qp) k, K = kernel(VptoQp.matrix, side = :left) gene_H0p = ModuleElem{gfp_elem}[Vp(vec(collect(K[i,:]))) for i in 1:k] - orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k)::Vector{Tuple{AbstractAlgebra.Generic.Submodule{gfp_elem}}, MatrixGroup{gfp_elem, gfp_mat}} + orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) for (orb, stab) in orb_and_stab i = orb.map _V = codomain(i) - for v in gens(ord) + for v in gens(orb) vv = _V(i(v)*fQp) if !can_solve_with_solution(i.matrix, vv.v, side = :left)[1] @goto non_fixed end end - gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{gfp_elem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] - gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{gfp_elem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] - gene_submod_in_Vp = vcat(gene_ord_in_Vp, gene_H0p) - gene_submod_in_V = TorQuadModElem[preimage(VtoVp, v) for v in gene_submod_in_Vp] + gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{gfp_fmpz_elem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] + gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{gfp_fmpz_elem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] + gene_submod_in_Vp = vcat(gene_orb_in_Vp, gene_H0p) + gene_submod_in_V = TorQuadModElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] gene_submod_in_q = TorQuadModElem[image(Vinq, v) for v in gene_submod_in_V] - orbq, _ = sub(q, gene_submod_in_q) - @assert order(orb) == p^g - stabq = image(MGptoG, stab) - push!(res, (orbq, stabq)) + orbq, orbqinq = sub(q, gene_submod_in_q) + @assert order(orbq) == p^g + stabq, _ = image(MGptoG, stab) + push!(res, (orbqinq, stabq)) @label non_fixed end return res From 0af1aab0d9d632c2b9fd72b952e2acffaa857251 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 25 Oct 2022 16:36:35 +0200 Subject: [PATCH 09/76] some more --- .../LatticesWithIsometry/Enumeration.jl | 47 +++++++++---------- src/NumberTheory/LatticesWithIsometry/Misc.jl | 23 +++++++++ 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 2de08c54266e..3b97ee488083 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -274,7 +274,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry A, B, C = lattice.([Afa, Bfb, Cfc]) @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" # we need to compare the type of the output with the one of (C, f_c^p) - t = type(lattice_with_isometry(C, isometry(Cfc)^p)) + t = type(Cfc) results = LatticeWithIsometry[] # this is the glue valuation: it is well-defined because the triple in input is admissible @@ -289,8 +289,9 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # this is where we will perform the glueing D, qAinD, qBinD = orthogonal_sum(qA, qB) OD = orthogonal_group(D) - prim_ext = Tuple{TorQuadMod, AutomorphismGroup}[] OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) + OqA = domain(OqAinOD) + OqB = domain(OqBinOD) # if the glue valuation is zero, then we glue along the trivial group and we don't # have much more to do. Since the triple is p-admissible, A+B = C @@ -301,12 +302,12 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry GC2 = sub(OD, gene)[1] C2 = orthogonal_sum(A, B)[1] fC2 = block_diagonal_matrix(fA, fB) - C2fC2 = lattice_with_isometry(C2, fC2) - if type(C2fC2) == t + if type(lattice_wit_isometry(C2, fC2^p)) == t + C2fC2 = lattice_wit_isometry(C2, fC2) set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) push!(results, C2fC2) end - @goto exit + return results end # these are GA/GM-invariant, fA/fB-stable, and should contain the kernels of any glue map @@ -337,6 +338,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry for (H1, H2) in R #return H1, H2 SAinqA, stabA = H1 + return SAinqA SA = domain(SAinqA) # SA might not be in normal form SBinqB, stabB = H2 @@ -344,7 +346,9 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # we already know they are but now we get the map ok, phi = is_anti_isometric_with_anti_isometry(SA, SB) @assert ok - OSA = orthogonal_group(SA) + OSAinOqA = embedding_orthogonal_group(SAinqA) + OSAinOD = compose(OSAinOqA, OqAinOD) + OSA = domain(OSAinqA) # we compute the image of the stabalizer in OSA actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) imA = image(actA, stabA)[1] @@ -352,9 +356,11 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry kerA = [OqAinOD(x) for x in gens(kernel(actA)[1])] fSA = _restrict(fqA, SAinqA) fSA = OSA(fSA) - OSB = orthogonal_group(SB) + OSBinOqB = embedding_orthogonal_group(SBinqB) + OSAinOD = compose(OSBinOqB, OqBinOD) + OSB = domain(OSBinOqB) actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) - imB = image(actB, stabB) + imB = image(actB, stabB)[1] kerB = [OqBinOD(x) for x in gens(kernel(actB)[1])] fSB = _restrict(fqB, SBinqB) fSB = OSB(fSB) @@ -399,32 +405,24 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # TODO: fix `overlattice` or change something somewhere... S = relations(domain(phig)) R = relations(codomain(phig)) - VS = ambient_space(S) - diag, trafo = Hecke._gram_schmidt(gram_matrix(S), identity) - V1 = quadratic_space(QQ, diag) - S1 = lattice(V1, basis_matrix(S)*inv(trafo)) - VR = ambient_space(R) - b,T = isisometric_with_isometry(VR, V1) - @assert b - R1 = lattice(V1, T) glue = [lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] z = zero_matrix(QQ,0,degree(S)+degree(R)) glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in glue], init=z) - glue = vcat(block_diagonal_matrix([basis_matrix(S1), basis_matrix(R1)]), glue) + glue = vcat(block_diagonal_matrix([basis_matrix(S), basis_matrix(R)]), glue) glue = FakeFmpqMat(glue) B = hnf(glue) B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(B)) - C2 = lattice(, B[end-rank(S)-rank(R)+1:end, :]) + C2 = lattice(ambient_space(cover(D)), B[end-rank(S)-rank(R)+1:end, :]) fC2 = block_diagonal_matrix([fA, fB]) - return C2, fC2 - C2fC2 = lattice_with_isometry(C2, fC2) - if type(C2fC2) != t + fC2 = basis_matrix(C2)*fC2*inv(basis_matrix(C2)) + if type(lattice_with_isometry(C2, fC2^p)) != t continue end - im2_phi = sub(OSA, [compose(phig, compose(g1, inv(phig))) for g1 in gens(imB)]) + C2fC2 = lattice_with_isometry(C2, fC2) + im2_phi = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)]))[1] im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) - stab = [(x, imB(compose(inv(phig), compose(x, phig)))) for x in gens(im3)] - stab = [OqAinOD(x[1])*OqBinOD(x[2]) for x in stab] + stab = [(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] + stab = [OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] stab = union(stab, kerA) stab = union(stab, kerB) stab = Oscar._orthogonal_group(discriminant_group(C2), matrix.(stab)) @@ -433,6 +431,5 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry end end - @label exit return results end diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index e03f6d745a13..41994dff39d2 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -88,6 +88,29 @@ end # ############################################################################## +function embedding_orthogonal_group(i) + ok, j = has_complement(i) + @req ok "domain(i) needs to have a complement in codomain(i)" + A = domain(i) + B = domain(j) + D = codomain(i) + gene = vcat(i.(gens(A)), j.(gens(B))) + n = ngens(A) + OD, OA, = orthogonal_group.([D, A]) + + geneOA = elem_type(OD)[] + for f in gens(OA) + imgs = [i(f(a)) for a in gens(A)] + imgs = vcat(imgs, gene[n+1:end]) + _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) + _f = hom(D.ab_grp, D.ab_grp, _f) + f = TorQuadModMor(D, D, _f) + push!(geneOA, OD(f)) + end + OAtoOD = hom(OA, OD, gens(OA), geneOA) + return OAtoOD +end + function embedding_orthogonal_group(i1, i2) D = codomain(i1) A = domain(i1) From 16b1bf4e80b77bd2c1aad878221df6b298b8ed4b Mon Sep 17 00:00:00 2001 From: StevellM Date: Sat, 29 Oct 2022 14:17:52 +0200 Subject: [PATCH 10/76] ambient isometry and orthogrp deg tqm --- .../LatticesWithIsometry/Enumeration.jl | 72 ++++++--- .../LatticesWithIsometry.jl | 120 +++++++++----- src/NumberTheory/LatticesWithIsometry/Misc.jl | 151 +++++++++++++++++- .../LatticesWithIsometry/TraceEquivalence.jl | 10 +- .../LatticesWithIsometry/Types.jl | 4 +- 5 files changed, 286 insertions(+), 71 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 3b97ee488083..16f95126977d 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -231,9 +231,7 @@ admissible_triples(L::ZLat, p::Integer) = admissible_triples(genus(L), p) function _get_V(q, f, fq, mu, p) L = relations(q) - f_ambient = inv(basis_matrix(L))*f*basis_matrix(L) - @assert f*gram_matrix(L)*transpose(f) == gram_matrix(L) - f_mu = mu(f_ambient) + f_mu = mu(f) if !is_zero(f_mu) @assert det(f_mu) != 0 L_sub = intersect(lattice_in_same_ambient_space(L, inv(f_mu)), dual(L)) @@ -272,6 +270,12 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" A, B, C = lattice.([Afa, Bfb, Cfc]) + + if ambient_space(Afa) === ambient_space(Bfb) + @req rank(intersect(A, B)) == 0 "Lattice in same ambient space must have empty intersection to glue" + @req rank(A) + rank(B) <= dim(ambient_space(A)) "Lattice cannot glue in their ambient space" + end + @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" # we need to compare the type of the output with the one of (C, f_c^p) t = type(Cfc) @@ -287,7 +291,11 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry GB = image_centralizer_in_Oq(Bfb) # this is where we will perform the glueing - D, qAinD, qBinD = orthogonal_sum(qA, qB) + if ambient_space(Afa) === ambient_space(Bfb) + D, qAinD, qBinD = inner_orthoognal_sum(qA, qB) + else + D, qAinD, qBinD = orthogonal_sum(qA, qB) + end OD = orthogonal_group(D) OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) OqA = domain(OqAinOD) @@ -300,10 +308,18 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry geneB = OqBinOD.(gens(GB)) gene = vcat(geneA, geneB) GC2 = sub(OD, gene)[1] - C2 = orthogonal_sum(A, B)[1] - fC2 = block_diagonal_matrix(fA, fB) + if ambient_space(Afa) === ambient_space(Bfb) + C2 = A+B + fC2 = block_diagonal_matrix([fA, fB]) + _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) + fC2 = _B*fC2*inv(_B) + @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + else + C2 = orthogonal_sum(A, B)[1] + fC2 = block_diagonal_matrix(fA, fB) + end if type(lattice_wit_isometry(C2, fC2^p)) == t - C2fC2 = lattice_wit_isometry(C2, fC2) + C2fC2 = lattice_wit_isometry(C2, fC2, ambient_representation=false) set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) push!(results, C2fC2) end @@ -311,8 +327,8 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry end # these are GA/GM-invariant, fA/fB-stable, and should contain the kernels of any glue map - VA, VAinqA, fVA = _get_V(qA, fA, fqA, minpoly(Bfb), p) - VB, VBinqB, fVB = _get_V(qB, fB, fqB, minpoly(Afa), p) + VA, VAinqA, fVA = _get_V(qA, ambient_isometry(Afa), fqA, minpoly(Bfb), p) + VB, VBinqB, fVB = _get_V(qB, ambient_isometry(Bfb), fqB, minpoly(Afa), p) # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g @@ -367,7 +383,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # we get all the elements of qB of order exactly p^l, which are not mutiple of an # element of order p^{l+1}. In theory, glue maps are classified by the orbit of phi # under the action of O(SB, rho_l(qB), fB) - rBinqB = _rho_functor(qB, p, valuation(l, p)) + rBinqB = _rho_functor(qB, p, valuation(l, p)+1) @assert Oscar._is_invariant(stabB, rBinqB) # otherwise there is something wrong! #return rBinqB, SBinqB rBinSB = hom(domain(rBinqB), SB, [SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) @@ -401,24 +417,32 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry for g in reps g = representative(g) phig = compose(phi, hom(g)) - # problem of lattices that are not in the same ambient space... - # TODO: fix `overlattice` or change something somewhere... S = relations(domain(phig)) R = relations(codomain(phig)) - glue = [lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] - z = zero_matrix(QQ,0,degree(S)+degree(R)) - glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in glue], init=z) - glue = vcat(block_diagonal_matrix([basis_matrix(S), basis_matrix(R)]), glue) - glue = FakeFmpqMat(glue) - B = hnf(glue) - B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(B)) - C2 = lattice(ambient_space(cover(D)), B[end-rank(S)-rank(R)+1:end, :]) - fC2 = block_diagonal_matrix([fA, fB]) - fC2 = basis_matrix(C2)*fC2*inv(basis_matrix(C2)) - if type(lattice_with_isometry(C2, fC2^p)) != t + if ambient_space(R) === ambient_space(S) + C2 = overlattice(phig) + fC2 = block_diagonal_matrix([fA, fB]) + _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) + fC2 = _B*fC2*inv(_B) + @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + else + glue = [lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] + z = zero_matrix(QQ,0,degree(S)+degree(R)) + glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in glue], init=z) + glue = vcat(block_diagonal_matrix([basis_matrix(S), basis_matrix(R)]), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + C2 = lattice(ambient_space(cover(D)), _B[end-rank(S)-rank(R)+1:end, :]) + fC2 = block_diagonal_matrix([fA, fB]) + __B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) + fC2 = __B*fC2*inv(__B) + @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + end + if type(lattice_with_isometry(C2, fC2^p, ambient_representation=false)) != t continue end - C2fC2 = lattice_with_isometry(C2, fC2) + C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) im2_phi = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)]))[1] im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) stab = [(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index c22201637672..35e8a8836cf1 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -1,5 +1,5 @@ export hermitian_structure, isometry, lattice_with_isometry, order_of_isometry, - type + type, ambient_isometry, image_centralizer_in_Oq, coinvariant_lattice ############################################################################### # @@ -22,21 +22,29 @@ end @doc Markdown.doc""" lattice(Lf::LatticeWithIsometry) -> ZLat -Given a lattice with isometry `Lf`, return the underlying lattice `L`. +Given a lattice with isometry `(L, f)`, return the underlying lattice `L`. """ lattice(Lf::LatticeWithIsometry) = Lf.Lb @doc Markdown.doc""" isometry(Lf::LatticeWithIsometry) -> fmpq_mat -Given a lattice with isometry `Lf`, return the underlying isometry `f`. +Given a lattice with isometry `(L, f)`, return the underlying isometry `f`. """ isometry(Lf::LatticeWithIsometry) = Lf.f +@doc Markdown.doc""" + ambient_isometry(Lf::LatticeWithIsometry) -> fmpq_mat + + Given a lattice with isometry `(L, f)`, return an isometry of underlying isometry +of the ambient space of `L` inducing `f` on `L` +""" +ambient_isometry(Lf::LatticeWithIsometry) = Lf.f_ambient + @doc Markdown.doc""" order_of_isometry(Lf::LatticeWithIsometry) -> Integer -Given a lattice with isometry `Lf`, return the order of the underlying +Given a lattice with isometry `(L, f)`, return the order of the underlying isometry `f`. """ order_of_isometry(Lf::LatticeWithIsometry) = Lf.n @@ -50,7 +58,7 @@ order_of_isometry(Lf::LatticeWithIsometry) = Lf.n @doc Markdown.doc""" rank(Lf::LatticeWithIsometry) -> Integer -Given a lattice with isometry `Lf`, return the rank of the underlying lattice +Given a lattice with isometry `(L, f)`, return the rank of the underlying lattice `L`. """ rank(Lf::LatticeWithIsometry) = rank(lattice(Lf)) @@ -58,7 +66,7 @@ rank(Lf::LatticeWithIsometry) = rank(lattice(Lf)) @doc Markdown.doc""" charpoly(Lf::LatticeWithIsometry) -> fmpq_poly -Given a lattice with isometry `Lf`, return the characteristic polynomial of the +Given a lattice with isometry `(L, f)`, return the characteristic polynomial of the underlying isometry `f`. """ charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf)) @@ -66,7 +74,7 @@ charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf)) @doc Markdown.doc""" minpoly(Lf::LatticeWithIsometry) -> fmpq_poly -Given a lattice with isometry `Lf`, return the minimal polynomial of the +Given a lattice with isometry `(L, f)`, return the minimal polynomial of the underlying isometry `f`. """ minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf)) @@ -74,13 +82,19 @@ minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf)) @doc Markdown.doc""" genus(Lf::LatticeWithIsometry) -> ZGenus -Given a lattice with isometry `Lf`, return the genus of the underlying +Given a lattice with isometry `(L, f)`, return the genus of the underlying lattice `L`. For now, in order for the genus to exist, the lattice must be integral. """ genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus(L) : error("Underlying lattice must be integral"); end +@doc Markdown.doc""" + ambient_space(Lf::LatticeWithIsometry) -> QuadSpace + +Given a lattice with isometry `(L, f)`, return the ambient space of the underlying +lattice `L`. +""" ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf)) ############################################################################### @@ -90,53 +104,78 @@ ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf)) ############################################################################### @doc Markdown.doc""" - lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true) - -> LatticeWithIsometry + lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true, + ambient_representation = true) + -> LatticeWithIsometry Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry of `L` of finite order `n`, return the corresponding lattice with isometry pair $(L, f)$. + +If `ambient_representation` is set to true, `f` is consider as an isometry of the +ambient space of `L` and the induced isometry on `L` is automatically computed. +Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity +on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true) +function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true, + ambient_representation::Bool = true) if rank(L) == 0 return LatticewithIsometry(L, matrix(QQ,0,0,[]), -1) end if check @req det(f) != 0 "f is not invertible" - G = gram_matrix(L) - @req f*G*transpose(f) == G "f does not define an isometry of L" m = Oscar._exponent(f) - @req m > 0 "f is not of finite exponent" - @req n == m "The order of f is not equal to n" + @req m > 0 "f is not finite" + @req n == m "The order of f is equal to $m, not $n" + end + + if ambient_representation + f_ambient = f + B = basis_matrix(L) + ok, f = can_solve_with_solution(B, B*f_ambient, side = :left) + @req ok "Isometry does not restrict to f" + else + V = ambient_space(L) + B = basis_matrix(L) + B2 = orthogonal_complement(V, B) + C = vcat(B, B2) + f_ambient = block_diagonal_matrix([f, identity_matrix(QQ, nrows(B2))]) + f_ambient = inv(C)*f_ambient*C + end + + if check + @req f*gram_matrix(L)*transpose(f) == gram_matrix(L) "f does not define an isometry of L" + @req f_ambient*gram_matrix(ambient_space(L))*transpose(f_ambient) == gram_matrix(ambient_space(L)) "f_ambient is not an isometry of the ambient space of L" + @assert basis_matrix(L)*f_ambient == f*basis_matrix(L) end - return LatticeWithIsometry(L, f, n) + return LatticeWithIsometry(L, f, f_ambient, n) end @doc Markdown.doc""" - lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true) - -> LatticeWithIsometry + lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true, + ambient_representation::Bool, = true) + -> LatticeWithIsometry Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry of `L` of finite order, return the corresponding lattice with isometry pair $(L, f)$. + +If `ambient_representation` is set to true, `f` is consider as an isometry of the +ambient space of `L` and the induced isometry on `L` is automatically computed. +Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity +on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true) +function lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true, + ambient_representation::Bool = true) if rank(L) == 0 - return LatticeWithIsometry(L, matrix(QQ,0,0,[]), -1) - end - - if check - @req det(f) != 0 "f is not invertibe" - G = gram_matrix(L) - @req f*G*transpose(f) == G "f does not define an isometry of L" - m = Oscar._exponent(f) - @req m > 0 "f is not of finite exponent" + return LatticeWithIsometry(L, matrix(QQ,0,0,[]), matrix(QQ, 0, 0, []), -1) end n = Oscar._exponent(f) - return lattice_with_isometry(L, f, Int(n), check = false) + return lattice_with_isometry(L, f, Int(n), check = check, + ambient_representation = ambient_representation) end @doc Markdown.doc""" @@ -146,9 +185,9 @@ Given a $\mathbb Z$-lattice `L` return the lattice with isometry pair $(L, f)$, where `f` corresponds to the identity mapping of `L`. """ function lattice_with_isometry(L::ZLat) - r = rank(L) - f = identity_matrix(QQ, r) - return lattice_with_isometry(L, f, check = false) + d = degree(L) + f = identity_matrix(QQ, d) + return lattice_with_isometry(L, f, check = false, ambient_representation = true) end ############################################################################### @@ -174,7 +213,8 @@ If it exists, the hermitian structure is cached. @req n >= 3 "No hermitian structures for n smaller than 3" - return inverse_trace_lattice(lattice(Lf), f, n = n, check = true) + return inverse_trace_lattice(lattice(Lf), f, n = n, check = true, + ambient_representation = false) end ############################################################################### @@ -192,19 +232,17 @@ of the underlying lattice `L` as well as this image of the underlying isometry """ function discriminant_group(Lf::LatticeWithIsometry) L = lattice(Lf) - f = isometry(Lf) + f = ambient_isometry(Lf) @req is_integral(L) "Underlying lattice must be integral" q = discriminant_group(L) Oq = orthogonal_group(q) - f_ambient = inv(basis_matrix(L))*f*basis_matrix(L) - return q, Oq(gens(matrix_group(f_ambient))[1]) + return q, Oq(gens(matrix_group(f))[1]) end @attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) n = order_of_isometry(Lf) L = lattice(Lf) - f = isometry(Lf) - f = inv(basis_matrix(L))*f*basis_matrix(L) + f = ambient_isometry(Lf) @req is_integral(L) "Underlying lattice must be integral" if n == 1 GL, _ = image_in_Oq(L) @@ -294,14 +332,14 @@ function kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) f = isometry(Lf) M = p(f) k, K = left_kernel(change_base_ring(ZZ, M)) - L2 = Zlattice(gram = K*gram_matrix(L)*transpose(K)) + L2 = lattice_in_same_ambient_space(L, K*basis_matrix(L)) f2 = solve_left(change_base_ring(QQ, K), K*f) @assert f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) chi = parent(p)(collect(coefficients(minpoly(f2)))) chif = parent(p)(collect(coefficients(minpoly(Lf)))) _chi = gcd(p, chif) @assert (rank(L2) == 0) || (chi == _chi) - return lattice_with_isometry(L2, f2, check = false) + return lattice_with_isometry(L2, f2, check = true, ambient_representation = false) end kernel_lattice(Lf::LatticeWithIsometry, p::fmpz_poly) = kernel_lattice(Lf, change_base_ring(QQ, p)) @@ -341,7 +379,7 @@ end for l in divs Hl = kernel_lattice(Lf, Oscar._cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = inverse_trace_lattice(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false) + Hl = inverse_trace_lattice(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false) end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 41994dff39d2..c37b6f5bc9be 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -1,5 +1,149 @@ GG = GAP.Globals +################################################################################ +# +# Inner orthogonal sum +# +################################################################################ + +function inner_orthogonal_sum(T::TorQuadMod, U::TorQuadMod) + @assert modulus_bilinear_form(T) == modulus_bilinear_form(U) + @assert modulus_quadratic_form(T) == modulus_quadratic_form(U) + @assert ambient_space(cover(T)) === ambient_space(cover(U)) + cS = cover(T)+cover(U) + rS = relations(T)+relations(U) + geneT = [lift(a) for a in gens(T)] + geneU = [lift(a) for a in gens(U)] + S = torsion_quadratic_module(cS, rS, gens = vcat(geneT, geneU), modulus = modulus_bilinear_form(T), modulus_qf = modulus_quadratic_form(T)) + TinS = hom(T, S, S.(geneT)) + UinS = hom(U, S, S.(geneU)) + return S, TinS, UinS +end + +############################################################################### +# +# orthogonal group degenerate form +# +############################################################################### + +function _normalize(T::TorQuadMod) + if order(T) == 1 + return T, id_hom(T) + end + @assert is_prime(elementary_divisors(T)[end]) + @assert is_degenerate(T) + p = exponent(T) + qT = Hecke.gram_matrix_quadratic(T) + if p == 2 + qT = 1//modulus_quadratic_form(T)*qT + else + qT = 1//modulus_bilinear_form(T)*qT + end + r = rank(qT) + if r != ncols(qT) + dT = denominator(qT) + _, U = hnf_with_transform(change_base_ring(ZZ, dT*qT)) + else + U = identity_matrix(QQ, r) + end + nondeg = U[1:r, :] + kernel = U[r+1:end, :] + qT = nondeg*qT*transpose(nondeg) + + qT1 = inv(qT) + prec = 6 + D, U = Hecke.padic_normal_form(qT1, p, prec=prec, partial=false) + R = ResidueRing(ZZ, ZZ(p)^prec) + U = map_entries(x -> R(ZZ(x)), U) + U = transpose(inv(U)) + dT = denominator(qT) + qTR = map_entries(x -> R(ZZ(x)), dT*qT) + D = U*qTR*transpose(U) + D = map_entries(x -> R(mod(lift(x), dT)), D) + if p != 2 + m = ZZ(modulus_quadratic_form(T)//modulus_bilinear_form(T)) + D = R(m)^-1*D + end + + D1, U1 = Hecke._normalize(D, ZZ(p), false) + U = U1*U + nondeg = U*map_entries(x -> R(ZZ(x)), nondeg) + U = vcat(nondeg, map_entries(x -> R(ZZ(x)), kernel)) + @assert nrows(U) == ncols(U) + + n = ncols(U) + gene = TorQuadModElem[] + for i in 1:n + g = sum(lift(U[i,j])*T[j] for j in 1:ncols(U)) + push!(gene, g) + end + D, DtoT = sub(T, gene) + if !is_degenerate(D) + return D, DtoT + end + gene = gens(D) + qb = Hecke.gram_matrix_bilinear(D) + n = ngens(D) + + kergens = [gene[i] for i in 1:n if is_zero(qb[i,:])] + nondeg = [gene[i] for i in 1:n if !(gene[i] in kergens)] + k = length(kergens) + + for i in 1:k + if Hecke.quadratic_product(kergens[i]) == 1 + for j in i+1:k + if Hecke.quadratic_product(kergens[j]) == 1 + kergens[j] += kergens[i] + end + end + kergens = reduce(vcat, [kergens[1:i-1], kergens[i+1:end], [kergens[i]]]) + break + end + end + gene = vcat(kergens, nondeg) + D2, D2inD = sub(D, gene) + return D2, compose(D2inD, DtoT) +end + +function orthogonal_group_degenerate(T::TorQuadMod) + @req is_degenerate(T) "T must be degenerate" + @req is_prime(elementary_divisors(T)[end]) "T must define a F_p-vector space" + p = elementary_divisors(T)[end] + n = ngens(T) + NT, NTtoT = _normalize(T) + @assert is_bijective(NTtoT) + q = Hecke.gram_matrix_quadratic(NT) + k = length([i for i in 1:n if iszero(q[:,i])]) + r = n-k + Idk = identity_matrix(ZZ, k) + Idr = identity_matrix(ZZ, r) + NR, NRtoNT = sub(NT, [NT[i] for i in k+1:n]) + @assert !is_degenerate(NR) + gensNR = [hom(g) for g in gens(orthogonal_group(NR))] + if k > 0 + gene_im = [block_diagonal_matrix([Idk, g.map_ab.map]) for g in gensNR] + gene_im = union(gene_im, [block_diagonal_matrix([lift(matrix(g)), Idr]) for g in gens(general_linear_group(k, GF(p)))]) + else + gene_im = [g.map_ab.ab for g in gensNR] + end + if k*r > 0 + bas = fmpz_mat[lift(matrix(GF(p), m)) for m in GAP.Globals.Basis(GAP.Globals.MatrixSpace(GAP.Globals.GF(GAP.julia_to_gap(p)), k, r))] + gene2 = [] + for g in bas + s = identity_matrix(ZZ, n) + s[1:k, k+1:end] = g + push!(gene_im, s) + end + MG1 = matrix_group([map_entries(GF(p), m) for m in gene_im]) + MG2 = matrix_group([map_entries(GF(p), m) for m in vcat(gene_im, gene2)]) + gene3 = lift.(matrix.(representative.(double_cosets(MG2, MG1, MG1)))) + gene_im = unique(vcat(gene_im, gene3)) + end + geneOT = [hom(NT, NT, g) for g in gene_im] + geneOT = [compose(compose(inv(NTtoT), g), NTtoT).map_ab.map for g in geneOT] + return _orthogonal_group(T, geneOT, check=false) +end + ############################################################################### # # Cyclotomic polynomials @@ -202,13 +346,14 @@ function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{To if dim(Qp) == 0 if order(V) == p^g - return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(Vinq, G)] + return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(Vinq, G)] end return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] end - gene_GV = TorQuadModMor[_restrict(g, Vinq) for g in gens(G)] - GV = Oscar._orthogonal_group(V, [m.map_ab.map for m in gene_GV]) + OV = is_degenerate(V) ? orthogonal_group_degenerate(V) : orthogonal_group(V) + gene_GV = TorQuadModMor[OV(_restrict(g, Vinq)) for g in gens(G)] + GV, _ = sub(OV, gene_GV) @assert GV(f) in GV GVinG = hom(GV, G, gens(GV), gens(G)) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index 0ab5951bb476..c0d217f82904 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -73,13 +73,19 @@ polynomial, return the corresponding hermitian lattice over $E/K$ where `E` is t `n`th cyclotomic field and `K` is a maximal real subfield of `E`, where the multiplication by the `n`th root of unity correspond to the mapping via `f`. """ -function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Bool = true) +function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Bool = true, + ambient_representation::Bool = true) if n <= 0 Oscar._exponent(f) end @req n > 0 "f is not of finite exponent" + if ambient_representation + ok, f = can_solve_with_solution(basis_matrix(L), basis_matrix(L)*f, side =:left) + @req ok "Isometry does not restrict to L" + end + if check @req n >= 3 "No hermitian inverse trace lattice for order less than 3" G = gram_matrix(L) @@ -95,7 +101,7 @@ function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Boo # We choose as basis for the hermitian lattice Lh the identity matrix gram = matrix(zeros(E, m,m)) G = gram_matrix(L) - v = zero_matrix(QQ, 1, degree(L)) + v = zero_matrix(QQ, 1, rank(L)) for i=1:m for j=1:m diff --git a/src/NumberTheory/LatticesWithIsometry/Types.jl b/src/NumberTheory/LatticesWithIsometry/Types.jl index a431916865e9..6fe147f85d72 100644 --- a/src/NumberTheory/LatticesWithIsometry/Types.jl +++ b/src/NumberTheory/LatticesWithIsometry/Types.jl @@ -3,12 +3,14 @@ export LatticeWithIsometry @attributes mutable struct LatticeWithIsometry Lb::ZLat f::fmpq_mat + f_ambient::fmpq_mat n::Integer - function LatticeWithIsometry(Lb::ZLat, f::fmpq_mat, n::Integer) + function LatticeWithIsometry(Lb::ZLat, f::fmpq_mat, f_ambient::fmpq_mat, n::Integer) z = new() z.Lb = Lb z.f = f + z.f_ambient = f_ambient z.n = n return z end From bdffe417ba9d23cbafe0e20a283d3ede3cd8bae1 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 31 Oct 2022 13:05:55 +0100 Subject: [PATCH 11/76] minor change --- src/NumberTheory/LatticesWithIsometry/Misc.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index c37b6f5bc9be..15a98b222860 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -119,7 +119,8 @@ function orthogonal_group_degenerate(T::TorQuadMod) Idr = identity_matrix(ZZ, r) NR, NRtoNT = sub(NT, [NT[i] for i in k+1:n]) @assert !is_degenerate(NR) - gensNR = [hom(g) for g in gens(orthogonal_group(NR))] + ONR = orthogonal_group(NR) + gensNR = [hom(g) for g in gens(ONR)] if k > 0 gene_im = [block_diagonal_matrix([Idk, g.map_ab.map]) for g in gensNR] gene_im = union(gene_im, [block_diagonal_matrix([lift(matrix(g)), Idr]) for g in gens(general_linear_group(k, GF(p)))]) @@ -141,7 +142,9 @@ function orthogonal_group_degenerate(T::TorQuadMod) end geneOT = [hom(NT, NT, g) for g in gene_im] geneOT = [compose(compose(inv(NTtoT), g), NTtoT).map_ab.map for g in geneOT] - return _orthogonal_group(T, geneOT, check=false) + OT = _orthogonal_group(T, geneOT, check=false) + @asert order(OT) == p*k*r*order(ONR)*order(GL(k, p)) + return OT end ############################################################################### From ff544f4768f59d0eca6731beb3a6d424800e5ab1 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 2 Nov 2022 14:02:56 +0100 Subject: [PATCH 12/76] some more --- .../LatticesWithIsometry/Enumeration.jl | 173 ++++++++++-------- .../LatticesWithIsometry.jl | 24 +-- src/NumberTheory/LatticesWithIsometry/Misc.jl | 67 ++++--- 3 files changed, 155 insertions(+), 109 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 16f95126977d..d07d4b8ba49e 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -8,16 +8,16 @@ export admissible_triples, is_admissible_triple ################################################################################## # The tuples in output are pairs of positive integers! -function _tuples_divisors(d::fmpz) +function _tuples_divisors(d::T) where T <: Union{Integer, fmpz} div = divisors(d) - return [(dd,abs(divexact(d,dd))) for dd in div] + return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] end # This is line 8 of Algorithm 1, they correspond to the possible # discriminant for the genera A and B to glue to fit in C. d is # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. -function _find_D(d::fmpz, m::Int, p::Int) +function _find_D(d::T, m::Int, p::Int) where T <: Union{Integer, fmpz} @assert is_prime(p) @assert d != 0 @@ -26,7 +26,7 @@ function _find_D(d::fmpz, m::Int, p::Int) return _tuples_divisors(d) end - D = Tuple{Int,Int}[] + D = Tuple{T, T}[] # We try all the values of g possible, from 1 to p^m for j=0:m g = p^j @@ -155,7 +155,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) for i = 1:l s1 = symbol(ABr)[i] - s2 = symbol(Cp)[i] + s2 = Cp[i] if s1[2] != s2[2] return false end @@ -191,8 +191,8 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) return true end -function is_admissible_triple(A::Union{ZLat, ZGenus}, B::Union{ZLat, ZGenus}, C::Union{ZLat, ZGenus}, p::Integer) - L = ZGenus[typeof(D) <: ZLat ? genus(D) : D for D = (A, B, C)] +function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZLat, LatticeWithIsometry} + L = ZGenus[genus(D) for D = (A, B, C)] return is_admissible_triple(L[1], L[2], L[3], p) end @@ -227,14 +227,12 @@ function admissible_triples(G::ZGenus, p::Int64) return L end -admissible_triples(L::ZLat, p::Integer) = admissible_triples(genus(L), p) +admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatticeWithIsometry} = admissible_triples(genus(L), p) -function _get_V(q, f, fq, mu, p) - L = relations(q) +function _get_V(L, q, f, fq, mu, p) f_mu = mu(f) if !is_zero(f_mu) - @assert det(f_mu) != 0 - L_sub = intersect(lattice_in_same_ambient_space(L, inv(f_mu)), dual(L)) + L_sub = intersect(lattice_in_same_ambient_space(L, inv(f_mu)*basis_matrix(L)), dual(L)) B = basis_matrix(L_sub) V, Vinq = sub(q, q.([vec(collect(B[i,:])) for i in 1:nrows(B)])) else @@ -270,20 +268,20 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" A, B, C = lattice.([Afa, Bfb, Cfc]) - + if ambient_space(Afa) === ambient_space(Bfb) @req rank(intersect(A, B)) == 0 "Lattice in same ambient space must have empty intersection to glue" @req rank(A) + rank(B) <= dim(ambient_space(A)) "Lattice cannot glue in their ambient space" end - @req is_admissible_triple(A, B, C, p) "(A, B, C) must be p-admissble" - # we need to compare the type of the output with the one of (C, f_c^p) + @req is_admissible_triple(Afa, Bfb, Cfc, p) "(A, B, C) must be p-admissble" + # we need to compare the type of the output with the one of Cfc t = type(Cfc) results = LatticeWithIsometry[] # this is the glue valuation: it is well-defined because the triple in input is admissible g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) - + fA, fB = isometry.([Afa, Bfb]) qA, fqA = discriminant_group(Afa) qB, fqB = discriminant_group(Bfb) @@ -292,10 +290,11 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # this is where we will perform the glueing if ambient_space(Afa) === ambient_space(Bfb) - D, qAinD, qBinD = inner_orthoognal_sum(qA, qB) + D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) else D, qAinD, qBinD = orthogonal_sum(qA, qB) end + OD = orthogonal_group(D) OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) OqA = domain(OqAinOD) @@ -304,10 +303,12 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # if the glue valuation is zero, then we glue along the trivial group and we don't # have much more to do. Since the triple is p-admissible, A+B = C if g == 0 - geneA = OqAinOD.(gens(GA)) - geneB = OqBinOD.(gens(GB)) + geneA = gens(GA) + geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(a) for a in geneA] + geneB = gens(GB) + geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(b) for b in geneB] gene = vcat(geneA, geneB) - GC2 = sub(OD, gene)[1] + GC2, _ = sub(OD, gene) if ambient_space(Afa) === ambient_space(Bfb) C2 = A+B fC2 = block_diagonal_matrix([fA, fB]) @@ -318,80 +319,85 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry C2 = orthogonal_sum(A, B)[1] fC2 = block_diagonal_matrix(fA, fB) end - if type(lattice_wit_isometry(C2, fC2^p)) == t - C2fC2 = lattice_wit_isometry(C2, fC2, ambient_representation=false) + if type(lattice_with_isometry(C2, fC2^p)) == t + C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) push!(results, C2fC2) + return results end - return results end - # these are GA/GM-invariant, fA/fB-stable, and should contain the kernels of any glue map - VA, VAinqA, fVA = _get_V(qA, ambient_isometry(Afa), fqA, minpoly(Bfb), p) - VB, VBinqB, fVB = _get_V(qB, ambient_isometry(Bfb), fqB, minpoly(Afa), p) - + # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map + VA, VAinqA, fVA = _get_V(A, qA, isometry(Afa), fqA, minpoly(Bfb), p) + VB, VBinqB, fVB = _get_V(B, qB, isometry(Bfb), fqB, minpoly(Afa), p) + # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g return results end - # scale of the dual: any glue kernel must contain the multiple of l of the respective + # scale of the dual: any glue kernel must contain the multiples of l of the respective # discriminant groups l = level(genus(C)) - # We look for the GA/GB-invariant and fA/fB-stable subgroups of VA/VB which respectively - # contained lqA/lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) + # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively + # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) subsA = _subgroups_representatives(VAinqA, GA, g, fVA, l) subsB = _subgroups_representatives(VBinqB, GB, g, fVB, l) + # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry - R = [(H1, H2) for H1 in subsA for H2 in subsB if is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1]))[1]] + R = Tuple{eltype(subsA), eltype(subsB), TorQuadModMor}[] + for H1 in subsA, H2 in subsB + ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) + !ok && continue + push!(R, (H1, H2, phi)) + end + # now, for each pair of anti-isometric potential kernels, we need to see whether # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the # corresponding overlattice and check whether it satisfies the type condition - for (H1, H2) in R - #return H1, H2 + for (H1, H2, phi) in R SAinqA, stabA = H1 - return SAinqA SA = domain(SAinqA) - # SA might not be in normal form - SBinqB, stabB = H2 - SB = domain(SBinqB) - # we already know they are but now we get the map - ok, phi = is_anti_isometric_with_anti_isometry(SA, SB) - @assert ok OSAinOqA = embedding_orthogonal_group(SAinqA) + OSA = domain(OSAinOqA) OSAinOD = compose(OSAinOqA, OqAinOD) - OSA = domain(OSAinqA) - # we compute the image of the stabalizer in OSA - actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) - imA = image(actA, stabA)[1] - # we keep track of the element of the stabilizer acting trivially on SA - kerA = [OqAinOD(x) for x in gens(kernel(actA)[1])] - fSA = _restrict(fqA, SAinqA) - fSA = OSA(fSA) + + SBinqB, stabB = H2 + SB = domain(SBinqB) OSBinOqB = embedding_orthogonal_group(SBinqB) - OSAinOD = compose(OSBinOqB, OqBinOD) OSB = domain(OSBinOqB) + OSBinOD = compose(OSBinOqB, OqBinOD) + + # we compute the image of the stabalizers in the respective OS* and we keep track + # of the elements of the stabilizers action trivially in the respective S* + + actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) + imA, _ = image(actA) + kerA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(x) for x in gens(kernel(actA)[1])] + fSA = OSA(_restrict(fqA, SAinqA)) + actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) - imB = image(actB, stabB)[1] - kerB = [OqBinOD(x) for x in gens(kernel(actB)[1])] - fSB = _restrict(fqB, SBinqB) - fSB = OSB(fSB) - # we get all the elements of qB of order exactly p^l, which are not mutiple of an - # element of order p^{l+1}. In theory, glue maps are classified by the orbit of phi - # under the action of O(SB, rho_l(qB), fB) + imB, _ = image(actB) + kerB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(x) for x in gens(kernel(actB)[1])] + fSB = OSB(_restrict(fqB, SBinqB)) + + # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an + # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi + # under the action of O(SB, rho_{l+1}(qB), fB) rBinqB = _rho_functor(qB, p, valuation(l, p)+1) - @assert Oscar._is_invariant(stabB, rBinqB) # otherwise there is something wrong! - #return rBinqB, SBinqB + @assert Oscar._is_invariant(stabB, rBinqB) rBinSB = hom(domain(rBinqB), SB, [SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) - @assert is_injective(rBinSB) # we indeed have rho_l(qB) which is a subgroup of SB + @assert is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB + # We compute the generators of O(SB, rho_l(qB)) - stabrB = [g for g in collect(OSB) if Oscar._is_invariant(g, rBinSB)] - OSBrB, _ = sub(OSB, stabrB) - @assert fSB in OSBrB # otherwise there is something wrong + OrBinOSB = embedding_orthogonal_group(rBinSB) + OSBrB, _ = image(OrBinOSB) + @assert fSB in OSBrB + # 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 glueing. # If not, we try the next potential pair. @@ -403,14 +409,16 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # Now the new phi is "sending" the restriction of fA to this of fB. # So we can glue SA and SB. @assert fSAinOSB == fSB + # Now it is time to compute generators for O(SB, rho_l(qB), fB), and the induced - # image of stabA for taking the double cosets next + # images of stabA|stabB for taking the double cosets next center, _ = centralizer(OSBrB, OSBrB(fSB)) - center, _ = sub(OSB, OSB.(gens(center))) - stabSAphi = [OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] + center, _ = sub(OSB, [OSB(c) for c in gens(center)]) + stabSAphi = AutomorphismGroupElem{TorQuadMod}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] stabSAphi, _ = sub(center, stabSAphi) - stabSB, _ = sub(center, actB.(gens(stabB))) + stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) reps = double_cosets(center, stabSAphi, stabSB) + # now we iterate over all double cosets and for each representative, we compute the # corresponding overlattice in the glueing. If it has the wanted type, we compute # the image of the centralizer in OD from the stabA ans stabB. @@ -419,16 +427,23 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry phig = compose(phi, hom(g)) S = relations(domain(phig)) R = relations(codomain(phig)) + if ambient_space(R) === ambient_space(S) - C2 = overlattice(phig) + _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] + z = zero_matrix(QQ,0, degree(S)) + glue = reduce(vcat, [matrix(QQ, 1, degree(S), g) for g in _glue], init=z) + glue = vcat(basis_matrix(S+R), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + C2 = lattice(ambient_space(S), _B[end-rank(S)-rank(R)+1:end, :]) fC2 = block_diagonal_matrix([fA, fB]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) - @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) else - glue = [lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] + _glue = Vector{fmpq}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] z = zero_matrix(QQ,0,degree(S)+degree(R)) - glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in glue], init=z) + glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in _glue], init=z) glue = vcat(block_diagonal_matrix([basis_matrix(S), basis_matrix(R)]), glue) glue = FakeFmpqMat(glue) _B = hnf(glue) @@ -439,17 +454,27 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry fC2 = __B*fC2*inv(__B) @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end + if type(lattice_with_isometry(C2, fC2^p, ambient_representation=false)) != t continue end + + ext, _ = sub(D, D.(_glue)) + perp, _ = orthogonal_submodule(D, ext) + disc = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), modulus_qf = modulus_quadratic_form(perp)) + disc, discinD = sub(D, gens(disc)) + qC2 = discriminant_group(C2) + ok, phi2 = is_isometric_with_isometry(qC2, disc) + @assert ok + C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - im2_phi = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)]))[1] + im2_phi, _ = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)])) im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) - stab = [(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] - stab = [OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] + stab = Tuple{AutomorphismGroupElem{TorQuadMod}, AutomorphismGroupElem{TorQuadMod}}[(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] + stab = AutomorphismGroupElem{TorQuadMod}[OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] stab = union(stab, kerA) stab = union(stab, kerB) - stab = Oscar._orthogonal_group(discriminant_group(C2), matrix.(stab)) + stab = Oscar._orthogonal_group(discriminant_group(C2), [compose(phi2, compose(_restrict(g, discinD), inv(phi2))).map_ab.map for g in stab]) set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) push!(results, C2fC2) end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 35e8a8836cf1..ea698618f037 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -61,7 +61,7 @@ order_of_isometry(Lf::LatticeWithIsometry) = Lf.n Given a lattice with isometry `(L, f)`, return the rank of the underlying lattice `L`. """ -rank(Lf::LatticeWithIsometry) = rank(lattice(Lf)) +rank(Lf::LatticeWithIsometry) = rank(lattice(Lf))::Integer @doc Markdown.doc""" charpoly(Lf::LatticeWithIsometry) -> fmpq_poly @@ -69,7 +69,7 @@ rank(Lf::LatticeWithIsometry) = rank(lattice(Lf)) Given a lattice with isometry `(L, f)`, return the characteristic polynomial of the underlying isometry `f`. """ -charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf)) +charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf))::fmpq_poly @doc Markdown.doc""" minpoly(Lf::LatticeWithIsometry) -> fmpq_poly @@ -77,7 +77,7 @@ charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf)) Given a lattice with isometry `(L, f)`, return the minimal polynomial of the underlying isometry `f`. """ -minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf)) +minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf))::fmpq_poly @doc Markdown.doc""" genus(Lf::LatticeWithIsometry) -> ZGenus @@ -87,7 +87,7 @@ lattice `L`. For now, in order for the genus to exist, the lattice must be integral. """ -genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus(L) : error("Underlying lattice must be integral"); end +genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus(L)::ZGenus : error("Underlying lattice must be integral"); end @doc Markdown.doc""" ambient_space(Lf::LatticeWithIsometry) -> QuadSpace @@ -95,7 +95,7 @@ genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus( Given a lattice with isometry `(L, f)`, return the ambient space of the underlying lattice `L`. """ -ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf)) +ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, fmpq_mat} ############################################################################### # @@ -150,7 +150,7 @@ function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = t @assert basis_matrix(L)*f_ambient == f*basis_matrix(L) end - return LatticeWithIsometry(L, f, f_ambient, n) + return LatticeWithIsometry(L, f, f_ambient, n)::LatticeWithIsometry end @doc Markdown.doc""" @@ -170,12 +170,12 @@ on the complement of the rational span of `L` if it is not of full rank. function lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true, ambient_representation::Bool = true) if rank(L) == 0 - return LatticeWithIsometry(L, matrix(QQ,0,0,[]), matrix(QQ, 0, 0, []), -1) + return LatticeWithIsometry(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) end n = Oscar._exponent(f) return lattice_with_isometry(L, f, Int(n), check = check, - ambient_representation = ambient_representation) + ambient_representation = ambient_representation)::LatticeWithIsometry end @doc Markdown.doc""" @@ -187,7 +187,7 @@ where `f` corresponds to the identity mapping of `L`. function lattice_with_isometry(L::ZLat) d = degree(L) f = identity_matrix(QQ, d) - return lattice_with_isometry(L, f, check = false, ambient_representation = true) + return lattice_with_isometry(L, f, check = false, ambient_representation = true)::LatticeWithIsometry end ############################################################################### @@ -224,7 +224,7 @@ end ############################################################################### @doc Markdown.doc""" - discriminant_group(Lf::LatticeWithIsometry) -> TorQuadMod, TorQuadModMor + discriminant_group(Lf::LatticeWithIsometry) -> TorQuadMod, AutomorphismGroupElem Given an integral lattice with isometry `Lf`, return the discriminant group `q` of the underlying lattice `L` as well as this image of the underlying isometry @@ -236,7 +236,7 @@ function discriminant_group(Lf::LatticeWithIsometry) @req is_integral(L) "Underlying lattice must be integral" q = discriminant_group(L) Oq = orthogonal_group(q) - return q, Oq(gens(matrix_group(f))[1]) + return (q, Oq(gens(matrix_group(f))[1]))::Tuple{TorQuadMod, AutomorphismGroupElem{TorQuadMod}} end @attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) @@ -262,7 +262,7 @@ end CdL, _ = centralizer(OqL, fqL) GL, _ = sub(OqL, [OqL(s.X) for s in CdL]) end - return GL + return GL::AutomorphismGroup{TorQuadMod} end ############################################################################### diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 15a98b222860..099abde87919 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -14,9 +14,15 @@ function inner_orthogonal_sum(T::TorQuadMod, U::TorQuadMod) rS = relations(T)+relations(U) geneT = [lift(a) for a in gens(T)] geneU = [lift(a) for a in gens(U)] - S = torsion_quadratic_module(cS, rS, gens = vcat(geneT, geneU), modulus = modulus_bilinear_form(T), modulus_qf = modulus_quadratic_form(T)) + S = torsion_quadratic_module(cS, rS, gens = unique([g for g in vcat(geneT, geneU) if !is_zero(g)]), modulus = modulus_bilinear_form(T), modulus_qf = modulus_quadratic_form(T)) TinS = hom(T, S, S.(geneT)) UinS = hom(U, S, S.(geneU)) + if order(S) == 1 + S.gram_matrix_quadratic = matrix(QQ,0,0,[]) + S.gram_matrix_bilinear = matrix(QQ,0,0,[]) + set_attribute!(S, :is_degenerate, false) + S.ab_grp = abelian_group() + end return S, TinS, UinS end @@ -27,18 +33,21 @@ end ############################################################################### function _normalize(T::TorQuadMod) + if order(T) == 1 return T, id_hom(T) end - @assert is_prime(elementary_divisors(T)[end]) - @assert is_degenerate(T) - p = exponent(T) + + p = elementary_divisors(T)[end] + @assert is_prime(p) + qT = Hecke.gram_matrix_quadratic(T) if p == 2 qT = 1//modulus_quadratic_form(T)*qT else qT = 1//modulus_bilinear_form(T)*qT end + r = rank(qT) if r != ncols(qT) dT = denominator(qT) @@ -46,6 +55,7 @@ function _normalize(T::TorQuadMod) else U = identity_matrix(QQ, r) end + nondeg = U[1:r, :] kernel = U[r+1:end, :] qT = nondeg*qT*transpose(nondeg) @@ -60,6 +70,7 @@ function _normalize(T::TorQuadMod) qTR = map_entries(x -> R(ZZ(x)), dT*qT) D = U*qTR*transpose(U) D = map_entries(x -> R(mod(lift(x), dT)), D) + if p != 2 m = ZZ(modulus_quadratic_form(T)//modulus_bilinear_form(T)) D = R(m)^-1*D @@ -69,24 +80,28 @@ function _normalize(T::TorQuadMod) U = U1*U nondeg = U*map_entries(x -> R(ZZ(x)), nondeg) U = vcat(nondeg, map_entries(x -> R(ZZ(x)), kernel)) + @assert nrows(U) == ncols(U) n = ncols(U) - gene = TorQuadModElem[] + gene = elem_type(T)[] + for i in 1:n g = sum(lift(U[i,j])*T[j] for j in 1:ncols(U)) push!(gene, g) end + D, DtoT = sub(T, gene) if !is_degenerate(D) return D, DtoT end + gene = gens(D) qb = Hecke.gram_matrix_bilinear(D) n = ngens(D) - kergens = [gene[i] for i in 1:n if is_zero(qb[i,:])] - nondeg = [gene[i] for i in 1:n if !(gene[i] in kergens)] + kergens = eltype(gene)[gene[i] for i in 1:n if is_zero(qb[i,:])] + nondeg = eltype(gene)[gene[i] for i in 1:n if !(gene[i] in kergens)] k = length(kergens) for i in 1:k @@ -107,8 +122,8 @@ end function orthogonal_group_degenerate(T::TorQuadMod) @req is_degenerate(T) "T must be degenerate" - @req is_prime(elementary_divisors(T)[end]) "T must define a F_p-vector space" p = elementary_divisors(T)[end] + @req is_prime(p) "T must define a F_p-vector space" n = ngens(T) NT, NTtoT = _normalize(T) @assert is_bijective(NTtoT) @@ -120,30 +135,30 @@ function orthogonal_group_degenerate(T::TorQuadMod) NR, NRtoNT = sub(NT, [NT[i] for i in k+1:n]) @assert !is_degenerate(NR) ONR = orthogonal_group(NR) - gensNR = [hom(g) for g in gens(ONR)] + gensNR = TorQuadModMor[hom(g) for g in gens(ONR)] if k > 0 - gene_im = [block_diagonal_matrix([Idk, g.map_ab.map]) for g in gensNR] + gene_im = fmpz_mat[block_diagonal_matrix([Idk, g.map_ab.map]) for g in gensNR] gene_im = union(gene_im, [block_diagonal_matrix([lift(matrix(g)), Idr]) for g in gens(general_linear_group(k, GF(p)))]) else - gene_im = [g.map_ab.ab for g in gensNR] + gene_im = fmpz_mat[g.map_ab.ab for g in gensNR] end if k*r > 0 bas = fmpz_mat[lift(matrix(GF(p), m)) for m in GAP.Globals.Basis(GAP.Globals.MatrixSpace(GAP.Globals.GF(GAP.julia_to_gap(p)), k, r))] - gene2 = [] + gene2 = fmpz_mat[] for g in bas s = identity_matrix(ZZ, n) s[1:k, k+1:end] = g - push!(gene_im, s) + push!(gene2, s) end MG1 = matrix_group([map_entries(GF(p), m) for m in gene_im]) MG2 = matrix_group([map_entries(GF(p), m) for m in vcat(gene_im, gene2)]) gene3 = lift.(matrix.(representative.(double_cosets(MG2, MG1, MG1)))) gene_im = unique(vcat(gene_im, gene3)) end - geneOT = [hom(NT, NT, g) for g in gene_im] - geneOT = [compose(compose(inv(NTtoT), g), NTtoT).map_ab.map for g in geneOT] + geneOT = TorQuadModMor[hom(NT, NT, g) for g in gene_im] + geneOT = fmpz_mat[compose(compose(inv(NTtoT), g), NTtoT).map_ab.map for g in geneOT] OT = _orthogonal_group(T, geneOT, check=false) - @asert order(OT) == p*k*r*order(ONR)*order(GL(k, p)) + @assert order(OT) == p^(k*r)*order(ONR)*order(GL(k, GF(p))) return OT end @@ -235,7 +250,7 @@ end # ############################################################################## -function embedding_orthogonal_group(i) +function embedding_orthogonal_group(i::TorQuadModMor) ok, j = has_complement(i) @req ok "domain(i) needs to have a complement in codomain(i)" A = domain(i) @@ -243,25 +258,31 @@ function embedding_orthogonal_group(i) D = codomain(i) gene = vcat(i.(gens(A)), j.(gens(B))) n = ngens(A) - OD, OA, = orthogonal_group.([D, A]) + OD = orthogonal_group(D) + OA = is_degenerate(A) ? orthogonal_group_degenerate(A) : orthogonal_group(A) geneOA = elem_type(OD)[] for f in gens(OA) imgs = [i(f(a)) for a in gens(A)] imgs = vcat(imgs, gene[n+1:end]) - _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) - _f = hom(D.ab_grp, D.ab_grp, _f) + if can_solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs])) + _f = solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs])) + _f = hom(D.ab_grp, D.ab_grp, _f) + else + _f = solve(reduce(vcat, [data(a).coeff for a in imgs]), reduce(vcat, [data(a).coeff for a in gene])) + _f = inv(hom(D.ab_grp, D.ab_grp, _f)) + end f = TorQuadModMor(D, D, _f) push!(geneOA, OD(f)) end OAtoOD = hom(OA, OD, gens(OA), geneOA) - return OAtoOD + return OAtoOD::GAPGroupHomomorphism end function embedding_orthogonal_group(i1, i2) D = codomain(i1) A = domain(i1) - B = domain(i2) + B = domain(i2) gene = vcat(i1.(gens(A)), i2.(gens(B))) n = ngens(A) OD, OA, OB = orthogonal_group.([D, A, B]) @@ -287,7 +308,7 @@ function embedding_orthogonal_group(i1, i2) OAtoOD = hom(OA, OD, gens(OA), geneOA) OBtoOD = hom(OB, OD, gens(OB), geneOB) - return OAtoOD, OBtoOD + return (OAtoOD, OBtoOD)::Tuple{GAPGroupHomomorphism, GAPGroupHomomorphism} end function _as_Fp_vector_space_quotient(HinV, p, f) From f60ad77f2e094ad4814439b545636a133c419cde Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 11 Nov 2022 12:01:36 +0100 Subject: [PATCH 13/76] more --- .../LatticesWithIsometry/Enumeration.jl | 302 ++++++++++++++++++ .../LatticesWithIsometry/TraceEquivalence.jl | 2 +- 2 files changed, 303 insertions(+), 1 deletion(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index d07d4b8ba49e..913fe932d125 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -7,6 +7,12 @@ export admissible_triples, is_admissible_triple # ################################################################################## +################################################################################## +# +# Admissible triples +# +################################################################################## + # The tuples in output are pairs of positive integers! function _tuples_divisors(d::T) where T <: Union{Integer, fmpz} div = divisors(d) @@ -229,6 +235,12 @@ end admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatticeWithIsometry} = admissible_triples(genus(L), p) +################################################################################## +# +# Primitive extensions +# +################################################################################## + function _get_V(L, q, f, fq, mu, p) f_mu = mu(f) if !is_zero(f_mu) @@ -482,3 +494,293 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry return results end + +################################################################################## +# +# Representatives of lattices with isometry +# +################################################################################## + +function _ideals_of_norm(E, d) + @assert E isa Hecke.NfRel + K = base_field(E) + OK = maximal_order(K) + OE = maximal_order(E) + DE = different(OE) + ids = [] + primes = [] + for p in prime_divisors(d) + v = valuation(d, p) + for (P, _) in prime_decomposition(OK, p) + if !is_coprime(DE, ideal(OE, P)) + P = prime_decomposition(OE, P)[1][1] + else + P = ideal(OE, P) + end + nv = valuation(absolute_norm(P), p) + push!(primes, [P^e for e in 1:divrem(v, nv)[1]]) + end + end + for I in Hecke.cartesian_product_iterator(primes) + I = prod(I) + if absolute_norm(I) == d + push!(ids, I) + end + end + return ids +end + +function _possible_signatures(s1, s2, E) + @assert E isa Hecke.NfRel + ok, q = Hecke.is_cyclotomic_type(E) + @assert ok + @assert iseven(s2) + @assert divides(2*(s1+s2), euler_phi(q))[1] + l = divexact(s2, 2) + K = base_field(E) + inf = real_places(K) + s = length(inf) + signs = Dict{typeof(inf[1]), Int}[] + parts = Vector{Int}[] + perm = AllPerms(s) + for v in AllParts(l) + if length(v) > s + continue + end + while length(v) != s + push!(v, 0) + end + for vv in perm + v2 = v[vv.d] + v2 in parts ? continue : push!(parts, v2) + end + end + for v in parts + push!(signs, Dict(a => b for (a,b) in zip(inf, v))) + end + return signs +end + +function representatives(Lf::LatticeWithIsometry, m::Int = 1) + @req m >= 1 "m must be a positive integer" + @req Oscar._is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polyomial must be irreducible and cyclotomic" + L = lattice(Lf) + rk = rank(L) + d = det(L) + n = order_of_isometry(Lf) + t = type(Lf) + s1, _, s2 = signature_tuple(L) + reps = LatticeWithIsometry[] + if n*m < 3 + gene = genera((s1, s2), ZZ(d), max_scale=scale(L), even=iseven(L)) + f = (-1)^(n*m+1)*identity_matrix(QQ, rk) + for G in gene + repre = representatives(G) + append!(reps, [lattice_with_isometry(LL, f, check=false) for LL in repre]) + end + return reps + end + ok, rk = divides(rk, euler_phi(n*m)) + if !ok + return reps + end + + gene = [] + E, b = cyclotomic_field_as_cm_extension(n*m, cached=false) + Eabs, EabstoE = absolute_simple_field(E) + DE = EabstoE(different(maximal_order(Eabs))) + K = base_field(E) + OE = maximal_order(E) + OK = maximal_order(K) + msE = E(scale(L)*n*m)*inv(DE) + ndE = ZZ(d*(absolute_norm(n*m*inv(DE))^rk)) + detE = _ideals_of_norm(E, ndE) + signatures = _possible_signatures(s1, s2, E) + for d in detE, sign in signatures + append!(gene, genera_hermitian(E, rk, sign, d)) + end + gene = unique(gene) + for g in gene + for H in representatives(g) + M = trace_lattice(H) + @assert det(lattice(M)) == det + @assert Oscar._is_cyclotomic_polynomial(minpoly(M)) + @assert order_of_isometry(M) == n*m + iseven(lattice(M)) == iseven(L) ? push!(reps, M) : continue + @assert type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) == t + end + end + return reps +end + +function representatives(t::Dict, m::Integer = 1; check::Bool = true) + @req m >= 1 "m must be a positive integer" + ke = collect(keys(t)) + n = max(ke) + if check + @req Set(divisors(n)) == Set(ke) "t does not define a type for lattices with isometry" + for i in ke + @req t[i][2] isa ZGenus "t does not define a type for lattices with isometry" + @req typeof(t[i][1]) <: Union{ZGenus, GenusHerm} "t does not define a type for lattices with isometry" + end + @req all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) "Minimal polynomial should be irreducible and cyclotomic" + @req rank(t[n][2]) == rank(t[n][1])*euler_phi(n) "Top-degree types do not agree" + end + G = t[n] + s1, s2 = signature_tuple(G) + rk = s1+s2 + d = det(G) + reps = LatticeWithIsometry[] + if n*m < 3 + gene = genera((s1, s2), ZZ(d), max_scale=scale(G), even=iseven(G)) + f = (-1)^(n*m+1)*identity_matrix(QQ, rk) + for g in gene + repre = representatives(g) + append!(reps, [lattice_with_isometry(LL, f, check=false) for LL in repre]) + end + return reps + end + ok, rk = divides(rk, euler_phi(n*m)) + if !ok + return reps + end + + gene = [] + E, b = cyclotomic_field_as_cm_extension(n*m, cached=false) + Eabs, EabstoE = absolute_simple_field(E) + DE = EabstoE(different(maximal_order(Eabs))) + K = base_field(E) + OE = maximal_order(E) + OK = maximal_order(K) + msE = E(scale(L)*n*m)*inv(DE) + ndE = ZZ(d*(absolute_norm(n*m*inv(DE))^rk)) + detE = _ideals_of_norm(E, ndE) + signatures = _possible_signatures(s1, s2, E) + for d in detE, sign in signatures + append!(gene, genera_hermitian(E, rk, sign, d)) + end + gene = unique(gene) + for g in gene + for H in representatives(g) + M = trace_lattice(H) + @assert det(lattice(M)) == det + iseven(lattice(M)) == iseven(G) ? push!(reps, M) : continue + @assert type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) == t + end + end + return reps +end + +function split(Lf::LatticeWithIsometry, p::Int) + @req is_prime(p) "p must be a prime number" + @req Oscar._is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polynomial must be irreducible and cyclotomic" + ok, q, d = is_prime_power_with_data(order_of_isometry(Lf)) + @req ok "Order of isometry must be a prime power" + @req p != q "Prime numbers must be distinct" + reps = LatticeWithIsometry[] + atp = admissible_triples(Lf, p) + for (A, B) in atp + LA = lattice_with_isometry(representative(A)) + RA = representatives(LA, p*q^d) + LB = lattice_with_isometry(representative(B)) + RB = representatives(LB, q^d) + for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) + E = primitive_extensions(L1, L2, Lf, p) + @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p) == type(Lf)), E) + append!(reps, E) + end + end + return reps +end + +function first_p(Lf::LatticeWithIsometry, p::Int, b::Int = 0) + @req is_prime(p) "p must be a prime number" + @req b in [0, 1] "b must be an integer equal to 0 or 1" + ok, q, e = is_prime_power_with_data(order_of_isometry(Lf)) + @req ok "Order of isometry must be a prime power" + @req p != q "Prime numbers must be distinct" + reps = LatticeWithIsometry[] + if e == 0 + return split(Lf, p) + end + x = gen(Hecke.Globals.Qx) + A0 = kernel_lattice(Lf, q^e) + B0 = kernel_lattice(Lf, x^(q^e-1)-1) + A = split(A0, p) + B = first_p(B0, p) + for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) + b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue + E = primitive_extensions(L1, L2, Lf, q) + @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p)) == type(Lf), E) + @assert b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) + append!(reps, E) + end + return reps +end + +function pure_up(Lf::LatticeWithIsometry, p::Int) + @req is_prime(p) "p must be a prime number" + @req order_of_isometry(Lf) > 0 "Isometry must be of finite order" + n = order_of_isometry(Lf) + pd = prime_divisors(n) + @req length(pd) <= 2 && p in pd "Order must be divisible by p and have at most 2 prime factors" + if length(pd) == 2 + q = pd[1] == p ? pd[2] : pd[1] + d = valuation(n, p) + e = valuation(n, q) + else + q = 1 + d = valuation(n, p) + e = 0 + end + phi = minpoly(Lf) + x = gen(parent(phi)) + chi = prod([cyclotomic(x, p^d*q^i) for i=0:e]) + @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" + reps = LatticeWithIsometry[] + if e == 0 + return representatives(Lf, p) + end + A0 = kernel_lattice(Lf, p^d*q^e) + bool, r = divides(phi, cyclotomic(x, p^d*q^e)) + @assert bool + B0 = kernel_lattice(Lf, r) + A = representatives(A0 ,p) + B = pure_up(B0, p) + for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) + E = extensions(LA, LB, Lf, q) + @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p)) == type(Lf), E) + append!(reps, E) + end + return reps +end + +function next_p(Lf::LatticeWithIsometry, p) + n = order_of_isometry(Lf) + @req n > 0 "Isometry must be of finite order" + pd = prime_divisors(n) + @req length(pd) <= 2 "Order must have at most 2 prime divisors" + if !(p in pd) + return first_p(Lf, p, 1) + end + d = valuation(n, p) + if n != p^d + _, q, e = is_prime_power_with_data(divexact(n, p^d)) + else + q = 1 + e = 0 + end + x = gen(parent(minpoly(Lf))) + B0 = kernel_lattice(Lf, x^(divexact(n, p)) - 1) + A0 = kernel_lattice(Lf, prod([cyclotomic(x, p^d*q^i) for i in 0:e])) + A = pure_up(A0, p) + B = next_p(B0, p) + for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) + E = extensions(LA, LB, Lf, p) + @assert all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) + @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p)) == type(Lf), E) + append!(reps, E) + end + return reps +end + diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index c0d217f82904..b9eb26d74026 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -59,7 +59,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T gram = matrix(QQ, d*n, d*n, coeffs) @assert f*gram*transpose(f) == gram LL = Zlattice(gram = 1//m*gram) - return lattice_with_isometry(LL, f, m, check = false) + return lattice_with_isometry(LL, f, ambient_representation=false, check = false) end @doc Markdown.doc""" From 71155d829daf52862f1918f562082e17aa25edf0 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 14 Nov 2022 15:09:49 +0100 Subject: [PATCH 14/76] more --- .../LatticesWithIsometry/Enumeration.jl | 107 ++++++++++-------- .../LatticesWithIsometry.jl | 12 +- src/NumberTheory/LatticesWithIsometry/Misc.jl | 48 -------- .../LatticesWithIsometry/TraceEquivalence.jl | 50 ++++---- 4 files changed, 89 insertions(+), 128 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 913fe932d125..1250dc89bec1 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -79,7 +79,6 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) end @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" - @req prime_divisors(level(C)) == [p] || level(C) == one(fmpq) "The level of C must be a power of p" AperpB = orthogonal_sum(A,B) # If A and B glue to C, the sum of their ranks must match the one of C if rank(AperpB) != rank(C) @@ -331,7 +330,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry C2 = orthogonal_sum(A, B)[1] fC2 = block_diagonal_matrix(fA, fB) end - if type(lattice_with_isometry(C2, fC2^p)) == t + if type(lattice_with_isometry(C2, fC2^p, ambient_representation = false)) == t C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) push!(results, C2fC2) @@ -417,10 +416,10 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry bool, g0 = representative_action(OSBrB, fSAinOSBrB, OSBrB(fSB)) bool || continue phi = compose(phi, hom(OSB(g0))) - fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + fSAinOSBrB = OSB(compose(inv(phi), compose(hom(fSA), phi))) # Now the new phi is "sending" the restriction of fA to this of fB. # So we can glue SA and SB. - @assert fSAinOSB == fSB + @assert fSAinOSBrB == fSB # 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 @@ -511,13 +510,14 @@ function _ideals_of_norm(E, d) primes = [] for p in prime_divisors(d) v = valuation(d, p) - for (P, _) in prime_decomposition(OK, p) - if !is_coprime(DE, ideal(OE, P)) - P = prime_decomposition(OE, P)[1][1] + pd = [P[1] for P in prime_decomposition(OK, p)] + for i in 1:length(pd) + if !is_coprime(DE, ideal(OE, pd[i])) + P = prime_decomposition(OE, pd[i])[1][1] else - P = ideal(OE, P) + P = ideal(OE, pd[i]) end - nv = valuation(absolute_norm(P), p) + nv = valuation(norm(P), pd[i]) push!(primes, [P^e for e in 1:divrem(v, nv)[1]]) end end @@ -563,7 +563,7 @@ end function representatives(Lf::LatticeWithIsometry, m::Int = 1) @req m >= 1 "m must be a positive integer" - @req Oscar._is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polyomial must be irreducible and cyclotomic" + @req is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polyomial must be irreducible and cyclotomic" L = lattice(Lf) rk = rank(L) d = det(L) @@ -572,43 +572,58 @@ function representatives(Lf::LatticeWithIsometry, m::Int = 1) s1, _, s2 = signature_tuple(L) reps = LatticeWithIsometry[] if n*m < 3 + @info "Order smaller than 3" gene = genera((s1, s2), ZZ(d), max_scale=scale(L), even=iseven(L)) + @info "All possible genera: $(length(gene))" f = (-1)^(n*m+1)*identity_matrix(QQ, rk) for G in gene repre = representatives(G) + @info "$(length(repre)) representatives" append!(reps, [lattice_with_isometry(LL, f, check=false) for LL in repre]) end return reps end + @info "Order bigger than 3" ok, rk = divides(rk, euler_phi(n*m)) if !ok return reps end gene = [] - E, b = cyclotomic_field_as_cm_extension(n*m, cached=false) + E, b = cyclotomic_field_as_cm_extension(n*m) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) + DE = different(maximal_order(E)) + @info "We have the different" K = base_field(E) OE = maximal_order(E) OK = maximal_order(K) msE = E(scale(L)*n*m)*inv(DE) - ndE = ZZ(d*(absolute_norm(n*m*inv(DE))^rk)) + ndE = ZZ(d*absolute_norm(n*m*inv(DE))^rk) detE = _ideals_of_norm(E, ndE) + @info "All possible ideal dets: $(length(detE))" signatures = _possible_signatures(s1, s2, E) - for d in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, d)) + @info "All possible signatures: $(length(signatures))" + for dd in detE, sign in signatures + append!(gene, genera_hermitian(E, rk, sign, dd)) end gene = unique(gene) + @info "All possible genera: $(length(gene))" for g in gene - for H in representatives(g) - M = trace_lattice(H) - @assert det(lattice(M)) == det - @assert Oscar._is_cyclotomic_polynomial(minpoly(M)) - @assert order_of_isometry(M) == n*m - iseven(lattice(M)) == iseven(L) ? push!(reps, M) : continue - @assert type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) == t + @info "g = $g" + H = representative(g) + @info "$H" + M = trace_lattice(H) + @assert det(lattice(M)) == d + @assert is_cyclotomic_polynomial(minpoly(M)) + @assert order_of_isometry(M) == n*m + if iseven(lattice(M)) != iseven(L) + continue end + #if type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) != t + # continue + #end + push!(reps, M) end return reps end @@ -616,17 +631,17 @@ end function representatives(t::Dict, m::Integer = 1; check::Bool = true) @req m >= 1 "m must be a positive integer" ke = collect(keys(t)) - n = max(ke) + n = maximum(ke) if check @req Set(divisors(n)) == Set(ke) "t does not define a type for lattices with isometry" for i in ke @req t[i][2] isa ZGenus "t does not define a type for lattices with isometry" - @req typeof(t[i][1]) <: Union{ZGenus, GenusHerm} "t does not define a type for lattices with isometry" + @req typeof(t[i][1]) <: Union{ZGenus, Hecke.GenusHerm} "t does not define a type for lattices with isometry" end @req all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) "Minimal polynomial should be irreducible and cyclotomic" @req rank(t[n][2]) == rank(t[n][1])*euler_phi(n) "Top-degree types do not agree" end - G = t[n] + G = t[n][2] s1, s2 = signature_tuple(G) rk = s1+s2 d = det(G) @@ -652,30 +667,30 @@ function representatives(t::Dict, m::Integer = 1; check::Bool = true) K = base_field(E) OE = maximal_order(E) OK = maximal_order(K) - msE = E(scale(L)*n*m)*inv(DE) + msE = E(scale(G)*n*m)*inv(DE) ndE = ZZ(d*(absolute_norm(n*m*inv(DE))^rk)) detE = _ideals_of_norm(E, ndE) signatures = _possible_signatures(s1, s2, E) - for d in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, d)) + for dd in detE, sign in signatures + append!(gene, genera_hermitian(E, rk, sign, dd)) end gene = unique(gene) for g in gene - for H in representatives(g) - M = trace_lattice(H) - @assert det(lattice(M)) == det - iseven(lattice(M)) == iseven(G) ? push!(reps, M) : continue - @assert type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) == t - end + H = representative(g) + M = trace_lattice(H) + @assert det(lattice(M)) == d + iseven(lattice(M)) == iseven(G) ? push!(reps, M) : continue + #@assert type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) == t end return reps end -function split(Lf::LatticeWithIsometry, p::Int) +function split_p(Lf::LatticeWithIsometry, p::Int) + rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" - @req Oscar._is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polynomial must be irreducible and cyclotomic" + @req is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polynomial must be irreducible and cyclotomic" ok, q, d = is_prime_power_with_data(order_of_isometry(Lf)) - @req ok "Order of isometry must be a prime power" + @req ok || d ==0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" reps = LatticeWithIsometry[] atp = admissible_triples(Lf, p) @@ -686,7 +701,6 @@ function split(Lf::LatticeWithIsometry, p::Int) RB = representatives(LB, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) E = primitive_extensions(L1, L2, Lf, p) - @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p) == type(Lf)), E) append!(reps, E) end end @@ -694,24 +708,24 @@ function split(Lf::LatticeWithIsometry, p::Int) end function first_p(Lf::LatticeWithIsometry, p::Int, b::Int = 0) + rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" @req b in [0, 1] "b must be an integer equal to 0 or 1" ok, q, e = is_prime_power_with_data(order_of_isometry(Lf)) - @req ok "Order of isometry must be a prime power" + @req ok || e == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" reps = LatticeWithIsometry[] if e == 0 - return split(Lf, p) + return split_p(Lf, p) end x = gen(Hecke.Globals.Qx) A0 = kernel_lattice(Lf, q^e) B0 = kernel_lattice(Lf, x^(q^e-1)-1) - A = split(A0, p) + A = split_p(A0, p) B = first_p(B0, p) for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue E = primitive_extensions(L1, L2, Lf, q) - @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p)) == type(Lf), E) @assert b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) end @@ -719,6 +733,7 @@ function first_p(Lf::LatticeWithIsometry, p::Int, b::Int = 0) end function pure_up(Lf::LatticeWithIsometry, p::Int) + rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" @req order_of_isometry(Lf) > 0 "Isometry must be of finite order" n = order_of_isometry(Lf) @@ -735,27 +750,27 @@ function pure_up(Lf::LatticeWithIsometry, p::Int) end phi = minpoly(Lf) x = gen(parent(phi)) - chi = prod([cyclotomic(x, p^d*q^i) for i=0:e]) + chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" reps = LatticeWithIsometry[] if e == 0 return representatives(Lf, p) end A0 = kernel_lattice(Lf, p^d*q^e) - bool, r = divides(phi, cyclotomic(x, p^d*q^e)) + bool, r = divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) @assert bool B0 = kernel_lattice(Lf, r) A = representatives(A0 ,p) B = pure_up(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = extensions(LA, LB, Lf, q) - @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p)) == type(Lf), E) append!(reps, E) end return reps end function next_p(Lf::LatticeWithIsometry, p) + rank(Lf) == 0 && return LatticeWithIsometry[Lf] n = order_of_isometry(Lf) @req n > 0 "Isometry must be of finite order" pd = prime_divisors(n) @@ -770,15 +785,15 @@ function next_p(Lf::LatticeWithIsometry, p) q = 1 e = 0 end + reps = LatticeWithIsometry[] x = gen(parent(minpoly(Lf))) B0 = kernel_lattice(Lf, x^(divexact(n, p)) - 1) - A0 = kernel_lattice(Lf, prod([cyclotomic(x, 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 = pure_up(A0, p) B = next_p(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = extensions(LA, LB, Lf, p) @assert all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) - @assert all(LL -> type(lattice_with_isometry(lattice(LL), ambient_isometry(LL)^p)) == type(Lf), E) append!(reps, E) end return reps diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index ea698618f037..f4cdb8b5326f 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -125,7 +125,7 @@ function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = t if check @req det(f) != 0 "f is not invertible" - m = Oscar._exponent(f) + m = multiplicative_order(f) @req m > 0 "f is not finite" @req n == m "The order of f is equal to $m, not $n" end @@ -170,10 +170,10 @@ on the complement of the rational span of `L` if it is not of full rank. function lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true, ambient_representation::Bool = true) if rank(L) == 0 - return LatticeWithIsometry(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) + return LatticeWithIsometry(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) end - n = Oscar._exponent(f) + n = multiplicative_order(f) return lattice_with_isometry(L, f, Int(n), check = check, ambient_representation = ambient_representation)::LatticeWithIsometry end @@ -296,7 +296,7 @@ function signatures(Lf::LatticeWithIsometry) @req rank(Lf) != 0 "Signatures non available for the empty lattice" L = lattice(Lf) f = isometry(Lf) - @req Oscar._is_cyclotomic_polynomial(minpoly(f)) "Minimal polynomial must be irreducible and cyclotomic" + @req is_cyclotomic_polynomial(minpoly(f)) "Minimal polynomial must be irreducible and cyclotomic" n = order_of_isometry(Lf) @req divides(rank(L), euler_phi(n))[1] "The totient of the order of the underlying isometry must divide the rank of the underlying lattice" C = CalciumField() @@ -346,7 +346,7 @@ kernel_lattice(Lf::LatticeWithIsometry, p::fmpz_poly) = kernel_lattice(Lf, chang function kernel_lattice(Lf::LatticeWithIsometry, l::Integer) @req divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" - p = Oscar._cyclotomic_polynomial(l) + p = cyclotomic_polynomial(l) return kernel_lattice(Lf, p) end @@ -377,7 +377,7 @@ end x = gen(Qx) t = Dict{Integer, Tuple}() for l in divs - Hl = kernel_lattice(Lf, Oscar._cyclotomic_polynomial(l)) + Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) Hl = inverse_trace_lattice(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false) end diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 099abde87919..830d25364d3e 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -162,54 +162,6 @@ function orthogonal_group_degenerate(T::TorQuadMod) return OT end -############################################################################### -# -# Cyclotomic polynomials -# -############################################################################### - -function _cyclotomic_polynomial(n::Int64) - @assert n > 0 - _, x = QQ["x"] - return Hecke.cyclotomic(n, x) -end - -function _is_cyclotomic_polynomial(p::Union{fmpz_poly, fmpq_poly}) - n = degree(p) - R = parent(p) - x = gen(R) - list_cyc = union(Int64[k for k in euler_phi_inv(n)], [1]) - list_poly = [Hecke.cyclotomic(k, x) for k in list_cyc] - return any(q -> R(collect(coefficients(q))) == p, list_poly) -end - -############################################################################### -# -# Exponent of fmpq/fmpz_mat -# -############################################################################### - -function _is_of_finite_exponent(f::Union{fmpq_mat, fmpz_mat}) - !Hecke.is_squarefree(minpoly(f)) && return false - chi = charpoly(f) - fact = collect(factor(chi)) - return all(p -> _is_cyclotomic_polynomial(p[1]), fact) -end - -function _exponent(f::Union{fmpq_mat, fmpz_mat}) - !_is_of_finite_exponent(f) && return -1 - degs = unique(degree.([p[1] for p in collect(factor(minpoly(f)))])) - exps = euler_phi_inv(degs[1])::Vector{fmpz} - for i in 2:length(degs) - union!(exps, euler_phi_inv(degs[i])) - end - maxdeg = lcm(exps) - divmd = divisors(maxdeg) - n = findfirst(k -> isone(f^k), divmd) - @assert n !== nothing - return divmd[n] -end - function _restrict(f::TorQuadModMor, i::TorQuadModMor) imgs = TorQuadModElem[] V = domain(i) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index b9eb26d74026..4621ddf3cbc2 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -17,9 +17,9 @@ $\mathbb Z$-lattice. """ function trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T @req order > 0 "The order must be positive" + @req degree(L) == rank(L) "Lattice must be of full rank" n = degree(L) E = base_field(L) - G = gram_matrix(rational_span(L)) if E == QQ || E == ZZ if order == 1 @@ -33,33 +33,27 @@ function trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T end bool, m = Hecke.is_cyclotomic_type(E) - @req bool "Base field must be cyclotomic" - Eabs, EabstoE = Hecke.absolute_simple_field(E) - EtoEabs = pseudo_inv(EabstoE) - d = degree(Eabs) - Gabs = map_entries(EtoEabs, G) - z = gen(Eabs) - chi = minpoly(z) + G = gram_matrix(ambient_space(L)) + d = absolute_degree(E) + coeffs = [] + b = gen(E) + for i in 1:n, iz in 0:d-1, j in 1:n, jz in 0:d-1 + g = b^(iz-jz)*G[i,j] + push!(coeffs, trace(g, QQ)) + end + V = quadratic_space(QQ, matrix(QQ,n*d, n*d, coeffs)) + gene = generators(L) + gene = [transpose(matrix(reduce(vcat, absolute_coordinates.(v)))) for v in gene] + BM = reduce(vcat, gene) + LL = lattice(V, BM) + LL = rescale(LL, 1//m) + chi = absolute_minpoly(b) Mchi = companion_matrix(chi) f = block_diagonal_matrix([Mchi for i=1:n]) - - coeffs = fmpq[] - for i=1:n - for iz=1:d - for j=1:n - for jz=1:d - g = z^(iz-jz)*Gabs[i,j] - push!(coeffs, trace(g)) - end - end - end - end - gram = matrix(QQ, d*n, d*n, coeffs) - @assert f*gram*transpose(f) == gram - LL = Zlattice(gram = 1//m*gram) - return lattice_with_isometry(LL, f, ambient_representation=false, check = false) + return LL, f + return lattice_with_isometry(LL, f, ambient_representation=true, check = true) end @doc Markdown.doc""" @@ -76,7 +70,7 @@ multiplication by the `n`th root of unity correspond to the mapping via `f`. function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Bool = true, ambient_representation::Bool = true) if n <= 0 - Oscar._exponent(f) + n = multiplicative_order(f) end @req n > 0 "f is not of finite exponent" @@ -89,13 +83,13 @@ function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Boo if check @req n >= 3 "No hermitian inverse trace lattice for order less than 3" G = gram_matrix(L) - @req Oscar._is_cyclotomic_polynomial(minpoly(f)) "The minimal polynomial of f must be irreducible and cyclotomic" + @req is_cyclotomic_polynomial(minpoly(f)) "The minimal polynomial of f must be irreducible and cyclotomic" @req f*G*transpose(f) == G "f does not define an isometry of L" - @req Oscar._exponent(f) == n "The order of f should be equal to n" + @req multiplicative_order(f) == n "The order of f should be equal to n" @req divides(rank(L), euler_phi(n))[1] "The totient of n must divides the rank of L" end - E,b = cyclotomic_field_as_cm_extension(n, cached = false) + E,b = cyclotomic_field_as_cm_extension(n) m = divexact(rank(L), euler_phi(n)) # We choose as basis for the hermitian lattice Lh the identity matrix From 392df671aa25885af981fec855f6750c25c74917 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 23 Nov 2022 15:41:09 +0100 Subject: [PATCH 15/76] more --- .../LatticesWithIsometry/Enumeration.jl | 135 +++++++++++------- .../LatticesWithIsometry.jl | 71 +++++++-- .../LatticesWithIsometry/TraceEquivalence.jl | 119 +++++++++------ .../LatticesWithIsometry/Types.jl | 6 +- 4 files changed, 224 insertions(+), 107 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 1250dc89bec1..010704f9bf9d 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -1,4 +1,4 @@ -export admissible_triples, is_admissible_triple +export admissible_triples, is_admissible_triple, representatives_of_pure_type ################################################################################## # @@ -500,7 +500,17 @@ end # ################################################################################## -function _ideals_of_norm(E, d) +function _ideals_of_norm(E, d::fmpq) + 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))] + 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))])] + end +end + +function _ideals_of_norm(E, d::fmpz) @assert E isa Hecke.NfRel K = base_field(E) OK = maximal_order(K) @@ -561,45 +571,45 @@ function _possible_signatures(s1, s2, E) return signs end -function representatives(Lf::LatticeWithIsometry, m::Int = 1) +function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) @req m >= 1 "m must be a positive integer" - @req is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polyomial must be irreducible and cyclotomic" + @req is_of_pure_type(Lf) "Minimal polyomial must be irreducible and cyclotomic" L = lattice(Lf) rk = rank(L) d = det(L) n = order_of_isometry(Lf) - t = type(Lf) s1, _, s2 = signature_tuple(L) reps = LatticeWithIsometry[] + if n*m < 3 @info "Order smaller than 3" - gene = genera((s1, s2), ZZ(d), max_scale=scale(L), even=iseven(L)) + gene = genera((s1, s2), ZZ(d), even=iseven(L)) @info "All possible genera: $(length(gene))" f = (-1)^(n*m+1)*identity_matrix(QQ, rk) for G in gene repre = representatives(G) @info "$(length(repre)) representatives" - append!(reps, [lattice_with_isometry(LL, f, check=false) for LL in repre]) + for LL in repre + is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) + end end return reps end @info "Order bigger than 3" ok, rk = divides(rk, euler_phi(n*m)) + if !ok return reps end - + + t = type(Lf) gene = [] E, b = cyclotomic_field_as_cm_extension(n*m) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) - DE = different(maximal_order(E)) @info "We have the different" K = base_field(E) - OE = maximal_order(E) - OK = maximal_order(K) - msE = E(scale(L)*n*m)*inv(DE) - ndE = ZZ(d*absolute_norm(n*m*inv(DE))^rk) + ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) @info "All possible ideal dets: $(length(detE))" signatures = _possible_signatures(s1, s2, E) @@ -612,93 +622,107 @@ function representatives(Lf::LatticeWithIsometry, m::Int = 1) for g in gene @info "g = $g" H = representative(g) + if !is_integral(DE*scale(H)) + continue + end @info "$H" M = trace_lattice(H) + return M @assert det(lattice(M)) == d @assert is_cyclotomic_polynomial(minpoly(M)) @assert order_of_isometry(M) == n*m if iseven(lattice(M)) != iseven(L) continue end - #if type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) != t - # continue - #end - push!(reps, M) + if !is_of_type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m), t) + continue + end + append!(reps, [trace_lattice(HH) for HH in genus_representatives(H)]) end return reps end -function representatives(t::Dict, m::Integer = 1; check::Bool = true) +function representatives_of_pure_type(t::Dict, m::Integer = 1; check::Bool = true) + M = representative(t, check = check) + M isa String && return M + return type_representatives(M, m) +end + +function representative(t::Dict; check::Bool = true) @req m >= 1 "m must be a positive integer" + !check || is_pure(t) || error("t must be pure") + ke = collect(keys(t)) n = maximum(ke) - if check - @req Set(divisors(n)) == Set(ke) "t does not define a type for lattices with isometry" - for i in ke - @req t[i][2] isa ZGenus "t does not define a type for lattices with isometry" - @req typeof(t[i][1]) <: Union{ZGenus, Hecke.GenusHerm} "t does not define a type for lattices with isometry" - end - @req all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) "Minimal polynomial should be irreducible and cyclotomic" - @req rank(t[n][2]) == rank(t[n][1])*euler_phi(n) "Top-degree types do not agree" - end + G = t[n][2] s1, s2 = signature_tuple(G) rk = s1+s2 d = det(G) - reps = LatticeWithIsometry[] - if n*m < 3 + + if n < 3 gene = genera((s1, s2), ZZ(d), max_scale=scale(G), even=iseven(G)) - f = (-1)^(n*m+1)*identity_matrix(QQ, rk) + f = (-1)^(n+1)*identity_matrix(QQ, rk) for g in gene repre = representatives(g) append!(reps, [lattice_with_isometry(LL, f, check=false) for LL in repre]) end return reps end - ok, rk = divides(rk, euler_phi(n*m)) + + ok, rk = divides(rk, euler_phi(n)) if !ok return reps end gene = [] - E, b = cyclotomic_field_as_cm_extension(n*m, cached=false) + E, b = cyclotomic_field_as_cm_extension(n, cached=false) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) - K = base_field(E) - OE = maximal_order(E) - OK = maximal_order(K) - msE = E(scale(G)*n*m)*inv(DE) - ndE = ZZ(d*(absolute_norm(n*m*inv(DE))^rk)) + ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) + @info "All possible ideal dets: $(length(detE))" signatures = _possible_signatures(s1, s2, E) + @info "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures append!(gene, genera_hermitian(E, rk, sign, dd)) end gene = unique(gene) for g in gene H = representative(g) + if !is_integral(DE*scale(H)) + continue + end + H = H M = trace_lattice(H) @assert det(lattice(M)) == d - iseven(lattice(M)) == iseven(G) ? push!(reps, M) : continue - #@assert type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) == t + @assert is_cyclotomic_polynomial(minpoly(M)) + @assert order_of_isometry(M) == n + if iseven(lattice(M)) != iseven(G) + continue + end + if !is_of_type(M, t) + continue + end + return M end - return reps + return "Empty type" end -function split_p(Lf::LatticeWithIsometry, p::Int) +function prime_splitting_of_pure_type(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" - @req is_cyclotomic_polynomial(minpoly(Lf)) "Minimal polynomial must be irreducible and cyclotomic" + @req is_of_pure_type(Lf) "Minimal polynomial must be irreducible and cyclotomic" ok, q, d = is_prime_power_with_data(order_of_isometry(Lf)) - @req ok || d ==0 "Order of isometry must be a prime power" + @req ok || d == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" reps = LatticeWithIsometry[] atp = admissible_triples(Lf, p) for (A, B) in atp LA = lattice_with_isometry(representative(A)) - RA = representatives(LA, p*q^d) + RA = representatives_of_pure_type(LA, p*q^d) LB = lattice_with_isometry(representative(B)) - RB = representatives(LB, q^d) + RB = representatives_of_pure_type(LB, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) E = primitive_extensions(L1, L2, Lf, p) append!(reps, E) @@ -707,7 +731,14 @@ function split_p(Lf::LatticeWithIsometry, p::Int) return reps end -function first_p(Lf::LatticeWithIsometry, p::Int, b::Int = 0) +function prime_splitting_of_pure_type_prime_power(t::Dict, p::Int) + @req is_prime(p) "p must be a prime number" + @req is_pure(t) "t must be pure" + Lf = representative(t) + return prime_root_of_pure_type(Lf, p) +end + +function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" @req b in [0, 1] "b must be an integer equal to 0 or 1" @@ -716,13 +747,13 @@ function first_p(Lf::LatticeWithIsometry, p::Int, b::Int = 0) @req p != q "Prime numbers must be distinct" reps = LatticeWithIsometry[] if e == 0 - return split_p(Lf, p) + return prime_splitting_of_pure_type_prime_power(Lf, p) end x = gen(Hecke.Globals.Qx) A0 = kernel_lattice(Lf, q^e) B0 = kernel_lattice(Lf, x^(q^e-1)-1) - A = split_p(A0, p) - B = first_p(B0, p) + A = prime_splitting_of_pure_type_prime_power(A0, p) + B = prime_splitting_of_prime_power(B0, p) for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue E = primitive_extensions(L1, L2, Lf, q) @@ -732,7 +763,7 @@ function first_p(Lf::LatticeWithIsometry, p::Int, b::Int = 0) return reps end -function pure_up(Lf::LatticeWithIsometry, p::Int) +function prime_splitting(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" @req order_of_isometry(Lf) > 0 "Isometry must be of finite order" @@ -754,13 +785,13 @@ function pure_up(Lf::LatticeWithIsometry, p::Int) @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" reps = LatticeWithIsometry[] if e == 0 - return representatives(Lf, p) + return representatives_of_pure_type(Lf, p) end A0 = kernel_lattice(Lf, p^d*q^e) bool, r = divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) @assert bool B0 = kernel_lattice(Lf, r) - A = representatives(A0 ,p) + A = representatives_of_pure_type(A0 ,p) B = pure_up(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = extensions(LA, LB, Lf, q) diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index f4cdb8b5326f..701f88a5c8be 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -1,5 +1,6 @@ export hermitian_structure, isometry, lattice_with_isometry, order_of_isometry, - type, ambient_isometry, image_centralizer_in_Oq, coinvariant_lattice + type, ambient_isometry, image_centralizer_in_Oq, coinvariant_lattice, + is_of_type, is_of_same_type, is_of_pure_type, is_pure ############################################################################### # @@ -104,7 +105,7 @@ ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf))::Hecke.QuadS ############################################################################### @doc Markdown.doc""" - lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true, + lattice_with_isometry(L::ZLat, f::fmpq_mat, n::IntExt; check::Bool = true, ambient_representation = true) -> LatticeWithIsometry @@ -117,7 +118,7 @@ ambient space of `L` and the induced isometry on `L` is automatically computed. Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::Integer; check::Bool = true, +function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::IntExt; check::Bool = true, ambient_representation::Bool = true) if rank(L) == 0 return LatticewithIsometry(L, matrix(QQ,0,0,[]), -1) @@ -174,7 +175,7 @@ function lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true, end n = multiplicative_order(f) - return lattice_with_isometry(L, f, Int(n), check = check, + return lattice_with_isometry(L, f, n, check = check, ambient_representation = ambient_representation)::LatticeWithIsometry end @@ -213,8 +214,8 @@ If it exists, the hermitian structure is cached. @req n >= 3 "No hermitian structures for n smaller than 3" - return inverse_trace_lattice(lattice(Lf), f, n = n, check = true, - ambient_representation = false) + return Oscar._hermitian_structure(lattice(Lf), f, n = n, check = true, + ambient_representation = false) end ############################################################################### @@ -319,6 +320,8 @@ end # ############################################################################### +divides(k::PosInf, n::Int) = true + @doc Markdown.doc""" kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) -> LatticeWithIsometry @@ -331,7 +334,8 @@ function kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) L = lattice(Lf) f = isometry(Lf) M = p(f) - k, K = left_kernel(change_base_ring(ZZ, M)) + d = denominator(M) + k, K = left_kernel(change_base_ring(ZZ, d*M)) L2 = lattice_in_same_ambient_space(L, K*basis_matrix(L)) f2 = solve_left(change_base_ring(QQ, K), K*f) @assert f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) @@ -372,6 +376,7 @@ end L = lattice(Lf) f = isometry(Lf) n = order_of_isometry(Lf) + @req is_finite(n) "Isometry must be of finite order" divs = divisors(n) Qx = Hecke.Globals.Qx x = gen(Qx) @@ -379,7 +384,7 @@ end for l in divs Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = inverse_trace_lattice(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false) + Hl = Oscar._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false) end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) @@ -387,3 +392,53 @@ end return t end +function _is_type(t::Dict) + ke = collect(keys(t)) + n = maximum(ke) + Set(divisors(n)) == Set(ke) || return false + + for i in ke + t[i][2] isa ZGenus || return false + typeof(t[i][1]) <: Union{ZGenus, Hecke.GenusHerm} || return false + end + rank(t[n][2]) == rank(t[n][1])*euler_phi(n) || return false + return true +end + +function is_of_type(L::LatticeWithIsometry, t) + @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" + @req _is_type(t) "t does not define a type for lattices with isometry" + divs = sort(collect(keys(t))) + x = gen(Hecke.Globals.Qx) + for l in divs + Hl = kernel_lattice(L, cyclotomic_polynomial(l)) + if !(order_of_isometry(Hl) in [-1, 1, 2]) + t[l][1] isa Hecke.GenusHerm || return false + Hl = Oscar._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false, E = base_field(t[l][1])) + end + genus(Hl) == t[l][1] || return false + Al = kernel_lattice(L, x^l-1) + genus(Al) == t[l][2] || return false + end + return true +end + +function is_of_same_type(L::LatticeWithIsometry, M::LatticeWithIsometry) + @req is_finite(order_of_isometry(L)*order_of_isometry(M)) "Type is defined only for finite order isometries" + order_of_isometry(L) != order_of_isometry(M) && return false + genus(L) != genus(M) && return false + return is_of_type(L, type(M)) +end + +function is_of_pure_type(L::LatticeWithIsometry) + @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" + return is_cyclotomic_polynomial(minpoly(L)) +end + +function is_pure(t::Dict) + @req _is_type(t) "t does not define a type for lattices with isometry" + ke = collect(keys(t)) + n = maximum(ke) + return all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) +end + diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index 4621ddf3cbc2..a1f4309e6d35 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -1,4 +1,4 @@ -export trace_lattice, inverse_trace_lattice +export trace_lattice @doc Markdown.doc""" trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T -> LatticeWithIsometry @@ -15,11 +15,18 @@ of `L` and the underlying map `f` is: Note that the optional argument `order` has no effect if `L` is not a $\mathbb Z$-lattice. """ -function trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T +function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), + beta::FieldElem = gen(base_field(L)), + order::Integer = 2) where T + E = base_field(L) + @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @req order > 0 "The order must be positive" @req degree(L) == rank(L) "Lattice must be of full rank" n = degree(L) - E = base_field(L) + s = involution(E) + if s(beta)*beta != 1 + beta = beta//s(beta) + end if E == QQ || E == ZZ if order == 1 @@ -33,42 +40,41 @@ function trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T end bool, m = Hecke.is_cyclotomic_type(E) - @req bool "Base field must be cyclotomic" + @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "beta must be a $m-primitive root of 1" + + Lres, f = restrict_scalars_with_map(L) + iso = zero_matrix(QQ, 0, degree(Lres)) + v = vec(zeros(QQ, 1, degree(Lres))) - G = gram_matrix(ambient_space(L)) - d = absolute_degree(E) - coeffs = [] - b = gen(E) - for i in 1:n, iz in 0:d-1, j in 1:n, jz in 0:d-1 - g = b^(iz-jz)*G[i,j] - push!(coeffs, trace(g, QQ)) + for i in 1:degree(Lres) + v[i] = one(QQ) + v2 = f(v) + v2 = beta.*v2 + v3 = f\v2 + iso = vcat(iso, transpose(matrix(v3))) + v[i] = zero(QQ) end - V = quadratic_space(QQ, matrix(QQ,n*d, n*d, coeffs)) - gene = generators(L) - gene = [transpose(matrix(reduce(vcat, absolute_coordinates.(v)))) for v in gene] - BM = reduce(vcat, gene) - LL = lattice(V, BM) - LL = rescale(LL, 1//m) - chi = absolute_minpoly(b) - Mchi = companion_matrix(chi) - f = block_diagonal_matrix([Mchi for i=1:n]) - return LL, f - return lattice_with_isometry(LL, f, ambient_representation=true, check = true) + + return lattice_with_isometry(Lres, iso, ambient_representation=true, check = true) end -@doc Markdown.doc""" - inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, - check::Bool = true) -> HermLat +function absolute_representation_matrix(b::Hecke.NfRelElem) + E = parent(b) + n = absolute_degree(E) + B = absolute_basis(E) + m = zero_matrix(QQ, n, n) + for i in 1:n + bb = B[i] + v = absolute_coordinates(b*bb) + m[i,:] = transpose(matrix(v)) + end + return m +end -Given a $\mathbb Z$-lattice `L` and a matrix with rational entries `f`, -representing an isometry of `L` of order $n \geq 3$, such that the totient of `n` -divides the rank of `L` and the minimal polynomial of `f` is the `n`th cyclotomic -polynomial, return the corresponding hermitian lattice over $E/K$ where `E` is the -`n`th cyclotomic field and `K` is a maximal real subfield of `E`, where the -multiplication by the `n`th root of unity correspond to the mapping via `f`. -""" -function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Bool = true, - ambient_representation::Bool = true) +function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, + n::Integer = -1, + check::Bool = true, + ambient_representation::Bool = true) if n <= 0 n = multiplicative_order(f) end @@ -81,37 +87,62 @@ function inverse_trace_lattice(L::ZLat, f::fmpq_mat; n::Integer = -1, check::Boo end if check - @req n >= 3 "No hermitian inverse trace lattice for order less than 3" + @req n >= 3 "No hermitian structure for order less than 3" G = gram_matrix(L) @req is_cyclotomic_polynomial(minpoly(f)) "The minimal polynomial of f must be irreducible and cyclotomic" @req f*G*transpose(f) == G "f does not define an isometry of L" @req multiplicative_order(f) == n "The order of f should be equal to n" @req divides(rank(L), euler_phi(n))[1] "The totient of n must divides the rank of L" end - - E,b = cyclotomic_field_as_cm_extension(n) - + + if E === nothing + E, b = cyclotomic_field_as_cm_extension(n) + elseif !Hecke.is_cyclotomic_type(E)[1] + @req degree(E) == 2 && absolute_degree(E) == 2*euler_phi(n) "E should be the $n-th cyclotomic field seen as a cm-extension of its real cyclotomic subfield" + Et, t = E["t"] + rt = roots(t^n-1) + @req length(rt) == euler_phi(n) "E is not of cyclotomic type" + b = rt[1] + else + b = gen(E) + end + + mb = absolute_representation_matrix(b) m = divexact(rank(L), euler_phi(n)) + diag = [mb for i in 1:m] + mb = block_diagonal_matrix(diag) + bca = Hecke._basis_of_commutator_algebra(f, mb) + @assert length(bca) == euler_phi(n) + l = inv(bca[1]) + B = matrix(absolute_basis(E)) + gene = Vector{elem_type(E)}[] + for i in 1:nrows(l) + vv = vec(zeros(E, 1, m)) + v = l[i,:] + for j in 1:m + a = (v[1, 1+(j-1)*euler_phi(n):j*euler_phi(n)]*B)[1] + vv[j] = a + end + push!(gene, vv) + end # We choose as basis for the hermitian lattice Lh the identity matrix - gram = matrix(zeros(E, m,m)) - G = gram_matrix(L) + gram = matrix(zeros(E, m, m)) + G = inv(l)*gram_matrix_of_rational_span(L)*inv(transpose(l)) v = zero_matrix(QQ, 1, rank(L)) - for i=1:m for j=1:m vi = deepcopy(v) vi[1,1+(i-1)*euler_phi(n)] = one(QQ) vj = deepcopy(v) vj[1,1+(j-1)*euler_phi(n)] = one(QQ) - alpha = sum([(vi*G*transpose(vj*f^k))[1]*b^k for k in 0:n-1]) + alpha = sum([(vi*G*transpose(vj*mb^k))[1]*b^k for k in 0:n-1]) gram[i,j] = alpha end end - s = involution(E) @assert transpose(map_entries(s, gram)) == gram - Lh = hermitian_lattice(E, gram = gram) + Lh = hermitian_lattice(E, gene, gram = 1//n*gram) return Lh end diff --git a/src/NumberTheory/LatticesWithIsometry/Types.jl b/src/NumberTheory/LatticesWithIsometry/Types.jl index 6fe147f85d72..a7472067667e 100644 --- a/src/NumberTheory/LatticesWithIsometry/Types.jl +++ b/src/NumberTheory/LatticesWithIsometry/Types.jl @@ -1,12 +1,12 @@ -export LatticeWithIsometry +export LatticeWithIsometry, LWIType @attributes mutable struct LatticeWithIsometry Lb::ZLat f::fmpq_mat f_ambient::fmpq_mat - n::Integer + n::IntExt - function LatticeWithIsometry(Lb::ZLat, f::fmpq_mat, f_ambient::fmpq_mat, n::Integer) + function LatticeWithIsometry(Lb::ZLat, f::fmpq_mat, f_ambient::fmpq_mat, n::IntExt) z = new() z.Lb = Lb z.f = f From 1b1bda0bc328504399ab52b5e59b026e697ed4b4 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 23 Nov 2022 16:38:57 +0100 Subject: [PATCH 16/76] . --- src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index a1f4309e6d35..b455ec5eb13f 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -22,6 +22,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @req order > 0 "The order must be positive" @req degree(L) == rank(L) "Lattice must be of full rank" + @req parent(beta) === E "beta must be an element of the base algebra of L" n = degree(L) s = involution(E) if s(beta)*beta != 1 @@ -42,7 +43,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) bool, m = Hecke.is_cyclotomic_type(E) @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "beta must be a $m-primitive root of 1" - Lres, f = restrict_scalars_with_map(L) + Lres, f = restrict_scalars_with_map(L, QQ, alpha) iso = zero_matrix(QQ, 0, degree(Lres)) v = vec(zeros(QQ, 1, degree(Lres))) @@ -112,7 +113,7 @@ function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, diag = [mb for i in 1:m] mb = block_diagonal_matrix(diag) bca = Hecke._basis_of_commutator_algebra(f, mb) - @assert length(bca) == euler_phi(n) + @assert !is_empty(bca) l = inv(bca[1]) B = matrix(absolute_basis(E)) gene = Vector{elem_type(E)}[] From ae0a13e983c6b82341b6bfd30618cbff1a16808e Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 12 Dec 2022 16:43:20 +0100 Subject: [PATCH 17/76] a bit more --- .../LatticesWithIsometry/TraceEquivalence.jl | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index b455ec5eb13f..174f3286f97e 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -72,6 +72,38 @@ function absolute_representation_matrix(b::Hecke.NfRelElem) return m end +function _find_mult_action(f::fmpq_mat, mb::fmpq_mat) + A = f + B = mb + n = nrows(A) + m = nrows(B) + linind = transpose(LinearIndices((n,m))) + zz = zero_matrix(QQ, n*m, n*m) + for i in 1:m + for j in 1:n + for k in 1:n + zz[linind[i, j], linind[i, k]] += A[k,j] + end + for k in 1:m + zz[linind[i,j], linind[k, j]] -= B[i,k] + end + end + end + + r, K = right_kernel(zz) + + res = fmpq_mat[] + for k in 1:ncols(K) + cartind = Hecke.cartesian_product_iterator([1:x for x in (n, m)], inplace=true) + M = zero_matrix(QQ, m, n) + for (l,v) in enumerate(cartind) + M[v[2], v[1]] = K[l, k] + end + push!(res, M) + end + return res +end + function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, n::Integer = -1, check::Bool = true, @@ -110,11 +142,11 @@ function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, mb = absolute_representation_matrix(b) m = divexact(rank(L), euler_phi(n)) - diag = [mb for i in 1:m] - mb = block_diagonal_matrix(diag) - bca = Hecke._basis_of_commutator_algebra(f, mb) + bca = _find_mult_action(f, mb) @assert !is_empty(bca) - l = inv(bca[1]) + l = reduce(vcat, bca[1:m]) + @assert det(l) != 0 + l = inv(l) B = matrix(absolute_basis(E)) gene = Vector{elem_type(E)}[] for i in 1:nrows(l) @@ -127,6 +159,7 @@ function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, push!(gene, vv) end # We choose as basis for the hermitian lattice Lh the identity matrix + mb = block_diagonal_matrix([mb for i in 1:m]) gram = matrix(zeros(E, m, m)) G = inv(l)*gram_matrix_of_rational_span(L)*inv(transpose(l)) v = zero_matrix(QQ, 1, rank(L)) From 276c165fe7e70903ae0e4a7707b1a0509e09f888 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 14 Dec 2022 22:13:59 +0100 Subject: [PATCH 18/76] apply recent changes --- src/NumberTheory/LatticesWithIsometry/Misc.jl | 192 +++--------------- 1 file changed, 28 insertions(+), 164 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 830d25364d3e..3e048a092144 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -26,142 +26,6 @@ function inner_orthogonal_sum(T::TorQuadMod, U::TorQuadMod) return S, TinS, UinS end -############################################################################### -# -# orthogonal group degenerate form -# -############################################################################### - -function _normalize(T::TorQuadMod) - - if order(T) == 1 - return T, id_hom(T) - end - - p = elementary_divisors(T)[end] - @assert is_prime(p) - - qT = Hecke.gram_matrix_quadratic(T) - if p == 2 - qT = 1//modulus_quadratic_form(T)*qT - else - qT = 1//modulus_bilinear_form(T)*qT - end - - r = rank(qT) - if r != ncols(qT) - dT = denominator(qT) - _, U = hnf_with_transform(change_base_ring(ZZ, dT*qT)) - else - U = identity_matrix(QQ, r) - end - - nondeg = U[1:r, :] - kernel = U[r+1:end, :] - qT = nondeg*qT*transpose(nondeg) - - qT1 = inv(qT) - prec = 6 - D, U = Hecke.padic_normal_form(qT1, p, prec=prec, partial=false) - R = ResidueRing(ZZ, ZZ(p)^prec) - U = map_entries(x -> R(ZZ(x)), U) - U = transpose(inv(U)) - dT = denominator(qT) - qTR = map_entries(x -> R(ZZ(x)), dT*qT) - D = U*qTR*transpose(U) - D = map_entries(x -> R(mod(lift(x), dT)), D) - - if p != 2 - m = ZZ(modulus_quadratic_form(T)//modulus_bilinear_form(T)) - D = R(m)^-1*D - end - - D1, U1 = Hecke._normalize(D, ZZ(p), false) - U = U1*U - nondeg = U*map_entries(x -> R(ZZ(x)), nondeg) - U = vcat(nondeg, map_entries(x -> R(ZZ(x)), kernel)) - - @assert nrows(U) == ncols(U) - - n = ncols(U) - gene = elem_type(T)[] - - for i in 1:n - g = sum(lift(U[i,j])*T[j] for j in 1:ncols(U)) - push!(gene, g) - end - - D, DtoT = sub(T, gene) - if !is_degenerate(D) - return D, DtoT - end - - gene = gens(D) - qb = Hecke.gram_matrix_bilinear(D) - n = ngens(D) - - kergens = eltype(gene)[gene[i] for i in 1:n if is_zero(qb[i,:])] - nondeg = eltype(gene)[gene[i] for i in 1:n if !(gene[i] in kergens)] - k = length(kergens) - - for i in 1:k - if Hecke.quadratic_product(kergens[i]) == 1 - for j in i+1:k - if Hecke.quadratic_product(kergens[j]) == 1 - kergens[j] += kergens[i] - end - end - kergens = reduce(vcat, [kergens[1:i-1], kergens[i+1:end], [kergens[i]]]) - break - end - end - gene = vcat(kergens, nondeg) - D2, D2inD = sub(D, gene) - return D2, compose(D2inD, DtoT) -end - -function orthogonal_group_degenerate(T::TorQuadMod) - @req is_degenerate(T) "T must be degenerate" - p = elementary_divisors(T)[end] - @req is_prime(p) "T must define a F_p-vector space" - n = ngens(T) - NT, NTtoT = _normalize(T) - @assert is_bijective(NTtoT) - q = Hecke.gram_matrix_quadratic(NT) - k = length([i for i in 1:n if iszero(q[:,i])]) - r = n-k - Idk = identity_matrix(ZZ, k) - Idr = identity_matrix(ZZ, r) - NR, NRtoNT = sub(NT, [NT[i] for i in k+1:n]) - @assert !is_degenerate(NR) - ONR = orthogonal_group(NR) - gensNR = TorQuadModMor[hom(g) for g in gens(ONR)] - if k > 0 - gene_im = fmpz_mat[block_diagonal_matrix([Idk, g.map_ab.map]) for g in gensNR] - gene_im = union(gene_im, [block_diagonal_matrix([lift(matrix(g)), Idr]) for g in gens(general_linear_group(k, GF(p)))]) - else - gene_im = fmpz_mat[g.map_ab.ab for g in gensNR] - end - if k*r > 0 - bas = fmpz_mat[lift(matrix(GF(p), m)) for m in GAP.Globals.Basis(GAP.Globals.MatrixSpace(GAP.Globals.GF(GAP.julia_to_gap(p)), k, r))] - gene2 = fmpz_mat[] - for g in bas - s = identity_matrix(ZZ, n) - s[1:k, k+1:end] = g - push!(gene2, s) - end - MG1 = matrix_group([map_entries(GF(p), m) for m in gene_im]) - MG2 = matrix_group([map_entries(GF(p), m) for m in vcat(gene_im, gene2)]) - gene3 = lift.(matrix.(representative.(double_cosets(MG2, MG1, MG1)))) - gene_im = unique(vcat(gene_im, gene3)) - end - geneOT = TorQuadModMor[hom(NT, NT, g) for g in gene_im] - geneOT = fmpz_mat[compose(compose(inv(NTtoT), g), NTtoT).map_ab.map for g in geneOT] - OT = _orthogonal_group(T, geneOT, check=false) - @assert order(OT) == p^(k*r)*order(ONR)*order(GL(k, GF(p))) - return OT -end - function _restrict(f::TorQuadModMor, i::TorQuadModMor) imgs = TorQuadModElem[] V = domain(i) @@ -232,35 +96,35 @@ function embedding_orthogonal_group(i::TorQuadModMor) end function embedding_orthogonal_group(i1, i2) - D = codomain(i1) - A = domain(i1) - B = domain(i2) - gene = vcat(i1.(gens(A)), i2.(gens(B))) - n = ngens(A) - OD, OA, OB = orthogonal_group.([D, A, B]) + D = codomain(i1) + A = domain(i1) + B = domain(i2) + gene = vcat(i1.(gens(A)), i2.(gens(B))) + n = ngens(A) + OD, OA, OB = orthogonal_group.([D, A, B]) - geneOA = elem_type(OD)[] - for f in gens(OA) - imgs = [i1(f(a)) for a in gens(A)] - imgs = vcat(imgs, gene[n+1:end]) - _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) - _f = hom(D.ab_grp, D.ab_grp, _f) - f = TorQuadModMor(D, D, _f) - push!(geneOA, OD(f)) - end - geneOB = elem_type(OD)[] - for f in gens(OB) - imgs = [i2(f(a)) for a in gens(B)] - imgs = vcat(gene[1:n], imgs) - _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) - _f = hom(D.ab_grp, D.ab_grp, _f) - f = TorQuadModMor(D, D, _f) - push!(geneOB, OD(f)) - end + geneOA = elem_type(OD)[] + for f in gens(OA) + imgs = [i1(f(a)) for a in gens(A)] + imgs = vcat(imgs, gene[n+1:end]) + _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) + _f = hom(D.ab_grp, D.ab_grp, _f) + f = TorQuadModMor(D, D, _f) + push!(geneOA, OD(f)) + end + geneOB = elem_type(OD)[] + for f in gens(OB) + imgs = [i2(f(a)) for a in gens(B)] + imgs = vcat(gene[1:n], imgs) + _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) + _f = hom(D.ab_grp, D.ab_grp, _f) + f = TorQuadModMor(D, D, _f) + push!(geneOB, OD(f)) + end - OAtoOD = hom(OA, OD, gens(OA), geneOA) - OBtoOD = hom(OB, OD, gens(OB), geneOB) - return (OAtoOD, OBtoOD)::Tuple{GAPGroupHomomorphism, GAPGroupHomomorphism} + OAtoOD = hom(OA, OD, gens(OA), geneOA) + OBtoOD = hom(OB, OD, gens(OB), geneOB) + return (OAtoOD, OBtoOD)::Tuple{GAPGroupHomomorphism, GAPGroupHomomorphism} end function _as_Fp_vector_space_quotient(HinV, p, f) @@ -327,7 +191,7 @@ function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{To return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] end - OV = is_degenerate(V) ? orthogonal_group_degenerate(V) : orthogonal_group(V) + OV = orthogonal_group(V) gene_GV = TorQuadModMor[OV(_restrict(g, Vinq)) for g in gens(G)] GV, _ = sub(OV, gene_GV) @assert GV(f) in GV From 80a4060d16f2d2707c6212fd99ea416cf19796f3 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 16 Dec 2022 11:35:18 +0100 Subject: [PATCH 19/76] more --- .../LatticesWithIsometry/Enumeration.jl | 2 +- .../LatticesWithIsometry.jl | 4 +- src/NumberTheory/LatticesWithIsometry/Misc.jl | 5 +- .../LatticesWithIsometry/TraceEquivalence.jl | 52 ++++++------------- 4 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 010704f9bf9d..cd84ffa588fc 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -49,7 +49,7 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Int, d, s::fmpz, l::fmpz, p::Int, even = true) +function _find_L(r::Int, d::RationalUnion, s::fmpz, l::fmpz, p::Int, even = true) L = ZGenus[] for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] gen = genera((s1,s2), d, even=even) diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 701f88a5c8be..7e7551c7a37e 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -398,8 +398,8 @@ function _is_type(t::Dict) Set(divisors(n)) == Set(ke) || return false for i in ke - t[i][2] isa ZGenus || return false - typeof(t[i][1]) <: Union{ZGenus, Hecke.GenusHerm} || return false + t[i][2] isa ZGenus || return false + typeof(t[i][1]) <: Union{ZGenus, Hecke.GenusHerm} || return false end rank(t[n][2]) == rank(t[n][1])*euler_phi(n) || return false return true diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 3e048a092144..6c20b8634fa2 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -44,12 +44,11 @@ end function _is_invariant(f::TorQuadModMor, i::TorQuadModMor) fab = f.map_ab V = domain(i) - bool = true for a in gens(V) b = f(i(a)) - bool &= haspreimage(fab, data(b))[1] + haspreimage(fab, data(b))[1] || return false end - return bool + return true end function _is_invariant(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index 174f3286f97e..026c3c29266d 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -1,7 +1,9 @@ export trace_lattice @doc Markdown.doc""" - trace_lattice(L::Hecke.AbsLat{T}; order::Integer = 2) where T -> LatticeWithIsometry + trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), + beta::FieldElem = gen(base_field(L)), + order::Integer = 2) where T -> LatticeWithIsometry Given a lattice `L` which is either a $\mathbb Z$-lattice or a hermitian lattice over the $E/K$ with `E` a cyclotomic field and `K` a maximal real subfield of @@ -14,6 +16,18 @@ of `L` and the underlying map `f` is: Note that the optional argument `order` has no effect if `L` is not a $\mathbb Z$-lattice. + +The choice of `alpha` corresponds to the choice of a "twist" for the trace construction +using `restrict_scalars`. + +The choice of `beta` corresponds to the choice of a primitive root of unity in the +base field of `L`, in the hermitian case, used to construct the isometry `f`. If `beta` +is not a primitive root of the unity, but its normalisation is, i.e. $\beta/(s\beta)$ +where `s` is the non trivial involution of the base algebra of `L`, then its +normalisation is automatically used by default. + +Note that the isometry `f` computed is given by its action on the ambient space of the +trace lattice of `L`. """ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), beta::FieldElem = gen(base_field(L)), @@ -41,7 +55,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) end bool, m = Hecke.is_cyclotomic_type(E) - @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "beta must be a $m-primitive root of 1" + @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "The normalisation of beta must be a $m-primitive root of 1" Lres, f = restrict_scalars_with_map(L, QQ, alpha) iso = zero_matrix(QQ, 0, degree(Lres)) @@ -72,38 +86,6 @@ function absolute_representation_matrix(b::Hecke.NfRelElem) return m end -function _find_mult_action(f::fmpq_mat, mb::fmpq_mat) - A = f - B = mb - n = nrows(A) - m = nrows(B) - linind = transpose(LinearIndices((n,m))) - zz = zero_matrix(QQ, n*m, n*m) - for i in 1:m - for j in 1:n - for k in 1:n - zz[linind[i, j], linind[i, k]] += A[k,j] - end - for k in 1:m - zz[linind[i,j], linind[k, j]] -= B[i,k] - end - end - end - - r, K = right_kernel(zz) - - res = fmpq_mat[] - for k in 1:ncols(K) - cartind = Hecke.cartesian_product_iterator([1:x for x in (n, m)], inplace=true) - M = zero_matrix(QQ, m, n) - for (l,v) in enumerate(cartind) - M[v[2], v[1]] = K[l, k] - end - push!(res, M) - end - return res -end - function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, n::Integer = -1, check::Bool = true, @@ -142,7 +124,7 @@ function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, mb = absolute_representation_matrix(b) m = divexact(rank(L), euler_phi(n)) - bca = _find_mult_action(f, mb) + bca = Hecke._basis_of_integral_commutator_algebra(f, mb) @assert !is_empty(bca) l = reduce(vcat, bca[1:m]) @assert det(l) != 0 From 6a5f5c91c5c2e70fc38f0d145f16cd63710274e9 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 20 Dec 2022 13:10:13 +0100 Subject: [PATCH 20/76] more --- src/Groups/abelian_aut.jl | 4 +- .../LatticesWithIsometry/Enumeration.jl | 20 ++++--- .../LatticesWithIsometry.jl | 17 +----- src/NumberTheory/LatticesWithIsometry/Misc.jl | 57 ++++++++++--------- 4 files changed, 45 insertions(+), 53 deletions(-) diff --git a/src/Groups/abelian_aut.jl b/src/Groups/abelian_aut.jl index 61c611ca1e2a..a66d79e2452d 100644 --- a/src/Groups/abelian_aut.jl +++ b/src/Groups/abelian_aut.jl @@ -68,7 +68,7 @@ function hom(f::AutGrpAbTorElem) end -function (aut::AutGrpAbTor)(f::Union{GrpAbFinGenMap,TorQuadModMor};check::Bool=true) +function (aut::AutGrpAbTor)(f::Union{GrpAbFinGenMap,TorQuadModMor}; check::Bool=true) !check || (domain(f) === codomain(f) === domain(aut) && is_bijective(f)) || error("Map does not define an automorphism of the abelian group.") to_gap = get_attribute(aut, :to_gap) to_oscar = get_attribute(aut, :to_oscar) @@ -100,7 +100,7 @@ function (aut::AutGrpAbTor)(g::MatrixGroupElem{fmpq, fmpq_mat}; check::Bool=true end T = domain(aut) g = hom(T, T, elem_type(T)[T(lift(t)*matrix(g)) for t in gens(T)]) - return aut(g) + return aut(g, check = check) end """ matrix(f::AutomorphismGroupElem{GrpAbFinGen}) -> fmpz_mat diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index cd84ffa588fc..a420e8c0d5c5 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -49,7 +49,7 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Int, d::RationalUnion, s::fmpz, l::fmpz, p::Int, even = true) +function _find_L(r::Int, d::Hecke.RationalUnion, s::fmpz, l::fmpz, p::Int, even = true) L = ZGenus[] for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] gen = genera((s1,s2), d, even=even) @@ -115,7 +115,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B qA = discriminant_group(A) qB = discriminant_group(B) - if !divides(det(C), p)[1] + if !divides(numerator(det(C)), p)[1] return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1])[1] end @@ -287,7 +287,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry @req is_admissible_triple(Afa, Bfb, Cfc, p) "(A, B, C) must be p-admissble" # we need to compare the type of the output with the one of Cfc - t = type(Cfc) + #t = type(Cfc) results = LatticeWithIsometry[] # this is the glue valuation: it is well-defined because the triple in input is admissible @@ -330,12 +330,12 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry C2 = orthogonal_sum(A, B)[1] fC2 = block_diagonal_matrix(fA, fB) end - if type(lattice_with_isometry(C2, fC2^p, ambient_representation = false)) == t + if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), t) C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) push!(results, C2fC2) - return results end + return results end # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map @@ -355,8 +355,8 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) - subsA = _subgroups_representatives(VAinqA, GA, g, fVA, l) - subsB = _subgroups_representatives(VBinqB, GB, g, fVB, l) + subsA = _subgroups_representatives(VAinqA, GA, g, fVA, ZZ(l)) + subsB = _subgroups_representatives(VBinqB, GB, g, fVB, ZZ(l)) # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry @@ -370,6 +370,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # now, for each pair of anti-isometric potential kernels, we need to see whether # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the # corresponding overlattice and check whether it satisfies the type condition + return R for (H1, H2, phi) in R SAinqA, stabA = H1 SA = domain(SAinqA) @@ -401,10 +402,11 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # under the action of O(SB, rho_{l+1}(qB), fB) rBinqB = _rho_functor(qB, p, valuation(l, p)+1) @assert Oscar._is_invariant(stabB, rBinqB) - rBinSB = hom(domain(rBinqB), SB, [SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) - @assert is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB + rBinSB = hom(domain(rBinqB), SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) + @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB # We compute the generators of O(SB, rho_l(qB)) + return rBinSB OrBinOSB = embedding_orthogonal_group(rBinSB) OSBrB, _ = image(OrBinOSB) @assert fSB in OSBrB diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 7e7551c7a37e..13eae630b22b 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -237,7 +237,7 @@ function discriminant_group(Lf::LatticeWithIsometry) @req is_integral(L) "Underlying lattice must be integral" q = discriminant_group(L) Oq = orthogonal_group(q) - return (q, Oq(gens(matrix_group(f))[1]))::Tuple{TorQuadMod, AutomorphismGroupElem{TorQuadMod}} + return (q, Oq(gens(matrix_group(f))[1], check = false))::Tuple{TorQuadMod, AutomorphismGroupElem{TorQuadMod}} end @attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) @@ -392,22 +392,8 @@ end return t end -function _is_type(t::Dict) - ke = collect(keys(t)) - n = maximum(ke) - Set(divisors(n)) == Set(ke) || return false - - for i in ke - t[i][2] isa ZGenus || return false - typeof(t[i][1]) <: Union{ZGenus, Hecke.GenusHerm} || return false - end - rank(t[n][2]) == rank(t[n][1])*euler_phi(n) || return false - return true -end - function is_of_type(L::LatticeWithIsometry, t) @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" - @req _is_type(t) "t does not define a type for lattices with isometry" divs = sort(collect(keys(t))) x = gen(Hecke.Globals.Qx) for l in divs @@ -436,7 +422,6 @@ function is_of_pure_type(L::LatticeWithIsometry) end function is_pure(t::Dict) - @req _is_type(t) "t does not define a type for lattices with isometry" ke = collect(keys(t)) n = maximum(ke) return all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index 6c20b8634fa2..ae284b281620 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -71,26 +71,20 @@ function embedding_orthogonal_group(i::TorQuadModMor) A = domain(i) B = domain(j) D = codomain(i) - gene = vcat(i.(gens(A)), j.(gens(B))) - n = ngens(A) + Dorth = direct_sum(A, B)[1] + ok, phi = is_isometric_with_isometry(Dorth, D) + @assert ok OD = orthogonal_group(D) - OA = is_degenerate(A) ? orthogonal_group_degenerate(A) : orthogonal_group(A) + OA = orthogonal_group(A) - geneOA = elem_type(OD)[] + geneOAinDorth = TorQuadModMor[] for f in gens(OA) - imgs = [i(f(a)) for a in gens(A)] - imgs = vcat(imgs, gene[n+1:end]) - if can_solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs])) - _f = solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs])) - _f = hom(D.ab_grp, D.ab_grp, _f) - else - _f = solve(reduce(vcat, [data(a).coeff for a in imgs]), reduce(vcat, [data(a).coeff for a in gene])) - _f = inv(hom(D.ab_grp, D.ab_grp, _f)) - end - f = TorQuadModMor(D, D, _f) - push!(geneOA, OD(f)) + m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) + m = hom(Dorth, Dorth, m) + push!(geneOAinDorth, m) end - OAtoOD = hom(OA, OD, gens(OA), geneOA) + geneOAinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] + OAtoOD = hom(OA, OD, gens(OA), geneOAinOD, check = false) return OAtoOD::GAPGroupHomomorphism end @@ -109,7 +103,7 @@ function embedding_orthogonal_group(i1, i2) _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) _f = hom(D.ab_grp, D.ab_grp, _f) f = TorQuadModMor(D, D, _f) - push!(geneOA, OD(f)) + push!(geneOA, OD(f, check = false)) end geneOB = elem_type(OD)[] for f in gens(OB) @@ -118,13 +112,25 @@ function embedding_orthogonal_group(i1, i2) _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) _f = hom(D.ab_grp, D.ab_grp, _f) f = TorQuadModMor(D, D, _f) - push!(geneOB, OD(f)) + push!(geneOB, OD(f, check = false)) end - OAtoOD = hom(OA, OD, gens(OA), geneOA) - OBtoOD = hom(OB, OD, gens(OB), geneOB) + OAtoOD = hom(OA, OD, gens(OA), geneOA, check = false) + OBtoOD = hom(OB, OD, gens(OB), geneOB, check = false) return (OAtoOD, OBtoOD)::Tuple{GAPGroupHomomorphism, GAPGroupHomomorphism} - end +end + +#function _small_Fp_stabilizer(i::TorquadModMor) +# ok, j = has_complement(i) +# @assert ok +# A = domain(i) +# B = domain(j) +# C = codomain(i) +# Corth = direct_sum(A, B)[1] +# R, phi = hom(abelian_group(B), abelian_group(A)) +# geneBtoA = filter(r -> matrix(phi(r))*Hecke.gram_matrix_quadratic(A)*transpose(matrix(phi(r))) == Hecke.gram_matrix_quadratic(B), R) + + function _as_Fp_vector_space_quotient(HinV, p, f) i = HinV.map_ab @@ -174,7 +180,7 @@ function _as_Fp_vector_space_quotient(HinV, p, f) return Qp, VtoVp, VptoQp, fQp end -function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, g::Int, f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), l::Union{Int, fmpz} = 0) +function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, g::Int, f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), l::Hecke.IntegerUnion = 0) V = domain(Vinq) q = codomain(Vinq) p = elementary_divisors(V)[1] @@ -191,16 +197,15 @@ function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{To end OV = orthogonal_group(V) - gene_GV = TorQuadModMor[OV(_restrict(g, Vinq)) for g in gens(G)] + gene_GV = elem_type(OV)[OV(_restrict(g, Vinq), check = false) for g in gens(G)] GV, _ = sub(OV, gene_GV) - @assert GV(f) in GV - GVinG = hom(GV, G, gens(GV), gens(G)) + GVinG = hom(GV, G, gens(GV), gens(G), check = false) act_GV_Vp = gfp_fmpz_mat[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] act_GV_Qp = gfp_fmpz_mat[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(act_GV_Qp) @assert fQp in MGp - MGptoGV = hom(MGp, GV, gens(GV)) + MGptoGV = hom(MGp, GV, gens(GV), check = false) MGptoG = compose(MGptoGV, GVinG) res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] From 0937073ef5bf8cce803f813a544d8a1b660abf8d Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 11 Jan 2023 12:09:36 +0100 Subject: [PATCH 21/76] more changes --- .../LatticesWithIsometry/Enumeration.jl | 202 ++++++++++++++---- .../LatticesWithIsometry.jl | 126 +++++++++-- src/NumberTheory/LatticesWithIsometry/Misc.jl | 33 ++- .../LatticesWithIsometry/TraceEquivalence.jl | 13 +- 4 files changed, 297 insertions(+), 77 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index a420e8c0d5c5..1516adf6db4b 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -1,4 +1,11 @@ -export admissible_triples, is_admissible_triple, representatives_of_pure_type +export admissible_triples, + is_admissible_triple, + prime_splitting, + prime_splitting_of_prime_power, + prime_splitting_of_pure_type_prime_power, + prime_splitting_of_semi_pure_type, + primitive_extensions, + representatives_of_pure_type ################################################################################## # @@ -49,12 +56,12 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Int, d::Hecke.RationalUnion, s::fmpz, l::fmpz, p::Int, even = true) +function _find_L(r::Int, d::Hecke.RationalUnion, s::fmpz, l::fmpz, p::IntegerUnion, even = true) L = ZGenus[] for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] gen = genera((s1,s2), d, even=even) - filter!(G -> divides(scale(G), s)[1], gen) - filter!(G -> divides(p*l, level(G))[1], gen) + filter!(G -> divides(numerator(scale(G)), s)[1], gen) + filter!(G -> divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) end return L @@ -81,7 +88,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" AperpB = orthogonal_sum(A,B) # If A and B glue to C, the sum of their ranks must match the one of C - if rank(AperpB) != rank(C) + if signature_tuple(AperpB) != signature_tuple(C) return false end @@ -210,8 +217,9 @@ $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and """ function admissible_triples(G::ZGenus, p::Int64) @req is_prime(p) "p must be a prime number" + @req is_integral(G) "G must be a genus of integral lattices" n = rank(G) - d = det(G) + d = numerator(det(G)) even = iseven(G) L = Tuple{ZGenus, ZGenus}[] for ep in 0:div(n, p-1) @@ -220,8 +228,8 @@ function admissible_triples(G::ZGenus, p::Int64) m = min(ep, r1) D = _find_D(d, m, p) for (d1, dp) in D - L1 = _find_L(r1, d1, scale(G), level(G), p, even) - Lp = _find_L(rp, dp, scale(G), level(G), p, even) + L1 = _find_L(r1, d1, numerator(scale(G)), numerator(level(G)), p, even) + Lp = _find_L(rp, dp, numerator(scale(G)), numerator(level(G)), p, even) for (A, B) in [(A, B) for A in L1 for B in Lp] if is_admissible_triple(A, B, G, p) push!(L, (A, B)) @@ -275,19 +283,34 @@ function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) return sub(q, gene)[2] end +@doc Markdown.doc""" + primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Int) + -> Vector{LatticeWithIsometry} + +Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a prime number +`p`, such that `(A, B, C)` is `p`-admissible, return a set of representatives of the double coset +$G_B\backslash S\slash/G_A$ where: + + - $G_A$ and $G_B$ are the respective images of the morphisms $O(A, fa) -> O(q_A, \bar{fa})$ + and $O(B, fb) -> O(q_B, \bar{fb})$; + - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry $fc'$ where + $p\cdot C' \subsetea A\perpB$ and the type of $(C', fc'^p)$ is equal to the type of $(C, fc)$. + +See Algorithm 2 of [BH22]. +""" function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer) # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" A, B, C = lattice.([Afa, Bfb, Cfc]) - if ambient_space(Afa) === ambient_space(Bfb) + @req is_admissible_triple(Afa, Bfb, Cfc, p) "(A, B, C) must be p-admissble" + + if ambient_space(Afa) === ambient_space(Bfb) === ambient_space(Cfc) @req rank(intersect(A, B)) == 0 "Lattice in same ambient space must have empty intersection to glue" @req rank(A) + rank(B) <= dim(ambient_space(A)) "Lattice cannot glue in their ambient space" end - @req is_admissible_triple(Afa, Bfb, Cfc, p) "(A, B, C) must be p-admissble" # we need to compare the type of the output with the one of Cfc - #t = type(Cfc) results = LatticeWithIsometry[] # this is the glue valuation: it is well-defined because the triple in input is admissible @@ -370,7 +393,6 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # now, for each pair of anti-isometric potential kernels, we need to see whether # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the # corresponding overlattice and check whether it satisfies the type condition - return R for (H1, H2, phi) in R SAinqA, stabA = H1 SA = domain(SAinqA) @@ -406,7 +428,6 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB # We compute the generators of O(SB, rho_l(qB)) - return rBinSB OrBinOSB = embedding_orthogonal_group(rBinSB) OSBrB, _ = image(OrBinOSB) @assert fSB in OSBrB @@ -463,12 +484,12 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) C2 = lattice(ambient_space(cover(D)), _B[end-rank(S)-rank(R)+1:end, :]) fC2 = block_diagonal_matrix([fA, fB]) - __B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) + __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) fC2 = __B*fC2*inv(__B) @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end - if type(lattice_with_isometry(C2, fC2^p, ambient_representation=false)) != t + if type(lattice_with_isometry(C2, fC2^p, ambient_representation=false)) != type(Cfc) continue end @@ -502,6 +523,8 @@ end # ################################################################################## +# we compute ideals of E/K whose absolute norm is equal to d + function _ideals_of_norm(E, d::fmpq) if denominator(d) == 1 return _ideals_of_norm(E, numerator(d)) @@ -542,12 +565,17 @@ function _ideals_of_norm(E, d::fmpz) return ids end -function _possible_signatures(s1, s2, E) +# given a cyclotomic field (as cm extension) E/K, return all +# the possible signatures dictionnaries of any hermitian lattice over +# E/K of rank rk, whose trace lattice has signature (s1, s2). + +function _possible_signatures(s1, s2, E, rk) @assert E isa Hecke.NfRel ok, q = Hecke.is_cyclotomic_type(E) @assert ok @assert iseven(s2) @assert divides(2*(s1+s2), euler_phi(q))[1] + n = l = divexact(s2, 2) K = base_field(E) inf = real_places(K) @@ -556,6 +584,9 @@ function _possible_signatures(s1, s2, E) parts = Vector{Int}[] perm = AllPerms(s) for v in AllParts(l) + if any(i -> i > rk, v) + continue + end if length(v) > s continue end @@ -573,6 +604,19 @@ function _possible_signatures(s1, s2, E) return signs end +@doc Markdown.doc""" + representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) + -> Vector{LatticeWithIsometry} + +Given a lattice with isometry $(L, f)$ of pure type (i.e. the minimal +polynomial of `f` is irreducible cyclotomic), and a positive integer `m` (set to +1 by default), return a set of representatives of isomorphism classes of lattices +with isometry of pure type $(M, g)$ and such that the type of $(B, g^m)$ is equal +to the type of $(L, f)$. Note that in this case, the isometries `g`'s are of order +$nm$. + +See Algorithm 3 of [BH22]. +""" function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) @req m >= 1 "m must be a positive integer" @req is_of_pure_type(Lf) "Minimal polyomial must be irreducible and cyclotomic" @@ -614,7 +658,7 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) @info "All possible ideal dets: $(length(detE))" - signatures = _possible_signatures(s1, s2, E) + signatures = _possible_signatures(s1, s2, E, rk) @info "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures append!(gene, genera_hermitian(E, rk, sign, dd)) @@ -629,7 +673,7 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) end @info "$H" M = trace_lattice(H) - return M + println(det(lattice(M)), d) @assert det(lattice(M)) == d @assert is_cyclotomic_polynomial(minpoly(M)) @assert order_of_isometry(M) == n*m @@ -644,14 +688,28 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) return reps end +@doc Markdown.doc""" + representatives_of_pure_type(t::Dict, m::Int = 1; check::Bool = true) + -> Vector{LatticeWithIsometry} + +Given a type `t` for lattices with isometry of pure type (i.e. the minimal +polymomial of the associated isometry is irreducible cyclotomic) and an intger +`m` (set to 1 by default), return a set of representatives of isomorphism +classes of lattices with isometry of pure type $(L, f)$ such that the +type of $(L, f^m)$ is equal to `t`. + +If `check === true`, then `t` is checked to be pure. Note that `n` can be 1. + +See Algorithm 3 of [BH22]. +""" function representatives_of_pure_type(t::Dict, m::Integer = 1; check::Bool = true) - M = representative(t, check = check) - M isa String && return M - return type_representatives(M, m) + M = _representative(t, check = check) + M === nothing && return LatticeWithIsometry[] + return representatives_of_pure_type(M, m) end -function representative(t::Dict; check::Bool = true) - @req m >= 1 "m must be a positive integer" + +function _representative(t::Dict; check::Bool = true) !check || is_pure(t) || error("t must be pure") ke = collect(keys(t)) @@ -687,7 +745,7 @@ function representative(t::Dict; check::Bool = true) signatures = _possible_signatures(s1, s2, E) @info "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, dd)) + append!(gene, genera_hermitian(E, s1+s2, sign, dd)) end gene = unique(gene) for g in gene @@ -708,10 +766,24 @@ function representative(t::Dict; check::Bool = true) end return M end - return "Empty type" + return nothing end -function prime_splitting_of_pure_type(Lf::LatticeWithIsometry, p::Int) +@doc Markdown.doc""" + prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::Int) + -> Vector{LatticeWithIsometry} + +Given a lattice with isometry $(L, f)$ of pure type (i.e. the minimal polynomial +of `f` is irreducible cyclotomic) with `f` of order $q^d$ for some prime number `q`, +and a prime number $p \neq q$, return a set of representatives of the isomorphisms +classes of lattices with isometry $(M, g)$ such that the type of $(M, g^p)$ is equal +to the type of $(L, f)$. + +Note that `d` can be 0. + +See Algorithm 4 of [BH22]. +""" +function prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" @req is_of_pure_type(Lf) "Minimal polynomial must be irreducible and cyclotomic" @@ -733,13 +805,42 @@ function prime_splitting_of_pure_type(Lf::LatticeWithIsometry, p::Int) return reps end + +@doc Markdown.doc""" + prime_splitting_of_pure_type_prime_power(t::Dict, p::Int) + -> Vector{LatticeWithIsometry} + +Given a type `t` of lattice with isometry of pure type $(L, f)$ (i.e. the minimal +polynomial of `f` is irreducible cyclotomic) with `f` of order $q^d$ for some +prime number `q`, and a prime number $p \neq q$, return a set of representatives +of the isomorphisms classes of lattices with isometry $(M, g)$ such that the type +of $(M, g^p)$ is equal to `t`. + +Note that `d` can be 0. + +See Algorithm 4 of [BH22]. +""" function prime_splitting_of_pure_type_prime_power(t::Dict, p::Int) @req is_prime(p) "p must be a prime number" @req is_pure(t) "t must be pure" - Lf = representative(t) - return prime_root_of_pure_type(Lf, p) + Lf = _representative(t) + return prime_splitting_of_pure_type_prime_power(Lf, p) end +@doc Markdown.doc""" + prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) + -> Vector{LatticeWithIsometry} + +Given a lattice with isometry $(L, f)$ with `f` of order $q^e$ for some prime number +`q`, a prime number $p \neq q$ and an integer $b = 0, 1$, return a set of representatives +of the isomorphism classes of lattices with isometry $(M, g)$ such that the type of +$(M, g^p)$ is equal to the type of $(L, f)$. If `b == 1`, return only the lattices +with isometry $(M, g)$ where `g` is of order $pq^e$. + +Note that `e` can be 0. + +See Algorithm 5 of [BH22]. +""" function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" @@ -765,13 +866,27 @@ function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int return reps end -function prime_splitting(Lf::LatticeWithIsometry, p::Int) +@doc Markdown.doc""" + prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) + -> Vector{LatticeWithIsometry} + +Given a lattice with isometry $(L, f)$ and a prime number `p`, such that +the minimal of `f` divides $\prod_{i=0}^e\Phi_{p^dq^i}(f)$ for some +$d > 0$ and $e \geq 0$, return a set of representatives of the isomorphism classes +of lattices with isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to the type +of $(L, f)$. + +Note that `e` can be 0, while `d` has to be positive. + +See Algorithm 6 of [BH22]. +""" +function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req is_prime(p) "p must be a prime number" - @req order_of_isometry(Lf) > 0 "Isometry must be of finite order" + @req is_finite(order_of_isometry(Lf)) "Isometry must be of finite order" n = order_of_isometry(Lf) pd = prime_divisors(n) - @req length(pd) <= 2 && p in pd "Order must be divisible by p and have at most 2 prime factors" + @req 1 <= length(pd) <= 2 && p in pd "Order must be divisible by p and have at most 2 prime factors" if length(pd) == 2 q = pd[1] == p ? pd[2] : pd[1] d = valuation(n, p) @@ -794,7 +909,7 @@ function prime_splitting(Lf::LatticeWithIsometry, p::Int) @assert bool B0 = kernel_lattice(Lf, r) A = representatives_of_pure_type(A0 ,p) - B = pure_up(B0, p) + B = prime_splitting(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = extensions(LA, LB, Lf, q) append!(reps, E) @@ -802,14 +917,27 @@ function prime_splitting(Lf::LatticeWithIsometry, p::Int) return reps end -function next_p(Lf::LatticeWithIsometry, p) +@doc Markdown.doc""" + prime_splitting(Lf::LatticeWithIsometry, p::Int) -> Vector{LatticeWithIsometry} + +Given a lattice with isometry $(L, f)$ and a prime number `p` such that +`f` is of order $p^dq^e$ for some prime number $q \neq p$, return a set +of representatives of the isomorphism classes of lattices with isometry +$(M, g)$ of order $p^{d+1}q^e$ such that the type of $(M, g^p)$ is equal +to the type of $(L, f)$. + +Note that `d` and `e` can be both zero. + +See Algorithm 7 of [BH22]. +""" +function prime_splitting(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[Lf] n = order_of_isometry(Lf) - @req n > 0 "Isometry must be of finite order" + @req is_finite(n) "Isometry must be of finite order" pd = prime_divisors(n) @req length(pd) <= 2 "Order must have at most 2 prime divisors" if !(p in pd) - return first_p(Lf, p, 1) + return prime_splitting_of_prime_power(Lf, p, 1) end d = valuation(n, p) if n != p^d @@ -822,8 +950,8 @@ function next_p(Lf::LatticeWithIsometry, p) x = gen(parent(minpoly(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])) - A = pure_up(A0, p) - B = next_p(B0, p) + A = prime_splitting_of_semi_pure_type(A0, p) + B = prime_splitting(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = extensions(LA, LB, Lf, p) @assert all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 13eae630b22b..bbf806682ec6 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -1,6 +1,15 @@ -export hermitian_structure, isometry, lattice_with_isometry, order_of_isometry, - type, ambient_isometry, image_centralizer_in_Oq, coinvariant_lattice, - is_of_type, is_of_same_type, is_of_pure_type, is_pure +export ambient_isometry, + coinvariant_lattice, + hermitian_structure, + image_centralizer_in_Oq, + isometry, + is_of_pure_type, + is_of_same_type, + is_of_type, + is_pure, + lattice_with_isometry, + order_of_isometry, + type ############################################################################### # @@ -110,8 +119,7 @@ ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf))::Hecke.QuadS -> LatticeWithIsometry Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry -of `L` of finite order `n`, return the corresponding lattice with isometry -pair $(L, f)$. +of `L` of order `n`, return the corresponding lattice with isometry pair $(L, f)$. If `ambient_representation` is set to true, `f` is consider as an isometry of the ambient space of `L` and the induced isometry on `L` is automatically computed. @@ -127,7 +135,6 @@ function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::IntExt; check::Bool = tr if check @req det(f) != 0 "f is not invertible" m = multiplicative_order(f) - @req m > 0 "f is not finite" @req n == m "The order of f is equal to $m, not $n" end @@ -160,8 +167,7 @@ end -> LatticeWithIsometry Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry -of `L` of finite order, return the corresponding lattice with isometry -pair $(L, f)$. +of `L`, return the corresponding lattice with isometry pair $(L, f)$. If `ambient_representation` is set to true, `f` is consider as an isometry of the ambient space of `L` and the induced isometry on `L` is automatically computed. @@ -211,10 +217,11 @@ If it exists, the hermitian structure is cached. @req rank(Lf) > 0 "Lf must be of positive rank" f = isometry(Lf) n = order_of_isometry(Lf) - + @req n >= 3 "No hermitian structures for n smaller than 3" + @req is_cyclotomic_polynomial(minpoly(f)) "The minimal polynomial must be irreducible and cyclotomic" - return Oscar._hermitian_structure(lattice(Lf), f, n = n, check = true, + return Oscar._hermitian_structure(lattice(Lf), f, n = n, check = false, ambient_representation = false) end @@ -240,6 +247,13 @@ function discriminant_group(Lf::LatticeWithIsometry) return (q, Oq(gens(matrix_group(f))[1], check = false))::Tuple{TorQuadMod, AutomorphismGroupElem{TorQuadMod}} end +@doc Markdown.doc""" + image_centralizer_in_Oq(Lf::LatticeWithIsometry) -> AutomorphismGroup + +Given an integral lattice with isometry `(L, f)`, return the image $G_L$ in $O(q_L, \bar{f})$ +of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ denotes the discriminant +group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. +""" @attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) n = order_of_isometry(Lf) L = lattice(Lf) @@ -250,13 +264,14 @@ end elseif is_definite(L) OL = orthogonal_group(L) f = OL(f) - UL = [OL(s) for s in gens(centralizer(OL, f)[1])] - OqL = orthogonal_group(discriminant_group(L)) - GL, _ = sub(OqL, [OqL(g) for g in UL]) + UL = fmpq_mat[matrix(OL(s)) for s in gens(centralizer(OL, f)[1])] + qL = discriminant_group(L) + UL = fmpz_mat[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in UL] + GL = Oscar._orthogonal_group(qL, UL) elseif rank(L) == euler_phi(n) - gene = matrix_group([-f^0, f]) - OqL = orthogonal_group(discriminant_group(L)) - GL, _ = sub(OqL, [OqL(g.X) for g in gens(gene)]) + qL = discriminant_group(L) + UL = fmpz_mat[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in [-f^0, f]] + GL = Oscar._orthogonal_group(qL, UL) else qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) @@ -293,6 +308,17 @@ function _real_kernel_signatures(L::ZLat, M) return (k1, k2) end +@doc Markdown.doc""" + signatures(Lf::LatticeWithIsometry) -> Dict{Int, Tuple{Int, Int}} + +Given a lattice with isometry `(L, f)` where the minimal polynomial of `f` +is irreducible cyclotomic, return the signatures of `(L, f)`. + +In this context, if we denote $z$ a primitive `n`-th root of unity, where `n` +is the order of `f`, then for each $1 \leq i \leq n/2$ such that $(i, n) = 1$, +the $i$-th signature of $(L, f)$ is given by the signatures of the real quadratic +form $\Ker(f + f^{-1} - z^i - z^{-i})$. +""" function signatures(Lf::LatticeWithIsometry) @req rank(Lf) != 0 "Signatures non available for the empty lattice" L = lattice(Lf) @@ -323,12 +349,15 @@ end divides(k::PosInf, n::Int) = true @doc Markdown.doc""" - kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) -> LatticeWithIsometry + kernel_lattice(Lf::LatticeWithIsometry, p::Union{fmpz_poly, fmpq_poly}) + -> LatticeWithIsometry -Given a lattice with isometry `Lf` and a polynomial `p` with rational +Given a lattice with isometry `(L, f)` and a polynomial `p` with rational coefficients, return the sublattice $M := \ker(p(f))$ of the underlying lattice `L` with isometry `f`, together with the restriction $f_{\mid M}$. """ +kernel_lattice(Lf::LatticeWithIsometry, p::Union{fmpz_poly, fmpq_poly}) + function kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) n = order_of_isometry(Lf) L = lattice(Lf) @@ -348,14 +377,36 @@ end kernel_lattice(Lf::LatticeWithIsometry, p::fmpz_poly) = kernel_lattice(Lf, change_base_ring(QQ, p)) +@doc Markdown.doc""" + kernel_lattice(Lf::LatticeWithIsometry, l::Integer) -> LatticeWithIsometry + +Given a lattice with isometry `(L, f)` and an integer `l`, return the kernel +lattice of `(L, f)` associated to the `l`-th cyclotomic polynomial. +""" function kernel_lattice(Lf::LatticeWithIsometry, l::Integer) @req divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" p = cyclotomic_polynomial(l) return kernel_lattice(Lf, p) end +@doc Markdown.doc""" + invariant_lattice(Lf::LatticeWithIsometry) -> LatticeWithIsometry + +Given a lattice with isometry `(L, f)`, return the invariant lattice $L^f$ of +`(L, f)` together with the restriction of `f` to $L^f$ (which is the identity +in this case) +""" invariant_lattice(Lf::LatticeWithIsometry) = kernel_lattice(Lf, 1) +@doc Markdown.doc""" + coinvariant_lattice(Lf::LatticeWithIsometry) -> LatticeWithIsometry + +Given a lattice with isometry `(L, f)`, return the coinvariant lattice $L_f$ of +`(L, f)` together with the restriction of `f` to $L_f$. + +The coinvariant lattice $L_f$ of `(L, f)` is the orthogonal complement in +`L` of the invariant lattice $L_f$. +""" function coinvariant_lattice(Lf::LatticeWithIsometry) chi = minpoly(Lf) if chi(1) == 0 @@ -372,6 +423,19 @@ end # ############################################################################### +@doc Markdown.doc""" + type(Lf::LatticeWithIsometry) + -> Dict{Int, Tuple{ <: Union{ZGenus, GenusHerm}, ZGenus}} + +Given a lattice with isometry `(L, f)` with `f` of finite order `n`, return the +type of `(L, f)`. + +In this context, the type is defined as follows: for each divisor `k` of `n`, +the `k`-type of `(L, f)` is the tuple $(H_k, A_K)$ consisting of the genus +$H_k$ of the lattice $\Ker(\Phi_k(f))$ viewed as a hermitian $\mathbb{Z}[\zeta_k]$- +lattice (so a $\mathbb{Z}$-lattice for k= 1, 2) and of the genus $A_k$ of the +$\mathbb{Z}$-lattice $\Ker(f^k-1)$. +""" @attr function type(Lf::LatticeWithIsometry) L = lattice(Lf) f = isometry(Lf) @@ -392,7 +456,12 @@ end return t end -function is_of_type(L::LatticeWithIsometry, t) +@doc Markdown.doc""" + is_of_type(Lf::LatticeWithIsometry, t::Dict) -> Bool + +Given a lattice with isometry `(L, f)`, return whether `(L, f)` is of type `t`. +""" +function is_of_type(L::LatticeWithIsometry, t::Dict) @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" divs = sort(collect(keys(t))) x = gen(Hecke.Globals.Qx) @@ -409,6 +478,12 @@ function is_of_type(L::LatticeWithIsometry, t) return true end +@doc Markdown.doc""" + is_of_same_type(Lf::LatticeWithIsometry, Mg::LatticeWithIsometry) -> Bool + +Given two lattices with isometry `(L, f)` and `(M, g)`, return whether they are +of the same type. +""" function is_of_same_type(L::LatticeWithIsometry, M::LatticeWithIsometry) @req is_finite(order_of_isometry(L)*order_of_isometry(M)) "Type is defined only for finite order isometries" order_of_isometry(L) != order_of_isometry(M) && return false @@ -416,11 +491,24 @@ function is_of_same_type(L::LatticeWithIsometry, M::LatticeWithIsometry) return is_of_type(L, type(M)) end +@doc Markdown.doc""" + is_of_pure_type(Lf::LatticeWithIsometry) -> Bool + +Given a lattice with isometry `(L, f)`, return whether the minimal polynomial +of `f` is irreducible cyclotomic. +""" function is_of_pure_type(L::LatticeWithIsometry) @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" return is_cyclotomic_polynomial(minpoly(L)) end +@doc Markdown.doc""" + is_pure(t::Dict) -> Bool + +Given a type `t` of lattices with isometry, return whether `t` is pure, i.e. +whether it defines the type of lattice with isometry whose minimal polynomial +is irreducible cyclotomic. +""" function is_pure(t::Dict) ke = collect(keys(t)) n = maximum(ke) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index ae284b281620..a38306ba5e19 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -92,29 +92,26 @@ function embedding_orthogonal_group(i1, i2) D = codomain(i1) A = domain(i1) B = domain(i2) - gene = vcat(i1.(gens(A)), i2.(gens(B))) - n = ngens(A) + Dorth = direct_sum(A, B)[1] + ok, phi = is_isometric_with_isometry(Dorth, D) + @assert ok OD, OA, OB = orthogonal_group.([D, A, B]) - geneOA = elem_type(OD)[] + geneOAinDorth = elem_type(OD)[] for f in gens(OA) - imgs = [i1(f(a)) for a in gens(A)] - imgs = vcat(imgs, gene[n+1:end]) - _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) - _f = hom(D.ab_grp, D.ab_grp, _f) - f = TorQuadModMor(D, D, _f) - push!(geneOA, OD(f, check = false)) + m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) + m = hom(Dorth, Dorth, m) + push!(geneOAinDorth, m) end - geneOB = elem_type(OD)[] + + geneOBinDorth = elem_type(OD)[] for f in gens(OB) - imgs = [i2(f(a)) for a in gens(B)] - imgs = vcat(gene[1:n], imgs) - _f = map_entries(ZZ, solve(reduce(vcat, [data(a).coeff for a in gene]), reduce(vcat, [data(a).coeff for a in imgs]))) - _f = hom(D.ab_grp, D.ab_grp, _f) - f = TorQuadModMor(D, D, _f) - push!(geneOB, OD(f, check = false)) + m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) + m = hom(Dorth, Dorth, m) + push!(geneOBinDorth, m) end - + geneOA = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] + geneOB = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOBinDorth] OAtoOD = hom(OA, OD, gens(OA), geneOA, check = false) OBtoOD = hom(OB, OD, gens(OB), geneOB, check = false) return (OAtoOD, OBtoOD)::Tuple{GAPGroupHomomorphism, GAPGroupHomomorphism} @@ -223,7 +220,7 @@ function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{To _V = codomain(i) for v in gens(orb) vv = _V(i(v)*fQp) - if !can_solve_with_solution(i.matrix, vv.v, side = :left)[1] + if !can_solve(i.matrix, vv.v, side = :left) @goto non_fixed end end diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index 026c3c29266d..9ba83f44a46c 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -117,16 +117,23 @@ function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, Et, t = E["t"] rt = roots(t^n-1) @req length(rt) == euler_phi(n) "E is not of cyclotomic type" - b = rt[1] + b = isone(rt[1]) ? rt[2] : rt[1] else b = gen(E) end mb = absolute_representation_matrix(b) m = divexact(rank(L), euler_phi(n)) - bca = Hecke._basis_of_integral_commutator_algebra(f, mb) + bca = Hecke._basis_of_commutator_algebra(f, mb) @assert !is_empty(bca) - l = reduce(vcat, bca[1:m]) + l = zero_matrix(QQ, 0, ncols(f)) + while rank(l) != ncols(f) + _m = popfirst!(bca) + _l = vcat(l, _m) + if rank(_l) > rank(l) + l = _l + end + end @assert det(l) != 0 l = inv(l) B = matrix(absolute_basis(E)) From fe61962ff678069bf773d73d6d44727e416af126 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 19 Jan 2023 16:35:26 +0100 Subject: [PATCH 22/76] more --- .../LatticesWithIsometry/Enumeration.jl | 53 +++++++++---------- src/NumberTheory/LatticesWithIsometry/Misc.jl | 2 +- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 1516adf6db4b..fbba2a5d3299 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -127,10 +127,10 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) end l = valuation(level(C), p) - Ap = symbol(local_symbol(A, p)) - Bp = symbol(local_symbol(B, p)) - a_max = sum(Int[s[2] for s in Ap if s[1] == l+1]) - b_max = sum(Int[s[2] for s in Bp if s[1] == l+1]) + Ap = local_symbol(A, p) + Bp = local_symbol(B, p) + a_max = symbol(Ap, l+1)[2] + b_max = symbol(Bp, l+1)[2] # For the glueing, rho_{l+1}(A_p) and rho_{l+1}(B_p) are anti-isometric, so they must have the # same order if a_max != b_max @@ -142,22 +142,22 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) return false end - a1 = sum(Int[s[2] for s in Ap if s[1] == 1]) - a2 = sum(Int[s[2] for s in Ap if s[1] >= 2]) - b1 = sum(Int[s[2] for s in Bp if s[1] == 1]) - b2 = sum(Int[s[2] for s in Bp if s[1] >= 2]) + a1 = symbol(Ap, 1)[2] + a2 = rank(Ap) - a1 - symbol(Ap, 0)[2] + b1 = symbol(Bp, 1)[2] + b2 = rank(Bp) - b1 - symbol(Bp, 0)[2] ABp = symbol(local_symbol(AperpB, p)) - Cp = symbol(local_symbol(C, p)) + Cp = local_symbol(C, p) if a_max == g - if length(Ap) > 1 + if length(symbol(Ap)) > 1 Ar = ZpGenus(p, Ap[1:end-1]) else Ar = genus(matrix(ZZ,0,0,[]), p) end - if length(Bp) > 1 + if length(symbol(Bp)) > 1 Br = ZpGenus(p, Bp[1:end-1]) else Br = genus(matrix(ZZ, 0, 0, []), p) @@ -166,8 +166,8 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) ABr = orthogonal_sum(Ar, Br) for i = 1:l - s1 = symbol(ABr)[i] - s2 = Cp[i] + s1 = symbol(ABr, i) + s2 = symbol(Cp, i) if s1[2] != s2[2] return false end @@ -181,13 +181,13 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) s3 = symbol(local_symbol(C, 2))[end] if p == 2 - s1 = Ap[s3[1]+1] - s2 = Bp[s3[1]+1] - if s1[3] != s2[3] + s1 = symbol(Ap, s3[1]+1) + s2 = symbol(Bp, s3[1]+1) + if s1[4] != s2[4] return false end end - + Cp = symbol(Cp) for s in Cp s[1] += 2 end @@ -315,7 +315,6 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry results = LatticeWithIsometry[] # this is the glue valuation: it is well-defined because the triple in input is admissible g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) - fA, fB = isometry.([Afa, Bfb]) qA, fqA = discriminant_group(Afa) qB, fqB = discriminant_group(Bfb) @@ -330,7 +329,8 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry end OD = orthogonal_group(D) - OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) + OqAinOD = embedding_orthogonal_group(qAinD) + OqBinOD = embedding_orthogonal_group(qBinD) OqA = domain(OqAinOD) OqB = domain(OqBinOD) @@ -353,10 +353,10 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry C2 = orthogonal_sum(A, B)[1] fC2 = block_diagonal_matrix(fA, fB) end - if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), t) - C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) - push!(results, C2fC2) + if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(Cfc)) + C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) + set_attribute!(C2fc2, :image_centralizer_in_Oq, GC2) + push!(results, C2fc2) end return results end @@ -380,7 +380,6 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # are fA-stable (resp. fB-stable) subsA = _subgroups_representatives(VAinqA, GA, g, fVA, ZZ(l)) subsB = _subgroups_representatives(VBinqB, GB, g, fVB, ZZ(l)) - # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry R = Tuple{eltype(subsA), eltype(subsB), TorQuadModMor}[] @@ -389,7 +388,6 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry !ok && continue push!(R, (H1, H2, phi)) end - # now, for each pair of anti-isometric potential kernels, we need to see whether # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the # corresponding overlattice and check whether it satisfies the type condition @@ -422,12 +420,13 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi # under the action of O(SB, rho_{l+1}(qB), fB) - rBinqB = _rho_functor(qB, p, valuation(l, p)+1) + rB, rBinqB = sub(qB, Int(l)*gens(qB)) @assert Oscar._is_invariant(stabB, rBinqB) rBinSB = hom(domain(rBinqB), SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB - # We compute the generators of O(SB, rho_l(qB)) + # We compute the generators of O(SB, rho_{l+1}(qB)) + return rBinSB OrBinOSB = embedding_orthogonal_group(rBinSB) OSBrB, _ = image(OrBinOSB) @assert fSB in OSBrB diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index a38306ba5e19..a7867910fab3 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -85,7 +85,7 @@ function embedding_orthogonal_group(i::TorQuadModMor) end geneOAinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] OAtoOD = hom(OA, OD, gens(OA), geneOAinOD, check = false) - return OAtoOD::GAPGroupHomomorphism + return OAtoOD end function embedding_orthogonal_group(i1, i2) From ca9d68bc60d29703a02b249d4a229a6e329f4a14 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 20 Jan 2023 14:21:48 +0100 Subject: [PATCH 23/76] close to be done --- .../LatticesWithIsometry/Enumeration.jl | 57 +++++++++++++-- .../LatticesWithIsometry.jl | 69 +++++++++---------- src/NumberTheory/LatticesWithIsometry/Misc.jl | 52 -------------- 3 files changed, 85 insertions(+), 93 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index fbba2a5d3299..4f45f3bfcc7e 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -58,6 +58,9 @@ end # the subgenus, d its determinant, s and l the scale and level of C function _find_L(r::Int, d::Hecke.RationalUnion, s::fmpz, l::fmpz, p::IntegerUnion, even = true) L = ZGenus[] + if r == 0 && d == 1 + return ZGenus[genus(Zlattice(gram = matrix(QQ, 0, 0, [])))] + end for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] gen = genera((s1,s2), d, even=even) filter!(G -> divides(numerator(scale(G)), s)[1], gen) @@ -280,7 +283,50 @@ function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) Gm = intersect(1//p^m*N, dual(N)) rholN = torsion_quadratic_module(Gl, Gk+p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) gene = [q(lift(g)) for g in gens(rholN)] - return sub(q, gene)[2] + return sub(q, gene) +end + +function _stabilizer(i::TorQuadModMor) + r = domain(i) + S = codomain(i) + OS = orthogonal_group(S) + if is_trivial(r.ab_grp) || is_zero(Hecke.gram_matrix_quadratic(r)) + return sub(OS, gens(OS)) + end + @assert is_injective(i) + if is_bijective(i) + return sub(OS, gens(OS)) + end + ok, j = has_complement(i) + @assert ok + t = domain(j) + rt = direct_sum(r,t)[1] + ok, rttoS = is_isometric_with_isometry(rt, S) + @assert ok + gensr = fmpz_mat[matrix(f) for f in gens(orthogonal_group(r))] + genst = fmpz_mat[matrix(f) for f in gens(orthogonal_group(t))] + R, phi = hom(abelian_group(t), abelian_group(r)) + c = [hom(t, r, f.map) for f in phi.(collect(R))] + filter!(f -> all(a -> all(b -> f(a)*f(b) == a*b, gens(t)), gens(t)), c) + filter!(f -> all(a -> Hecke.quadratic_product(f(a)) == Hecke.quadratic_product(a), gens(t)), c) + c = fmpz_mat[f.map_ab.map for f in c] + gene = fmpz_mat[] + for x in gensr + m = block_diagonal_matrix([identity_matrix(ZZ, ngens(t)), x]) + push!(gene, m) + end + for x in genst + m = block_diagonal_matrix([x, identity_matrix(ZZ, ngens(r))]) + push!(gene, m) + end + for x in c + m = identity_matrix(ZZ, ngens(rt)) + m[(ngens(t)+1):end, 1:ngens(t)] = x + push!(gene, m) + end + gene = TorQuadModMor[hom(rt, rt, m) for m in gene] + gene = fmpz_mat[compose(compose(inv(rttoS), g), rttoS).map_ab.map for g in gene] + return sub(OS, gene) end @doc Markdown.doc""" @@ -328,6 +374,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry D, qAinD, qBinD = orthogonal_sum(qA, qB) end + return qAinD OD = orthogonal_group(D) OqAinOD = embedding_orthogonal_group(qAinD) OqBinOD = embedding_orthogonal_group(qBinD) @@ -420,15 +467,13 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi # under the action of O(SB, rho_{l+1}(qB), fB) - rB, rBinqB = sub(qB, Int(l)*gens(qB)) + rB, rBinqB = _rho_functor(qB, p, valuation(l, p)+1) @assert Oscar._is_invariant(stabB, rBinqB) rBinSB = hom(domain(rBinqB), SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB # We compute the generators of O(SB, rho_{l+1}(qB)) - return rBinSB - OrBinOSB = embedding_orthogonal_group(rBinSB) - OSBrB, _ = image(OrBinOSB) + OSBrB, OSBrbinOSB = _stabilizer(rBinSB) @assert fSB in OSBrB # phi might not "send" the restriction of fA to this of fB, but at least phi(fA)phi^-1 @@ -617,6 +662,7 @@ $nm$. See Algorithm 3 of [BH22]. """ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) + rank(Lf) == 0 && return LatticeWithIsometry[Lf] @req m >= 1 "m must be a positive integer" @req is_of_pure_type(Lf) "Minimal polyomial must be irreducible and cyclotomic" L = lattice(Lf) @@ -672,7 +718,6 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) end @info "$H" M = trace_lattice(H) - println(det(lattice(M)), d) @assert det(lattice(M)) == d @assert is_cyclotomic_polynomial(minpoly(M)) @assert order_of_isometry(M) == n*m diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index bbf806682ec6..222d48493c34 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -25,28 +25,28 @@ end ############################################################################### # -# Attributes +# Accessors # ############################################################################### @doc Markdown.doc""" lattice(Lf::LatticeWithIsometry) -> ZLat -Given a lattice with isometry `(L, f)`, return the underlying lattice `L`. +Given a lattice with isometry $(L, f)$, return the underlying lattice `L`. """ lattice(Lf::LatticeWithIsometry) = Lf.Lb @doc Markdown.doc""" isometry(Lf::LatticeWithIsometry) -> fmpq_mat -Given a lattice with isometry `(L, f)`, return the underlying isometry `f`. +Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. """ isometry(Lf::LatticeWithIsometry) = Lf.f @doc Markdown.doc""" ambient_isometry(Lf::LatticeWithIsometry) -> fmpq_mat - Given a lattice with isometry `(L, f)`, return an isometry of underlying isometry +Given a lattice with isometry $(L, f)$, return an isometry of underlying isometry of the ambient space of `L` inducing `f` on `L` """ ambient_isometry(Lf::LatticeWithIsometry) = Lf.f_ambient @@ -54,21 +54,21 @@ ambient_isometry(Lf::LatticeWithIsometry) = Lf.f_ambient @doc Markdown.doc""" order_of_isometry(Lf::LatticeWithIsometry) -> Integer -Given a lattice with isometry `(L, f)`, return the order of the underlying +Given a lattice with isometry $(L, f)$, return the order of the underlying isometry `f`. """ order_of_isometry(Lf::LatticeWithIsometry) = Lf.n ############################################################################### # -# Predicates +# Attributes # ############################################################################### @doc Markdown.doc""" rank(Lf::LatticeWithIsometry) -> Integer -Given a lattice with isometry `(L, f)`, return the rank of the underlying lattice +Given a lattice with isometry $(L, f)$, return the rank of the underlying lattice `L`. """ rank(Lf::LatticeWithIsometry) = rank(lattice(Lf))::Integer @@ -76,7 +76,7 @@ rank(Lf::LatticeWithIsometry) = rank(lattice(Lf))::Integer @doc Markdown.doc""" charpoly(Lf::LatticeWithIsometry) -> fmpq_poly -Given a lattice with isometry `(L, f)`, return the characteristic polynomial of the +Given a lattice with isometry $(L, f)$, return the characteristic polynomial of the underlying isometry `f`. """ charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf))::fmpq_poly @@ -84,7 +84,7 @@ charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf))::fmpq_poly @doc Markdown.doc""" minpoly(Lf::LatticeWithIsometry) -> fmpq_poly -Given a lattice with isometry `(L, f)`, return the minimal polynomial of the +Given a lattice with isometry $(L, f)$, return the minimal polynomial of the underlying isometry `f`. """ minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf))::fmpq_poly @@ -92,17 +92,15 @@ minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf))::fmpq_poly @doc Markdown.doc""" genus(Lf::LatticeWithIsometry) -> ZGenus -Given a lattice with isometry `(L, f)`, return the genus of the underlying +Given a lattice with isometry $(L, f)$, return the genus of the underlying lattice `L`. - -For now, in order for the genus to exist, the lattice must be integral. """ genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus(L)::ZGenus : error("Underlying lattice must be integral"); end @doc Markdown.doc""" ambient_space(Lf::LatticeWithIsometry) -> QuadSpace -Given a lattice with isometry `(L, f)`, return the ambient space of the underlying +Given a lattice with isometry $(L, f)$, return the ambient space of the underlying lattice `L`. """ ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, fmpq_mat} @@ -206,7 +204,7 @@ end @doc Markdown.doc""" hermitian_structure(Lf::LatticeWithIsometry; check::Bool = true) -> HermLat -Given a lattice with isometry `Lf` such that the minimal polynomial of the +Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the underlying isometry `f` is irreducible and cyclotomic, return the hermitian structure of the underlying lattice `L` over the $n$th cyclotomic field, where $n$ is the order of `f`. @@ -234,7 +232,7 @@ end @doc Markdown.doc""" discriminant_group(Lf::LatticeWithIsometry) -> TorQuadMod, AutomorphismGroupElem -Given an integral lattice with isometry `Lf`, return the discriminant group `q` +Given an integral lattice with isometry $(L, f)$, return the discriminant group `q` of the underlying lattice `L` as well as this image of the underlying isometry `f` inside $O(q)$. """ @@ -250,16 +248,17 @@ end @doc Markdown.doc""" image_centralizer_in_Oq(Lf::LatticeWithIsometry) -> AutomorphismGroup -Given an integral lattice with isometry `(L, f)`, return the image $G_L$ in $O(q_L, \bar{f})$ -of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ denotes the discriminant -group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. +Given an integral lattice with isometry $(L, f)$, return the image $G_L$ in +$O(q_L, \bar{f})$ of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ +denotes the discriminant group of `L` and $\bar{f}$ is the isometry of +$q_L$ induced by `f`. """ @attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) n = order_of_isometry(Lf) L = lattice(Lf) f = ambient_isometry(Lf) @req is_integral(L) "Underlying lattice must be integral" - if n == 1 + if n in [1, -1] GL, _ = image_in_Oq(L) elseif is_definite(L) OL = orthogonal_group(L) @@ -311,8 +310,8 @@ end @doc Markdown.doc""" signatures(Lf::LatticeWithIsometry) -> Dict{Int, Tuple{Int, Int}} -Given a lattice with isometry `(L, f)` where the minimal polynomial of `f` -is irreducible cyclotomic, return the signatures of `(L, f)`. +Given a lattice with isometry $(L, f)$ where the minimal polynomial of `f` +is irreducible cyclotomic, return the signatures of $(L, f)$. In this context, if we denote $z$ a primitive `n`-th root of unity, where `n` is the order of `f`, then for each $1 \leq i \leq n/2$ such that $(i, n) = 1$, @@ -352,7 +351,7 @@ divides(k::PosInf, n::Int) = true kernel_lattice(Lf::LatticeWithIsometry, p::Union{fmpz_poly, fmpq_poly}) -> LatticeWithIsometry -Given a lattice with isometry `(L, f)` and a polynomial `p` with rational +Given a lattice with isometry $(L, f)$ and a polynomial `p` with rational coefficients, return the sublattice $M := \ker(p(f))$ of the underlying lattice `L` with isometry `f`, together with the restriction $f_{\mid M}$. """ @@ -380,8 +379,8 @@ kernel_lattice(Lf::LatticeWithIsometry, p::fmpz_poly) = kernel_lattice(Lf, chang @doc Markdown.doc""" kernel_lattice(Lf::LatticeWithIsometry, l::Integer) -> LatticeWithIsometry -Given a lattice with isometry `(L, f)` and an integer `l`, return the kernel -lattice of `(L, f)` associated to the `l`-th cyclotomic polynomial. +Given a lattice with isometry $(L, f)$ and an integer `l`, return the kernel +lattice of $(L, f)$ associated to the `l`-th cyclotomic polynomial. """ function kernel_lattice(Lf::LatticeWithIsometry, l::Integer) @req divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" @@ -392,8 +391,8 @@ end @doc Markdown.doc""" invariant_lattice(Lf::LatticeWithIsometry) -> LatticeWithIsometry -Given a lattice with isometry `(L, f)`, return the invariant lattice $L^f$ of -`(L, f)` together with the restriction of `f` to $L^f$ (which is the identity +Given a lattice with isometry $(L, f)$, return the invariant lattice $L^f$ of +$(L, f)$ together with the restriction of `f` to $L^f$ (which is the identity in this case) """ invariant_lattice(Lf::LatticeWithIsometry) = kernel_lattice(Lf, 1) @@ -401,10 +400,10 @@ invariant_lattice(Lf::LatticeWithIsometry) = kernel_lattice(Lf, 1) @doc Markdown.doc""" coinvariant_lattice(Lf::LatticeWithIsometry) -> LatticeWithIsometry -Given a lattice with isometry `(L, f)`, return the coinvariant lattice $L_f$ of -`(L, f)` together with the restriction of `f` to $L_f$. +Given a lattice with isometry $(L, f)$, return the coinvariant lattice $L_f$ of +$(L, f)$ together with the restriction of `f` to $L_f$. -The coinvariant lattice $L_f$ of `(L, f)` is the orthogonal complement in +The coinvariant lattice $L_f$ of $(L, f)$ is the orthogonal complement in `L` of the invariant lattice $L_f$. """ function coinvariant_lattice(Lf::LatticeWithIsometry) @@ -427,11 +426,11 @@ end type(Lf::LatticeWithIsometry) -> Dict{Int, Tuple{ <: Union{ZGenus, GenusHerm}, ZGenus}} -Given a lattice with isometry `(L, f)` with `f` of finite order `n`, return the -type of `(L, f)`. +Given a lattice with isometry $(L, f)$ with `f` of finite order `n`, return the +type of $(L, f)$. In this context, the type is defined as follows: for each divisor `k` of `n`, -the `k`-type of `(L, f)` is the tuple $(H_k, A_K)$ consisting of the genus +the `k`-type of $(L, f)$ is the tuple $(H_k, A_K)$ consisting of the genus $H_k$ of the lattice $\Ker(\Phi_k(f))$ viewed as a hermitian $\mathbb{Z}[\zeta_k]$- lattice (so a $\mathbb{Z}$-lattice for k= 1, 2) and of the genus $A_k$ of the $\mathbb{Z}$-lattice $\Ker(f^k-1)$. @@ -459,7 +458,7 @@ end @doc Markdown.doc""" is_of_type(Lf::LatticeWithIsometry, t::Dict) -> Bool -Given a lattice with isometry `(L, f)`, return whether `(L, f)` is of type `t`. +Given a lattice with isometry $(L, f)$, return whether $(L, f)$ is of type `t`. """ function is_of_type(L::LatticeWithIsometry, t::Dict) @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" @@ -481,7 +480,7 @@ end @doc Markdown.doc""" is_of_same_type(Lf::LatticeWithIsometry, Mg::LatticeWithIsometry) -> Bool -Given two lattices with isometry `(L, f)` and `(M, g)`, return whether they are +Given two lattices with isometry $(L, f)$ and $(M, g)$, return whether they are of the same type. """ function is_of_same_type(L::LatticeWithIsometry, M::LatticeWithIsometry) @@ -494,7 +493,7 @@ end @doc Markdown.doc""" is_of_pure_type(Lf::LatticeWithIsometry) -> Bool -Given a lattice with isometry `(L, f)`, return whether the minimal polynomial +Given a lattice with isometry $(L, f)$, return whether the minimal polynomial of `f` is irreducible cyclotomic. """ function is_of_pure_type(L::LatticeWithIsometry) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index a7867910fab3..aa3bcad24e36 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -65,58 +65,6 @@ end # ############################################################################## -function embedding_orthogonal_group(i::TorQuadModMor) - ok, j = has_complement(i) - @req ok "domain(i) needs to have a complement in codomain(i)" - A = domain(i) - B = domain(j) - D = codomain(i) - Dorth = direct_sum(A, B)[1] - ok, phi = is_isometric_with_isometry(Dorth, D) - @assert ok - OD = orthogonal_group(D) - OA = orthogonal_group(A) - - geneOAinDorth = TorQuadModMor[] - for f in gens(OA) - m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) - m = hom(Dorth, Dorth, m) - push!(geneOAinDorth, m) - end - geneOAinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] - OAtoOD = hom(OA, OD, gens(OA), geneOAinOD, check = false) - return OAtoOD -end - -function embedding_orthogonal_group(i1, i2) - D = codomain(i1) - A = domain(i1) - B = domain(i2) - Dorth = direct_sum(A, B)[1] - ok, phi = is_isometric_with_isometry(Dorth, D) - @assert ok - OD, OA, OB = orthogonal_group.([D, A, B]) - - geneOAinDorth = elem_type(OD)[] - for f in gens(OA) - m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) - m = hom(Dorth, Dorth, m) - push!(geneOAinDorth, m) - end - - geneOBinDorth = elem_type(OD)[] - for f in gens(OB) - m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) - m = hom(Dorth, Dorth, m) - push!(geneOBinDorth, m) - end - geneOA = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] - geneOB = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOBinDorth] - OAtoOD = hom(OA, OD, gens(OA), geneOA, check = false) - OBtoOD = hom(OB, OD, gens(OB), geneOB, check = false) - return (OAtoOD, OBtoOD)::Tuple{GAPGroupHomomorphism, GAPGroupHomomorphism} -end - #function _small_Fp_stabilizer(i::TorquadModMor) # ok, j = has_complement(i) # @assert ok From c10346625f3323f236f34db924b568454e39a3de Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 25 Jan 2023 09:43:22 +0100 Subject: [PATCH 24/76] minor fixes --- .../LatticesWithIsometry/Enumeration.jl | 175 +++++++++++------- .../LatticesWithIsometry.jl | 4 +- src/NumberTheory/LatticesWithIsometry/Misc.jl | 12 -- .../LatticesWithIsometry/TraceEquivalence.jl | 6 +- 4 files changed, 114 insertions(+), 83 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 4f45f3bfcc7e..f8dbdef98f36 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -80,6 +80,8 @@ Definition 4.13. [BH22] """ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) zg = genus(Zlattice(gram = matrix(QQ, 0, 0, []))) + AperpB = orthogonal_sum(A, B) + (signature_tuple(AperpB) == signature_tuple(C)) || (return false) if ((A == zg) && (B == C)) || ((B == zg) && (A == C)) # C can be always glued with the empty genus to obtain C return true @@ -89,11 +91,6 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) end @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" - AperpB = orthogonal_sum(A,B) - # If A and B glue to C, the sum of their ranks must match the one of C - if signature_tuple(AperpB) != signature_tuple(C) - return false - end lA = ngens(discriminant_group(A)) lB = ngens(discriminant_group(B)) @@ -155,13 +152,13 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) if a_max == g if length(symbol(Ap)) > 1 - Ar = ZpGenus(p, Ap[1:end-1]) + Ar = ZpGenus(p, symbol(Ap)[1:end-1]) else Ar = genus(matrix(ZZ,0,0,[]), p) end if length(symbol(Bp)) > 1 - Br = ZpGenus(p, Bp[1:end-1]) + Br = ZpGenus(p, symbol(Bp)[1:end-1]) else Br = genus(matrix(ZZ, 0, 0, []), p) end @@ -344,23 +341,25 @@ $G_B\backslash S\slash/G_A$ where: See Algorithm 2 of [BH22]. """ -function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer) +function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer; check=true) # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" A, B, C = lattice.([Afa, Bfb, Cfc]) - @req is_admissible_triple(Afa, Bfb, Cfc, p) "(A, B, C) must be p-admissble" - - if ambient_space(Afa) === ambient_space(Bfb) === ambient_space(Cfc) - @req rank(intersect(A, B)) == 0 "Lattice in same ambient space must have empty intersection to glue" - @req rank(A) + rank(B) <= dim(ambient_space(A)) "Lattice cannot glue in their ambient space" + if check + @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" + @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple" + if ambient_space(A) === ambient_space(B) === ambient_space(C) + G = gram_matrix(ambient_space(C)) + @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattice in same ambient space must be orthogonal" + end end - # we need to compare the type of the output with the one of Cfc - results = LatticeWithIsometry[] + # this is the glue valuation: it is well-defined because the triple in input is admissible g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) + fA, fB = isometry.([Afa, Bfb]) qA, fqA = discriminant_group(Afa) qB, fqB = discriminant_group(Bfb) @@ -374,7 +373,6 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry D, qAinD, qBinD = orthogonal_sum(qA, qB) end - return qAinD OD = orthogonal_group(D) OqAinOD = embedding_orthogonal_group(qAinD) OqBinOD = embedding_orthogonal_group(qBinD) @@ -390,7 +388,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(b) for b in geneB] gene = vcat(geneA, geneB) GC2, _ = sub(OD, gene) - if ambient_space(Afa) === ambient_space(Bfb) + if ambient_space(A) === ambient_space(B) === ambient_space(C) C2 = A+B fC2 = block_diagonal_matrix([fA, fB]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) @@ -398,7 +396,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) else C2 = orthogonal_sum(A, B)[1] - fC2 = block_diagonal_matrix(fA, fB) + fC2 = block_diagonal_matrix([fA, fB]) end if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(Cfc)) C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) @@ -427,6 +425,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # are fA-stable (resp. fB-stable) subsA = _subgroups_representatives(VAinqA, GA, g, fVA, ZZ(l)) subsB = _subgroups_representatives(VBinqB, GB, g, fVB, ZZ(l)) + # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry R = Tuple{eltype(subsA), eltype(subsB), TorQuadModMor}[] @@ -435,6 +434,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry !ok && continue push!(R, (H1, H2, phi)) end + # now, for each pair of anti-isometric potential kernels, we need to see whether # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the # corresponding overlattice and check whether it satisfies the type condition @@ -452,8 +452,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry OSBinOD = compose(OSBinOqB, OqBinOD) # we compute the image of the stabalizers in the respective OS* and we keep track - # of the elements of the stabilizers action trivially in the respective S* - + # of the elements of the stabilizers acting trivially in the respective S* actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) imA, _ = image(actA) kerA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(x) for x in gens(kernel(actA)[1])] @@ -473,10 +472,10 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB # We compute the generators of O(SB, rho_{l+1}(qB)) - OSBrB, OSBrbinOSB = _stabilizer(rBinSB) + OSBrB, _ = _stabilizer(rBinSB) @assert fSB in OSBrB - # phi might not "send" the restriction of fA to this of fB, but at least phi(fA)phi^-1 + # 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 glueing. # If not, we try the next potential pair. fSAinOSBrB = OSBrB(compose(inv(phi), compose(hom(fSA), phi))) @@ -499,7 +498,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # now we iterate over all double cosets and for each representative, we compute the # corresponding overlattice in the glueing. If it has the wanted type, we compute - # the image of the centralizer in OD from the stabA ans stabB. + # the image of the centralizer in OD from the stabA and stabB. for g in reps g = representative(g) phig = compose(phi, hom(g)) @@ -508,7 +507,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry if ambient_space(R) === ambient_space(S) _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] - z = zero_matrix(QQ,0, degree(S)) + z = zero_matrix(QQ, 0, degree(S)) glue = reduce(vcat, [matrix(QQ, 1, degree(S), g) for g in _glue], init=z) glue = vcat(basis_matrix(S+R), glue) glue = FakeFmpqMat(glue) @@ -533,14 +532,14 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end - if type(lattice_with_isometry(C2, fC2^p, ambient_representation=false)) != type(Cfc) + if !is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation=false), type(Cfc)) continue end ext, _ = sub(D, D.(_glue)) - perp, _ = orthogonal_submodule(D, ext) + 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, discinD = sub(D, gens(disc)) + qC2 = discriminant_group(C2) ok, phi2 = is_isometric_with_isometry(qC2, disc) @assert ok @@ -552,7 +551,9 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry stab = AutomorphismGroupElem{TorQuadMod}[OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] stab = union(stab, kerA) stab = union(stab, kerB) - stab = Oscar._orthogonal_group(discriminant_group(C2), [compose(phi2, compose(_restrict(g, discinD), inv(phi2))).map_ab.map for g in stab]) + stab = TorQuadModMor[_restrict(g, j) for g in stab] + stab = TorQuadModMor[hom(disc, disc, [disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] + stab = Oscar._orthogonal_group(discriminant_group(C2), [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) push!(results, C2fC2) end @@ -580,6 +581,7 @@ function _ideals_of_norm(E, d::fmpq) end function _ideals_of_norm(E, d::fmpz) + isone(d) && return [1*maximal_order(E)] @assert E isa Hecke.NfRel K = base_field(E) OK = maximal_order(K) @@ -597,7 +599,7 @@ function _ideals_of_norm(E, d::fmpz) P = ideal(OE, pd[i]) end nv = valuation(norm(P), pd[i]) - push!(primes, [P^e for e in 1:divrem(v, nv)[1]]) + push!(primes, [P^e for e in 0:divrem(v, nv)[1]]) end end for I in Hecke.cartesian_product_iterator(primes) @@ -662,54 +664,60 @@ $nm$. See Algorithm 3 of [BH22]. """ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) - rank(Lf) == 0 && return LatticeWithIsometry[Lf] + rank(Lf) == 0 && return LatticeWithIsometry[] + @req m >= 1 "m must be a positive integer" @req is_of_pure_type(Lf) "Minimal polyomial must be irreducible and cyclotomic" + L = lattice(Lf) rk = rank(L) d = det(L) n = order_of_isometry(Lf) s1, _, s2 = signature_tuple(L) + reps = LatticeWithIsometry[] if n*m < 3 @info "Order smaller than 3" - gene = genera((s1, s2), ZZ(d), even=iseven(L)) - @info "All possible genera: $(length(gene))" f = (-1)^(n*m+1)*identity_matrix(QQ, rk) - for G in gene - repre = representatives(G) - @info "$(length(repre)) representatives" - for LL in repre - is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) - end + G = genus(Lf) + repre = representatives(G) + @info "$(length(repre)) representatives" + for LL in repre + is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) end return reps end + @info "Order bigger than 3" + ok, rk = divides(rk, euler_phi(n*m)) - if !ok - return reps - end + ok || return reps - t = type(Lf) gene = [] E, b = cyclotomic_field_as_cm_extension(n*m) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) + @info "We have the different" - K = base_field(E) + ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) + @info "All possible ideal dets: $(length(detE))" + signatures = _possible_signatures(s1, s2, E, rk) + @info "All possible signatures: $(length(signatures))" + for dd in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, dd)) + append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) end gene = unique(gene) + @info "All possible genera: $(length(gene))" + for g in gene @info "g = $g" H = representative(g) @@ -724,7 +732,7 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) if iseven(lattice(M)) != iseven(L) continue end - if !is_of_type(lattice_with_isometry(lattice(M), ambient_isometry(M)^m), t) + if !is_of_same_type(Lf, lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) continue end append!(reps, [trace_lattice(HH) for HH in genus_representatives(H)]) @@ -765,33 +773,33 @@ function _representative(t::Dict; check::Bool = true) d = det(G) if n < 3 - gene = genera((s1, s2), ZZ(d), max_scale=scale(G), even=iseven(G)) - f = (-1)^(n+1)*identity_matrix(QQ, rk) - for g in gene - repre = representatives(g) - append!(reps, [lattice_with_isometry(LL, f, check=false) for LL in repre]) - end - return reps + L = representative(G) + return trace_lattice(L, order = n) end ok, rk = divides(rk, euler_phi(n)) - if !ok - return reps - end + + ok || reps gene = [] E, b = cyclotomic_field_as_cm_extension(n, cached=false) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) + ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) + @info "All possible ideal dets: $(length(detE))" + signatures = _possible_signatures(s1, s2, E) + @info "All possible signatures: $(length(signatures))" + for dd in detE, sign in signatures - append!(gene, genera_hermitian(E, s1+s2, sign, dd)) + append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) end gene = unique(gene) + for g in gene H = representative(g) if !is_integral(DE*scale(H)) @@ -828,19 +836,25 @@ Note that `d` can be 0. See Algorithm 4 of [BH22]. """ function prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::Int) - rank(Lf) == 0 && return LatticeWithIsometry[Lf] + rank(Lf) == 0 && return LatticeWithIsometry[] + @req is_prime(p) "p must be a prime number" @req is_of_pure_type(Lf) "Minimal polynomial must be irreducible and cyclotomic" + ok, q, d = is_prime_power_with_data(order_of_isometry(Lf)) + @req ok || d == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" + reps = LatticeWithIsometry[] + atp = admissible_triples(Lf, p) for (A, B) in atp - LA = lattice_with_isometry(representative(A)) - RA = representatives_of_pure_type(LA, p*q^d) LB = lattice_with_isometry(representative(B)) - RB = representatives_of_pure_type(LB, q^d) + RB = representatives_of_pure_type(LB, p*q^d) + isempty(RB) && continue + LA = lattice_with_isometry(representative(A)) + RA = representatives_of_pure_type(LA, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) E = primitive_extensions(L1, L2, Lf, p) append!(reps, E) @@ -886,20 +900,29 @@ Note that `e` can be 0. See Algorithm 5 of [BH22]. """ function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) - rank(Lf) == 0 && return LatticeWithIsometry[Lf] + rank(Lf) == 0 && return LatticeWithIsometry[] + @req is_prime(p) "p must be a prime number" @req b in [0, 1] "b must be an integer equal to 0 or 1" + ok, q, e = is_prime_power_with_data(order_of_isometry(Lf)) + @req ok || e == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" + reps = LatticeWithIsometry[] + if e == 0 - return prime_splitting_of_pure_type_prime_power(Lf, p) + reps = prime_splitting_of_pure_type_prime_power(Lf, p) + (b == 1) && filter!(M -> order_of_isometry(M) == p, reps) + return reps end + x = gen(Hecke.Globals.Qx) A0 = kernel_lattice(Lf, q^e) B0 = kernel_lattice(Lf, x^(q^e-1)-1) A = prime_splitting_of_pure_type_prime_power(A0, p) + is_empty(A) && return reps B = prime_splitting_of_prime_power(B0, p) for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue @@ -925,12 +948,16 @@ Note that `e` can be 0, while `d` has to be positive. See Algorithm 6 of [BH22]. """ function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) - rank(Lf) == 0 && return LatticeWithIsometry[Lf] + rank(Lf) == 0 && return LatticeWithIsometry[] + @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 factors" + if length(pd) == 2 q = pd[1] == p ? pd[2] : pd[1] d = valuation(n, p) @@ -940,20 +967,27 @@ function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) d = valuation(n, p) e = 0 end + phi = minpoly(Lf) x = gen(parent(phi)) chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) + @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" + reps = LatticeWithIsometry[] + if e == 0 return representatives_of_pure_type(Lf, p) end + A0 = kernel_lattice(Lf, p^d*q^e) bool, r = divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) @assert bool + B0 = kernel_lattice(Lf, r) - A = representatives_of_pure_type(A0 ,p) - B = prime_splitting(B0, p) + A = representatives_of_pure_type(A0, p) + is_empty(A) && return reps + B = prime_splitting_of_semi_pure_type(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = extensions(LA, LB, Lf, q) append!(reps, E) @@ -975,14 +1009,20 @@ Note that `d` and `e` can be both zero. See Algorithm 7 of [BH22]. """ function prime_splitting(Lf::LatticeWithIsometry, p::Int) - rank(Lf) == 0 && return LatticeWithIsometry[Lf] + rank(Lf) == 0 && return LatticeWithIsometry[] + n = order_of_isometry(Lf) + @req is_finite(n) "Isometry must be of finite order" + pd = prime_divisors(n) + @req length(pd) <= 2 "Order must have at most 2 prime divisors" + if !(p in pd) return prime_splitting_of_prime_power(Lf, p, 1) end + d = valuation(n, p) if n != p^d _, q, e = is_prime_power_with_data(divexact(n, p^d)) @@ -990,11 +1030,14 @@ function prime_splitting(Lf::LatticeWithIsometry, p::Int) q = 1 e = 0 end + reps = LatticeWithIsometry[] + x = gen(parent(minpoly(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])) A = prime_splitting_of_semi_pure_type(A0, p) + isempty(A) && return reps B = prime_splitting(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = extensions(LA, LB, Lf, p) diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 222d48493c34..2cef956b8eb7 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -140,7 +140,7 @@ function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::IntExt; check::Bool = tr f_ambient = f B = basis_matrix(L) ok, f = can_solve_with_solution(B, B*f_ambient, side = :left) - @req ok "Isometry does not restrict to f" + @req ok "Isometry does not restrict to L" else V = ambient_space(L) B = basis_matrix(L) @@ -253,7 +253,7 @@ $O(q_L, \bar{f})$ of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ denotes the discriminant group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. """ -@attr AutomorphismGroup function image_centralizer_in_Oq(Lf::LatticeWithIsometry) +@attr AutomorphismGroup{TorQuadMod} function image_centralizer_in_Oq(Lf::LatticeWithIsometry) n = order_of_isometry(Lf) L = lattice(Lf) f = ambient_isometry(Lf) diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl index aa3bcad24e36..b70496a0e56f 100644 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ b/src/NumberTheory/LatticesWithIsometry/Misc.jl @@ -65,18 +65,6 @@ end # ############################################################################## -#function _small_Fp_stabilizer(i::TorquadModMor) -# ok, j = has_complement(i) -# @assert ok -# A = domain(i) -# B = domain(j) -# C = codomain(i) -# Corth = direct_sum(A, B)[1] -# R, phi = hom(abelian_group(B), abelian_group(A)) -# geneBtoA = filter(r -> matrix(phi(r))*Hecke.gram_matrix_quadratic(A)*transpose(matrix(phi(r))) == Hecke.gram_matrix_quadratic(B), R) - - - function _as_Fp_vector_space_quotient(HinV, p, f) i = HinV.map_ab H = domain(HinV) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index 9ba83f44a46c..c405562a18d6 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -43,7 +43,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) beta = beta//s(beta) end - if E == QQ || E == ZZ + if E == QQ if order == 1 f = identity_matrix(E, n) elseif order == 2 @@ -70,7 +70,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) v[i] = zero(QQ) end - return lattice_with_isometry(Lres, iso, ambient_representation=true, check = true) + return lattice_with_isometry(Lres, iso, ambient_representation=true) end function absolute_representation_matrix(b::Hecke.NfRelElem) @@ -94,7 +94,7 @@ function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, n = multiplicative_order(f) end - @req n > 0 "f is not of finite exponent" + @req is_finite(n) && n > 0 "Isometry must be non-trivial and of finite exponent" if ambient_representation ok, f = can_solve_with_solution(basis_matrix(L), basis_matrix(L)*f, side =:left) From 45cab5f2aebca44ec9342086b714c6090916356e Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 2 Feb 2023 15:58:44 +0100 Subject: [PATCH 25/76] add embeddings --- src/NumberTheory/LWI.jl | 2 +- .../LatticesWithIsometry/Embeddings.jl | 620 ++++++++++++++++++ .../LatticesWithIsometry/Enumeration.jl | 321 --------- src/NumberTheory/LatticesWithIsometry/Misc.jl | 176 ----- 4 files changed, 621 insertions(+), 498 deletions(-) create mode 100644 src/NumberTheory/LatticesWithIsometry/Embeddings.jl delete mode 100644 src/NumberTheory/LatticesWithIsometry/Misc.jl diff --git a/src/NumberTheory/LWI.jl b/src/NumberTheory/LWI.jl index b8b80764cafe..42ee840b537d 100644 --- a/src/NumberTheory/LWI.jl +++ b/src/NumberTheory/LWI.jl @@ -1,4 +1,4 @@ -include("LatticesWithIsometry/Misc.jl") +include("LatticesWithIsometry/Embeddings.jl") include("LatticesWithIsometry/Types.jl") include("LatticesWithIsometry/TraceEquivalence.jl") include("LatticesWithIsometry/LatticesWithIsometry.jl") diff --git a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl new file mode 100644 index 000000000000..077433989802 --- /dev/null +++ b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl @@ -0,0 +1,620 @@ +export primitive_embeddings_in_elementary, + primitive_extensions + +GG = GAP.Globals + +################################################################################ +# +# Miscellaneous +# +################################################################################ + +function inner_orthogonal_sum(T::TorQuadMod, U::TorQuadMod) + @assert modulus_bilinear_form(T) == modulus_bilinear_form(U) + @assert modulus_quadratic_form(T) == modulus_quadratic_form(U) + @assert ambient_space(cover(T)) === ambient_space(cover(U)) + cS = cover(T)+cover(U) + rS = relations(T)+relations(U) + geneT = [lift(a) for a in gens(T)] + geneU = [lift(a) for a in gens(U)] + S = torsion_quadratic_module(cS, rS, gens = unique([g for g in vcat(geneT, geneU) if !is_zero(g)]), modulus = modulus_bilinear_form(T), modulus_qf = modulus_quadratic_form(T)) + TinS = hom(T, S, S.(geneT)) + UinS = hom(U, S, S.(geneU)) + if order(S) == 1 + S.gram_matrix_quadratic = matrix(QQ,0,0,[]) + S.gram_matrix_bilinear = matrix(QQ,0,0,[]) + set_attribute!(S, :is_degenerate, false) + S.ab_grp = abelian_group() + end + return S, TinS, UinS +end + +function _restrict(f::TorQuadModMor, i::TorQuadModMor) + imgs = TorQuadModElem[] + V = domain(i) + for g in gens(V) + h = f(i(g)) + hV = preimage(i, h) + push!(imgs, hV) + end + return hom(V, V, imgs) +end + +function _restrict(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) + return _restrict(hom(f), i) +end + +function _is_invariant(f::TorQuadModMor, i::TorQuadModMor) + fab = f.map_ab + V = domain(i) + for a in gens(V) + b = f(i(a)) + haspreimage(fab, data(b))[1] || return false + end + return true +end + +function _is_invariant(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) + return _is_invariant(hom(f), i) +end + +function _is_invariant(aut::AutomorphismGroup{TorQuadMod}, i::TorQuadModMor) + return all(g -> _is_invariant(g, i), gens(aut)) +end + +function _get_V(L, q, f, fq, mu, p) + f_mu = mu(f) + if !is_zero(f_mu) + L_sub = intersect(lattice_in_same_ambient_space(L, inv(f_mu)*basis_matrix(L)), dual(L)) + B = basis_matrix(L_sub) + V, Vinq = sub(q, q.([vec(collect(B[i,:])) for i in 1:nrows(B)])) + else + V, Vinq = q, id_hom(q) + end + pV, pVinV = primary_part(V, p) + pV, pVinV = sub(V, pVinV.([divexact(order(g), p)*g for g in gens(pV) if !(order(g)==1)])) + pVinq = compose(pVinV, Vinq) + fpV = _restrict(fq, pVinq) + return pV, pVinq, fpV +end + +function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) + N = relations(q) + if l == 0 + Gl = N + Gm = intersect(1//p*N, dual(N)) + rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) + gene = [q(lift(g)) for g in gens(rholN)] + return sub(q, gene)[2] + end + k = l-1 + m = l+1 + Gk = intersect(1//p^k*N, dual(N)) + Gl = intersect(1//p^l*N, dual(N)) + Gm = intersect(1//p^m*N, dual(N)) + rholN = torsion_quadratic_module(Gl, Gk+p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) + gene = [q(lift(g)) for g in gens(rholN)] + return sub(q, gene) +end + +function on_subgroup(H::Oscar.GAPGroup, g::AutomorphismGroupElem) + G = domain(parent(g)) + return sub(G, g.(gens(H)))[1] +end + +function stabilizer(O::AutomorphismGroup{TorQuadMod}, i::TorQuadModMor) + @req domain(O) === codomain(i) "Incompatible arguments" + q = domain(O) + togap = get_attribute(O, :to_gap) + tooscar = get_attribute(O, :to_oscar) + A = codomain(togap) + OA = automorphism_group(A) + OinOA, _ = sub(OA, OA.([g.X for g in gens(O)])) + N, _ = sub(A, togap.(i.(gens(domain(i))))) + orb = orbit(OinOA, on_subgroup, N) + stab, _ = stabilizer(OinOA, N, on_subgroup) + return sub(O, O.([h.X for h in gens(stab)])) +end + +############################################################################## +# +# Orbits and stabilizers of elementary conjugate subgroups +# +############################################################################## + +function _as_Fp_vector_space_quotient(HinV, p, f) + i = HinV.map_ab + H = domain(HinV) + Hab = domain(i) + Hs, HstoHab = snf(Hab) + V = codomain(HinV) + Vab = codomain(i) + Vs, VstoVab = snf(Vab) + + function _VtoVs(x::TorQuadModElem) + return inv(VstoVab)(data(x)) + end + + function _VstoV(x::GrpAbFinGenElem) + return V(VstoVab(x)) + end + + VtoVs = Hecke.MapFromFunc(_VtoVs, _VstoV, V, Vs) + + n = ngens(Vs) + F = GF(p) + MVs = matrix(compose(VstoVab, compose(f.map_ab, inv(VstoVab)))) + Vp = VectorSpace(F, n) + + function _VstoVp(x::GrpAbFinGenElem) + v = x.coeff + return Vp(vec(collect(v))) + end + + function _VptoVs(v::ModuleElem{gfp_fmpz_elem}) + x = lift.(v.v) + return Vs(vec(collect(x))) + end + + VstoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) + VtoVp = compose(VtoVs, VstoVp) + gene = gfp_fmpz_mat[matrix(F.((i(HstoHab(a))).coeff)) for a in gens(Hs)] + subgene = [Vp(vec(collect(transpose(v)))) for v in gene] + Hp, _ = sub(Vp, subgene) + Qp, VptoQp = quo(Vp, Hp) + fVp = change_base_ring(F, MVs) + ok, fQp = can_solve_with_solution(VptoQp.matrix, fVp*VptoQp.matrix) + @assert ok + + + return Qp, VtoVp, VptoQp, fQp +end + +function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, g::Int, f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), l::Hecke.IntegerUnion = 0) + V = domain(Vinq) + q = codomain(Vinq) + p = elementary_divisors(V)[1] + @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" + H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) + Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) + Vp = codomain(VtoVp) + + if dim(Qp) == 0 + if order(V) == p^g + return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(Vinq, G)] + end + return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + end + + OV = orthogonal_group(V) + gene_GV = elem_type(OV)[OV(_restrict(g, Vinq), check = false) for g in gens(G)] + GV, _ = sub(OV, gene_GV) + GVinG = hom(GV, G, gens(GV), gens(G), check = false) + + act_GV_Vp = gfp_fmpz_mat[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] + act_GV_Qp = gfp_fmpz_mat[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] + MGp = matrix_group(act_GV_Qp) + @assert fQp in MGp + MGptoGV = hom(MGp, GV, gens(GV), check = false) + MGptoG = compose(MGptoGV, GVinG) + + res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + + if g-ngens(snf(abelian_group(H0))[1]) > dim(Qp) + return res + end + + F = base_ring(Qp) + k, K = kernel(VptoQp.matrix, side = :left) + gene_H0p = ModuleElem{gfp_elem}[Vp(vec(collect(K[i,:]))) for i in 1:k] + orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) + for (orb, stab) in orb_and_stab + i = orb.map + _V = codomain(i) + for v in gens(orb) + vv = _V(i(v)*fQp) + if !can_solve(i.matrix, vv.v, side = :left) + @goto non_fixed + end + end + gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{gfp_fmpz_elem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] + gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{gfp_fmpz_elem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] + gene_submod_in_Vp = vcat(gene_orb_in_Vp, gene_H0p) + gene_submod_in_V = TorQuadModElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] + gene_submod_in_q = TorQuadModElem[image(Vinq, v) for v in gene_submod_in_V] + orbq, orbqinq = sub(q, gene_submod_in_q) + @assert order(orbq) == p^g + stabq, _ = image(MGptoG, stab) + push!(res, (orbqinq, stabq)) + @label non_fixed + end + return res +end + +function subgroups_orbits_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadMod}; order::Hecke.IntegerUnion = -1) + togap = get_attribute(O, :to_gap) + tooscar = get_attribute(O, :to_oscar) + q = domain(O) + A = abelian_group(q) + it = order == -1 ? subgroups(A) : subgroups(A, order=order) + co = [sub(q, q.(j[2].(gens(j[1])))) for j in it] + coAgap = [sub(Agap, togap.(j[1].(gens(j[2]))))[1] for j in co] + Agap = codomain(togap) + OAgap = automorphism_group(Agap) + OinOAgap, j = sub(OAgap, OAgap.([g.X for g in gens(O)])) + m = gset(OinOAgap, on_sub, coAgap) + orbs = orbits(m) + res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + for orb in orbs + rep = representative(orb) + stab, _ = stabilizer(OinOAgap, rep, on_subgroup) + rep = sub(q, TorQuadModElem[tooscar(Agap(g)) for g in gens(rep)]) + stab, _ = sub(O, O.([h.X for h in gens(stab)])) + push!(res, (rep, stab)) + end + return res +end + +function classes_conjugate_subgroups(O::AutomorphismGroup{TorQuadMod}, q::TorQuadMod) + sors = subgroups_orbits_representatives_and_stabilizers(O, order=order(q)) + return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) +end + +################################################################################# +# +# Embeddings in elementary lattices +# +################################################################################# + +function _isomorphism_classes_primitive_embeddings(N::Zlat, M::ZLat, H::TorQuadMod) + results = Tuple{ZLat, ZLat, ZLat}[] + GN, GNinqN = image_in_Oq(N) + GM, GMinqM = image_in_Oq(M) + qN = codomain(GNinqN) + qM = codomain(GMinqM) + + D, qNinD, qMinD = orthogonal_sum(qN, qM) + OD = orthogonal_group(D) + OqNinOD = embedding_orthogonal_group(qNinOD) + OqMinOD = embedding_orthogonal_group(qMinOD) + OqN = domain(OqNinOD) + OqM = domain(OqMinOD) + + subsN = classes_conjugate_subgroups(GN, rescale(H, -1)) + @assert !isempty(N) + subsM = classes_conjugate_subgroups(GM, H) + @assert !isempty(N) + + for H1 in subsN, H2 in subsM + ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) + @assert ok + + HNinqN, stabN = H1 + HN = domain(HNinqN) + OHNinOqN = embedding_orthogonal_group(HNinqN) + OHN = domain(OHNinOqN) + OHNinOD = compose(OHNinOqN, OqNinOD) + + HMinqM, stabM = H2 + HM = domain(HMinqM) + OHMinOqM = embedding_orthogonal_group(HMinqM) + OHM = domain(OHMinOqM) + OHMinOD = compose(OHMinOqM, OqMinOD) + + actN = hom(stabN, OHN, [OHN(_restrict(x, HNinqN)) for x in gens(stabN)]) + imN, _ = image(actN) + kerN = AutomorphismGroupElem{TorQuadMod}[OqNinOD(x) for in gens(kernel(actN)[1])] + + actM = hom(stabM, OHM, [OHM(_restrict(x, HMinqM)) for x in gens(stabM)]) + imM, _ = image(actM) + kerM = AutomorphismGroupElem{TorQuadMod}[OqMinOD(x) for x in gens(kernel(actM)[1])] + + stabNphi = AutomorphismGroupElem{TorQuadMod}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabA)] + stabNphi, _ = sub(OHM, stabNphi) + reps = double_cosets(OHM, stabNphi, imM) + + for g in reps + g = representative(g) + phig = compose(phi, hom(g)) + S = relations(domain(phig)) + R = relations(codomain(phig)) + _glue = Vector{fmpq}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] + z = zero_matrix(QQ, 0, degree(N)+degree(M)) + glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) + glue = vcat(block_diagonal_matrix([basis_matrix(N), basis_matrix(M)]), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) + N2 = lattice_in_same_ambient_space(L, _B[1:rank(N),:]) + @assert genus(N) == genus(N2) + M2 = orthgonal_submodule(L, N2) + @assert genus(M) == genus(M2) + push!(results, (L, M2, N2)) + end + end + return results +end + +function primitive_embeddings_in_elementary(L::ZLat, M::ZLat) + bool, p = is_elementary_with_prime(L) + @req bool "L must be unimodular or elementary" + + results = Tuple{ZLat, ZLat, ZLat}[] + qL = discriminant_group(L) + OqL = orthogonal_group(qL) + pL, nL = signature_pair(L) + pM, nM = signature_pair(M) + @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" + qM = discriminant_group(M) + VM, VMinqM, _ = _get_V(M, qM, identity_matrix(QQ, rank(M)), id_hom(qM), minpoly(identity_matrix(QQ,1)), p) + g = 0 + while p^g <= min(order(qL), order(VM)) + subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), OqL, g) + for H in subsL + HL = domain(H[1]) + it = subgroups(abelian_group(VM), order = order(HL)) + subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1]))))) for j in it] + filter!(HM -> is_isometric_with_isometry(domain(HM), HL), subsM) + isempty(subsM) && continue + ok, j = has_complement(subsM[1]) + @assert ok + qM2 = domain(j) + qL2 = codomain(has_complement(H)[2]) + D = orthogonal_sum(rescale(qM2, -1), qL2) + !is_genus(D, (pL-pM, nL-nM)) && continue + G = genus(D, (pL-pM, nL-nM)) + Ns = representatives(g) + Ns = lll.(Ns) + for N in Ns + append!(results, [_isomorphism_classes_primitive_embeddings(N, M, qM2)]) + end + end + g += 1 + end + return results +end + +#################################################################################### +# +# Admissible primitive extensions +# +#################################################################################### + +@doc Markdown.doc""" + primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Int) + -> Vector{LatticeWithIsometry} + +Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a prime number +`p`, such that `(A, B, C)` is `p`-admissible, return a set of representatives of the double coset +$G_B\backslash S\slash/G_A$ where: + + - $G_A$ and $G_B$ are the respective images of the morphisms $O(A, fa) -> O(q_A, \bar{fa})$ + and $O(B, fb) -> O(q_B, \bar{fb})$; + - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry $fc'$ where + $p\cdot C' \subsetea A\perpB$ and the type of $(C', fc'^p)$ is equal to the type of $(C, fc)$. + +See Algorithm 2 of [BH22]. +""" +function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer; check=true) + # requirement for the algorithm of BH22 + @req is_prime(p) "p must be a prime number" + A, B, C = lattice.([Afa, Bfb, Cfc]) + + if check + @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" + @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple" + if ambient_space(A) === ambient_space(B) === ambient_space(C) + G = gram_matrix(ambient_space(C)) + @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattice in same ambient space must be orthogonal" + end + end + + results = LatticeWithIsometry[] + + # this is the glue valuation: it is well-defined because the triple in input is admissible + g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) + + fA, fB = isometry.([Afa, Bfb]) + qA, fqA = discriminant_group(Afa) + qB, fqB = discriminant_group(Bfb) + GA = image_centralizer_in_Oq(Afa) + GB = image_centralizer_in_Oq(Bfb) + + # this is where we will perform the glueing + if ambient_space(Afa) === ambient_space(Bfb) + D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) + else + D, qAinD, qBinD = orthogonal_sum(qA, qB) + end + + OD = orthogonal_group(D) + OqAinOD = embedding_orthogonal_group(qAinD) + OqBinOD = embedding_orthogonal_group(qBinD) + OqA = domain(OqAinOD) + OqB = domain(OqBinOD) + + # if the glue valuation is zero, then we glue along the trivial group and we don't + # have much more to do. Since the triple is p-admissible, A+B = C + if g == 0 + geneA = gens(GA) + geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(a) for a in geneA] + geneB = gens(GB) + geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(b) for b in geneB] + gene = vcat(geneA, geneB) + GC2, _ = sub(OD, gene) + if ambient_space(A) === ambient_space(B) === ambient_space(C) + C2 = A+B + fC2 = block_diagonal_matrix([fA, fB]) + _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) + fC2 = _B*fC2*inv(_B) + @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + else + C2 = orthogonal_sum(A, B)[1] + fC2 = block_diagonal_matrix([fA, fB]) + end + if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(Cfc)) + C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) + set_attribute!(C2fc2, :image_centralizer_in_Oq, GC2) + push!(results, C2fc2) + end + return results + end + + # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map + VA, VAinqA, fVA = _get_V(A, qA, isometry(Afa), fqA, minpoly(Bfb), p) + VB, VBinqB, fVB = _get_V(B, qB, isometry(Bfb), fqB, minpoly(Afa), p) + + # since the glue kernels must have order p^g, in this condition, we have nothing + if min(order(VA), order(VB)) < p^g + return results + end + + # scale of the dual: any glue kernel must contain the multiples of l of the respective + # discriminant groups + l = level(genus(C)) + + # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively + # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) + # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones + # are fA-stable (resp. fB-stable) + subsA = _subgroups_representatives(VAinqA, GA, g, fVA, ZZ(l)) + subsB = _subgroups_representatives(VBinqB, GB, g, fVB, ZZ(l)) + + # once we have the potential kernels, we create pairs of anti-isometric groups since glue + # maps are anti-isometry + R = Tuple{eltype(subsA), eltype(subsB), TorQuadModMor}[] + for H1 in subsA, H2 in subsB + ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) + !ok && continue + push!(R, (H1, H2, phi)) + end + + # now, for each pair of anti-isometric potential kernels, we need to see whether + # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the + # corresponding overlattice and check whether it satisfies the type condition + for (H1, H2, phi) in R + SAinqA, stabA = H1 + SA = domain(SAinqA) + OSAinOqA = embedding_orthogonal_group(SAinqA) + OSA = domain(OSAinOqA) + OSAinOD = compose(OSAinOqA, OqAinOD) + + SBinqB, stabB = H2 + SB = domain(SBinqB) + OSBinOqB = embedding_orthogonal_group(SBinqB) + OSB = domain(OSBinOqB) + OSBinOD = compose(OSBinOqB, OqBinOD) + + # we compute the image of the stabalizers in the respective OS* and we keep track + # of the elements of the stabilizers acting trivially in the respective S* + actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) + imA, _ = image(actA) + kerA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(x) for x in gens(kernel(actA)[1])] + fSA = OSA(_restrict(fqA, SAinqA)) + + actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) + imB, _ = image(actB) + kerB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(x) for x in gens(kernel(actB)[1])] + fSB = OSB(_restrict(fqB, SBinqB)) + + # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an + # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi + # under the action of O(SB, rho_{l+1}(qB), fB) + rB, rBinqB = _rho_functor(qB, p, valuation(l, p)+1) + @assert Oscar._is_invariant(stabB, rBinqB) + rBinSB = hom(domain(rBinqB), SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) + @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB + + # We compute the generators of O(SB, rho_{l+1}(qB)) + OSBrB, _ = _stabilizer(rBinSB) + @assert fSB in OSBrB + + # 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 glueing. + # If not, we try the next potential pair. + fSAinOSBrB = OSBrB(compose(inv(phi), compose(hom(fSA), phi))) + bool, g0 = representative_action(OSBrB, fSAinOSBrB, OSBrB(fSB)) + bool || continue + phi = compose(phi, hom(OSB(g0))) + fSAinOSBrB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + # Now the new phi is "sending" the restriction of fA to this of fB. + # So we can glue SA and SB. + @assert fSAinOSBrB == fSB + + # 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, OSBrB(fSB)) + center, _ = sub(OSB, [OSB(c) for c in gens(center)]) + stabSAphi = AutomorphismGroupElem{TorQuadMod}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] + stabSAphi, _ = sub(center, stabSAphi) + stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) + reps = double_cosets(center, stabSB, stabSAphi) + + # now we iterate over all double cosets and for each representative, we compute the + # corresponding overlattice in the glueing. If it has the wanted type, we compute + # the image of the centralizer in OD from the stabA and stabB. + + for g in reps + g = representative(g) + phig = compose(phi, hom(g)) + S = relations(domain(phig)) + R = relations(codomain(phig)) + + if ambient_space(R) === ambient_space(S) + _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] + z = zero_matrix(QQ, 0, degree(S)) + glue = reduce(vcat, [matrix(QQ, 1, degree(S), g) for g in _glue], init=z) + glue = vcat(basis_matrix(S+R), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + C2 = lattice(ambient_space(S), _B[end-rank(S)-rank(R)+1:end, :]) + fC2 = block_diagonal_matrix([fA, fB]) + _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) + fC2 = _B*fC2*inv(_B) + else + _glue = Vector{fmpq}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] + z = zero_matrix(QQ,0,degree(S)+degree(R)) + glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in _glue], init=z) + glue = vcat(block_diagonal_matrix([basis_matrix(S), basis_matrix(R)]), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + C2 = lattice(ambient_space(cover(D)), _B[end-rank(S)-rank(R)+1:end, :]) + fC2 = block_diagonal_matrix([fA, fB]) + __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) + fC2 = __B*fC2*inv(__B) + @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + end + + if !is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation=false), type(Cfc)) + continue + end + + ext, _ = sub(D, D.(_glue)) + 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)) + + qC2 = discriminant_group(C2) + ok, phi2 = is_isometric_with_isometry(qC2, disc) + @assert ok + + C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) + im2_phi, _ = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)])) + im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) + stab = Tuple{AutomorphismGroupElem{TorQuadMod}, AutomorphismGroupElem{TorQuadMod}}[(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] + stab = AutomorphismGroupElem{TorQuadMod}[OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] + stab = union(stab, kerA) + stab = union(stab, kerB) + stab = TorQuadModMor[_restrict(g, j) for g in stab] + stab = TorQuadModMor[hom(disc, disc, [disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] + stab = Oscar._orthogonal_group(discriminant_group(C2), [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) + set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) + push!(results, C2fC2) + end + end + + return results +end + diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index f8dbdef98f36..e73490b379b4 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -4,7 +4,6 @@ export admissible_triples, prime_splitting_of_prime_power, prime_splitting_of_pure_type_prime_power, prime_splitting_of_semi_pure_type, - primitive_extensions, representatives_of_pure_type ################################################################################## @@ -242,326 +241,6 @@ end admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatticeWithIsometry} = admissible_triples(genus(L), p) -################################################################################## -# -# Primitive extensions -# -################################################################################## - -function _get_V(L, q, f, fq, mu, p) - f_mu = mu(f) - if !is_zero(f_mu) - L_sub = intersect(lattice_in_same_ambient_space(L, inv(f_mu)*basis_matrix(L)), dual(L)) - B = basis_matrix(L_sub) - V, Vinq = sub(q, q.([vec(collect(B[i,:])) for i in 1:nrows(B)])) - else - V, Vinq = q, id_hom(q) - end - pV, pVinV = primary_part(V, p) - pV, pVinV = sub(V, pVinV.([divexact(order(g), p)*g for g in gens(pV) if !(order(g)==1)])) - pVinq = compose(pVinV, Vinq) - fpV = _restrict(fq, pVinq) - return pV, pVinq, fpV -end - -function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) - N = relations(q) - if l == 0 - Gl = N - Gm = intersect(1//p*N, dual(N)) - rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) - gene = [q(lift(g)) for g in gens(rholN)] - return sub(q, gene)[2] - end - k = l-1 - m = l+1 - Gk = intersect(1//p^k*N, dual(N)) - Gl = intersect(1//p^l*N, dual(N)) - Gm = intersect(1//p^m*N, dual(N)) - rholN = torsion_quadratic_module(Gl, Gk+p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) - gene = [q(lift(g)) for g in gens(rholN)] - return sub(q, gene) -end - -function _stabilizer(i::TorQuadModMor) - r = domain(i) - S = codomain(i) - OS = orthogonal_group(S) - if is_trivial(r.ab_grp) || is_zero(Hecke.gram_matrix_quadratic(r)) - return sub(OS, gens(OS)) - end - @assert is_injective(i) - if is_bijective(i) - return sub(OS, gens(OS)) - end - ok, j = has_complement(i) - @assert ok - t = domain(j) - rt = direct_sum(r,t)[1] - ok, rttoS = is_isometric_with_isometry(rt, S) - @assert ok - gensr = fmpz_mat[matrix(f) for f in gens(orthogonal_group(r))] - genst = fmpz_mat[matrix(f) for f in gens(orthogonal_group(t))] - R, phi = hom(abelian_group(t), abelian_group(r)) - c = [hom(t, r, f.map) for f in phi.(collect(R))] - filter!(f -> all(a -> all(b -> f(a)*f(b) == a*b, gens(t)), gens(t)), c) - filter!(f -> all(a -> Hecke.quadratic_product(f(a)) == Hecke.quadratic_product(a), gens(t)), c) - c = fmpz_mat[f.map_ab.map for f in c] - gene = fmpz_mat[] - for x in gensr - m = block_diagonal_matrix([identity_matrix(ZZ, ngens(t)), x]) - push!(gene, m) - end - for x in genst - m = block_diagonal_matrix([x, identity_matrix(ZZ, ngens(r))]) - push!(gene, m) - end - for x in c - m = identity_matrix(ZZ, ngens(rt)) - m[(ngens(t)+1):end, 1:ngens(t)] = x - push!(gene, m) - end - gene = TorQuadModMor[hom(rt, rt, m) for m in gene] - gene = fmpz_mat[compose(compose(inv(rttoS), g), rttoS).map_ab.map for g in gene] - return sub(OS, gene) -end - -@doc Markdown.doc""" - primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Int) - -> Vector{LatticeWithIsometry} - -Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a prime number -`p`, such that `(A, B, C)` is `p`-admissible, return a set of representatives of the double coset -$G_B\backslash S\slash/G_A$ where: - - - $G_A$ and $G_B$ are the respective images of the morphisms $O(A, fa) -> O(q_A, \bar{fa})$ - and $O(B, fb) -> O(q_B, \bar{fb})$; - - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry $fc'$ where - $p\cdot C' \subsetea A\perpB$ and the type of $(C', fc'^p)$ is equal to the type of $(C, fc)$. - -See Algorithm 2 of [BH22]. -""" -function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer; check=true) - # requirement for the algorithm of BH22 - @req is_prime(p) "p must be a prime number" - A, B, C = lattice.([Afa, Bfb, Cfc]) - - if check - @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" - @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple" - if ambient_space(A) === ambient_space(B) === ambient_space(C) - G = gram_matrix(ambient_space(C)) - @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattice in same ambient space must be orthogonal" - end - end - - results = LatticeWithIsometry[] - - # this is the glue valuation: it is well-defined because the triple in input is admissible - g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) - - fA, fB = isometry.([Afa, Bfb]) - qA, fqA = discriminant_group(Afa) - qB, fqB = discriminant_group(Bfb) - GA = image_centralizer_in_Oq(Afa) - GB = image_centralizer_in_Oq(Bfb) - - # this is where we will perform the glueing - if ambient_space(Afa) === ambient_space(Bfb) - D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) - else - D, qAinD, qBinD = orthogonal_sum(qA, qB) - end - - OD = orthogonal_group(D) - OqAinOD = embedding_orthogonal_group(qAinD) - OqBinOD = embedding_orthogonal_group(qBinD) - OqA = domain(OqAinOD) - OqB = domain(OqBinOD) - - # if the glue valuation is zero, then we glue along the trivial group and we don't - # have much more to do. Since the triple is p-admissible, A+B = C - if g == 0 - geneA = gens(GA) - geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(a) for a in geneA] - geneB = gens(GB) - geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(b) for b in geneB] - gene = vcat(geneA, geneB) - GC2, _ = sub(OD, gene) - if ambient_space(A) === ambient_space(B) === ambient_space(C) - C2 = A+B - fC2 = block_diagonal_matrix([fA, fB]) - _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) - fC2 = _B*fC2*inv(_B) - @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) - else - C2 = orthogonal_sum(A, B)[1] - fC2 = block_diagonal_matrix([fA, fB]) - end - if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(Cfc)) - C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - set_attribute!(C2fc2, :image_centralizer_in_Oq, GC2) - push!(results, C2fc2) - end - return results - end - - # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map - VA, VAinqA, fVA = _get_V(A, qA, isometry(Afa), fqA, minpoly(Bfb), p) - VB, VBinqB, fVB = _get_V(B, qB, isometry(Bfb), fqB, minpoly(Afa), p) - - # since the glue kernels must have order p^g, in this condition, we have nothing - if min(order(VA), order(VB)) < p^g - return results - end - - # scale of the dual: any glue kernel must contain the multiples of l of the respective - # discriminant groups - l = level(genus(C)) - - # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively - # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) - # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones - # are fA-stable (resp. fB-stable) - subsA = _subgroups_representatives(VAinqA, GA, g, fVA, ZZ(l)) - subsB = _subgroups_representatives(VBinqB, GB, g, fVB, ZZ(l)) - - # once we have the potential kernels, we create pairs of anti-isometric groups since glue - # maps are anti-isometry - R = Tuple{eltype(subsA), eltype(subsB), TorQuadModMor}[] - for H1 in subsA, H2 in subsB - ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) - !ok && continue - push!(R, (H1, H2, phi)) - end - - # now, for each pair of anti-isometric potential kernels, we need to see whether - # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the - # corresponding overlattice and check whether it satisfies the type condition - for (H1, H2, phi) in R - SAinqA, stabA = H1 - SA = domain(SAinqA) - OSAinOqA = embedding_orthogonal_group(SAinqA) - OSA = domain(OSAinOqA) - OSAinOD = compose(OSAinOqA, OqAinOD) - - SBinqB, stabB = H2 - SB = domain(SBinqB) - OSBinOqB = embedding_orthogonal_group(SBinqB) - OSB = domain(OSBinOqB) - OSBinOD = compose(OSBinOqB, OqBinOD) - - # we compute the image of the stabalizers in the respective OS* and we keep track - # of the elements of the stabilizers acting trivially in the respective S* - actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) - imA, _ = image(actA) - kerA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(x) for x in gens(kernel(actA)[1])] - fSA = OSA(_restrict(fqA, SAinqA)) - - actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) - imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(x) for x in gens(kernel(actB)[1])] - fSB = OSB(_restrict(fqB, SBinqB)) - - # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an - # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi - # under the action of O(SB, rho_{l+1}(qB), fB) - rB, rBinqB = _rho_functor(qB, p, valuation(l, p)+1) - @assert Oscar._is_invariant(stabB, rBinqB) - rBinSB = hom(domain(rBinqB), SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) - @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB - - # We compute the generators of O(SB, rho_{l+1}(qB)) - OSBrB, _ = _stabilizer(rBinSB) - @assert fSB in OSBrB - - # 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 glueing. - # If not, we try the next potential pair. - fSAinOSBrB = OSBrB(compose(inv(phi), compose(hom(fSA), phi))) - bool, g0 = representative_action(OSBrB, fSAinOSBrB, OSBrB(fSB)) - bool || continue - phi = compose(phi, hom(OSB(g0))) - fSAinOSBrB = OSB(compose(inv(phi), compose(hom(fSA), phi))) - # Now the new phi is "sending" the restriction of fA to this of fB. - # So we can glue SA and SB. - @assert fSAinOSBrB == fSB - - # 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, OSBrB(fSB)) - center, _ = sub(OSB, [OSB(c) for c in gens(center)]) - stabSAphi = AutomorphismGroupElem{TorQuadMod}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] - stabSAphi, _ = sub(center, stabSAphi) - stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) - reps = double_cosets(center, stabSAphi, stabSB) - - # now we iterate over all double cosets and for each representative, we compute the - # corresponding overlattice in the glueing. If it has the wanted type, we compute - # the image of the centralizer in OD from the stabA and stabB. - for g in reps - g = representative(g) - phig = compose(phi, hom(g)) - S = relations(domain(phig)) - R = relations(codomain(phig)) - - if ambient_space(R) === ambient_space(S) - _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] - z = zero_matrix(QQ, 0, degree(S)) - glue = reduce(vcat, [matrix(QQ, 1, degree(S), g) for g in _glue], init=z) - glue = vcat(basis_matrix(S+R), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = lattice(ambient_space(S), _B[end-rank(S)-rank(R)+1:end, :]) - fC2 = block_diagonal_matrix([fA, fB]) - _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) - fC2 = _B*fC2*inv(_B) - else - _glue = Vector{fmpq}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] - z = zero_matrix(QQ,0,degree(S)+degree(R)) - glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in _glue], init=z) - glue = vcat(block_diagonal_matrix([basis_matrix(S), basis_matrix(R)]), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = lattice(ambient_space(cover(D)), _B[end-rank(S)-rank(R)+1:end, :]) - fC2 = block_diagonal_matrix([fA, fB]) - __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) - fC2 = __B*fC2*inv(__B) - @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) - end - - if !is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation=false), type(Cfc)) - continue - end - - ext, _ = sub(D, D.(_glue)) - 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)) - - qC2 = discriminant_group(C2) - ok, phi2 = is_isometric_with_isometry(qC2, disc) - @assert ok - - C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - im2_phi, _ = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)])) - im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) - stab = Tuple{AutomorphismGroupElem{TorQuadMod}, AutomorphismGroupElem{TorQuadMod}}[(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] - stab = AutomorphismGroupElem{TorQuadMod}[OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] - stab = union(stab, kerA) - stab = union(stab, kerB) - stab = TorQuadModMor[_restrict(g, j) for g in stab] - stab = TorQuadModMor[hom(disc, disc, [disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] - stab = Oscar._orthogonal_group(discriminant_group(C2), [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) - set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) - push!(results, C2fC2) - end - end - - return results -end - ################################################################################## # # Representatives of lattices with isometry diff --git a/src/NumberTheory/LatticesWithIsometry/Misc.jl b/src/NumberTheory/LatticesWithIsometry/Misc.jl deleted file mode 100644 index b70496a0e56f..000000000000 --- a/src/NumberTheory/LatticesWithIsometry/Misc.jl +++ /dev/null @@ -1,176 +0,0 @@ -GG = GAP.Globals - -################################################################################ -# -# Inner orthogonal sum -# -################################################################################ - -function inner_orthogonal_sum(T::TorQuadMod, U::TorQuadMod) - @assert modulus_bilinear_form(T) == modulus_bilinear_form(U) - @assert modulus_quadratic_form(T) == modulus_quadratic_form(U) - @assert ambient_space(cover(T)) === ambient_space(cover(U)) - cS = cover(T)+cover(U) - rS = relations(T)+relations(U) - geneT = [lift(a) for a in gens(T)] - geneU = [lift(a) for a in gens(U)] - S = torsion_quadratic_module(cS, rS, gens = unique([g for g in vcat(geneT, geneU) if !is_zero(g)]), modulus = modulus_bilinear_form(T), modulus_qf = modulus_quadratic_form(T)) - TinS = hom(T, S, S.(geneT)) - UinS = hom(U, S, S.(geneU)) - if order(S) == 1 - S.gram_matrix_quadratic = matrix(QQ,0,0,[]) - S.gram_matrix_bilinear = matrix(QQ,0,0,[]) - set_attribute!(S, :is_degenerate, false) - S.ab_grp = abelian_group() - end - return S, TinS, UinS -end - -function _restrict(f::TorQuadModMor, i::TorQuadModMor) - imgs = TorQuadModElem[] - V = domain(i) - for g in gens(V) - h = f(i(g)) - hV = preimage(i, h) - push!(imgs, hV) - end - return hom(V, V, imgs) -end - -function _restrict(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) - return _restrict(hom(f), i) -end - -function _is_invariant(f::TorQuadModMor, i::TorQuadModMor) - fab = f.map_ab - V = domain(i) - for a in gens(V) - b = f(i(a)) - haspreimage(fab, data(b))[1] || return false - end - return true -end - -function _is_invariant(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) - return _is_invariant(hom(f), i) -end - -function _is_invariant(aut::AutomorphismGroup{TorQuadMod}, i::TorQuadModMor) - return all(g -> _is_invariant(g, i), gens(aut)) -end - -############################################################################## -# -# Group functionalities -# -############################################################################## - -function _as_Fp_vector_space_quotient(HinV, p, f) - i = HinV.map_ab - H = domain(HinV) - Hab = domain(i) - Hs, HstoHab = snf(Hab) - V = codomain(HinV) - Vab = codomain(i) - Vs, VstoVab = snf(Vab) - - function _VtoVs(x::TorQuadModElem) - return inv(VstoVab)(data(x)) - end - - function _VstoV(x::GrpAbFinGenElem) - return V(VstoVab(x)) - end - - VtoVs = Hecke.MapFromFunc(_VtoVs, _VstoV, V, Vs) - - n = ngens(Vs) - F = GF(p) - MVs = matrix(compose(VstoVab, compose(f.map_ab, inv(VstoVab)))) - Vp = VectorSpace(F, n) - - function _VstoVp(x::GrpAbFinGenElem) - v = x.coeff - return Vp(vec(collect(v))) - end - - function _VptoVs(v::ModuleElem{gfp_fmpz_elem}) - x = lift.(v.v) - return Vs(vec(collect(x))) - end - - VstoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) - VtoVp = compose(VtoVs, VstoVp) - gene = gfp_fmpz_mat[matrix(F.((i(HstoHab(a))).coeff)) for a in gens(Hs)] - subgene = [Vp(vec(collect(transpose(v)))) for v in gene] - Hp, _ = sub(Vp, subgene) - Qp, VptoQp = quo(Vp, Hp) - fVp = change_base_ring(F, MVs) - ok, fQp = can_solve_with_solution(VptoQp.matrix, fVp*VptoQp.matrix) - @assert ok - - - return Qp, VtoVp, VptoQp, fQp -end - -function _subgroups_representatives(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, g::Int, f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), l::Hecke.IntegerUnion = 0) - V = domain(Vinq) - q = codomain(Vinq) - p = elementary_divisors(V)[1] - @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" - H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) - Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) - Vp = codomain(VtoVp) - - if dim(Qp) == 0 - if order(V) == p^g - return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(Vinq, G)] - end - return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] - end - - OV = orthogonal_group(V) - gene_GV = elem_type(OV)[OV(_restrict(g, Vinq), check = false) for g in gens(G)] - GV, _ = sub(OV, gene_GV) - GVinG = hom(GV, G, gens(GV), gens(G), check = false) - - act_GV_Vp = gfp_fmpz_mat[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] - act_GV_Qp = gfp_fmpz_mat[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] - MGp = matrix_group(act_GV_Qp) - @assert fQp in MGp - MGptoGV = hom(MGp, GV, gens(GV), check = false) - MGptoG = compose(MGptoGV, GVinG) - - res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] - - if g-ngens(snf(abelian_group(H0))[1]) > dim(Qp) - return res - end - - F = base_ring(Qp) - k, K = kernel(VptoQp.matrix, side = :left) - gene_H0p = ModuleElem{gfp_elem}[Vp(vec(collect(K[i,:]))) for i in 1:k] - orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) - for (orb, stab) in orb_and_stab - i = orb.map - _V = codomain(i) - for v in gens(orb) - vv = _V(i(v)*fQp) - if !can_solve(i.matrix, vv.v, side = :left) - @goto non_fixed - end - end - gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{gfp_fmpz_elem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] - gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{gfp_fmpz_elem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] - gene_submod_in_Vp = vcat(gene_orb_in_Vp, gene_H0p) - gene_submod_in_V = TorQuadModElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] - gene_submod_in_q = TorQuadModElem[image(Vinq, v) for v in gene_submod_in_V] - orbq, orbqinq = sub(q, gene_submod_in_q) - @assert order(orbq) == p^g - stabq, _ = image(MGptoG, stab) - push!(res, (orbqinq, stabq)) - @label non_fixed - end - return res -end - From 70ecc8655bc51efc7298dd6855d4ec1ab55b9f3f Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 3 Feb 2023 15:49:24 +0100 Subject: [PATCH 26/76] fixes --- src/NumberTheory/LWI.jl | 2 +- .../LatticesWithIsometry/Embeddings.jl | 249 +++++++++++------- .../LatticesWithIsometry/Enumeration.jl | 12 +- 3 files changed, 161 insertions(+), 102 deletions(-) diff --git a/src/NumberTheory/LWI.jl b/src/NumberTheory/LWI.jl index 42ee840b537d..bb775f42b07e 100644 --- a/src/NumberTheory/LWI.jl +++ b/src/NumberTheory/LWI.jl @@ -1,5 +1,5 @@ -include("LatticesWithIsometry/Embeddings.jl") include("LatticesWithIsometry/Types.jl") include("LatticesWithIsometry/TraceEquivalence.jl") include("LatticesWithIsometry/LatticesWithIsometry.jl") include("LatticesWithIsometry/Enumeration.jl") +include("LatticesWithIsometry/Embeddings.jl") diff --git a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl index 077433989802..2884a6f806ee 100644 --- a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl +++ b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl @@ -1,5 +1,5 @@ -export primitive_embeddings_in_elementary, - primitive_extensions +export admissible_equivariant_primitive_extensions, + primitive_embeddings_in_primary_lattice GG = GAP.Globals @@ -85,7 +85,7 @@ function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) Gm = intersect(1//p*N, dual(N)) rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) gene = [q(lift(g)) for g in gens(rholN)] - return sub(q, gene)[2] + return sub(q, gene) end k = l-1 m = l+1 @@ -97,6 +97,12 @@ function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) return sub(q, gene) end +############################################################################## +# +# Orbits and stabilizers of discriminant subgroups +# +############################################################################## + function on_subgroup(H::Oscar.GAPGroup, g::AutomorphismGroupElem) G = domain(parent(g)) return sub(G, g.(gens(H)))[1] @@ -111,18 +117,14 @@ function stabilizer(O::AutomorphismGroup{TorQuadMod}, i::TorQuadModMor) OA = automorphism_group(A) OinOA, _ = sub(OA, OA.([g.X for g in gens(O)])) N, _ = sub(A, togap.(i.(gens(domain(i))))) - orb = orbit(OinOA, on_subgroup, N) stab, _ = stabilizer(OinOA, N, on_subgroup) return sub(O, O.([h.X for h in gens(stab)])) end -############################################################################## -# -# Orbits and stabilizers of elementary conjugate subgroups -# -############################################################################## - function _as_Fp_vector_space_quotient(HinV, p, f) + if f isa AutomorphismGroupElem + f = hom(domain(f), domain(f), matrix(f)) + end i = HinV.map_ab H = domain(HinV) Hab = domain(i) @@ -170,17 +172,29 @@ function _as_Fp_vector_space_quotient(HinV, p, f) return Qp, VtoVp, VptoQp, fQp end -function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, g::Int, f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), l::Hecke.IntegerUnion = 0) +function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModMor, + G::AutomorphismGroup{TorQuadMod}, + ord::Hecke.IntegerUnion, + f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), + l::Hecke.IntegerUnion = 0) + if ord == 1 + if l != 0 + return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + end + _, triv = sub(codomain(Vinq), TorQuadModElem[]) + return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(triv, G)] + end V = domain(Vinq) q = codomain(Vinq) p = elementary_divisors(V)[1] + g = valuation(ord, p) @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) Vp = codomain(VtoVp) if dim(Qp) == 0 - if order(V) == p^g + if order(V) == ord return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(Vinq, G)] end return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] @@ -223,7 +237,7 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua gene_submod_in_V = TorQuadModElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] gene_submod_in_q = TorQuadModElem[image(Vinq, v) for v in gene_submod_in_V] orbq, orbqinq = sub(q, gene_submod_in_q) - @assert order(orbq) == p^g + @assert order(orbq) == ord stabq, _ = image(MGptoG, stab) push!(res, (orbqinq, stabq)) @label non_fixed @@ -231,24 +245,23 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua return res end -function subgroups_orbits_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadMod}; order::Hecke.IntegerUnion = -1) +function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadMod}; order::Hecke.IntegerUnion = -1) togap = get_attribute(O, :to_gap) tooscar = get_attribute(O, :to_oscar) q = domain(O) A = abelian_group(q) it = order == -1 ? subgroups(A) : subgroups(A, order=order) - co = [sub(q, q.(j[2].(gens(j[1])))) for j in it] - coAgap = [sub(Agap, togap.(j[1].(gens(j[2]))))[1] for j in co] Agap = codomain(togap) + coAgap = [sub(Agap, togap.(q.(j[2].(gens(j[1])))))[1] for j in it] OAgap = automorphism_group(Agap) OinOAgap, j = sub(OAgap, OAgap.([g.X for g in gens(O)])) - m = gset(OinOAgap, on_sub, coAgap) + m = gset(OinOAgap, on_subgroup, coAgap) orbs = orbits(m) res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] for orb in orbs rep = representative(orb) stab, _ = stabilizer(OinOAgap, rep, on_subgroup) - rep = sub(q, TorQuadModElem[tooscar(Agap(g)) for g in gens(rep)]) + _, rep = sub(q, TorQuadModElem[tooscar(Agap(g)) for g in gens(rep)]) stab, _ = sub(O, O.([h.X for h in gens(stab)])) push!(res, (rep, stab)) end @@ -256,7 +269,7 @@ function subgroups_orbits_representatives_and_stabilizers(O::AutomorphismGroup{T end function classes_conjugate_subgroups(O::AutomorphismGroup{TorQuadMod}, q::TorQuadMod) - sors = subgroups_orbits_representatives_and_stabilizers(O, order=order(q)) + sors = subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) end @@ -266,24 +279,29 @@ end # ################################################################################# -function _isomorphism_classes_primitive_embeddings(N::Zlat, M::ZLat, H::TorQuadMod) +# here for convenience, we choose in entry N and M to be of full rank and +# with basis matrix equal to the identity matrix + +function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadMod) + @assert is_one(basis_matrix(N)) + @assert is_one(basis_matrix(M)) results = Tuple{ZLat, ZLat, ZLat}[] GN, GNinqN = image_in_Oq(N) GM, GMinqM = image_in_Oq(M) - qN = codomain(GNinqN) - qM = codomain(GMinqM) + qN = domain(GN) + qM = domain(GM) D, qNinD, qMinD = orthogonal_sum(qN, qM) OD = orthogonal_group(D) - OqNinOD = embedding_orthogonal_group(qNinOD) - OqMinOD = embedding_orthogonal_group(qMinOD) + OqNinOD = embedding_orthogonal_group(qNinD) + OqMinOD = embedding_orthogonal_group(qMinD) OqN = domain(OqNinOD) OqM = domain(OqMinOD) subsN = classes_conjugate_subgroups(GN, rescale(H, -1)) - @assert !isempty(N) + @assert !isempty(subsN) subsM = classes_conjugate_subgroups(GM, H) - @assert !isempty(N) + @assert !isempty(subsM) for H1 in subsN, H2 in subsM ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) @@ -291,112 +309,154 @@ function _isomorphism_classes_primitive_embeddings(N::Zlat, M::ZLat, H::TorQuadM HNinqN, stabN = H1 HN = domain(HNinqN) - OHNinOqN = embedding_orthogonal_group(HNinqN) - OHN = domain(OHNinOqN) - OHNinOD = compose(OHNinOqN, OqNinOD) + OHN = orthogonal_group(HN) HMinqM, stabM = H2 HM = domain(HMinqM) - OHMinOqM = embedding_orthogonal_group(HMinqM) - OHM = domain(OHMinOqM) - OHMinOD = compose(OHMinOqM, OqMinOD) + OHM = orthogonal_group(HM) actN = hom(stabN, OHN, [OHN(_restrict(x, HNinqN)) for x in gens(stabN)]) imN, _ = image(actN) - kerN = AutomorphismGroupElem{TorQuadMod}[OqNinOD(x) for in gens(kernel(actN)[1])] + kerN = AutomorphismGroupElem{TorQuadMod}[OqNinOD(x) for x in gens(kernel(actN)[1])] actM = hom(stabM, OHM, [OHM(_restrict(x, HMinqM)) for x in gens(stabM)]) imM, _ = image(actM) kerM = AutomorphismGroupElem{TorQuadMod}[OqMinOD(x) for x in gens(kernel(actM)[1])] - stabNphi = AutomorphismGroupElem{TorQuadMod}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabA)] + stabNphi = AutomorphismGroupElem{TorQuadMod}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) - + @info "$(length(reps)) isomorphism classe(s) of primitive extensions" for g in reps g = representative(g) phig = compose(phi, hom(g)) - S = relations(domain(phig)) - R = relations(codomain(phig)) _glue = Vector{fmpq}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(N)+degree(M)) glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) - glue = vcat(block_diagonal_matrix([basis_matrix(N), basis_matrix(M)]), glue) + glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) glue = FakeFmpqMat(glue) _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) - N2 = lattice_in_same_ambient_space(L, _B[1:rank(N),:]) + N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) @assert genus(N) == genus(N2) - M2 = orthgonal_submodule(L, N2) + M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) @assert genus(M) == genus(M2) push!(results, (L, M2, N2)) + @info "Gluing done" + GC.gc() end end return results end -function primitive_embeddings_in_elementary(L::ZLat, M::ZLat) - bool, p = is_elementary_with_prime(L) - @req bool "L must be unimodular or elementary" +@doc Markdown.doc""" + primitive_embeddings_in_elementary_lattice(L::ZLat, M::ZLat) + -> Vector{Tuple{ZLat, ZLat, ZLat}} +Given a `p`-primary lattice `L`, unique in its genus, and a lattice `M`, +compute representatives for all isomorphism classes of primitive embeddings +of `M` in `L` up to the actions of $\bar{O}(M)$ and $O(L)$. Here +$\bar{O}(M)$ denotes the image of $O(M)\to O(q_M)$. + +The output is given in terms of triples `(L', M', N')` where `L'` is +isometric to `L`, `M'` is a sublattice of `L'` isometric to `M` and +`N'` is the orthogonal complement of `M'` in `L'`. +""" +function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; check = false) + bool, p = is_primary_with_prime(L) + @req bool "L must be unimodular or elementary" + el = is_elementary(L, p) + if check + @req length(genus_representatives(L)) == 1 "L must be unique in its genus" + end + M = Zlattice(gram = gram_matrix(M)) results = Tuple{ZLat, ZLat, ZLat}[] qL = discriminant_group(L) OqL = orthogonal_group(qL) - pL, nL = signature_pair(L) - pM, nM = signature_pair(M) + pL, _, nL = signature_tuple(L) + pM, _, nM = signature_tuple(M) @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" + if ((pL, nL) == (pM, nM)) + genus(M) != genus(L) && return results + return [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] + end qM = discriminant_group(M) - VM, VMinqM, _ = _get_V(M, qM, identity_matrix(QQ, rank(M)), id_hom(qM), minpoly(identity_matrix(QQ,1)), p) - g = 0 - while p^g <= min(order(qL), order(VM)) - subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), OqL, g) + if el + VM, VMinqM, _ = _get_V(M, qM, identity_matrix(QQ, rank(M)), id_hom(qM), minpoly(identity_matrix(QQ,1)), p) + else + VM, VMinqM = primary_part(qM, p) + end + for k in divisors(order(qL)) if k <= order(VM) + @info "Glue order: $(k)" + if el + subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), OqL, k) + else + subsL = subgroups_orbit_representatives_and_stabilizers(OqL, order = k) + end + @info "$(length(subsL)) subgroup(s)" for H in subsL HL = domain(H[1]) it = subgroups(abelian_group(VM), order = order(HL)) - subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1]))))) for j in it] - filter!(HM -> is_isometric_with_isometry(domain(HM), HL), subsM) - isempty(subsM) && continue - ok, j = has_complement(subsM[1]) - @assert ok - qM2 = domain(j) - qL2 = codomain(has_complement(H)[2]) - D = orthogonal_sum(rescale(qM2, -1), qL2) - !is_genus(D, (pL-pM, nL-nM)) && continue + subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1])))))[2] for j in it] + filter!(HM -> is_isometric_with_isometry(domain(HM), HL)[1], subsM) + isempty(subsM) && continue + @info "Possible gluing" + qM2, _ = orthogonal_submodule(qM, domain(subsM[1])) + qL2, _ = orthogonal_submodule(qL, domain(H[1])) + D = direct_sum(rescale(qM2, -1), qL2)[1] + !is_genus(D, (pL-pM, nL-nM)) && continue G = genus(D, (pL-pM, nL-nM)) - Ns = representatives(g) + @info "We can glue: $G" + Ns = representatives(G) + @info "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) + Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] for N in Ns - append!(results, [_isomorphism_classes_primitive_embeddings(N, M, qM2)]) + append!(results, _isomorphism_classes_primitive_extensions(N, M, qM2)) + GC.gc() end + GC.gc() end - g += 1 end + end + @assert all(triple -> genus(triple[1]) == genus(L), results) return results end #################################################################################### # -# Admissible primitive extensions +# Admissible equivariant primitive extensions # #################################################################################### @doc Markdown.doc""" - primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Int) - -> Vector{LatticeWithIsometry} - -Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a prime number -`p`, such that `(A, B, C)` is `p`-admissible, return a set of representatives of the double coset -$G_B\backslash S\slash/G_A$ where: - - - $G_A$ and $G_B$ are the respective images of the morphisms $O(A, fa) -> O(q_A, \bar{fa})$ - and $O(B, fb) -> O(q_B, \bar{fb})$; - - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry $fc'$ where - $p\cdot C' \subsetea A\perpB$ and the type of $(C', fc'^p)$ is equal to the type of $(C, fc)$. + admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, + Bfb::LatticeWithIsometry, + Cfc::LatticeWithIsometry, + p::Int; check=true) + -> Vector{LatticeWithIsometry} + +Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a +prime number `p`, such that `(A, B, C)` is `p`-admissible, return a set of +representatives of the double coset $G_B\backslash S\slash/G_A$ where: + + - $G_A$ and $G_B$ are the respective images of the morphisms + $O(A, fa) -> O(q_A, \bar{fa})$ and $O(B, fb) -> O(q_B, \bar{fb})$; + - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry + $fc'$ where $p\cdot C' \subsetea A\perpB$ and the type of $(C', fc'^p)$ is equal + to the type of $(C, fc)$. + +If `check == true` the input triple is checked to a `p`-admissible triple of +integral lattices (with isometry), imposing that `A` and `B` are orthogonal +if `A`, `B` and `C` lie in the same ambient quadratic space. See Algorithm 2 of [BH22]. """ -function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, Cfc::LatticeWithIsometry, p::Integer; check=true) +function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, + Bfb::LatticeWithIsometry, + Cfc::LatticeWithIsometry, + p::Integer; check=true) # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" A, B, C = lattice.([Afa, Bfb, Cfc]) @@ -437,10 +497,8 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # if the glue valuation is zero, then we glue along the trivial group and we don't # have much more to do. Since the triple is p-admissible, A+B = C if g == 0 - geneA = gens(GA) - geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(a) for a in geneA] - geneB = gens(GB) - geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(b) for b in geneB] + geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(a) for a in gens(GA)] + geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(b) for b in gens(GB)] gene = vcat(geneA, geneB) GC2, _ = sub(OD, gene) if ambient_space(A) === ambient_space(B) === ambient_space(C) @@ -450,7 +508,7 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry fC2 = _B*fC2*inv(_B) @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) else - C2 = orthogonal_sum(A, B)[1] + C2 = direct_sum(A, B)[1] fC2 = block_diagonal_matrix([fA, fB]) end if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(Cfc)) @@ -478,8 +536,8 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) - subsA = _subgroups_representatives(VAinqA, GA, g, fVA, ZZ(l)) - subsB = _subgroups_representatives(VBinqB, GB, g, fVB, ZZ(l)) + subsA = subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fVA, ZZ(l)) + subsB = subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry @@ -523,11 +581,11 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry # under the action of O(SB, rho_{l+1}(qB), fB) rB, rBinqB = _rho_functor(qB, p, valuation(l, p)+1) @assert Oscar._is_invariant(stabB, rBinqB) - rBinSB = hom(domain(rBinqB), SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(domain(rBinqB))]) - @assert is_trivial(domain(rBinSB).ab_grp) || is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB + rBinSB = hom(rB, SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(rB)]) + @assert is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB # We compute the generators of O(SB, rho_{l+1}(qB)) - OSBrB, _ = _stabilizer(rBinSB) + OSBrB, _ = stabilizer(OSB, rBinSB) @assert fSB in OSBrB # phi might not "send" the restriction of fA to this of fB, but at least phi*fA*phi^-1 @@ -558,30 +616,28 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry for g in reps g = representative(g) phig = compose(phi, hom(g)) - S = relations(domain(phig)) - R = relations(codomain(phig)) - if ambient_space(R) === ambient_space(S) + if ambient_space(A) === ambient_space(B) _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] - z = zero_matrix(QQ, 0, degree(S)) - glue = reduce(vcat, [matrix(QQ, 1, degree(S), g) for g in _glue], init=z) - glue = vcat(basis_matrix(S+R), glue) + z = zero_matrix(QQ, 0, degree(A)) + glue = reduce(vcat, [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)) - C2 = lattice(ambient_space(S), _B[end-rank(S)-rank(R)+1:end, :]) + C2 = lattice(ambient_space(S), _B[end-rank(A)-rank(B)+1:end, :]) fC2 = block_diagonal_matrix([fA, fB]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) else _glue = Vector{fmpq}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] - z = zero_matrix(QQ,0,degree(S)+degree(R)) - glue = reduce(vcat, [matrix(QQ,1,degree(S)+degree(R),g) for g in _glue], init=z) - glue = vcat(block_diagonal_matrix([basis_matrix(S), basis_matrix(R)]), glue) + z = zero_matrix(QQ,0,degree(A)+degree(B)) + glue = reduce(vcat, [matrix(QQ,1,degree(A)+degree(B),g) for g in _glue], init=z) + glue = vcat(block_diagonal_matrix([basis_matrix(A), basis_matrix(B)]), glue) glue = FakeFmpqMat(glue) _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = lattice(ambient_space(cover(D)), _B[end-rank(S)-rank(R)+1:end, :]) + C2 = lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) fC2 = block_diagonal_matrix([fA, fB]) __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) fC2 = __B*fC2*inv(__B) @@ -594,7 +650,8 @@ function primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry ext, _ = sub(D, D.(_glue)) 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 = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), + modulus_qf = modulus_quadratic_form(perp)) qC2 = discriminant_group(C2) ok, phi2 = is_isometric_with_isometry(qC2, disc) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index e73490b379b4..91ed39401b4f 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -212,7 +212,9 @@ end Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and -`B` is of rank divisible by $p-1$. See Algorithm 1 [BH22]. +`B` is of rank divisible by $p-1$. + +See Algorithm 1 of [BH22]. """ function admissible_triples(G::ZGenus, p::Int64) @req is_prime(p) "p must be a prime number" @@ -535,7 +537,7 @@ function prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::In LA = lattice_with_isometry(representative(A)) RA = representatives_of_pure_type(LA, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) - E = primitive_extensions(L1, L2, Lf, p) + E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p, check=false) append!(reps, E) end end @@ -605,7 +607,7 @@ function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int B = prime_splitting_of_prime_power(B0, p) for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue - E = primitive_extensions(L1, L2, Lf, q) + E = admissible_equivariant_primitive_extensions(L1, L2, Lf, q, check=false) @assert b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) end @@ -668,7 +670,7 @@ function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) is_empty(A) && return reps B = prime_splitting_of_semi_pure_type(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) - E = extensions(LA, LB, Lf, q) + E = admissible_equivariant_primitive_extensions(LA, LB, Lf, q, check = false) append!(reps, E) end return reps @@ -719,7 +721,7 @@ function prime_splitting(Lf::LatticeWithIsometry, p::Int) isempty(A) && return reps B = prime_splitting(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) - E = extensions(LA, LB, Lf, p) + E = admissible_equivariant_primitive_extensions(LA, LB, Lf, p, check = false) @assert all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) append!(reps, E) end From 7c62b25e5ee0f0cb5890a6472be9a78c5e889746 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 3 Feb 2023 19:33:45 +0100 Subject: [PATCH 27/76] extra fix --- src/Groups/spinor_norms.jl | 2 +- .../LatticesWithIsometry/Embeddings.jl | 80 ++++++++++++++++--- .../LatticesWithIsometry/Enumeration.jl | 10 ++- .../LatticesWithIsometry.jl | 8 +- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/Groups/spinor_norms.jl b/src/Groups/spinor_norms.jl index 2617aace9652..d6289fb4694a 100644 --- a/src/Groups/spinor_norms.jl +++ b/src/Groups/spinor_norms.jl @@ -480,7 +480,7 @@ julia> order(Oq) # we can compute the orthogonal group of L Oq = orthogonal_group(discriminant_group(L)) G = orthogonal_group(L) - return sub(Oq, [Oq(g, check=false) for g in gens(G)]) + return sub(Oq, unique([Oq(g, check=false) for g in gens(G)])) end @attr function image_in_Oq_signed(L::ZLat)::Tuple{AutomorphismGroup{Hecke.TorQuadMod}, GAPGroupHomomorphism{AutomorphismGroup{Hecke.TorQuadMod}, AutomorphismGroup{Hecke.TorQuadMod}}} diff --git a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl index 2884a6f806ee..25e5cfea11cc 100644 --- a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl +++ b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl @@ -29,6 +29,62 @@ function inner_orthogonal_sum(T::TorQuadMod, U::TorQuadMod) return S, TinS, UinS end +function embedding_orthogonal_group(i::TorQuadModMor, j::TorQuadModMor) + A = domain(i) + B = domain(j) + D = codomain(i) + Dorth = direct_sum(A, B)[1] + ok, phi = is_isometric_with_isometry(Dorth, D) + @assert ok + OD = orthogonal_group(D) + OA = orthogonal_group(A) + OB = orthogonal_group(B) + + geneOAinDorth = TorQuadModMor[] + for f in gens(OA) + m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) + m = hom(Dorth, Dorth, m) + push!(geneOAinDorth, m) + end + + geneOBinDorth = TorQuadModMor[] + for f in gens(OB) + m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) + m = hom(Dorth, Dorth, m) + push!(geneOBinDorth, m) + end + geneOAinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] + OAtoOD = hom(OA, OD, geneOAinOD, check = false) + geneOBinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOBinDorth] + OBtoOD = hom(OB, OD, geneOBinOD, check = false) + return OAtoOD, OBtoOD +end + +function _embedding_orthogonal_group(i::TorQuadModMor) + @req is_injective(i) "i must be injective" + A = domain(i) + D = codomain(i) + OA = orthogonal_group(A) + OD = orthogonal_group(D) + if order(OA) == 1 + return hom(OA, OD, [one(OD)], check = false) + end + B = orthogonal_submodule(D, A)[1] + Dorth = inner_orthogonal_sum(A, B)[1] + ok, phi = is_isometric_with_isometry(Dorth, D) + @assert ok + + geneOAinDorth = TorQuadModMor[] + for f in gens(OA) + m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) + m = hom(Dorth, Dorth, m) + push!(geneOAinDorth, m) + end + geneOAinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] + OAtoOD = hom(OA, OD, geneOAinOD, check = false) + return OAtoOD::GAPGroupHomomorphism{AutomorphismGroup{TorQuadMod}, AutomorphismGroup{TorQuadMod}} +end + function _restrict(f::TorQuadModMor, i::TorQuadModMor) imgs = TorQuadModElem[] V = domain(i) @@ -483,22 +539,25 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, # this is where we will perform the glueing if ambient_space(Afa) === ambient_space(Bfb) - D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) + D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) + OD = orthogonal_group(D) + OqAinOD = embedding_orthogonal_group(qAinD) + OqBinOD = embedding_orthogonal_group(qBinD) else - D, qAinD, qBinD = orthogonal_sum(qA, qB) + D, qAinD, qBinD = orthogonal_sum(qA, qB) + OD = orthogonal_group(D) + OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) end - OD = orthogonal_group(D) - OqAinOD = embedding_orthogonal_group(qAinD) - OqBinOD = embedding_orthogonal_group(qBinD) + println(qBinD) OqA = domain(OqAinOD) OqB = domain(OqBinOD) # if the glue valuation is zero, then we glue along the trivial group and we don't # have much more to do. Since the triple is p-admissible, A+B = C if g == 0 - geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(a) for a in gens(GA)] - geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(b) for b in gens(GB)] + geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(OqA(a.X)) for a in gens(GA)] + geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(OqB(b.X)) for b in gens(GB)] gene = vcat(geneA, geneB) GC2, _ = sub(OD, gene) if ambient_space(A) === ambient_space(B) === ambient_space(C) @@ -531,14 +590,15 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, # scale of the dual: any glue kernel must contain the multiples of l of the respective # discriminant groups l = level(genus(C)) + # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) + subsA = subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fVA, ZZ(l)) subsB = subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) - # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry R = Tuple{eltype(subsA), eltype(subsB), TorQuadModMor}[] @@ -554,13 +614,13 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, for (H1, H2, phi) in R SAinqA, stabA = H1 SA = domain(SAinqA) - OSAinOqA = embedding_orthogonal_group(SAinqA) + OSAinOqA = _embedding_orthogonal_group(SAinqA) OSA = domain(OSAinOqA) OSAinOD = compose(OSAinOqA, OqAinOD) SBinqB, stabB = H2 SB = domain(SBinqB) - OSBinOqB = embedding_orthogonal_group(SBinqB) + OSBinOqB = _embedding_orthogonal_group(SBinqB) OSB = domain(OSBinOqB) OSBinOD = compose(OSBinOqB, OqBinOD) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index 91ed39401b4f..f7529168f86a 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -363,7 +363,7 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) f = (-1)^(n*m+1)*identity_matrix(QQ, rk) G = genus(Lf) repre = representatives(G) - @info "$(length(repre)) representatives" + @info "$(length(repre)) representative(s)" for LL in repre is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) end @@ -528,16 +528,20 @@ function prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::In @req p != q "Prime numbers must be distinct" reps = LatticeWithIsometry[] - + @info "Compute admissible triples" atp = admissible_triples(Lf, p) + @info "$(atp) admissible triple(s)" for (A, B) in atp LB = lattice_with_isometry(representative(B)) RB = representatives_of_pure_type(LB, p*q^d) - isempty(RB) && continue + if is_empty(RB) + continue + end LA = lattice_with_isometry(representative(A)) RA = representatives_of_pure_type(LA, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p, check=false) + GC.gc() append!(reps, E) end end diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 2cef956b8eb7..e12d90a8076a 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -266,16 +266,18 @@ $q_L$ induced by `f`. UL = fmpq_mat[matrix(OL(s)) for s in gens(centralizer(OL, f)[1])] qL = discriminant_group(L) UL = fmpz_mat[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in UL] - GL = Oscar._orthogonal_group(qL, UL) + unique!(UL) + GL = Oscar._orthogonal_group(qL, UL, check = false) elseif rank(L) == euler_phi(n) qL = discriminant_group(L) UL = fmpz_mat[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in [-f^0, f]] - GL = Oscar._orthogonal_group(qL, UL) + unique!(UL) + GL = Oscar._orthogonal_group(qL, UL, check = false) else qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) CdL, _ = centralizer(OqL, fqL) - GL, _ = sub(OqL, [OqL(s.X) for s in CdL]) + GL, _ = sub(OqL, unique([OqL(s.X) for s in CdL])) end return GL::AutomorphismGroup{TorQuadMod} end From aa682789f1f6b69415ca9a6d2fab76ecc1211269 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 10 Feb 2023 10:03:49 +0100 Subject: [PATCH 28/76] some changes and starting of hermitian miranda-morrison --- src/NumberTheory/LWI.jl | 2 + .../LatticesWithIsometry/Embeddings.jl | 112 +++++++-------- .../LatticesWithIsometry/Enumeration.jl | 131 +++++++++-------- src/NumberTheory/LatticesWithIsometry/HMM.jl | 133 ++++++++++++++++++ .../LatticesWithIsometry.jl | 93 ++++++++---- .../LatticesWithIsometry/TraceEquivalence.jl | 46 +++++- 6 files changed, 360 insertions(+), 157 deletions(-) create mode 100644 src/NumberTheory/LatticesWithIsometry/HMM.jl diff --git a/src/NumberTheory/LWI.jl b/src/NumberTheory/LWI.jl index bb775f42b07e..cb3a157fe0d5 100644 --- a/src/NumberTheory/LWI.jl +++ b/src/NumberTheory/LWI.jl @@ -1,5 +1,7 @@ include("LatticesWithIsometry/Types.jl") +include("LatticesWithIsometry/HMM.jl") include("LatticesWithIsometry/TraceEquivalence.jl") include("LatticesWithIsometry/LatticesWithIsometry.jl") include("LatticesWithIsometry/Enumeration.jl") include("LatticesWithIsometry/Embeddings.jl") + diff --git a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl index 25e5cfea11cc..ddefc06f00c3 100644 --- a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl +++ b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl @@ -342,17 +342,13 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM @assert is_one(basis_matrix(N)) @assert is_one(basis_matrix(M)) results = Tuple{ZLat, ZLat, ZLat}[] - GN, GNinqN = image_in_Oq(N) - GM, GMinqM = image_in_Oq(M) + GN, _ = image_in_Oq(N) + GM, _ = image_in_Oq(M) qN = domain(GN) qM = domain(GM) D, qNinD, qMinD = orthogonal_sum(qN, qM) OD = orthogonal_group(D) - OqNinOD = embedding_orthogonal_group(qNinD) - OqMinOD = embedding_orthogonal_group(qMinD) - OqN = domain(OqNinOD) - OqM = domain(OqMinOD) subsN = classes_conjugate_subgroups(GN, rescale(H, -1)) @assert !isempty(subsN) @@ -364,20 +360,15 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM @assert ok HNinqN, stabN = H1 - HN = domain(HNinqN) OHN = orthogonal_group(HN) HMinqM, stabM = H2 - HM = domain(HMinqM) OHM = orthogonal_group(HM) actN = hom(stabN, OHN, [OHN(_restrict(x, HNinqN)) for x in gens(stabN)]) - imN, _ = image(actN) - kerN = AutomorphismGroupElem{TorQuadMod}[OqNinOD(x) for x in gens(kernel(actN)[1])] actM = hom(stabM, OHM, [OHM(_restrict(x, HMinqM)) for x in gens(stabM)]) imM, _ = image(actM) - kerM = AutomorphismGroupElem{TorQuadMod}[OqMinOD(x) for x in gens(kernel(actM)[1])] stabNphi = AutomorphismGroupElem{TorQuadMod}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) @@ -407,7 +398,7 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM end @doc Markdown.doc""" - primitive_embeddings_in_elementary_lattice(L::ZLat, M::ZLat) + primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat) -> Vector{Tuple{ZLat, ZLat, ZLat}} Given a `p`-primary lattice `L`, unique in its genus, and a lattice `M`, @@ -419,7 +410,7 @@ The output is given in terms of triples `(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice of `L'` isometric to `M` and `N'` is the orthogonal complement of `M'` in `L'`. """ -function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; check = false) +function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadMod} = orthogonal_group(discriminant_group(L)); check::Bool = false) bool, p = is_primary_with_prime(L) @req bool "L must be unimodular or elementary" el = is_elementary(L, p) @@ -428,8 +419,8 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; check = false end M = Zlattice(gram = gram_matrix(M)) results = Tuple{ZLat, ZLat, ZLat}[] - qL = discriminant_group(L) - OqL = orthogonal_group(qL) + qL = rescale(discriminant_group(L), -1) + GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) pL, _, nL = signature_tuple(L) pM, _, nM = signature_tuple(M) @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" @@ -438,36 +429,46 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; check = false return [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] end qM = discriminant_group(M) + D, (qMinD, qLinD), proj = Hecke._orthogonal_sum_with_injections_and_projections([qM, qL]) if el VM, VMinqM, _ = _get_V(M, qM, identity_matrix(QQ, rank(M)), id_hom(qM), minpoly(identity_matrix(QQ,1)), p) else VM, VMinqM = primary_part(qM, p) end - for k in divisors(order(qL)) if k <= order(VM) + for k in divisors(gcd(order(VM), order(qL))) @info "Glue order: $(k)" if el - subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), OqL, k) + subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(GL), GL, k) else - subsL = subgroups_orbit_representatives_and_stabilizers(OqL, order = k) + subsL = subgroups_orbit_representatives_and_stabilizers(GL, order = k) end @info "$(length(subsL)) subgroup(s)" for H in subsL HL = domain(H[1]) it = subgroups(abelian_group(VM), order = order(HL)) subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1])))))[2] for j in it] - filter!(HM -> is_isometric_with_isometry(domain(HM), HL)[1], subsM) + filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue @info "Possible gluing" - qM2, _ = orthogonal_submodule(qM, domain(subsM[1])) - qL2, _ = orthogonal_submodule(qL, domain(H[1])) - D = direct_sum(rescale(qM2, -1), qL2)[1] - !is_genus(D, (pL-pM, nL-nM)) && continue - G = genus(D, (pL-pM, nL-nM)) + HM = subsM[1] + ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) + @assert ok + _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] + ext, _ = sub(D, D.(_glue)) + 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 = rescale(disc, -1) + !is_genus(disc, (pL-pM, nL-nM)) && continue + G = genus(disc, (pL-pM, nL-nM)) @info "We can glue: $G" Ns = representatives(G) @info "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] + ext2, _ = sub(D, [qMinD(HM(g)) for g in gens(domain(HM))]) + perp2, j = orthogonal_submodule(D, ext2) + qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp2)]) for N in Ns append!(results, _isomorphism_classes_primitive_extensions(N, M, qM2)) GC.gc() @@ -475,7 +476,6 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; check = false GC.gc() end end - end @assert all(triple -> genus(triple[1]) == genus(L), results) return results end @@ -500,25 +500,30 @@ representatives of the double coset $G_B\backslash S\slash/G_A$ where: - $G_A$ and $G_B$ are the respective images of the morphisms $O(A, fa) -> O(q_A, \bar{fa})$ and $O(B, fb) -> O(q_B, \bar{fb})$; - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry - $fc'$ where $p\cdot C' \subsetea A\perpB$ and the type of $(C', fc'^p)$ is equal + $fc'$ where $p\cdot C' \subseteq A\perpB$ and the type of $(C', fc'^p)$ is equal to the type of $(C, fc)$. If `check == true` the input triple is checked to a `p`-admissible triple of -integral lattices (with isometry), imposing that `A` and `B` are orthogonal +integral lattices (with isometry) with `fA` and `fB` having relatively coprime +irreducible minimal polynomials and imposing that `A` and `B` are orthogonal if `A`, `B` and `C` lie in the same ambient quadratic space. +Note that `Afa` and `Bfb` must be of pure type, i.e. the minimal polynomials +of the associated isometries must be irreducible (and relatively coprime). + See Algorithm 2 of [BH22]. """ -function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, - Bfb::LatticeWithIsometry, - Cfc::LatticeWithIsometry, +function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, + B::LatticeWithIsometry, + C::LatticeWithIsometry, p::Integer; check=true) # requirement for the algorithm of BH22 - @req is_prime(p) "p must be a prime number" - A, B, C = lattice.([Afa, Bfb, Cfc]) + @req is_prime(p) "p must be a prime number" if check @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" + @req is_of_hermitian_type(A) && is_of_hermitian_type(B) "Afa and Bfb must be of hermitian type" + @req gcd(minpoly(A), minpoly(B)) == 1 "Minimal irreducible polynomials must be relatively coprime" @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple" if ambient_space(A) === ambient_space(B) === ambient_space(C) G = gram_matrix(ambient_space(C)) @@ -531,14 +536,13 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, # this is the glue valuation: it is well-defined because the triple in input is admissible g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) - fA, fB = isometry.([Afa, Bfb]) - qA, fqA = discriminant_group(Afa) - qB, fqB = discriminant_group(Bfb) - GA = image_centralizer_in_Oq(Afa) - GB = image_centralizer_in_Oq(Bfb) + qA, fqA = discriminant_group(A) + qB, fqB = discriminant_group(B) + GA = image_centralizer_in_Oq(A) + GB = image_centralizer_in_Oq(B) # this is where we will perform the glueing - if ambient_space(Afa) === ambient_space(Bfb) + if ambient_space(A) === ambient_space(B) D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) OD = orthogonal_group(D) OqAinOD = embedding_orthogonal_group(qAinD) @@ -549,7 +553,6 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) end - println(qBinD) OqA = domain(OqAinOD) OqB = domain(OqBinOD) @@ -561,16 +564,16 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, gene = vcat(geneA, geneB) GC2, _ = sub(OD, gene) if ambient_space(A) === ambient_space(B) === ambient_space(C) - C2 = A+B - fC2 = block_diagonal_matrix([fA, fB]) + C2 = lattice(A)+lattice(B) + fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) else - C2 = direct_sum(A, B)[1] - fC2 = block_diagonal_matrix([fA, fB]) + C2 = direct_sum(lattice(A), lattice(B))[1] + fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) end - if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(Cfc)) + if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(C)) C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) set_attribute!(C2fc2, :image_centralizer_in_Oq, GC2) push!(results, C2fc2) @@ -579,8 +582,8 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, end # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map - VA, VAinqA, fVA = _get_V(A, qA, isometry(Afa), fqA, minpoly(Bfb), p) - VB, VBinqB, fVB = _get_V(B, qB, isometry(Bfb), fqB, minpoly(Afa), p) + VA, VAinqA, fVA = _get_V(lattice(A), qA, isometry(A), fqA, minpoly(B), p) + VB, VBinqB, fVB = _get_V(lattice(B), qB, isometry(B), fqB, minpoly(A), p) # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g @@ -613,7 +616,6 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, # corresponding overlattice and check whether it satisfies the type condition for (H1, H2, phi) in R SAinqA, stabA = H1 - SA = domain(SAinqA) OSAinOqA = _embedding_orthogonal_group(SAinqA) OSA = domain(OSAinOqA) OSAinOD = compose(OSAinOqA, OqAinOD) @@ -681,12 +683,12 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(A)) glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) - glue = vcat(basis_matrix(A+B), glue) + glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) glue = FakeFmpqMat(glue) _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = lattice(ambient_space(S), _B[end-rank(A)-rank(B)+1:end, :]) - fC2 = block_diagonal_matrix([fA, fB]) + C2 = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) + fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) else @@ -698,13 +700,13 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) C2 = lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) - fC2 = block_diagonal_matrix([fA, fB]) + fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) fC2 = __B*fC2*inv(__B) @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end - if !is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation=false), type(Cfc)) + if !is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation=false), type(C)) continue end @@ -717,7 +719,7 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, ok, phi2 = is_isometric_with_isometry(qC2, disc) @assert ok - C2fC2 = lattice_with_isometry(C2, fC2, ambient_representation=false) + C2 = lattice_with_isometry(C2, fC2, ambient_representation=false) im2_phi, _ = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)])) im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) stab = Tuple{AutomorphismGroupElem{TorQuadMod}, AutomorphismGroupElem{TorQuadMod}}[(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] @@ -727,8 +729,8 @@ function admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, stab = TorQuadModMor[_restrict(g, j) for g in stab] stab = TorQuadModMor[hom(disc, disc, [disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] stab = Oscar._orthogonal_group(discriminant_group(C2), [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) - set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) - push!(results, C2fC2) + set_attribute!(C2, :image_centralizer_in_Oq, stab) + push!(results, C2) end end diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index f7529168f86a..b7879ada4109 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -1,10 +1,10 @@ export admissible_triples, is_admissible_triple, - prime_splitting, - prime_splitting_of_prime_power, - prime_splitting_of_pure_type_prime_power, - prime_splitting_of_semi_pure_type, - representatives_of_pure_type + splitting_of_hermitian_prime_power, + splitting_of_mixed_prime_power, + splitting_of_partial_mixed_prime_power, + splitting_of_prime_power, + representatives_of_hermitian_type ################################################################################## # @@ -332,29 +332,27 @@ function _possible_signatures(s1, s2, E, rk) end @doc Markdown.doc""" - representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) + representatives_of_hermitian_type(Lf::LatticeWithIsometry, m::Int = 1) -> Vector{LatticeWithIsometry} -Given a lattice with isometry $(L, f)$ of pure type (i.e. the minimal -polynomial of `f` is irreducible cyclotomic), and a positive integer `m` (set to -1 by default), return a set of representatives of isomorphism classes of lattices -with isometry of pure type $(M, g)$ and such that the type of $(B, g^m)$ is equal -to the type of $(L, f)$. Note that in this case, the isometries `g`'s are of order -$nm$. +Given a lattice with isometry $(L, f)$ of hermitian type (i.e. the minimal polynomial +of `f` is irreducible cyclotomic), and a positive integer `m`, return a set of +representatives of isomorphism classes of lattices with isometry of hermitian +type $(M, g)$ and such that the type of $(B, g^m)$ is equal to the type of +$(L, f)$. Note that in this case, the isometries `g`'s are of order $nm$. See Algorithm 3 of [BH22]. """ -function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) +function representatives_of_hermitian_type(Lf::LatticeWithIsometry, m::Int = 1) rank(Lf) == 0 && return LatticeWithIsometry[] @req m >= 1 "m must be a positive integer" - @req is_of_pure_type(Lf) "Minimal polyomial must be irreducible and cyclotomic" + @req is_of_hermitian_type(Lf) "Lf must be of hermitian" - L = lattice(Lf) - rk = rank(L) - d = det(L) + rk = rank(Lf) + d = det(Lf) n = order_of_isometry(Lf) - s1, _, s2 = signature_tuple(L) + s1, _, s2 = signature_tuple(Lf) reps = LatticeWithIsometry[] @@ -407,10 +405,10 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) end @info "$H" M = trace_lattice(H) - @assert det(lattice(M)) == d - @assert is_cyclotomic_polynomial(minpoly(M)) + @assert det(M) == d + @assert is_of_hermitian_type(M) @assert order_of_isometry(M) == n*m - if iseven(lattice(M)) != iseven(L) + if iseven(M) != iseven(Lf) continue end if !is_of_same_type(Lf, lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) @@ -422,28 +420,27 @@ function representatives_of_pure_type(Lf::LatticeWithIsometry, m::Int = 1) end @doc Markdown.doc""" - representatives_of_pure_type(t::Dict, m::Int = 1; check::Bool = true) + representatives_of_hermitian_type(t::Dict, m::Int = 1; check::Bool = true) -> Vector{LatticeWithIsometry} -Given a type `t` for lattices with isometry of pure type (i.e. the minimal +Given a hermitian type `t` for lattices with isometry (i.e. the minimal polymomial of the associated isometry is irreducible cyclotomic) and an intger `m` (set to 1 by default), return a set of representatives of isomorphism -classes of lattices with isometry of pure type $(L, f)$ such that the +classes of lattices with isometry of hermitian type $(L, f)$ such that the type of $(L, f^m)$ is equal to `t`. -If `check === true`, then `t` is checked to be pure. Note that `n` can be 1. +If `check === true`, then `t` is checked to be hermitian. Note that `n` can be 1. See Algorithm 3 of [BH22]. """ -function representatives_of_pure_type(t::Dict, m::Integer = 1; check::Bool = true) +function representatives_of_hermitian_type(t::Dict, m::Integer = 1; check::Bool = true) M = _representative(t, check = check) M === nothing && return LatticeWithIsometry[] - return representatives_of_pure_type(M, m) + return representatives_of_hermitian_type(M, m) end - function _representative(t::Dict; check::Bool = true) - !check || is_pure(t) || error("t must be pure") + !check || is_hermitian(t) || error("t must be pure") ke = collect(keys(t)) n = maximum(ke) @@ -488,10 +485,10 @@ function _representative(t::Dict; check::Bool = true) end H = H M = trace_lattice(H) - @assert det(lattice(M)) == d - @assert is_cyclotomic_polynomial(minpoly(M)) + @assert det(M) == d + @assert is_of_hermitian_type(M) @assert order_of_isometry(M) == n - if iseven(lattice(M)) != iseven(G) + if iseven(M) != iseven(G) continue end if !is_of_type(M, t) @@ -503,24 +500,23 @@ function _representative(t::Dict; check::Bool = true) end @doc Markdown.doc""" - prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::Int) + splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int) -> Vector{LatticeWithIsometry} -Given a lattice with isometry $(L, f)$ of pure type (i.e. the minimal polynomial -of `f` is irreducible cyclotomic) with `f` of order $q^d$ for some prime number `q`, -and a prime number $p \neq q$, return a set of representatives of the isomorphisms -classes of lattices with isometry $(M, g)$ such that the type of $(M, g^p)$ is equal -to the type of $(L, f)$. +Given a lattice with isometry $(L, f)$ of hermitian type with `f` of order $q^d$ +for some prime number `q`, and given another prime number $p \neq q$, return a +set of representatives of the isomorphism classes of lattices with isometry +$(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. Note that `d` can be 0. See Algorithm 4 of [BH22]. """ -function prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::Int) +function splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[] @req is_prime(p) "p must be a prime number" - @req is_of_pure_type(Lf) "Minimal polynomial must be irreducible and cyclotomic" + @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" ok, q, d = is_prime_power_with_data(order_of_isometry(Lf)) @@ -533,12 +529,12 @@ function prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::In @info "$(atp) admissible triple(s)" for (A, B) in atp LB = lattice_with_isometry(representative(B)) - RB = representatives_of_pure_type(LB, p*q^d) + RB = representatives_of_hermitian_type(LB, p*q^d) if is_empty(RB) continue end LA = lattice_with_isometry(representative(A)) - RA = representatives_of_pure_type(LA, q^d) + RA = representatives_of_hermitian_type(LA, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p, check=false) GC.gc() @@ -548,30 +544,28 @@ function prime_splitting_of_pure_type_prime_power(Lf::LatticeWithIsometry, p::In return reps end - @doc Markdown.doc""" - prime_splitting_of_pure_type_prime_power(t::Dict, p::Int) + splitting_of_hermitian_prime_power(t::Dict, p::Int) -> Vector{LatticeWithIsometry} -Given a type `t` of lattice with isometry of pure type $(L, f)$ (i.e. the minimal -polynomial of `f` is irreducible cyclotomic) with `f` of order $q^d$ for some -prime number `q`, and a prime number $p \neq q$, return a set of representatives -of the isomorphisms classes of lattices with isometry $(M, g)$ such that the type -of $(M, g^p)$ is equal to `t`. +Given a hermitian type `t` of lattice with isometry $(L, f)$ with `f` of order +$q^d$ for some prime number `q`, and given another prime number $p \neq q$, +return a set of representatives of the isomorphisms classes of lattices with +isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to `t`. Note that `d` can be 0. See Algorithm 4 of [BH22]. """ -function prime_splitting_of_pure_type_prime_power(t::Dict, p::Int) +function splitting_of_hermitian_prime_power(t::Dict, p::Int) @req is_prime(p) "p must be a prime number" - @req is_pure(t) "t must be pure" + @req is_hermitian(t) "t must be hermitian" Lf = _representative(t) - return prime_splitting_of_pure_type_prime_power(Lf, p) + return splitting_of_hermitian_prime_power(Lf, p) end @doc Markdown.doc""" - prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) + splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) -> Vector{LatticeWithIsometry} Given a lattice with isometry $(L, f)$ with `f` of order $q^e$ for some prime number @@ -584,7 +578,7 @@ Note that `e` can be 0. See Algorithm 5 of [BH22]. """ -function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) +function splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) rank(Lf) == 0 && return LatticeWithIsometry[] @req is_prime(p) "p must be a prime number" @@ -598,7 +592,7 @@ function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int reps = LatticeWithIsometry[] if e == 0 - reps = prime_splitting_of_pure_type_prime_power(Lf, p) + reps = splitting_of_hermitian_prime_power(Lf, p) (b == 1) && filter!(M -> order_of_isometry(M) == p, reps) return reps end @@ -606,9 +600,9 @@ function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int x = gen(Hecke.Globals.Qx) A0 = kernel_lattice(Lf, q^e) B0 = kernel_lattice(Lf, x^(q^e-1)-1) - A = prime_splitting_of_pure_type_prime_power(A0, p) + A = splitting_of_hermitian_prime_power(A0, p) is_empty(A) && return reps - B = prime_splitting_of_prime_power(B0, p) + B = splitting_of_prime_power(B0, p) for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue E = admissible_equivariant_primitive_extensions(L1, L2, Lf, q, check=false) @@ -619,11 +613,11 @@ function prime_splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int end @doc Markdown.doc""" - prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) + splitting_of_partial_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) -> Vector{LatticeWithIsometry} Given a lattice with isometry $(L, f)$ and a prime number `p`, such that -the minimal of `f` divides $\prod_{i=0}^e\Phi_{p^dq^i}(f)$ for some +the minimal polynomial of `f` divides $\prod_{i=0}^e\Phi_{p^dq^i}(f)$ for some $d > 0$ and $e \geq 0$, return a set of representatives of the isomorphism classes of lattices with isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. @@ -632,7 +626,7 @@ Note that `e` can be 0, while `d` has to be positive. See Algorithm 6 of [BH22]. """ -function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) +function splitting_of_partial_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[] @req is_prime(p) "p must be a prime number" @@ -662,7 +656,7 @@ function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) reps = LatticeWithIsometry[] if e == 0 - return representatives_of_pure_type(Lf, p) + return splitting_of_hermitian_prime_power(Lf, p) end A0 = kernel_lattice(Lf, p^d*q^e) @@ -670,9 +664,9 @@ function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) @assert bool B0 = kernel_lattice(Lf, r) - A = representatives_of_pure_type(A0, p) + A = splitting_of_prime_power(A0, p) is_empty(A) && return reps - B = prime_splitting_of_semi_pure_type(B0, p) + B = splitting_of_partial_mixed_prime_power(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = admissible_equivariant_primitive_extensions(LA, LB, Lf, q, check = false) append!(reps, E) @@ -681,7 +675,8 @@ function prime_splitting_of_semi_pure_type(Lf::LatticeWithIsometry, p::Int) end @doc Markdown.doc""" - prime_splitting(Lf::LatticeWithIsometry, p::Int) -> Vector{LatticeWithIsometry} + splitting_of_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) + -> Vector{LatticeWithIsometry} Given a lattice with isometry $(L, f)$ and a prime number `p` such that `f` is of order $p^dq^e$ for some prime number $q \neq p$, return a set @@ -693,7 +688,7 @@ Note that `d` and `e` can be both zero. See Algorithm 7 of [BH22]. """ -function prime_splitting(Lf::LatticeWithIsometry, p::Int) +function splitting_of_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) rank(Lf) == 0 && return LatticeWithIsometry[] n = order_of_isometry(Lf) @@ -705,7 +700,7 @@ function prime_splitting(Lf::LatticeWithIsometry, p::Int) @req length(pd) <= 2 "Order must have at most 2 prime divisors" if !(p in pd) - return prime_splitting_of_prime_power(Lf, p, 1) + return splitting_of_prime_power(Lf, p, 1) end d = valuation(n, p) @@ -721,9 +716,9 @@ function prime_splitting(Lf::LatticeWithIsometry, p::Int) x = gen(parent(minpoly(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])) - A = prime_splitting_of_semi_pure_type(A0, p) + A = splitting_of_partial_mixed_prime_power(A0, p) isempty(A) && return reps - B = prime_splitting(B0, p) + B = splitting_of_mixed_prime_power(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = admissible_equivariant_primitive_extensions(LA, LB, Lf, p, check = false) @assert all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) diff --git a/src/NumberTheory/LatticesWithIsometry/HMM.jl b/src/NumberTheory/LatticesWithIsometry/HMM.jl new file mode 100644 index 000000000000..eba65f8157d5 --- /dev/null +++ b/src/NumberTheory/LatticesWithIsometry/HMM.jl @@ -0,0 +1,133 @@ +function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) + OE = order(P) + E = nf(OE) + Eabs, EabstoE = Hecke.absolute_simple_field(E, cached = false) + + Pabs = EabstoE\P + OEabs = order(Pabs) + RPabs, mRPabs = quo(OEabs, Pabs^i) + URPabs, mURPabs = unit_group(RPabs) + + function dlog(x::Hecke.NfRelElem) + @assert parent(x) === E + @assert is_integral(x) + return muRPabs\(mRPabs(OEabs(EabstoE\x))) + end + + function exp(k::GrpAbFinGenElem) + @assert parent(k) === URPabs + x = EabstoE(Eabs(mRPabs\(mURPabs(k)))) + @assert dlog(x) == k + return x + end + + return URPabs, exp, dlog +end + +function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) + OE = order(P) + OK = base_ring(OE) + E = nf(OE) + K = base_field(E) + p = minimum(P) + + Eabs, EabstoE = Hecke.absolute_simple_field(E, cached=false) + Pabs = EabstoE\P + OEabs = order(Pabs) + Rp, mRp = quo(OK, p^i) + URp, mURp = unit_group(Rp) + + RPabs, mRPabs = quo(OEabs, Pabs^i) + URPabs, mURPabs = unit_group(RPabs) + f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(URPabs[i])))))))) for i in 1:ngens(URPabs)]) + + K, mK = kernel(f) + + S, mS = snf(K) + + function exp(k::GrpAbFinGenElem) + @assert parent(k) === S + return EabstoE(elem_in_nf(mRPabs\(mURPabs(mK(mS(k)))))) + end + + function dlog(x::Hecke.NfRelElem) + @assert parent(x) === E + @assert is_integral(x) + return mS\(mK\(mURPabs\(mRPabs(OEabs(EabstoE\x))))) + end + + return S, exp, dlog +end + +function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) + OE = order(P) + E = nf(OE) + p = minimum(P) + e = valuation(different(OE), P) + + if i < e + S = abelian_group(Int[]) + return S, x -> one(E), x -> id(S) + end + + t = e-1 + + psi(x) = x <= t ? x : t + 2*(x-t) + + pi = uniformizer(P) + + jj = t+1//2 + while ceil(psi(jj)) != i + jj += 1//2 + end + j = Int(ceil(jj)) + + Pi = P^i + + Eabs, EabstoE = Hecke.absolute_simple_field(E, cached=false) + OK = order(p) + Rp, mRp = quo(OK, p^j) + URp, mURp = unit_group(Rp) + + Pabs = EabstoE\P + OEabs = order(Pabs) + RPabs, mRPabs = quo(OEabs, Pabs^i) + URPabs, mURPabs = unit_group(RPabs) + + f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(URPabs[i])))))))) for i in 1:ngens(URPabs)]) + + K, mK = kernel(f) + + S, mS = snf(K) + + function exp(k::GrpAbFinGenElem) + @assert parent(k) === S + return EabstoE(elem_in_nf(mRPabs\(mURPabs(mK(mS(k)))))) + end + + function dlog(x::Hecke.NfRelElem) + @assert parent(x) === E + @assert is_integral(x) + return mS\(mK\(mURPabs\(mRPabs(OEabs(EabstoE\x))))) + end + + return S, exp, dlog +end + +function _get_quotient(P::Hecke.NfRelOrdIdl, i::Int) + @assert is_prime(P) + @assert is_maximal(order(P)) + OE = order(P) + F = prime_decomposition(OE, minimum(P)) + if length(F) == 2 + S, dexp, dlog = _get_quotient_split(P, i) + elseif F[1][2] == 1 + S, dexp, dlog = _get_quotient_inert(P, i) + else + S, dexp, dlog = _get_quotient_ramified(P, i) + end + return S, dexp, dlog +end + +#function _get_big_quotient(E::) + diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index e12d90a8076a..991a81bd9b25 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -3,10 +3,10 @@ export ambient_isometry, hermitian_structure, image_centralizer_in_Oq, isometry, - is_of_pure_type, + is_of_hermitian_type, is_of_same_type, is_of_type, - is_pure, + is_hermitian, lattice_with_isometry, order_of_isometry, type @@ -95,7 +95,7 @@ minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf))::fmpq_poly Given a lattice with isometry $(L, f)$, return the genus of the underlying lattice `L`. """ -genus(Lf::LatticeWithIsometry) = begin; L = lattice(Lf); is_integral(L) ? genus(L)::ZGenus : error("Underlying lattice must be integral"); end +genus(Lf::LatticeWithIsometry) = genus(lattice(Lf))::ZGenus @doc Markdown.doc""" ambient_space(Lf::LatticeWithIsometry) -> QuadSpace @@ -105,6 +105,29 @@ lattice `L`. """ ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, fmpq_mat} +basis_matrix(Lf::LatticeWithIsometry) = basis_matrix(lattice(Lf))::fmpq_mat + +gram_matrix(Lf::LatticeWithIsometry) = gram_matrix(lattice(Lf))::fmpq_mat + +rational_span(Lf::LatticeWithIsometry) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, fmpq_mat} + +det(Lf::LatticeWithIsometry) = det(lattice(Lf))::fmpq + +scale(Lf::LatticeWithIsometry) = det(lattice(Lf))::fmpq + +norm(Lf::LatticeWithIsometry) = norm(lattice(Lf))::fmpq + +is_integral(Lf::LatticeWithIsometry) = is_integral(lattice(Lf))::Bool + +degree(Lf::LatticeWithIsometry) = degree(lattice(Lf))::Int + +is_even(Lf::LatticeWithIsometry) = is_even(lattice(Lf))::Int + +discriminant(Lf::LatticeWithIsometry) = discriminant(Lf)::fmpq + +signature_tuple(Lf::LatticeWithIsometry) = signature_tuple(Lf)::Tuple{Int, Int, Int} + + ############################################################################### # # Constructor @@ -195,12 +218,36 @@ function lattice_with_isometry(L::ZLat) return lattice_with_isometry(L, f, check = false, ambient_representation = true)::LatticeWithIsometry end +############################################################################### +# +# Operations on lattice with isometry +# +############################################################################### + +function rescale(Lf::LatticeWithIsometry, a::Hecke.RationalUnion) + return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), order_of_isometry(Lf), check=false) +end + +function dual(Lf::LatticeWithIsometry) + @req is_integral(Lf) "Underlying lattice must be integral" + return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), order_of_isometry(Lf), check = false) +end + ############################################################################### # # Hermitian structure # ############################################################################### +function is_of_hermitian_type(Lf::LatticeWithIsometry) + @req rank(Lf) > 0 "Underlying lattice must have positive rank" + n = order_of_isometry(Lf) + if n <= 2 || !is_finite(n) + return false + end + return is_cyclotomic_polynomial(minpoly(f)) +end + @doc Markdown.doc""" hermitian_structure(Lf::LatticeWithIsometry; check::Bool = true) -> HermLat @@ -212,13 +259,10 @@ field, where $n$ is the order of `f`. If it exists, the hermitian structure is cached. """ @attr HermLat function hermitian_structure(Lf::LatticeWithIsometry) - @req rank(Lf) > 0 "Lf must be of positive rank" + @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" f = isometry(Lf) n = order_of_isometry(Lf) - @req n >= 3 "No hermitian structures for n smaller than 3" - @req is_cyclotomic_polynomial(minpoly(f)) "The minimal polynomial must be irreducible and cyclotomic" - return Oscar._hermitian_structure(lattice(Lf), f, n = n, check = false, ambient_representation = false) end @@ -237,9 +281,9 @@ of the underlying lattice `L` as well as this image of the underlying isometry `f` inside $O(q)$. """ function discriminant_group(Lf::LatticeWithIsometry) + @req is_integral(Lf) "Underlying lattice must be integral" L = lattice(Lf) f = ambient_isometry(Lf) - @req is_integral(L) "Underlying lattice must be integral" q = discriminant_group(L) Oq = orthogonal_group(q) return (q, Oq(gens(matrix_group(f))[1], check = false))::Tuple{TorQuadMod, AutomorphismGroupElem{TorQuadMod}} @@ -254,13 +298,13 @@ denotes the discriminant group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. """ @attr AutomorphismGroup{TorQuadMod} function image_centralizer_in_Oq(Lf::LatticeWithIsometry) + @req is_integral(Lf) "Underlying lattice must be integral" n = order_of_isometry(Lf) L = lattice(Lf) f = ambient_isometry(Lf) - @req is_integral(L) "Underlying lattice must be integral" - if n in [1, -1] + if (n in [1, -1]) || (isometry(Lf) == -identity_matrix(QQ, rank(L))) GL, _ = image_in_Oq(L) - elseif is_definite(L) + elseif is_definite(L) OL = orthogonal_group(L) f = OL(f) UL = fmpq_mat[matrix(OL(s)) for s in gens(centralizer(OL, f)[1])] @@ -274,6 +318,7 @@ $q_L$ induced by `f`. unique!(UL) GL = Oscar._orthogonal_group(qL, UL, check = false) else + @req is_hermitian(Lf) "Not yet implemented for indefinite lattices with isometry which are not hermitian" qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) CdL, _ = centralizer(OqL, fqL) @@ -321,12 +366,10 @@ the $i$-th signature of $(L, f)$ is given by the signatures of the real quadrati form $\Ker(f + f^{-1} - z^i - z^{-i})$. """ function signatures(Lf::LatticeWithIsometry) - @req rank(Lf) != 0 "Signatures non available for the empty lattice" + @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" L = lattice(Lf) f = isometry(Lf) - @req is_cyclotomic_polynomial(minpoly(f)) "Minimal polynomial must be irreducible and cyclotomic" n = order_of_isometry(Lf) - @req divides(rank(L), euler_phi(n))[1] "The totient of the order of the underlying isometry must divide the rank of the underlying lattice" C = CalciumField() eig = eigenvalues(f, QQBar) j = findfirst(z -> findfirst(k -> isone(z^k), 1:n) == n, eig) @@ -373,7 +416,7 @@ function kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) chif = parent(p)(collect(coefficients(minpoly(Lf)))) _chi = gcd(p, chif) @assert (rank(L2) == 0) || (chi == _chi) - return lattice_with_isometry(L2, f2, check = true, ambient_representation = false) + return lattice_with_isometry(L2, f2, ambient_representation = false) end kernel_lattice(Lf::LatticeWithIsometry, p::fmpz_poly) = kernel_lattice(Lf, change_base_ring(QQ, p)) @@ -493,24 +536,12 @@ function is_of_same_type(L::LatticeWithIsometry, M::LatticeWithIsometry) end @doc Markdown.doc""" - is_of_pure_type(Lf::LatticeWithIsometry) -> Bool - -Given a lattice with isometry $(L, f)$, return whether the minimal polynomial -of `f` is irreducible cyclotomic. -""" -function is_of_pure_type(L::LatticeWithIsometry) - @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" - return is_cyclotomic_polynomial(minpoly(L)) -end - -@doc Markdown.doc""" - is_pure(t::Dict) -> Bool + is_hermitian(t::Dict) -> Bool -Given a type `t` of lattices with isometry, return whether `t` is pure, i.e. -whether it defines the type of lattice with isometry whose minimal polynomial -is irreducible cyclotomic. +Given a type `t` of lattices with isometry, return whether `t` is hermitian, i.e. +whether it defines the type of a hermitian lattice with isometry. """ -function is_pure(t::Dict) +function is_hermitian(t::Dict) ke = collect(keys(t)) n = maximum(ke) return all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl index c405562a18d6..dab7e057f008 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl @@ -30,8 +30,15 @@ Note that the isometry `f` computed is given by its action on the ambient space trace lattice of `L`. """ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = gen(base_field(L)), + beta::FieldElem = one(base_field(L)), order::Integer = 2) where T + + return trace_lattice_with_map(L, alpha=alpha, beta=beta, order=order)[1] +end + +function trace_lattice_with_map(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), + beta::FieldElem = gen(base_field(L)), + order::Integer = 2) where T E = base_field(L) @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @req order > 0 "The order must be positive" @@ -44,6 +51,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) end if E == QQ + V = ambient_space(L) if order == 1 f = identity_matrix(E, n) elseif order == 2 @@ -51,7 +59,7 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) else error("For ZLat the order must be 1 or 2") end - return lattice_with_isometry(L, f, order, check = false) + return lattice_with_isometry(L, f, order, check = false), VecSpaceRes{typeof(V), typeof(V)}(V, V) end bool, m = Hecke.is_cyclotomic_type(E) @@ -70,6 +78,37 @@ function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)) v[i] = zero(QQ) end + return lattice_with_isometry(Lres, iso, ambient_representation=true), f +end + +function trace_lattice(L::Hecke.AbsLat{T}, f::SpaceRes; beta::FieldElem = gen(base_field(L))) where T + @req codomain(f) === ambient_space(L) "f must be a map of restriction of scalars associated to the ambient space of L" + E = base_field(L) + @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" + @req degree(L) == rank(L) "Lattice must be of full rank" + @req parent(beta) == E "beta must be an element of the base algebra of L" + @req !is_zero(beta) "beta must be non zero" + s = involution(E) + if s(beta)*beta != 1 + beta = beta//s(beta) + end + + bool, m = Hecke.is_cyclotomic_type(E) + @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "The normalisation of beta must be a $m-primitive root of 1" + + Lres = restrict_scalars(L, f) + iso = zero_matrix(QQ, 0, degree(Lres)) + v = vec(zeros(QQ, 1, degree(Lres))) + + for i in 1:degree(Lres) + v[i] = one(QQ) + v2 = f(v) + v2 = beta.*v2 + v3 = f\v2 + iso = vcat(iso, transpose(matrix(v3))) + v[i] = zero(QQ) + end + return lattice_with_isometry(Lres, iso, ambient_representation=true) end @@ -113,9 +152,10 @@ function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, if E === nothing E, b = cyclotomic_field_as_cm_extension(n) elseif !Hecke.is_cyclotomic_type(E)[1] - @req degree(E) == 2 && absolute_degree(E) == 2*euler_phi(n) "E should be the $n-th cyclotomic field seen as a cm-extension of its real cyclotomic subfield" + @req degree(E) == 2 && absolute_degree(E) == euler_phi(n) "E should be the $n-th cyclotomic field seen as a cm-extension of its real cyclotomic subfield" Et, t = E["t"] rt = roots(t^n-1) + filter!(l -> findfirst(i -> isone(l^i), 1:n) == n, rt) @req length(rt) == euler_phi(n) "E is not of cyclotomic type" b = isone(rt[1]) ? rt[2] : rt[1] else From 63c4499d2d13dc36cc2bdb5a35da7d44291218c9 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 15 Feb 2023 10:54:03 +0100 Subject: [PATCH 29/76] more --- .../LatticesWithIsometry/Embeddings.jl | 241 +++++++++++++++++- .../LatticesWithIsometry/Enumeration.jl | 8 +- .../LatticesWithIsometry.jl | 8 +- 3 files changed, 248 insertions(+), 9 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl index ddefc06f00c3..460f297528ab 100644 --- a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl +++ b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl @@ -1,5 +1,7 @@ export admissible_equivariant_primitive_extensions, - primitive_embeddings_in_primary_lattice + equivariant_primitive_extensions, + primitive_embeddings_in_primary_lattice, + primitive_embeddings_of_elementary_lattice GG = GAP.Globals @@ -179,7 +181,7 @@ end function _as_Fp_vector_space_quotient(HinV, p, f) if f isa AutomorphismGroupElem - f = hom(domain(f), domain(f), matrix(f)) + f = hom(f) end i = HinV.map_ab H = domain(HinV) @@ -231,7 +233,7 @@ end function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, ord::Hecke.IntegerUnion, - f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = one(G), + f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = id_hom(codomain(Vinq)), l::Hecke.IntegerUnion = 0) if ord == 1 if l != 0 @@ -246,7 +248,7 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua g = valuation(ord, p) @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) - Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) + Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, _restrict(f, Vinq)) Vp = codomain(VtoVp) if dim(Qp) == 0 @@ -397,6 +399,71 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM return results end +function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadMod) + @assert is_one(basis_matrix(N)) + @assert is_one(basis_matrix(M)) + bool, p = is_elementary_with_prime(H) + @assert bool + @assert is_elementary(M, p) + results = Tuple{ZLat, ZLat, ZLat}[] + GN, _ = image_in_Oq(N) + GM, _ = image_in_Oq(M) + qN = domain(GN) + qM = domain(GM) + + D, qNinD, qMinD = orthogonal_sum(qN, qM) + OD = orthogonal_group(D) + VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) + subsN = subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) + filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) + @assert !isempty(subsN) + subsM = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) + filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) + @assert !isempty(subsM) + + for H1 in subsN, H2 in subsM + ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) + @assert ok + + HNinqN, stabN = H1 + OHN = orthogonal_group(HN) + + HMinqM, stabM = H2 + OHM = orthogonal_group(HM) + + actN = hom(stabN, OHN, [OHN(_restrict(x, HNinqN)) for x in gens(stabN)]) + + actM = hom(stabM, OHM, [OHM(_restrict(x, HMinqM)) for x in gens(stabM)]) + imM, _ = image(actM) + + stabNphi = AutomorphismGroupElem{TorQuadMod}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] + stabNphi, _ = sub(OHM, stabNphi) + reps = double_cosets(OHM, stabNphi, imM) + @info "$(length(reps)) isomorphism classe(s) of primitive extensions" + for g in reps + g = representative(g) + phig = compose(phi, hom(g)) + _glue = Vector{fmpq}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] + z = zero_matrix(QQ, 0, degree(N)+degree(M)) + glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) + glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) + N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) + @assert genus(N) == genus(N2) + M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) + @assert genus(M) == genus(M2) + push!(results, (L, M2, N2)) + @info "Gluing done" + GC.gc() + end + end + return results +end + + @doc Markdown.doc""" primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat) -> Vector{Tuple{ZLat, ZLat, ZLat}} @@ -480,12 +547,178 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph return results end +function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadMod} = orthogonal_group(discriminant_group(L)); classification::Bool = false, check::Bool = false) + bool, p = is_elementary_with_prime(M) + @req bool "M must be elementary" + if check + @req length(genus_representatives(L)) == 1 "L must be unique in its genus" + end + results = Tuple{ZLat, ZLat, ZLat}[] + qL = rescale(discriminant_group(L), -1) + GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) + pL, _, nL = signature_tuple(L) + pM, _, nM = signature_tuple(M) + @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" + if ((pL, nL) == (pM, nM)) + if genus(M) != genus(L) + return false, results + else + return true, [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] + end + end + qM = discriminant_group(M) + D, (qMinD, qLinD), proj = Hecke._orthogonal_sum_with_injections_and_projections([qM, qL]) + VL, VLinqL, _ = _get_V(L, qL, identity_matrix(QQ, rank(L)), id_hom(qL), minpoly(identity_matrix(QQ,1)), p) + for k in divisors(gcd(order(qM), order(VL))) + @info "Glue order: $(k)" + subsL = subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) + @info "$(length(subsL)) subgroup(s)" + subsM = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), orthogonal_group(qM), k) + for H in subsL + HL = domain(H[1]) + _subsM = filter(HM -> is_anti_isometric_with_anti_isometry(domain(HM[1]), HL)[1], subsM) + isempty(_subsM) && continue + @info "Possible gluing" + HM = _subsM[1][1] + ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) + @assert ok + _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] + ext, _ = sub(D, D.(_glue)) + 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 = rescale(disc, -1) + !is_genus(disc, (pL-pM, nL-nM)) && continue + G = genus(disc, (pL-pM, nL-nM)) + !classification && return true, G + @info "We can glue: $G" + Ns = representatives(G) + @info "$(length(Ns)) possible orthogonal complement(s)" + Ns = lll.(Ns) + Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] + ext2, _ = sub(D, [qMinD(HM(g)) for g in gens(domain(HM))]) + perp2, j = orthogonal_submodule(D, ext2) + qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp2)]) + for N in Ns + append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) + GC.gc() + end + GC.gc() + end + end + @assert all(triple -> genus(triple[1]) == genus(L), results) + return (length(results) >0), results +end + #################################################################################### # # Admissible equivariant primitive extensions # #################################################################################### +function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::AutomorphismGroup{TorQuadMod}, + B::LatticeWithIsometry, GB::AutomorphismGroup{TorQuadMod}, + L::ZLat; check::Bool = false) + + if check + pA, nA = signature_tuple(A) + pB, nB = signature_tuple(B) + pL, nL = signature_tuple(L) + @req pA+pB == pL "Incompatible signatures" + @req nA+nB == nL "Incompatible signatures" + @req ambient_space(A) === ambient_space(B) === ambient_space(L) "Lattices must all live in the same ambient space" + end + + results = LatticeWithIsometry[] + + qA, fqA = discriminant_group(A) + qB, fqB = discriminant_group(B) + + if check + @req all(g -> compose(g, compose(fqA, inv(g))) == fqA, GA) "GA does not centralize fqA" + @req all(g -> compose(g, compose(fqB, inv(g))) == fqB, GB) "GB does not centralize fqB" + end + D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) + OD = orthogonal_group(D) + OqAinOD = embedding_orthogonal_group(qAinD) + OqBinOD = embedding_orthogonal_group(qBinD) + OqA = domain(OqAinOD) + OqB = domain(OqBinOD) + + gamma, _, _ = glue_map(L, lattice(A), lattice(B)) + subsA = classes_conjugate_subgroups(GA, domain(gamma)) + @assert !isempty(subsN) + subsB = classes_conjugate_subgroups(GB, codomain(gamma)) + @assert !isempty(subsM) + + for (H1, H2) in Hecke.cartesian_product_iterator([subsA, subsB], inplace=true) + ok, phi = is_anti_isometric_with_anti_isometry(H1, H2) + @assert ok + SAinqA, stabA = H1 + OSAinOqA = _embedding_orthogonal_group(SAinqA) + OSA = domain(OSAinOqA) + OSAinOD = compose(OSAinOqA, OqAinOD) + + SBinqB, stabB = H2 + SB = domain(SBinqB) + OSBinOqB = _embedding_orthogonal_group(SBinqB) + OSB = domain(OSBinOqB) + OSBinOD = compose(OSBinOqB, OqBinOD) + + # we compute the image of the stabalizers in the respective OS* and we keep track + # of the elements of the stabilizers acting trivially in the respective S* + actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) + imA, _ = image(actA) + kerA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(x) for x in gens(kernel(actA)[1])] + fSA = OSA(_restrict(fqA, SAinqA)) + + actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) + imB, _ = image(actB) + kerB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(x) for x in gens(kernel(actB)[1])] + fSB = OSB(_restrict(fqB, SBinqB)) + + + fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + bool, g0 = representative_action(OSB, fSAinOSB, fSB) + bool || continue + phi = compose(phi, hom(OSB(g0))) + fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + # Now the new phi is "sending" the restriction of fA to this of fB. + # So we can glue SA and SB. + @assert fSAinOSB == fSB + + # 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(OSB, fSB) + center, _ = sub(OSB, [OSB(c) for c in gens(center)]) + stabSAphi = AutomorphismGroupElem{TorQuadMod}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] + stabSAphi, _ = sub(center, stabSAphi) + stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) + reps = double_cosets(center, stabSB, stabSAphi) + + for g in reps + g = representative(g) + phig = compose(phi, hom(g)) + + _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] + z = zero_matrix(QQ, 0, degree(A)) + glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) + glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + C2 = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) + fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) + _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) + fC2 = _B*fC2*inv(_B) + L2 = lattice_with_isometry(C2, fC2, ambient_representation = false) + @assert genus(L2) == genus(L) + push!(results, L2) + end + end + return results +end + @doc Markdown.doc""" admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, Bfb::LatticeWithIsometry, diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl index b7879ada4109..3e90c0e099df 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatticesWithIsometry/Enumeration.jl @@ -512,7 +512,7 @@ Note that `d` can be 0. See Algorithm 4 of [BH22]. """ -function splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int) +function splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int; pA::Int = -1, pB::Int = -1) rank(Lf) == 0 && return LatticeWithIsometry[] @req is_prime(p) "p must be a prime number" @@ -526,6 +526,12 @@ function splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int) reps = LatticeWithIsometry[] @info "Compute admissible triples" atp = admissible_triples(Lf, p) + if pA >= 0 + filter!(t -> signature_pair(t[1])[1] == pA, atp) + end + if pB >= 0 + filter!(t -> singature_pairhe(t[2][1]) == pB, atp) + end @info "$(atp) admissible triple(s)" for (A, B) in atp LB = lattice_with_isometry(representative(B)) diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl index 991a81bd9b25..c19d9192e4e1 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl @@ -123,9 +123,9 @@ degree(Lf::LatticeWithIsometry) = degree(lattice(Lf))::Int is_even(Lf::LatticeWithIsometry) = is_even(lattice(Lf))::Int -discriminant(Lf::LatticeWithIsometry) = discriminant(Lf)::fmpq +discriminant(Lf::LatticeWithIsometry) = discriminant(lattice(Lf))::fmpq -signature_tuple(Lf::LatticeWithIsometry) = signature_tuple(Lf)::Tuple{Int, Int, Int} +signature_tuple(Lf::LatticeWithIsometry) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} ############################################################################### @@ -242,10 +242,10 @@ end function is_of_hermitian_type(Lf::LatticeWithIsometry) @req rank(Lf) > 0 "Underlying lattice must have positive rank" n = order_of_isometry(Lf) - if n <= 2 || !is_finite(n) + if n == -1 || !is_finite(n) return false end - return is_cyclotomic_polynomial(minpoly(f)) + return is_cyclotomic_polynomial(minpoly(isometry(Lf))) end @doc Markdown.doc""" From b9ed9be94a29b457bfff36113b0382682e6ded7c Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 20 Feb 2023 11:07:21 +0100 Subject: [PATCH 30/76] more --- src/NumberTheory/LatticesWithIsometry/Embeddings.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl index 460f297528ab..8e5d459c0810 100644 --- a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl +++ b/src/NumberTheory/LatticesWithIsometry/Embeddings.jl @@ -107,7 +107,7 @@ function _is_invariant(f::TorQuadModMor, i::TorQuadModMor) V = domain(i) for a in gens(V) b = f(i(a)) - haspreimage(fab, data(b))[1] || return false + haspreimage(i.map_ab, data(b))[1] || return false end return true end @@ -233,7 +233,7 @@ end function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModMor, G::AutomorphismGroup{TorQuadMod}, ord::Hecke.IntegerUnion, - f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = id_hom(codomain(Vinq)), + f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = id_hom(domain(Vinq)), l::Hecke.IntegerUnion = 0) if ord == 1 if l != 0 @@ -248,7 +248,7 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua g = valuation(ord, p) @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) - Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, _restrict(f, Vinq)) + Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) Vp = codomain(VtoVp) if dim(Qp) == 0 @@ -547,7 +547,7 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph return results end -function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadMod} = orthogonal_group(discriminant_group(L)); classification::Bool = false, check::Bool = false) +function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadMod} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) bool, p = is_elementary_with_prime(M) @req bool "M must be elementary" if check @@ -601,6 +601,9 @@ function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::Automo qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp2)]) for N in Ns append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) + if first + return results + end GC.gc() end GC.gc() @@ -961,7 +964,7 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, stab = union(stab, kerB) stab = TorQuadModMor[_restrict(g, j) for g in stab] stab = TorQuadModMor[hom(disc, disc, [disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] - stab = Oscar._orthogonal_group(discriminant_group(C2), [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) + stab = Oscar._orthogonal_group(discriminant_group(C2)[1], [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) set_attribute!(C2, :image_centralizer_in_Oq, stab) push!(results, C2) end From 35bd82a52e0d61ac705720e19ae9f3cee6252936 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 24 Feb 2023 17:43:37 +0100 Subject: [PATCH 31/76] prepare for breaking changes --- src/NumberTheory/LWI.jl | 12 +- .../Embeddings.jl | 318 ++++++++---------- .../Enumeration.jl | 96 +++--- .../HMM.jl | 0 .../LatWithIsom.jl} | 178 +++++----- .../TraceEquivalence.jl | 22 +- src/NumberTheory/LatWithIsom/Types.jl | 31 ++ .../LatticesWithIsometry/Types.jl | 18 - 8 files changed, 325 insertions(+), 350 deletions(-) rename src/NumberTheory/{LatticesWithIsometry => LatWithIsom}/Embeddings.jl (74%) rename src/NumberTheory/{LatticesWithIsometry => LatWithIsom}/Enumeration.jl (87%) rename src/NumberTheory/{LatticesWithIsometry => LatWithIsom}/HMM.jl (100%) rename src/NumberTheory/{LatticesWithIsometry/LatticesWithIsometry.jl => LatWithIsom/LatWithIsom.jl} (73%) rename src/NumberTheory/{LatticesWithIsometry => LatWithIsom}/TraceEquivalence.jl (87%) create mode 100644 src/NumberTheory/LatWithIsom/Types.jl delete mode 100644 src/NumberTheory/LatticesWithIsometry/Types.jl diff --git a/src/NumberTheory/LWI.jl b/src/NumberTheory/LWI.jl index cb3a157fe0d5..a7f046e4edd8 100644 --- a/src/NumberTheory/LWI.jl +++ b/src/NumberTheory/LWI.jl @@ -1,7 +1,7 @@ -include("LatticesWithIsometry/Types.jl") -include("LatticesWithIsometry/HMM.jl") -include("LatticesWithIsometry/TraceEquivalence.jl") -include("LatticesWithIsometry/LatticesWithIsometry.jl") -include("LatticesWithIsometry/Enumeration.jl") -include("LatticesWithIsometry/Embeddings.jl") +include("LatWithIsom/Types.jl") +include("LatWithIsom/HMM.jl") +include("LatWithIsom/TraceEquivalence.jl") +include("LatWithIsom/LatWithIsom.jl") +include("LatWithIsom/Enumeration.jl") +include("LatWithIsom/Embeddings.jl") diff --git a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl b/src/NumberTheory/LatWithIsom/Embeddings.jl similarity index 74% rename from src/NumberTheory/LatticesWithIsometry/Embeddings.jl rename to src/NumberTheory/LatWithIsom/Embeddings.jl index 8e5d459c0810..5111f9ca651c 100644 --- a/src/NumberTheory/LatticesWithIsometry/Embeddings.jl +++ b/src/NumberTheory/LatWithIsom/Embeddings.jl @@ -1,9 +1,9 @@ export admissible_equivariant_primitive_extensions, - equivariant_primitive_extensions, - primitive_embeddings_in_primary_lattice, - primitive_embeddings_of_elementary_lattice +export equivariant_primitive_extensions, +export primitive_embeddings_in_primary_lattice, +export primitive_embeddings_of_elementary_lattice -GG = GAP.Globals +const GG = GAP.Globals ################################################################################ # @@ -11,84 +11,61 @@ GG = GAP.Globals # ################################################################################ -function inner_orthogonal_sum(T::TorQuadMod, U::TorQuadMod) - @assert modulus_bilinear_form(T) == modulus_bilinear_form(U) - @assert modulus_quadratic_form(T) == modulus_quadratic_form(U) - @assert ambient_space(cover(T)) === ambient_space(cover(U)) - cS = cover(T)+cover(U) - rS = relations(T)+relations(U) - geneT = [lift(a) for a in gens(T)] - geneU = [lift(a) for a in gens(U)] - S = torsion_quadratic_module(cS, rS, gens = unique([g for g in vcat(geneT, geneU) if !is_zero(g)]), modulus = modulus_bilinear_form(T), modulus_qf = modulus_quadratic_form(T)) - TinS = hom(T, S, S.(geneT)) - UinS = hom(U, S, S.(geneU)) - if order(S) == 1 - S.gram_matrix_quadratic = matrix(QQ,0,0,[]) - S.gram_matrix_bilinear = matrix(QQ,0,0,[]) - set_attribute!(S, :is_degenerate, false) - S.ab_grp = abelian_group() - end - return S, TinS, UinS -end - -function embedding_orthogonal_group(i::TorQuadModMor, j::TorQuadModMor) - A = domain(i) - B = domain(j) - D = codomain(i) - Dorth = direct_sum(A, B)[1] - ok, phi = is_isometric_with_isometry(Dorth, D) - @assert ok +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)]) OD = orthogonal_group(D) OA = orthogonal_group(A) OB = orthogonal_group(B) - geneOAinDorth = TorQuadModMor[] + gene = GrpAbFinGenElem[data(D(lift(a))+D(lift(b))) for a in gens(A), b in gens(B)] + geneOAinOD = TorQuadModuleMor[] for f in gens(OA) - m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) - m = hom(Dorth, Dorth, m) - push!(geneOAinDorth, m) + imgf = GrpAbFinGenElem[data(D(lift(f(a))) + D(lift(b))) for a in gens(A), b in gens(B)] + fab = hom(gene, imgf) + fD = hom(D, D, fab.map) + push!(geneOAinOD, fD) end - geneOBinDorth = TorQuadModMor[] + geneOBinOD = TorQuadModuleMor[] for f in gens(OB) - m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) - m = hom(Dorth, Dorth, m) - push!(geneOBinDorth, m) + imgf = GrpAbFinGenElem[data(D(lift(a)) + D(lift(f(b)))) for a in gens(A), b in gens(B)] + fab = hom(gene, imgf) + fD = hom(D, D, fab.map) + push!(geneOBinOD, fD) end - geneOAinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] OAtoOD = hom(OA, OD, geneOAinOD, check = false) - geneOBinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOBinDorth] OBtoOD = hom(OB, OD, geneOBinOD, check = false) - return OAtoOD, OBtoOD + return D, AinD, BinD, OD, OAtoOD, OBtoOD end -function _embedding_orthogonal_group(i::TorQuadModMor) - @req is_injective(i) "i must be injective" - A = domain(i) - D = codomain(i) - OA = orthogonal_group(A) +function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModule) + D, [AinD, BinD] = direct_sum(A, B) OD = orthogonal_group(D) - if order(OA) == 1 - return hom(OA, OD, [one(OD)], check = false) - end - B = orthogonal_submodule(D, A)[1] - Dorth = inner_orthogonal_sum(A, B)[1] - ok, phi = is_isometric_with_isometry(Dorth, D) - @assert ok + OA = orthogonal_group(A) + OB = orthogonal_group(B) - geneOAinDorth = TorQuadModMor[] + geneOAinOD = TorQuadModuleMor[] for f in gens(OA) - m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) - m = hom(Dorth, Dorth, m) - push!(geneOAinDorth, m) + m = block_diagonal_matrix([matrix(f), indentity_matrix(ZZ, ngens(B))]) + fD = hom(D, D, m) + push!(geneOAinOD, fD) + end + + geneOBinOD = TorQuadModuleMor[] + for f in gens(OB) + m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) + fD = hom(D, D, m) + push!(geneOBinOD, fD) end - geneOAinOD = [OD(compose(inv(phi), compose(g, phi)), check = false) for g in geneOAinDorth] OAtoOD = hom(OA, OD, geneOAinOD, check = false) - return OAtoOD::GAPGroupHomomorphism{AutomorphismGroup{TorQuadMod}, AutomorphismGroup{TorQuadMod}} + OBtoOD = hom(OB, OD, geneOBinOD, check = false) + return D, AinD, BinD, OD, OAtoOD, OBtoOD end -function _restrict(f::TorQuadModMor, i::TorQuadModMor) - imgs = TorQuadModElem[] +function restrict_automorphism(f::TorQuadModuleMor, i::TorQuadModuleMor) + imgs = TorQuadModuleElem[] V = domain(i) for g in gens(V) h = f(i(g)) @@ -98,11 +75,11 @@ function _restrict(f::TorQuadModMor, i::TorQuadModMor) return hom(V, V, imgs) end -function _restrict(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) - return _restrict(hom(f), i) +function restrict_automorphism(f::AutomorphismGroupElem{TorQuadModule}, i::TorQuadModuleMor) + return restrict_automorphism(hom(f), i) end -function _is_invariant(f::TorQuadModMor, i::TorQuadModMor) +function is_invariant(f::TorQuadModuleMor, i::TorQuadModuleMor) fab = f.map_ab V = domain(i) for a in gens(V) @@ -112,12 +89,12 @@ function _is_invariant(f::TorQuadModMor, i::TorQuadModMor) return true end -function _is_invariant(f::AutomorphismGroupElem{TorQuadMod}, i::TorQuadModMor) - return _is_invariant(hom(f), i) +function is_invariant(f::AutomorphismGroupElem{TorQuadModule}, i::TorQuadModuleMor) + return is_invariant(hom(f), i) end -function _is_invariant(aut::AutomorphismGroup{TorQuadMod}, i::TorQuadModMor) - return all(g -> _is_invariant(g, i), gens(aut)) +function is_invariant(aut::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) + return all(g -> is_invariant(g, i), gens(aut)) end function _get_V(L, q, f, fq, mu, p) @@ -132,17 +109,17 @@ function _get_V(L, q, f, fq, mu, p) pV, pVinV = primary_part(V, p) pV, pVinV = sub(V, pVinV.([divexact(order(g), p)*g for g in gens(pV) if !(order(g)==1)])) pVinq = compose(pVinV, Vinq) - fpV = _restrict(fq, pVinq) + fpV = restrict_automorphism(fq, pVinq) return pV, pVinq, fpV end -function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) +function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) N = relations(q) if l == 0 Gl = N Gm = intersect(1//p*N, dual(N)) rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) - gene = [q(lift(g)) for g in gens(rholN)] + gene = elem_type(q)[q(lift(g)) for g in gens(rholN)] return sub(q, gene) end k = l-1 @@ -151,7 +128,7 @@ function _rho_functor(q::TorQuadMod, p, l::Union{Integer, fmpz}) Gl = intersect(1//p^l*N, dual(N)) Gm = intersect(1//p^m*N, dual(N)) rholN = torsion_quadratic_module(Gl, Gk+p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) - gene = [q(lift(g)) for g in gens(rholN)] + gene = elem_type(q)[q(lift(g)) for g in gens(rholN)] return sub(q, gene) end @@ -166,7 +143,7 @@ function on_subgroup(H::Oscar.GAPGroup, g::AutomorphismGroupElem) return sub(G, g.(gens(H)))[1] end -function stabilizer(O::AutomorphismGroup{TorQuadMod}, i::TorQuadModMor) +function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) togap = get_attribute(O, :to_gap) @@ -191,7 +168,7 @@ function _as_Fp_vector_space_quotient(HinV, p, f) Vab = codomain(i) Vs, VstoVab = snf(Vab) - function _VtoVs(x::TorQuadModElem) + function _VtoVs(x::TorQuadModuleElem) return inv(VstoVab)(data(x)) end @@ -211,14 +188,14 @@ function _as_Fp_vector_space_quotient(HinV, p, f) return Vp(vec(collect(v))) end - function _VptoVs(v::ModuleElem{gfp_fmpz_elem}) + function _VptoVs(v::ModuleElem{FpFieldElem}) x = lift.(v.v) return Vs(vec(collect(x))) end VstoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) VtoVp = compose(VtoVs, VstoVp) - gene = gfp_fmpz_mat[matrix(F.((i(HstoHab(a))).coeff)) for a in gens(Hs)] + gene = FpMatrix[matrix(F.((i(HstoHab(a))).coeff)) for a in gens(Hs)] subgene = [Vp(vec(collect(transpose(v)))) for v in gene] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) @@ -230,17 +207,17 @@ function _as_Fp_vector_space_quotient(HinV, p, f) return Qp, VtoVp, VptoQp, fQp end -function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModMor, - G::AutomorphismGroup{TorQuadMod}, +function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMor, + G::AutomorphismGroup{TorQuadModule}, ord::Hecke.IntegerUnion, - f::Union{TorQuadModMor, AutomorphismGroupElem{TorQuadMod}} = id_hom(domain(Vinq)), + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq)), l::Hecke.IntegerUnion = 0) if ord == 1 if l != 0 - return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] end - _, triv = sub(codomain(Vinq), TorQuadModElem[]) - return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(triv, G)] + _, triv = sub(codomain(Vinq), TorQuadModuleElem[]) + return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[(triv, G)] end V = domain(Vinq) q = codomain(Vinq) @@ -253,24 +230,24 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua if dim(Qp) == 0 if order(V) == ord - return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[(Vinq, G)] + return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[(Vinq, G)] end - return Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] end OV = orthogonal_group(V) - gene_GV = elem_type(OV)[OV(_restrict(g, Vinq), check = false) for g in gens(G)] + gene_GV = elem_type(OV)[OV(restrict_automorphism(g, Vinq), check = false) for g in gens(G)] GV, _ = sub(OV, gene_GV) GVinG = hom(GV, G, gens(GV), gens(G), check = false) - act_GV_Vp = gfp_fmpz_mat[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] - act_GV_Qp = gfp_fmpz_mat[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] + act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] + act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(act_GV_Qp) @assert fQp in MGp MGptoGV = hom(MGp, GV, gens(GV), check = false) MGptoG = compose(MGptoGV, GVinG) - res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] if g-ngens(snf(abelian_group(H0))[1]) > dim(Qp) return res @@ -278,7 +255,7 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua F = base_ring(Qp) k, K = kernel(VptoQp.matrix, side = :left) - gene_H0p = ModuleElem{gfp_elem}[Vp(vec(collect(K[i,:]))) for i in 1:k] + gene_H0p = ModuleElem{fpFieldElem}[Vp(vec(collect(K[i,:]))) for i in 1:k] orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) for (orb, stab) in orb_and_stab i = orb.map @@ -289,11 +266,11 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua @goto non_fixed end end - gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{gfp_fmpz_elem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] - gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{gfp_fmpz_elem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] + gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{FpFieldElem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] + gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{FpFieldElem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] gene_submod_in_Vp = vcat(gene_orb_in_Vp, gene_H0p) - gene_submod_in_V = TorQuadModElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] - gene_submod_in_q = TorQuadModElem[image(Vinq, v) for v in gene_submod_in_V] + gene_submod_in_V = TorQuadModuleElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] + gene_submod_in_q = TorQuadModuleElem[image(Vinq, v) for v in gene_submod_in_V] orbq, orbqinq = sub(q, gene_submod_in_q) @assert order(orbq) == ord stabq, _ = image(MGptoG, stab) @@ -303,7 +280,7 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua return res end -function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadMod}; order::Hecke.IntegerUnion = -1) +function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadModule}; order::Hecke.IntegerUnion = -1) togap = get_attribute(O, :to_gap) tooscar = get_attribute(O, :to_oscar) q = domain(O) @@ -315,18 +292,18 @@ function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{To OinOAgap, j = sub(OAgap, OAgap.([g.X for g in gens(O)])) m = gset(OinOAgap, on_subgroup, coAgap) orbs = orbits(m) - res = Tuple{TorQuadModMor, AutomorphismGroup{TorQuadMod}}[] + res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] for orb in orbs rep = representative(orb) stab, _ = stabilizer(OinOAgap, rep, on_subgroup) - _, rep = sub(q, TorQuadModElem[tooscar(Agap(g)) for g in gens(rep)]) + _, rep = sub(q, TorQuadModuleElem[tooscar(Agap(g)) for g in gens(rep)]) stab, _ = sub(O, O.([h.X for h in gens(stab)])) push!(res, (rep, stab)) end return res end -function classes_conjugate_subgroups(O::AutomorphismGroup{TorQuadMod}, q::TorQuadMod) +function classes_conjugate_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) sors = subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) end @@ -340,7 +317,7 @@ end # here for convenience, we choose in entry N and M to be of full rank and # with basis matrix equal to the identity matrix -function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadMod) +function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadModule) @assert is_one(basis_matrix(N)) @assert is_one(basis_matrix(M)) results = Tuple{ZLat, ZLat, ZLat}[] @@ -349,7 +326,7 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM qN = domain(GN) qM = domain(GM) - D, qNinD, qMinD = orthogonal_sum(qN, qM) + D, [qNinD, qMinD] = direct_sum(qN, qM) OD = orthogonal_group(D) subsN = classes_conjugate_subgroups(GN, rescale(H, -1)) @@ -367,19 +344,19 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM HMinqM, stabM = H2 OHM = orthogonal_group(HM) - actN = hom(stabN, OHN, [OHN(_restrict(x, HNinqN)) for x in gens(stabN)]) + actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) - actM = hom(stabM, OHM, [OHM(_restrict(x, HMinqM)) for x in gens(stabM)]) + actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) for x in gens(stabM)]) imM, _ = image(actM) - stabNphi = AutomorphismGroupElem{TorQuadMod}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] + stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) @info "$(length(reps)) isomorphism classe(s) of primitive extensions" for g in reps g = representative(g) phig = compose(phi, hom(g)) - _glue = Vector{fmpq}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] + _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(N)+degree(M)) glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) @@ -399,11 +376,12 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM return results end -function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadMod) +function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadModule) @assert is_one(basis_matrix(N)) @assert is_one(basis_matrix(M)) - bool, p = is_elementary_with_prime(H) - @assert bool + q = elementary_divisors(H)[end] + ok, p, _ = is_primme_power_with_data(q) + @assert ok @assert is_elementary(M, p) results = Tuple{ZLat, ZLat, ZLat}[] GN, _ = image_in_Oq(N) @@ -411,7 +389,7 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: qN = domain(GN) qM = domain(GM) - D, qNinD, qMinD = orthogonal_sum(qN, qM) + D, [qNinD, qMinD] = direct_sum(qN, qM) OD = orthogonal_group(D) VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) subsN = subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) @@ -431,19 +409,19 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: HMinqM, stabM = H2 OHM = orthogonal_group(HM) - actN = hom(stabN, OHN, [OHN(_restrict(x, HNinqN)) for x in gens(stabN)]) + actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) - actM = hom(stabM, OHM, [OHM(_restrict(x, HMinqM)) for x in gens(stabM)]) + actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) for x in gens(stabM)]) imM, _ = image(actM) - stabNphi = AutomorphismGroupElem{TorQuadMod}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] + stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) @info "$(length(reps)) isomorphism classe(s) of primitive extensions" for g in reps g = representative(g) phig = compose(phi, hom(g)) - _glue = Vector{fmpq}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] + _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(N)+degree(M)) glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) @@ -477,9 +455,9 @@ The output is given in terms of triples `(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice of `L'` isometric to `M` and `N'` is the orthogonal complement of `M'` in `L'`. """ -function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadMod} = orthogonal_group(discriminant_group(L)); check::Bool = false) +function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); check::Bool = false) bool, p = is_primary_with_prime(L) - @req bool "L must be unimodular or elementary" + @req bool "L must be unimodular or primary" el = is_elementary(L, p) if check @req length(genus_representatives(L)) == 1 "L must be unique in its genus" @@ -496,7 +474,7 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph return [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] end qM = discriminant_group(M) - D, (qMinD, qLinD), proj = Hecke._orthogonal_sum_with_injections_and_projections([qM, qL]) + D, [qMinD, qLinD], proj = biproduct(qM, qL) if el VM, VMinqM, _ = _get_V(M, qM, identity_matrix(QQ, rank(M)), id_hom(qM), minpoly(identity_matrix(QQ,1)), p) else @@ -516,7 +494,7 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1])))))[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue - @info "Possible gluing" + @info "Possible gluings" HM = subsM[1] ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) @assert ok @@ -533,9 +511,7 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph @info "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] - ext2, _ = sub(D, [qMinD(HM(g)) for g in gens(domain(HM))]) - perp2, j = orthogonal_submodule(D, ext2) - qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp2)]) + qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) for N in Ns append!(results, _isomorphism_classes_primitive_extensions(N, M, qM2)) GC.gc() @@ -547,7 +523,7 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph return results end -function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadMod} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) +function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) bool, p = is_elementary_with_prime(M) @req bool "M must be elementary" if check @@ -567,7 +543,7 @@ function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::Automo end end qM = discriminant_group(M) - D, (qMinD, qLinD), proj = Hecke._orthogonal_sum_with_injections_and_projections([qM, qL]) + D, [qMinD, qLinD], proj = biproduct(qM, qL) VL, VLinqL, _ = _get_V(L, qL, identity_matrix(QQ, rank(L)), id_hom(qL), minpoly(identity_matrix(QQ,1)), p) for k in divisors(gcd(order(qM), order(VL))) @info "Glue order: $(k)" @@ -596,9 +572,7 @@ function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::Automo @info "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] - ext2, _ = sub(D, [qMinD(HM(g)) for g in gens(domain(HM))]) - perp2, j = orthogonal_submodule(D, ext2) - qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp2)]) + qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) for N in Ns append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) if first @@ -619,8 +593,8 @@ end # #################################################################################### -function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::AutomorphismGroup{TorQuadMod}, - B::LatticeWithIsometry, GB::AutomorphismGroup{TorQuadMod}, +function equivariant_primitive_extensions(A::LatWithIsom, GA::AutomorphismGroup{TorQuadModule}, + B::LatWithIsom, GB::AutomorphismGroup{TorQuadModule}, L::ZLat; check::Bool = false) if check @@ -632,7 +606,7 @@ function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::Automorphi @req ambient_space(A) === ambient_space(B) === ambient_space(L) "Lattices must all live in the same ambient space" end - results = LatticeWithIsometry[] + results = LatWithIsom[] qA, fqA = discriminant_group(A) qB, fqB = discriminant_group(B) @@ -641,10 +615,7 @@ function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::Automorphi @req all(g -> compose(g, compose(fqA, inv(g))) == fqA, GA) "GA does not centralize fqA" @req all(g -> compose(g, compose(fqB, inv(g))) == fqB, GB) "GB does not centralize fqB" end - D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) - OD = orthogonal_group(D) - OqAinOD = embedding_orthogonal_group(qAinD) - OqBinOD = embedding_orthogonal_group(qBinD) + D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) OqA = domain(OqAinOD) OqB = domain(OqBinOD) @@ -658,27 +629,27 @@ function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::Automorphi ok, phi = is_anti_isometric_with_anti_isometry(H1, H2) @assert ok SAinqA, stabA = H1 - OSAinOqA = _embedding_orthogonal_group(SAinqA) + OSAinOqA = embedding_orthogonal_group(SAinqA) OSA = domain(OSAinOqA) OSAinOD = compose(OSAinOqA, OqAinOD) SBinqB, stabB = H2 SB = domain(SBinqB) - OSBinOqB = _embedding_orthogonal_group(SBinqB) + OSBinOqB = embedding_orthogonal_group(SBinqB) OSB = domain(OSBinOqB) OSBinOD = compose(OSBinOqB, OqBinOD) # we compute the image of the stabalizers in the respective OS* and we keep track # of the elements of the stabilizers acting trivially in the respective S* - actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) + actA = hom(stabA, OSA, [OSA(Oscar.restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) imA, _ = image(actA) - kerA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(x) for x in gens(kernel(actA)[1])] - fSA = OSA(_restrict(fqA, SAinqA)) + kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(kernel(actA)[1])] + fSA = OSA(restrict_automorphism(fqA, SAinqA)) - actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) + actB = hom(stabB, OSB, [OSB(Oscar.restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(x) for x in gens(kernel(actB)[1])] - fSB = OSB(_restrict(fqB, SBinqB)) + kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(kernel(actB)[1])] + fSB = OSB(restrict_automorphism(fqB, SBinqB)) fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) @@ -694,7 +665,7 @@ function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::Automorphi # images of stabA|stabB for taking the double cosets next center, _ = centralizer(OSB, fSB) center, _ = sub(OSB, [OSB(c) for c in gens(center)]) - stabSAphi = AutomorphismGroupElem{TorQuadMod}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] + stabSAphi = AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] stabSAphi, _ = sub(center, stabSAphi) stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) reps = double_cosets(center, stabSB, stabSAphi) @@ -703,7 +674,7 @@ function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::Automorphi g = representative(g) phig = compose(phi, hom(g)) - _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] + _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(A)) glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) @@ -723,11 +694,11 @@ function equivariant_primitive_extensions(A::LatticeWithIsometry, GA::Automorphi end @doc Markdown.doc""" - admissible_equivariant_primitive_extensions(Afa::LatticeWithIsometry, - Bfb::LatticeWithIsometry, - Cfc::LatticeWithIsometry, + admissible_equivariant_primitive_extensions(Afa::LatWithIsom, + Bfb::LatWithIsom, + Cfc::LatWithIsom, p::Int; check=true) - -> Vector{LatticeWithIsometry} + -> Vector{LatWithIsom} Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a prime number `p`, such that `(A, B, C)` is `p`-admissible, return a set of @@ -749,9 +720,9 @@ of the associated isometries must be irreducible (and relatively coprime). See Algorithm 2 of [BH22]. """ -function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, - B::LatticeWithIsometry, - C::LatticeWithIsometry, +function admissible_equivariant_primitive_extensions(A::LatWithIsom, + B::LatWithIsom, + C::LatWithIsom, p::Integer; check=true) # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" @@ -767,7 +738,7 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, end end - results = LatticeWithIsometry[] + results = LatWithIsom[] # this is the glue valuation: it is well-defined because the triple in input is admissible g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) @@ -779,14 +750,9 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, # this is where we will perform the glueing if ambient_space(A) === ambient_space(B) - D, qAinD, qBinD = inner_orthogonal_sum(qA, qB) - OD = orthogonal_group(D) - OqAinOD = embedding_orthogonal_group(qAinD) - OqBinOD = embedding_orthogonal_group(qBinD) + D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) else - D, qAinD, qBinD = orthogonal_sum(qA, qB) - OD = orthogonal_group(D) - OqAinOD, OqBinOD = embedding_orthogonal_group(qAinD, qBinD) + D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _direct_sum_with_embeddings_orthogonal_groups(qA, qB) end OqA = domain(OqAinOD) @@ -795,8 +761,8 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, # if the glue valuation is zero, then we glue along the trivial group and we don't # have much more to do. Since the triple is p-admissible, A+B = C if g == 0 - geneA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(OqA(a.X)) for a in gens(GA)] - geneB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(OqB(b.X)) for b in gens(GB)] + geneA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(OqA(a.X)) for a in gens(GA)] + geneB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(OqB(b.X)) for b in gens(GB)] gene = vcat(geneA, geneB) GC2, _ = sub(OD, gene) if ambient_space(A) === ambient_space(B) === ambient_space(C) @@ -840,7 +806,7 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, subsB = subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry - R = Tuple{eltype(subsA), eltype(subsB), TorQuadModMor}[] + R = Tuple{eltype(subsA), eltype(subsB), TorQuadModuleMor}[] for H1 in subsA, H2 in subsB ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) !ok && continue @@ -852,34 +818,34 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, # corresponding overlattice and check whether it satisfies the type condition for (H1, H2, phi) in R SAinqA, stabA = H1 - OSAinOqA = _embedding_orthogonal_group(SAinqA) + OSAinOqA = embedding_orthogonal_group(SAinqA) OSA = domain(OSAinOqA) OSAinOD = compose(OSAinOqA, OqAinOD) SBinqB, stabB = H2 SB = domain(SBinqB) - OSBinOqB = _embedding_orthogonal_group(SBinqB) + OSBinOqB = embedding_orthogonal_group(SBinqB) OSB = domain(OSBinOqB) OSBinOD = compose(OSBinOqB, OqBinOD) # we compute the image of the stabalizers in the respective OS* and we keep track # of the elements of the stabilizers acting trivially in the respective S* - actA = hom(stabA, OSA, [OSA(Oscar._restrict(x, SAinqA)) for x in gens(stabA)]) + actA = hom(stabA, OSA, [OSA(restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) imA, _ = image(actA) - kerA = AutomorphismGroupElem{TorQuadMod}[OqAinOD(x) for x in gens(kernel(actA)[1])] - fSA = OSA(_restrict(fqA, SAinqA)) + kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(kernel(actA)[1])] + fSA = OSA(restrict_automorphism(fqA, SAinqA)) - actB = hom(stabB, OSB, [OSB(Oscar._restrict(x, SBinqB)) for x in gens(stabB)]) + actB = hom(stabB, OSB, [OSB(restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadMod}[OqBinOD(x) for x in gens(kernel(actB)[1])] - fSB = OSB(_restrict(fqB, SBinqB)) + kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(kernel(actB)[1])] + fSB = OSB(restrict_automorphism(fqB, SBinqB)) # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi # under the action of O(SB, rho_{l+1}(qB), fB) rB, rBinqB = _rho_functor(qB, p, valuation(l, p)+1) - @assert Oscar._is_invariant(stabB, rBinqB) - rBinSB = hom(rB, SB, TorQuadModElem[SBinqB\(rBinqB(k)) for k in gens(rB)]) + @assert is_invariant(stabB, rBinqB) + rBinSB = hom(rB, SB, TorQuadModuleElem[SBinqB\(rBinqB(k)) for k in gens(rB)]) @assert is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB # We compute the generators of O(SB, rho_{l+1}(qB)) @@ -902,7 +868,7 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, # images of stabA|stabB for taking the double cosets next center, _ = centralizer(OSBrB, OSBrB(fSB)) center, _ = sub(OSB, [OSB(c) for c in gens(center)]) - stabSAphi = AutomorphismGroupElem{TorQuadMod}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] + stabSAphi = AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] stabSAphi, _ = sub(center, stabSAphi) stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) reps = double_cosets(center, stabSB, stabSAphi) @@ -916,7 +882,7 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, phig = compose(phi, hom(g)) if ambient_space(A) === ambient_space(B) - _glue = Vector{fmpq}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] + _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(A)) glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) @@ -928,7 +894,7 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) else - _glue = Vector{fmpq}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] + _glue = Vector{QQFieldElem}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] z = zero_matrix(QQ,0,degree(A)+degree(B)) glue = reduce(vcat, [matrix(QQ,1,degree(A)+degree(B),g) for g in _glue], init=z) glue = vcat(block_diagonal_matrix([basis_matrix(A), basis_matrix(B)]), glue) @@ -958,12 +924,12 @@ function admissible_equivariant_primitive_extensions(A::LatticeWithIsometry, C2 = lattice_with_isometry(C2, fC2, ambient_representation=false) im2_phi, _ = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)])) im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) - stab = Tuple{AutomorphismGroupElem{TorQuadMod}, AutomorphismGroupElem{TorQuadMod}}[(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] - stab = AutomorphismGroupElem{TorQuadMod}[OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] + stab = Tuple{AutomorphismGroupElem{TorQuadModule}, AutomorphismGroupElem{TorQuadModule}}[(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] + stab = AutomorphismGroupElem{TorQuadModule}[OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] stab = union(stab, kerA) stab = union(stab, kerB) - stab = TorQuadModMor[_restrict(g, j) for g in stab] - stab = TorQuadModMor[hom(disc, disc, [disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] + 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 = Oscar._orthogonal_group(discriminant_group(C2)[1], [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) set_attribute!(C2, :image_centralizer_in_Oq, stab) push!(results, C2) diff --git a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl b/src/NumberTheory/LatWithIsom/Enumeration.jl similarity index 87% rename from src/NumberTheory/LatticesWithIsometry/Enumeration.jl rename to src/NumberTheory/LatWithIsom/Enumeration.jl index 3e90c0e099df..43c46d3a3bcb 100644 --- a/src/NumberTheory/LatticesWithIsometry/Enumeration.jl +++ b/src/NumberTheory/LatWithIsom/Enumeration.jl @@ -1,10 +1,10 @@ export admissible_triples, - is_admissible_triple, - splitting_of_hermitian_prime_power, - splitting_of_mixed_prime_power, - splitting_of_partial_mixed_prime_power, - splitting_of_prime_power, - representatives_of_hermitian_type +export is_admissible_triple, +export splitting_of_hermitian_prime_power, +export splitting_of_mixed_prime_power, +export splitting_of_partial_mixed_prime_power, +export splitting_of_prime_power, +export representatives_of_hermitian_type ################################################################################## # @@ -20,7 +20,7 @@ export admissible_triples, ################################################################################## # The tuples in output are pairs of positive integers! -function _tuples_divisors(d::T) where T <: Union{Integer, fmpz} +function _tuples_divisors(d::IntegerUnion) div = divisors(d) return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] end @@ -29,7 +29,7 @@ end # discriminant for the genera A and B to glue to fit in C. d is # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. -function _find_D(d::T, m::Int, p::Int) where T <: Union{Integer, fmpz} +function _find_D(d::IntegerUnion, m::Int, p::Int) @assert is_prime(p) @assert d != 0 @@ -55,13 +55,13 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Int, d::Hecke.RationalUnion, s::fmpz, l::fmpz, p::IntegerUnion, even = true) +function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::IntegerUnion, even = true) L = ZGenus[] if r == 0 && d == 1 return ZGenus[genus(Zlattice(gram = matrix(QQ, 0, 0, [])))] end for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] - gen = genera((s1,s2), d, even=even) + gen = Zgenera((s1,s2), d, even=even) filter!(G -> divides(numerator(scale(G)), s)[1], gen) filter!(G -> divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) @@ -79,7 +79,7 @@ Definition 4.13. [BH22] """ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) zg = genus(Zlattice(gram = matrix(QQ, 0, 0, []))) - AperpB = orthogonal_sum(A, B) + AperpB = direct_sum(A, B) (signature_tuple(AperpB) == signature_tuple(C)) || (return false) if ((A == zg) && (B == C)) || ((B == zg) && (A == C)) # C can be always glued with the empty genus to obtain C @@ -162,7 +162,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) Br = genus(matrix(ZZ, 0, 0, []), p) end - ABr = orthogonal_sum(Ar, Br) + ABr = direct_sum(Ar, Br) for i = 1:l s1 = symbol(ABr, i) @@ -202,7 +202,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) return true end -function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZLat, LatticeWithIsometry} +function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZLat, LatWithIsom} L = ZGenus[genus(D) for D = (A, B, C)] return is_admissible_triple(L[1], L[2], L[3], p) end @@ -241,7 +241,7 @@ function admissible_triples(G::ZGenus, p::Int64) return L end -admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatticeWithIsometry} = admissible_triples(genus(L), p) +admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatWithIsom} = admissible_triples(genus(L), p) ################################################################################## # @@ -251,7 +251,7 @@ admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatticeWithIsometry} # we compute ideals of E/K whose absolute norm is equal to d -function _ideals_of_norm(E, d::fmpq) +function _ideals_of_norm(E, d::QQRingElem) if denominator(d) == 1 return _ideals_of_norm(E, numerator(d)) elseif numerator(d) == 1 @@ -261,7 +261,7 @@ function _ideals_of_norm(E, d::fmpq) end end -function _ideals_of_norm(E, d::fmpz) +function _ideals_of_norm(E, d::ZZRingElem) isone(d) && return [1*maximal_order(E)] @assert E isa Hecke.NfRel K = base_field(E) @@ -332,8 +332,8 @@ function _possible_signatures(s1, s2, E, rk) end @doc Markdown.doc""" - representatives_of_hermitian_type(Lf::LatticeWithIsometry, m::Int = 1) - -> Vector{LatticeWithIsometry} + representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) + -> Vector{LatWithIsom} Given a lattice with isometry $(L, f)$ of hermitian type (i.e. the minimal polynomial of `f` is irreducible cyclotomic), and a positive integer `m`, return a set of @@ -343,8 +343,8 @@ $(L, f)$. Note that in this case, the isometries `g`'s are of order $nm$. See Algorithm 3 of [BH22]. """ -function representatives_of_hermitian_type(Lf::LatticeWithIsometry, m::Int = 1) - rank(Lf) == 0 && return LatticeWithIsometry[] +function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) + rank(Lf) == 0 && return LatWithIsom[] @req m >= 1 "m must be a positive integer" @req is_of_hermitian_type(Lf) "Lf must be of hermitian" @@ -354,7 +354,7 @@ function representatives_of_hermitian_type(Lf::LatticeWithIsometry, m::Int = 1) n = order_of_isometry(Lf) s1, _, s2 = signature_tuple(Lf) - reps = LatticeWithIsometry[] + reps = LatWithIsom[] if n*m < 3 @info "Order smaller than 3" @@ -374,7 +374,7 @@ function representatives_of_hermitian_type(Lf::LatticeWithIsometry, m::Int = 1) ok || return reps - gene = [] + gene = HermGenus[] E, b = cyclotomic_field_as_cm_extension(n*m) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) @@ -414,14 +414,14 @@ function representatives_of_hermitian_type(Lf::LatticeWithIsometry, m::Int = 1) if !is_of_same_type(Lf, lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) continue end - append!(reps, [trace_lattice(HH) for HH in genus_representatives(H)]) + append!(reps, LatWithIsom[trace_lattice(HH) for HH in genus_representatives(H)]) end return reps end @doc Markdown.doc""" representatives_of_hermitian_type(t::Dict, m::Int = 1; check::Bool = true) - -> Vector{LatticeWithIsometry} + -> Vector{LatWithIsom} Given a hermitian type `t` for lattices with isometry (i.e. the minimal polymomial of the associated isometry is irreducible cyclotomic) and an intger @@ -435,7 +435,7 @@ See Algorithm 3 of [BH22]. """ function representatives_of_hermitian_type(t::Dict, m::Integer = 1; check::Bool = true) M = _representative(t, check = check) - M === nothing && return LatticeWithIsometry[] + M === nothing && return LatWithIsom[] return representatives_of_hermitian_type(M, m) end @@ -459,7 +459,7 @@ function _representative(t::Dict; check::Bool = true) ok || reps - gene = [] + gene = HermGenus[] E, b = cyclotomic_field_as_cm_extension(n, cached=false) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) @@ -500,8 +500,7 @@ function _representative(t::Dict; check::Bool = true) end @doc Markdown.doc""" - splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int) - -> Vector{LatticeWithIsometry} + splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int) -> Vector{LatWithIsom} Given a lattice with isometry $(L, f)$ of hermitian type with `f` of order $q^d$ for some prime number `q`, and given another prime number $p \neq q$, return a @@ -512,8 +511,8 @@ Note that `d` can be 0. See Algorithm 4 of [BH22]. """ -function splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int; pA::Int = -1, pB::Int = -1) - rank(Lf) == 0 && return LatticeWithIsometry[] +function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = -1, pB::Int = -1) + rank(Lf) == 0 && return LatWithIsom[] @req is_prime(p) "p must be a prime number" @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" @@ -523,14 +522,14 @@ function splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int; pA: @req ok || d == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" - reps = LatticeWithIsometry[] + reps = LatWithIsom[] @info "Compute admissible triples" atp = admissible_triples(Lf, p) if pA >= 0 filter!(t -> signature_pair(t[1])[1] == pA, atp) end if pB >= 0 - filter!(t -> singature_pairhe(t[2][1]) == pB, atp) + filter!(t -> singature_pair(t[2][1]) == pB, atp) end @info "$(atp) admissible triple(s)" for (A, B) in atp @@ -551,8 +550,7 @@ function splitting_of_hermitian_prime_power(Lf::LatticeWithIsometry, p::Int; pA: end @doc Markdown.doc""" - splitting_of_hermitian_prime_power(t::Dict, p::Int) - -> Vector{LatticeWithIsometry} + splitting_of_hermitian_prime_power(t::Dict, p::Int) -> Vector{LatWithIsom} Given a hermitian type `t` of lattice with isometry $(L, f)$ with `f` of order $q^d$ for some prime number `q`, and given another prime number $p \neq q$, @@ -571,8 +569,7 @@ function splitting_of_hermitian_prime_power(t::Dict, p::Int) end @doc Markdown.doc""" - splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) - -> Vector{LatticeWithIsometry} + splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) -> Vector{LatWithIsom} Given a lattice with isometry $(L, f)$ with `f` of order $q^e$ for some prime number `q`, a prime number $p \neq q$ and an integer $b = 0, 1$, return a set of representatives @@ -584,8 +581,8 @@ Note that `e` can be 0. See Algorithm 5 of [BH22]. """ -function splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) - rank(Lf) == 0 && return LatticeWithIsometry[] +function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) + rank(Lf) == 0 && return LatWithIsom[] @req is_prime(p) "p must be a prime number" @req b in [0, 1] "b must be an integer equal to 0 or 1" @@ -595,7 +592,7 @@ function splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) @req ok || e == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" - reps = LatticeWithIsometry[] + reps = LatWithIsom[] if e == 0 reps = splitting_of_hermitian_prime_power(Lf, p) @@ -619,8 +616,8 @@ function splitting_of_prime_power(Lf::LatticeWithIsometry, p::Int, b::Int = 0) end @doc Markdown.doc""" - splitting_of_partial_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) - -> Vector{LatticeWithIsometry} + splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) + -> Vector{LatWithIsom} Given a lattice with isometry $(L, f)$ and a prime number `p`, such that the minimal polynomial of `f` divides $\prod_{i=0}^e\Phi_{p^dq^i}(f)$ for some @@ -632,8 +629,8 @@ Note that `e` can be 0, while `d` has to be positive. See Algorithm 6 of [BH22]. """ -function splitting_of_partial_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) - rank(Lf) == 0 && return LatticeWithIsometry[] +function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) + rank(Lf) == 0 && return LatWithIsom[] @req is_prime(p) "p must be a prime number" @req is_finite(order_of_isometry(Lf)) "Isometry must be of finite order" @@ -654,12 +651,11 @@ function splitting_of_partial_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) end phi = minpoly(Lf) - x = gen(parent(phi)) chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" - reps = LatticeWithIsometry[] + reps = LatWithIsom[] if e == 0 return splitting_of_hermitian_prime_power(Lf, p) @@ -681,8 +677,8 @@ function splitting_of_partial_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) end @doc Markdown.doc""" - splitting_of_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) - -> Vector{LatticeWithIsometry} + splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) + -> Vector{LatWithIsom} Given a lattice with isometry $(L, f)$ and a prime number `p` such that `f` is of order $p^dq^e$ for some prime number $q \neq p$, return a set @@ -694,8 +690,8 @@ Note that `d` and `e` can be both zero. See Algorithm 7 of [BH22]. """ -function splitting_of_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) - rank(Lf) == 0 && return LatticeWithIsometry[] +function splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) + rank(Lf) == 0 && return LatWithIsom[] n = order_of_isometry(Lf) @@ -717,7 +713,7 @@ function splitting_of_mixed_prime_power(Lf::LatticeWithIsometry, p::Int) e = 0 end - reps = LatticeWithIsometry[] + reps = LatWithIsom[] x = gen(parent(minpoly(Lf))) B0 = kernel_lattice(Lf, x^(divexact(n, p)) - 1) diff --git a/src/NumberTheory/LatticesWithIsometry/HMM.jl b/src/NumberTheory/LatWithIsom/HMM.jl similarity index 100% rename from src/NumberTheory/LatticesWithIsometry/HMM.jl rename to src/NumberTheory/LatWithIsom/HMM.jl diff --git a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl b/src/NumberTheory/LatWithIsom/LatWithIsom.jl similarity index 73% rename from src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl rename to src/NumberTheory/LatWithIsom/LatWithIsom.jl index c19d9192e4e1..a1b50ff20bc6 100644 --- a/src/NumberTheory/LatticesWithIsometry/LatticesWithIsometry.jl +++ b/src/NumberTheory/LatWithIsom/LatWithIsom.jl @@ -1,15 +1,15 @@ export ambient_isometry, - coinvariant_lattice, - hermitian_structure, - image_centralizer_in_Oq, - isometry, - is_of_hermitian_type, - is_of_same_type, - is_of_type, - is_hermitian, - lattice_with_isometry, - order_of_isometry, - type +export coinvariant_lattice, +export hermitian_structure, +export image_centralizer_in_Oq, +export isometry, +export is_of_hermitian_type, +export is_of_same_type, +export is_of_type, +export is_hermitian, +export lattice_with_isometry, +export order_of_isometry, +export type ############################################################################### # @@ -17,7 +17,7 @@ export ambient_isometry, # ############################################################################### -function Base.show(io::IO, Lf::LatticeWithIsometry) +function Base.show(io::IO, Lf::LatWithIsom) println(io, "Lattice of rank $(rank(Lf)) with isometry of order $(order_of_isometry(Lf))") println(io, "given by") print(IOContext(io, :compact => true), isometry(Lf)) @@ -30,34 +30,34 @@ end ############################################################################### @doc Markdown.doc""" - lattice(Lf::LatticeWithIsometry) -> ZLat + lattice(Lf::LatWithIsom) -> ZLat Given a lattice with isometry $(L, f)$, return the underlying lattice `L`. """ -lattice(Lf::LatticeWithIsometry) = Lf.Lb +lattice(Lf::LatWithIsom) = Lf.Lb @doc Markdown.doc""" - isometry(Lf::LatticeWithIsometry) -> fmpq_mat + isometry(Lf::LatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. """ -isometry(Lf::LatticeWithIsometry) = Lf.f +isometry(Lf::LatWithIsom) = Lf.f @doc Markdown.doc""" - ambient_isometry(Lf::LatticeWithIsometry) -> fmpq_mat + ambient_isometry(Lf::LatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return an isometry of underlying isometry of the ambient space of `L` inducing `f` on `L` """ -ambient_isometry(Lf::LatticeWithIsometry) = Lf.f_ambient +ambient_isometry(Lf::LatWithIsom) = Lf.f_ambient @doc Markdown.doc""" - order_of_isometry(Lf::LatticeWithIsometry) -> Integer + order_of_isometry(Lf::LatWithIsom) -> Integer Given a lattice with isometry $(L, f)$, return the order of the underlying isometry `f`. """ -order_of_isometry(Lf::LatticeWithIsometry) = Lf.n +order_of_isometry(Lf::LatWithIsom) = Lf.n ############################################################################### # @@ -66,66 +66,66 @@ order_of_isometry(Lf::LatticeWithIsometry) = Lf.n ############################################################################### @doc Markdown.doc""" - rank(Lf::LatticeWithIsometry) -> Integer + rank(Lf::LatWithIsom) -> Integer Given a lattice with isometry $(L, f)$, return the rank of the underlying lattice `L`. """ -rank(Lf::LatticeWithIsometry) = rank(lattice(Lf))::Integer +rank(Lf::LatWithIsom) = rank(lattice(Lf))::Integer @doc Markdown.doc""" - charpoly(Lf::LatticeWithIsometry) -> fmpq_poly + charpoly(Lf::LatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the characteristic polynomial of the underlying isometry `f`. """ -charpoly(Lf::LatticeWithIsometry) = charpoly(isometry(Lf))::fmpq_poly +charpoly(Lf::LatWithIsom) = charpoly(isometry(Lf))::QQPolyRingElem @doc Markdown.doc""" - minpoly(Lf::LatticeWithIsometry) -> fmpq_poly + minpoly(Lf::LatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the minimal polynomial of the underlying isometry `f`. """ -minpoly(Lf::LatticeWithIsometry) = minpoly(isometry(Lf))::fmpq_poly +minpoly(Lf::LatWithIsom) = minpoly(isometry(Lf))::QQPolyRingElem @doc Markdown.doc""" - genus(Lf::LatticeWithIsometry) -> ZGenus + genus(Lf::LatWithIsom) -> ZGenus Given a lattice with isometry $(L, f)$, return the genus of the underlying lattice `L`. """ -genus(Lf::LatticeWithIsometry) = genus(lattice(Lf))::ZGenus +genus(Lf::LatWithIsom) = genus(lattice(Lf))::ZGenus @doc Markdown.doc""" - ambient_space(Lf::LatticeWithIsometry) -> QuadSpace + ambient_space(Lf::LatWithIsom) -> QuadSpace Given a lattice with isometry $(L, f)$, return the ambient space of the underlying lattice `L`. """ -ambient_space(Lf::LatticeWithIsometry) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, fmpq_mat} +ambient_space(Lf::LatWithIsom) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} -basis_matrix(Lf::LatticeWithIsometry) = basis_matrix(lattice(Lf))::fmpq_mat +basis_matrix(Lf::LatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix -gram_matrix(Lf::LatticeWithIsometry) = gram_matrix(lattice(Lf))::fmpq_mat +gram_matrix(Lf::LatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix -rational_span(Lf::LatticeWithIsometry) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, fmpq_mat} +rational_span(Lf::LatWithIsom) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} -det(Lf::LatticeWithIsometry) = det(lattice(Lf))::fmpq +det(Lf::LatWithIsom) = det(lattice(Lf))::QQRingElem -scale(Lf::LatticeWithIsometry) = det(lattice(Lf))::fmpq +scale(Lf::LatWithIsom) = det(lattice(Lf))::QQRingElem -norm(Lf::LatticeWithIsometry) = norm(lattice(Lf))::fmpq +norm(Lf::LatWithIsom) = norm(lattice(Lf))::QQRingElem -is_integral(Lf::LatticeWithIsometry) = is_integral(lattice(Lf))::Bool +is_integral(Lf::LatWithIsom) = is_integral(lattice(Lf))::Bool -degree(Lf::LatticeWithIsometry) = degree(lattice(Lf))::Int +degree(Lf::LatWithIsom) = degree(lattice(Lf))::Int -is_even(Lf::LatticeWithIsometry) = is_even(lattice(Lf))::Int +is_even(Lf::LatWithIsom) = is_even(lattice(Lf))::Int -discriminant(Lf::LatticeWithIsometry) = discriminant(lattice(Lf))::fmpq +discriminant(Lf::LatWithIsom) = discriminant(lattice(Lf))::QQRingElem -signature_tuple(Lf::LatticeWithIsometry) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} +signature_tuple(Lf::LatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} ############################################################################### @@ -135,9 +135,9 @@ signature_tuple(Lf::LatticeWithIsometry) = signature_tuple(lattice(Lf))::Tuple{I ############################################################################### @doc Markdown.doc""" - lattice_with_isometry(L::ZLat, f::fmpq_mat, n::IntExt; check::Bool = true, - ambient_representation = true) - -> LatticeWithIsometry + lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = true, + ambient_representation = true) + -> LatWithIsom Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry of `L` of order `n`, return the corresponding lattice with isometry pair $(L, f)$. @@ -147,10 +147,10 @@ ambient space of `L` and the induced isometry on `L` is automatically computed. Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::IntExt; check::Bool = true, +function lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = true, ambient_representation::Bool = true) if rank(L) == 0 - return LatticewithIsometry(L, matrix(QQ,0,0,[]), -1) + return LatWithIsom(L, matrix(QQ,0,0,[]), -1) end if check @@ -179,13 +179,13 @@ function lattice_with_isometry(L::ZLat, f::fmpq_mat, n::IntExt; check::Bool = tr @assert basis_matrix(L)*f_ambient == f*basis_matrix(L) end - return LatticeWithIsometry(L, f, f_ambient, n)::LatticeWithIsometry + return LatWithIsom(L, f, f_ambient, n)::LatWithIsom end @doc Markdown.doc""" - lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true, + lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, ambient_representation::Bool, = true) - -> LatticeWithIsometry + -> LatWithIsom Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry of `L`, return the corresponding lattice with isometry pair $(L, f)$. @@ -195,19 +195,19 @@ ambient space of `L` and the induced isometry on `L` is automatically computed. Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZLat, f::fmpq_mat; check::Bool = true, +function lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, ambient_representation::Bool = true) if rank(L) == 0 - return LatticeWithIsometry(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) + return LatWithIsom(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) end n = multiplicative_order(f) return lattice_with_isometry(L, f, n, check = check, - ambient_representation = ambient_representation)::LatticeWithIsometry + ambient_representation = ambient_representation)::LatWithIsom end @doc Markdown.doc""" - lattice_with_isometry(L::ZLat) -> LatticeWithIsometry + lattice_with_isometry(L::ZLat) -> LatWithIsom Given a $\mathbb Z$-lattice `L` return the lattice with isometry pair $(L, f)$, where `f` corresponds to the identity mapping of `L`. @@ -215,7 +215,7 @@ where `f` corresponds to the identity mapping of `L`. function lattice_with_isometry(L::ZLat) d = degree(L) f = identity_matrix(QQ, d) - return lattice_with_isometry(L, f, check = false, ambient_representation = true)::LatticeWithIsometry + return lattice_with_isometry(L, f, check = false, ambient_representation = true)::LatWithIsom end ############################################################################### @@ -224,11 +224,11 @@ end # ############################################################################### -function rescale(Lf::LatticeWithIsometry, a::Hecke.RationalUnion) +function rescale(Lf::LatWithIsom, a::Hecke.RationalUnion) return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), order_of_isometry(Lf), check=false) end -function dual(Lf::LatticeWithIsometry) +function dual(Lf::LatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), order_of_isometry(Lf), check = false) end @@ -239,7 +239,7 @@ end # ############################################################################### -function is_of_hermitian_type(Lf::LatticeWithIsometry) +function is_of_hermitian_type(Lf::LatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" n = order_of_isometry(Lf) if n == -1 || !is_finite(n) @@ -249,7 +249,7 @@ function is_of_hermitian_type(Lf::LatticeWithIsometry) end @doc Markdown.doc""" - hermitian_structure(Lf::LatticeWithIsometry; check::Bool = true) -> HermLat + hermitian_structure(Lf::LatWithIsom; check::Bool = true) -> HermLat Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the underlying isometry `f` is irreducible and cyclotomic, return the @@ -258,7 +258,7 @@ field, where $n$ is the order of `f`. If it exists, the hermitian structure is cached. """ -@attr HermLat function hermitian_structure(Lf::LatticeWithIsometry) +@attr HermLat function hermitian_structure(Lf::LatWithIsom) @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" f = isometry(Lf) n = order_of_isometry(Lf) @@ -274,30 +274,30 @@ end ############################################################################### @doc Markdown.doc""" - discriminant_group(Lf::LatticeWithIsometry) -> TorQuadMod, AutomorphismGroupElem + discriminant_group(Lf::LatWithIsom) -> TorQuadMod, AutomorphismGroupElem Given an integral lattice with isometry $(L, f)$, return the discriminant group `q` of the underlying lattice `L` as well as this image of the underlying isometry `f` inside $O(q)$. """ -function discriminant_group(Lf::LatticeWithIsometry) +function discriminant_group(Lf::LatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" L = lattice(Lf) f = ambient_isometry(Lf) q = discriminant_group(L) Oq = orthogonal_group(q) - return (q, Oq(gens(matrix_group(f))[1], check = false))::Tuple{TorQuadMod, AutomorphismGroupElem{TorQuadMod}} + return (q, Oq(gens(matrix_group(f))[1], check = false))::Tuple{TorQuadModule, AutomorphismGroupElem{TorQuadModule}} end @doc Markdown.doc""" - image_centralizer_in_Oq(Lf::LatticeWithIsometry) -> AutomorphismGroup + image_centralizer_in_Oq(Lf::LatWithIsom) -> AutomorphismGroup{TorQuadModule} Given an integral lattice with isometry $(L, f)$, return the image $G_L$ in $O(q_L, \bar{f})$ of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ denotes the discriminant group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. """ -@attr AutomorphismGroup{TorQuadMod} function image_centralizer_in_Oq(Lf::LatticeWithIsometry) +@attr AutomorphismGroup{TorQuadModule} function image_centralizer_in_Oq(Lf::LatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" n = order_of_isometry(Lf) L = lattice(Lf) @@ -307,14 +307,14 @@ $q_L$ induced by `f`. elseif is_definite(L) OL = orthogonal_group(L) f = OL(f) - UL = fmpq_mat[matrix(OL(s)) for s in gens(centralizer(OL, f)[1])] + UL = QQMatrix[matrix(OL(s)) for s in gens(centralizer(OL, f)[1])] qL = discriminant_group(L) - UL = fmpz_mat[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in UL] + UL = ZZMatrix[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in UL] unique!(UL) GL = Oscar._orthogonal_group(qL, UL, check = false) elseif rank(L) == euler_phi(n) qL = discriminant_group(L) - UL = fmpz_mat[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in [-f^0, f]] + UL = ZZMatrix[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in [-f^0, f]] unique!(UL) GL = Oscar._orthogonal_group(qL, UL, check = false) else @@ -324,7 +324,7 @@ $q_L$ induced by `f`. CdL, _ = centralizer(OqL, fqL) GL, _ = sub(OqL, unique([OqL(s.X) for s in CdL])) end - return GL::AutomorphismGroup{TorQuadMod} + return GL::AutomorphismGroup{TorQuadModule} end ############################################################################### @@ -355,7 +355,7 @@ function _real_kernel_signatures(L::ZLat, M) end @doc Markdown.doc""" - signatures(Lf::LatticeWithIsometry) -> Dict{Int, Tuple{Int, Int}} + signatures(Lf::LatWithIsom) -> Dict{Int, Tuple{Int, Int}} Given a lattice with isometry $(L, f)$ where the minimal polynomial of `f` is irreducible cyclotomic, return the signatures of $(L, f)$. @@ -365,7 +365,7 @@ is the order of `f`, then for each $1 \leq i \leq n/2$ such that $(i, n) = 1$, the $i$-th signature of $(L, f)$ is given by the signatures of the real quadratic form $\Ker(f + f^{-1} - z^i - z^{-i})$. """ -function signatures(Lf::LatticeWithIsometry) +function signatures(Lf::LatWithIsom) @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" L = lattice(Lf) f = isometry(Lf) @@ -393,16 +393,16 @@ end divides(k::PosInf, n::Int) = true @doc Markdown.doc""" - kernel_lattice(Lf::LatticeWithIsometry, p::Union{fmpz_poly, fmpq_poly}) - -> LatticeWithIsometry + kernel_lattice(Lf::LatWithIsom, p::Union{fmpz_poly, QQPolyRingElem}) + -> LatWithIsom Given a lattice with isometry $(L, f)$ and a polynomial `p` with rational coefficients, return the sublattice $M := \ker(p(f))$ of the underlying lattice `L` with isometry `f`, together with the restriction $f_{\mid M}$. """ -kernel_lattice(Lf::LatticeWithIsometry, p::Union{fmpz_poly, fmpq_poly}) +kernel_lattice(Lf::LatWithIsom, p::Union{ZZPolyRingElem, QQPolyRingElem}) -function kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) +function kernel_lattice(Lf::LatWithIsom, p::QQPolyRingElem) n = order_of_isometry(Lf) L = lattice(Lf) f = isometry(Lf) @@ -419,31 +419,31 @@ function kernel_lattice(Lf::LatticeWithIsometry, p::fmpq_poly) return lattice_with_isometry(L2, f2, ambient_representation = false) end -kernel_lattice(Lf::LatticeWithIsometry, p::fmpz_poly) = kernel_lattice(Lf, change_base_ring(QQ, p)) +kernel_lattice(Lf::LatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change_base_ring(QQ, p)) @doc Markdown.doc""" - kernel_lattice(Lf::LatticeWithIsometry, l::Integer) -> LatticeWithIsometry + kernel_lattice(Lf::LatWithIsom, l::Integer) -> LatWithIsom Given a lattice with isometry $(L, f)$ and an integer `l`, return the kernel lattice of $(L, f)$ associated to the `l`-th cyclotomic polynomial. """ -function kernel_lattice(Lf::LatticeWithIsometry, l::Integer) +function kernel_lattice(Lf::LatWithIsom, l::Integer) @req divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" p = cyclotomic_polynomial(l) return kernel_lattice(Lf, p) end @doc Markdown.doc""" - invariant_lattice(Lf::LatticeWithIsometry) -> LatticeWithIsometry + invariant_lattice(Lf::LatWithIsom) -> LatWithIsom Given a lattice with isometry $(L, f)$, return the invariant lattice $L^f$ of $(L, f)$ together with the restriction of `f` to $L^f$ (which is the identity in this case) """ -invariant_lattice(Lf::LatticeWithIsometry) = kernel_lattice(Lf, 1) +invariant_lattice(Lf::LatWithIsom) = kernel_lattice(Lf, 1) @doc Markdown.doc""" - coinvariant_lattice(Lf::LatticeWithIsometry) -> LatticeWithIsometry + coinvariant_lattice(Lf::LatWithIsom) -> LatWithIsom Given a lattice with isometry $(L, f)$, return the coinvariant lattice $L_f$ of $(L, f)$ together with the restriction of `f` to $L_f$. @@ -451,7 +451,7 @@ $(L, f)$ together with the restriction of `f` to $L_f$. The coinvariant lattice $L_f$ of $(L, f)$ is the orthogonal complement in `L` of the invariant lattice $L_f$. """ -function coinvariant_lattice(Lf::LatticeWithIsometry) +function coinvariant_lattice(Lf::LatWithIsom) chi = minpoly(Lf) if chi(1) == 0 R = parent(chi) @@ -468,8 +468,8 @@ end ############################################################################### @doc Markdown.doc""" - type(Lf::LatticeWithIsometry) - -> Dict{Int, Tuple{ <: Union{ZGenus, GenusHerm}, ZGenus}} + type(Lf::LatWithIsom) + -> Dict{Int, Tuple{ <: Union{ZGenus, HermGenus}, ZGenus}} Given a lattice with isometry $(L, f)$ with `f` of finite order `n`, return the type of $(L, f)$. @@ -480,7 +480,7 @@ $H_k$ of the lattice $\Ker(\Phi_k(f))$ viewed as a hermitian $\mathbb{Z}[\zeta_k lattice (so a $\mathbb{Z}$-lattice for k= 1, 2) and of the genus $A_k$ of the $\mathbb{Z}$-lattice $\Ker(f^k-1)$. """ -@attr function type(Lf::LatticeWithIsometry) +@attr function type(Lf::LatWithIsom) L = lattice(Lf) f = isometry(Lf) n = order_of_isometry(Lf) @@ -501,18 +501,18 @@ $\mathbb{Z}$-lattice $\Ker(f^k-1)$. end @doc Markdown.doc""" - is_of_type(Lf::LatticeWithIsometry, t::Dict) -> Bool + is_of_type(Lf::LatWithIsom, t::Dict) -> Bool Given a lattice with isometry $(L, f)$, return whether $(L, f)$ is of type `t`. """ -function is_of_type(L::LatticeWithIsometry, t::Dict) +function is_of_type(L::LatWithIsom, t::Dict) @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" divs = sort(collect(keys(t))) x = gen(Hecke.Globals.Qx) for l in divs Hl = kernel_lattice(L, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1, 1, 2]) - t[l][1] isa Hecke.GenusHerm || return false + t[l][1] isa Hecke.HermGenus || return false Hl = Oscar._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false, E = base_field(t[l][1])) end genus(Hl) == t[l][1] || return false @@ -523,12 +523,12 @@ function is_of_type(L::LatticeWithIsometry, t::Dict) end @doc Markdown.doc""" - is_of_same_type(Lf::LatticeWithIsometry, Mg::LatticeWithIsometry) -> Bool + is_of_same_type(Lf::LatWithIsom, Mg::LatWithIsom) -> Bool Given two lattices with isometry $(L, f)$ and $(M, g)$, return whether they are of the same type. """ -function is_of_same_type(L::LatticeWithIsometry, M::LatticeWithIsometry) +function is_of_same_type(L::LatWithIsom, M::LatWithIsom) @req is_finite(order_of_isometry(L)*order_of_isometry(M)) "Type is defined only for finite order isometries" order_of_isometry(L) != order_of_isometry(M) && return false genus(L) != genus(M) && return false diff --git a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl b/src/NumberTheory/LatWithIsom/TraceEquivalence.jl similarity index 87% rename from src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl rename to src/NumberTheory/LatWithIsom/TraceEquivalence.jl index dab7e057f008..ce2664b656b7 100644 --- a/src/NumberTheory/LatticesWithIsometry/TraceEquivalence.jl +++ b/src/NumberTheory/LatWithIsom/TraceEquivalence.jl @@ -1,9 +1,9 @@ export trace_lattice @doc Markdown.doc""" - trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = gen(base_field(L)), - order::Integer = 2) where T -> LatticeWithIsometry + trace_lattice(L::AbstractLat{T}; alpha::FieldElem = one(base_field(L)), + beta::FieldElem = gen(base_field(L)), + order::Integer = 2) where T -> LatWithIsom Given a lattice `L` which is either a $\mathbb Z$-lattice or a hermitian lattice over the $E/K$ with `E` a cyclotomic field and `K` a maximal real subfield of @@ -29,16 +29,16 @@ normalisation is automatically used by default. Note that the isometry `f` computed is given by its action on the ambient space of the trace lattice of `L`. """ -function trace_lattice(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = one(base_field(L)), - order::Integer = 2) where T +function trace_lattice(L::Hecke.AbstractLat{T}; alpha::FieldElem = one(base_field(L)), + beta::FieldElem = one(base_field(L)), + order::Integer = 2) where T return trace_lattice_with_map(L, alpha=alpha, beta=beta, order=order)[1] end -function trace_lattice_with_map(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = gen(base_field(L)), - order::Integer = 2) where T +function trace_lattice_with_map(L::Hecke.AbstractLat{T}; alpha::FieldElem = one(base_field(L)), + beta::FieldElem = gen(base_field(L)), + order::Integer = 2) where T E = base_field(L) @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @req order > 0 "The order must be positive" @@ -81,7 +81,7 @@ function trace_lattice_with_map(L::Hecke.AbsLat{T}; alpha::FieldElem = one(base_ return lattice_with_isometry(Lres, iso, ambient_representation=true), f end -function trace_lattice(L::Hecke.AbsLat{T}, f::SpaceRes; beta::FieldElem = gen(base_field(L))) where T +function trace_lattice(L::Hecke.AbstractLat{T}, f::SpaceRes; beta::FieldElem = gen(base_field(L))) where T @req codomain(f) === ambient_space(L) "f must be a map of restriction of scalars associated to the ambient space of L" E = base_field(L) @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @@ -125,7 +125,7 @@ function absolute_representation_matrix(b::Hecke.NfRelElem) return m end -function _hermitian_structure(L::ZLat, f::fmpq_mat; E = nothing, +function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, n::Integer = -1, check::Bool = true, ambient_representation::Bool = true) diff --git a/src/NumberTheory/LatWithIsom/Types.jl b/src/NumberTheory/LatWithIsom/Types.jl new file mode 100644 index 000000000000..d9b1f914a262 --- /dev/null +++ b/src/NumberTheory/LatWithIsom/Types.jl @@ -0,0 +1,31 @@ +export LatWithIsom + +@doc Markdown.doc""" + LatWithIsom + +A container type for a pair `(L, f)` consisting on an integer lattice `L` of +type `ZLat` and an isometry `f` given as a `QQMatrix` representing the action +on a given basis of `L`. + +The associated action `f_ambient` on the ambient space of `L` as well as the order +`n` of `f` are stored in this container type. + +To construct an object of type `LatWithIsom`, see the set of functions called +[`lattice_with_isometry`](@ref) +""" +@attributes mutable struct LatWithIsom + Lb::ZLat + f::QQMatrix + f_ambient::QQMatrix + n::IntExt + + function LatWithIsom(Lb::ZLat, f::QQMatrix, f_ambient::QQMatrix, n::IntExt) + z = new() + z.Lb = Lb + z.f = f + z.f_ambient = f_ambient + z.n = n + return z + end +end + diff --git a/src/NumberTheory/LatticesWithIsometry/Types.jl b/src/NumberTheory/LatticesWithIsometry/Types.jl deleted file mode 100644 index a7472067667e..000000000000 --- a/src/NumberTheory/LatticesWithIsometry/Types.jl +++ /dev/null @@ -1,18 +0,0 @@ -export LatticeWithIsometry, LWIType - -@attributes mutable struct LatticeWithIsometry - Lb::ZLat - f::fmpq_mat - f_ambient::fmpq_mat - n::IntExt - - function LatticeWithIsometry(Lb::ZLat, f::fmpq_mat, f_ambient::fmpq_mat, n::IntExt) - z = new() - z.Lb = Lb - z.f = f - z.f_ambient = f_ambient - z.n = n - return z - end -end - From 58a72146b24a806f2aa0d2dcaa5aa69be22fd730 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 27 Feb 2023 16:43:38 +0100 Subject: [PATCH 32/76] more on hmm --- src/NumberTheory/LatWithIsom/HMM.jl | 145 +++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 14 deletions(-) diff --git a/src/NumberTheory/LatWithIsom/HMM.jl b/src/NumberTheory/LatWithIsom/HMM.jl index eba65f8157d5..01ad2c0ed888 100644 --- a/src/NumberTheory/LatWithIsom/HMM.jl +++ b/src/NumberTheory/LatWithIsom/HMM.jl @@ -9,9 +9,25 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) URPabs, mURPabs = unit_group(RPabs) function dlog(x::Hecke.NfRelElem) - @assert parent(x) === E - @assert is_integral(x) - return muRPabs\(mRPabs(OEabs(EabstoE\x))) + @assert parent(x) == E + d = denominator(x) + xabs = OEabs(d*EabstoE\(x)) + dabs = OEabs(d) + F = prime_decomposition(OEabs, EabstoE\(minimum(P))) + for PP in F + @assert valuation(EasbtoE\(x), PP[1]) >= 0 + api = OEabs(anti_uniformizer(PP[1])) + exp = valuation(OEabs(d), PP[1]) + dabs *= api^exp + xabs *= api^exp + end + + xabs_image = mURPabs\(mRPabs(xabs)) + dabs_image = mURPabs\(mRPabs(dabs)) + + ret = xabs_image - dabs_image + + return ret end function exp(k::GrpAbFinGenElem) @@ -52,8 +68,24 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) function dlog(x::Hecke.NfRelElem) @assert parent(x) === E - @assert is_integral(x) - return mS\(mK\(mURPabs\(mRPabs(OEabs(EabstoE\x))))) + d = denominator(x) + xabs = OEabs(EabstoE\(d*x)) + dabs = OEabs(d) + F = prime_decomposition(OEabs, EabstoE\(minimum(P))) + for PP in F + @assert valuation(EabstoE\(x), PP[1]) >= 0 + api = OEabs(anti_uniformizer(PP[1])) + exp = valuation(OEabs(d), PP[1]) + dabs *= api^exp + xabs *= api^exp + end + + xabs_image = mURPabs\(mRPabs(xabs)) + dabs_image = mURPabs\(mRPabs(dabs)) + + ret = mS\(mK\(xabs_image - dabs_image)) + + return ret end return S, exp, dlog @@ -107,18 +139,34 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) function dlog(x::Hecke.NfRelElem) @assert parent(x) === E - @assert is_integral(x) - return mS\(mK\(mURPabs\(mRPabs(OEabs(EabstoE\x))))) + d = denomiator(x) + xabs = OEabs(EasbtoE\(d*x)) + dabs = OEabs(d) + F = prime_decomposition(OEabs, EabstoE\(minimum(P))) + for PP in F + @assert valuation(EabstoE\(x), PP[1]) >= 0 + api = OEabs(anti_uniformizer(PP[1])) + exp = valuation(OEabs(d), PP[1]) + dabs *= api^exp + xabs *= api^exp + end + + xabs_image = mURPabs\(mRPabs(xabs)) + dabs_image = mURPabs\(mRPabs(dabs)) + + ret = mS\(mK\ (xabs_image - dabs_image)) + return ret end return S, exp, dlog end -function _get_quotient(P::Hecke.NfRelOrdIdl, i::Int) - @assert is_prime(P) - @assert is_maximal(order(P)) - OE = order(P) - F = prime_decomposition(OE, minimum(P)) +function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) + @assert is_prime(p) + @assert is_maximal(order(p)) + @assert order(p) === base_ring(O) + F = prime_decomposition(O, p) + P = F[1][1] if length(F) == 2 S, dexp, dlog = _get_quotient_split(P, i) elseif F[1][2] == 1 @@ -126,8 +174,77 @@ function _get_quotient(P::Hecke.NfRelOrdIdl, i::Int) else S, dexp, dlog = _get_quotient_ramified(P, i) end - return S, dexp, dlog + return S, dexp, dlog, P +end + +function _get_big_quotient(E::Hecke.NfRel, Fac::Vector{Tuple{Hecke.NfOrdIdl, Int}}) + OE = maximal_order(e) + groups = [] + exps = [] + dlogs = [] + Ps = [] + + if length(Fac) == 0 + A = abelian_group() + function dlog(x::Hecke.NfRelElem); return one(A); end; + function exp(x::GrpAbFinGenElem); return one(E); end; + return A, dlog, exp + end + + for i in 1:length(Fac) + p, e = Fac[i] + KK, f, g, P = _get_quotient(OE, p, e) + push!(groups, KK) + push!(exps, f) + push!(dlogs, g) + push!(Ps, P) + end + + if length(groups) == 1 + return groups[1], dlogs[1], exps[1] + end + + G, inj, proj = biproduct(groups) + + function dlog(x::Hecke.NfRelElem) + return sum([inj[i](dlogs[i](x)) for i in 1:length(Fac)]) + end + + function exp(x::GrpAbFinGenElem) + v = Hecke.NfRelOrdElem[OE(exps[i](proj[i](x))) for i in 1:length(Fac)] + a = crt(v, [Ps[i]^(3*Fac[i][2]) for i in 1:length(Ps)]) + @assert dlog(a) == x + return a + end + + return G, dlog, exp end -#function _get_big_quotient(E::) +function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) + OE = base_ring(L) + E = nf(OE) + lp = prime_decomposition(OE, p) + if lp[1][2] != 2 || !is_even(rank(L)) + return false + end + _, _R, S = jordan_decomposition(L, p) + R = [nrows(m) for m in _R] + for r in R + if r != 2 + return false + end + end + P = lp[1][1] + e = valuation(different(OE), P) + for i in 1:length(S) + if !iseven(e- S[i]) + return false + end + end + u = 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)]) + return is_locally_isometric(L, hermitian_lattice(E, gram = H), p) +end From 89b5fc4dacee97a5412a06dc277ecf1fa5861e86 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 6 Mar 2023 11:05:40 +0100 Subject: [PATCH 33/76] herm mir-mor + more features+docs --- src/NumberTheory/LatWithIsom/HMM.jl | 205 +++++++++++++++++- src/NumberTheory/LatWithIsom/LatWithIsom.jl | 120 +++++++++- .../LatWithIsom/TraceEquivalence.jl | 75 ++++--- 3 files changed, 355 insertions(+), 45 deletions(-) diff --git a/src/NumberTheory/LatWithIsom/HMM.jl b/src/NumberTheory/LatWithIsom/HMM.jl index 01ad2c0ed888..ed2869ac5ac7 100644 --- a/src/NumberTheory/LatWithIsom/HMM.jl +++ b/src/NumberTheory/LatWithIsom/HMM.jl @@ -1,7 +1,15 @@ + + +############################################################################### +# +# Local determinants morphism +# +############################################################################### + function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) OE = order(P) E = nf(OE) - Eabs, EabstoE = Hecke.absolute_simple_field(E, cached = false) + Eabs, EabstoE = Hecke.absolute_simple_field(E) Pabs = EabstoE\P OEabs = order(Pabs) @@ -47,7 +55,7 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) K = base_field(E) p = minimum(P) - Eabs, EabstoE = Hecke.absolute_simple_field(E, cached=false) + Eabs, EabstoE = Hecke.absolute_simple_field(E) Pabs = EabstoE\P OEabs = order(Pabs) Rp, mRp = quo(OK, p^i) @@ -116,7 +124,7 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) Pi = P^i - Eabs, EabstoE = Hecke.absolute_simple_field(E, cached=false) + Eabs, EabstoE = Hecke.absolute_simple_field(E) OK = order(p) Rp, mRp = quo(OK, p^j) URp, mURp = unit_group(Rp) @@ -177,8 +185,7 @@ function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) return S, dexp, dlog, P end -function _get_big_quotient(E::Hecke.NfRel, Fac::Vector{Tuple{Hecke.NfOrdIdl, Int}}) - OE = maximal_order(e) +function _get_product_quotient(E::Hecke.NfRel, Fac) groups = [] exps = [] dlogs = [] @@ -206,7 +213,7 @@ function _get_big_quotient(E::Hecke.NfRel, Fac::Vector{Tuple{Hecke.NfOrdIdl, Int G, inj, proj = biproduct(groups) - function dlog(x::Hecke.NfRelElem) + function dlog(x::Vector{Hecke.NfRelElem}) return sum([inj[i](dlogs[i](x)) for i in 1:length(Fac)]) end @@ -220,6 +227,72 @@ function _get_big_quotient(E::Hecke.NfRel, Fac::Vector{Tuple{Hecke.NfOrdIdl, Int return G, dlog, exp end +function _local_determinant_morphism(Lf::LatWithIsom) + @assert is_of_hermitian_type(Lf) + qL, fqL = discriminant_group(Lf) + OqL = orthogonal_group(qL) + G, _ = centralizer(OqL, fqL) + H = hermitian_structure(Lf) + l = get_attribute(Lf, :transfert_data) + E = base_ring(H) + OE = maximal_order(E) + DKQ = different(base_ring(OE))*OE + DEK = different(OE) + DEQ = DEK*DKQ + DinvH = inv(DEQ)*H + res = Hecke.SpaceRes(ambient_space(Lf), ambient_space(H)) + Lv = trace_lattice(DinvH, res, l = l) + @assert cover(q) === lattice(Lv) + @assert ambient_isometry(Lv) == ambient_isometry(Lf) + gene_herm = [_transfer_discriminant_isometries(res, g) for g in gens(G)] + @assert all(m -> m*gram_matrix_of_rational_span(DinvH)*map_entries(involution(E), transpose(m)) == gram_matrix_of_rational_span(DinvH), gene_herm) + + S = elementary_divisors(DinvH, H) + + N = norm(L) + + Fsharpdata = Tuple{NfOrdIdl, Int}[] + for p in S + lp = prime_decomposition(OE, p) + P = lp[1][1] + k = valuation(N, p) + a = valuation(DEQ, P) + push!(Fsharpdata, (p, 2*k+a)) + end + + RmodFsharp, Fsharplog, Fsharpexp = _get_product_quotient(E, Fsharpdata) + + Fdata = Tuple{NfOrdIdl, Int}[] + for p in S + if !_is_special(H, p) + continue + end + lp = prime_decomposition(OE, p) + P = lp[1][1] + e = valuation(DEK, P) + push!(Fdata, (p, e)) + end + + RmodF, Flog, Fexp = _get_product_quotient(E, Fdata) + + A = [Flog(Fsharpexp(g)) for g in gens(RmodFsharp)] + f = hom(gens(RmodFsharp), A) + FmodFsharp, j = kernel(f) + + Eabs, EabstoE = Hecke.absolute_simple_field(E) + OEabs = maximal_order(Eabs) + UOEabs, mUOEabs = unit_group(OEabs) + UOK, mUOK = unit_group(base_ring(OE)) + + fU = hom(UOEabs, UOK, [mUOK\(norm(OE(EabstoE(Eabs(mUOEabs(k)))))) for k in gens(UOEabs)]) + KU, jU = kernel(fU) + + gene_norm_one = NfRelOrdElem[OE(EabstoE(Eabs(mUOEabs(jU(k))))) for k in gens(KU)] + + FOEmodFsharp, m = sub(RmodFsharp, ) + +end + function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) OE = base_ring(L) E = nf(OE) @@ -248,3 +321,123 @@ function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) return is_locally_isometric(L, hermitian_lattice(E, gram = H), p) end + +############################################################################### +# +# Local hermitian lifting +# +############################################################################### + +function _transfer_discriminant_isometries(res::Hecke.SpaceRes, g::AutomorphismGroupElem{TorQuadModule}) + E = base_ring(codomain(res)) + OE = maximal_order(OE) + q = domain(g) + @assert ambient_space(cover(q)) === domain(res) + gE = zero_matrix(OE, 0, degree(H)) + vE = zeros(E, 1, degree(H)) + for i in 1:degree(H) + vE[i] = one(E) + vQ = res\vE + vq = q(vQ) + gvq = g(vq) + gvQ = lift(gvq) + gvE = res(gvQ) + gE = vcat(gE, gvE) + vE[i] = zero(E) + end + return gE +end + +function _get_piecewise_actions_modular(H::Hecke.HermLat, g::MatrixSpaceElem{NfRelOrdElem}, p::NfOrdIdl, a::Int) + @assert g*gram_matrix_of_rational_span(H)*map_entries(involution(H), transpose(g)) == gram_matrix_of_rational_span(H) + E = base_field(H) + OE = base_ring(H) + @assert base_ring(g) === OE + gE = map_entries(E, g) + B, _, exp = jordan_decomposition(H, p) + j = findfirst(i -> i == -a, exp) + if j !== nothing + popat!(B, j) + popat!(exp, j) + end + local_act = typeof(g)[] + for b in B + gbE = solve(b, b*gE) + gb = map_entries(OE, gb) + push!(local_act, gb) + end + return exp, local_act +end + +function _local_hermitian_lifting(G::MatrixSpaceElem{NfRelElem}, F::MatrixSpaceElem{NFRelOrdElem}, rho::NfRelElem, l::Int, P::NfRelOrdIdl; check = true) + @assert trace(rho) == 1 + E = base_ring(G) + @assert G == map_entries(involution(E), transpose(G)) + OE = base_ring(F) + @assert nf(OE) === E + OK = base_ring(OE) + DEK = different(OE) + DKQ = different(OK)*OE + DEQ = DKQ*DEK + e = valuation(rho, P) + e = 1 - e + @assert valuation(DEK, P) == e + a = valuation(DEQ, P) + FE = map_entries(E, F) + RE = G - FE*G*map_entries(involution(E), transpose(FE)) + + if check + @assert min([valuation(v, P) for v in collect(inv(G)) if !iszero(v)]) >= l+a + @assert min([valuation(v, P) for v in diagonal(inv(G)) if !iszero(v)]) >= e=a + @assert min([valuation(v, P) for v in collect(R) if !iszero(v)]) >= l-a + @assert min([valuation(v, P) for v in diagonal(R) if !iszero(v)]) >= l+e-1-a + end + + UE = zero_matrix(E, nrows(RE), ncols(RE)) + for i in 1:nrows(RE) + for j in 1:i-1 + UE[i, j] = RE[i, j] + end + end + + diagE = RE - UE - map_entries(involution(E), transpose(UE)) + + newFE = FE + (UE + rho*diagE)*map_entries(involution(E), inv(transpose(FE)))*inv(G) + newF = map_entries(OE, newFE) + + if check + l2 = 2*l+1 + @assert min([valuation(v, P) for v in collect(F-newF) if !is_zero(v)]) >= l+1 + R2E = G-newFE*G*map_entries(involution(E), transpose(newFE)) + @assert min([valuation(v, P) for v in collect(R2E) if !iszero(v)]) >= l2-a + @assert min([valuation(v, P) for v in diagonal(R2e) if !iszero(v)]) >= l2+e-1-a + end + + return newF +end + +function _find_rho(P::NfRelOrdIdl) + OE = order(P) + E = nf(OE) + dya = valuation(2, norm(P)) > 0 + !dya && return E(1//2) + K = base_field(E) + while true + Et, t = E["t"] + g = E(-rand(K, -5:5)^2-1) + nu = 2*valuation(E(2), P)-2 + nug = valuation(g, P) + if nu == nug + d = denominator(g+1, OE) + rt = roots(t^2 - (g+1)*d^2, max_roots = 1, ispure = true, is_normal=true) + if !is_empty(rt) + break + end + end + end + rho = (1+rt[1])//2 + @assert valuation(rho, P) == -1 + @assert trace(rho) == 1 + return rho +end + diff --git a/src/NumberTheory/LatWithIsom/LatWithIsom.jl b/src/NumberTheory/LatWithIsom/LatWithIsom.jl index a1b50ff20bc6..b1886d4e948f 100644 --- a/src/NumberTheory/LatWithIsom/LatWithIsom.jl +++ b/src/NumberTheory/LatWithIsom/LatWithIsom.jl @@ -93,7 +93,7 @@ minpoly(Lf::LatWithIsom) = minpoly(isometry(Lf))::QQPolyRingElem genus(Lf::LatWithIsom) -> ZGenus Given a lattice with isometry $(L, f)$, return the genus of the underlying -lattice `L`. +lattice `L` (see [`genus(::ZLat)`](@ref)). """ genus(Lf::LatWithIsom) = genus(lattice(Lf))::ZGenus @@ -101,32 +101,100 @@ genus(Lf::LatWithIsom) = genus(lattice(Lf))::ZGenus ambient_space(Lf::LatWithIsom) -> QuadSpace Given a lattice with isometry $(L, f)$, return the ambient space of the underlying -lattice `L`. +lattice `L` (see [`ambient_space(::ZLat)`](@ref)). """ ambient_space(Lf::LatWithIsom) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} +@doc Markdown.doc""" + basis_matrix(Lf::LatWithIsom) -> QQMatrix + +Given a lattice with isometry $(L, f)$, return the basis matrix of the underlying +lattice `L` (see [`basis_matrix(::ZLat)`](@ref)). +""" basis_matrix(Lf::LatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix +@doc Markdown.doc""" + gram_matrix(Lf::LatWithIsom) -> QQMatrix + +Given a lattice with isometry $(L, f)$ with basis matric `B` (see [`basis_matrix(Lf::LatWithIsom)`](@ref)) +inside the space $(V, \Phi)$ (see [`ambient_space(Lf::LatWithIsom)`](@ref)), return the gram matrix +of lattice `L` associted to `B` with respect to $\Phi$. +""" gram_matrix(Lf::LatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix +@doc Markdown.doc""" + rational_span(Lf::LatWithIsom) -> QuadSpace + +Given a lattice with isometry $(L, f)$, return the rational span $L \otimes \mathbb{Q}$ +of the underlying lattice `L`. +""" rational_span(Lf::LatWithIsom) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} +@doc Markdown.doc""" + det(Lf::LatWithIsom) -> QQRingElem + +Given a lattice with isometry $(L, f)$, return the determinant of the +underlying lattice `L` (see [`det(::ZLat)`](@ref)). +""" det(Lf::LatWithIsom) = det(lattice(Lf))::QQRingElem +@doc Markdown.doc""" + scale(Lf::LatWithIsom) -> QQRingElem + +Given a lattice with isometry $(L, f)$, return the scale of the underlying +lattice `L` (see [`scale(::ZLat)`](@ref)). +""" scale(Lf::LatWithIsom) = det(lattice(Lf))::QQRingElem +@doc Markdown.doc""" + norm(Lf::LatWithIsom) -> QQRingElem + +Given a lattice with isometry $(L, f)$, return the norm of the underlying +lattice `L` (see [`norm(::ZLat)`](@ref)). +""" norm(Lf::LatWithIsom) = norm(lattice(Lf))::QQRingElem +@doc Markdown.doc""" + is_integral(Lf::LatWithIsom) -> Bool + +Given a lattice with isometry $(L, f)$, return whether the underlying lattice +is integral, i.e. whether its scale is an integer (see [`scale(::LatWithIsom)`](@ref)). +""" is_integral(Lf::LatWithIsom) = is_integral(lattice(Lf))::Bool +@doc Markdown.doc""" + degree(Lf::LatWithIsom) -> Int + +Given a lattice with isometry $(L, f)$ inside the quadratic space $(V, \Phi)$, +return the dimension of `V` as a $\mathbb Q$ vector space. +""" degree(Lf::LatWithIsom) = degree(lattice(Lf))::Int +@doc Markdown.doc""" + is_even(Lf::LatWithIsom) -> Bool + +Given a lattice with isometry $(L, f)$, return whether the underlying lattice +`L` is even, i.e. whether its norm is an even integer ([`norn(::LatWithIsom)`](@ref)). + +Note that to be even, `L` must be integral (see [`is_integral(::ZLat)`](@ref)). +""" is_even(Lf::LatWithIsom) = is_even(lattice(Lf))::Int +@doc Markdown.doc""" + discriminant(Lf::LatWithIsom) -> QQRingElem + +Given a lattice with isometry $(L, f)$, return the discriminant of the underlying +lattice `L` (see [`discriminant(::ZLat)`](@ref)). +""" discriminant(Lf::LatWithIsom) = discriminant(lattice(Lf))::QQRingElem -signature_tuple(Lf::LatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} +@doc Markdown.doc""" + signature_tuple(Lf::LatWithIsom) -> Tuple{Int, Int, Int} +Given a lattice with isometry $(L, f)$, return the signature tuple of the +underlying lattice `L` (see [`signature_tuple(::ZLat)`](@ref)). +""" +signature_tuple(Lf::LatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} ############################################################################### # @@ -224,21 +292,61 @@ end # ############################################################################### +@doc Markdown.doc""" + rescale(Lf::LatWithIsom, a::RationalUnion) -> LatWithIsom + +Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lattice +with isometry $(L(a), f)$ (see [`rescale(::ZLat, ::RationalUnion)`](@ref)). +""" function rescale(Lf::LatWithIsom, a::Hecke.RationalUnion) return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), order_of_isometry(Lf), check=false) end +@doc Markdown.doc""" + dual(Lf::LatWithIsom) -> LatWithIsom + +Given a lattice with isometry $(L, f)$ inside the space $(V, \Phi)$, such that `f` is +induced by an isometry `g` of $(V, \Phi)$, return the lattice with isometry $(L^{\vee}, h)$ +where $L^{\vee}$ is the dual of `L` in $(V, \Phi)$ (see [`dual(::ZLat)`](@ref)) and `h` is +induced by `g`. +""" function dual(Lf::LatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), order_of_isometry(Lf), check = false) end +@doc Markdown.doc""" + lll(Lf::LatWithIsom) -> LatWithIsom + +Given a lattice with isometry $(L, f)$, return the same lattice with isometry with a different +basis matrix for `L` (see [`basis_matrix(::ZLat)`](@ref)) obtained by performing an LLL-reduction +on the associated gram matrix of `L` (see [`gram_matrix(::ZLat)`](@ref)). + +Note that matrix representing the action of `f` on `L` changes but the global action +on the ambient space of `L` stays the same. +""" +function lll(Lf::LatWithIsom) + f = ambient_isometry(Lf) + L2 = lll(lattice(Lf), same_ambient=true) + return lattice_with_isometry(L2, f, ambient_representation = true) +end + ############################################################################### # # Hermitian structure # ############################################################################### +@doc Markdown.doc""" + is_of_hermitian_type(Lf::LatWithIsom) -> Bool + +Given a lattice with isometry $(L, f)$, return the minimal polynomial of the +underlying isometry `f` is (irreducible) cyclotomic. + +Note that if $(L, f)$ is of hermitian type with `f` of order `n`, then `L` can +be seen as a hermitian lattice over the order $\mathbb{Z}[\zeta_n]$ where $\zeta_n$ +is a primitive $n$-th root of unity. +""" function is_of_hermitian_type(Lf::LatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" n = order_of_isometry(Lf) @@ -263,8 +371,10 @@ If it exists, the hermitian structure is cached. f = isometry(Lf) n = order_of_isometry(Lf) - return Oscar._hermitian_structure(lattice(Lf), f, n = n, check = false, - ambient_representation = false) + H, l = Oscar._hermitian_structure(lattice(Lf), f, n = n, check = false, + ambient_representation = false) + set_attribute!(Lf, :transfert_data, l) + return H end ############################################################################### diff --git a/src/NumberTheory/LatWithIsom/TraceEquivalence.jl b/src/NumberTheory/LatWithIsom/TraceEquivalence.jl index ce2664b656b7..64da2b8c83d4 100644 --- a/src/NumberTheory/LatWithIsom/TraceEquivalence.jl +++ b/src/NumberTheory/LatWithIsom/TraceEquivalence.jl @@ -31,14 +31,16 @@ trace lattice of `L`. """ function trace_lattice(L::Hecke.AbstractLat{T}; alpha::FieldElem = one(base_field(L)), beta::FieldElem = one(base_field(L)), - order::Integer = 2) where T + order::Integer = 2, + l = nothing) where T - return trace_lattice_with_map(L, alpha=alpha, beta=beta, order=order)[1] + return trace_lattice_with_map(L, alpha=alpha, beta=beta, order=order, l = l)[1] end function trace_lattice_with_map(L::Hecke.AbstractLat{T}; alpha::FieldElem = one(base_field(L)), beta::FieldElem = gen(base_field(L)), - order::Integer = 2) where T + order::Integer = 2, + l = nothing) where T E = base_field(L) @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @req order > 0 "The order must be positive" @@ -66,14 +68,18 @@ function trace_lattice_with_map(L::Hecke.AbstractLat{T}; alpha::FieldElem = one( @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "The normalisation of beta must be a $m-primitive root of 1" Lres, f = restrict_scalars_with_map(L, QQ, alpha) + if l === nothing + l = identity_matrix(QQ, degree(Lres)) + end + Lres = lattice_in_same_ambient_space(Lres, basis_matrix(Lres)*inv(l)) iso = zero_matrix(QQ, 0, degree(Lres)) v = vec(zeros(QQ, 1, degree(Lres))) for i in 1:degree(Lres) v[i] = one(QQ) - v2 = f(v) + v2 = f(v*inv(l)) v2 = beta.*v2 - v3 = f\v2 + v3 = (f\v2)*l iso = vcat(iso, transpose(matrix(v3))) v[i] = zero(QQ) end @@ -81,7 +87,7 @@ function trace_lattice_with_map(L::Hecke.AbstractLat{T}; alpha::FieldElem = one( return lattice_with_isometry(Lres, iso, ambient_representation=true), f end -function trace_lattice(L::Hecke.AbstractLat{T}, f::SpaceRes; beta::FieldElem = gen(base_field(L))) where T +function trace_lattice(L::Hecke.AbstractLat{T}, f::Hecke.SpaceRes; beta::FieldElem = gen(base_field(L)), l = nothing) where T @req codomain(f) === ambient_space(L) "f must be a map of restriction of scalars associated to the ambient space of L" E = base_field(L) @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @@ -97,14 +103,18 @@ function trace_lattice(L::Hecke.AbstractLat{T}, f::SpaceRes; beta::FieldElem = g @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "The normalisation of beta must be a $m-primitive root of 1" Lres = restrict_scalars(L, f) + if l === nothing + l = identity_matrix(QQ, degree(Lres)) + end + Lres = lattice_in_same_ambient_space(Lres, basis_matrix(Lres)*inv(l)) iso = zero_matrix(QQ, 0, degree(Lres)) v = vec(zeros(QQ, 1, degree(Lres))) for i in 1:degree(Lres) v[i] = one(QQ) - v2 = f(v) + v2 = f(v*inv(l)) v2 = beta.*v2 - v3 = f\v2 + v3 = (f\v2)*l iso = vcat(iso, transpose(matrix(v3))) v[i] = zero(QQ) end @@ -128,7 +138,8 @@ end function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, n::Integer = -1, check::Bool = true, - ambient_representation::Bool = true) + ambient_representation::Bool = true + l = nothing) if n <= 0 n = multiplicative_order(f) end @@ -162,35 +173,31 @@ function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, b = gen(E) end - mb = absolute_representation_matrix(b) + _mb = absolute_representation_matrix(b) m = divexact(rank(L), euler_phi(n)) - bca = Hecke._basis_of_commutator_algebra(f, mb) - @assert !is_empty(bca) - l = zero_matrix(QQ, 0, ncols(f)) - while rank(l) != ncols(f) - _m = popfirst!(bca) - _l = vcat(l, _m) - if rank(_l) > rank(l) - l = _l + mb = block_diagonal_matrix([_mb for i in 1:m]) + if l === nothing + bca = Hecke._basis_of_commutator_algebra(f, _mb) + @assert !is_empty(bca) + l = zero_matrix(QQ, 0, ncols(f)) + while rank(l) != ncols(f) + _m = popfirst!(bca) + _l = vcat(l, _m) + if rank(_l) > rank(l) + l = _l + end end + @assert det(l) != 0 + @assert l*f == mb*l + elseif check + @req l isa QQMatrix "l must be a matrix with rational entries" + @req !is_zero(det(l)) "l must be invertible" + @req l*f == mb*l "The basis described by l is not compatible with multiplication by a $n-th root of unity" end - @assert det(l) != 0 - l = inv(l) - B = matrix(absolute_basis(E)) - gene = Vector{elem_type(E)}[] - for i in 1:nrows(l) - vv = vec(zeros(E, 1, m)) - v = l[i,:] - for j in 1:m - a = (v[1, 1+(j-1)*euler_phi(n):j*euler_phi(n)]*B)[1] - vv[j] = a - end - push!(gene, vv) - end + # We choose as basis for the hermitian lattice Lh the identity matrix - mb = block_diagonal_matrix([mb for i in 1:m]) gram = matrix(zeros(E, m, m)) - G = inv(l)*gram_matrix_of_rational_span(L)*inv(transpose(l)) + G = l*gram_matrix_of_rational_span(L)*transpose(l) v = zero_matrix(QQ, 1, rank(L)) for i=1:m for j=1:m @@ -206,6 +213,6 @@ function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, @assert transpose(map_entries(s, gram)) == gram Lh = hermitian_lattice(E, gene, gram = 1//n*gram) - return Lh + return Lh, l end From b28da18329f5b46cf99b3e6ba587c2d5da89613d Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 7 Mar 2023 13:59:29 +0100 Subject: [PATCH 34/76] move to experimental --- experimental/Experimental.jl | 1 + experimental/LatWithIsom.jl | 1 + .../LatWithIsom/Embeddings.jl | 0 .../LatWithIsom/Enumeration.jl | 0 .../LatWithIsom/HMM.jl | 0 experimental/LatWithIsom/LWI.jl | 20 +++++++++++++++++++ .../LatWithIsom/LatWithIsom.jl | 0 .../LatWithIsom/TraceEquivalence.jl | 0 .../LatWithIsom/Types.jl | 0 src/NumberTheory/LWI.jl | 7 ------- src/Oscar.jl | 1 - test/Experimental/LatWithIsom.jl | 5 +++++ test/runtests.jl | 1 + 13 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 experimental/LatWithIsom.jl rename {src/NumberTheory => experimental}/LatWithIsom/Embeddings.jl (100%) rename {src/NumberTheory => experimental}/LatWithIsom/Enumeration.jl (100%) rename {src/NumberTheory => experimental}/LatWithIsom/HMM.jl (100%) create mode 100644 experimental/LatWithIsom/LWI.jl rename {src/NumberTheory => experimental}/LatWithIsom/LatWithIsom.jl (100%) rename {src/NumberTheory => experimental}/LatWithIsom/TraceEquivalence.jl (100%) rename {src/NumberTheory => experimental}/LatWithIsom/Types.jl (100%) delete mode 100644 src/NumberTheory/LWI.jl create mode 100644 test/Experimental/LatWithIsom.jl diff --git a/experimental/Experimental.jl b/experimental/Experimental.jl index d293d65db4a9..a4729bbd339f 100644 --- a/experimental/Experimental.jl +++ b/experimental/Experimental.jl @@ -6,6 +6,7 @@ include("GITFans.jl") include("GModule.jl") include("MPolyRingSparse.jl") include("SymmetricIntersections.jl") +include("LWI.jl") include("Schemes/Types.jl") include("Schemes/SpecialTypes.jl") diff --git a/experimental/LatWithIsom.jl b/experimental/LatWithIsom.jl new file mode 100644 index 000000000000..2bb2c27fca28 --- /dev/null +++ b/experimental/LatWithIsom.jl @@ -0,0 +1 @@ +include("LatWithIsom/LWI.jl") diff --git a/src/NumberTheory/LatWithIsom/Embeddings.jl b/experimental/LatWithIsom/Embeddings.jl similarity index 100% rename from src/NumberTheory/LatWithIsom/Embeddings.jl rename to experimental/LatWithIsom/Embeddings.jl diff --git a/src/NumberTheory/LatWithIsom/Enumeration.jl b/experimental/LatWithIsom/Enumeration.jl similarity index 100% rename from src/NumberTheory/LatWithIsom/Enumeration.jl rename to experimental/LatWithIsom/Enumeration.jl diff --git a/src/NumberTheory/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl similarity index 100% rename from src/NumberTheory/LatWithIsom/HMM.jl rename to experimental/LatWithIsom/HMM.jl diff --git a/experimental/LatWithIsom/LWI.jl b/experimental/LatWithIsom/LWI.jl new file mode 100644 index 000000000000..99792e46d571 --- /dev/null +++ b/experimental/LatWithIsom/LWI.jl @@ -0,0 +1,20 @@ +module LatWithIsom +using Oscar, Markdown + +macro req(args...) + @assert length(args) == 2 + quote + if !($(esc(args[1]))) + throw(ArgumentError($(esc(args[2])))) + end + end +end + +include("Types.jl") +include("HMM.jl") +include("TraceEquivalence.jl") +include("LatWithIsom.jl") +include("Enumeration.jl") +include("Embeddings.jl") + +end diff --git a/src/NumberTheory/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl similarity index 100% rename from src/NumberTheory/LatWithIsom/LatWithIsom.jl rename to experimental/LatWithIsom/LatWithIsom.jl diff --git a/src/NumberTheory/LatWithIsom/TraceEquivalence.jl b/experimental/LatWithIsom/TraceEquivalence.jl similarity index 100% rename from src/NumberTheory/LatWithIsom/TraceEquivalence.jl rename to experimental/LatWithIsom/TraceEquivalence.jl diff --git a/src/NumberTheory/LatWithIsom/Types.jl b/experimental/LatWithIsom/Types.jl similarity index 100% rename from src/NumberTheory/LatWithIsom/Types.jl rename to experimental/LatWithIsom/Types.jl diff --git a/src/NumberTheory/LWI.jl b/src/NumberTheory/LWI.jl deleted file mode 100644 index a7f046e4edd8..000000000000 --- a/src/NumberTheory/LWI.jl +++ /dev/null @@ -1,7 +0,0 @@ -include("LatWithIsom/Types.jl") -include("LatWithIsom/HMM.jl") -include("LatWithIsom/TraceEquivalence.jl") -include("LatWithIsom/LatWithIsom.jl") -include("LatWithIsom/Enumeration.jl") -include("LatWithIsom/Embeddings.jl") - diff --git a/src/Oscar.jl b/src/Oscar.jl index b6f33f68582f..b83d98d292d7 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -481,7 +481,6 @@ include("Modules/mpolyquo.jl") include("Rings/ReesAlgebra.jl") include("NumberTheory/NmbThy.jl") -include("NumberTheory/LWI.jl") include("PolyhedralGeometry/main.jl") diff --git a/test/Experimental/LatWithIsom.jl b/test/Experimental/LatWithIsom.jl new file mode 100644 index 000000000000..57f6c5ad4da5 --- /dev/null +++ b/test/Experimental/LatWithIsom.jl @@ -0,0 +1,5 @@ +using Oscar.LatWithIsom + +@testset "Basics" begin + +end diff --git a/test/runtests.jl b/test/runtests.jl index 1809f563f078..31bf79f18142 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -107,6 +107,7 @@ include("Experimental/MPolyRingSparse-test.jl") include("Experimental/MatrixGroups-test.jl") include("Experimental/SymmetricIntersections-test.jl") include("Experimental/ExteriorAlgebra-test.jl") +include("Experimental/LatWithIsom-test.jl") include("Modules/UngradedModules.jl") include("Modules/FreeModElem-orderings-test.jl") From 7b2e6a1f741a7f2674280f649db1e9c62fb16f57 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 8 Mar 2023 08:39:05 +0100 Subject: [PATCH 35/76] begin tests --- experimental/Experimental.jl | 2 +- experimental/LatWithIsom/Embeddings.jl | 23 ++++++---- experimental/LatWithIsom/Enumeration.jl | 20 ++++----- experimental/LatWithIsom/HMM.jl | 16 +++---- experimental/LatWithIsom/LWI.jl | 2 +- experimental/LatWithIsom/LatWithIsom.jl | 46 ++++++++++---------- experimental/LatWithIsom/TraceEquivalence.jl | 2 +- test/Experimental/LatWithIsom.jl | 38 +++++++++++++++- 8 files changed, 95 insertions(+), 54 deletions(-) diff --git a/experimental/Experimental.jl b/experimental/Experimental.jl index a4729bbd339f..fda156dfa9e4 100644 --- a/experimental/Experimental.jl +++ b/experimental/Experimental.jl @@ -6,7 +6,7 @@ include("GITFans.jl") include("GModule.jl") include("MPolyRingSparse.jl") include("SymmetricIntersections.jl") -include("LWI.jl") +include("LatWithIsom.jl") include("Schemes/Types.jl") include("Schemes/SpecialTypes.jl") diff --git a/experimental/LatWithIsom/Embeddings.jl b/experimental/LatWithIsom/Embeddings.jl index 5111f9ca651c..585ce80ef511 100644 --- a/experimental/LatWithIsom/Embeddings.jl +++ b/experimental/LatWithIsom/Embeddings.jl @@ -1,6 +1,6 @@ -export admissible_equivariant_primitive_extensions, -export equivariant_primitive_extensions, -export primitive_embeddings_in_primary_lattice, +export admissible_equivariant_primitive_extensions +export equivariant_primitive_extensions +export primitive_embeddings_in_primary_lattice export primitive_embeddings_of_elementary_lattice const GG = GAP.Globals @@ -41,7 +41,8 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu end function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModule) - D, [AinD, BinD] = direct_sum(A, B) + D, inj = direct_sum(A, B) + AinD, BinD = inj OD = orthogonal_group(D) OA = orthogonal_group(A) OB = orthogonal_group(B) @@ -326,7 +327,8 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM qN = domain(GN) qM = domain(GM) - D, [qNinD, qMinD] = direct_sum(qN, qM) + D, inj = direct_sum(qN, qM) + qNinD, qMinD = inj OD = orthogonal_group(D) subsN = classes_conjugate_subgroups(GN, rescale(H, -1)) @@ -380,7 +382,7 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: @assert is_one(basis_matrix(N)) @assert is_one(basis_matrix(M)) q = elementary_divisors(H)[end] - ok, p, _ = is_primme_power_with_data(q) + ok, p, _ = is_prime_power_with_data(q) @assert ok @assert is_elementary(M, p) results = Tuple{ZLat, ZLat, ZLat}[] @@ -389,7 +391,8 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: qN = domain(GN) qM = domain(GM) - D, [qNinD, qMinD] = direct_sum(qN, qM) + D, inj = direct_sum(qN, qM) + qNinD, qMinD = inj OD = orthogonal_group(D) VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) subsN = subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) @@ -474,7 +477,8 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph return [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] end qM = discriminant_group(M) - D, [qMinD, qLinD], proj = biproduct(qM, qL) + D, inj, proj = biproduct(qM, qL) + qMinD, qLinD = inj if el VM, VMinqM, _ = _get_V(M, qM, identity_matrix(QQ, rank(M)), id_hom(qM), minpoly(identity_matrix(QQ,1)), p) else @@ -543,7 +547,8 @@ function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::Automo end end qM = discriminant_group(M) - D, [qMinD, qLinD], proj = biproduct(qM, qL) + D, inj, proj = biproduct(qM, qL) + qMinD, qLinD = inj VL, VLinqL, _ = _get_V(L, qL, identity_matrix(QQ, rank(L)), id_hom(qL), minpoly(identity_matrix(QQ,1)), p) for k in divisors(gcd(order(qM), order(VL))) @info "Glue order: $(k)" diff --git a/experimental/LatWithIsom/Enumeration.jl b/experimental/LatWithIsom/Enumeration.jl index 43c46d3a3bcb..3d9d4baf7416 100644 --- a/experimental/LatWithIsom/Enumeration.jl +++ b/experimental/LatWithIsom/Enumeration.jl @@ -1,9 +1,9 @@ -export admissible_triples, -export is_admissible_triple, -export splitting_of_hermitian_prime_power, -export splitting_of_mixed_prime_power, -export splitting_of_partial_mixed_prime_power, -export splitting_of_prime_power, +export admissible_triples +export is_admissible_triple +export splitting_of_hermitian_prime_power +export splitting_of_mixed_prime_power +export splitting_of_partial_mixed_prime_power +export splitting_of_prime_power export representatives_of_hermitian_type ################################################################################## @@ -20,7 +20,7 @@ export representatives_of_hermitian_type ################################################################################## # The tuples in output are pairs of positive integers! -function _tuples_divisors(d::IntegerUnion) +function _tuples_divisors(d::Hecke.IntegerUnion) div = divisors(d) return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] end @@ -29,7 +29,7 @@ end # discriminant for the genera A and B to glue to fit in C. d is # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. -function _find_D(d::IntegerUnion, m::Int, p::Int) +function _find_D(d::Hecke.IntegerUnion, m::Int, p::Int) @assert is_prime(p) @assert d != 0 @@ -55,7 +55,7 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::IntegerUnion, even = true) +function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::Hecke.IntegerUnion, even = true) L = ZGenus[] if r == 0 && d == 1 return ZGenus[genus(Zlattice(gram = matrix(QQ, 0, 0, [])))] @@ -251,7 +251,7 @@ admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatWithIsom} = admis # we compute ideals of E/K whose absolute norm is equal to d -function _ideals_of_norm(E, d::QQRingElem) +function _ideals_of_norm(E, d::QQFieldElem) if denominator(d) == 1 return _ideals_of_norm(E, numerator(d)) elseif numerator(d) == 1 diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index ed2869ac5ac7..474877b31274 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -193,9 +193,9 @@ function _get_product_quotient(E::Hecke.NfRel, Fac) if length(Fac) == 0 A = abelian_group() - function dlog(x::Hecke.NfRelElem); return one(A); end; - function exp(x::GrpAbFinGenElem); return one(E); end; - return A, dlog, exp + function dlog_0(x::Hecke.NfRelElem); return one(A); end; + function exp_0(x::GrpAbFinGenElem); return one(E); end; + return A, dlog_0, exp_0 end for i in 1:length(Fac) @@ -287,7 +287,7 @@ function _local_determinant_morphism(Lf::LatWithIsom) fU = hom(UOEabs, UOK, [mUOK\(norm(OE(EabstoE(Eabs(mUOEabs(k)))))) for k in gens(UOEabs)]) KU, jU = kernel(fU) - gene_norm_one = NfRelOrdElem[OE(EabstoE(Eabs(mUOEabs(jU(k))))) for k in gens(KU)] + gene_norm_one = Hecke.NfRelOrdElem[OE(EabstoE(Eabs(mUOEabs(jU(k))))) for k in gens(KU)] FOEmodFsharp, m = sub(RmodFsharp, ) @@ -348,7 +348,7 @@ function _transfer_discriminant_isometries(res::Hecke.SpaceRes, g::AutomorphismG return gE end -function _get_piecewise_actions_modular(H::Hecke.HermLat, g::MatrixSpaceElem{NfRelOrdElem}, p::NfOrdIdl, a::Int) +function _get_piecewise_actions_modular(H::Hecke.HermLat, g::MatrixElem{Hecke.NfRelOrdElem}, p::Hecke.NfOrdIdl, a::Int) @assert g*gram_matrix_of_rational_span(H)*map_entries(involution(H), transpose(g)) == gram_matrix_of_rational_span(H) E = base_field(H) OE = base_ring(H) @@ -369,7 +369,7 @@ function _get_piecewise_actions_modular(H::Hecke.HermLat, g::MatrixSpaceElem{NfR return exp, local_act end -function _local_hermitian_lifting(G::MatrixSpaceElem{NfRelElem}, F::MatrixSpaceElem{NFRelOrdElem}, rho::NfRelElem, l::Int, P::NfRelOrdIdl; check = true) +function _local_hermitian_lifting(G::MatrixElem{Hecke.NfRelElem}, F::MatrixElem{Hecke.NfRelOrdElem}, rho::Hecke.NfRelElem, l::Int, P::Hecke.NfRelOrdIdl; check = true) @assert trace(rho) == 1 E = base_ring(G) @assert G == map_entries(involution(E), transpose(G)) @@ -388,7 +388,7 @@ function _local_hermitian_lifting(G::MatrixSpaceElem{NfRelElem}, F::MatrixSpaceE if check @assert min([valuation(v, P) for v in collect(inv(G)) if !iszero(v)]) >= l+a - @assert min([valuation(v, P) for v in diagonal(inv(G)) if !iszero(v)]) >= e=a + @assert min([valuation(v, P) for v in diagonal(inv(G)) if !iszero(v)]) >= e+a @assert min([valuation(v, P) for v in collect(R) if !iszero(v)]) >= l-a @assert min([valuation(v, P) for v in diagonal(R) if !iszero(v)]) >= l+e-1-a end @@ -416,7 +416,7 @@ function _local_hermitian_lifting(G::MatrixSpaceElem{NfRelElem}, F::MatrixSpaceE return newF end -function _find_rho(P::NfRelOrdIdl) +function _find_rho(P::Hecke.NfRelOrdIdl) OE = order(P) E = nf(OE) dya = valuation(2, norm(P)) > 0 diff --git a/experimental/LatWithIsom/LWI.jl b/experimental/LatWithIsom/LWI.jl index 99792e46d571..79b5e26ef8ca 100644 --- a/experimental/LatWithIsom/LWI.jl +++ b/experimental/LatWithIsom/LWI.jl @@ -1,4 +1,4 @@ -module LatWithIsom +module LWI using Oscar, Markdown macro req(args...) diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index b1886d4e948f..e741a37876af 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -1,16 +1,17 @@ -export ambient_isometry, -export coinvariant_lattice, -export hermitian_structure, -export image_centralizer_in_Oq, -export isometry, -export is_of_hermitian_type, -export is_of_same_type, -export is_of_type, -export is_hermitian, -export lattice_with_isometry, -export order_of_isometry, +export ambient_isometry +export coinvariant_lattice +export hermitian_structure +export image_centralizer_in_Oq +export isometry +export is_of_hermitian_type +export is_of_same_type +export is_of_type +export is_hermitian +export lattice_with_isometry +export order_of_isometry export type - + +import Hecke: kernel_lattice, invariant_lattice ############################################################################### # # String I/O @@ -131,28 +132,28 @@ of the underlying lattice `L`. rational_span(Lf::LatWithIsom) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} @doc Markdown.doc""" - det(Lf::LatWithIsom) -> QQRingElem + det(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the determinant of the underlying lattice `L` (see [`det(::ZLat)`](@ref)). """ -det(Lf::LatWithIsom) = det(lattice(Lf))::QQRingElem +det(Lf::LatWithIsom) = det(lattice(Lf))::QQFieldElem @doc Markdown.doc""" - scale(Lf::LatWithIsom) -> QQRingElem + scale(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the scale of the underlying lattice `L` (see [`scale(::ZLat)`](@ref)). """ -scale(Lf::LatWithIsom) = det(lattice(Lf))::QQRingElem +scale(Lf::LatWithIsom) = det(lattice(Lf))::QQFieldElem @doc Markdown.doc""" - norm(Lf::LatWithIsom) -> QQRingElem + norm(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the norm of the underlying lattice `L` (see [`norm(::ZLat)`](@ref)). """ -norm(Lf::LatWithIsom) = norm(lattice(Lf))::QQRingElem +norm(Lf::LatWithIsom) = norm(lattice(Lf))::QQFieldElem @doc Markdown.doc""" is_integral(Lf::LatWithIsom) -> Bool @@ -181,12 +182,12 @@ Note that to be even, `L` must be integral (see [`is_integral(::ZLat)`](@ref)). is_even(Lf::LatWithIsom) = is_even(lattice(Lf))::Int @doc Markdown.doc""" - discriminant(Lf::LatWithIsom) -> QQRingElem + discriminant(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the discriminant of the underlying lattice `L` (see [`discriminant(::ZLat)`](@ref)). """ -discriminant(Lf::LatWithIsom) = discriminant(lattice(Lf))::QQRingElem +discriminant(Lf::LatWithIsom) = discriminant(lattice(Lf))::QQFieldElem @doc Markdown.doc""" signature_tuple(Lf::LatWithIsom) -> Tuple{Int, Int, Int} @@ -216,7 +217,7 @@ Otherwise, an isometry of the ambient space of `L` is constructed, setting the i on the complement of the rational span of `L` if it is not of full rank. """ function lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = true, - ambient_representation::Bool = true) + ambient_representation::Bool = true) if rank(L) == 0 return LatWithIsom(L, matrix(QQ,0,0,[]), -1) end @@ -250,6 +251,7 @@ function lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = tr return LatWithIsom(L, f, f_ambient, n)::LatWithIsom end + @doc Markdown.doc""" lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, ambient_representation::Bool, = true) @@ -299,7 +301,7 @@ Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lat with isometry $(L(a), f)$ (see [`rescale(::ZLat, ::RationalUnion)`](@ref)). """ function rescale(Lf::LatWithIsom, a::Hecke.RationalUnion) - return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), order_of_isometry(Lf), check=false) + return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), order_of_isometry(Lf), check=false) end @doc Markdown.doc""" diff --git a/experimental/LatWithIsom/TraceEquivalence.jl b/experimental/LatWithIsom/TraceEquivalence.jl index 64da2b8c83d4..c1f648ee1dbe 100644 --- a/experimental/LatWithIsom/TraceEquivalence.jl +++ b/experimental/LatWithIsom/TraceEquivalence.jl @@ -138,7 +138,7 @@ end function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, n::Integer = -1, check::Bool = true, - ambient_representation::Bool = true + ambient_representation::Bool = true, l = nothing) if n <= 0 n = multiplicative_order(f) diff --git a/test/Experimental/LatWithIsom.jl b/test/Experimental/LatWithIsom.jl index 57f6c5ad4da5..cbc9832501b1 100644 --- a/test/Experimental/LatWithIsom.jl +++ b/test/Experimental/LatWithIsom.jl @@ -1,5 +1,39 @@ -using Oscar.LatWithIsom +using Oscar.LWI -@testset "Basics" begin +@testset "Constructors and accessors" begin + A4 = root_lattice(:A, 4) + agg = = automorphism_group_generators(A4, ambient_representation = false) + agg_ambient = automorphism_group_generators(A4, ambient_representation = true) + f = rand(agg) + g_ambient = rand(agg_ambient) + + L = @inferred lattice_with_isometry(A4) + @test isone(isometry(L)) + @test isone(ambient_isometry(L)) + @test isone(order_of_isometry(L)) + + for func in [rank, charpoly, minpoly, genus, ambient_space, basis_matrix, + gram_matrix, rational_span, det, scale, norm, is_integral, + degree, is_even, discriminant, signature_tuple] + out = @inferred func(L) + end + + nf = multiplicative_order(f) + @test_throws ArgumentError lattice_with_isometry(A4, f, nf+1) + @test_throws ArgumentError lattice_with_isometry(A4, zero_matrix(QQ, 0, 0), -1) + + L2 = @inferred lattice_with_isometry(A4, f, ambient_representation = false) + @test order_of_isometry(L2) == nf + L2v = @inferred dual(L2) + @test order_of_isometry(L2v) == nf + @test ambient_isometry(L2v) == ambient_isometry(L2) + L3 = @inferred lattice_with_isometry(A4, g_ambient, ambient_representation = true) + @test order_of_isometry(L2) == multiplicative_order(g_ambient) + + L4 = @inferred rescale(L3, QQ(1//4)) + @test !is_integral(L4) + @test order_of_isometry(L4) == order_of_isometry(L3) + @test_throws ArgumentError dual(L4) + @test ambient_isometry(lll(L4)) == ambient_isometry(L4) end From 14346857dbe5b86ed4fed72b483e98fbb5fe310b Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 14 Mar 2023 08:18:38 +0100 Subject: [PATCH 36/76] more changes on trace equivalence --- experimental/LatWithIsom/HMM.jl | 2 +- experimental/LatWithIsom/LWI.jl | 1 + experimental/LatWithIsom/LatWithIsom.jl | 7 +- experimental/LatWithIsom/TraceEquivalence.jl | 96 +++++++++++++------- 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index 474877b31274..3c7ed2a9a787 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -239,7 +239,7 @@ function _local_determinant_morphism(Lf::LatWithIsom) DKQ = different(base_ring(OE))*OE DEK = different(OE) DEQ = DEK*DKQ - DinvH = inv(DEQ)*H + DinvHdash = inv(DEQ)*dual(H) res = Hecke.SpaceRes(ambient_space(Lf), ambient_space(H)) Lv = trace_lattice(DinvH, res, l = l) @assert cover(q) === lattice(Lv) diff --git a/experimental/LatWithIsom/LWI.jl b/experimental/LatWithIsom/LWI.jl index 79b5e26ef8ca..2116594d6942 100644 --- a/experimental/LatWithIsom/LWI.jl +++ b/experimental/LatWithIsom/LWI.jl @@ -1,5 +1,6 @@ module LWI using Oscar, Markdown +import Hecke macro req(args...) @assert length(args) == 2 diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index e741a37876af..b896d4068196 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -11,7 +11,10 @@ export lattice_with_isometry export order_of_isometry export type -import Hecke: kernel_lattice, invariant_lattice +import Hecke: kernel_lattice, invariant_lattice, rank, genus, basis_matrix, + gram_matrix, ambient_space, rational_span, scale, signature_tuple, + is_integral, det, norm, degree, discriminant, charpoly, minpoly, + rescale, dual, lll, discriminant_group ############################################################################### # # String I/O @@ -373,7 +376,7 @@ If it exists, the hermitian structure is cached. f = isometry(Lf) n = order_of_isometry(Lf) - H, l = Oscar._hermitian_structure(lattice(Lf), f, n = n, check = false, + H, l = LWI._hermitian_structure(lattice(Lf), f, n = n, check = false, ambient_representation = false) set_attribute!(Lf, :transfert_data, l) return H diff --git a/experimental/LatWithIsom/TraceEquivalence.jl b/experimental/LatWithIsom/TraceEquivalence.jl index c1f648ee1dbe..0506e878112b 100644 --- a/experimental/LatWithIsom/TraceEquivalence.jl +++ b/experimental/LatWithIsom/TraceEquivalence.jl @@ -30,7 +30,7 @@ Note that the isometry `f` computed is given by its action on the ambient space trace lattice of `L`. """ function trace_lattice(L::Hecke.AbstractLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = one(base_field(L)), + beta::FieldElem = gen(base_field(L)), order::Integer = 2, l = nothing) where T @@ -77,18 +77,17 @@ function trace_lattice_with_map(L::Hecke.AbstractLat{T}; alpha::FieldElem = one( for i in 1:degree(Lres) v[i] = one(QQ) - v2 = f(v*inv(l)) + v2 = f(v) v2 = beta.*v2 - v3 = (f\v2)*l + v3 = (f\v2) iso = vcat(iso, transpose(matrix(v3))) v[i] = zero(QQ) end - return lattice_with_isometry(Lres, iso, ambient_representation=true), f end -function trace_lattice(L::Hecke.AbstractLat{T}, f::Hecke.SpaceRes; beta::FieldElem = gen(base_field(L)), l = nothing) where T - @req codomain(f) === ambient_space(L) "f must be a map of restriction of scalars associated to the ambient space of L" +function trace_lattice(L::Hecke.AbstractLat{T}, res::Hecke.SpaceRes; beta::FieldElem = gen(base_field(L)), l = nothing) where T + @req codomain(res) === ambient_space(L) "f must be a map of restriction of scalars associated to the ambient space of L" E = base_field(L) @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" @req degree(L) == rank(L) "Lattice must be of full rank" @@ -102,24 +101,29 @@ function trace_lattice(L::Hecke.AbstractLat{T}, f::Hecke.SpaceRes; beta::FieldEl bool, m = Hecke.is_cyclotomic_type(E) @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "The normalisation of beta must be a $m-primitive root of 1" - Lres = restrict_scalars(L, f) + #Lres = restrict_scalars(L, f) if l === nothing - l = identity_matrix(QQ, degree(Lres)) + il = identity_matrix(QQ, rank(domain(res))) + l = il + else + il = inv(l) end - Lres = lattice_in_same_ambient_space(Lres, basis_matrix(Lres)*inv(l)) - iso = zero_matrix(QQ, 0, degree(Lres)) - v = vec(zeros(QQ, 1, degree(Lres))) - - for i in 1:degree(Lres) - v[i] = one(QQ) - v2 = f(v*inv(l)) - v2 = beta.*v2 - v3 = (f\v2)*l - iso = vcat(iso, transpose(matrix(v3))) - v[i] = zero(QQ) + Babs = absolute_basis(E) + Mabs = zero_matrix(QQ, 0, rank(domain(res))) + for w in absolute_basis(L) + w = transpose(matrix(reduce(vcat, absolute_coordinates.(w)))) + v = w*l + Mabs = vcat(Mabs, v) end - - return lattice_with_isometry(Lres, iso, ambient_representation=true) + Lres = Hecke.lattice(domain(res), Mabs) + mb = absolute_representation_matrix(beta) + iso = deepcopy(mb) + for i in 2:degree(L) + iso = diagonal_matrix(iso, mb) + end + println(iso) + println(gram_matrix(Lres)) + return lattice_with_isometry(Lres, il*iso*l, ambient_representation=true) end function absolute_representation_matrix(b::Hecke.NfRelElem) @@ -135,22 +139,35 @@ function absolute_representation_matrix(b::Hecke.NfRelElem) return m end -function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, - n::Integer = -1, - check::Bool = true, - ambient_representation::Bool = true, - l = nothing) +function _hermitian_structure(_L::ZLat, f::QQMatrix; E = nothing, + n::Integer = -1, + check::Bool = true, + ambient_representation::Bool = true, + l = nothing) + if rank(_L) != degree(_L) + L = Zlattice(gram = gram_matrix(_L)) + if ambient_representation + ok, f = can_solve_with_solution(basis_matrix(_L), basis_matrix(_L)*f, side=:left) + @req ok "Isometry does not restrict to L" + end + else + L = _L + if !ambient_representation + V = ambient_space(_L) + B = basis_matrix(_L) + B2 = orthogonal_complement(V, B) + C = vcat(B, B2) + f_ambient = block_diagonal_matrix([f, identity_matrix(QQ, nrows(B2))]) + f = inv(C)*f_ambient*C + end + end + if n <= 0 n = multiplicative_order(f) end @req is_finite(n) && n > 0 "Isometry must be non-trivial and of finite exponent" - if ambient_representation - ok, f = can_solve_with_solution(basis_matrix(L), basis_matrix(L)*f, side =:left) - @req ok "Isometry does not restrict to L" - end - if check @req n >= 3 "No hermitian structure for order less than 3" G = gram_matrix(L) @@ -195,9 +212,22 @@ function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, @req l*f == mb*l "The basis described by l is not compatible with multiplication by a $n-th root of unity" end + B = matrix(absolute_basis(E)) + BL = basis_matrix(L) + gene = Vector{elem_type(E)}[] + for i in 1:nrows(l) + vv = vec(zeros(E, 1, m)) + v = BL[i,:]*inv(l) + for j in 1:m + a = (v[1, 1+(j-1)*euler_phi(n):j*euler_phi(n)]*B)[1] + vv[j] = a + end + push!(gene, vv) + end + # We choose as basis for the hermitian lattice Lh the identity matrix gram = matrix(zeros(E, m, m)) - G = l*gram_matrix_of_rational_span(L)*transpose(l) + G = l*gram_matrix(ambient_space(L))*transpose(l) v = zero_matrix(QQ, 1, rank(L)) for i=1:m for j=1:m @@ -213,6 +243,6 @@ function _hermitian_structure(L::ZLat, f::QQMatrix; E = nothing, @assert transpose(map_entries(s, gram)) == gram Lh = hermitian_lattice(E, gene, gram = 1//n*gram) - return Lh, l + return Lh, l end From 43e629fb9a986f2ed24bbe2c5ec6b1ae7a0227d4 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 16 Mar 2023 12:07:13 +0100 Subject: [PATCH 37/76] some fixes --- experimental/LatWithIsom/Embeddings.jl | 63 +++++++++++--------- experimental/LatWithIsom/Enumeration.jl | 76 ++++++++++++++++--------- experimental/LatWithIsom/LatWithIsom.jl | 11 ++-- 3 files changed, 90 insertions(+), 60 deletions(-) diff --git a/experimental/LatWithIsom/Embeddings.jl b/experimental/LatWithIsom/Embeddings.jl index c53debf30e8d..08d1107ec7e6 100644 --- a/experimental/LatWithIsom/Embeddings.jl +++ b/experimental/LatWithIsom/Embeddings.jl @@ -19,20 +19,20 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu OA = orthogonal_group(A) OB = orthogonal_group(B) - gene = GrpAbFinGenElem[data(D(lift(a))+D(lift(b))) for a in gens(A), b in gens(B)] - geneOAinOD = TorQuadModuleMor[] + gene = data.(union(AinD.(gens(A), BinD.(gens(B))))) + geneOAinOD = elem_type(OD)[] for f in gens(OA) - imgf = GrpAbFinGenElem[data(D(lift(f(a))) + D(lift(b))) for a in gens(A), b in gens(B)] + imgf = data.(union(AinD.(f.(gens(A))), BinD.(gens(B)))) fab = hom(gene, imgf) - fD = hom(D, D, fab.map) + fD = OD(hom(D, D, fab.map)) push!(geneOAinOD, fD) end - geneOBinOD = TorQuadModuleMor[] + geneOBinOD = elem_type(OD)[] for f in gens(OB) - imgf = GrpAbFinGenElem[data(D(lift(a)) + D(lift(f(b)))) for a in gens(A), b in gens(B)] + imgf = data.(union(AinD.(gens(A)), BinD.(f.(gens(B))))) fab = hom(gene, imgf) - fD = hom(D, D, fab.map) + fD = OD(hom(D, D, fab.map)) push!(geneOBinOD, fD) end OAtoOD = hom(OA, OD, geneOAinOD, check = false) @@ -47,17 +47,17 @@ function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQ OA = orthogonal_group(A) OB = orthogonal_group(B) - geneOAinOD = TorQuadModuleMor[] + geneOAinOD = elem_type(OD)[] for f in gens(OA) - m = block_diagonal_matrix([matrix(f), indentity_matrix(ZZ, ngens(B))]) - fD = hom(D, D, m) + m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) + fD = OD(hom(D, D, m), check=false) push!(geneOAinOD, fD) end - geneOBinOD = TorQuadModuleMor[] + geneOBinOD = elem_type(OD)[] for f in gens(OB) m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) - fD = hom(D, D, m) + fD = OD(hom(D, D, m), check=false) push!(geneOBinOD, fD) end OAtoOD = hom(OA, OD, geneOAinOD, check = false) @@ -153,7 +153,7 @@ function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) OA = automorphism_group(A) OinOA, _ = sub(OA, OA.([g.X for g in gens(O)])) N, _ = sub(A, togap.(i.(gens(domain(i))))) - stab, _ = stabilizer(OinOA, N, on_subgroup) + stab, _ = Oscar.stabilizer(OinOA, N, on_subgroup) return sub(O, O.([h.X for h in gens(stab)])) end @@ -296,7 +296,7 @@ function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{To res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] for orb in orbs rep = representative(orb) - stab, _ = stabilizer(OinOAgap, rep, on_subgroup) + stab, _ = Oscar.stabilizer(OinOAgap, rep, on_subgroup) _, rep = sub(q, TorQuadModuleElem[tooscar(Agap(g)) for g in gens(rep)]) stab, _ = sub(O, O.([h.X for h in gens(stab)])) push!(res, (rep, stab)) @@ -341,9 +341,11 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM @assert ok HNinqN, stabN = H1 + HN = domain(HNinqN) OHN = orthogonal_group(HN) HMinqM, stabM = H2 + HM = domain(HMinqM) OHM = orthogonal_group(HM) actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) @@ -365,7 +367,7 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM glue = FakeFmpqMat(glue) _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) + L = Hecke.lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) @assert genus(N) == genus(N2) M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) @@ -755,6 +757,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # this is where we will perform the glueing if ambient_space(A) === ambient_space(B) + println("same_amb") D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) else D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _direct_sum_with_embeddings_orthogonal_groups(qA, qB) @@ -766,6 +769,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # if the glue valuation is zero, then we glue along the trivial group and we don't # have much more to do. Since the triple is p-admissible, A+B = C if g == 0 + println(0) geneA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(OqA(a.X)) for a in gens(GA)] geneB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(OqB(b.X)) for b in gens(GB)] gene = vcat(geneA, geneB) @@ -781,13 +785,17 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) end if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(C)) + qC2 = discriminant_group(C2) + ok, phi2 = is_isometric_with_isometry(qC2, D) + @assert ok + GC = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GC2)]) C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - set_attribute!(C2fc2, :image_centralizer_in_Oq, GC2) + set_attribute!(C2fc2, :image_centralizer_in_Oq, GC) push!(results, C2fc2) end return results end - + println("not 0") # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map VA, VAinqA, fVA = _get_V(lattice(A), qA, isometry(A), fqA, minpoly(B), p) VB, VBinqB, fVB = _get_V(lattice(B), qB, isometry(B), fqB, minpoly(A), p) @@ -823,15 +831,12 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # corresponding overlattice and check whether it satisfies the type condition for (H1, H2, phi) in R SAinqA, stabA = H1 - OSAinOqA = embedding_orthogonal_group(SAinqA) - OSA = domain(OSAinOqA) - OSAinOD = compose(OSAinOqA, OqAinOD) + SA = domain(SAinqA) + OSA = orthogonal_group(SA) SBinqB, stabB = H2 SB = domain(SBinqB) - OSBinOqB = embedding_orthogonal_group(SBinqB) - OSB = domain(OSBinOqB) - OSBinOD = compose(OSBinOqB, OqBinOD) + OSB = orthogonal_group(SB) # we compute the image of the stabalizers in the respective OS* and we keep track # of the elements of the stabilizers acting trivially in the respective S* @@ -906,7 +911,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, glue = FakeFmpqMat(glue) _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) + C2 = Hecke.lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) fC2 = __B*fC2*inv(__B) @@ -918,19 +923,21 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, end ext, _ = sub(D, D.(_glue)) + println(ext == C2) 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)) qC2 = discriminant_group(C2) - ok, phi2 = is_isometric_with_isometry(qC2, disc) - @assert ok + OqC2 = orthogonal_group(C2) + phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) + @assert is_bijective(phi2) C2 = lattice_with_isometry(C2, fC2, ambient_representation=false) im2_phi, _ = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)])) im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) - stab = Tuple{AutomorphismGroupElem{TorQuadModule}, AutomorphismGroupElem{TorQuadModule}}[(x, imB(compose(inv(phig), compose(hom(x), phig)))) for x in gens(im3)] - stab = AutomorphismGroupElem{TorQuadModule}[OSAinOD(x[1])*OSBinOD(x[2]) for x in stab] + stab = Tuple{AutomorphismGroupElem{TorQuadModule}, AutomorphismGroupElem{TorQuadModule}}[(actA\x, actB\(imB(compose(inv(phig), compose(hom(x), phig))))) for x in gens(im3)] + stab = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x[1])*OqBinOD(x[2]) for x in stab] stab = union(stab, kerA) stab = union(stab, kerB) stab = TorQuadModuleMor[restrict_automorphism(g, j) for g in stab] diff --git a/experimental/LatWithIsom/Enumeration.jl b/experimental/LatWithIsom/Enumeration.jl index 3d9d4baf7416..4305728a8718 100644 --- a/experimental/LatWithIsom/Enumeration.jl +++ b/experimental/LatWithIsom/Enumeration.jl @@ -20,7 +20,7 @@ export representatives_of_hermitian_type ################################################################################## # The tuples in output are pairs of positive integers! -function _tuples_divisors(d::Hecke.IntegerUnion) +function _tuples_divisors(d::T) where T <: Hecke.IntegerUnion div = divisors(d) return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] end @@ -29,7 +29,7 @@ end # discriminant for the genera A and B to glue to fit in C. d is # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. -function _find_D(d::Hecke.IntegerUnion, m::Int, p::Int) +function _find_D(d::T, m::Int, p::Int) where T <: Hecke.IntegerUnion @assert is_prime(p) @assert d != 0 @@ -55,16 +55,24 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::Hecke.IntegerUnion, even = true) +function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::Hecke.IntegerUnion, even = true; pos::Int = -1) L = ZGenus[] if r == 0 && d == 1 return ZGenus[genus(Zlattice(gram = matrix(QQ, 0, 0, [])))] end - for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] - gen = Zgenera((s1,s2), d, even=even) - filter!(G -> divides(numerator(scale(G)), s)[1], gen) - filter!(G -> divides(p*l, numerator(level(G)))[1], gen) + if pos >= 0 + neg = r-pos + gen = Zgenera((pos, neg), d, even=even) + filter!(G -> Hecke.divides(numerator(scale(G)), s)[1], gen) + filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) + else + for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] + gen = Zgenera((s1,s2), d, even=even) + filter!(G -> Hecke.divides(numerator(scale(G)), s)[1], gen) + filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) + append!(L, gen) + end end return L end @@ -89,7 +97,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) return false end - @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" + @req Hecke.divides(rank(B), p-1)[1] "p-1 must divide the rank of B" lA = ngens(discriminant_group(A)) lB = ngens(discriminant_group(B)) @@ -113,7 +121,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) return false end - if !(divides(scale(AperpB), scale(C))[1] && divides(p*level(C), level(AperpB))[1]) + if !(Hecke.divides(scale(AperpB), scale(C))[1] && Hecke.divides(p*level(C), level(AperpB))[1]) return false end @@ -121,7 +129,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B qA = discriminant_group(A) qB = discriminant_group(B) - if !divides(numerator(det(C)), p)[1] + if !Hecke.divides(numerator(det(C)), p)[1] return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1])[1] end @@ -216,21 +224,39 @@ $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and See Algorithm 1 of [BH22]. """ -function admissible_triples(G::ZGenus, p::Int64) +function admissible_triples(G::ZGenus, p::Int64; 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" n = rank(G) + p, _ = signature_pair(G) + if pA >= 0 + @req pA <= p "Wrong restrictions" + if pB >= 0 + @req pA + pB == p "Wrong restrictions" + else + pB = p - pA + end + elseif pB >= 0 + @req pB <= p "Wrong restrictions" + pA = p - pB + end d = numerator(det(G)) even = iseven(G) L = Tuple{ZGenus, ZGenus}[] for ep in 0:div(n, p-1) rp = (p-1)*ep + if pB >= 0 + rp >= pB || continue + end r1 = n - rp + if pA >= 0 + r1 >= pA || continue + end m = min(ep, r1) D = _find_D(d, m, p) for (d1, dp) in D - L1 = _find_L(r1, d1, numerator(scale(G)), numerator(level(G)), p, even) - Lp = _find_L(rp, dp, numerator(scale(G)), numerator(level(G)), p, even) + L1 = _find_L(r1, d1, numerator(scale(G)), numerator(level(G)), p, even, pos = pA) + Lp = _find_L(rp, dp, numerator(scale(G)), numerator(level(G)), p, even, pos = pB) for (A, B) in [(A, B) for A in L1 for B in Lp] if is_admissible_triple(A, B, G, p) push!(L, (A, B)) @@ -241,7 +267,7 @@ function admissible_triples(G::ZGenus, p::Int64) return L end -admissible_triples(L::T, p::Integer) where T <: Union{ZLat, LatWithIsom} = admissible_triples(genus(L), p) +admissible_triples(L::T, p::Integer; pA::Int = -1, pB::Int = -1) where T <: Union{ZLat, LatWithIsom} = admissible_triples(genus(L), p, pA = pA, pB = pB) ################################################################################## # @@ -301,7 +327,7 @@ function _possible_signatures(s1, s2, E, rk) ok, q = Hecke.is_cyclotomic_type(E) @assert ok @assert iseven(s2) - @assert divides(2*(s1+s2), euler_phi(q))[1] + @assert Hecke.divides(2*(s1+s2), euler_phi(q))[1] n = l = divexact(s2, 2) K = base_field(E) @@ -370,7 +396,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) @info "Order bigger than 3" - ok, rk = divides(rk, euler_phi(n*m)) + ok, rk = Hecke.divides(rk, euler_phi(n*m)) ok || return reps @@ -455,7 +481,7 @@ function _representative(t::Dict; check::Bool = true) return trace_lattice(L, order = n) end - ok, rk = divides(rk, euler_phi(n)) + ok, rk = Hecke.divides(rk, euler_phi(n)) ok || reps @@ -524,14 +550,8 @@ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = - reps = LatWithIsom[] @info "Compute admissible triples" - atp = admissible_triples(Lf, p) - if pA >= 0 - filter!(t -> signature_pair(t[1])[1] == pA, atp) - end - if pB >= 0 - filter!(t -> singature_pair(t[2][1]) == pB, atp) - end - @info "$(atp) admissible triple(s)" + atp = admissible_triples(Lf, p, pA = pA, pB = pB) + @info "$(length(atp)) admissible triple(s)" for (A, B) in atp LB = lattice_with_isometry(representative(B)) RB = representatives_of_hermitian_type(LB, p*q^d) @@ -607,7 +627,7 @@ function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) is_empty(A) && return reps B = splitting_of_prime_power(B0, p) for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) - b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue + b == 1 && !Hecke.divides(order_of_isometry(L1), p)[1] && !Hecke.divides(order_of_isometry(L2), p)[1] && continue E = admissible_equivariant_primitive_extensions(L1, L2, Lf, q, check=false) @assert b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) @@ -653,7 +673,7 @@ function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) phi = minpoly(Lf) chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) - @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" + @req Hecke.divides(chi, phi)[1] "Minimal polynomial is not of the correct form" reps = LatWithIsom[] @@ -662,7 +682,7 @@ function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) end A0 = kernel_lattice(Lf, p^d*q^e) - bool, r = divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) + bool, r = Hecke.divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) @assert bool B0 = kernel_lattice(Lf, r) diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index b896d4068196..ffe55008b49b 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -14,7 +14,7 @@ export type import Hecke: kernel_lattice, invariant_lattice, rank, genus, basis_matrix, gram_matrix, ambient_space, rational_span, scale, signature_tuple, is_integral, det, norm, degree, discriminant, charpoly, minpoly, - rescale, dual, lll, discriminant_group + rescale, dual, lll, discriminant_group, divides, lattice ############################################################################### # # String I/O @@ -377,7 +377,7 @@ If it exists, the hermitian structure is cached. n = order_of_isometry(Lf) H, l = LWI._hermitian_structure(lattice(Lf), f, n = n, check = false, - ambient_representation = false) + ambient_representation = false) set_attribute!(Lf, :transfert_data, l) return H end @@ -505,7 +505,10 @@ end # ############################################################################### -divides(k::PosInf, n::Int) = true +function _divides(k::IntExt, n::Int) + is_finite(k) && return Hecke.divides(k, n)[1] + return true +end @doc Markdown.doc""" kernel_lattice(Lf::LatWithIsom, p::Union{fmpz_poly, QQPolyRingElem}) @@ -543,7 +546,7 @@ Given a lattice with isometry $(L, f)$ and an integer `l`, return the kernel lattice of $(L, f)$ associated to the `l`-th cyclotomic polynomial. """ function kernel_lattice(Lf::LatWithIsom, l::Integer) - @req divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" + @req _divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" p = cyclotomic_polynomial(l) return kernel_lattice(Lf, p) end From 262aa6ea97468ab42a8d0945db02c7495bd53845 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 22 Mar 2023 17:39:16 +0100 Subject: [PATCH 38/76] more on hermitian miranda-morrsion --- experimental/LatWithIsom/Embeddings.jl | 6 +- experimental/LatWithIsom/Enumeration.jl | 7 +- experimental/LatWithIsom/HMM.jl | 397 +++++++++++++++++------- experimental/LatWithIsom/LatWithIsom.jl | 8 +- 4 files changed, 291 insertions(+), 127 deletions(-) diff --git a/experimental/LatWithIsom/Embeddings.jl b/experimental/LatWithIsom/Embeddings.jl index 08d1107ec7e6..c1ae311724e8 100644 --- a/experimental/LatWithIsom/Embeddings.jl +++ b/experimental/LatWithIsom/Embeddings.jl @@ -15,6 +15,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu 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(v -> AinD(v[1])*BinD(v[2]) == 0, Hecke.cartesian_product_iterator([gens(A), gens(B)], inplce=true)) OD = orthogonal_group(D) OA = orthogonal_group(A) OB = orthogonal_group(B) @@ -766,10 +767,9 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, OqA = domain(OqAinOD) OqB = domain(OqBinOD) - # if the glue valuation is zero, then we glue along the trivial group and we don't + # if the glue valuation is zero, then we glue along the trivial group and we don't # have much more to do. Since the triple is p-admissible, A+B = C if g == 0 - println(0) geneA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(OqA(a.X)) for a in gens(GA)] geneB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(OqB(b.X)) for b in gens(GB)] gene = vcat(geneA, geneB) @@ -795,7 +795,6 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, end return results end - println("not 0") # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map VA, VAinqA, fVA = _get_V(lattice(A), qA, isometry(A), fqA, minpoly(B), p) VB, VBinqB, fVB = _get_V(lattice(B), qB, isometry(B), fqB, minpoly(A), p) @@ -809,7 +808,6 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # discriminant groups l = level(genus(C)) - # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones diff --git a/experimental/LatWithIsom/Enumeration.jl b/experimental/LatWithIsom/Enumeration.jl index 4305728a8718..6e467655e055 100644 --- a/experimental/LatWithIsom/Enumeration.jl +++ b/experimental/LatWithIsom/Enumeration.jl @@ -328,7 +328,6 @@ function _possible_signatures(s1, s2, E, rk) @assert ok @assert iseven(s2) @assert Hecke.divides(2*(s1+s2), euler_phi(q))[1] - n = l = divexact(s2, 2) K = base_field(E) inf = real_places(K) @@ -429,6 +428,9 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) if !is_integral(DE*scale(H)) continue end + if iseven(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) + continue + end @info "$H" M = trace_lattice(H) @assert det(M) == d @@ -509,6 +511,9 @@ function _representative(t::Dict; check::Bool = true) if !is_integral(DE*scale(H)) continue end + if iseven(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) + continue + end H = H M = trace_lattice(H) @assert det(M) == d diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index cdb63b53af75..cc1b0e5d1018 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -1,7 +1,7 @@ ############################################################################### # -# Local determinants morphism +# Computations of the finite quotients E_0/E^i: subsection 6.8. of BH22 # ############################################################################### @@ -17,20 +17,20 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) function dlog(x::Hecke.NfRelElem) @assert parent(x) == E - d = denominator(x) - xabs = OEabs(d*EabstoE\(x)) - dabs = OEabs(d) - F = prime_decomposition(OEabs, EabstoE\(minimum(P))) + d = denominator(x, OE) + xabs = d*EabstoE\(x) + dabs = copy(d) + F = prime_decomposition(OEabs, minimum(EabstoE\P)) for PP in F - @assert valuation(EasbtoE\(x), PP[1]) >= 0 - api = OEabs(anti_uniformizer(PP[1])) + @assert valuation(EabstoE\(x), PP[1]) >= 0 + api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp xabs *= api^exp end - xabs_image = mURPabs\(mRPabs(xabs)) - dabs_image = mURPabs\(mRPabs(dabs)) + xabs_image = mURPabs\mRPabs(OEabs(xabs)) + dabs_image = mURPabs\mRPabs(OEabs(dabs)) ret = xabs_image - dabs_image @@ -39,7 +39,7 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) function exp(k::GrpAbFinGenElem) @assert parent(k) === URPabs - x = EabstoE(Eabs(mRPabs\(mURPabs(k)))) + x = EabstoE(Eabs(mRPabs\mURPabs(k))) @assert dlog(x) == k return x end @@ -75,20 +75,20 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) function dlog(x::Hecke.NfRelElem) @assert parent(x) === E - d = denominator(x) - xabs = OEabs(EabstoE\(d*x)) - dabs = OEabs(d) - F = prime_decomposition(OEabs, EabstoE\(minimum(P))) + d = denominator(x, OE) + xabs = EabstoE\(d*x) + dabs = copy(d) + F = prime_decomposition(OEabs, minimum(EabstoE\P)) for PP in F @assert valuation(EabstoE\(x), PP[1]) >= 0 - api = OEabs(anti_uniformizer(PP[1])) + api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp xabs *= api^exp end - xabs_image = mURPabs\(mRPabs(xabs)) - dabs_image = mURPabs\(mRPabs(dabs)) + xabs_image = mURPabs\(mRPabs(OEabs(xabs))) + dabs_image = mURPabs\(mRPabs(OEabs(dabs))) ret = mS\(mK\(xabs_image - dabs_image)) @@ -121,8 +121,6 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) end j = Int(ceil(jj)) - Pi = P^i - Eabs, EabstoE = Hecke.absolute_simple_field(E) OK = order(p) Rp, mRp = quo(OK, p^j) @@ -146,20 +144,20 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) function dlog(x::Hecke.NfRelElem) @assert parent(x) === E - d = denomiator(x) - xabs = OEabs(EasbtoE\(d*x)) - dabs = OEabs(d) - F = prime_decomposition(OEabs, EabstoE\(minimum(P))) + d = denominator(x, OE) + xabs = EabstoE\(d*x) + dabs = copy(d) + F = prime_decomposition(OEabs, minimum(EabstoE\P)) for PP in F @assert valuation(EabstoE\(x), PP[1]) >= 0 - api = OEabs(anti_uniformizer(PP[1])) + api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp xabs *= api^exp end - xabs_image = mURPabs\(mRPabs(xabs)) - dabs_image = mURPabs\(mRPabs(dabs)) + xabs_image = mURPabs\(mRPabs(OEabs(xabs))) + dabs_image = mURPabs\(mRPabs(OEabs(dabs))) ret = mS\(mK\ (xabs_image - dabs_image)) return ret @@ -174,6 +172,12 @@ function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) @assert order(p) === base_ring(O) F = prime_decomposition(O, p) P = F[1][1] + if i == 0 + A = abelian_group() + function dlog_0(x::Hecke.NfRelElem); return id(A); end; + function exp_0(x::GrpAbFinGenElem); return one(E); end; + return A, dexp_0, dlog_0, P + end if length(F) == 2 S, dexp, dlog = _get_quotient_split(P, i) elseif F[1][2] == 1 @@ -185,14 +189,15 @@ function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) end function _get_product_quotient(E::Hecke.NfRel, Fac) - groups = [] + OE = maximal_order(E) + groups = GrpAbFinGen[] exps = [] dlogs = [] Ps = [] if length(Fac) == 0 A = abelian_group() - function dlog_0(x::Hecke.NfRelElem); return one(A); end; + function dlog_0(x::Hecke.NfRelElem); return id(A); end; function exp_0(x::GrpAbFinGenElem); return one(E); end; return A, dlog_0, exp_0 end @@ -210,45 +215,102 @@ function _get_product_quotient(E::Hecke.NfRel, Fac) return groups[1], dlogs[1], exps[1] end - G, inj, proj = biproduct(groups) + G, proj, inj = biproduct(groups...) function dlog(x::Vector{Hecke.NfRelElem}) - return sum([inj[i](dlogs[i](x)) for i in 1:length(Fac)]) + if length(x) == 1 + return sum([inj[i](dlogs[i](x[1])) for i in 1:length(Fac)]) + else + @assert length(x) == length(Fac) + return sum([inj[i](dlogs[i](x[i])) for i in 1:length(Fac)]) + end end function exp(x::GrpAbFinGenElem) - v = Hecke.NfRelOrdElem[OE(exps[i](proj[i](x))) for i in 1:length(Fac)] - a = crt(v, [Ps[i]^(3*Fac[i][2]) for i in 1:length(Ps)]) - @assert dlog(a) == x - return a + v = Hecke.NfRelElem[exps[i](proj[i](x)) for i in 1:length(Fac)] + @assert dlog(v) == x + return v end return G, dlog, exp end -function _local_determinant_morphism(Lf::LatWithIsom) +############################################################################### +# +# Local determinants morphism, alias \delta in BH22 +# +############################################################################### + +# We collect all the prime ideals p for which the local quotient +# (D^{-1}L^#/L)_p is not unimodular + +function _elementary_divisors(L::HermLat, D::Hecke.NfRelOrdIdl) + Ps = collect(keys(factor(D))) + primess = NfOrdIdl[] + for P in Ps + p = minimum(P) + ok, a = is_modular(L, p) + ok && a == -valuation(D, P) && continue + push!(primess, p) + end + for p in primes(genus(L)) + (p in primess) && continue + push!(primess, p) + end + return primess +end + +# We compute here the map delta from Theorem 6.15 of BH22. Its kernel is +# precisely the image of the map O(L, f) \to O(D_L, D_f). + +function _local_determinants_morphism(Lf::LatWithIsom) @assert is_of_hermitian_type(Lf) + qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) + # Since any isometry of L centrlizing f induces an isometry of qL centralising + # 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 BH22. G, _ = centralizer(OqL, fqL) + + # 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, + # where D is the absolute different of the base algebra of H (a cyclotomic + # field). H = hermitian_structure(Lf) - l = get_attribute(Lf, :transfert_data) - E = base_ring(H) + l = get_attribute(Lf, :transfert_data) #TODO: remove once things have changed on Hecke + + E = base_field(H) OE = maximal_order(E) DKQ = different(base_ring(OE))*OE DEK = different(OE) DEQ = DEK*DKQ - DinvHdash = inv(DEQ)*dual(H) - res = Hecke.SpaceRes(ambient_space(Lf), ambient_space(H)) - Lv = trace_lattice(DinvH, res, l = l) - @assert cover(q) === lattice(Lv) - @assert ambient_isometry(Lv) == ambient_isometry(Lf) - gene_herm = [_transfer_discriminant_isometries(DinvHdash, res, g, l) for g in gens(G)] - @assert all(m -> m*gram_matrix_of_rational_span(DinvH)*map_entries(involution(E), transpose(m)) == gram_matrix_of_rational_span(DinvH), gene_herm) - S = elementary_divisors(DinvH, H) + H2 = inv(DEQ)*dual(H) + @assert is_sublattice(H2, H) # This should be true since the lattice in Lf is integral + + res = Hecke.SpaceRes{typeof(ambient_space(Lf)), typeof(ambient_space(H))}(ambient_space(Lf), ambient_space(H)) #TODO: remove once things have changed on Hecke + + # These are invertible matrices representing lifts to the ambient space of the + # lattice in Lf of the generators of G. These are not isometries. We will + # transfer them using `res` to the ambient space of H. They are though isometry + # of H2/H. Our goal lift them to isometry of H2 up to a good enough precision + # and compute their determinant. + gene_herm = [_transfer_discriminant_isometries(res, g, l) for g in gens(G)] - N = norm(L) + # We want only the prime ideal in O_K which divides the quotient H2/H. For + # this, we collect all the primes dividing DEQ or for which H is not locally + # unimodular. Then, we check for which prime ideals p, the local quotient + # (H2/H)_p is non trivial. + S = _elementary_divisors(H, DEQ) + + N = norm(H) + + # We want to produce the product of the F(H)/F^#(L). For + # this, we create the map between the alternative products R/F(L) \to R/F^#(L) + # whose kernel is exactly what we want. Here R is just a big enough group. + # Note that here the products can be constructed since there are only finitely + # many primes in both cases for which the local quotients are non-trivial. Fsharpdata = Tuple{NfOrdIdl, Int}[] for p in S @@ -261,23 +323,37 @@ function _local_determinant_morphism(Lf::LatWithIsom) RmodFsharp, Fsharplog, Fsharpexp = _get_product_quotient(E, Fsharpdata) + # Here thanks to results due to M. Kirschmer, some of the p's used for the + # previous product of quotients might produce trivial factors. We can detect + # them and this is the goal of the `_is_special` routine. For those particular + # prime, we use the trivial group as factor + # + # Note: we do not remove the factor to be able to map the corresponding the + # factors between the two products we construct. We do this componentwise to + # avoid computing unncessary crt. This will hold for the rest of the code, we + # for those particular objects, the `dlog` maps take vectors, corresponding to + # finite adeles. Fdata = Tuple{NfOrdIdl, Int}[] for p in S if !_is_special(H, p) - continue + push!(Fdata, (p, 0)) + else + lp = prime_decomposition(OE, p) + P = lp[1][1] + e = valuation(DEK, P) + push!(Fdata, (p, e)) end - lp = prime_decomposition(OE, p) - P = lp[1][1] - e = valuation(DEK, P) - push!(Fdata, (p, e)) end - RmodF, Flog, Fexp = _get_product_quotient(E, Fdata) + RmodF, Flog, _ = _get_product_quotient(E, Fdata) + # Since we can map F^#(L) into F(L), this next map is natural. A = [Flog(Fsharpexp(g)) for g in gens(RmodFsharp)] f = hom(gens(RmodFsharp), A) FmodFsharp, j = kernel(f) + # Now according to Theorem 6.15 of BH22, it remains to quotient out the image + # of the units in E of norm 1. Eabs, EabstoE = Hecke.absolute_simple_field(E) OEabs = maximal_order(Eabs) UOEabs, mUOEabs = unit_group(OEabs) @@ -286,17 +362,52 @@ function _local_determinant_morphism(Lf::LatWithIsom) fU = hom(UOEabs, UOK, [mUOK\(norm(OE(EabstoE(Eabs(mUOEabs(k)))))) for k in gens(UOEabs)]) KU, jU = kernel(fU) - gene_norm_one = Hecke.NfRelOrdElem[OE(EabstoE(Eabs(mUOEabs(jU(k))))) for k in gens(KU)] + gene_norm_one = Hecke.NfRelElem[EabstoE(Eabs(mUOEabs(jU(k)))) for k in gens(KU)] + + # Now according to Theorem 6.15 of BH22, it remains to quotient out + FOEmodFsharp, m = sub(RmodFsharp, elem_type(RmodFsharp)[Fsharplog([x]) for x in gene_norm_one]) - FOEmodFsharp, m = sub(RmodFsharp, ) + I = intersect(FOEmodFsharp, FmodFsharp) + # Q is where the determinant of our lifts to good precision will live. So + # we just need to create the map from G to Q. + Q, mQ = quo(FmodFsharp, I) + + function dlog(x::Vector{Hecke.NfRelElem}) + @assert length(x) == length(Fsharpdata) + return mQ(FmodFsharp(Fsharplog(x))) + end + + imgs = elem_type(Q)[] + # For each of our matrices in gene_herm, we do successive P-adic liftings in + # order to approximate an isometry of D^{-1}H^#, up to a certain precision + # (given by Theorem 6.25 in BH22). We do this for all the primes we have to + # 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 gene_herm + ds = elem_type(E)[] + for p in S + lp = prime_decomposition(OE, p) + P = lp[1][1] + k = valuation(N, p) + a = valuation(DEQ, P) + e = valuation(DEK, P) + g_approx = _approximate_isometry(DinvHdash, gtemp, P, e, a, k) + push!(ds, det(gtemp)) + end + push!(imgs, dlog(ds)) + end + f = hom(G, Q, gens(G), imgs_Q) + return f end +# We check whether for the prime ideal p E_O(L_p) != F(L_p). function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) OE = base_ring(L) E = nf(OE) lp = prime_decomposition(OE, p) - if lp[1][2] != 2 || !is_even(rank(L)) + if lp[1][2] != 2 || !Oscar.iseven(rank(L)) return false end _, _R, S = jordan_decomposition(L, p) @@ -327,6 +438,9 @@ end # ############################################################################### +# Starting from an isometry of the torsion quadratic module `domain(g)`, for +# which we assume that the cover M has full rank, we compute a fake lift to the +# ambient space of the cover. This is not an isometry, but induces g on `domain(g)`. function _get_ambient_isometry(g::AutomorphismGroupElem{TorQuadModule}) q = domain(g) L = cover(q) @@ -344,6 +458,11 @@ function _get_ambient_isometry(g::AutomorphismGroupElem{TorQuadModule}) return iB*M*B end +# Using the function used for the transfer construction, between the ambient +# space of the cover of our torsion module, and the ambient space of the +# corresponding hermitian structure, we transfer the fake lift computed with the +# previous function. This will be an invertible matrix, but the corresponding +# automorphism is not an isometry. function _transfer_discriminant_isometries(res::Hecke.SpaceRes, g::AutomorphismGroupElem{TorQuadModule}, l::QQMatrix) E = base_ring(codomain(res)) OE = maximal_order(E) @@ -351,111 +470,153 @@ function _transfer_discriminant_isometries(res::Hecke.SpaceRes, g::AutomorphismG q = domain(g) il = inv(l) @assert ambient_space(cover(q)) === domain(res) - gE = zero_matrix(OE, 0, rank(codomain(res))) + gE = zero_matrix(E, 0, rank(codomain(res))) vE = vec(collect(zeros(E, 1, rank(codomain(res))))) for i in 1:rank(codomain(res)) vE[i] = one(E) vQ = res\vE - vQ = matrix(QQ, 1, length(vQ), vQ)*l + vQ = matrix(QQ, 1, length(vQ), vQ)*il gvq = vQ*m - gvQ = vec(collect(gvq*il)) + gvQ = vec(collect(gvq*l)) gvE = res(gvQ) - gE = vcat(gE, matrix(OE, 1, length(gvE), gvE)) + gE = vcat(gE, matrix(E, 1, length(gvE), gvE)) vE[i] = zero(E) end return gE end -function _get_piecewise_actions_modular(H::Hecke.HermLat, g::MatrixElem{Hecke.NfRelOrdElem}, p::Hecke.NfOrdIdl, a::Int) - @assert g*gram_matrix_of_rational_span(H)*map_entries(involution(H), transpose(g)) == gram_matrix_of_rational_span(H) - E = base_field(H) - OE = base_ring(H) - @assert base_ring(g) === OE - gE = map_entries(E, g) - B, _, exp = jordan_decomposition(H, p) - j = findfirst(i -> i == -a, exp) - if j !== nothing - popat!(B, j) - popat!(exp, j) - end - local_act = typeof(g)[] - for b in B - gbE = solve(b, b*gE) - gb = map_entries(OE, gb) - push!(local_act, gb) - end - return exp, local_act +# the minimum P-valuation among all the non-zero entries of M +function _scale_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} + @assert nf(order(P)) === base_ring(M) + return minimum([valuation(v, P) for v in collect(M) if !iszero(v)]) +end + +# the minimum P-valuation among all the non-zero diagonal entries of M +function _norm_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} + @assert nf(order(P)) === base_ring(M) + return minimum([valuation(v, P) for v in diagonal(M) if !iszero(v)]) end -function _local_hermitian_lifting(G::MatrixElem{Hecke.NfRelElem}, F::MatrixElem{Hecke.NfRelOrdElem}, rho::Hecke.NfRelElem, l::Int, P::Hecke.NfRelOrdIdl; check = true) +# This is algorithm 8 of BH22: under the good assumptions, then we can do a +# P-adic lifting of a matrix which represents an isometry up to a certain +# precision. In this way, we approximate our matrix by another matrix, to a +# given precision and the new matrix defines also an isometry up to a finer +# precision than the initial matrix. +# +# We use this method iteratively to lift isometries (along a surjective map), by +# looking at better representatives until we reach a good enough precision for +# our purpose. +function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::Hecke.NfRelOrdIdl, e::Int, a::Int; check = true) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} @assert trace(rho) == 1 + @assert valuation(rho, P) == 1-e E = base_ring(G) + # G here is a local gram matrix @assert G == map_entries(involution(E), transpose(G)) - OE = base_ring(F) - @assert nf(OE) === E - OK = base_ring(OE) - DEK = different(OE) - DKQ = different(OK)*OE - DEQ = DKQ*DEK - e = valuation(rho, P) - e = 1 - e - @assert valuation(DEK, P) == e - a = valuation(DEQ, P) - FE = map_entries(E, F) - RE = G - FE*G*map_entries(involution(E), transpose(FE)) + @assert base_ring(F) === E + # R represents the defect, how far F is to be an isometry of G + R = G - F*G*map_entries(involution(E), transpose(F)) + + # These are the necessary conditions for the input of algorithm 8 in BH22 if check - @assert min([valuation(v, P) for v in collect(inv(G)) if !iszero(v)]) >= l+a - @assert min([valuation(v, P) for v in diagonal(inv(G)) if !iszero(v)]) >= e+a - @assert min([valuation(v, P) for v in collect(R) if !iszero(v)]) >= l-a - @assert min([valuation(v, P) for v in diagonal(R) if !iszero(v)]) >= l+e-1-a + @assert _scale_valuation(inv(G), P) >= 1+a + @assert _norm_valuation(inv(G), P) >= e+a + @assert _scale_valuation(R, P) >= l-a + @assert _norm_valuation(R, P) >= l+e-1-a end - UE = zero_matrix(E, nrows(RE), ncols(RE)) - for i in 1:nrows(RE) + # R is s-symmetric, where s is the canonical involution of E/K. We split R + # into U + D + s(U), i.e we take U to be the strict upper triangular part of + # R and D to be the diagonal. + U = zero_matrix(E, nrows(R), ncols(R)) + for i in 1:nrows(R) for j in 1:i-1 - UE[i, j] = RE[i, j] + U[i, j] = R[i, j] end end - diagE = RE - UE - map_entries(involution(E), transpose(UE)) + diag = R - U - map_entries(involution(E), transpose(U)) - newFE = FE + (UE + rho*diagE)*map_entries(involution(E), inv(transpose(FE)))*inv(G) - newF = map_entries(OE, newFE) + # this newF is suppose to be a better lift than F, i.e. it is congruent to F + # modulo P^{l+1} and the corresponding defect R2 has a higher P-valuation (so + # P-adic, we are close to have a proper isometry) + newF = F + (U + rho*diag)*map_entries(involution(E), inv(transpose(F)))*inv(G) if check l2 = 2*l+1 - @assert min([valuation(v, P) for v in collect(F-newF) if !is_zero(v)]) >= l+1 - R2E = G-newFE*G*map_entries(involution(E), transpose(newFE)) - @assert min([valuation(v, P) for v in collect(R2E) if !iszero(v)]) >= l2-a - @assert min([valuation(v, P) for v in diagonal(R2e) if !iszero(v)]) >= l2+e-1-a + @assert _scale_valuation(F-newF, P) >= l+1 + R2 = G-newF*G*map_entries(involution(E), transpose(newF)) + @assert _scale_valuation(R2, P) >= l2-a + @assert _norm_valuation(R2, P) >= l2+e-1-a end - return newF + return newF, l2 end -function _find_rho(P::Hecke.NfRelOrdIdl) +# Starting from our fake isometry g on H (which will be here D^{-1}H^#), and a +# prime ideal P, we iteratively lift g to a finer fake isometry, i.e. a matrix +# defining a P-local isometry up to the precision P^{2*k+a}. +function _approximate_isometry(H::Hecke.HermLat, g::T, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} + E = base_ring(g) + @assert base_field(H) === E + @assert nf(order(P)) === E + + ok, m = is_modular(H, minimum(p)) + + # This should never happend since we collect the prime ideals in such a way it + # is not possible. Though, we keep it in case something was forgotten before + if ok && (m == -a) + return identity_matrix(E, rank(H)) + end + + # we do know work on the p-completions, p = P \cap O_K + Bp = local_basis_matrix(H, minimum(P)) + Gp = Bp*gram_matrix_of_rational_span(H)*map_entries(involution(E), transpose(Bp)) + Fp = Bp*g*inv(Bp) + # 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)) + + rho = _find_rho(P, e) + k1 = _scale_valuation(Rp, P) + a + k2 = _norm_valuation(Rp, P) + a + 1 - e + l = min(k1, k2) + + while _scale_valuation(Rp, P) < 2*k+a + Fp, l = _local_hermitian_lifting(Gp, Fp, rho, l, P, e, a) + Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) + end + + return Fp +end + +# We need a special rho for Algorithm 8 of BH22: we construct such an element +# here, which will be used to lift fake isometries up to a better precision. +function _find_rho(P::Hecke.NfRelOrdIdl, e) OE = order(P) E = nf(OE) - dya = valuation(2, norm(P)) > 0 + dya = is_dyadic(P) !dya && return E(1//2) K = base_field(E) + Eabs, EabstoE = Hecke.absolute_simple_field(E) + Pabs = EabstoE\P + OEabs = order(Pabs) while true - Et, t = E["t"] - g = E(-rand(K, -5:5)^2-1) - nu = 2*valuation(E(2), P)-2 - nug = valuation(g, P) + println("bla") + Eabst, t = Eabs["t"] + g = EabstoE\(E(-rand(K, -5:5)^2-1)) + nu = 2*valuation(Eabs(2), Pabs)-2*e+2 + nug = valuation(g, Pabs) if nu == nug - d = denominator(g+1, OE) + d = denominator(g+1, OEabs) rt = roots(t^2 - (g+1)*d^2, max_roots = 1, ispure = true, is_normal=true) if !is_empty(rt) - break + rho = (1+rt[1])//2 + @assert valuation(rho, Pabs) == 1-e + @assert trace(EabstoE(rho)) == 1 + return EabstoE(rho) end end end - rho = (1+rt[1])//2 - @assert valuation(rho, P) == -1 - @assert trace(rho) == 1 - return rho end diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index ffe55008b49b..460abd51344d 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -362,14 +362,14 @@ function is_of_hermitian_type(Lf::LatWithIsom) end @doc Markdown.doc""" - hermitian_structure(Lf::LatWithIsom; check::Bool = true) -> HermLat + hermitian_structure(Lf::LatWithIsom) -> HermLat Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the underlying isometry `f` is irreducible and cyclotomic, return the hermitian structure of the underlying lattice `L` over the $n$th cyclotomic field, where $n$ is the order of `f`. -If it exists, the hermitian structure is cached. +If it exists, the hermitian structure is stored. """ @attr HermLat function hermitian_structure(Lf::LatWithIsom) @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" @@ -610,7 +610,7 @@ $\mathbb{Z}$-lattice $\Ker(f^k-1)$. for l in divs Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = Oscar._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false) + Hl = LWI._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false)[1] end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) @@ -631,7 +631,7 @@ function is_of_type(L::LatWithIsom, t::Dict) Hl = kernel_lattice(L, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1, 1, 2]) t[l][1] isa Hecke.HermGenus || return false - Hl = Oscar._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false, E = base_field(t[l][1])) + Hl = LWI._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false, E = base_field(t[l][1])) end genus(Hl) == t[l][1] || return false Al = kernel_lattice(L, x^l-1) From 49ad9d7cfa093979285f0c631ea7e60fd1d01a02 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 23 Mar 2023 09:08:06 +0100 Subject: [PATCH 39/76] little fix --- experimental/LatWithIsom/HMM.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index cc1b0e5d1018..6ba2b701f726 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -307,7 +307,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) N = norm(H) # We want to produce the product of the F(H)/F^#(L). For - # this, we create the map between the alternative products R/F(L) \to R/F^#(L) + # this, we create the map between the alternative products R/F^#(L) \to R/F(L) # whose kernel is exactly what we want. Here R is just a big enough group. # Note that here the products can be constructed since there are only finitely # many primes in both cases for which the local quotients are non-trivial. @@ -347,7 +347,6 @@ function _local_determinants_morphism(Lf::LatWithIsom) RmodF, Flog, _ = _get_product_quotient(E, Fdata) - # Since we can map F^#(L) into F(L), this next map is natural. A = [Flog(Fsharpexp(g)) for g in gens(RmodFsharp)] f = hom(gens(RmodFsharp), A) FmodFsharp, j = kernel(f) @@ -571,7 +570,7 @@ function _approximate_isometry(H::Hecke.HermLat, g::T, P::Hecke.NfRelOrdIdl, e:: # we do know work on the p-completions, p = P \cap O_K Bp = local_basis_matrix(H, minimum(P)) - Gp = Bp*gram_matrix_of_rational_span(H)*map_entries(involution(E), transpose(Bp)) + Gp = Bp*gram_matrix(ambient_space(H))*map_entries(involution(E), transpose(Bp)) Fp = Bp*g*inv(Bp) # This is the local defect. By default, it should have scale P-valuations -a # and norm P-valuation e-1-a From cce29f88737654ba5d5d46ccf13f86ac2fceca57 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 27 Mar 2023 15:43:39 +0200 Subject: [PATCH 40/76] more --- experimental/LatWithIsom/HMM.jl | 65 +++++++++----------- experimental/LatWithIsom/TraceEquivalence.jl | 2 +- 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index 6ba2b701f726..c7b06029f316 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -1,7 +1,7 @@ ############################################################################### # -# Computations of the finite quotients E_0/E^i: subsection 6.8. of BH22 +# Computations of the finite quotients E_0/E^i: subsection 6.8. of BH23 # ############################################################################### @@ -175,7 +175,7 @@ function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) if i == 0 A = abelian_group() function dlog_0(x::Hecke.NfRelElem); return id(A); end; - function exp_0(x::GrpAbFinGenElem); return one(E); end; + function dexp_0(x::GrpAbFinGenElem); return one(E); end; return A, dexp_0, dlog_0, P end if length(F) == 2 @@ -217,7 +217,7 @@ function _get_product_quotient(E::Hecke.NfRel, Fac) G, proj, inj = biproduct(groups...) - function dlog(x::Vector{Hecke.NfRelElem}) + function dlog(x::Vector) if length(x) == 1 return sum([inj[i](dlogs[i](x[1])) for i in 1:length(Fac)]) else @@ -237,7 +237,7 @@ end ############################################################################### # -# Local determinants morphism, alias \delta in BH22 +# Local determinants morphism, alias \delta in BH23 # ############################################################################### @@ -272,7 +272,6 @@ function _local_determinants_morphism(Lf::LatWithIsom) # 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 BH22. G, _ = centralizer(OqL, fqL) - # 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, # where D is the absolute different of the base algebra of H (a cyclotomic @@ -364,13 +363,13 @@ function _local_determinants_morphism(Lf::LatWithIsom) gene_norm_one = Hecke.NfRelElem[EabstoE(Eabs(mUOEabs(jU(k)))) for k in gens(KU)] # Now according to Theorem 6.15 of BH22, it remains to quotient out - FOEmodFsharp, m = sub(RmodFsharp, elem_type(RmodFsharp)[Fsharplog([x]) for x in gene_norm_one]) + FOEmodFsharp, m = sub(RmodFsharp, elem_type(RmodFsharp)[Fsharplog([x for i in 1:length(S)]) for x in gene_norm_one]) I = intersect(FOEmodFsharp, FmodFsharp) # Q is where the determinant of our lifts to good precision will live. So # we just need to create the map from G to Q. Q, mQ = quo(FmodFsharp, I) - + function dlog(x::Vector{Hecke.NfRelElem}) @assert length(x) == length(Fsharpdata) return mQ(FmodFsharp(Fsharplog(x))) @@ -391,8 +390,8 @@ function _local_determinants_morphism(Lf::LatWithIsom) k = valuation(N, p) a = valuation(DEQ, P) e = valuation(DEK, P) - g_approx = _approximate_isometry(DinvHdash, gtemp, P, e, a, k) - push!(ds, det(gtemp)) + g_approx = _approximate_isometry(H2, g, P, e, a, k) + push!(ds, det(g_approx)) end push!(imgs, dlog(ds)) end @@ -433,7 +432,7 @@ end ############################################################################### # -# Local hermitian lifting +# Local hermitian lifting - path to algorithm 8 of BH23 # ############################################################################### @@ -444,17 +443,10 @@ function _get_ambient_isometry(g::AutomorphismGroupElem{TorQuadModule}) q = domain(g) L = cover(q) @assert rank(L) == degree(L) - B = basis_matrix(L) - iB = inv(B) - M = zero_matrix(QQ, 0, rank(L)) - for i in 1:nrows(B) - v = vec(collect(B[i, :])) - vq = q(v) - gvq = g(vq) - gv = transpose(matrix(lift(gvq)))*iB - M = vcat(M, gv) - end - return iB*M*B + d = degree(L) + M1 = reduce(vcat, [matrix(QQ, 1, d, lift(t)) for t in gens(q)]) + M2 = reduce(vcat, [matrix(QQ, 1, d, lift(g(t))) for t in gens(q)]) + return solve(M1, M2) end # Using the function used for the transfer construction, between the ambient @@ -464,7 +456,6 @@ end # automorphism is not an isometry. function _transfer_discriminant_isometries(res::Hecke.SpaceRes, g::AutomorphismGroupElem{TorQuadModule}, l::QQMatrix) E = base_ring(codomain(res)) - OE = maximal_order(E) m = _get_ambient_isometry(g) q = domain(g) il = inv(l) @@ -474,9 +465,9 @@ function _transfer_discriminant_isometries(res::Hecke.SpaceRes, g::AutomorphismG for i in 1:rank(codomain(res)) vE[i] = one(E) vQ = res\vE - vQ = matrix(QQ, 1, length(vQ), vQ)*il + vQ = matrix(QQ, 1, length(vQ), vQ)*l gvq = vQ*m - gvQ = vec(collect(gvq*l)) + gvQ = vec(collect(gvq*il)) gvE = res(gvQ) gE = vcat(gE, matrix(E, 1, length(gvE), gvE)) vE[i] = zero(E) @@ -493,7 +484,8 @@ end # the minimum P-valuation among all the non-zero diagonal entries of M function _norm_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} @assert nf(order(P)) === base_ring(M) - return minimum([valuation(v, P) for v in diagonal(M) if !iszero(v)]) + r = minimum([valuation(v, P) for v in diagonal(M) if !iszero(v)]) + return r end # This is algorithm 8 of BH22: under the good assumptions, then we can do a @@ -507,7 +499,6 @@ end # our purpose. function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::Hecke.NfRelOrdIdl, e::Int, a::Int; check = true) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} @assert trace(rho) == 1 - @assert valuation(rho, P) == 1-e E = base_ring(G) # G here is a local gram matrix @assert G == map_entries(involution(E), transpose(G)) @@ -515,13 +506,12 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H # R represents the defect, how far F is to be an isometry of G R = G - F*G*map_entries(involution(E), transpose(F)) - # These are the necessary conditions for the input of algorithm 8 in BH22 if check @assert _scale_valuation(inv(G), P) >= 1+a - @assert _norm_valuation(inv(G), P) >= e+a + @assert _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a @assert _scale_valuation(R, P) >= l-a - @assert _norm_valuation(R, P) >= l+e-1-a + @assert _norm_valuation(R, P) + valuation(rho,P) >= l-a end # R is s-symmetric, where s is the canonical involution of E/K. We split R @@ -541,12 +531,12 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H # P-adic, we are close to have a proper isometry) newF = F + (U + rho*diag)*map_entries(involution(E), inv(transpose(F)))*inv(G) + l2 = 2*l+1 if check - l2 = 2*l+1 @assert _scale_valuation(F-newF, P) >= l+1 R2 = G-newF*G*map_entries(involution(E), transpose(newF)) @assert _scale_valuation(R2, P) >= l2-a - @assert _norm_valuation(R2, P) >= l2+e-1-a + @assert _norm_valuation(R2, P) + valuation(rho, P) >= l2-a end return newF, l2 @@ -560,7 +550,7 @@ function _approximate_isometry(H::Hecke.HermLat, g::T, P::Hecke.NfRelOrdIdl, e:: @assert base_field(H) === E @assert nf(order(P)) === E - ok, m = is_modular(H, minimum(p)) + ok, m = is_modular(H, minimum(P)) # This should never happend since we collect the prime ideals in such a way it # is not possible. Though, we keep it in case something was forgotten before @@ -577,9 +567,7 @@ function _approximate_isometry(H::Hecke.HermLat, g::T, P::Hecke.NfRelOrdIdl, e:: Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) rho = _find_rho(P, e) - k1 = _scale_valuation(Rp, P) + a - k2 = _norm_valuation(Rp, P) + a + 1 - e - l = min(k1, k2) + l = 0 while _scale_valuation(Rp, P) < 2*k+a Fp, l = _local_hermitian_lifting(Gp, Fp, rho, l, P, e, a) @@ -589,13 +577,18 @@ function _approximate_isometry(H::Hecke.HermLat, g::T, P::Hecke.NfRelOrdIdl, e:: return Fp end -# We need a special rho for Algorithm 8 of BH22: we construct such an element +# We need a special rho for Algorithm 8 of BH23: we construct such an element # here, which will be used to lift fake isometries up to a better precision. function _find_rho(P::Hecke.NfRelOrdIdl, e) OE = order(P) E = nf(OE) + lp = prime_decomposition(OE, minimum(P)) dya = is_dyadic(P) !dya && return E(1//2) + lp = prime_decomposition(OE, minimum(P)) + if lp[1][2] == 1 + return Hecke._special_unit(P, minimum(P)) + end K = base_field(E) Eabs, EabstoE = Hecke.absolute_simple_field(E) Pabs = EabstoE\P diff --git a/experimental/LatWithIsom/TraceEquivalence.jl b/experimental/LatWithIsom/TraceEquivalence.jl index 0506e878112b..9f5a5021d9e2 100644 --- a/experimental/LatWithIsom/TraceEquivalence.jl +++ b/experimental/LatWithIsom/TraceEquivalence.jl @@ -235,7 +235,7 @@ function _hermitian_structure(_L::ZLat, f::QQMatrix; E = nothing, vi[1,1+(i-1)*euler_phi(n)] = one(QQ) vj = deepcopy(v) vj[1,1+(j-1)*euler_phi(n)] = one(QQ) - alpha = sum([(vi*G*transpose(vj*mb^k))[1]*b^k for k in 0:n-1]) + alpha = sum([((vi*mb^k)*G*transpose(vj))[1]*b^k for k in 0:n-1]) gram[i,j] = alpha end end From 509ac02a3d3281193480f7c4864577133af09f3d Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 29 Mar 2023 08:53:32 +0200 Subject: [PATCH 41/76] some fixes --- Project.toml | 2 +- experimental/LatWithIsom/HMM.jl | 14 +- experimental/LatWithIsom/LWI.jl | 1 - experimental/LatWithIsom/LatWithIsom.jl | 17 +- experimental/LatWithIsom/TraceEquivalence.jl | 248 ------------------- 5 files changed, 15 insertions(+), 267 deletions(-) delete mode 100644 experimental/LatWithIsom/TraceEquivalence.jl diff --git a/Project.toml b/Project.toml index c34f9640c76e..c375626916f4 100644 --- a/Project.toml +++ b/Project.toml @@ -29,7 +29,7 @@ AbstractAlgebra = "0.28.4" AlgebraicSolving = "0.3.0" DocStringExtensions = "0.8, 0.9" GAP = "0.9.4" -Hecke = "0.18.2" +Hecke = "0.18.5" JSON = "^0.20, ^0.21" Nemo = "0.33" Polymake = "0.9.0" diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index c7b06029f316..08d38e11bc4d 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -277,7 +277,6 @@ function _local_determinants_morphism(Lf::LatWithIsom) # where D is the absolute different of the base algebra of H (a cyclotomic # field). H = hermitian_structure(Lf) - l = get_attribute(Lf, :transfert_data) #TODO: remove once things have changed on Hecke E = base_field(H) OE = maximal_order(E) @@ -288,14 +287,14 @@ function _local_determinants_morphism(Lf::LatWithIsom) H2 = inv(DEQ)*dual(H) @assert is_sublattice(H2, H) # This should be true since the lattice in Lf is integral - res = Hecke.SpaceRes{typeof(ambient_space(Lf)), typeof(ambient_space(H))}(ambient_space(Lf), ambient_space(H)) #TODO: remove once things have changed on Hecke + res = get_attribute(Lf, :transfer_data) # These are invertible matrices representing lifts to the ambient space of the # lattice in Lf of the generators of G. These are not isometries. We will # transfer them using `res` to the ambient space of H. They are though isometry # of H2/H. Our goal lift them to isometry of H2 up to a good enough precision # and compute their determinant. - gene_herm = [_transfer_discriminant_isometries(res, g, l) for g in gens(G)] + gene_herm = [_transfer_discriminant_isometries(res, g) for g in gens(G)] # We want only the prime ideal in O_K which divides the quotient H2/H. For # this, we collect all the primes dividing DEQ or for which H is not locally @@ -385,6 +384,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) for g in gene_herm ds = elem_type(E)[] for p in S + println(p) lp = prime_decomposition(OE, p) P = lp[1][1] k = valuation(N, p) @@ -454,20 +454,19 @@ end # corresponding hermitian structure, we transfer the fake lift computed with the # previous function. This will be an invertible matrix, but the corresponding # automorphism is not an isometry. -function _transfer_discriminant_isometries(res::Hecke.SpaceRes, g::AutomorphismGroupElem{TorQuadModule}, l::QQMatrix) +function _transfer_discriminant_isometries(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}) E = base_ring(codomain(res)) m = _get_ambient_isometry(g) q = domain(g) - il = inv(l) @assert ambient_space(cover(q)) === domain(res) gE = zero_matrix(E, 0, rank(codomain(res))) vE = vec(collect(zeros(E, 1, rank(codomain(res))))) for i in 1:rank(codomain(res)) vE[i] = one(E) vQ = res\vE - vQ = matrix(QQ, 1, length(vQ), vQ)*l + vQ = matrix(QQ, 1, length(vQ), vQ) gvq = vQ*m - gvQ = vec(collect(gvq*il)) + gvQ = vec(collect(gvq)) gvE = res(gvQ) gE = vcat(gE, matrix(E, 1, length(gvE), gvE)) vE[i] = zero(E) @@ -568,7 +567,6 @@ function _approximate_isometry(H::Hecke.HermLat, g::T, P::Hecke.NfRelOrdIdl, e:: rho = _find_rho(P, e) l = 0 - while _scale_valuation(Rp, P) < 2*k+a Fp, l = _local_hermitian_lifting(Gp, Fp, rho, l, P, e, a) Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) diff --git a/experimental/LatWithIsom/LWI.jl b/experimental/LatWithIsom/LWI.jl index 2116594d6942..90d453ad3c28 100644 --- a/experimental/LatWithIsom/LWI.jl +++ b/experimental/LatWithIsom/LWI.jl @@ -13,7 +13,6 @@ end include("Types.jl") include("HMM.jl") -include("TraceEquivalence.jl") include("LatWithIsom.jl") include("Enumeration.jl") include("Embeddings.jl") diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index 460abd51344d..bf5fb01364dd 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -1,6 +1,4 @@ export ambient_isometry -export coinvariant_lattice -export hermitian_structure export image_centralizer_in_Oq export isometry export is_of_hermitian_type @@ -14,7 +12,9 @@ export type import Hecke: kernel_lattice, invariant_lattice, rank, genus, basis_matrix, gram_matrix, ambient_space, rational_span, scale, signature_tuple, is_integral, det, norm, degree, discriminant, charpoly, minpoly, - rescale, dual, lll, discriminant_group, divides, lattice + rescale, dual, lll, discriminant_group, divides, lattice, + hermitian_structure, coinvariant_lattice + ############################################################################### # # String I/O @@ -374,11 +374,10 @@ If it exists, the hermitian structure is stored. @attr HermLat function hermitian_structure(Lf::LatWithIsom) @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" f = isometry(Lf) - n = order_of_isometry(Lf) - H, l = LWI._hermitian_structure(lattice(Lf), f, n = n, check = false, - ambient_representation = false) - set_attribute!(Lf, :transfert_data, l) + H, res = Hecke.hermitian_structure_with_transfer_data(lattice(Lf), f, ambient_representation = false) + + set_attribute!(Lf, :transfer_data, res) return H end @@ -610,7 +609,7 @@ $\mathbb{Z}$-lattice $\Ker(f^k-1)$. for l in divs Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = LWI._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false)[1] + Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl), check=false, ambient_representation=false)[1] end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) @@ -631,7 +630,7 @@ function is_of_type(L::LatWithIsom, t::Dict) Hl = kernel_lattice(L, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1, 1, 2]) t[l][1] isa Hecke.HermGenus || return false - Hl = LWI._hermitian_structure(lattice(Hl), isometry(Hl), n=order_of_isometry(Hl), check=false, ambient_representation=false, E = base_field(t[l][1])) + Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl), check=false, ambient_representation=false, E = base_field(t[l][1])) end genus(Hl) == t[l][1] || return false Al = kernel_lattice(L, x^l-1) diff --git a/experimental/LatWithIsom/TraceEquivalence.jl b/experimental/LatWithIsom/TraceEquivalence.jl deleted file mode 100644 index 9f5a5021d9e2..000000000000 --- a/experimental/LatWithIsom/TraceEquivalence.jl +++ /dev/null @@ -1,248 +0,0 @@ -export trace_lattice - -@doc Markdown.doc""" - trace_lattice(L::AbstractLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = gen(base_field(L)), - order::Integer = 2) where T -> LatWithIsom - -Given a lattice `L` which is either a $\mathbb Z$-lattice or a hermitian lattice -over the $E/K$ with `E` a cyclotomic field and `K` a maximal real subfield of -`E`, return the trace lattice `Lf` whose underlying lattice is the trace lattice -of `L` and the underlying map `f` is: - - the identity if `L` is a `ZLat` and `order == 1`; - - the opposite of the identity if `L` is a `ZLat` and `order == 2`; - - given by the multiplication by a $n$-th root of the unity if `L` is a - `HermLat`, where `n` is the order a primitive element in `E`. - -Note that the optional argument `order` has no effect if `L` is not a -$\mathbb Z$-lattice. - -The choice of `alpha` corresponds to the choice of a "twist" for the trace construction -using `restrict_scalars`. - -The choice of `beta` corresponds to the choice of a primitive root of unity in the -base field of `L`, in the hermitian case, used to construct the isometry `f`. If `beta` -is not a primitive root of the unity, but its normalisation is, i.e. $\beta/(s\beta)$ -where `s` is the non trivial involution of the base algebra of `L`, then its -normalisation is automatically used by default. - -Note that the isometry `f` computed is given by its action on the ambient space of the -trace lattice of `L`. -""" -function trace_lattice(L::Hecke.AbstractLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = gen(base_field(L)), - order::Integer = 2, - l = nothing) where T - - return trace_lattice_with_map(L, alpha=alpha, beta=beta, order=order, l = l)[1] -end - -function trace_lattice_with_map(L::Hecke.AbstractLat{T}; alpha::FieldElem = one(base_field(L)), - beta::FieldElem = gen(base_field(L)), - order::Integer = 2, - l = nothing) where T - E = base_field(L) - @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" - @req order > 0 "The order must be positive" - @req degree(L) == rank(L) "Lattice must be of full rank" - @req parent(beta) === E "beta must be an element of the base algebra of L" - n = degree(L) - s = involution(E) - if s(beta)*beta != 1 - beta = beta//s(beta) - end - - if E == QQ - V = ambient_space(L) - if order == 1 - f = identity_matrix(E, n) - elseif order == 2 - f = -identity_matrix(E, n) - else - error("For ZLat the order must be 1 or 2") - end - return lattice_with_isometry(L, f, order, check = false), VecSpaceRes{typeof(V), typeof(V)}(V, V) - end - - bool, m = Hecke.is_cyclotomic_type(E) - @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "The normalisation of beta must be a $m-primitive root of 1" - - Lres, f = restrict_scalars_with_map(L, QQ, alpha) - if l === nothing - l = identity_matrix(QQ, degree(Lres)) - end - Lres = lattice_in_same_ambient_space(Lres, basis_matrix(Lres)*inv(l)) - iso = zero_matrix(QQ, 0, degree(Lres)) - v = vec(zeros(QQ, 1, degree(Lres))) - - for i in 1:degree(Lres) - v[i] = one(QQ) - v2 = f(v) - v2 = beta.*v2 - v3 = (f\v2) - iso = vcat(iso, transpose(matrix(v3))) - v[i] = zero(QQ) - end - return lattice_with_isometry(Lres, iso, ambient_representation=true), f -end - -function trace_lattice(L::Hecke.AbstractLat{T}, res::Hecke.SpaceRes; beta::FieldElem = gen(base_field(L)), l = nothing) where T - @req codomain(res) === ambient_space(L) "f must be a map of restriction of scalars associated to the ambient space of L" - E = base_field(L) - @req maximal_order(E) == equation_order(E) "Equation order and maximal order must coincide" - @req degree(L) == rank(L) "Lattice must be of full rank" - @req parent(beta) == E "beta must be an element of the base algebra of L" - @req !is_zero(beta) "beta must be non zero" - s = involution(E) - if s(beta)*beta != 1 - beta = beta//s(beta) - end - - bool, m = Hecke.is_cyclotomic_type(E) - @req !bool || findfirst(i -> isone(beta^i), 1:m) == m "The normalisation of beta must be a $m-primitive root of 1" - - #Lres = restrict_scalars(L, f) - if l === nothing - il = identity_matrix(QQ, rank(domain(res))) - l = il - else - il = inv(l) - end - Babs = absolute_basis(E) - Mabs = zero_matrix(QQ, 0, rank(domain(res))) - for w in absolute_basis(L) - w = transpose(matrix(reduce(vcat, absolute_coordinates.(w)))) - v = w*l - Mabs = vcat(Mabs, v) - end - Lres = Hecke.lattice(domain(res), Mabs) - mb = absolute_representation_matrix(beta) - iso = deepcopy(mb) - for i in 2:degree(L) - iso = diagonal_matrix(iso, mb) - end - println(iso) - println(gram_matrix(Lres)) - return lattice_with_isometry(Lres, il*iso*l, ambient_representation=true) -end - -function absolute_representation_matrix(b::Hecke.NfRelElem) - E = parent(b) - n = absolute_degree(E) - B = absolute_basis(E) - m = zero_matrix(QQ, n, n) - for i in 1:n - bb = B[i] - v = absolute_coordinates(b*bb) - m[i,:] = transpose(matrix(v)) - end - return m -end - -function _hermitian_structure(_L::ZLat, f::QQMatrix; E = nothing, - n::Integer = -1, - check::Bool = true, - ambient_representation::Bool = true, - l = nothing) - if rank(_L) != degree(_L) - L = Zlattice(gram = gram_matrix(_L)) - if ambient_representation - ok, f = can_solve_with_solution(basis_matrix(_L), basis_matrix(_L)*f, side=:left) - @req ok "Isometry does not restrict to L" - end - else - L = _L - if !ambient_representation - V = ambient_space(_L) - B = basis_matrix(_L) - B2 = orthogonal_complement(V, B) - C = vcat(B, B2) - f_ambient = block_diagonal_matrix([f, identity_matrix(QQ, nrows(B2))]) - f = inv(C)*f_ambient*C - end - end - - if n <= 0 - n = multiplicative_order(f) - end - - @req is_finite(n) && n > 0 "Isometry must be non-trivial and of finite exponent" - - if check - @req n >= 3 "No hermitian structure for order less than 3" - G = gram_matrix(L) - @req is_cyclotomic_polynomial(minpoly(f)) "The minimal polynomial of f must be irreducible and cyclotomic" - @req f*G*transpose(f) == G "f does not define an isometry of L" - @req multiplicative_order(f) == n "The order of f should be equal to n" - @req divides(rank(L), euler_phi(n))[1] "The totient of n must divides the rank of L" - end - - if E === nothing - E, b = cyclotomic_field_as_cm_extension(n) - elseif !Hecke.is_cyclotomic_type(E)[1] - @req degree(E) == 2 && absolute_degree(E) == euler_phi(n) "E should be the $n-th cyclotomic field seen as a cm-extension of its real cyclotomic subfield" - Et, t = E["t"] - rt = roots(t^n-1) - filter!(l -> findfirst(i -> isone(l^i), 1:n) == n, rt) - @req length(rt) == euler_phi(n) "E is not of cyclotomic type" - b = isone(rt[1]) ? rt[2] : rt[1] - else - b = gen(E) - end - - _mb = absolute_representation_matrix(b) - m = divexact(rank(L), euler_phi(n)) - mb = block_diagonal_matrix([_mb for i in 1:m]) - if l === nothing - bca = Hecke._basis_of_commutator_algebra(f, _mb) - @assert !is_empty(bca) - l = zero_matrix(QQ, 0, ncols(f)) - while rank(l) != ncols(f) - _m = popfirst!(bca) - _l = vcat(l, _m) - if rank(_l) > rank(l) - l = _l - end - end - @assert det(l) != 0 - @assert l*f == mb*l - elseif check - @req l isa QQMatrix "l must be a matrix with rational entries" - @req !is_zero(det(l)) "l must be invertible" - @req l*f == mb*l "The basis described by l is not compatible with multiplication by a $n-th root of unity" - end - - B = matrix(absolute_basis(E)) - BL = basis_matrix(L) - gene = Vector{elem_type(E)}[] - for i in 1:nrows(l) - vv = vec(zeros(E, 1, m)) - v = BL[i,:]*inv(l) - for j in 1:m - a = (v[1, 1+(j-1)*euler_phi(n):j*euler_phi(n)]*B)[1] - vv[j] = a - end - push!(gene, vv) - end - - # We choose as basis for the hermitian lattice Lh the identity matrix - gram = matrix(zeros(E, m, m)) - G = l*gram_matrix(ambient_space(L))*transpose(l) - v = zero_matrix(QQ, 1, rank(L)) - for i=1:m - for j=1:m - vi = deepcopy(v) - vi[1,1+(i-1)*euler_phi(n)] = one(QQ) - vj = deepcopy(v) - vj[1,1+(j-1)*euler_phi(n)] = one(QQ) - alpha = sum([((vi*mb^k)*G*transpose(vj))[1]*b^k for k in 0:n-1]) - gram[i,j] = alpha - end - end - s = involution(E) - @assert transpose(map_entries(s, gram)) == gram - - Lh = hermitian_lattice(E, gene, gram = 1//n*gram) - return Lh, l -end - From 8abf285c34b4d79e667ed83692dd72174d8dc744 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 5 Apr 2023 08:10:26 +0200 Subject: [PATCH 42/76] more improvements --- Project.toml | 2 +- experimental/LatWithIsom/Embeddings.jl | 3 +- experimental/LatWithIsom/Enumeration.jl | 26 +++-- experimental/LatWithIsom/HMM.jl | 145 ++++++++++++++---------- experimental/LatWithIsom/LatWithIsom.jl | 12 +- 5 files changed, 105 insertions(+), 83 deletions(-) diff --git a/Project.toml b/Project.toml index c375626916f4..f00c5243205c 100644 --- a/Project.toml +++ b/Project.toml @@ -29,7 +29,7 @@ AbstractAlgebra = "0.28.4" AlgebraicSolving = "0.3.0" DocStringExtensions = "0.8, 0.9" GAP = "0.9.4" -Hecke = "0.18.5" +Hecke = "0.18.7" JSON = "^0.20, ^0.21" Nemo = "0.33" Polymake = "0.9.0" diff --git a/experimental/LatWithIsom/Embeddings.jl b/experimental/LatWithIsom/Embeddings.jl index c1ae311724e8..39d52edc63cc 100644 --- a/experimental/LatWithIsom/Embeddings.jl +++ b/experimental/LatWithIsom/Embeddings.jl @@ -921,13 +921,12 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, end ext, _ = sub(D, D.(_glue)) - println(ext == C2) 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)) qC2 = discriminant_group(C2) - OqC2 = orthogonal_group(C2) + OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) @assert is_bijective(phi2) diff --git a/experimental/LatWithIsom/Enumeration.jl b/experimental/LatWithIsom/Enumeration.jl index 6e467655e055..c731a4932ce1 100644 --- a/experimental/LatWithIsom/Enumeration.jl +++ b/experimental/LatWithIsom/Enumeration.jl @@ -288,7 +288,7 @@ function _ideals_of_norm(E, d::QQFieldElem) end function _ideals_of_norm(E, d::ZZRingElem) - isone(d) && return [1*maximal_order(E)] + isone(d) && return [fractional_ideal(maximal_order(E), one(E))] @assert E isa Hecke.NfRel K = base_field(E) OK = maximal_order(K) @@ -312,7 +312,7 @@ function _ideals_of_norm(E, d::ZZRingElem) for I in Hecke.cartesian_product_iterator(primes) I = prod(I) if absolute_norm(I) == d - push!(ids, I) + push!(ids, fractional_ideal(OE, I)) end end return ids @@ -399,7 +399,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) ok || return reps - gene = HermGenus[] + gene = Hecke.HermGenus[] E, b = cyclotomic_field_as_cm_extension(n*m) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) @@ -416,33 +416,37 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) @info "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) + append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = Hecke.numerator(DE*dd))) end gene = unique(gene) @info "All possible genera: $(length(gene))" - for g in gene @info "g = $g" H = representative(g) if !is_integral(DE*scale(H)) continue end - if iseven(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) + if is_even(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) continue end @info "$H" - M = trace_lattice(H) + M, fM = Hecke.trace_lattice_with_isometry(H) @assert det(M) == d + M = lattice_with_isometry(M, fM) @assert is_of_hermitian_type(M) @assert order_of_isometry(M) == n*m - if iseven(M) != iseven(Lf) + if is_even(M) != is_even(Lf) continue end if !is_of_same_type(Lf, lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) continue end - append!(reps, LatWithIsom[trace_lattice(HH) for HH in genus_representatives(H)]) + gr = genus_representatives(H) + for HH in gr + M, fM = Hecke.trace_lattice_with_isometry(HH) + push!(reps, lattice_with_isometry(M, fM)) + end end return reps end @@ -566,7 +570,9 @@ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = - LA = lattice_with_isometry(representative(A)) RA = representatives_of_hermitian_type(LA, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) - E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p, check=false) + E = try admissible_equivariant_primitive_extensions(L1, L2, Lf, p, check=false) + catch e return L1, L2, Lf, p + end GC.gc() append!(reps, E) end diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index 08d38e11bc4d..60e52fa629f0 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -18,9 +18,9 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) function dlog(x::Hecke.NfRelElem) @assert parent(x) == E d = denominator(x, OE) - xabs = d*EabstoE\(x) + xabs = d*(EabstoE\(x)) dabs = copy(d) - F = prime_decomposition(OEabs, minimum(EabstoE\P)) + F = prime_decomposition(OEabs, minimum(Pabs)) for PP in F @assert valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) @@ -62,7 +62,7 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) RPabs, mRPabs = quo(OEabs, Pabs^i) URPabs, mURPabs = unit_group(RPabs) - f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(URPabs[i])))))))) for i in 1:ngens(URPabs)]) + f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(x)))))))) for x in gens(URPabs)]) K, mK = kernel(f) @@ -78,7 +78,7 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) d = denominator(x, OE) xabs = EabstoE\(d*x) dabs = copy(d) - F = prime_decomposition(OEabs, minimum(EabstoE\P)) + F = prime_decomposition(OEabs, minimum(Pabs)) for PP in F @assert valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) @@ -111,7 +111,7 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) t = e-1 - psi(x) = x <= t ? x : t + 2*(x-t) + psi(x) = t + 2*(x-t) pi = uniformizer(P) @@ -131,7 +131,7 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) RPabs, mRPabs = quo(OEabs, Pabs^i) URPabs, mURPabs = unit_group(RPabs) - f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(URPabs[i])))))))) for i in 1:ngens(URPabs)]) + f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(x)))))))) for x in gens(URPabs)]) K, mK = kernel(f) @@ -170,6 +170,7 @@ function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) @assert is_prime(p) @assert is_maximal(order(p)) @assert order(p) === base_ring(O) + E = nf(O) F = prime_decomposition(O, p) P = F[1][1] if i == 0 @@ -232,6 +233,11 @@ function _get_product_quotient(E::Hecke.NfRel, Fac) return v end + for i in 1:10 + a = rand(G) + @assert dlog(exp(a)) == a + end + return G, dlog, exp end @@ -268,7 +274,8 @@ function _local_determinants_morphism(Lf::LatWithIsom) qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) - # Since any isometry of L centrlizing f induces an isometry of qL centralising + + # Since any isometry of L centralizing f induces an isometry of qL centralising # 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 BH22. G, _ = centralizer(OqL, fqL) @@ -289,13 +296,6 @@ function _local_determinants_morphism(Lf::LatWithIsom) res = get_attribute(Lf, :transfer_data) - # These are invertible matrices representing lifts to the ambient space of the - # lattice in Lf of the generators of G. These are not isometries. We will - # transfer them using `res` to the ambient space of H. They are though isometry - # of H2/H. Our goal lift them to isometry of H2 up to a good enough precision - # and compute their determinant. - gene_herm = [_transfer_discriminant_isometries(res, g) for g in gens(G)] - # We want only the prime ideal in O_K which divides the quotient H2/H. For # this, we collect all the primes dividing DEQ or for which H is not locally # unimodular. Then, we check for which prime ideals p, the local quotient @@ -314,9 +314,9 @@ function _local_determinants_morphism(Lf::LatWithIsom) for p in S lp = prime_decomposition(OE, p) P = lp[1][1] - k = valuation(N, p) + n = valuation(N*OE, P) a = valuation(DEQ, P) - push!(Fsharpdata, (p, 2*k+a)) + push!(Fsharpdata, (p, n+a)) end RmodFsharp, Fsharplog, Fsharpexp = _get_product_quotient(E, Fsharpdata) @@ -354,9 +354,10 @@ function _local_determinants_morphism(Lf::LatWithIsom) Eabs, EabstoE = Hecke.absolute_simple_field(E) OEabs = maximal_order(Eabs) UOEabs, mUOEabs = unit_group(OEabs) - UOK, mUOK = unit_group(base_ring(OE)) + OK = base_ring(OE) + UOK, mUOK = unit_group(OK) - fU = hom(UOEabs, UOK, [mUOK\(norm(OE(EabstoE(Eabs(mUOEabs(k)))))) for k in gens(UOEabs)]) + fU = hom(UOEabs, UOK, [mUOK\norm(OE(mUOK(m))) for m in gens(UOK)]) KU, jU = kernel(fU) gene_norm_one = Hecke.NfRelElem[EabstoE(Eabs(mUOEabs(jU(k)))) for k in gens(KU)] @@ -369,34 +370,37 @@ function _local_determinants_morphism(Lf::LatWithIsom) # we just need to create the map from G to Q. Q, mQ = quo(FmodFsharp, I) - function dlog(x::Vector{Hecke.NfRelElem}) + SQ, SQtoQ = snf(Q) + + function dlog(x::Vector) @assert length(x) == length(Fsharpdata) - return mQ(FmodFsharp(Fsharplog(x))) + return SQtoQ\(mQ(j\(Fsharplog(x)))) end - imgs = elem_type(Q)[] + imgs = elem_type(SQ)[] # For each of our matrices in gene_herm, we do successive P-adic liftings in # order to approximate an isometry of D^{-1}H^#, up to a certain precision # (given by Theorem 6.25 in BH22). We do this for all the primes we have to # 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 gene_herm + for g in gens(G) ds = elem_type(E)[] for p in S - println(p) lp = prime_decomposition(OE, p) P = lp[1][1] k = valuation(N, p) a = valuation(DEQ, P) e = valuation(DEK, P) - g_approx = _approximate_isometry(H2, g, P, e, a, k) + g_approx = _approximate_isometry(H2, g, P, e, a, k, res) push!(ds, det(g_approx)) end push!(imgs, dlog(ds)) end - f = hom(G, Q, gens(G), imgs_Q) + GSQ, SQtoGSQ, _ = Oscar._isomorphic_gap_group(SQ) + f = hom(G, GSQ, gens(G), SQtoGSQ.(imgs), check=false) + return f end @@ -422,7 +426,7 @@ function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) return false end end - u = uniformizer(P) + 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)]) @@ -439,50 +443,49 @@ end # Starting from an isometry of the torsion quadratic module `domain(g)`, for # which we assume that the cover M has full rank, we compute a fake lift to the # ambient space of the cover. This is not an isometry, but induces g on `domain(g)`. -function _get_ambient_isometry(g::AutomorphismGroupElem{TorQuadModule}) - q = domain(g) - L = cover(q) - @assert rank(L) == degree(L) - d = degree(L) - M1 = reduce(vcat, [matrix(QQ, 1, d, lift(t)) for t in gens(q)]) - M2 = reduce(vcat, [matrix(QQ, 1, d, lift(g(t))) for t in gens(q)]) - return solve(M1, M2) -end +#function _get_ambient_isometry(g::AutomorphismGroupElem{TorQuadModule}) +# q = domain(g) +# L = cover(q) +# @assert rank(L) == degree(L) +# d = degree(L) +# M1 = reduce(vcat, [matrix(QQ, 1, d, lift(t)) for t in gens(q)]) +# M2 = reduce(vcat, [matrix(QQ, 1, d, lift(g(t))) for t in gens(q)]) +# return solve(M1, M2) +#end # Using the function used for the transfer construction, between the ambient # space of the cover of our torsion module, and the ambient space of the # corresponding hermitian structure, we transfer the fake lift computed with the # previous function. This will be an invertible matrix, but the corresponding # automorphism is not an isometry. -function _transfer_discriminant_isometries(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}) +function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} E = base_ring(codomain(res)) - m = _get_ambient_isometry(g) q = domain(g) @assert ambient_space(cover(q)) === domain(res) - gE = zero_matrix(E, 0, rank(codomain(res))) - vE = vec(collect(zeros(E, 1, rank(codomain(res))))) - for i in 1:rank(codomain(res)) - vE[i] = one(E) + B2 = zero(Bp) + for i in 1:nrows(Bp) + vE = vec(collect(Bp[i, :])) vQ = res\vE - vQ = matrix(QQ, 1, length(vQ), vQ) - gvq = vQ*m - gvQ = vec(collect(gvq)) + vq = q(vQ) + gvq = g(vq) + gvQ = lift(gvq) gvE = res(gvQ) - gE = vcat(gE, matrix(E, 1, length(gvE), gvE)) - vE[i] = zero(E) + B2[i, :] = gvE end - return gE + return solve_left(Bp[:, 1:nrows(Bp)], B2[:, 1:nrows(B2)]) end # the minimum P-valuation among all the non-zero entries of M function _scale_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} @assert nf(order(P)) === base_ring(M) + iszero(M) && return inf return minimum([valuation(v, P) for v in collect(M) if !iszero(v)]) end # the minimum P-valuation among all the non-zero diagonal entries of M function _norm_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} @assert nf(order(P)) === base_ring(M) + iszero(diagonal(M)) && return inf r = minimum([valuation(v, P) for v in diagonal(M) if !iszero(v)]) return r end @@ -509,6 +512,8 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H if check @assert _scale_valuation(inv(G), P) >= 1+a @assert _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a + println(_scale_valuation(R, P)) + println(a) @assert _scale_valuation(R, P) >= l-a @assert _norm_valuation(R, P) + valuation(rho,P) >= l-a end @@ -518,7 +523,7 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H # R and D to be the diagonal. U = zero_matrix(E, nrows(R), ncols(R)) for i in 1:nrows(R) - for j in 1:i-1 + for j in i+1:ncols(R) U[i, j] = R[i, j] end end @@ -544,35 +549,50 @@ end # Starting from our fake isometry g on H (which will be here D^{-1}H^#), and a # prime ideal P, we iteratively lift g to a finer fake isometry, i.e. a matrix # defining a P-local isometry up to the precision P^{2*k+a}. -function _approximate_isometry(H::Hecke.HermLat, g::T, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} - E = base_ring(g) - @assert base_field(H) === E +function _approximate_isometry(H::Hecke.HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) + E = base_field(H) @assert nf(order(P)) === E - - ok, m = is_modular(H, minimum(P)) - - # This should never happend since we collect the prime ideals in such a way it - # is not possible. Though, we keep it in case something was forgotten before - if ok && (m == -a) + ok, b = is_modular(H, minimum(P)) + if ok && b == -a return identity_matrix(E, rank(H)) end - # we do know work on the p-completions, p = P \cap O_K - Bp = local_basis_matrix(H, minimum(P)) + Bp = _local_basis_modular_submodules(H, minimum(P), a, res) Gp = Bp*gram_matrix(ambient_space(H))*map_entries(involution(E), transpose(Bp)) - Fp = Bp*g*inv(Bp) + Fp = _transfer_discriminant_isometry(res, g, Bp) + println(Fp) # 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)) - rho = _find_rho(P, e) + l = 0 while _scale_valuation(Rp, P) < 2*k+a Fp, l = _local_hermitian_lifting(Gp, Fp, rho, l, P, e, a) Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) end - return Fp + return Fp[1:nd,1:nd] +end + +function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfRelOrdIdl, a::Int, res::AbstractSpaceRes) + L = restrict_scalars(H, res) + B, _ , exps = jordan_decomposition(H, p) + exps[end] == -a && pop!(B) + subs = eltype(B)[] + for b in B + H2 = lattice_in_same_ambient_space(H, b) + if !is_sublattice(H, H2) + L2 = restrict_scalars(H2, res) + L2 = intersect(L, L2) + B2 = basis_matrix(L2) + gene = [res(vec(collect(B2[i, :]))) for in in 1:nrows(B2)] + H2 = lattice(ambient_space(H), gene) + b = local_basis_matrix(H2, p, type = :submodule) + end + push!(subs, b) + end + return reduce(vcat, subs) end # We need a special rho for Algorithm 8 of BH23: we construct such an element @@ -592,7 +612,6 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) Pabs = EabstoE\P OEabs = order(Pabs) while true - println("bla") Eabst, t = Eabs["t"] g = EabstoE\(E(-rand(K, -5:5)^2-1)) nu = 2*valuation(Eabs(2), Pabs)-2*e+2 diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index bf5fb01364dd..fa0eff08baf0 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -13,7 +13,7 @@ import Hecke: kernel_lattice, invariant_lattice, rank, genus, basis_matrix, gram_matrix, ambient_space, rational_span, scale, signature_tuple, is_integral, det, norm, degree, discriminant, charpoly, minpoly, rescale, dual, lll, discriminant_group, divides, lattice, - hermitian_structure, coinvariant_lattice + hermitian_structure, coinvariant_lattice, is_even ############################################################################### # @@ -182,7 +182,7 @@ Given a lattice with isometry $(L, f)$, return whether the underlying lattice Note that to be even, `L` must be integral (see [`is_integral(::ZLat)`](@ref)). """ -is_even(Lf::LatWithIsom) = is_even(lattice(Lf))::Int +is_even(Lf::LatWithIsom) = Hecke.iseven(lattice(Lf))::Bool @doc Markdown.doc""" discriminant(Lf::LatWithIsom) -> QQFieldElem @@ -432,11 +432,9 @@ $q_L$ induced by `f`. unique!(UL) GL = Oscar._orthogonal_group(qL, UL, check = false) else - @req is_hermitian(Lf) "Not yet implemented for indefinite lattices with isometry which are not hermitian" - qL, fqL = discriminant_group(Lf) - OqL = orthogonal_group(qL) - CdL, _ = centralizer(OqL, fqL) - GL, _ = sub(OqL, unique([OqL(s.X) for s in CdL])) + @req is_of_hermitian_type(Lf) "Not yet implemented for indefinite lattices with isometry which are not of hermitian type" + dets = LWI._local_determinants_morphism(Lf) + GL, _ = kernel(dets) end return GL::AutomorphismGroup{TorQuadModule} end From f5e9b1894a84df673016ab8378c04e1c8662eae7 Mon Sep 17 00:00:00 2001 From: StevellM Date: Sat, 8 Apr 2023 15:34:24 +0200 Subject: [PATCH 43/76] more improvments --- experimental/LatWithIsom/Embeddings.jl | 147 ++++++++++++++++++------ experimental/LatWithIsom/Enumeration.jl | 42 ++++--- experimental/LatWithIsom/HMM.jl | 46 +++++--- experimental/LatWithIsom/LatWithIsom.jl | 48 ++------ src/Oscar.jl | 1 + 5 files changed, 184 insertions(+), 100 deletions(-) diff --git a/experimental/LatWithIsom/Embeddings.jl b/experimental/LatWithIsom/Embeddings.jl index 39d52edc63cc..d8a43ddb88a3 100644 --- a/experimental/LatWithIsom/Embeddings.jl +++ b/experimental/LatWithIsom/Embeddings.jl @@ -5,6 +5,8 @@ export primitive_embeddings_of_elementary_lattice const GG = GAP.Globals +import Base: +, -, *, ^ +import Oscar: evaluate ################################################################################ # # Miscellaneous @@ -99,20 +101,86 @@ function is_invariant(aut::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor return all(g -> is_invariant(g, i), gens(aut)) end -function _get_V(L, q, f, fq, mu, p) - f_mu = mu(f) - if !is_zero(f_mu) - L_sub = intersect(lattice_in_same_ambient_space(L, inv(f_mu)*basis_matrix(L)), dual(L)) - B = basis_matrix(L_sub) - V, Vinq = sub(q, q.([vec(collect(B[i,:])) for i in 1:nrows(B)])) - else - V, Vinq = q, id_hom(q) - end - pV, pVinV = primary_part(V, p) - pV, pVinV = sub(V, pVinV.([divexact(order(g), p)*g for g in gens(pV) if !(order(g)==1)])) - pVinq = compose(pVinV, Vinq) +function ^(f::TorQuadModuleMor, y::Integer) + @assert y >= 0 + @assert domain(f) === codomain(f) + if y == 0 + return id_hom(domain(f)) + elseif y == 1 + return f + else + k = 1 + f2 = f + while k != y + f2 = compose(f2, f) + k += 1 + end + return f2 + end +end + +function +(f::TorQuadModuleMor, g::TorQuadModuleMor) + @assert domain(f) === domain(g) + @assert codomain(f) === codomain(g) + return hom(domain(f), codomain(g), [f(a)+g(a) for a in gens(domain(f))]) +end + +function -(f::TorQuadModuleMor, g::TorQuadModuleMor) + @assert domain(f) === domain(g) + @assert codomain(f) === codomain(g) + return hom(domain(f), codomain(g), [f(a)-g(a) for a in gens(domain(f))]) +end + +function *(x::Int, f::TorQuadModuleMor) + z = hom(domain(f), codomain(f), zero_matrix(ZZ, ngens(domain(f)), ngens(codomain(f)))) + if x == 0 + return z + elseif x < 0 + neg = true + x = -x + else + neg = false + end + k = 1 + f2 = f + while k != x + f2 += f + k += 1 + end + if neg + return z-f + else + return f + end +end + +function evaluate(p::QQPolyRingElem, f::TorQuadModuleMor) + c = collect(coefficients(p)) + @assert domain(f) === codomain(f) + g = hom(domain(f), codomain(f), zero_matrix(ZZ, ngens(domain(f)), ngens(codomain(f)))) + for i in 1:length(c) + g += Int(ZZ(c[i]))*f^(i-1) + end + return g +end + +function kernel(f::TorQuadModuleMor) + g = f.map_ab + Kg, KgtoA = kernel(g) + S, StoKg = snf(Kg) + return sub(domain(f), TorQuadModuleElem[domain(f)(KgtoA(StoKg(a))) for a in gens(S)]) +end + +function _get_V(fq, mu, p) + q = domain(fq) + V, Vinq = primary_part(q, p) + pV, pVinq = sub(q, Vinq.([divexact(order(g), p)*g for g in gens(V) if !(order(g)==1)])) fpV = restrict_automorphism(fq, pVinq) - return pV, pVinq, fpV + fpV_mu = evaluate(mu, fpV) + K, KtopV = kernel(fpV_mu) + Ktoq = compose(KtopV, pVinq) + fK = restrict_automorphism(fq, Ktoq) + return K, Ktoq, fK end function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) @@ -705,7 +773,8 @@ end admissible_equivariant_primitive_extensions(Afa::LatWithIsom, Bfb::LatWithIsom, Cfc::LatWithIsom, - p::Int; check=true) + p::Integer, + q::Integer = p; check=true) -> Vector{LatWithIsom} Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a @@ -715,8 +784,8 @@ representatives of the double coset $G_B\backslash S\slash/G_A$ where: - $G_A$ and $G_B$ are the respective images of the morphisms $O(A, fa) -> O(q_A, \bar{fa})$ and $O(B, fb) -> O(q_B, \bar{fb})$; - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry - $fc'$ where $p\cdot C' \subseteq A\perpB$ and the type of $(C', fc'^p)$ is equal - to the type of $(C, fc)$. + $fc'$ where $p\cdot C' \subseteq A\perpB$ and such that the type of $(C', fc'^q)$ + is equal to the type of `(C, fc)`. If `check == true` the input triple is checked to a `p`-admissible triple of integral lattices (with isometry) with `fA` and `fB` having relatively coprime @@ -731,14 +800,16 @@ See Algorithm 2 of [BH22]. function admissible_equivariant_primitive_extensions(A::LatWithIsom, B::LatWithIsom, C::LatWithIsom, - p::Integer; check=true) + p::Integer, + q::Integer = p; check=true) # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" if check @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" - @req is_of_hermitian_type(A) && is_of_hermitian_type(B) "Afa and Bfb must be of hermitian type" - @req gcd(minpoly(A), minpoly(B)) == 1 "Minimal irreducible polynomials must be relatively coprime" + chiA = minpoly(A) + chiB = minpoly(parent(chiA), isometry(B)) + @req gcd(chiA, chiB) == 1 "Minimal irreducible polynomials must be relatively coprime" @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple" if ambient_space(A) === ambient_space(B) === ambient_space(C) G = gram_matrix(ambient_space(C)) @@ -754,11 +825,12 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, qA, fqA = discriminant_group(A) qB, fqB = discriminant_group(B) GA = image_centralizer_in_Oq(A) + @assert fqA in GA GB = image_centralizer_in_Oq(B) + @assert fqB in GB # this is where we will perform the glueing if ambient_space(A) === ambient_space(B) - println("same_amb") D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) else D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _direct_sum_with_embeddings_orthogonal_groups(qA, qB) @@ -781,23 +853,26 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fC2 = _B*fC2*inv(_B) @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) else - C2 = direct_sum(lattice(A), lattice(B))[1] + _B = block_diagonal_matrix([basis_matrix(A), basis_matrix(B)]) + C2 = lattice_in_same_ambient_space(cover(D), _B) fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) + @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end - if is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation = false), type(C)) - qC2 = discriminant_group(C2) - ok, phi2 = is_isometric_with_isometry(qC2, D) - @assert ok + qC2 = discriminant_group(C2) + phi2 = hom(qC2, D, [D(lift(x)) for x in gens(qC2)]) + @assert is_bijective(phi2) + if is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation=false), type(C)) GC = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GC2)]) C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) + @assert discriminant_group(C2fc2)[2] in GC set_attribute!(C2fc2, :image_centralizer_in_Oq, GC) push!(results, C2fc2) end return results end # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map - VA, VAinqA, fVA = _get_V(lattice(A), qA, isometry(A), fqA, minpoly(B), p) - VB, VBinqB, fVB = _get_V(lattice(B), qB, isometry(B), fqB, minpoly(A), p) + VA, VAinqA, fVA = _get_V(hom(fqA), minpoly(B), p) + VB, VBinqB, fVB = _get_V(hom(fqB), minpoly(A), p) # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g @@ -841,11 +916,13 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, actA = hom(stabA, 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, [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)) # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an @@ -916,10 +993,6 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end - if !is_of_type(lattice_with_isometry(C2, fC2^p, ambient_representation=false), type(C)) - continue - end - ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) disc = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), @@ -929,10 +1002,15 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) @assert is_bijective(phi2) - + + if !is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) + continue + end C2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - im2_phi, _ = sub(OSA, OSA.([compose(phig, compose(hom(g1), inv(phig))) for g1 in gens(imB)])) - im3, _ = sub(imA, gens(intersect(imA, im2_phi)[1])) + geneOSA = AutomorphismGroupElem{TorQuadModule}[OSA(compose(phig, compose(hom(g1), inv(phig)))) for g1 in unique(gens(imB))] + im2_phi, _ = sub(OSA, geneOSA) + gene_inter = AutomorphismGroupElem{TorQuadModule}[h for h in unique(gens(intersect(imA, im2_phi)[1]))] + im3, _ = sub(imA, gene_inter) stab = Tuple{AutomorphismGroupElem{TorQuadModule}, AutomorphismGroupElem{TorQuadModule}}[(actA\x, actB\(imB(compose(inv(phig), compose(hom(x), phig))))) for x in gens(im3)] stab = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x[1])*OqBinOD(x[2]) for x in stab] stab = union(stab, kerA) @@ -940,6 +1018,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, 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 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) + @assert discriminant_group(C2)[2] in stab set_attribute!(C2, :image_centralizer_in_Oq, stab) push!(results, C2) end diff --git a/experimental/LatWithIsom/Enumeration.jl b/experimental/LatWithIsom/Enumeration.jl index c731a4932ce1..4cfc23c42684 100644 --- a/experimental/LatWithIsom/Enumeration.jl +++ b/experimental/LatWithIsom/Enumeration.jl @@ -228,17 +228,17 @@ function admissible_triples(G::ZGenus, p::Int64; 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" n = rank(G) - p, _ = signature_pair(G) + pG, _ = signature_pair(G) if pA >= 0 - @req pA <= p "Wrong restrictions" + @req pA <= pG "Wrong restrictions" if pB >= 0 - @req pA + pB == p "Wrong restrictions" + @req pA + pB == pG "Wrong restrictions" else - pB = p - pA + pB = pG - pA end elseif pB >= 0 - @req pB <= p "Wrong restrictions" - pA = p - pB + @req pB <= pG "Wrong restrictions" + pA = pG - pB end d = numerator(det(G)) even = iseven(G) @@ -369,7 +369,7 @@ $(L, f)$. Note that in this case, the isometries `g`'s are of order $nm$. See Algorithm 3 of [BH22]. """ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) - rank(Lf) == 0 && return LatWithIsom[] + rank(Lf) == 0 && return LatWithIsom[Lf] @req m >= 1 "m must be a positive integer" @req is_of_hermitian_type(Lf) "Lf must be of hermitian" @@ -393,6 +393,8 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) return reps end + !iseven(s2) && return reps + @info "Order bigger than 3" ok, rk = Hecke.divides(rk, euler_phi(n*m)) @@ -416,7 +418,8 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) @info "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = Hecke.numerator(DE*dd))) + println(dd) + append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(dd)*DE)) end gene = unique(gene) @@ -432,7 +435,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) end @info "$H" M, fM = Hecke.trace_lattice_with_isometry(H) - @assert det(M) == d + det(M) == d || continue M = lattice_with_isometry(M, fM) @assert is_of_hermitian_type(M) @assert order_of_isometry(M) == n*m @@ -520,7 +523,7 @@ function _representative(t::Dict; check::Bool = true) end H = H M = trace_lattice(H) - @assert det(M) == d + det(M) == d || continue @assert is_of_hermitian_type(M) @assert order_of_isometry(M) == n if iseven(M) != iseven(G) @@ -547,7 +550,7 @@ Note that `d` can be 0. See Algorithm 4 of [BH22]. """ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = -1, pB::Int = -1) - rank(Lf) == 0 && return LatWithIsom[] + rank(Lf) == 0 && return LatWithIsom[Lf] @req is_prime(p) "p must be a prime number" @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" @@ -570,7 +573,7 @@ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = - LA = lattice_with_isometry(representative(A)) RA = representatives_of_hermitian_type(LA, q^d) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) - E = try admissible_equivariant_primitive_extensions(L1, L2, Lf, p, check=false) + E = try admissible_equivariant_primitive_extensions(L1, L2, Lf, p) catch e return L1, L2, Lf, p end GC.gc() @@ -613,7 +616,10 @@ Note that `e` can be 0. See Algorithm 5 of [BH22]. """ function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) - rank(Lf) == 0 && return LatWithIsom[] + if rank(Lf) == 0 + (b == 0) && return LatWithIsom[Lf] + return LatWithIsom[] + end @req is_prime(p) "p must be a prime number" @req b in [0, 1] "b must be an integer equal to 0 or 1" @@ -633,13 +639,15 @@ function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) x = gen(Hecke.Globals.Qx) A0 = kernel_lattice(Lf, q^e) - B0 = kernel_lattice(Lf, x^(q^e-1)-1) + B0 = kernel_lattice(Lf, x^(q^(e-1))-1) A = splitting_of_hermitian_prime_power(A0, p) is_empty(A) && return reps B = splitting_of_prime_power(B0, p) for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) b == 1 && !Hecke.divides(order_of_isometry(L1), p)[1] && !Hecke.divides(order_of_isometry(L2), p)[1] && continue - E = admissible_equivariant_primitive_extensions(L1, L2, Lf, q, check=false) + E = try admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p) + catch e return L2, L1, Lf, q, p + end @assert b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) end @@ -701,7 +709,7 @@ function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) is_empty(A) && return reps B = splitting_of_partial_mixed_prime_power(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) - E = admissible_equivariant_primitive_extensions(LA, LB, Lf, q, check = false) + E = admissible_equivariant_primitive_extensions(LB, LA, Lf, q, p) append!(reps, E) end return reps @@ -753,7 +761,7 @@ function splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) isempty(A) && return reps B = splitting_of_mixed_prime_power(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) - E = admissible_equivariant_primitive_extensions(LA, LB, Lf, p, check = false) + E = admissible_equivariant_primitive_extensions(LB, LA, Lf, p) @assert all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) append!(reps, E) end diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index 60e52fa629f0..3095fda57162 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -440,6 +440,8 @@ end # ############################################################################### +Oscar.canonical_unit(x::NfOrdQuoRingElem) = one(parent(x)) + # Starting from an isometry of the torsion quadratic module `domain(g)`, for # which we assume that the cover M has full rank, we compute a fake lift to the # ambient space of the cover. This is not an isometry, but induces g on `domain(g)`. @@ -458,10 +460,14 @@ end # corresponding hermitian structure, we transfer the fake lift computed with the # previous function. This will be an invertible matrix, but the corresponding # automorphism is not an isometry. -function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} +function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T, P::Hecke.NfRelOrdIdl, i::Int) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} E = base_ring(codomain(res)) + Eabs, EabstoE = Hecke.absolute_simple_field(E) + Pabs = EabstoE\P + OEabs = order(Pabs) q = domain(g) @assert ambient_space(cover(q)) === domain(res) + #Bp = reduce(vcat, [matrix(E, 1, ncols(Bp), res(lift(q(res\vec(collect(Bp[i, :])))))) for i in 1:nrows(Bp)]) B2 = zero(Bp) for i in 1:nrows(Bp) vE = vec(collect(Bp[i, :])) @@ -472,7 +478,21 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG gvE = res(gvQ) B2[i, :] = gvE end - return solve_left(Bp[:, 1:nrows(Bp)], B2[:, 1:nrows(B2)]) + + Bpabs = map_entries(a -> EabstoE\a, Bp) + B2abs = map_entries(a -> EabstoE\a, B2) + d = lcm(lcm([denominator(a, OEabs) for a in Bpabs]), lcm([denominator(a, OEabs) for a in B2abs])) + dBpabs = change_base_ring(OEabs, d*Bpabs) + dB2abs = change_base_ring(OEabs, d*B2abs) + Q, p = quo(OEabs, Pabs^(i+valuation(d, Pabs))) + BpQ = map_entries(p, dBpabs) + B2Q = map_entries(p, dB2abs) + println(BpQ, B2Q) + KQ = solve_left(BpQ, B2Q) + K = map_entries(a -> EabstoE(Eabs(p\a)), KQ) + @assert _scale_valuation(K*Bp-B2, P) >= i + println(K) + return K end # the minimum P-valuation among all the non-zero entries of M @@ -512,8 +532,6 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H if check @assert _scale_valuation(inv(G), P) >= 1+a @assert _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a - println(_scale_valuation(R, P)) - println(a) @assert _scale_valuation(R, P) >= l-a @assert _norm_valuation(R, P) + valuation(rho,P) >= l-a end @@ -554,13 +572,12 @@ function _approximate_isometry(H::Hecke.HermLat, g::AutomorphismGroupElem{TorQua @assert nf(order(P)) === E ok, b = is_modular(H, minimum(P)) if ok && b == -a - return identity_matrix(E, rank(H)) + return identity_matrix(E, 1) end - Bp = _local_basis_modular_submodules(H, minimum(P), a, res) + Bp, v = _local_basis_modular_submodules(H, minimum(P), a, res) Gp = Bp*gram_matrix(ambient_space(H))*map_entries(involution(E), transpose(Bp)) - Fp = _transfer_discriminant_isometry(res, g, Bp) - println(Fp) + Fp = _transfer_discriminant_isometry(res, g, Bp, P, v) # 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)) @@ -572,13 +589,16 @@ function _approximate_isometry(H::Hecke.HermLat, g::AutomorphismGroupElem{TorQua Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) end - return Fp[1:nd,1:nd] + return Fp end -function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfRelOrdIdl, a::Int, res::AbstractSpaceRes) +function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfOrdIdl, a::Int, res::AbstractSpaceRes) L = restrict_scalars(H, res) B, _ , exps = jordan_decomposition(H, p) - exps[end] == -a && pop!(B) + if exps[end] == -a + pop!(B) + pop!(exps) + end subs = eltype(B)[] for b in B H2 = lattice_in_same_ambient_space(H, b) @@ -586,13 +606,13 @@ function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfRelOrdIdl, L2 = restrict_scalars(H2, res) L2 = intersect(L, L2) B2 = basis_matrix(L2) - gene = [res(vec(collect(B2[i, :]))) for in in 1:nrows(B2)] + gene = [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 push!(subs, b) end - return reduce(vcat, subs) + return reduce(vcat, subs), a-exps[end] end # We need a special rho for Algorithm 8 of BH23: we construct such an element diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index fa0eff08baf0..14c0deade318 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -13,7 +13,7 @@ import Hecke: kernel_lattice, invariant_lattice, rank, genus, basis_matrix, gram_matrix, ambient_space, rational_span, scale, signature_tuple, is_integral, det, norm, degree, discriminant, charpoly, minpoly, rescale, dual, lll, discriminant_group, divides, lattice, - hermitian_structure, coinvariant_lattice, is_even + hermitian_structure, coinvariant_lattice ############################################################################### # @@ -207,9 +207,9 @@ signature_tuple(Lf::LatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, ############################################################################### @doc Markdown.doc""" - lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = true, - ambient_representation = true) - -> LatWithIsom + lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, + ambient_representation = true) + -> LatWithIsom Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry of `L` of order `n`, return the corresponding lattice with isometry pair $(L, f)$. @@ -219,16 +219,14 @@ ambient space of `L` and the induced isometry on `L` is automatically computed. Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = true, - ambient_representation::Bool = true) +function lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, + ambient_representation::Bool = true) if rank(L) == 0 - return LatWithIsom(L, matrix(QQ,0,0,[]), -1) + return LatWithIsom(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) end if check @req det(f) != 0 "f is not invertible" - m = multiplicative_order(f) - @req n == m "The order of f is equal to $m, not $n" end if ambient_representation @@ -245,6 +243,8 @@ function lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = tr f_ambient = inv(C)*f_ambient*C end + n = multiplicative_order(f) + if check @req f*gram_matrix(L)*transpose(f) == gram_matrix(L) "f does not define an isometry of L" @req f_ambient*gram_matrix(ambient_space(L))*transpose(f_ambient) == gram_matrix(ambient_space(L)) "f_ambient is not an isometry of the ambient space of L" @@ -255,30 +255,6 @@ function lattice_with_isometry(L::ZLat, f::QQMatrix, n::IntExt; check::Bool = tr end -@doc Markdown.doc""" - lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, - ambient_representation::Bool, = true) - -> LatWithIsom - -Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry -of `L`, return the corresponding lattice with isometry pair $(L, f)$. - -If `ambient_representation` is set to true, `f` is consider as an isometry of the -ambient space of `L` and the induced isometry on `L` is automatically computed. -Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity -on the complement of the rational span of `L` if it is not of full rank. -""" -function lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, - ambient_representation::Bool = true) - if rank(L) == 0 - return LatWithIsom(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) - end - - n = multiplicative_order(f) - return lattice_with_isometry(L, f, n, check = check, - ambient_representation = ambient_representation)::LatWithIsom -end - @doc Markdown.doc""" lattice_with_isometry(L::ZLat) -> LatWithIsom @@ -304,7 +280,7 @@ Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lat with isometry $(L(a), f)$ (see [`rescale(::ZLat, ::RationalUnion)`](@ref)). """ function rescale(Lf::LatWithIsom, a::Hecke.RationalUnion) - return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), order_of_isometry(Lf), check=false) + return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), check=false) end @doc Markdown.doc""" @@ -317,7 +293,7 @@ induced by `g`. """ function dual(Lf::LatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" - return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), order_of_isometry(Lf), check = false) + return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), check = false) end @doc Markdown.doc""" @@ -607,7 +583,7 @@ $\mathbb{Z}$-lattice $\Ker(f^k-1)$. for l in divs Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl), check=false, ambient_representation=false)[1] + Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl), check=false, ambient_representation=false) end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) diff --git a/src/Oscar.jl b/src/Oscar.jl index 2487854073bd..a7d3dabf3475 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -46,6 +46,7 @@ const oldexppkgs = [ "Rings", "Schemes", "SymmetricIntersections", + "LatWithIsom" ] const exppkgs = filter(x->isdir(joinpath(expdir, x)) && !(x in oldexppkgs), readdir(expdir)) From b17d7a93bda37a4fa275437a875e1f879f29eaf1 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 13 Apr 2023 09:21:59 +0200 Subject: [PATCH 44/76] fix hmm --- experimental/LatWithIsom/Embeddings.jl | 13 ++++++------ experimental/LatWithIsom/Enumeration.jl | 4 ++-- experimental/LatWithIsom/HMM.jl | 27 ++++++++++++++----------- experimental/LatWithIsom/LatWithIsom.jl | 16 +++++++++++++++ 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/experimental/LatWithIsom/Embeddings.jl b/experimental/LatWithIsom/Embeddings.jl index d8a43ddb88a3..75de9c43fb16 100644 --- a/experimental/LatWithIsom/Embeddings.jl +++ b/experimental/LatWithIsom/Embeddings.jl @@ -7,6 +7,7 @@ const GG = GAP.Globals import Base: +, -, *, ^ import Oscar: evaluate +import Hecke: kernel ################################################################################ # # Miscellaneous @@ -166,7 +167,7 @@ end function kernel(f::TorQuadModuleMor) g = f.map_ab - Kg, KgtoA = kernel(g) + Kg, KgtoA = Hecke.kernel(g) S, StoKg = snf(Kg) return sub(domain(f), TorQuadModuleElem[domain(f)(KgtoA(StoKg(a))) for a in gens(S)]) end @@ -324,7 +325,7 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua end F = base_ring(Qp) - k, K = kernel(VptoQp.matrix, side = :left) + k, K = Hecke.kernel(VptoQp.matrix, side = :left) gene_H0p = ModuleElem{fpFieldElem}[Vp(vec(collect(K[i,:]))) for i in 1:k] orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) for (orb, stab) in orb_and_stab @@ -719,12 +720,12 @@ function equivariant_primitive_extensions(A::LatWithIsom, GA::AutomorphismGroup{ # of the elements of the stabilizers acting trivially in the respective S* actA = hom(stabA, OSA, [OSA(Oscar.restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) imA, _ = image(actA) - kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(kernel(actA)[1])] + kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(Hecke.kernel(actA)[1])] fSA = OSA(restrict_automorphism(fqA, SAinqA)) actB = hom(stabB, OSB, [OSB(Oscar.restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(kernel(actB)[1])] + kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] fSB = OSB(restrict_automorphism(fqB, SBinqB)) @@ -915,13 +916,13 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # of the elements of the stabilizers acting trivially in the respective S* actA = hom(stabA, 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])] + kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(Hecke.kernel(actA)[1])] push!(kerA, OqAinOD(one(OqA))) fSA = OSA(restrict_automorphism(fqA, SAinqA)) actB = hom(stabB, 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])] + kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] push!(kerB, OqBinOD(one(OqB))) fSB = OSB(restrict_automorphism(fqB, SBinqB)) diff --git a/experimental/LatWithIsom/Enumeration.jl b/experimental/LatWithIsom/Enumeration.jl index 4cfc23c42684..d324f69bdecd 100644 --- a/experimental/LatWithIsom/Enumeration.jl +++ b/experimental/LatWithIsom/Enumeration.jl @@ -409,6 +409,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) @info "We have the different" ndE = d*inv(QQ(absolute_norm(DE)))^rk + println(ndE) detE = _ideals_of_norm(E, ndE) @info "All possible ideal dets: $(length(detE))" @@ -416,9 +417,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) signatures = _possible_signatures(s1, s2, E, rk) @info "All possible signatures: $(length(signatures))" - for dd in detE, sign in signatures - println(dd) append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(dd)*DE)) end gene = unique(gene) @@ -500,6 +499,7 @@ function _representative(t::Dict; check::Bool = true) DE = EabstoE(different(maximal_order(Eabs))) ndE = d*inv(QQ(absolute_norm(DE)))^rk + println(ndE) detE = _ideals_of_norm(E, ndE) @info "All possible ideal dets: $(length(detE))" diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatWithIsom/HMM.jl index 3095fda57162..f6b4ef93b7e6 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatWithIsom/HMM.jl @@ -392,7 +392,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) k = valuation(N, p) a = valuation(DEQ, P) e = valuation(DEK, P) - g_approx = _approximate_isometry(H2, g, P, e, a, k, res) + g_approx = _approximate_isometry(H, H2, g, P, e, a, k, res) push!(ds, det(g_approx)) end push!(imgs, dlog(ds)) @@ -460,7 +460,7 @@ Oscar.canonical_unit(x::NfOrdQuoRingElem) = one(parent(x)) # corresponding hermitian structure, we transfer the fake lift computed with the # previous function. This will be an invertible matrix, but the corresponding # automorphism is not an isometry. -function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T, P::Hecke.NfRelOrdIdl, i::Int) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} +function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T, P::Hecke.NfRelOrdIdl, BHp::T) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} E = base_ring(codomain(res)) Eabs, EabstoE = Hecke.absolute_simple_field(E) Pabs = EabstoE\P @@ -479,19 +479,19 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG B2[i, :] = gvE end - Bpabs = map_entries(a -> EabstoE\a, Bp) - B2abs = map_entries(a -> EabstoE\a, B2) + newBp = Bp*BHp + newB2 = B2*BHp + Bpabs = map_entries(a -> EabstoE\a, newBp) + B2abs = map_entries(a -> EabstoE\a, newB2) d = lcm(lcm([denominator(a, OEabs) for a in Bpabs]), lcm([denominator(a, OEabs) for a in B2abs])) dBpabs = change_base_ring(OEabs, d*Bpabs) dB2abs = change_base_ring(OEabs, d*B2abs) - Q, p = quo(OEabs, Pabs^(i+valuation(d, Pabs))) + Q, p = quo(OEabs, Pabs^(1+valuation(d, Pabs))) BpQ = map_entries(p, dBpabs) B2Q = map_entries(p, dB2abs) - println(BpQ, B2Q) KQ = solve_left(BpQ, B2Q) K = map_entries(a -> EabstoE(Eabs(p\a)), KQ) - @assert _scale_valuation(K*Bp-B2, P) >= i - println(K) + @assert _scale_valuation(K*newBp-newB2, P) >= 0 return K end @@ -567,7 +567,7 @@ end # Starting from our fake isometry g on H (which will be here D^{-1}H^#), and a # prime ideal P, we iteratively lift g to a finer fake isometry, i.e. a matrix # defining a P-local isometry up to the precision P^{2*k+a}. -function _approximate_isometry(H::Hecke.HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) +function _approximate_isometry(H::Hecke.HermLat, H2::Hecke.HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) E = base_field(H) @assert nf(order(P)) === E ok, b = is_modular(H, minimum(P)) @@ -575,9 +575,12 @@ function _approximate_isometry(H::Hecke.HermLat, g::AutomorphismGroupElem{TorQua return identity_matrix(E, 1) end - Bp, v = _local_basis_modular_submodules(H, minimum(P), a, res) + BHp = local_basis_matrix(H, minimum(P), type = :submodule) + BHp_inv = inv(BHp) + 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 = _transfer_discriminant_isometry(res, g, Bp, P, v) + Fp = block_diagonal_matrix([_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)) @@ -612,7 +615,7 @@ function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfOrdIdl, a: end push!(subs, b) end - return reduce(vcat, subs), a-exps[end] + return subs end # We need a special rho for Algorithm 8 of BH23: we construct such an element diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatWithIsom/LatWithIsom.jl index 14c0deade318..a7bd9ec3e8d6 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatWithIsom/LatWithIsom.jl @@ -638,3 +638,19 @@ function is_hermitian(t::Dict) return all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) end +############################################################################### +# +# Useful +# +############################################################################### + +function to_oscar(Lf::LatWithIsom) + L = lattice(Lf) + f = ambient_isometry(Lf) + println(stdout, "B = matrix(QQ, $(rank(L)), $(degree(L)), " , basis_matrix(L), " );") + println(stdout, "G = matrix(QQ, $(degree(L)), $(degree(L)), ", gram_matrix(ambient_space(L)), " );") + println(stdout, "L = Zlattice(B, gram = G);") + println(stdout, "f = matrix(QQ, $(degree(L)), $(degree(L)), ", f, " );") + println(stdout, "Lf = lattice_with_isometry(L, f);") +end + From b8fb430681e9dffcf3bb638081c5ce28044b9b2f Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 14 Apr 2023 19:33:13 +0200 Subject: [PATCH 45/76] adapt to new exp struct and further new codes --- experimental/LatWithIsom.jl | 1 - experimental/LatWithIsom/LWI.jl | 20 --- experimental/LatticesWithIsometry/README.md | 0 .../src}/Embeddings.jl | 89 +++++++---- .../src}/Enumeration.jl | 7 - .../LatticesWithIsometry/src/Exports.jl | 24 +++ .../src/HermitianMirandaMorrison.jl} | 150 +++++++++++++++--- .../src}/LatWithIsom.jl | 28 ---- .../src/LatticesWithIsometry.jl | 7 + .../LatticesWithIsometry/src/Printings.jl | 24 +++ .../src}/Types.jl | 6 +- src/Oscar.jl | 3 +- 12 files changed, 238 insertions(+), 121 deletions(-) delete mode 100644 experimental/LatWithIsom.jl delete mode 100644 experimental/LatWithIsom/LWI.jl create mode 100644 experimental/LatticesWithIsometry/README.md rename experimental/{LatWithIsom => LatticesWithIsometry/src}/Embeddings.jl (93%) rename experimental/{LatWithIsom => LatticesWithIsometry/src}/Enumeration.jl (98%) create mode 100644 experimental/LatticesWithIsometry/src/Exports.jl rename experimental/{LatWithIsom/HMM.jl => LatticesWithIsometry/src/HermitianMirandaMorrison.jl} (74%) rename experimental/{LatWithIsom => LatticesWithIsometry/src}/LatWithIsom.jl (95%) create mode 100644 experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl create mode 100644 experimental/LatticesWithIsometry/src/Printings.jl rename experimental/{LatWithIsom => LatticesWithIsometry/src}/Types.jl (81%) diff --git a/experimental/LatWithIsom.jl b/experimental/LatWithIsom.jl deleted file mode 100644 index 2bb2c27fca28..000000000000 --- a/experimental/LatWithIsom.jl +++ /dev/null @@ -1 +0,0 @@ -include("LatWithIsom/LWI.jl") diff --git a/experimental/LatWithIsom/LWI.jl b/experimental/LatWithIsom/LWI.jl deleted file mode 100644 index 90d453ad3c28..000000000000 --- a/experimental/LatWithIsom/LWI.jl +++ /dev/null @@ -1,20 +0,0 @@ -module LWI -using Oscar, Markdown -import Hecke - -macro req(args...) - @assert length(args) == 2 - quote - if !($(esc(args[1]))) - throw(ArgumentError($(esc(args[2])))) - end - end -end - -include("Types.jl") -include("HMM.jl") -include("LatWithIsom.jl") -include("Enumeration.jl") -include("Embeddings.jl") - -end diff --git a/experimental/LatticesWithIsometry/README.md b/experimental/LatticesWithIsometry/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/experimental/LatWithIsom/Embeddings.jl b/experimental/LatticesWithIsometry/src/Embeddings.jl similarity index 93% rename from experimental/LatWithIsom/Embeddings.jl rename to experimental/LatticesWithIsometry/src/Embeddings.jl index 75de9c43fb16..bc7899a9dafc 100644 --- a/experimental/LatWithIsom/Embeddings.jl +++ b/experimental/LatticesWithIsometry/src/Embeddings.jl @@ -1,19 +1,17 @@ -export admissible_equivariant_primitive_extensions -export equivariant_primitive_extensions -export primitive_embeddings_in_primary_lattice -export primitive_embeddings_of_elementary_lattice - const GG = GAP.Globals -import Base: +, -, *, ^ -import Oscar: evaluate -import Hecke: kernel ################################################################################ # # Miscellaneous # ################################################################################ +# this function is used whenever A and B are in direct orthogonal sum in a +# bigger torsion module. Let D be this sum. Since A and B are in orthogonal +# direct sum in D, we can embed O(A) and O(B) in O(D). +# +# This function returns D, the embeddings A\to D and B\to D, as well as O(D) +# 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)]) @@ -44,6 +42,11 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu return D, AinD, BinD, OD, OAtoOD, OBtoOD end +# Construct the direct sum D of A and B. Since the images of A and B are in +# orthogonal direct sum, we can embed O(A) in O(D) and O(B) in O(D). +# +# This function returns D together with the injections A \to D and B \to D, as +# well as O(D) with the embeddings O(A) \to O(D) and O(B) \to O(D). function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModule) D, inj = direct_sum(A, B) AinD, BinD = inj @@ -172,6 +175,13 @@ function kernel(f::TorQuadModuleMor) return sub(domain(f), TorQuadModuleElem[domain(f)(KgtoA(StoKg(a))) for a in gens(S)]) end +# If fq is an isometry of the torsion module q, we compute the kernel of mu(fq) +# restricted to the p-elementary part of q (i.e. the submodule of q generated by +# all the elements of order p) +# +# 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, mu, p) q = domain(fq) V, Vinq = primary_part(q, p) @@ -184,6 +194,7 @@ function _get_V(fq, mu, p) return K, Ktoq, fK end +# This is the rho function as defined in Definition 4.8 of BH23. function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) N = relations(q) if l == 0 @@ -227,47 +238,43 @@ function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) return sub(O, O.([h.X for h in gens(stab)])) end -function _as_Fp_vector_space_quotient(HinV, p, f) +# The underlying abelian groups of H and V are elementary abelian p-groups, f is +# an automorphism of V fixing H, so in particular it acts on the quotient V/H +# whose abelian structure actually defines a finite dimensional Fp-vector space. +# +# This map return Qp := V/H as an Fp-vector space, the map which transforms V into a +# Fp-vector space Vp, the quotient map Vp \to Qp, and the restriction fQp of f +# to Qp +function _cokernel_as_Fp_vector_space(HinV, p, f) if f isa AutomorphismGroupElem f = hom(f) end - i = HinV.map_ab H = domain(HinV) - Hab = domain(i) - Hs, HstoHab = snf(Hab) + HS, HStoH = snf(H) + HStoV = compose(HStoH, HinV) V = codomain(HinV) - Vab = codomain(i) - Vs, VstoVab = snf(Vab) - - function _VtoVs(x::TorQuadModuleElem) - return inv(VstoVab)(data(x)) - end - - function _VstoV(x::GrpAbFinGenElem) - return V(VstoVab(x)) - end + VS, VStoV = snf(V) - VtoVs = Hecke.MapFromFunc(_VtoVs, _VstoV, V, Vs) + VtoVS = inv(VStoV) n = ngens(Vs) F = GF(p) - MVs = matrix(compose(VstoVab, compose(f.map_ab, inv(VstoVab)))) + MVS = matrix(compose(VstoV, compose(f, VtoVS))) Vp = VectorSpace(F, n) - function _VstoVp(x::GrpAbFinGenElem) - v = x.coeff + function _VstoVp(x::TorQuadModuleElem) + v = data(x).coeff return Vp(vec(collect(v))) end function _VptoVs(v::ModuleElem{FpFieldElem}) x = lift.(v.v) - return Vs(vec(collect(x))) + return VS(vec(collect(x))) end - VstoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) - VtoVp = compose(VtoVs, VstoVp) - gene = FpMatrix[matrix(F.((i(HstoHab(a))).coeff)) for a in gens(Hs)] - subgene = [Vp(vec(collect(transpose(v)))) for v in gene] + VStoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) + VtoVp = compose(VtoVs, VStoVp) + subgene = elem_type(Vp)[VtoVp(HStoV(a)) for a in gens(HS)] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) fVp = change_base_ring(F, MVs) @@ -278,6 +285,14 @@ function _as_Fp_vector_space_quotient(HinV, p, f) return Qp, VtoVp, VptoQp, fQp end +# Given an abelian group injection V \to q where the group structure on V is +# abelian p-elementary, compute orbits and stabilizers of subgroups of V of +# order ord, which contains l*q and which are fixed under the action of f, under +# the action by automorphisms of q in G. +# +# Note that V is fixed by the elements in G, f commutes with the elements in G +# and G is seen as a set of outer automorphisms (so two subgroups are in the +# same orbit if they are G-automorphic). function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMor, G::AutomorphismGroup{TorQuadModule}, ord::Hecke.IntegerUnion, @@ -294,9 +309,9 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua q = codomain(Vinq) p = elementary_divisors(V)[1] g = valuation(ord, p) - @req all(a -> haspreimage(Vinq.map_ab, data(l*a))[1], gens(q)) "lq is not contained in V" + @req all(a -> haspreimage(abelian_group_homomorphism(Vinq), data(l*a))[1], gens(q)) "lq is not contained in V" H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) - Qp, VtoVp, VptoQp, fQp = _as_Fp_vector_space_quotient(H0inV, p, f) + Qp, VtoVp, VptoQp, fQp = _cokernel_as_Fp_vector_space(H0inV, p, f) Vp = codomain(VtoVp) if dim(Qp) == 0 @@ -387,7 +402,13 @@ end # here for convenience, we choose in entry N and M to be of full rank and # with basis matrix equal to the identity matrix - +# +# We compute representatives of isomorphism classes of primitive extensions +# M \oplus N \to L, where we glue along HM and HN which are respectively +# isometric and anti-isometric to H. +# +# We follow the second definition of Nikulin, i.e. we classify up to the actions +# of O(M) and O(N). function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadModule) @assert is_one(basis_matrix(N)) @assert is_one(basis_matrix(M)) diff --git a/experimental/LatWithIsom/Enumeration.jl b/experimental/LatticesWithIsometry/src/Enumeration.jl similarity index 98% rename from experimental/LatWithIsom/Enumeration.jl rename to experimental/LatticesWithIsometry/src/Enumeration.jl index d324f69bdecd..a71cffd12304 100644 --- a/experimental/LatWithIsom/Enumeration.jl +++ b/experimental/LatticesWithIsometry/src/Enumeration.jl @@ -1,10 +1,3 @@ -export admissible_triples -export is_admissible_triple -export splitting_of_hermitian_prime_power -export splitting_of_mixed_prime_power -export splitting_of_partial_mixed_prime_power -export splitting_of_prime_power -export representatives_of_hermitian_type ################################################################################## # diff --git a/experimental/LatticesWithIsometry/src/Exports.jl b/experimental/LatticesWithIsometry/src/Exports.jl new file mode 100644 index 000000000000..0cf97fd10dab --- /dev/null +++ b/experimental/LatticesWithIsometry/src/Exports.jl @@ -0,0 +1,24 @@ +export LatWithIsom + +export admissible_equivariant_primitive_extensions +export admissible_triples +export ambient_isometry +export equivariant_primitive_extensions +export image_centralizer_in_Oq +export isometry +export is_admissible_triple +export is_of_hermitian_type +export is_of_same_type +export is_of_type +export is_hermitian +export lattice_with_isometry +export order_of_isometry +export primitive_embeddings_in_primary_lattice +export primitive_embeddings_of_elementary_lattice +export representatives_of_hermitian_type +export splitting_of_hermitian_prime_power +export splitting_of_mixed_prime_power +export splitting_of_partial_mixed_prime_power +export splitting_of_prime_power +export type + diff --git a/experimental/LatWithIsom/HMM.jl b/experimental/LatticesWithIsometry/src/HermitianMirandaMorrison.jl similarity index 74% rename from experimental/LatWithIsom/HMM.jl rename to experimental/LatticesWithIsometry/src/HermitianMirandaMorrison.jl index f6b4ef93b7e6..684e3aa3bdff 100644 --- a/experimental/LatWithIsom/HMM.jl +++ b/experimental/LatticesWithIsometry/src/HermitianMirandaMorrison.jl @@ -2,9 +2,15 @@ ############################################################################### # # Computations of the finite quotients E_0/E^i: subsection 6.8. of BH23 +# ============================================================================ +# +# The 5 following functions are an import of identical functions written by +# Tommy Hofmann on Magma. # ############################################################################### +# Split case + function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) OE = order(P) E = nf(OE) @@ -47,6 +53,8 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) return URPabs, exp, dlog end +# Inert case + function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) OE = order(P) OK = base_ring(OE) @@ -98,6 +106,8 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) return S, exp, dlog end +# Ramified case + function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) OE = order(P) E = nf(OE) @@ -166,6 +176,9 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) return S, exp, dlog end +# We obtain the quotient here: we first check what the ramification type of p +# in O is to distribute to the appropriate function above. + function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) @assert is_prime(p) @assert is_maximal(order(p)) @@ -189,6 +202,10 @@ function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) return S, dexp, dlog, P end +# This is the product quotient from Remark 6.16 in BH23, the finite +# abelian group where one do the local determinants computations +# for the hermitian version of Miranda-Morrison theory + function _get_product_quotient(E::Hecke.NfRel, Fac) OE = maximal_order(E) groups = GrpAbFinGen[] @@ -248,7 +265,13 @@ end ############################################################################### # We collect all the prime ideals p for which the local quotient -# (D^{-1}L^#/L)_p is not unimodular +# (D^{-1}L^#/L)_p is not unimodular. +# +# According to BH23, the quotient D^{-1}L^#/L is unimodular at p +# if and only if +# - either L is unimodular at p, and D and p are coprime +# - or D^{-1}L^# is P^a-modular where P is largest prime ideal +# over p fixed the canonical involution, and a is the valuation of D at P. function _elementary_divisors(L::HermLat, D::Hecke.NfRelOrdIdl) Ps = collect(keys(factor(D))) @@ -268,6 +291,23 @@ end # We compute here the map delta from Theorem 6.15 of BH22. Its kernel is # precisely the image of the map O(L, f) \to O(D_L, D_f). +# +# This map is defined on the centralizer O(D_L, D_f) of D_f in O(D_L). For +# each generator g, we find a good enough approximation on the ambient space +# of L restricting g, which we transport by the trace construction on the +# hermitian structure of (L, f), and we create a better approximation of the new +# isometry of D^{-1}L^#/L on the hermitian ambient space of L, up to the +# valuation given by Theorem 6.25 in BH23. +# +# This better approximation is obtained by applying successive hermitian hensel +# lifting as described in Algorithm 8 of BH23. +# +# Once this new approximation obtained, we compute its determinant which we map +# in the big product quotient constructed according to BH23, Section 6. +# +# The major part of the following algorithm follows the implementation of a +# similar function on Magma by Tommy Hofmann. Only the last loop about +# determinants approximations is new in this code. function _local_determinants_morphism(Lf::LatWithIsom) @assert is_of_hermitian_type(Lf) @@ -279,6 +319,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) # 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 BH22. G, _ = centralizer(OqL, fqL) + # 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, # where D is the absolute different of the base algebra of H (a cyclotomic @@ -294,6 +335,11 @@ function _local_determinants_morphism(Lf::LatWithIsom) H2 = inv(DEQ)*dual(H) @assert is_sublattice(H2, H) # This should be true since the lattice in Lf is integral + # This is the map used for the trace construction: it is stored on Lf when we + # have constructed H. We need this map because it sets the rule of the + # transfer between the quadratic world in which we study (L, f) and the + # hermitian world in which H lives. In particular, the trace lattice of H2 + # with respect to res will be exactly the dual of L. res = get_attribute(Lf, :transfer_data) # We want only the prime ideal in O_K which divides the quotient H2/H. For @@ -405,6 +451,17 @@ function _local_determinants_morphism(Lf::LatWithIsom) end # We check whether for the prime ideal p E_O(L_p) != F(L_p). +# +# There exists some prime ideals p which are elementary divisors for the +# quotients D^{-1}L^#/L and, according to Kir16b, for which the above quotient +# can still be trivial. We call special those which do not satisfy this second +# property: considering them, we can actually reduce the size of the finite +# abelian group in which we will perform our local approximate determinants +# computations. +# +# This function is an import of a function written by Markus Kirschmer in the +# Magma package about hermitian lattices. + function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) OE = base_ring(L) E = nf(OE) @@ -433,7 +490,6 @@ function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) return is_locally_isometric(L, hermitian_lattice(E, gram = H), p) end - ############################################################################### # # Local hermitian lifting - path to algorithm 8 of BH23 @@ -442,24 +498,12 @@ end Oscar.canonical_unit(x::NfOrdQuoRingElem) = one(parent(x)) -# Starting from an isometry of the torsion quadratic module `domain(g)`, for -# which we assume that the cover M has full rank, we compute a fake lift to the -# ambient space of the cover. This is not an isometry, but induces g on `domain(g)`. -#function _get_ambient_isometry(g::AutomorphismGroupElem{TorQuadModule}) -# q = domain(g) -# L = cover(q) -# @assert rank(L) == degree(L) -# d = degree(L) -# M1 = reduce(vcat, [matrix(QQ, 1, d, lift(t)) for t in gens(q)]) -# M2 = reduce(vcat, [matrix(QQ, 1, d, lift(g(t))) for t in gens(q)]) -# return solve(M1, M2) -#end - -# Using the function used for the transfer construction, between the ambient -# space of the cover of our torsion module, and the ambient space of the -# corresponding hermitian structure, we transfer the fake lift computed with the -# previous function. This will be an invertible matrix, but the corresponding -# automorphism is not an isometry. +# Once we have fixed good local basis matrices if D^{-1}H^# and H, at the prime +# ideal p below P, we transfer any g\in O(D_L, D_f) via the trace construction +# (fixed by res). The new map we obtain should be a local invertible map with +# integer (for the given local field at p) entries which defines an isometry of +# D^{-1}H^# modulo H. + function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T, P::Hecke.NfRelOrdIdl, BHp::T) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} E = base_ring(codomain(res)) Eabs, EabstoE = Hecke.absolute_simple_field(E) @@ -467,7 +511,9 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG OEabs = order(Pabs) q = domain(g) @assert ambient_space(cover(q)) === domain(res) - #Bp = reduce(vcat, [matrix(E, 1, ncols(Bp), res(lift(q(res\vec(collect(Bp[i, :])))))) for i in 1:nrows(Bp)]) + + # B2 will be a local basis at p of the image of D^{-1}H^# under the induced + # action of g via res. B2 = zero(Bp) for i in 1:nrows(Bp) vE = vec(collect(Bp[i, :])) @@ -479,18 +525,42 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG B2[i, :] = gvE end + # Here BHp is the inverse of a local basis matrix of H at p. Since we look for + # F such that F*Bp == B2 mod inv(BHp), we then transform the problem into + # finding a matrix F such that F*newBp == newB2 mod O_p. Then to ensure that F + # has integral coefficients, we multiplying newBp and newB2 by a big enough + # integer d so that they are both integral (all their entries are in OEabs, + # after passing to an absolute simple extension), we keep track of the + # P-valuation i of d, and we then map everything in OEabs/Pabs^i. There, there + # should be a modular solution KQ such that KQ*BpQ == B2Q. We lift this matrix + # in OEabs and map it back to OE. newBp = Bp*BHp newB2 = B2*BHp Bpabs = map_entries(a -> EabstoE\a, newBp) B2abs = map_entries(a -> EabstoE\a, newB2) + + # Here d is not necessarily well defined in O_E, but as it is implemented, + # each of the denominator(a, O_E) return the smallest positive integer d such + # that d*a lies in O_E, while a might be a non-integral element in E. d = lcm(lcm([denominator(a, OEabs) for a in Bpabs]), lcm([denominator(a, OEabs) for a in B2abs])) dBpabs = change_base_ring(OEabs, d*Bpabs) dB2abs = change_base_ring(OEabs, d*B2abs) - Q, p = quo(OEabs, Pabs^(1+valuation(d, Pabs))) + + # We would need to solve the equation modulo OE, but we multiplied by d, so we + # need to keep track of d. Note that with the precaution we took earlier, the + # Pabs-valuation of d is necessarily positive, so this quotient cannot be + # trivial. + Q, p = quo(OEabs, Pabs^(valuation(d, Pabs))) BpQ = map_entries(p, dBpabs) B2Q = map_entries(p, dB2abs) + + # Our local modular solution we have to lift KQ = solve_left(BpQ, B2Q) K = map_entries(a -> EabstoE(Eabs(p\a)), KQ) + + # If what we have done is correct then K*newBp == newB2 modulo O_p, so all + # entries in the difference K*newBp-newB2 must have non-negative P-valuation. + # Since it is the case, then K satisfies K*Bp == B2 mod H locally at p. @assert _scale_valuation(K*newBp-newB2, P) >= 0 return K end @@ -564,9 +634,20 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H return newF, l2 end -# Starting from our fake isometry g on H (which will be here D^{-1}H^#), and a -# prime ideal P, we iteratively lift g to a finer fake isometry, i.e. a matrix -# defining a P-local isometry up to the precision P^{2*k+a}. +# Starting from our isometry g on H2/H (here H2= D^{-1}H^#), and a +# prime ideal P, we iteratively lift g to invertible map which defines an +# isometry of H2 up to a given precision specified in BH23, local at the prime +# ideal p below P. +# +# Here: +# - e is the P-valuation of the relative different +# - a is the P-valuation of the absolute different +# - k is the p-valuation of the norm of H +# - res is the map representing the functor used for the trace equivalence +# - g is the torsion quadratic module automorphism, which centralizes D_f in +# D_L (H is the hermitian structure associated to (L, f) along res) which +# aims to approximately transfer to H2 along res at P +# function _approximate_isometry(H::Hecke.HermLat, H2::Hecke.HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) E = base_field(H) @assert nf(order(P)) === E @@ -595,6 +676,17 @@ function _approximate_isometry(H::Hecke.HermLat, H2::Hecke.HermLat, g::Automorph return Fp end +# We use this function compute a local basis matrix of H at p, whose integral +# span defines a sublattice of H. If H has a P^{-a}-modular block at p, we remove +# the corresponding basis vector(s) from the output to only keep the Jordan +# blocks of H_p which are of P-valuation different from -a +# +# In our context of use, -a is actually the biggest scale valuation for +# D^{-1}H^#. Since we take care ealier to discard the cases where D^{-1}H^# is +# P^{-a}=modular locally at p, we just have to remove the last jordan block from +# the jordan decomposition of D^{-1}H^# at p. From that point, we massage a bit +# the basis matrices of the other jordan blocks to obtain local basis matrices +# which span sublattices of D^{-1}H^#. function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfOrdIdl, a::Int, res::AbstractSpaceRes) L = restrict_scalars(H, res) B, _ , exps = jordan_decomposition(H, p) @@ -620,6 +712,14 @@ end # We need a special rho for Algorithm 8 of BH23: we construct such an element # here, which will be used to lift fake isometries up to a better precision. +# +# If the prime ideal is non dyadic, then 1//2 will always be good for us. +# Otherwise, if the prime p is dyadic, we do in two different ways +# - p is inert or split, in which case we can use an algorithm of Markus +# Kirschmer, implemented on Hecke which provies us the "special unit", which is +# 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) OE = order(P) E = nf(OE) diff --git a/experimental/LatWithIsom/LatWithIsom.jl b/experimental/LatticesWithIsometry/src/LatWithIsom.jl similarity index 95% rename from experimental/LatWithIsom/LatWithIsom.jl rename to experimental/LatticesWithIsometry/src/LatWithIsom.jl index a7bd9ec3e8d6..5f6a383d81b9 100644 --- a/experimental/LatWithIsom/LatWithIsom.jl +++ b/experimental/LatticesWithIsometry/src/LatWithIsom.jl @@ -1,31 +1,3 @@ -export ambient_isometry -export image_centralizer_in_Oq -export isometry -export is_of_hermitian_type -export is_of_same_type -export is_of_type -export is_hermitian -export lattice_with_isometry -export order_of_isometry -export type - -import Hecke: kernel_lattice, invariant_lattice, rank, genus, basis_matrix, - gram_matrix, ambient_space, rational_span, scale, signature_tuple, - is_integral, det, norm, degree, discriminant, charpoly, minpoly, - rescale, dual, lll, discriminant_group, divides, lattice, - hermitian_structure, coinvariant_lattice - -############################################################################### -# -# String I/O -# -############################################################################### - -function Base.show(io::IO, Lf::LatWithIsom) - println(io, "Lattice of rank $(rank(Lf)) with isometry of order $(order_of_isometry(Lf))") - println(io, "given by") - print(IOContext(io, :compact => true), isometry(Lf)) -end ############################################################################### # diff --git a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl new file mode 100644 index 000000000000..c4886eae94f1 --- /dev/null +++ b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl @@ -0,0 +1,7 @@ +include("Exports.jl") +include("Types.jl") +include("LatWithIsom.jl") +include("HermitianMirandaMorrison.jl") +include("Enumeration.jl") +include("Embeddings.jl") +include("Printings.jl") diff --git a/experimental/LatticesWithIsometry/src/Printings.jl b/experimental/LatticesWithIsometry/src/Printings.jl new file mode 100644 index 000000000000..0bb83d08689f --- /dev/null +++ b/experimental/LatticesWithIsometry/src/Printings.jl @@ -0,0 +1,24 @@ +function Base.show(io::IO, ::MIME"text/plain", Lf::LatWithIsom) + println(io, lattice(Lf)) + n = order_of_isometry(Lf) + if is_finite(n) + println(io, "with isometry of finite order $n") + else + println(io, "with isometry of infinite order") + end + println(io, "given by") + print(IOContext(io, :compact => true), isometry(Lf)) +end + +function Base.show(io::IO, Lf::LatWithIsom) + if get(io, :supercompact, false) + print(io, "Integer lattice with isometry") + else + n = order_of_isometry(Lf) + if is_finite(n) + print(io, "Integer lattice with isometry of finite order $n") + else + print(io, "Integer lattice with isometry of infinite order") + end + end +end diff --git a/experimental/LatWithIsom/Types.jl b/experimental/LatticesWithIsometry/src/Types.jl similarity index 81% rename from experimental/LatWithIsom/Types.jl rename to experimental/LatticesWithIsometry/src/Types.jl index d9b1f914a262..e376bf0ad8ba 100644 --- a/experimental/LatWithIsom/Types.jl +++ b/experimental/LatticesWithIsometry/src/Types.jl @@ -1,14 +1,12 @@ -export LatWithIsom - @doc Markdown.doc""" LatWithIsom -A container type for a pair `(L, f)` consisting on an integer lattice `L` of +A container type for pairs `(L, f)` consisting on an integer lattice `L` of type `ZLat` and an isometry `f` given as a `QQMatrix` representing the action on a given basis of `L`. The associated action `f_ambient` on the ambient space of `L` as well as the order -`n` of `f` are stored in this container type. +`n` of `f` are also stored. To construct an object of type `LatWithIsom`, see the set of functions called [`lattice_with_isometry`](@ref) diff --git a/src/Oscar.jl b/src/Oscar.jl index a7d3dabf3475..6bd27baa9a1e 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -45,8 +45,7 @@ const oldexppkgs = [ "MPolyRingSparse", "Rings", "Schemes", - "SymmetricIntersections", - "LatWithIsom" + "SymmetricIntersections" ] const exppkgs = filter(x->isdir(joinpath(expdir, x)) && !(x in oldexppkgs), readdir(expdir)) From 94c363225af5db9332390311d7b45ddbff5cc679 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 1 May 2023 17:35:52 +0200 Subject: [PATCH 46/76] change format --- .../src/LatticesWithIsometry.jl | 24 +- .../src/{Embeddings.jl => embeddings.jl} | 611 ++++++++---------- .../src/{Enumeration.jl => enumeration.jl} | 57 +- .../src/{Exports.jl => exports.jl} | 2 - ...rison.jl => hermitian_miranda_morrison.jl} | 74 +-- ...tWithIsom.jl => lattices_with_isometry.jl} | 16 +- .../src/{Printings.jl => printings.jl} | 4 +- .../src/{Types.jl => types.jl} | 0 8 files changed, 356 insertions(+), 432 deletions(-) rename experimental/LatticesWithIsometry/src/{Embeddings.jl => embeddings.jl} (68%) rename experimental/LatticesWithIsometry/src/{Enumeration.jl => enumeration.jl} (93%) rename experimental/LatticesWithIsometry/src/{Exports.jl => exports.jl} (86%) rename experimental/LatticesWithIsometry/src/{HermitianMirandaMorrison.jl => hermitian_miranda_morrison.jl} (91%) rename experimental/LatticesWithIsometry/src/{LatWithIsom.jl => lattices_with_isometry.jl} (97%) rename experimental/LatticesWithIsometry/src/{Printings.jl => printings.jl} (84%) rename experimental/LatticesWithIsometry/src/{Types.jl => types.jl} (100%) diff --git a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl index c4886eae94f1..c358246d605c 100644 --- a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl +++ b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl @@ -1,7 +1,17 @@ -include("Exports.jl") -include("Types.jl") -include("LatWithIsom.jl") -include("HermitianMirandaMorrison.jl") -include("Enumeration.jl") -include("Embeddings.jl") -include("Printings.jl") +add_assertion_scope(:LatWithIsom) +add_verbosity_scope(:LatWithIsom) + +function set_lwi_level(k::Int) + set_assertion_level(:LatWithIsom, k) + set_verbosity_level(:LatWithIsom, k) +end + +set_lwi_level(1) + +include("exports.jl") +include("types.jl") +include("lattices_with_isometry.jl") +include("hermitian_miranda_morrison.jl") +include("enumeration.jl") +include("embeddings.jl") +include("printings.jl") diff --git a/experimental/LatticesWithIsometry/src/Embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl similarity index 68% rename from experimental/LatticesWithIsometry/src/Embeddings.jl rename to experimental/LatticesWithIsometry/src/embeddings.jl index bc7899a9dafc..65c59478cbf2 100644 --- a/experimental/LatticesWithIsometry/src/Embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -72,109 +72,6 @@ function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQ return D, AinD, BinD, OD, OAtoOD, OBtoOD end -function restrict_automorphism(f::TorQuadModuleMor, i::TorQuadModuleMor) - imgs = TorQuadModuleElem[] - V = domain(i) - for g in gens(V) - h = f(i(g)) - hV = preimage(i, h) - push!(imgs, hV) - end - return hom(V, V, imgs) -end - -function restrict_automorphism(f::AutomorphismGroupElem{TorQuadModule}, i::TorQuadModuleMor) - return restrict_automorphism(hom(f), i) -end - -function is_invariant(f::TorQuadModuleMor, i::TorQuadModuleMor) - fab = f.map_ab - V = domain(i) - for a in gens(V) - b = f(i(a)) - haspreimage(i.map_ab, data(b))[1] || return false - end - return true -end - -function is_invariant(f::AutomorphismGroupElem{TorQuadModule}, i::TorQuadModuleMor) - return is_invariant(hom(f), i) -end - -function is_invariant(aut::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) - return all(g -> is_invariant(g, i), gens(aut)) -end - -function ^(f::TorQuadModuleMor, y::Integer) - @assert y >= 0 - @assert domain(f) === codomain(f) - if y == 0 - return id_hom(domain(f)) - elseif y == 1 - return f - else - k = 1 - f2 = f - while k != y - f2 = compose(f2, f) - k += 1 - end - return f2 - end -end - -function +(f::TorQuadModuleMor, g::TorQuadModuleMor) - @assert domain(f) === domain(g) - @assert codomain(f) === codomain(g) - return hom(domain(f), codomain(g), [f(a)+g(a) for a in gens(domain(f))]) -end - -function -(f::TorQuadModuleMor, g::TorQuadModuleMor) - @assert domain(f) === domain(g) - @assert codomain(f) === codomain(g) - return hom(domain(f), codomain(g), [f(a)-g(a) for a in gens(domain(f))]) -end - -function *(x::Int, f::TorQuadModuleMor) - z = hom(domain(f), codomain(f), zero_matrix(ZZ, ngens(domain(f)), ngens(codomain(f)))) - if x == 0 - return z - elseif x < 0 - neg = true - x = -x - else - neg = false - end - k = 1 - f2 = f - while k != x - f2 += f - k += 1 - end - if neg - return z-f - else - return f - end -end - -function evaluate(p::QQPolyRingElem, f::TorQuadModuleMor) - c = collect(coefficients(p)) - @assert domain(f) === codomain(f) - g = hom(domain(f), codomain(f), zero_matrix(ZZ, ngens(domain(f)), ngens(codomain(f)))) - for i in 1:length(c) - g += Int(ZZ(c[i]))*f^(i-1) - end - return g -end - -function kernel(f::TorQuadModuleMor) - g = f.map_ab - Kg, KgtoA = Hecke.kernel(g) - S, StoKg = snf(Kg) - return sub(domain(f), TorQuadModuleElem[domain(f)(KgtoA(StoKg(a))) for a in gens(S)]) -end - # If fq is an isometry of the torsion module q, we compute the kernel of mu(fq) # restricted to the p-elementary part of q (i.e. the submodule of q generated by # all the elements of order p) @@ -220,7 +117,7 @@ end # ############################################################################## -function on_subgroup(H::Oscar.GAPGroup, g::AutomorphismGroupElem) +function _on_subgroup_automorphic(H::Oscar.GAPGroup, g::AutomorphismGroupElem) G = domain(parent(g)) return sub(G, g.(gens(H)))[1] end @@ -234,7 +131,7 @@ function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) OA = automorphism_group(A) OinOA, _ = sub(OA, OA.([g.X for g in gens(O)])) N, _ = sub(A, togap.(i.(gens(domain(i))))) - stab, _ = Oscar.stabilizer(OinOA, N, on_subgroup) + stab, _ = stabilizer(OinOA, N, _on_subgroup_automorphic) return sub(O, O.([h.X for h in gens(stab)])) end @@ -293,55 +190,52 @@ end # Note that V is fixed by the elements in G, f commutes with the elements in G # and G is seen as a set of outer automorphisms (so two subgroups are in the # same orbit if they are G-automorphic). -function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMor, - G::AutomorphismGroup{TorQuadModule}, - ord::Hecke.IntegerUnion, - f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq)), - l::Hecke.IntegerUnion = 0) +function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMor, + G::AutomorphismGroup{TorQuadModule}, + ord::Hecke.IntegerUnion, + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq)), + l::Hecke.IntegerUnion = 0) + res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] + if ord == 1 - if l != 0 - return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] - end + l != 0 && (return res) _, triv = sub(codomain(Vinq), TorQuadModuleElem[]) - return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[(triv, G)] + push!(res, (triv, G)) + return res end + V = domain(Vinq) q = codomain(Vinq) p = elementary_divisors(V)[1] + pq, pqtoq = primary_part(q, p) g = valuation(ord, p) - @req all(a -> haspreimage(abelian_group_homomorphism(Vinq), data(l*a))[1], gens(q)) "lq is not contained in V" - H0, H0inV = sub(V, [preimage(Vinq, (l*a)) for a in gens(q)]) + @hassert :LatWithIsom 1 all(a -> haspreimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) + H0, H0inV = sub(V, [preimage(Vinq, (p^l)*pqtoq(a)) for a in gens(pq)]) Qp, VtoVp, VptoQp, fQp = _cokernel_as_Fp_vector_space(H0inV, p, f) Vp = codomain(VtoVp) if dim(Qp) == 0 - if order(V) == ord - return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[(Vinq, G)] - end - return Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] + order(V) != ord && (return res) + push!(res, (Vinq, G)) + return res end OV = orthogonal_group(V) - gene_GV = elem_type(OV)[OV(restrict_automorphism(g, Vinq), check = false) for g in gens(G)] - GV, _ = sub(OV, gene_GV) - GVinG = hom(GV, G, gens(GV), gens(G), check = false) + GV, GtoGV = restrict_automorphism_group(G, Vinq) act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(act_GV_Qp) - @assert fQp in MGp + @hassert :LatWithIsom 1 fQp in MGp MGptoGV = hom(MGp, GV, gens(GV), check = false) - MGptoG = compose(MGptoGV, GVinG) - - res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] if g-ngens(snf(abelian_group(H0))[1]) > dim(Qp) return res end F = base_ring(Qp) - k, K = Hecke.kernel(VptoQp.matrix, side = :left) - gene_H0p = ModuleElem{fpFieldElem}[Vp(vec(collect(K[i,:]))) for i in 1:k] + k, K = kernel(VptoQp.matrix, side = :left) + gene_H0p = elem_type(Vp)[Vp(vec(collect(K[i,:]))) for i in 1:k] orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) for (orb, stab) in orb_and_stab i = orb.map @@ -352,21 +246,25 @@ function subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQua @goto non_fixed end end - gene_orb_in_Qp = AbstractAlgebra.Generic.QuotientModuleElem{FpFieldElem}[Qp(vec(collect(i(v).v))) for v in gens(orb)] - gene_orb_in_Vp = AbstractAlgebra.Generic.ModuleElem{FpFieldElem}[preimage(VptoQp, v) for v in gene_orb_in_Qp] + + gene_orb_in_Qp = elem_type(Qp)[Qp(vec(collect(i(v).v))) for v in gens(orb)] + gene_orb_in_Vp = elem_type(Vp)[preimage(VptoQp, v) for v in gene_orb_in_Qp] + gene_submod_in_Vp = vcat(gene_orb_in_Vp, gene_H0p) gene_submod_in_V = TorQuadModuleElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] gene_submod_in_q = TorQuadModuleElem[image(Vinq, v) for v in gene_submod_in_V] orbq, orbqinq = sub(q, gene_submod_in_q) - @assert order(orbq) == ord - stabq, _ = image(MGptoG, stab) + @hassert :LatWithIsom 1 order(orbq) == ord + + stabq = AutomorphismGroup{TorQuadModule}[GtoGV\(MGptoGV(s)) for s in gens(stab)] + stabq, _ = sub(G, stabq) push!(res, (orbqinq, stabq)) @label non_fixed end return res end -function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadModule}; order::Hecke.IntegerUnion = -1) +function _subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadModule}; order::Hecke.IntegerUnion = -1) togap = get_attribute(O, :to_gap) tooscar = get_attribute(O, :to_oscar) q = domain(O) @@ -381,7 +279,7 @@ function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{To res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] for orb in orbs rep = representative(orb) - stab, _ = Oscar.stabilizer(OinOAgap, rep, on_subgroup) + stab, _ = stabilizer(OinOAgap, rep, on_subgroup) _, rep = sub(q, TorQuadModuleElem[tooscar(Agap(g)) for g in gens(rep)]) stab, _ = sub(O, O.([h.X for h in gens(stab)])) push!(res, (rep, stab)) @@ -389,8 +287,8 @@ function subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{To return res end -function classes_conjugate_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) - sors = subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) +function _classes_automorphic_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) + sors = _subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) end @@ -410,8 +308,8 @@ end # We follow the second definition of Nikulin, i.e. we classify up to the actions # of O(M) and O(N). function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadModule) - @assert is_one(basis_matrix(N)) - @assert is_one(basis_matrix(M)) + @hassert :LatWithIsom 1 is_one(basis_matrix(N)) + @hassert :LatWithIsom 1 is_one(basis_matrix(M)) results = Tuple{ZLat, ZLat, ZLat}[] GN, _ = image_in_Oq(N) GM, _ = image_in_Oq(M) @@ -422,14 +320,14 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM qNinD, qMinD = inj OD = orthogonal_group(D) - subsN = classes_conjugate_subgroups(GN, rescale(H, -1)) - @assert !isempty(subsN) - subsM = classes_conjugate_subgroups(GM, H) - @assert !isempty(subsM) + subsN = _classes_automorphic_subgroups(GN, rescale(H, -1)) + @hassert :LatWithIsom 1 !isempty(subsN) + subsM = _classes_automorphic_subgroups(GM, H) + @hassert :LatWithIsom 1 !isempty(subsM) for H1 in subsN, H2 in subsM ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) - @assert ok + @hassert :LatWithIsom 1 ok HNinqN, stabN = H1 HN = domain(HNinqN) @@ -447,7 +345,8 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) - @info "$(length(reps)) isomorphism classe(s) of primitive extensions" + @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" + for g in reps g = representative(g) phig = compose(phi, hom(g)) @@ -460,11 +359,11 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) L = Hecke.lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) - @assert genus(N) == genus(N2) + @hassert :LatWithIsom 1 genus(N) == genus(N2) M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) - @assert genus(M) == genus(M2) + @hassert :LatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) - @info "Gluing done" + @vprint :LatWithIsom 1 "Gluing done" GC.gc() end end @@ -472,12 +371,12 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM end function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadModule) - @assert is_one(basis_matrix(N)) - @assert is_one(basis_matrix(M)) + @hassert :LatWithIsom 1 is_one(basis_matrix(N)) + @hassert :LatWithIsom 1 is_one(basis_matrix(M)) q = elementary_divisors(H)[end] ok, p, _ = is_prime_power_with_data(q) - @assert ok - @assert is_elementary(M, p) + @hassert :LatWithIsom 1 ok + @hassert :LatWithIsom 1 is_elementary(M, p) results = Tuple{ZLat, ZLat, ZLat}[] GN, _ = image_in_Oq(N) GM, _ = image_in_Oq(M) @@ -488,16 +387,16 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: qNinD, qMinD = inj OD = orthogonal_group(D) VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) - subsN = subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) + subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) @assert !isempty(subsN) - subsM = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) + subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) @assert !isempty(subsM) for H1 in subsN, H2 in subsM ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) - @assert ok + @hassert :LatWithIsom 1 ok HNinqN, stabN = H1 OHN = orthogonal_group(HN) @@ -513,7 +412,7 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) - @info "$(length(reps)) isomorphism classe(s) of primitive extensions" + @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" for g in reps g = representative(g) phig = compose(phi, hom(g)) @@ -526,11 +425,11 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) - @assert genus(N) == genus(N2) + @hassert :LatWithIsom 1 genus(N) == genus(N2) M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) - @assert genus(M) == genus(M2) + @hassert :LatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) - @info "Gluing done" + @vprint :LatWithIsom 1 "Gluing done" GC.gc() end end @@ -578,23 +477,23 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph VM, VMinqM = primary_part(qM, p) end for k in divisors(gcd(order(VM), order(qL))) - @info "Glue order: $(k)" + @vprint :LatWithIsom 1 "Glue order: $(k)" if el subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(GL), GL, k) else subsL = subgroups_orbit_representatives_and_stabilizers(GL, order = k) end - @info "$(length(subsL)) subgroup(s)" + @vprint :LatWithIsom 1 "$(length(subsL)) subgroup(s)" for H in subsL HL = domain(H[1]) it = subgroups(abelian_group(VM), order = order(HL)) subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1])))))[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue - @info "Possible gluings" + @vprint :LatWithIsom 1 "Possible gluings" HM = subsM[1] ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) - @assert ok + @hassert :LatWithIsom 1 ok _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) @@ -603,9 +502,9 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph disc = rescale(disc, -1) !is_genus(disc, (pL-pM, nL-nM)) && continue G = genus(disc, (pL-pM, nL-nM)) - @info "We can glue: $G" + @vprint :LatWithIsom 1 "We can glue: $G" Ns = representatives(G) - @info "$(length(Ns)) possible orthogonal complement(s)" + @vprint :LatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) @@ -616,74 +515,74 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph GC.gc() end end - @assert all(triple -> genus(triple[1]) == genus(L), results) + @hassert :LatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) return results end -function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) - bool, p = is_elementary_with_prime(M) - @req bool "M must be elementary" - if check - @req length(genus_representatives(L)) == 1 "L must be unique in its genus" - end - results = Tuple{ZLat, ZLat, ZLat}[] - qL = rescale(discriminant_group(L), -1) - GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) - pL, _, nL = signature_tuple(L) - pM, _, nM = signature_tuple(M) - @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" - if ((pL, nL) == (pM, nM)) - if genus(M) != genus(L) - return false, results - else - return true, [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] - end - end - qM = discriminant_group(M) - D, inj, proj = biproduct(qM, qL) - qMinD, qLinD = inj - VL, VLinqL, _ = _get_V(L, qL, identity_matrix(QQ, rank(L)), id_hom(qL), minpoly(identity_matrix(QQ,1)), p) - for k in divisors(gcd(order(qM), order(VL))) - @info "Glue order: $(k)" - subsL = subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) - @info "$(length(subsL)) subgroup(s)" - subsM = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), orthogonal_group(qM), k) - for H in subsL - HL = domain(H[1]) - _subsM = filter(HM -> is_anti_isometric_with_anti_isometry(domain(HM[1]), HL)[1], subsM) - isempty(_subsM) && continue - @info "Possible gluing" - HM = _subsM[1][1] - ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) - @assert ok - _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] - ext, _ = sub(D, D.(_glue)) - 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 = rescale(disc, -1) - !is_genus(disc, (pL-pM, nL-nM)) && continue - G = genus(disc, (pL-pM, nL-nM)) - !classification && return true, G - @info "We can glue: $G" - Ns = representatives(G) - @info "$(length(Ns)) possible orthogonal complement(s)" - Ns = lll.(Ns) - Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] - qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) - for N in Ns - append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) - if first - return results - end - GC.gc() - end - GC.gc() - end - end - @assert all(triple -> genus(triple[1]) == genus(L), results) - return (length(results) >0), results -end +#function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) +# bool, p = is_elementary_with_prime(M) +# @req bool "M must be elementary" +# if check +# @req length(genus_representatives(L)) == 1 "L must be unique in its genus" +# end +# results = Tuple{ZLat, ZLat, ZLat}[] +# qL = rescale(discriminant_group(L), -1) +# GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) +# pL, _, nL = signature_tuple(L) +# pM, _, nM = signature_tuple(M) +# @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" +# if ((pL, nL) == (pM, nM)) +# if genus(M) != genus(L) +# return false, results +# else +# return true, [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] +# end +# end +# qM = discriminant_group(M) +# D, inj, proj = biproduct(qM, qL) +# qMinD, qLinD = inj +# VL, VLinqL, _ = _get_V(L, qL, identity_matrix(QQ, rank(L)), id_hom(qL), minpoly(identity_matrix(QQ,1)), p) +# for k in divisors(gcd(order(qM), order(VL))) +# @info "Glue order: $(k)" +# subsL = subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) +# @info "$(length(subsL)) subgroup(s)" +# subsM = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), orthogonal_group(qM), k) +# for H in subsL +# HL = domain(H[1]) +# _subsM = filter(HM -> is_anti_isometric_with_anti_isometry(domain(HM[1]), HL)[1], subsM) +# isempty(_subsM) && continue +# @info "Possible gluing" +# HM = _subsM[1][1] +# ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) +# @assert ok +# _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] +# ext, _ = sub(D, D.(_glue)) +# 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 = rescale(disc, -1) +# !is_genus(disc, (pL-pM, nL-nM)) && continue +# G = genus(disc, (pL-pM, nL-nM)) +# !classification && return true, G +# @info "We can glue: $G" +# Ns = representatives(G) +# @info "$(length(Ns)) possible orthogonal complement(s)" +# Ns = lll.(Ns) +# Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] +# qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) +# for N in Ns +# append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) +# if first +# return results +# end +# GC.gc() +# end +# GC.gc() +# end +# end +# @assert all(triple -> genus(triple[1]) == genus(L), results) +# return (length(results) >0), results +#end #################################################################################### # @@ -691,105 +590,105 @@ end # #################################################################################### -function equivariant_primitive_extensions(A::LatWithIsom, GA::AutomorphismGroup{TorQuadModule}, - B::LatWithIsom, GB::AutomorphismGroup{TorQuadModule}, - L::ZLat; check::Bool = false) - - if check - pA, nA = signature_tuple(A) - pB, nB = signature_tuple(B) - pL, nL = signature_tuple(L) - @req pA+pB == pL "Incompatible signatures" - @req nA+nB == nL "Incompatible signatures" - @req ambient_space(A) === ambient_space(B) === ambient_space(L) "Lattices must all live in the same ambient space" - end - - results = LatWithIsom[] - - qA, fqA = discriminant_group(A) - qB, fqB = discriminant_group(B) - - if check - @req all(g -> compose(g, compose(fqA, inv(g))) == fqA, GA) "GA does not centralize fqA" - @req all(g -> compose(g, compose(fqB, inv(g))) == fqB, GB) "GB does not centralize fqB" - end - D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) - OqA = domain(OqAinOD) - OqB = domain(OqBinOD) - - gamma, _, _ = glue_map(L, lattice(A), lattice(B)) - subsA = classes_conjugate_subgroups(GA, domain(gamma)) - @assert !isempty(subsN) - subsB = classes_conjugate_subgroups(GB, codomain(gamma)) - @assert !isempty(subsM) - - for (H1, H2) in Hecke.cartesian_product_iterator([subsA, subsB], inplace=true) - ok, phi = is_anti_isometric_with_anti_isometry(H1, H2) - @assert ok - SAinqA, stabA = H1 - OSAinOqA = embedding_orthogonal_group(SAinqA) - OSA = domain(OSAinOqA) - OSAinOD = compose(OSAinOqA, OqAinOD) - - SBinqB, stabB = H2 - SB = domain(SBinqB) - OSBinOqB = embedding_orthogonal_group(SBinqB) - OSB = domain(OSBinOqB) - OSBinOD = compose(OSBinOqB, OqBinOD) - - # we compute the image of the stabalizers in the respective OS* and we keep track - # of the elements of the stabilizers acting trivially in the respective S* - actA = hom(stabA, OSA, [OSA(Oscar.restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) - imA, _ = image(actA) - kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(Hecke.kernel(actA)[1])] - fSA = OSA(restrict_automorphism(fqA, SAinqA)) - - actB = hom(stabB, OSB, [OSB(Oscar.restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) - imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] - fSB = OSB(restrict_automorphism(fqB, SBinqB)) - - - fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) - bool, g0 = representative_action(OSB, fSAinOSB, fSB) - bool || continue - phi = compose(phi, hom(OSB(g0))) - fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) - # Now the new phi is "sending" the restriction of fA to this of fB. - # So we can glue SA and SB. - @assert fSAinOSB == fSB - - # 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(OSB, fSB) - center, _ = sub(OSB, [OSB(c) for c in gens(center)]) - stabSAphi = AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] - stabSAphi, _ = sub(center, stabSAphi) - stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) - reps = double_cosets(center, stabSB, stabSAphi) - - for g in reps - g = representative(g) - phig = compose(phi, hom(g)) - - _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] - z = zero_matrix(QQ, 0, degree(A)) - glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) - glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) - fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) - _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) - fC2 = _B*fC2*inv(_B) - L2 = lattice_with_isometry(C2, fC2, ambient_representation = false) - @assert genus(L2) == genus(L) - push!(results, L2) - end - end - return results -end +#function equivariant_primitive_extensions(A::LatWithIsom, GA::AutomorphismGroup{TorQuadModule}, +# B::LatWithIsom, GB::AutomorphismGroup{TorQuadModule}, +# L::ZLat; check::Bool = false) +# +# if check +# pA, nA = signature_tuple(A) +# pB, nB = signature_tuple(B) +# pL, nL = signature_tuple(L) +# @req pA+pB == pL "Incompatible signatures" +# @req nA+nB == nL "Incompatible signatures" +# @req ambient_space(A) === ambient_space(B) === ambient_space(L) "Lattices must all live in the same ambient space" +# end +# +# results = LatWithIsom[] +# +# qA, fqA = discriminant_group(A) +# qB, fqB = discriminant_group(B) +# +# if check +# @req all(g -> compose(g, compose(fqA, inv(g))) == fqA, GA) "GA does not centralize fqA" +# @req all(g -> compose(g, compose(fqB, inv(g))) == fqB, GB) "GB does not centralize fqB" +# end +# D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) +# OqA = domain(OqAinOD) +# OqB = domain(OqBinOD) +# +# gamma, _, _ = glue_map(L, lattice(A), lattice(B)) +# subsA = classes_conjugate_subgroups(GA, domain(gamma)) +# @assert !isempty(subsN) +# subsB = classes_conjugate_subgroups(GB, codomain(gamma)) +# @assert !isempty(subsM) +# +# for (H1, H2) in Hecke.cartesian_product_iterator([subsA, subsB], inplace=true) +# ok, phi = is_anti_isometric_with_anti_isometry(H1, H2) +# @assert ok +# SAinqA, stabA = H1 +# OSAinOqA = embedding_orthogonal_group(SAinqA) +# OSA = domain(OSAinOqA) +# OSAinOD = compose(OSAinOqA, OqAinOD) +# +# SBinqB, stabB = H2 +# SB = domain(SBinqB) +# OSBinOqB = embedding_orthogonal_group(SBinqB) +# OSB = domain(OSBinOqB) +# OSBinOD = compose(OSBinOqB, OqBinOD) +# +# # we compute the image of the stabalizers in the respective OS* and we keep track +# # of the elements of the stabilizers acting trivially in the respective S* +# actA = hom(stabA, OSA, [OSA(Oscar.restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) +# imA, _ = image(actA) +# kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(Hecke.kernel(actA)[1])] +# fSA = OSA(restrict_automorphism(fqA, SAinqA)) +# +# actB = hom(stabB, OSB, [OSB(Oscar.restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) +# imB, _ = image(actB) +# kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] +# fSB = OSB(restrict_automorphism(fqB, SBinqB)) +# +# +# fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) +# bool, g0 = representative_action(OSB, fSAinOSB, fSB) +# bool || continue +# phi = compose(phi, hom(OSB(g0))) +# fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) +# # Now the new phi is "sending" the restriction of fA to this of fB. +# # So we can glue SA and SB. +# @assert fSAinOSB == fSB +# +# # 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(OSB, fSB) +# center, _ = sub(OSB, [OSB(c) for c in gens(center)]) +# stabSAphi = AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] +# stabSAphi, _ = sub(center, stabSAphi) +# stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) +# reps = double_cosets(center, stabSB, stabSAphi) +# +# for g in reps +# g = representative(g) +# phig = compose(phi, hom(g)) +# +# _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] +# z = zero_matrix(QQ, 0, degree(A)) +# glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) +# glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) +# glue = FakeFmpqMat(glue) +# _B = hnf(glue) +# _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) +# C2 = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) +# fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) +# _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) +# fC2 = _B*fC2*inv(_B) +# L2 = lattice_with_isometry(C2, fC2, ambient_representation = false) +# @assert genus(L2) == genus(L) +# push!(results, L2) +# end +# end +# return results +#end @doc Markdown.doc""" admissible_equivariant_primitive_extensions(Afa::LatWithIsom, @@ -847,9 +746,9 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, qA, fqA = discriminant_group(A) qB, fqB = discriminant_group(B) GA = image_centralizer_in_Oq(A) - @assert fqA in GA + @hassert :LatWithIsom 1 fqA in GA GB = image_centralizer_in_Oq(B) - @assert fqB in GB + @hassert :LatWithIsom 1 fqB in GB # this is where we will perform the glueing if ambient_space(A) === ambient_space(B) @@ -873,20 +772,20 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) - @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + @hassert :LatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) else _B = block_diagonal_matrix([basis_matrix(A), basis_matrix(B)]) C2 = lattice_in_same_ambient_space(cover(D), _B) fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) - @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + @hassert :LatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end qC2 = discriminant_group(C2) phi2 = hom(qC2, D, [D(lift(x)) for x in gens(qC2)]) - @assert is_bijective(phi2) + @hassert :LatWithIsom 1 _is_isometry(phi2) if is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation=false), type(C)) GC = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GC2)]) C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - @assert discriminant_group(C2fc2)[2] in GC + @hassert :LatWithIsom 1 discriminant_group(C2fc2)[2] in GC set_attribute!(C2fc2, :image_centralizer_in_Oq, GC) push!(results, C2fc2) end @@ -903,15 +802,15 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # scale of the dual: any glue kernel must contain the multiples of l of the respective # discriminant groups - l = level(genus(C)) + l = valuation(level(genus(C)), p) # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) - subsA = subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fVA, ZZ(l)) - subsB = subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) + subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fVA, ZZ(l)) + subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry R = Tuple{eltype(subsA), eltype(subsB), TorQuadModuleMor}[] @@ -937,27 +836,27 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # of the elements of the stabilizers acting trivially in the respective S* actA = hom(stabA, OSA, [OSA(restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) imA, _ = image(actA) - kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(Hecke.kernel(actA)[1])] + 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, [OSB(restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] + kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(ctB)[1])] push!(kerB, OqBinOD(one(OqB))) fSB = OSB(restrict_automorphism(fqB, SBinqB)) # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi # under the action of O(SB, rho_{l+1}(qB), fB) - rB, rBinqB = _rho_functor(qB, p, valuation(l, p)+1) - @assert is_invariant(stabB, rBinqB) + rB, rBinqB = _rho_functor(qB, p, l+1) + @hassert :LatWithIsom 1 is_invariant(stabB, rBinqB) rBinSB = hom(rB, SB, TorQuadModuleElem[SBinqB\(rBinqB(k)) for k in gens(rB)]) - @assert is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB + @hassert :LatWithIsom 1 is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB # We compute the generators of O(SB, rho_{l+1}(qB)) OSBrB, _ = stabilizer(OSB, rBinSB) - @assert fSB in OSBrB + @hassert :LatWithIsom fSB in OSBrB # 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 glueing. @@ -969,7 +868,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fSAinOSBrB = OSB(compose(inv(phi), compose(hom(fSA), phi))) # Now the new phi is "sending" the restriction of fA to this of fB. # So we can glue SA and SB. - @assert fSAinOSBrB == fSB + @hassert :LatWithIsom 1 fSAinOSBrB == fSB # 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 @@ -1012,7 +911,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) fC2 = __B*fC2*inv(__B) - @assert fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + @hassert :LatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end ext, _ = sub(D, D.(_glue)) @@ -1023,11 +922,12 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) - @assert is_bijective(phi2) + @hassert :LatWithIsom 1 _is_isometry(phi2) if !is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) continue end + C2 = lattice_with_isometry(C2, fC2, ambient_representation=false) geneOSA = AutomorphismGroupElem{TorQuadModule}[OSA(compose(phig, compose(hom(g1), inv(phig)))) for g1 in unique(gens(imB))] im2_phi, _ = sub(OSA, geneOSA) @@ -1040,7 +940,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, 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 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) - @assert discriminant_group(C2)[2] in stab + @hassert :LatWithIsom 1 discriminant_group(C2)[2] in stab set_attribute!(C2, :image_centralizer_in_Oq, stab) push!(results, C2) end @@ -1049,3 +949,18 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, return results end +function _is_isometry(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 + if Hecke.quadratic_product(a) != Hecke.quadratic_product(f(a)) + return false + end + end + return true +end + diff --git a/experimental/LatticesWithIsometry/src/Enumeration.jl b/experimental/LatticesWithIsometry/src/enumeration.jl similarity index 93% rename from experimental/LatticesWithIsometry/src/Enumeration.jl rename to experimental/LatticesWithIsometry/src/enumeration.jl index a71cffd12304..789b8dd9b7c6 100644 --- a/experimental/LatticesWithIsometry/src/Enumeration.jl +++ b/experimental/LatticesWithIsometry/src/enumeration.jl @@ -15,7 +15,8 @@ # The tuples in output are pairs of positive integers! function _tuples_divisors(d::T) where T <: Hecke.IntegerUnion div = divisors(d) - return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] + if d >= 0 + return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] end # This is line 8 of Algorithm 1, they correspond to the possible @@ -23,8 +24,8 @@ end # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. function _find_D(d::T, m::Int, p::Int) where T <: Hecke.IntegerUnion - @assert is_prime(p) - @assert d != 0 + @hassert :LatWithIsom 1 is_prime(p) + @hassert :LatWithIsom 1 d != 0 # If m == 0, there are no conditions on the gcd of d1 and dp if m == 0 @@ -282,7 +283,7 @@ end function _ideals_of_norm(E, d::ZZRingElem) isone(d) && return [fractional_ideal(maximal_order(E), one(E))] - @assert E isa Hecke.NfRel + @hassert :LatWithIsom 1 E isa Hecke.NfRel K = base_field(E) OK = maximal_order(K) OE = maximal_order(E) @@ -316,11 +317,11 @@ end # E/K of rank rk, whose trace lattice has signature (s1, s2). function _possible_signatures(s1, s2, E, rk) - @assert E isa Hecke.NfRel + @hassert :LatWithIsom 1 E isa Hecke.NfRel ok, q = Hecke.is_cyclotomic_type(E) - @assert ok - @assert iseven(s2) - @assert Hecke.divides(2*(s1+s2), euler_phi(q))[1] + @hassert :LatWithIsom 1 ok + @hassert :LatWithIsom 1 iseven(s2) + @hassert :LatWithIsom 1 Hecke.divides(2*(s1+s2), euler_phi(q))[1] l = divexact(s2, 2) K = base_field(E) inf = real_places(K) @@ -375,11 +376,11 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) reps = LatWithIsom[] if n*m < 3 - @info "Order smaller than 3" + @vprint :LatWithIsom 1 "Order smaller than 3" f = (-1)^(n*m+1)*identity_matrix(QQ, rk) G = genus(Lf) repre = representatives(G) - @info "$(length(repre)) representative(s)" + @vprint :LatWithIsom 1 "$(length(repre)) representative(s)" for LL in repre is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) end @@ -388,7 +389,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) !iseven(s2) && return reps - @info "Order bigger than 3" + @vprint :LatWithIsom 1 "Order bigger than 3" ok, rk = Hecke.divides(rk, euler_phi(n*m)) @@ -399,25 +400,25 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) - @info "We have the different" + @vprint :LatWithIsom 1 "We have the different" ndE = d*inv(QQ(absolute_norm(DE)))^rk println(ndE) detE = _ideals_of_norm(E, ndE) - @info "All possible ideal dets: $(length(detE))" + @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))" signatures = _possible_signatures(s1, s2, E, rk) - @info "All possible signatures: $(length(signatures))" + @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(dd)*DE)) end gene = unique(gene) - @info "All possible genera: $(length(gene))" + @vprint :LatWithIsom 1 "All possible genera: $(length(gene))" for g in gene - @info "g = $g" + @vprint :LatWithIsom 1 "g = $g" H = representative(g) if !is_integral(DE*scale(H)) continue @@ -425,12 +426,12 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) if is_even(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) continue end - @info "$H" + @vprint :LatWithIsom 1 "$H" M, fM = Hecke.trace_lattice_with_isometry(H) det(M) == d || continue M = lattice_with_isometry(M, fM) - @assert is_of_hermitian_type(M) - @assert order_of_isometry(M) == n*m + @hassert :LatWithIsom 1 is_of_hermitian_type(M) + @hassert :LatWithIsom 1 order_of_isometry(M) == n*m if is_even(M) != is_even(Lf) continue end @@ -495,11 +496,11 @@ function _representative(t::Dict; check::Bool = true) println(ndE) detE = _ideals_of_norm(E, ndE) - @info "All possible ideal dets: $(length(detE))" + @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))" signatures = _possible_signatures(s1, s2, E) - @info "All possible signatures: $(length(signatures))" + @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) @@ -517,8 +518,8 @@ function _representative(t::Dict; check::Bool = true) H = H M = trace_lattice(H) det(M) == d || continue - @assert is_of_hermitian_type(M) - @assert order_of_isometry(M) == n + @hassert :LatWithIsom 1 is_of_hermitian_type(M) + @hassert :LatWithIsom 1 order_of_isometry(M) == n if iseven(M) != iseven(G) continue end @@ -554,9 +555,9 @@ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = - @req p != q "Prime numbers must be distinct" reps = LatWithIsom[] - @info "Compute admissible triples" + @vprint :LatWithIsom 1 "Compute admissible triples" atp = admissible_triples(Lf, p, pA = pA, pB = pB) - @info "$(length(atp)) admissible triple(s)" + @vprint :LatWithIsom 1 "$(length(atp)) admissible triple(s)" for (A, B) in atp LB = lattice_with_isometry(representative(B)) RB = representatives_of_hermitian_type(LB, p*q^d) @@ -641,7 +642,7 @@ function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) E = try admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p) catch e return L2, L1, Lf, q, p end - @assert b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) + @hassert :LatWithIsom 1 b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) end return reps @@ -695,7 +696,7 @@ function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) A0 = kernel_lattice(Lf, p^d*q^e) bool, r = Hecke.divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) - @assert bool + @hassert :LatWithIsom 1 bool B0 = kernel_lattice(Lf, r) A = splitting_of_prime_power(A0, p) @@ -755,7 +756,7 @@ function splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) B = splitting_of_mixed_prime_power(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) E = admissible_equivariant_primitive_extensions(LB, LA, Lf, p) - @assert all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) + @hassert :LatWithIsom 1 all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) append!(reps, E) end return reps diff --git a/experimental/LatticesWithIsometry/src/Exports.jl b/experimental/LatticesWithIsometry/src/exports.jl similarity index 86% rename from experimental/LatticesWithIsometry/src/Exports.jl rename to experimental/LatticesWithIsometry/src/exports.jl index 0cf97fd10dab..44f18b06de87 100644 --- a/experimental/LatticesWithIsometry/src/Exports.jl +++ b/experimental/LatticesWithIsometry/src/exports.jl @@ -3,7 +3,6 @@ export LatWithIsom export admissible_equivariant_primitive_extensions export admissible_triples export ambient_isometry -export equivariant_primitive_extensions export image_centralizer_in_Oq export isometry export is_admissible_triple @@ -14,7 +13,6 @@ export is_hermitian export lattice_with_isometry export order_of_isometry export primitive_embeddings_in_primary_lattice -export primitive_embeddings_of_elementary_lattice export representatives_of_hermitian_type export splitting_of_hermitian_prime_power export splitting_of_mixed_prime_power diff --git a/experimental/LatticesWithIsometry/src/HermitianMirandaMorrison.jl b/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl similarity index 91% rename from experimental/LatticesWithIsometry/src/HermitianMirandaMorrison.jl rename to experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl index 684e3aa3bdff..f2a151df2ce2 100644 --- a/experimental/LatticesWithIsometry/src/HermitianMirandaMorrison.jl +++ b/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl @@ -22,13 +22,13 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) URPabs, mURPabs = unit_group(RPabs) function dlog(x::Hecke.NfRelElem) - @assert parent(x) == E + @hassert :LatWithIsom 1 parent(x) == E d = denominator(x, OE) xabs = d*(EabstoE\(x)) dabs = copy(d) F = prime_decomposition(OEabs, minimum(Pabs)) for PP in F - @assert valuation(EabstoE\(x), PP[1]) >= 0 + @hassert :LatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp @@ -44,9 +44,9 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) end function exp(k::GrpAbFinGenElem) - @assert parent(k) === URPabs + @hassert :LatWithIsom 1 parent(k) === URPabs x = EabstoE(Eabs(mRPabs\mURPabs(k))) - @assert dlog(x) == k + @hassert :LatWithIsom 1 dlog(x) == k return x end @@ -77,18 +77,18 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) S, mS = snf(K) function exp(k::GrpAbFinGenElem) - @assert parent(k) === S + @hassert :LatWithIsom 1 parent(k) === S return EabstoE(elem_in_nf(mRPabs\(mURPabs(mK(mS(k)))))) end function dlog(x::Hecke.NfRelElem) - @assert parent(x) === E + @hassert :LatWithIsom 1 parent(x) === E d = denominator(x, OE) xabs = EabstoE\(d*x) dabs = copy(d) F = prime_decomposition(OEabs, minimum(Pabs)) for PP in F - @assert valuation(EabstoE\(x), PP[1]) >= 0 + @hassert :LatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp @@ -148,18 +148,18 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) S, mS = snf(K) function exp(k::GrpAbFinGenElem) - @assert parent(k) === S + @hassert :LatWithIsom 1 parent(k) === S return EabstoE(elem_in_nf(mRPabs\(mURPabs(mK(mS(k)))))) end function dlog(x::Hecke.NfRelElem) - @assert parent(x) === E + @hassert :LatWithIsom 1 parent(x) === E d = denominator(x, OE) xabs = EabstoE\(d*x) dabs = copy(d) F = prime_decomposition(OEabs, minimum(EabstoE\P)) for PP in F - @assert valuation(EabstoE\(x), PP[1]) >= 0 + @hassert :LatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp @@ -180,9 +180,9 @@ end # in O is to distribute to the appropriate function above. function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) - @assert is_prime(p) - @assert is_maximal(order(p)) - @assert order(p) === base_ring(O) + @hassert :LatWithIsom 1 is_prime(p) + @hassert :LatWithIsom 1 is_maximal(order(p)) + @hassert :LatWithIsom 1 order(p) === base_ring(O) E = nf(O) F = prime_decomposition(O, p) P = F[1][1] @@ -239,20 +239,20 @@ function _get_product_quotient(E::Hecke.NfRel, Fac) if length(x) == 1 return sum([inj[i](dlogs[i](x[1])) for i in 1:length(Fac)]) else - @assert length(x) == length(Fac) + @hassert :LatWithIsom 1 length(x) == 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)] - @assert dlog(v) == x + @hassert :LatWithIsom 1 dlog(v) == x return v end for i in 1:10 a = rand(G) - @assert dlog(exp(a)) == a + @hassert :LatWithIsom 1 dlog(exp(a)) == a end return G, dlog, exp @@ -310,7 +310,7 @@ end # determinants approximations is new in this code. function _local_determinants_morphism(Lf::LatWithIsom) - @assert is_of_hermitian_type(Lf) + @hassert :LatWithIsom 1 is_of_hermitian_type(Lf) qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) @@ -333,7 +333,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) DEQ = DEK*DKQ H2 = inv(DEQ)*dual(H) - @assert is_sublattice(H2, H) # This should be true since the lattice in Lf is integral + @hassert :LatWithIsom 1 is_sublattice(H2, H) # This should be true since the lattice in Lf is integral # This is the map used for the trace construction: it is stored on Lf when we # have constructed H. We need this map because it sets the rule of the @@ -419,7 +419,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) SQ, SQtoQ = snf(Q) function dlog(x::Vector) - @assert length(x) == length(Fsharpdata) + @hassert :LatWithIsom 1 length(x) == length(Fsharpdata) return SQtoQ\(mQ(j\(Fsharplog(x)))) end @@ -510,7 +510,7 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG Pabs = EabstoE\P OEabs = order(Pabs) q = domain(g) - @assert ambient_space(cover(q)) === domain(res) + @hassert :LatWithIsom 1 ambient_space(cover(q)) === domain(res) # B2 will be a local basis at p of the image of D^{-1}H^# under the induced # action of g via res. @@ -541,7 +541,7 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG # Here d is not necessarily well defined in O_E, but as it is implemented, # each of the denominator(a, O_E) return the smallest positive integer d such - # that d*a lies in O_E, while a might be a non-integral element in E. + # that d*a lies in O_E, while `a` might be a non-integral element in E. d = lcm(lcm([denominator(a, OEabs) for a in Bpabs]), lcm([denominator(a, OEabs) for a in B2abs])) dBpabs = change_base_ring(OEabs, d*Bpabs) dB2abs = change_base_ring(OEabs, d*B2abs) @@ -561,20 +561,20 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG # If what we have done is correct then K*newBp == newB2 modulo O_p, so all # entries in the difference K*newBp-newB2 must have non-negative P-valuation. # Since it is the case, then K satisfies K*Bp == B2 mod H locally at p. - @assert _scale_valuation(K*newBp-newB2, P) >= 0 + @hassert :LatWithIsom 1 _scale_valuation(K*newBp-newB2, P) >= 0 return K end # the minimum P-valuation among all the non-zero entries of M function _scale_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} - @assert nf(order(P)) === base_ring(M) + @hassert :LatWithIsom 1 nf(order(P)) === base_ring(M) iszero(M) && return inf return minimum([valuation(v, P) for v in collect(M) if !iszero(v)]) end # the minimum P-valuation among all the non-zero diagonal entries of M function _norm_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} - @assert nf(order(P)) === base_ring(M) + @hassert :LatWithIsom 1 nf(order(P)) === base_ring(M) iszero(diagonal(M)) && return inf r = minimum([valuation(v, P) for v in diagonal(M) if !iszero(v)]) return r @@ -590,20 +590,20 @@ end # looking at better representatives until we reach a good enough precision for # our purpose. function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::Hecke.NfRelOrdIdl, e::Int, a::Int; check = true) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} - @assert trace(rho) == 1 + @hassert :LatWithIsom 1 trace(rho) == 1 E = base_ring(G) # G here is a local gram matrix - @assert G == map_entries(involution(E), transpose(G)) - @assert base_ring(F) === E + @hassert :LatWithIsom 1 G == map_entries(involution(E), transpose(G)) + @hassert :LatWithIsom 1 base_ring(F) === E # R represents the defect, how far F is to be an isometry of G R = G - F*G*map_entries(involution(E), transpose(F)) # These are the necessary conditions for the input of algorithm 8 in BH22 if check - @assert _scale_valuation(inv(G), P) >= 1+a - @assert _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a - @assert _scale_valuation(R, P) >= l-a - @assert _norm_valuation(R, P) + valuation(rho,P) >= l-a + @hassert :LatWithIsom 1 _scale_valuation(inv(G), P) >= 1+a + @hassert :LatWithIsom 1 _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a + @hassert :LatWithIsom 1 _scale_valuation(R, P) >= l-a + @hassert :LatWithIsom 1 _norm_valuation(R, P) + valuation(rho,P) >= l-a end # R is s-symmetric, where s is the canonical involution of E/K. We split R @@ -625,10 +625,10 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H l2 = 2*l+1 if check - @assert _scale_valuation(F-newF, P) >= l+1 + @hassert :LatWithIsom 1 _scale_valuation(F-newF, P) >= l+1 R2 = G-newF*G*map_entries(involution(E), transpose(newF)) - @assert _scale_valuation(R2, P) >= l2-a - @assert _norm_valuation(R2, P) + valuation(rho, P) >= l2-a + @hassert :LatWithIsom 1 _scale_valuation(R2, P) >= l2-a + @hassert :LatWithIsom 1 _norm_valuation(R2, P) + valuation(rho, P) >= l2-a end return newF, l2 @@ -650,7 +650,7 @@ end # function _approximate_isometry(H::Hecke.HermLat, H2::Hecke.HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) E = base_field(H) - @assert nf(order(P)) === E + @hassert :LatWithIsom 1 nf(order(P)) === E ok, b = is_modular(H, minimum(P)) if ok && b == -a return identity_matrix(E, 1) @@ -744,8 +744,8 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) rt = roots(t^2 - (g+1)*d^2, max_roots = 1, ispure = true, is_normal=true) if !is_empty(rt) rho = (1+rt[1])//2 - @assert valuation(rho, Pabs) == 1-e - @assert trace(EabstoE(rho)) == 1 + @hassert :LatWithIsom 1 valuation(rho, Pabs) == 1-e + @hassert :LatWithIsom 1 trace(EabstoE(rho)) == 1 return EabstoE(rho) end end diff --git a/experimental/LatticesWithIsometry/src/LatWithIsom.jl b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl similarity index 97% rename from experimental/LatticesWithIsometry/src/LatWithIsom.jl rename to experimental/LatticesWithIsometry/src/lattices_with_isometry.jl index 5f6a383d81b9..566e55524eea 100644 --- a/experimental/LatticesWithIsometry/src/LatWithIsom.jl +++ b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl @@ -120,7 +120,7 @@ det(Lf::LatWithIsom) = det(lattice(Lf))::QQFieldElem Given a lattice with isometry $(L, f)$, return the scale of the underlying lattice `L` (see [`scale(::ZLat)`](@ref)). """ -scale(Lf::LatWithIsom) = det(lattice(Lf))::QQFieldElem +scale(Lf::LatWithIsom) = scale(lattice(Lf))::QQFieldElem @doc Markdown.doc""" norm(Lf::LatWithIsom) -> QQFieldElem @@ -154,7 +154,7 @@ Given a lattice with isometry $(L, f)$, return whether the underlying lattice Note that to be even, `L` must be integral (see [`is_integral(::ZLat)`](@ref)). """ -is_even(Lf::LatWithIsom) = Hecke.iseven(lattice(Lf))::Bool +is_even(Lf::LatWithIsom) = iseven(lattice(Lf))::Bool @doc Markdown.doc""" discriminant(Lf::LatWithIsom) -> QQFieldElem @@ -220,7 +220,7 @@ function lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, if check @req f*gram_matrix(L)*transpose(f) == gram_matrix(L) "f does not define an isometry of L" @req f_ambient*gram_matrix(ambient_space(L))*transpose(f_ambient) == gram_matrix(ambient_space(L)) "f_ambient is not an isometry of the ambient space of L" - @assert basis_matrix(L)*f_ambient == f*basis_matrix(L) + @hassert :LatWithIsom 1 basis_matrix(L)*f_ambient == f*basis_matrix(L) end return LatWithIsom(L, f, f_ambient, n)::LatWithIsom @@ -381,7 +381,7 @@ $q_L$ induced by `f`. GL = Oscar._orthogonal_group(qL, UL, check = false) else @req is_of_hermitian_type(Lf) "Not yet implemented for indefinite lattices with isometry which are not of hermitian type" - dets = LWI._local_determinants_morphism(Lf) + dets = Oscar._local_determinants_morphism(Lf) GL, _ = kernel(dets) end return GL::AutomorphismGroup{TorQuadModule} @@ -405,8 +405,8 @@ function _real_kernel_signatures(L::ZLat, M) newGC = Hecke._gram_schmidt(newGC, C)[1] diagC = diagonal(newGC) - @assert all(z -> isreal(z), diagC) - @assert all(z -> !iszero(z), diagC) + @hassert :LatWithIsom 1 all(z -> isreal(z), diagC) + @hassert :LatWithIsom 1 all(z -> !iszero(z), diagC) k1 = count(z -> z > 0, diagC) k2 = length(diagC) - k1 @@ -474,11 +474,11 @@ function kernel_lattice(Lf::LatWithIsom, p::QQPolyRingElem) k, K = left_kernel(change_base_ring(ZZ, d*M)) L2 = lattice_in_same_ambient_space(L, K*basis_matrix(L)) f2 = solve_left(change_base_ring(QQ, K), K*f) - @assert f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) + @hassert :LatWithIsom 1 f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) chi = parent(p)(collect(coefficients(minpoly(f2)))) chif = parent(p)(collect(coefficients(minpoly(Lf)))) _chi = gcd(p, chif) - @assert (rank(L2) == 0) || (chi == _chi) + @hassert :LatWithIsom 1 (rank(L2) == 0) || (chi == _chi) return lattice_with_isometry(L2, f2, ambient_representation = false) end diff --git a/experimental/LatticesWithIsometry/src/Printings.jl b/experimental/LatticesWithIsometry/src/printings.jl similarity index 84% rename from experimental/LatticesWithIsometry/src/Printings.jl rename to experimental/LatticesWithIsometry/src/printings.jl index 0bb83d08689f..a55d75955a22 100644 --- a/experimental/LatticesWithIsometry/src/Printings.jl +++ b/experimental/LatticesWithIsometry/src/printings.jl @@ -2,9 +2,9 @@ function Base.show(io::IO, ::MIME"text/plain", Lf::LatWithIsom) println(io, lattice(Lf)) n = order_of_isometry(Lf) if is_finite(n) - println(io, "with isometry of finite order $n") + println(io, "with Isometry of finite order $n") else - println(io, "with isometry of infinite order") + println(io, "with Isometry of infinite order") end println(io, "given by") print(IOContext(io, :compact => true), isometry(Lf)) diff --git a/experimental/LatticesWithIsometry/src/Types.jl b/experimental/LatticesWithIsometry/src/types.jl similarity index 100% rename from experimental/LatticesWithIsometry/src/Types.jl rename to experimental/LatticesWithIsometry/src/types.jl From c6ea2702edb2458b268ea0d135b80eba7eebf1d3 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 3 May 2023 15:29:59 +0200 Subject: [PATCH 47/76] bug fix and new functionalities --- Project.toml | 2 +- experimental/Experimental.jl | 1 - .../src/LatticesWithIsometry.jl | 5 - .../LatticesWithIsometry/src/embeddings.jl | 45 +++-- .../LatticesWithIsometry/src/enumeration.jl | 47 +++-- .../src/lattices_with_isometry.jl | 189 ++++++++++++++---- .../LatticesWithIsometry/src/types.jl | 2 +- .../LatticesWithIsometry/test/runtests.jl | 0 src/Oscar.jl | 3 + 9 files changed, 201 insertions(+), 93 deletions(-) create mode 100644 experimental/LatticesWithIsometry/test/runtests.jl diff --git a/Project.toml b/Project.toml index 15f2887367d7..bbfe033e0c2f 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ AbstractAlgebra = "0.29.3" AlgebraicSolving = "0.3.0" DocStringExtensions = "0.8, 0.9" GAP = "0.9.4" -Hecke = "0.18.8" +Hecke = "0.18.10" JSON = "^0.20, ^0.21" Nemo = "0.33.7" Polymake = "0.9.0" diff --git a/experimental/Experimental.jl b/experimental/Experimental.jl index 431dbdcc07b7..75e96442a0a5 100644 --- a/experimental/Experimental.jl +++ b/experimental/Experimental.jl @@ -11,7 +11,6 @@ include("GITFans.jl") include("GModule.jl") include("MPolyRingSparse.jl") include("SymmetricIntersections.jl") -include("LinearQuotients.jl") include("Schemes/Types.jl") include("Schemes/SpecialTypes.jl") diff --git a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl index c358246d605c..22ec351d39b3 100644 --- a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl +++ b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl @@ -1,13 +1,8 @@ -add_assertion_scope(:LatWithIsom) -add_verbosity_scope(:LatWithIsom) - function set_lwi_level(k::Int) set_assertion_level(:LatWithIsom, k) set_verbosity_level(:LatWithIsom, k) end -set_lwi_level(1) - include("exports.jl") include("types.jl") include("lattices_with_isometry.jl") diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index 65c59478cbf2..6e7aa8571155 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -83,11 +83,11 @@ function _get_V(fq, mu, p) q = domain(fq) V, Vinq = primary_part(q, p) pV, pVinq = sub(q, Vinq.([divexact(order(g), p)*g for g in gens(V) if !(order(g)==1)])) - fpV = restrict_automorphism(fq, pVinq) + fpV = restrict_endomorphism(fq, pVinq) fpV_mu = evaluate(mu, fpV) K, KtopV = kernel(fpV_mu) Ktoq = compose(KtopV, pVinq) - fK = restrict_automorphism(fq, Ktoq) + fK = restrict_endomorphism(fq, Ktoq) return K, Ktoq, fK end @@ -154,9 +154,9 @@ function _cokernel_as_Fp_vector_space(HinV, p, f) VtoVS = inv(VStoV) - n = ngens(Vs) + n = ngens(VS) F = GF(p) - MVS = matrix(compose(VstoV, compose(f, VtoVS))) + MVS = matrix(compose(VStoV, compose(f, VtoVS))) Vp = VectorSpace(F, n) function _VstoVp(x::TorQuadModuleElem) @@ -169,12 +169,12 @@ function _cokernel_as_Fp_vector_space(HinV, p, f) return VS(vec(collect(x))) end - VStoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, Vs, Vp) - VtoVp = compose(VtoVs, VStoVp) + VStoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, VS, Vp) + VtoVp = compose(VtoVS, VStoVp) subgene = elem_type(Vp)[VtoVp(HStoV(a)) for a in gens(HS)] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) - fVp = change_base_ring(F, MVs) + fVp = change_base_ring(F, MVS) ok, fQp = can_solve_with_solution(VptoQp.matrix, fVp*VptoQp.matrix) @assert ok @@ -209,7 +209,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu p = elementary_divisors(V)[1] pq, pqtoq = primary_part(q, p) g = valuation(ord, p) - @hassert :LatWithIsom 1 all(a -> haspreimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) + @hassert :LatWithIsom 1 all(a -> has_preimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) H0, H0inV = sub(V, [preimage(Vinq, (p^l)*pqtoq(a)) for a in gens(pq)]) Qp, VtoVp, VptoQp, fQp = _cokernel_as_Fp_vector_space(H0inV, p, f) Vp = codomain(VtoVp) @@ -345,7 +345,7 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) - @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" + @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" for g in reps g = representative(g) @@ -363,7 +363,7 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) @hassert :LatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) - @vprint :LatWithIsom 1 "Gluing done" + @vprint :LatWithIsom 1 "Gluing done\n" GC.gc() end end @@ -412,7 +412,7 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) - @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" + @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" for g in reps g = representative(g) phig = compose(phi, hom(g)) @@ -429,7 +429,7 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) @hassert :LatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) - @vprint :LatWithIsom 1 "Gluing done" + @vprint :LatWithIsom 1 "Gluing done\n" GC.gc() end end @@ -437,7 +437,7 @@ function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M:: end -@doc Markdown.doc""" +@doc raw""" primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat) -> Vector{Tuple{ZLat, ZLat, ZLat}} @@ -477,20 +477,20 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph VM, VMinqM = primary_part(qM, p) end for k in divisors(gcd(order(VM), order(qL))) - @vprint :LatWithIsom 1 "Glue order: $(k)" + @vprint :LatWithIsom 1 "Glue order: $(k)\n" if el subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(GL), GL, k) else subsL = subgroups_orbit_representatives_and_stabilizers(GL, order = k) end - @vprint :LatWithIsom 1 "$(length(subsL)) subgroup(s)" + @vprint :LatWithIsom 1 "$(length(subsL)) subgroup(s)\n" for H in subsL HL = domain(H[1]) it = subgroups(abelian_group(VM), order = order(HL)) subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1])))))[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue - @vprint :LatWithIsom 1 "Possible gluings" + @vprint :LatWithIsom 1 "Possible gluings\n" HM = subsM[1] ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) @hassert :LatWithIsom 1 ok @@ -690,7 +690,7 @@ end # return results #end -@doc Markdown.doc""" +@doc raw""" admissible_equivariant_primitive_extensions(Afa::LatWithIsom, Bfb::LatWithIsom, Cfc::LatWithIsom, @@ -726,13 +726,14 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # requirement for the algorithm of BH22 @req is_prime(p) "p must be a prime number" + amb = ambient_space(A) === ambient_space(B) === ambient_space(C) if check @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" chiA = minpoly(A) chiB = minpoly(parent(chiA), isometry(B)) @req gcd(chiA, chiB) == 1 "Minimal irreducible polynomials must be relatively coprime" @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple" - if ambient_space(A) === ambient_space(B) === ambient_space(C) + if amb G = gram_matrix(ambient_space(C)) @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattice in same ambient space must be orthogonal" end @@ -751,7 +752,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, @hassert :LatWithIsom 1 fqB in GB # this is where we will perform the glueing - if ambient_space(A) === ambient_space(B) + if amb D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) else D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _direct_sum_with_embeddings_orthogonal_groups(qA, qB) @@ -767,7 +768,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, geneB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(OqB(b.X)) for b in gens(GB)] gene = vcat(geneA, geneB) GC2, _ = sub(OD, gene) - if ambient_space(A) === ambient_space(B) === ambient_space(C) + if amb C2 = lattice(A)+lattice(B) fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) @@ -842,7 +843,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, actB = hom(stabB, OSB, [OSB(restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(ctB)[1])] + kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] push!(kerB, OqBinOD(one(OqB))) fSB = OSB(restrict_automorphism(fqB, SBinqB)) @@ -887,7 +888,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, g = representative(g) phig = compose(phi, hom(g)) - if ambient_space(A) === ambient_space(B) + if amb _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(A)) glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) diff --git a/experimental/LatticesWithIsometry/src/enumeration.jl b/experimental/LatticesWithIsometry/src/enumeration.jl index 789b8dd9b7c6..1abdee7b3c52 100644 --- a/experimental/LatticesWithIsometry/src/enumeration.jl +++ b/experimental/LatticesWithIsometry/src/enumeration.jl @@ -15,8 +15,7 @@ # The tuples in output are pairs of positive integers! function _tuples_divisors(d::T) where T <: Hecke.IntegerUnion div = divisors(d) - if d >= 0 - return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] + return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] end # This is line 8 of Algorithm 1, they correspond to the possible @@ -71,7 +70,7 @@ function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p return L end -@doc Markdown.doc""" +@doc raw""" is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) -> Bool Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such @@ -209,7 +208,7 @@ function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZLa return is_admissible_triple(L[1], L[2], L[3], p) end -@doc Markdown.doc""" +@doc raw""" admissible_triples(C::ZGenus, p::Integer) -> Vector{Tuple{ZGenus, ZGenus}} Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of @@ -350,7 +349,7 @@ function _possible_signatures(s1, s2, E, rk) return signs end -@doc Markdown.doc""" +@doc raw""" representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) -> Vector{LatWithIsom} @@ -376,11 +375,11 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) reps = LatWithIsom[] if n*m < 3 - @vprint :LatWithIsom 1 "Order smaller than 3" + @vprint :LatWithIsom 1 "Order smaller than 3\n" f = (-1)^(n*m+1)*identity_matrix(QQ, rk) G = genus(Lf) repre = representatives(G) - @vprint :LatWithIsom 1 "$(length(repre)) representative(s)" + @vprint :LatWithIsom 1 "$(length(repre)) representative(s)\n" for LL in repre is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) end @@ -389,7 +388,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) !iseven(s2) && return reps - @vprint :LatWithIsom 1 "Order bigger than 3" + @vprint :LatWithIsom 1 "Order bigger than 3\n" ok, rk = Hecke.divides(rk, euler_phi(n*m)) @@ -400,25 +399,25 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) - @vprint :LatWithIsom 1 "We have the different" + @vprint :LatWithIsom 1 "We have the different\n" ndE = d*inv(QQ(absolute_norm(DE)))^rk println(ndE) detE = _ideals_of_norm(E, ndE) - @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))" + @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))\n" signatures = _possible_signatures(s1, s2, E, rk) - @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))" + @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))\n" for dd in detE, sign in signatures append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(dd)*DE)) end gene = unique(gene) - @vprint :LatWithIsom 1 "All possible genera: $(length(gene))" + @vprint :LatWithIsom 1 "All possible genera: $(length(gene))\n" for g in gene - @vprint :LatWithIsom 1 "g = $g" + @vprint :LatWithIsom 1 "g = $g\n" H = representative(g) if !is_integral(DE*scale(H)) continue @@ -426,7 +425,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) if is_even(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) continue end - @vprint :LatWithIsom 1 "$H" + @vprint :LatWithIsom 1 "$H\n" M, fM = Hecke.trace_lattice_with_isometry(H) det(M) == d || continue M = lattice_with_isometry(M, fM) @@ -447,7 +446,7 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) return reps end -@doc Markdown.doc""" +@doc raw""" representatives_of_hermitian_type(t::Dict, m::Int = 1; check::Bool = true) -> Vector{LatWithIsom} @@ -496,11 +495,11 @@ function _representative(t::Dict; check::Bool = true) println(ndE) detE = _ideals_of_norm(E, ndE) - @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))" + @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))\n" signatures = _possible_signatures(s1, s2, E) - @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))" + @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))\n" for dd in detE, sign in signatures append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) @@ -531,7 +530,7 @@ function _representative(t::Dict; check::Bool = true) return nothing end -@doc Markdown.doc""" +@doc raw""" splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int) -> Vector{LatWithIsom} Given a lattice with isometry $(L, f)$ of hermitian type with `f` of order $q^d$ @@ -555,9 +554,9 @@ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = - @req p != q "Prime numbers must be distinct" reps = LatWithIsom[] - @vprint :LatWithIsom 1 "Compute admissible triples" + @vprint :LatWithIsom 1 "Compute admissible triples\n" atp = admissible_triples(Lf, p, pA = pA, pB = pB) - @vprint :LatWithIsom 1 "$(length(atp)) admissible triple(s)" + @vprint :LatWithIsom 1 "$(length(atp)) admissible triple(s)\n" for (A, B) in atp LB = lattice_with_isometry(representative(B)) RB = representatives_of_hermitian_type(LB, p*q^d) @@ -577,7 +576,7 @@ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = - return reps end -@doc Markdown.doc""" +@doc raw""" splitting_of_hermitian_prime_power(t::Dict, p::Int) -> Vector{LatWithIsom} Given a hermitian type `t` of lattice with isometry $(L, f)$ with `f` of order @@ -596,7 +595,7 @@ function splitting_of_hermitian_prime_power(t::Dict, p::Int) return splitting_of_hermitian_prime_power(Lf, p) end -@doc Markdown.doc""" +@doc raw""" splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) -> Vector{LatWithIsom} Given a lattice with isometry $(L, f)$ with `f` of order $q^e$ for some prime number @@ -648,7 +647,7 @@ function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) return reps end -@doc Markdown.doc""" +@doc raw""" splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) -> Vector{LatWithIsom} @@ -709,7 +708,7 @@ function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) return reps end -@doc Markdown.doc""" +@doc raw""" splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) -> Vector{LatWithIsom} diff --git a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl index 566e55524eea..8538ef419416 100644 --- a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl +++ b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl @@ -5,21 +5,21 @@ # ############################################################################### -@doc Markdown.doc""" +@doc raw""" lattice(Lf::LatWithIsom) -> ZLat Given a lattice with isometry $(L, f)$, return the underlying lattice `L`. """ lattice(Lf::LatWithIsom) = Lf.Lb -@doc Markdown.doc""" +@doc raw""" isometry(Lf::LatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. """ isometry(Lf::LatWithIsom) = Lf.f -@doc Markdown.doc""" +@doc raw""" ambient_isometry(Lf::LatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return an isometry of underlying isometry @@ -27,7 +27,7 @@ of the ambient space of `L` inducing `f` on `L` """ ambient_isometry(Lf::LatWithIsom) = Lf.f_ambient -@doc Markdown.doc""" +@doc raw""" order_of_isometry(Lf::LatWithIsom) -> Integer Given a lattice with isometry $(L, f)$, return the order of the underlying @@ -41,7 +41,7 @@ order_of_isometry(Lf::LatWithIsom) = Lf.n # ############################################################################### -@doc Markdown.doc""" +@doc raw""" rank(Lf::LatWithIsom) -> Integer Given a lattice with isometry $(L, f)$, return the rank of the underlying lattice @@ -49,7 +49,7 @@ Given a lattice with isometry $(L, f)$, return the rank of the underlying lattic """ rank(Lf::LatWithIsom) = rank(lattice(Lf))::Integer -@doc Markdown.doc""" +@doc raw""" charpoly(Lf::LatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the characteristic polynomial of the @@ -57,7 +57,7 @@ underlying isometry `f`. """ charpoly(Lf::LatWithIsom) = charpoly(isometry(Lf))::QQPolyRingElem -@doc Markdown.doc""" +@doc raw""" minpoly(Lf::LatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the minimal polynomial of the @@ -65,7 +65,7 @@ underlying isometry `f`. """ minpoly(Lf::LatWithIsom) = minpoly(isometry(Lf))::QQPolyRingElem -@doc Markdown.doc""" +@doc raw""" genus(Lf::LatWithIsom) -> ZGenus Given a lattice with isometry $(L, f)$, return the genus of the underlying @@ -73,7 +73,7 @@ lattice `L` (see [`genus(::ZLat)`](@ref)). """ genus(Lf::LatWithIsom) = genus(lattice(Lf))::ZGenus -@doc Markdown.doc""" +@doc raw""" ambient_space(Lf::LatWithIsom) -> QuadSpace Given a lattice with isometry $(L, f)$, return the ambient space of the underlying @@ -81,7 +81,7 @@ lattice `L` (see [`ambient_space(::ZLat)`](@ref)). """ ambient_space(Lf::LatWithIsom) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} -@doc Markdown.doc""" +@doc raw""" basis_matrix(Lf::LatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the basis matrix of the underlying @@ -89,7 +89,7 @@ lattice `L` (see [`basis_matrix(::ZLat)`](@ref)). """ basis_matrix(Lf::LatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix -@doc Markdown.doc""" +@doc raw""" gram_matrix(Lf::LatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$ with basis matric `B` (see [`basis_matrix(Lf::LatWithIsom)`](@ref)) @@ -98,7 +98,7 @@ of lattice `L` associted to `B` with respect to $\Phi$. """ gram_matrix(Lf::LatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix -@doc Markdown.doc""" +@doc raw""" rational_span(Lf::LatWithIsom) -> QuadSpace Given a lattice with isometry $(L, f)$, return the rational span $L \otimes \mathbb{Q}$ @@ -106,7 +106,7 @@ of the underlying lattice `L`. """ rational_span(Lf::LatWithIsom) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} -@doc Markdown.doc""" +@doc raw""" det(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the determinant of the @@ -114,7 +114,7 @@ underlying lattice `L` (see [`det(::ZLat)`](@ref)). """ det(Lf::LatWithIsom) = det(lattice(Lf))::QQFieldElem -@doc Markdown.doc""" +@doc raw""" scale(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the scale of the underlying @@ -122,7 +122,7 @@ lattice `L` (see [`scale(::ZLat)`](@ref)). """ scale(Lf::LatWithIsom) = scale(lattice(Lf))::QQFieldElem -@doc Markdown.doc""" +@doc raw""" norm(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the norm of the underlying @@ -130,7 +130,42 @@ lattice `L` (see [`norm(::ZLat)`](@ref)). """ norm(Lf::LatWithIsom) = norm(lattice(Lf))::QQFieldElem -@doc Markdown.doc""" +@doc raw""" + is_positive_definite(Lf::LatWithIsom) -> Bool + +Given a lattice with isometry $(L, f)$, return whether the underlying +lattice `L` is positive definite (see [`is_positive_definite(::ZLat)`](@ref)). +""" +is_positive_definite(Lf::LatWithIsom) = is_positive_definite(lattice(Lf))::Bool + +@doc raw""" + is_negative_definite(Lf::LatWithIsom) -> Bool + +Given a lattice with isometry $(L, f)$, return whether the underlying +lattice `L` is negative definite (see [`is_positive_definite(::ZLat)`](@ref)). +""" +is_negative_definite(Lf::LatWithIsom) = is_negative_definite(lattice(Lf))::Bool + +@doc raw""" + is_definite(Lf::LatWithIsom) -> Bool + +Given a lattice with isometry $(L, f)$, return whether the underlying +lattice `L` is definite (see [`is_definite(::ZLat)`](@ref)). +""" +is_definite(Lf::LatWithIsom) = is_definite(lattice(Lf))::Bool + +@doc raw""" + minimum(Lf::LatWithIsom) -> QQFieldElem + +Given a positive definite lattice with isometry $(L, f)$, return the minimum +of the underlying lattice `L` (see [`minimum(::ZLat)`](@ref)). +""" +function minimum(Lf::LatWithIsom) + @req is_positive_definite(Lf) "Underlying lattice must be positive definite" + return minimum(lattice(Lf)) +end + +@doc raw""" is_integral(Lf::LatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice @@ -138,7 +173,7 @@ is integral, i.e. whether its scale is an integer (see [`scale(::LatWithIsom)`]( """ is_integral(Lf::LatWithIsom) = is_integral(lattice(Lf))::Bool -@doc Markdown.doc""" +@doc raw""" degree(Lf::LatWithIsom) -> Int Given a lattice with isometry $(L, f)$ inside the quadratic space $(V, \Phi)$, @@ -146,7 +181,7 @@ return the dimension of `V` as a $\mathbb Q$ vector space. """ degree(Lf::LatWithIsom) = degree(lattice(Lf))::Int -@doc Markdown.doc""" +@doc raw""" is_even(Lf::LatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice @@ -156,7 +191,7 @@ Note that to be even, `L` must be integral (see [`is_integral(::ZLat)`](@ref)). """ is_even(Lf::LatWithIsom) = iseven(lattice(Lf))::Bool -@doc Markdown.doc""" +@doc raw""" discriminant(Lf::LatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the discriminant of the underlying @@ -164,7 +199,7 @@ lattice `L` (see [`discriminant(::ZLat)`](@ref)). """ discriminant(Lf::LatWithIsom) = discriminant(lattice(Lf))::QQFieldElem -@doc Markdown.doc""" +@doc raw""" signature_tuple(Lf::LatWithIsom) -> Tuple{Int, Int, Int} Given a lattice with isometry $(L, f)$, return the signature tuple of the @@ -178,7 +213,7 @@ signature_tuple(Lf::LatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, # ############################################################################### -@doc Markdown.doc""" +@doc raw""" lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, ambient_representation = true) -> LatWithIsom @@ -227,7 +262,7 @@ function lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, end -@doc Markdown.doc""" +@doc raw""" lattice_with_isometry(L::ZLat) -> LatWithIsom Given a $\mathbb Z$-lattice `L` return the lattice with isometry pair $(L, f)$, @@ -241,11 +276,11 @@ end ############################################################################### # -# Operations on lattice with isometry +# Operations on lattices with isometry # ############################################################################### -@doc Markdown.doc""" +@doc raw""" rescale(Lf::LatWithIsom, a::RationalUnion) -> LatWithIsom Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lattice @@ -255,7 +290,7 @@ function rescale(Lf::LatWithIsom, a::Hecke.RationalUnion) return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), check=false) end -@doc Markdown.doc""" +@doc raw""" dual(Lf::LatWithIsom) -> LatWithIsom Given a lattice with isometry $(L, f)$ inside the space $(V, \Phi)$, such that `f` is @@ -268,7 +303,7 @@ function dual(Lf::LatWithIsom) return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), check = false) end -@doc Markdown.doc""" +@doc raw""" lll(Lf::LatWithIsom) -> LatWithIsom Given a lattice with isometry $(L, f)$, return the same lattice with isometry with a different @@ -284,13 +319,89 @@ function lll(Lf::LatWithIsom) return lattice_with_isometry(L2, f, ambient_representation = true) end +@doc raw""" + direct_sum(x::Vector{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} + direct_sum(x::Vararg{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} + +Given a collection of lattices with isometries $(L_1, f_1) \ldots, (L_n, f_n)$, +return the lattice with isometry $(L, f)$ together with the injections $L_i \to L$, +where `L` is the direct sum $L := L_1 \oplus \ldots \oplus L_n$ and `f` is the +isometry of `L` induced by the diagonal actions of the $f_i$'s. + +For objects of type `LatWithIsom`, finite direct sums and finite direct products +agree and they are therefore called biproducts. +If one wants to obtain $(L, f)$ as a direct product with the projections $L \to L_i$, +one should call `direct_product(x)`. +If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ and +the projections $L \to L_i$, one should call `biproduct(x)`. +""" +function direct_sum(x::Vector{LatWithIsom}) + @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" + W, inj = direct_sum(lattice.(x)) + f = block_diagonal_matrix(ambient_isometry.(x)) + return lattice_with_isometry(W, f, check=false), inj +end + +direct_sum(x::Vararg{LatWithIsom}) = direct_sum(collect(x)) + +@doc raw""" + direct_product(x::Vector{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} + direct_product(x::Vararg{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} + +Given a collection of lattices with isometries $(L_1, f_1), \ldots, (L_n, f_n)$, +return the lattice with isometry $(L, f)$ together with the projections $L \to L_i$, +where `L` is the direct product $L := L_1 \times \ldots \times L_n$ and `f` is the +isometry of `L` induced by the diagonal actions of the $f_i$'s. + +For objects of type `LatWithIsom`, finite direct sums and finite direct products +agree and they are therefore called biproducts. +If one wants to obtain $(L, f)$ as a direct sum with the injections $L_i \to L$, +one should call `direct_sum(x)`. +If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ and +the projections $L \to L_i$, one should call `biproduct(x)`. +""" +function direct_product(x::Vector{LatWithIsom}) + @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" + W, proj = direct_product(lattice.(x)) + f = block_diagonal_matrix(ambient_isometry.(x)) + return lattice_with_isometry(W, f, check=false), proj +end + +direct_product(x::Vararg{LatWithIsom}) = direct_product(collect(x)) + +@doc raw""" + biproduct(x::Vector{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} + biproduct(x::Vararg{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} + +Given a collection of lattices with isometries $(L_1, f_1), \ldots, (L_n, f_n)$, +return the lattice with isometry $(L, f)$ together with the injections +$L_i \to L$ and the projections $L \to L_i$, where `L` is the biproduct +$L := L_1 \oplus \ldots \oplus L_n$ and `f` is the isometry of `L` induced by the +diagonal actions of the $f_i$'s. + +For objects of type `LatWithIsom`, finite direct sums and finite direct products +agree and they are therefore called biproducts. +If one wants to obtain $(L, f)$ as a direct sum with the injections $L_i \to L$, +one should call `direct_sum(x)`. +If one wants to obtain $(L, f)$ as a direct product with the projections $L \to L_i$, +one should call `direct_product(x)`. +""" +function biproduct(x::Vector{LatWithIsom}) + @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" + W, inj, proj = biproduct(lattice.(x)) + f = block_diagonal_matrix(ambient_isometry.(x)) + return lattice_with_isometry(W, f, check=false), inj, proj +end + +biproduct(x::Vararg{LatWithIsom}) = biproduct(collect(x)) + ############################################################################### # # Hermitian structure # ############################################################################### -@doc Markdown.doc""" +@doc raw""" is_of_hermitian_type(Lf::LatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return the minimal polynomial of the @@ -309,7 +420,7 @@ function is_of_hermitian_type(Lf::LatWithIsom) return is_cyclotomic_polynomial(minpoly(isometry(Lf))) end -@doc Markdown.doc""" +@doc raw""" hermitian_structure(Lf::LatWithIsom) -> HermLat Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the @@ -335,7 +446,7 @@ end # ############################################################################### -@doc Markdown.doc""" +@doc raw""" discriminant_group(Lf::LatWithIsom) -> TorQuadMod, AutomorphismGroupElem Given an integral lattice with isometry $(L, f)$, return the discriminant group `q` @@ -351,7 +462,7 @@ function discriminant_group(Lf::LatWithIsom) return (q, Oq(gens(matrix_group(f))[1], check = false))::Tuple{TorQuadModule, AutomorphismGroupElem{TorQuadModule}} end -@doc Markdown.doc""" +@doc raw""" image_centralizer_in_Oq(Lf::LatWithIsom) -> AutomorphismGroup{TorQuadModule} Given an integral lattice with isometry $(L, f)$, return the image $G_L$ in @@ -414,7 +525,7 @@ function _real_kernel_signatures(L::ZLat, M) return (k1, k2) end -@doc Markdown.doc""" +@doc raw""" signatures(Lf::LatWithIsom) -> Dict{Int, Tuple{Int, Int}} Given a lattice with isometry $(L, f)$ where the minimal polynomial of `f` @@ -455,7 +566,7 @@ function _divides(k::IntExt, n::Int) return true end -@doc Markdown.doc""" +@doc raw""" kernel_lattice(Lf::LatWithIsom, p::Union{fmpz_poly, QQPolyRingElem}) -> LatWithIsom @@ -484,7 +595,7 @@ end kernel_lattice(Lf::LatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change_base_ring(QQ, p)) -@doc Markdown.doc""" +@doc raw""" kernel_lattice(Lf::LatWithIsom, l::Integer) -> LatWithIsom Given a lattice with isometry $(L, f)$ and an integer `l`, return the kernel @@ -496,7 +607,7 @@ function kernel_lattice(Lf::LatWithIsom, l::Integer) return kernel_lattice(Lf, p) end -@doc Markdown.doc""" +@doc raw""" invariant_lattice(Lf::LatWithIsom) -> LatWithIsom Given a lattice with isometry $(L, f)$, return the invariant lattice $L^f$ of @@ -505,7 +616,7 @@ in this case) """ invariant_lattice(Lf::LatWithIsom) = kernel_lattice(Lf, 1) -@doc Markdown.doc""" +@doc raw""" coinvariant_lattice(Lf::LatWithIsom) -> LatWithIsom Given a lattice with isometry $(L, f)$, return the coinvariant lattice $L_f$ of @@ -530,7 +641,7 @@ end # ############################################################################### -@doc Markdown.doc""" +@doc raw""" type(Lf::LatWithIsom) -> Dict{Int, Tuple{ <: Union{ZGenus, HermGenus}, ZGenus}} @@ -563,7 +674,7 @@ $\mathbb{Z}$-lattice $\Ker(f^k-1)$. return t end -@doc Markdown.doc""" +@doc raw""" is_of_type(Lf::LatWithIsom, t::Dict) -> Bool Given a lattice with isometry $(L, f)$, return whether $(L, f)$ is of type `t`. @@ -585,7 +696,7 @@ function is_of_type(L::LatWithIsom, t::Dict) return true end -@doc Markdown.doc""" +@doc raw""" is_of_same_type(Lf::LatWithIsom, Mg::LatWithIsom) -> Bool Given two lattices with isometry $(L, f)$ and $(M, g)$, return whether they are @@ -598,7 +709,7 @@ function is_of_same_type(L::LatWithIsom, M::LatWithIsom) return is_of_type(L, type(M)) end -@doc Markdown.doc""" +@doc raw""" is_hermitian(t::Dict) -> Bool Given a type `t` of lattices with isometry, return whether `t` is hermitian, i.e. diff --git a/experimental/LatticesWithIsometry/src/types.jl b/experimental/LatticesWithIsometry/src/types.jl index e376bf0ad8ba..a606a1ccfd10 100644 --- a/experimental/LatticesWithIsometry/src/types.jl +++ b/experimental/LatticesWithIsometry/src/types.jl @@ -1,4 +1,4 @@ -@doc Markdown.doc""" +@doc raw""" LatWithIsom A container type for pairs `(L, f)` consisting on an integer lattice `L` of diff --git a/experimental/LatticesWithIsometry/test/runtests.jl b/experimental/LatticesWithIsometry/test/runtests.jl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Oscar.jl b/src/Oscar.jl index 366b168aa51f..caed59b2b8e9 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -256,6 +256,9 @@ function __init__() add_verbose_scope(:GlobalWeierstrassModel) add_verbosity_scope(:LinearQuotients) + + add_assertion_scope(:LatWithIsom) + add_verbosity_scope(:LatWithIsom) end const PROJECT_TOML = Pkg.TOML.parsefile(joinpath(@__DIR__, "..", "Project.toml")) From 4d616375342b2bd05b17a18b019bd67c41c8ddd5 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 8 May 2023 15:17:17 +0200 Subject: [PATCH 48/76] final state --- .../LatticesWithIsometry/src/embeddings.jl | 187 +++++++++--------- .../src/lattices_with_isometry.jl | 14 +- .../LatticesWithIsometry/test/runtests.jl | 45 +++++ test/Experimental/LatWithIsom.jl | 39 ---- 4 files changed, 145 insertions(+), 140 deletions(-) delete mode 100644 test/Experimental/LatWithIsom.jl diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index 6e7aa8571155..e832c47ab619 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -16,7 +16,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu 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(v -> AinD(v[1])*BinD(v[2]) == 0, Hecke.cartesian_product_iterator([gens(A), gens(B)], inplce=true)) + @assert all(v -> AinD(v[1])*BinD(v[2]) == 0, Hecke.cartesian_product_iterator([gens(A), gens(B)], inplace=true)) OD = orthogonal_group(D) OA = orthogonal_group(A) OB = orthogonal_group(B) @@ -274,12 +274,12 @@ function _subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{T coAgap = [sub(Agap, togap.(q.(j[2].(gens(j[1])))))[1] for j in it] OAgap = automorphism_group(Agap) OinOAgap, j = sub(OAgap, OAgap.([g.X for g in gens(O)])) - m = gset(OinOAgap, on_subgroup, coAgap) + m = gset(OinOAgap, _on_subgroup_automorphic, coAgap) orbs = orbits(m) res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] for orb in orbs rep = representative(orb) - stab, _ = stabilizer(OinOAgap, rep, on_subgroup) + stab, _ = stabilizer(OinOAgap, rep, _on_subgroup_automorphic) _, rep = sub(q, TorQuadModuleElem[tooscar(Agap(g)) for g in gens(rep)]) stab, _ = sub(O, O.([h.X for h in gens(stab)])) push!(res, (rep, stab)) @@ -307,19 +307,18 @@ end # # We follow the second definition of Nikulin, i.e. we classify up to the actions # of O(M) and O(N). -function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadModule) +function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadModule; GN = nothing, GM = nothing) @hassert :LatWithIsom 1 is_one(basis_matrix(N)) @hassert :LatWithIsom 1 is_one(basis_matrix(M)) results = Tuple{ZLat, ZLat, ZLat}[] - GN, _ = image_in_Oq(N) - GM, _ = image_in_Oq(M) + GN = GN === nothing ? image_in_Oq(N)[1] : GN + GM = GM === nothing ? image_in_Oq(M)[1] : GM qN = domain(GN) qM = domain(GM) D, inj = direct_sum(qN, qM) qNinD, qMinD = inj OD = orthogonal_group(D) - subsN = _classes_automorphic_subgroups(GN, rescale(H, -1)) @hassert :LatWithIsom 1 !isempty(subsN) subsM = _classes_automorphic_subgroups(GM, H) @@ -370,71 +369,71 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM return results end -function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadModule) - @hassert :LatWithIsom 1 is_one(basis_matrix(N)) - @hassert :LatWithIsom 1 is_one(basis_matrix(M)) - q = elementary_divisors(H)[end] - ok, p, _ = is_prime_power_with_data(q) - @hassert :LatWithIsom 1 ok - @hassert :LatWithIsom 1 is_elementary(M, p) - results = Tuple{ZLat, ZLat, ZLat}[] - GN, _ = image_in_Oq(N) - GM, _ = image_in_Oq(M) - qN = domain(GN) - qM = domain(GM) - - D, inj = direct_sum(qN, qM) - qNinD, qMinD = inj - OD = orthogonal_group(D) - VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) - subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) - filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) - @assert !isempty(subsN) - subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) - filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) - @assert !isempty(subsM) - - for H1 in subsN, H2 in subsM - ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) - @hassert :LatWithIsom 1 ok - - HNinqN, stabN = H1 - OHN = orthogonal_group(HN) - - HMinqM, stabM = H2 - OHM = orthogonal_group(HM) - - actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) - - actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) 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, _ = sub(OHM, stabNphi) - reps = double_cosets(OHM, stabNphi, imM) - @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" - for g in reps - g = representative(g) - phig = compose(phi, hom(g)) - _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] - z = zero_matrix(QQ, 0, degree(N)+degree(M)) - glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) - glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) - N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) - @hassert :LatWithIsom 1 genus(N) == genus(N2) - M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) - @hassert :LatWithIsom 1 genus(M) == genus(M2) - push!(results, (L, M2, N2)) - @vprint :LatWithIsom 1 "Gluing done\n" - GC.gc() - end - end - return results -end +#function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadModule) +# @hassert :LatWithIsom 1 is_one(basis_matrix(N)) +# @hassert :LatWithIsom 1 is_one(basis_matrix(M)) +# q = elementary_divisors(H)[end] +# ok, p, _ = is_prime_power_with_data(q) +# @hassert :LatWithIsom 1 ok +# @hassert :LatWithIsom 1 is_elementary(M, p) +# results = Tuple{ZLat, ZLat, ZLat}[] +# GN, _ = image_in_Oq(N) +# GM, _ = image_in_Oq(M) +# qN = domain(GN) +# qM = domain(GM) +# +# D, inj = direct_sum(qN, qM) +# qNinD, qMinD = inj +# OD = orthogonal_group(D) +# VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) +# subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) +# filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) +# @assert !isempty(subsN) +# subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) +# filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) +# @assert !isempty(subsM) +# +# for H1 in subsN, H2 in subsM +# ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) +# @hassert :LatWithIsom 1 ok +# +# HNinqN, stabN = H1 +# OHN = orthogonal_group(HN) +# +# HMinqM, stabM = H2 +# OHM = orthogonal_group(HM) +# +# actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) +# +# actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) 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, _ = sub(OHM, stabNphi) +# reps = double_cosets(OHM, stabNphi, imM) +# @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" +# for g in reps +# g = representative(g) +# phig = compose(phi, hom(g)) +# _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] +# z = zero_matrix(QQ, 0, degree(N)+degree(M)) +# glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) +# glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) +# glue = FakeFmpqMat(glue) +# _B = hnf(glue) +# _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) +# L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) +# N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) +# @hassert :LatWithIsom 1 genus(N) == genus(N2) +# M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) +# @hassert :LatWithIsom 1 genus(M) == genus(M2) +# push!(results, (L, M2, N2)) +# @vprint :LatWithIsom 1 "Gluing done\n" +# GC.gc() +# end +# end +# return results +#end @doc raw""" @@ -443,14 +442,17 @@ end Given a `p`-primary lattice `L`, unique in its genus, and a lattice `M`, compute representatives for all isomorphism classes of primitive embeddings -of `M` in `L` up to the actions of $\bar{O}(M)$ and $O(L)$. Here -$\bar{O}(M)$ denotes the image of $O(M)\to O(q_M)$. +of `M` in `L` up to the actions of $\bar{O}(M)$ and $\bar{O}(L)$. The output is given in terms of triples `(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice of `L'` isometric to `M` and `N'` is the orthogonal complement of `M'` in `L'`. """ -function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); check::Bool = false) +function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; GL::AutomorphismGroup{TorQuadModule} = image_in_Oq(L)[1], GM::AutomorphismGroup{TorQuadModule} = image_in_Oq(M)[1], check::Bool = false) + pL, _, nL = signature_tuple(L) + pM, _, nM = signature_tuple(M) + @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" + @req rank(M) < rank(L) "M must be of smaller rank than L" bool, p = is_primary_with_prime(L) @req bool "L must be unimodular or primary" el = is_elementary(L, p) @@ -458,30 +460,27 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph @req length(genus_representatives(L)) == 1 "L must be unique in its genus" end M = Zlattice(gram = gram_matrix(M)) + qM = discriminant_group(M) + ok, phi = is_isometric_with_isometry(qM, domain(GM)) + @assert ok + iphi = inv(phi) + GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(compose(phi, compose(hom(g), iphi))) for g in unique(gens(GM))]) results = Tuple{ZLat, ZLat, ZLat}[] qL = rescale(discriminant_group(L), -1) GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) - pL, _, nL = signature_tuple(L) - pM, _, nM = signature_tuple(M) - @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" - if ((pL, nL) == (pM, nM)) - genus(M) != genus(L) && return results - return [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] - end - qM = discriminant_group(M) D, inj, proj = biproduct(qM, qL) qMinD, qLinD = inj if el - VM, VMinqM, _ = _get_V(M, qM, identity_matrix(QQ, rank(M)), id_hom(qM), minpoly(identity_matrix(QQ,1)), p) + VM, VMinqM, _ = _get_V(id_hom(qM), minpoly(identity_matrix(QQ,1)), p) else VM, VMinqM = primary_part(qM, p) end - for k in divisors(gcd(order(VM), order(qL))) + for k in divisors(gcd(order(VM), order(qL))) @vprint :LatWithIsom 1 "Glue order: $(k)\n" if el - subsL = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(GL), GL, k) + subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), GL, k) else - subsL = subgroups_orbit_representatives_and_stabilizers(GL, order = k) + subsL = _subgroups_orbit_representatives_and_stabilizers(GL, order = k) end @vprint :LatWithIsom 1 "$(length(subsL)) subgroup(s)\n" for H in subsL @@ -499,17 +498,17 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat, GL::Automorph 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 = rescale(disc, -1) - !is_genus(disc, (pL-pM, nL-nM)) && continue - G = genus(disc, (pL-pM, nL-nM)) - @vprint :LatWithIsom 1 "We can glue: $G" + disc2 = rescale(disc, -1) + !is_genus(disc2, (pL-pM, nL-nM)) && continue + G = genus(disc2, (pL-pM, nL-nM)) + @vprint :LatWithIsom 1 "We can glue: $G\n" Ns = representatives(G) - @vprint :LatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" + @vprint :LatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" Ns = lll.(Ns) Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] - qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) + qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - append!(results, _isomorphism_classes_primitive_extensions(N, M, qM2)) + append!(results, _isomorphism_classes_primitive_extensions(N, M, GM = GM, qM2)) GC.gc() end GC.gc() diff --git a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl index 8538ef419416..f1b8eb5c381a 100644 --- a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl +++ b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl @@ -404,12 +404,12 @@ biproduct(x::Vararg{LatWithIsom}) = biproduct(collect(x)) @doc raw""" is_of_hermitian_type(Lf::LatWithIsom) -> Bool -Given a lattice with isometry $(L, f)$, return the minimal polynomial of the -underlying isometry `f` is (irreducible) cyclotomic. +Given a lattice with isometry $(L, f)$, return whether the minimal polynomial of +the underlying isometry `f` is (irreducible) cyclotomic. Note that if $(L, f)$ is of hermitian type with `f` of order `n`, then `L` can -be seen as a hermitian lattice over the order $\mathbb{Z}[\zeta_n]$ where $\zeta_n$ -is a primitive $n$-th root of unity. +be seen as a hermitian lattice over the order $\mathbb{Z}[\zeta_n]$ where +$\zeta_n$ is a primitive $n$-th root of unity. """ function is_of_hermitian_type(Lf::LatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" @@ -424,9 +424,9 @@ end hermitian_structure(Lf::LatWithIsom) -> HermLat Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the -underlying isometry `f` is irreducible and cyclotomic, return the -hermitian structure of the underlying lattice `L` over the $n$th cyclotomic -field, where $n$ is the order of `f`. +underlying isometry `f` is cyclotomic, return the hermitian structure of the +underlying lattice `L` over the $n$th cyclotomic field, where $n$ is the +order of `f`. If it exists, the hermitian structure is stored. """ diff --git a/experimental/LatticesWithIsometry/test/runtests.jl b/experimental/LatticesWithIsometry/test/runtests.jl index e69de29bb2d1..e54683b211ca 100644 --- a/experimental/LatticesWithIsometry/test/runtests.jl +++ b/experimental/LatticesWithIsometry/test/runtests.jl @@ -0,0 +1,45 @@ +using test +using Oscar + +@testset "Constructors and accessors" begin + A4 = root_lattice(:A, 4) + agg = = automorphism_group_generators(A4, ambient_representation = false) + agg_ambient = automorphism_group_generators(A4, ambient_representation = true) + f = rand(agg) + g_ambient = rand(agg_ambient) + + L = @inferred lattice_with_isometry(A4) + @test isone(isometry(L)) + @test isone(ambient_isometry(L)) + @test isone(order_of_isometry(L)) + + for func in [rank, charpoly, minpoly, genus, ambient_space, basis_matrix, + gram_matrix, rational_span, det, scale, norm, is_integral, + degree, is_even, discriminant, signature_tuple] + out = @inferred func(L) + end + + @test minimum(rescale(L, -1)) == 2 + @test !is_positive_definite(L) + @test is_definite(L) + + nf = multiplicative_order(f) + @test_throws ArgumentError lattice_with_isometry(A4, zero_matrix(QQ, 0, 0)) + + L2 = @inferred lattice_with_isometry(A4, f, ambient_representation = false) + @test order_of_isometry(L2) == nf + L2v = @inferred dual(L2) + @test order_of_isometry(L2v) == nf + @test ambient_isometry(L2v) == ambient_isometry(L2) + + L3 = @inferred lattice_with_isometry(A4, g_ambient, ambient_representation = true) + @test order_of_isometry(L2) == multiplicative_order(g_ambient) + + L4 = @inferred rescale(L3, QQ(1//4)) + @test !is_integral(L4) + @test order_of_isometry(L4) == order_of_isometry(L3) + @test_throws ArgumentError dual(L4) + @test ambient_isometry(lll(L4)) == ambient_isometry(L4) + + @test order_of_isometry(biproduct(L2, L3)) == lcm(order_of_isometry(L2), order_of_isometry(L3)) +end diff --git a/test/Experimental/LatWithIsom.jl b/test/Experimental/LatWithIsom.jl deleted file mode 100644 index cbc9832501b1..000000000000 --- a/test/Experimental/LatWithIsom.jl +++ /dev/null @@ -1,39 +0,0 @@ -using Oscar.LWI - -@testset "Constructors and accessors" begin - A4 = root_lattice(:A, 4) - agg = = automorphism_group_generators(A4, ambient_representation = false) - agg_ambient = automorphism_group_generators(A4, ambient_representation = true) - f = rand(agg) - g_ambient = rand(agg_ambient) - - L = @inferred lattice_with_isometry(A4) - @test isone(isometry(L)) - @test isone(ambient_isometry(L)) - @test isone(order_of_isometry(L)) - - for func in [rank, charpoly, minpoly, genus, ambient_space, basis_matrix, - gram_matrix, rational_span, det, scale, norm, is_integral, - degree, is_even, discriminant, signature_tuple] - out = @inferred func(L) - end - - nf = multiplicative_order(f) - @test_throws ArgumentError lattice_with_isometry(A4, f, nf+1) - @test_throws ArgumentError lattice_with_isometry(A4, zero_matrix(QQ, 0, 0), -1) - - L2 = @inferred lattice_with_isometry(A4, f, ambient_representation = false) - @test order_of_isometry(L2) == nf - L2v = @inferred dual(L2) - @test order_of_isometry(L2v) == nf - @test ambient_isometry(L2v) == ambient_isometry(L2) - - L3 = @inferred lattice_with_isometry(A4, g_ambient, ambient_representation = true) - @test order_of_isometry(L2) == multiplicative_order(g_ambient) - - L4 = @inferred rescale(L3, QQ(1//4)) - @test !is_integral(L4) - @test order_of_isometry(L4) == order_of_isometry(L3) - @test_throws ArgumentError dual(L4) - @test ambient_isometry(lll(L4)) == ambient_isometry(L4) -end From 0c08b06dacd58d46786581c6355619311ee2bb01 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 12 May 2023 10:08:50 +0200 Subject: [PATCH 49/76] temp --- .../LatticesWithIsometry/src/embeddings.jl | 642 ++++++++++-------- .../LatticesWithIsometry/src/enumeration.jl | 16 +- 2 files changed, 356 insertions(+), 302 deletions(-) diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index e832c47ab619..984021a3c6f4 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -21,7 +21,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu OA = orthogonal_group(A) OB = orthogonal_group(B) - gene = data.(union(AinD.(gens(A), BinD.(gens(B))))) + gene = data.(union(AinD.(gens(A)), BinD.(gens(B)))) geneOAinOD = elem_type(OD)[] for f in gens(OA) imgf = data.(union(AinD.(f.(gens(A))), BinD.(gens(B)))) @@ -82,33 +82,38 @@ end function _get_V(fq, mu, p) q = domain(fq) V, Vinq = primary_part(q, p) - pV, pVinq = sub(q, Vinq.([divexact(order(g), p)*g for g in gens(V) if !(order(g)==1)])) + pV, pVinq = sub(q, [q(lift(divexact(order(g), p)*g)) for g in gens(V) if !(order(g)==1)]) fpV = restrict_endomorphism(fq, pVinq) fpV_mu = evaluate(mu, fpV) - K, KtopV = kernel(fpV_mu) - Ktoq = compose(KtopV, pVinq) + K, _ = kernel(fpV_mu) + Ktoq = hom(K, q, [q(lift(a)) for a in gens(K)]) + @hassert :LatWithIsom 1 is_injective(Ktoq) fK = restrict_endomorphism(fq, Ktoq) return K, Ktoq, fK end # This is the rho function as defined in Definition 4.8 of BH23. function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) + Nv = cover(q) N = relations(q) if l == 0 Gl = N - Gm = intersect(1//p*N, dual(N)) - rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) - gene = elem_type(q)[q(lift(g)) for g in gens(rholN)] - return sub(q, gene) - end - k = l-1 - m = l+1 - Gk = intersect(1//p^k*N, dual(N)) - Gl = intersect(1//p^l*N, dual(N)) - Gm = intersect(1//p^m*N, dual(N)) - rholN = torsion_quadratic_module(Gl, Gk+p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) - gene = elem_type(q)[q(lift(g)) for g in gens(rholN)] - return sub(q, gene) + Gm = intersect(1//p*N, Nv) + rholN = torsion_quadratic_module(Gl, p*Gm) + else + k = l-1 + m = l+1 + Gk = intersect((1//(p^k))*N, Nv) + Gl = intersect((1//(p^l))*N, Nv) + Gm = intersect((1//(p^m))*N, Nv) + B = Gk+p*Gm + rholN = torsion_quadratic_module(Gl, B) + end + rho = rescale(rholN, QQ(p)^(l-1)) + if order(rho) == 1 + rho = torsion_quadratic_module(cover(rho), relations(rho), modulus = QQ(1), modulus_qf = QQ(2)) + end + return rho end ############################################################################## @@ -122,6 +127,12 @@ function _on_subgroup_automorphic(H::Oscar.GAPGroup, g::AutomorphismGroupElem) return sub(G, g.(gens(H)))[1] end +function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) + q = domain(parent(g)) + gene = TorQuadModuleElem[g(t) for t in gens(T)] + return sub(q, gene)[1] +end + function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) @@ -146,6 +157,7 @@ function _cokernel_as_Fp_vector_space(HinV, p, f) if f isa AutomorphismGroupElem f = hom(f) end + H = domain(HinV) HS, HStoH = snf(H) HStoV = compose(HStoH, HinV) @@ -184,7 +196,7 @@ end # Given an abelian group injection V \to q where the group structure on V is # abelian p-elementary, compute orbits and stabilizers of subgroups of V of -# order ord, which contains l*q and which are fixed under the action of f, under +# order ord, which contains p^l*q and which are fixed under the action of f, under # the action by automorphisms of q in G. # # Note that V is fixed by the elements in G, f commutes with the elements in G @@ -197,31 +209,41 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu l::Hecke.IntegerUnion = 0) res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] + V = domain(Vinq) + q = codomain(Vinq) + p = elementary_divisors(V)[1] + pq, pqtoq = primary_part(q, p) + g = valuation(ord, p) + if ord == 1 - l != 0 && (return res) + l < valuation(order(pq), p) && (return res) _, triv = sub(codomain(Vinq), TorQuadModuleElem[]) push!(res, (triv, G)) return res end - V = domain(Vinq) - q = codomain(Vinq) - p = elementary_divisors(V)[1] - pq, pqtoq = primary_part(q, p) - g = valuation(ord, p) @hassert :LatWithIsom 1 all(a -> has_preimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) - H0, H0inV = sub(V, [preimage(Vinq, (p^l)*pqtoq(a)) for a in gens(pq)]) - Qp, VtoVp, VptoQp, fQp = _cokernel_as_Fp_vector_space(H0inV, p, f) - Vp = codomain(VtoVp) + H0, H0inq = sub(q, [q(lift((p^l)*a)) for a in gens(pq)]) + H0inV = hom(H0, V, [V(lift(a)) for a in gens(H0)]) - if dim(Qp) == 0 - order(V) != ord && (return res) + if order(H0) >= ord + order(H0) > ord && (return res) + push!(res, (H0inq, G)) + return res + end + + if ord == order(V) push!(res, (Vinq, G)) return res end - OV = orthogonal_group(V) + Qp, VtoVp, VptoQp, fQp = _cokernel_as_Fp_vector_space(H0inV, p, f) + Vp = codomain(VtoVp) + + dim(Qp) == 0 && (return res) + GV, GtoGV = restrict_automorphism_group(G, Vinq) + satV, j = kernel(GtoGV) act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] @@ -229,10 +251,8 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu @hassert :LatWithIsom 1 fQp in MGp MGptoGV = hom(MGp, GV, gens(GV), check = false) - if g-ngens(snf(abelian_group(H0))[1]) > dim(Qp) - return res - end - + @hassert :LatWithIsom g-ngens(snf(abelian_group(H0))[1]) < dim(Qp) + F = base_ring(Qp) k, K = kernel(VptoQp.matrix, side = :left) gene_H0p = elem_type(Vp)[Vp(vec(collect(K[i,:]))) for i in 1:k] @@ -257,7 +277,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu @hassert :LatWithIsom 1 order(orbq) == ord stabq = AutomorphismGroup{TorQuadModule}[GtoGV\(MGptoGV(s)) for s in gens(stab)] - stabq, _ = sub(G, stabq) + stabq, _ = sub(G, union(stabq, satV)) push!(res, (orbqinq, stabq)) @label non_fixed end @@ -369,73 +389,6 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM return results end -#function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadModule) -# @hassert :LatWithIsom 1 is_one(basis_matrix(N)) -# @hassert :LatWithIsom 1 is_one(basis_matrix(M)) -# q = elementary_divisors(H)[end] -# ok, p, _ = is_prime_power_with_data(q) -# @hassert :LatWithIsom 1 ok -# @hassert :LatWithIsom 1 is_elementary(M, p) -# results = Tuple{ZLat, ZLat, ZLat}[] -# GN, _ = image_in_Oq(N) -# GM, _ = image_in_Oq(M) -# qN = domain(GN) -# qM = domain(GM) -# -# D, inj = direct_sum(qN, qM) -# qNinD, qMinD = inj -# OD = orthogonal_group(D) -# VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) -# subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) -# filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) -# @assert !isempty(subsN) -# subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) -# filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) -# @assert !isempty(subsM) -# -# for H1 in subsN, H2 in subsM -# ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) -# @hassert :LatWithIsom 1 ok -# -# HNinqN, stabN = H1 -# OHN = orthogonal_group(HN) -# -# HMinqM, stabM = H2 -# OHM = orthogonal_group(HM) -# -# actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) -# -# actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) 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, _ = sub(OHM, stabNphi) -# reps = double_cosets(OHM, stabNphi, imM) -# @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" -# for g in reps -# g = representative(g) -# phig = compose(phi, hom(g)) -# _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] -# z = zero_matrix(QQ, 0, degree(N)+degree(M)) -# glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) -# glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) -# glue = FakeFmpqMat(glue) -# _B = hnf(glue) -# _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) -# L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) -# N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) -# @hassert :LatWithIsom 1 genus(N) == genus(N2) -# M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) -# @hassert :LatWithIsom 1 genus(M) == genus(M2) -# push!(results, (L, M2, N2)) -# @vprint :LatWithIsom 1 "Gluing done\n" -# GC.gc() -# end -# end -# return results -#end - - @doc raw""" primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat) -> Vector{Tuple{ZLat, ZLat, ZLat}} @@ -518,177 +471,12 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; GL::Automorph return results end -#function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) -# bool, p = is_elementary_with_prime(M) -# @req bool "M must be elementary" -# if check -# @req length(genus_representatives(L)) == 1 "L must be unique in its genus" -# end -# results = Tuple{ZLat, ZLat, ZLat}[] -# qL = rescale(discriminant_group(L), -1) -# GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) -# pL, _, nL = signature_tuple(L) -# pM, _, nM = signature_tuple(M) -# @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" -# if ((pL, nL) == (pM, nM)) -# if genus(M) != genus(L) -# return false, results -# else -# return true, [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] -# end -# end -# qM = discriminant_group(M) -# D, inj, proj = biproduct(qM, qL) -# qMinD, qLinD = inj -# VL, VLinqL, _ = _get_V(L, qL, identity_matrix(QQ, rank(L)), id_hom(qL), minpoly(identity_matrix(QQ,1)), p) -# for k in divisors(gcd(order(qM), order(VL))) -# @info "Glue order: $(k)" -# subsL = subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) -# @info "$(length(subsL)) subgroup(s)" -# subsM = subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), orthogonal_group(qM), k) -# for H in subsL -# HL = domain(H[1]) -# _subsM = filter(HM -> is_anti_isometric_with_anti_isometry(domain(HM[1]), HL)[1], subsM) -# isempty(_subsM) && continue -# @info "Possible gluing" -# HM = _subsM[1][1] -# ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) -# @assert ok -# _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] -# ext, _ = sub(D, D.(_glue)) -# 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 = rescale(disc, -1) -# !is_genus(disc, (pL-pM, nL-nM)) && continue -# G = genus(disc, (pL-pM, nL-nM)) -# !classification && return true, G -# @info "We can glue: $G" -# Ns = representatives(G) -# @info "$(length(Ns)) possible orthogonal complement(s)" -# Ns = lll.(Ns) -# Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] -# qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) -# for N in Ns -# append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) -# if first -# return results -# end -# GC.gc() -# end -# GC.gc() -# end -# end -# @assert all(triple -> genus(triple[1]) == genus(L), results) -# return (length(results) >0), results -#end - #################################################################################### # # Admissible equivariant primitive extensions # #################################################################################### -#function equivariant_primitive_extensions(A::LatWithIsom, GA::AutomorphismGroup{TorQuadModule}, -# B::LatWithIsom, GB::AutomorphismGroup{TorQuadModule}, -# L::ZLat; check::Bool = false) -# -# if check -# pA, nA = signature_tuple(A) -# pB, nB = signature_tuple(B) -# pL, nL = signature_tuple(L) -# @req pA+pB == pL "Incompatible signatures" -# @req nA+nB == nL "Incompatible signatures" -# @req ambient_space(A) === ambient_space(B) === ambient_space(L) "Lattices must all live in the same ambient space" -# end -# -# results = LatWithIsom[] -# -# qA, fqA = discriminant_group(A) -# qB, fqB = discriminant_group(B) -# -# if check -# @req all(g -> compose(g, compose(fqA, inv(g))) == fqA, GA) "GA does not centralize fqA" -# @req all(g -> compose(g, compose(fqB, inv(g))) == fqB, GB) "GB does not centralize fqB" -# end -# D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) -# OqA = domain(OqAinOD) -# OqB = domain(OqBinOD) -# -# gamma, _, _ = glue_map(L, lattice(A), lattice(B)) -# subsA = classes_conjugate_subgroups(GA, domain(gamma)) -# @assert !isempty(subsN) -# subsB = classes_conjugate_subgroups(GB, codomain(gamma)) -# @assert !isempty(subsM) -# -# for (H1, H2) in Hecke.cartesian_product_iterator([subsA, subsB], inplace=true) -# ok, phi = is_anti_isometric_with_anti_isometry(H1, H2) -# @assert ok -# SAinqA, stabA = H1 -# OSAinOqA = embedding_orthogonal_group(SAinqA) -# OSA = domain(OSAinOqA) -# OSAinOD = compose(OSAinOqA, OqAinOD) -# -# SBinqB, stabB = H2 -# SB = domain(SBinqB) -# OSBinOqB = embedding_orthogonal_group(SBinqB) -# OSB = domain(OSBinOqB) -# OSBinOD = compose(OSBinOqB, OqBinOD) -# -# # we compute the image of the stabalizers in the respective OS* and we keep track -# # of the elements of the stabilizers acting trivially in the respective S* -# actA = hom(stabA, OSA, [OSA(Oscar.restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) -# imA, _ = image(actA) -# kerA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x) for x in gens(Hecke.kernel(actA)[1])] -# fSA = OSA(restrict_automorphism(fqA, SAinqA)) -# -# actB = hom(stabB, OSB, [OSB(Oscar.restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) -# imB, _ = image(actB) -# kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] -# fSB = OSB(restrict_automorphism(fqB, SBinqB)) -# -# -# fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) -# bool, g0 = representative_action(OSB, fSAinOSB, fSB) -# bool || continue -# phi = compose(phi, hom(OSB(g0))) -# fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) -# # Now the new phi is "sending" the restriction of fA to this of fB. -# # So we can glue SA and SB. -# @assert fSAinOSB == fSB -# -# # 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(OSB, fSB) -# center, _ = sub(OSB, [OSB(c) for c in gens(center)]) -# stabSAphi = AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] -# stabSAphi, _ = sub(center, stabSAphi) -# stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) -# reps = double_cosets(center, stabSB, stabSAphi) -# -# for g in reps -# g = representative(g) -# phig = compose(phi, hom(g)) -# -# _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] -# z = zero_matrix(QQ, 0, degree(A)) -# glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) -# glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) -# glue = FakeFmpqMat(glue) -# _B = hnf(glue) -# _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) -# C2 = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) -# fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) -# _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) -# fC2 = _B*fC2*inv(_B) -# L2 = lattice_with_isometry(C2, fC2, ambient_representation = false) -# @assert genus(L2) == genus(L) -# push!(results, L2) -# end -# end -# return results -#end - @doc raw""" admissible_equivariant_primitive_extensions(Afa::LatWithIsom, Bfb::LatWithIsom, @@ -745,6 +533,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, qA, fqA = discriminant_group(A) qB, fqB = discriminant_group(B) + qC, _ = discriminant_group(C) GA = image_centralizer_in_Oq(A) @hassert :LatWithIsom 1 fqA in GA GB = image_centralizer_in_Oq(B) @@ -803,7 +592,8 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # scale of the dual: any glue kernel must contain the multiples of l of the respective # discriminant groups l = valuation(level(genus(C)), p) - + spec = (p == 2) && (_is_free(qA, p, l+1)) && (_is_free(qB, p, l+1)) && (_is_even(_rho_functor(qC, p, l))) + # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones @@ -811,6 +601,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fVA, ZZ(l)) subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) + # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry R = Tuple{eltype(subsA), eltype(subsB), TorQuadModuleMor}[] @@ -831,7 +622,8 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, SBinqB, stabB = H2 SB = domain(SBinqB) OSB = orthogonal_group(SB) - + + phi = _find_admissible_gluing(SAinqA, SBinqB, phi, p, spec) # we compute the image of the stabalizers in the respective OS* and we keep track # of the elements of the stabilizers acting trivially in the respective S* actA = hom(stabA, OSA, [OSA(restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) @@ -842,21 +634,12 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, actB = hom(stabB, OSB, [OSB(restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) imB, _ = image(actB) - kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(Hecke.kernel(actB)[1])] + kerB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(x) for x in gens(kernel(actB)[1])] push!(kerB, OqBinOD(one(OqB))) fSB = OSB(restrict_automorphism(fqB, SBinqB)) - # we get all the elements of qB of order exactly p^{l+1}, which are not mutiple of an - # element of order p^{l+2}. In theory, glue maps are classified by the orbit of phi - # under the action of O(SB, rho_{l+1}(qB), fB) - rB, rBinqB = _rho_functor(qB, p, l+1) - @hassert :LatWithIsom 1 is_invariant(stabB, rBinqB) - rBinSB = hom(rB, SB, TorQuadModuleElem[SBinqB\(rBinqB(k)) for k in gens(rB)]) - @hassert :LatWithIsom 1 is_injective(rBinSB) # we indeed have rho_{l+1}(qB) which is a subgroup of SB - - # We compute the generators of O(SB, rho_{l+1}(qB)) - OSBrB, _ = stabilizer(OSB, rBinSB) - @hassert :LatWithIsom fSB in OSBrB + OSBrB = _compute_double_stabilizer(SBinqB, l, spec) + @hassert :LatWithIsom 1 fSB in OSBrB # 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 glueing. @@ -874,11 +657,10 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # images of stabA|stabB for taking the double cosets next center, _ = centralizer(OSBrB, OSBrB(fSB)) center, _ = sub(OSB, [OSB(c) for c in gens(center)]) - stabSAphi = AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(actA(g)), phi))) for g in gens(stabA)] - stabSAphi, _ = sub(center, stabSAphi) - stabSB, _ = sub(center, [actB(s) for s in gens(stabB)]) + stabSAphi, _ = sub(OSB, AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(g), phi))) for g in gens(imA)]) + stabSAphi, _ = intersect(center, stabSAphi) + stabSB, _ = intersect(center, imB) reps = double_cosets(center, stabSB, stabSAphi) - # now we iterate over all double cosets and for each representative, we compute the # corresponding overlattice in the glueing. If it has the wanted type, we compute # the image of the centralizer in OD from the stabA and stabB. @@ -886,6 +668,8 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, for g in reps g = representative(g) phig = compose(phi, hom(g)) + @assert _is_anti_isometry(phig) + @assert all(a -> Hecke.quadratic_product(a) + Hecke.quadratic_product(phig(a)) == 0, gens(domain(phig))) if amb _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] @@ -900,14 +684,15 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) else - _glue = Vector{QQFieldElem}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(domain(phig))] - z = zero_matrix(QQ,0,degree(A)+degree(B)) - glue = reduce(vcat, [matrix(QQ,1,degree(A)+degree(B),g) for g in _glue], init=z) - glue = vcat(block_diagonal_matrix([basis_matrix(A), basis_matrix(B)]), glue) + _glue = Vector{QQFieldElem}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(SA)] + z = zero_matrix(QQ,0,degree(cover(D))) + glue = reduce(vcat, [matrix(QQ, 1, degree(cover(D)),g) for g in _glue], init=z) + AB = lattice_in_same_ambient_space(cover(D), basis_matrix(A)*matrix(qAinD)) + lattice_in_same_ambient_space(cover(D), basis_matrix(B)*matrix(qBinD)) + glue = vcat(basis_matrix(AB), glue) glue = FakeFmpqMat(glue) _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = Hecke.lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) + C2 = lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) fC2 = __B*fC2*inv(__B) @@ -918,12 +703,12 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, 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)) - qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) @hassert :LatWithIsom 1 _is_isometry(phi2) - + println(genus(C2)) + println(multiplicative_order(fC2)) if !is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) continue end @@ -945,10 +730,61 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, push!(results, C2) end end - return results end +function _on_modular_matrix(M::QQMatrix, g::AutomorphismGroupElem) + q = domain(parent(g)) + R = Hecke.value_module(q) + m = matrix(g) + return map_entries(a -> lift(R(a)), m*M*transpose(m)) +end + +function _on_modular_matrix_quad(M::QQMatrix, g::AutomorphismGroupElem) + q = domain(parent(g)) + R = Hecke.value_module_quadratic_form(q) + m = matrix(g) + return map_entries(a -> lift(R(a)), m*M*transpose(m)) +end + +function _compute_double_stabilizer(SBinqB, l, spec) + SB = domain(SBinqB) + qB = codomain(SBinqB) + OSB = orthogonal_group(SB) + p = elementary_divisors(SB)[1] + pqB, pqBinqB = primary_part(qB, p) + _HB, _ = sub(qB, [qB(lift((p^l)*a)) for a in gens(pqB)]) + HB, _ = snf(_HB) + HBinqB = hom(HB, qB, [qB(lift(a)) for a in gens(HB)]) + + rB = _rho_functor(qB, p, l+1) + HBinSB = hom(HB, SB, TorQuadModuleElem[SB(lift(k)) for k in gens(HB)]) + @hassert :LatWithIsom 1 is_injective(HBinSB) + + OSBHB, _ = stabilizer(OSB, HBinSB) + OHB, OSBHBtoOHB = restrict_automorphism_group(OSBHB, HBinSB) + K, _ = kernel(OSBHBtoOHB) + rBtoHB = hom(rB, HB, [HB(QQ(p^l)*lift(a)) for a in gens(rB)]) + @hassert :LatWithIsom 1 is_bijective(rBtoHB) + HBtorB = inv(rBtoHB) + d = modulus_bilinear_form(rB) + rBd = rescale(rB, 1//d) + rBtorBd = hom(rB, rBd, gens(rBd)) + HBtorBd = compose(HBtorB, rBtorBd) + rBdtorB = inv(rBtorBd) + rBdtoHB = compose(rBdtorB, rBtoHB) + @hassert :LatWithIsom 1 modulus_quadratic_form(rBd) == 2 + if p != 2 + OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rBd), _on_modular_matrix) + elseif spec + OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_quadratic(rBd), _on_modular_matrix_quad) + else + OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rBd), _on_modular_matrix) + end + OSBrB, _ = sub(OSB, union(gens(K), [OSB(OSBHBtoOHB\(g)) for g in gens(OHBrB)])) + return OSBrB +end + function _is_isometry(f::TorQuadModuleMor) !is_bijective(f) && return false for a in gens(domain(f)) @@ -964,3 +800,223 @@ function _is_isometry(f::TorQuadModuleMor) return true end +function _is_anti_isometry(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 + +function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) + SA = domain(SAinqA) + SB = domain(SBinqB) + p = elementary_divisors(SA)[1] + qA = codomain(SAinqA) + qB = codomain(SBinqB) + rA = _rho_functor(qA, p, l+1) + rB = _rho_functor(qB, p, l+1) + HA, _ = sub(qA, unique(TorQuadModuleElem[qA(QQ(p^l)*lift(a)) for a in gens(rA)])) + HB, _ = sub(qB, unique(TorQuadModuleElem[qB(QQ(p^l)*lift(b)) for b in gens(rB)])) + rAtoHA = hom(rA, HA, [HA(QQ(p^l)*lift(a)) for a in gens(rA)]) + rBtoHB = hom(rB, HB, [HB(QQ(p^l)*lift(b)) for b in gens(rB)]) + if p != 2 + phi_0 = _anti_isometry_bilinear(rA, rB) + elseif spec + ok, phi_0 = is_anti_isometric_with_anti_isometry(rA, rB) + @hassert :LatWithIsom 1 ok + else + phi_0 = _anti_isometry_bilinear(rA, rB) + end + phiHA, _ = sub(SB, [SB(lift(phi(rAtoHA\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 + @hassert :LatWithIsom 1 cover(_on_subgroup_automorphic(phiHA, g)) == cover(HB) + phi_1 = compose(phi, hom(g)) + HAinSA = hom(HA, SA, SA.(lift.(gens(HA)))) + HBinSB = hom(HB, SB, SB.(lift.(gens(HB)))) + OSBHB, _ = stabilizer(OSB, HBinSB) + g = one(OSBHB) + for f in OSBHB + g = f + phif = compose(phi_1, hom(f)) + psi = hom(rA, rB, [rBtoHB\(HBinSB\(phif(HAinSA(rAtoHA(a))))) for a in gens(rA)]) + if matrix(psi) == matrix(phi_0) + break + end + end + phig = compose(phi_1, hom(g)) + @hassert :LatWithIsom 1 cover(sub(SB, [SB(lift(phig(HAinSA(a)))) for a in HA])[1]) == cover(HB) + @hassert :LatWithIsom 1 matrix(hom(rA, rB, [rBtoHB\(HBinSB\(phig(HAinSA(rAtoHA(a))))) for a in gens(rA)])) == matrix(phi_0) + return phig +end + +function _is_even(T) + B = Hecke.gram_matrix_bilinear(T) + return is_empty(B) || all(is_zero, diagonal(B)) +end + +function _is_free(T, p, l) + return _is_even(_rho_functor(T, p, l-1)) && _is_even(_rho_functor(T, p, l+1)) +end + +function _anti_isometry_bilinear(r1, r2) + @hassert :LatWithIsom is_semi_regular(r1) === is_semi_regular(r2) === true + r2m = rescale(r2, -1) + r1N, r1tor1N = normal_form(r1) + r2mN, r2mtor2mN = normal_form(r2m) + r2tor2mN = compose(hom(r2, r2m, gens(r2m)), r2mtor2mN) + @hassert :LatWithIsom modulus_bilinear_form(r1N) == modulus_bilinear_form(r2mN) + @hassert :LatWithIsom Hecke.gram_matrix_bilinear(r1N) == Hecke.gram_matrix_bilinear(r2mN) + T = hom(r1N, r2mN, identity_matrix(ZZ, ngens(r1N))) + T = compose(r1tor1N, compose(T, inv(r2tor2mN))) + @hassert :LatWithIsom _is_anti_isometry(T) + return T +end + +############################################################################### +# +# Extra +# +############################################################################### + +function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadModule) + @hassert :LatWithIsom 1 is_one(basis_matrix(N)) + @hassert :LatWithIsom 1 is_one(basis_matrix(M)) + q = elementary_divisors(H)[end] + ok, p, _ = is_prime_power_with_data(q) + @hassert :LatWithIsom 1 ok + @hassert :LatWithIsom 1 is_elementary(M, p) + results = Tuple{ZLat, ZLat, ZLat}[] + GN, _ = image_in_Oq(N) + GM, _ = image_in_Oq(M) + qN = domain(GN) + qM = domain(GM) + + D, inj = direct_sum(qN, qM) + qNinD, qMinD = inj + OD = orthogonal_group(D) + VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) + subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) + filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) + @assert !isempty(subsN) + subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) + filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) + @assert !isempty(subsM) + + for H1 in subsN, H2 in subsM + ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) + @hassert :LatWithIsom 1 ok + + HNinqN, stabN = H1 + OHN = orthogonal_group(HN) + + HMinqM, stabM = H2 + OHM = orthogonal_group(HM) + + actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) + + actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) 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, _ = sub(OHM, stabNphi) + reps = double_cosets(OHM, stabNphi, imM) + @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" + for g in reps + g = representative(g) + phig = compose(phi, hom(g)) + _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] + z = zero_matrix(QQ, 0, degree(N)+degree(M)) + glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) + glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) + glue = FakeFmpqMat(glue) + _B = hnf(glue) + _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) + L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) + N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) + @hassert :LatWithIsom 1 genus(N) == genus(N2) + M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) + @hassert :LatWithIsom 1 genus(M) == genus(M2) + push!(results, (L, M2, N2)) + @vprint :LatWithIsom 1 "Gluing done\n" + GC.gc() + end + end + return results +end + +function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) + bool, p = is_elementary_with_prime(M) + @req bool "M must be elementary" + if check + @req length(genus_representatives(L)) == 1 "L must be unique in its genus" + end + results = Tuple{ZLat, ZLat, ZLat}[] + qL = rescale(discriminant_group(L), -1) + GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) + pL, _, nL = signature_tuple(L) + pM, _, nM = signature_tuple(M) + @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" + if ((pL, nL) == (pM, nM)) + if genus(M) != genus(L) + return false, results + else + return true, [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] + end + end + qM = discriminant_group(M) + D, inj, proj = biproduct(qM, qL) + qMinD, qLinD = inj + VL, VLinqL, _ = _get_V(id_hom(qL), minpoly(identity_matrix(QQ,1)), p) + for k in divisors(gcd(order(qM), order(VL))) + @info "Glue order: $(k)" + subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) + @info "$(length(subsL)) subgroup(s)" + subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), orthogonal_group(qM), k) + for H in subsL + HL = domain(H[1]) + _subsM = filter(HM -> is_anti_isometric_with_anti_isometry(domain(HM[1]), HL)[1], subsM) + isempty(_subsM) && continue + @info "Possible gluing" + HM = _subsM[1][1] + ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) + @assert ok + _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] + ext, _ = sub(D, D.(_glue)) + 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 = rescale(disc, -1) + !is_genus(disc, (pL-pM, nL-nM)) && continue + G = genus(disc, (pL-pM, nL-nM)) + !classification && return true, G + @info "We can glue: $G" + Ns = representatives(G) + @info "$(length(Ns)) possible orthogonal complement(s)" + Ns = lll.(Ns) + Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] + qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) + for N in Ns + append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) + if first + return results + end + GC.gc() + end + GC.gc() + end + end + @assert all(triple -> genus(triple[1]) == genus(L), results) + return (length(results) >0), results +end diff --git a/experimental/LatticesWithIsometry/src/enumeration.jl b/experimental/LatticesWithIsometry/src/enumeration.jl index 1abdee7b3c52..a8a4b1f990b4 100644 --- a/experimental/LatticesWithIsometry/src/enumeration.jl +++ b/experimental/LatticesWithIsometry/src/enumeration.jl @@ -48,7 +48,7 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::Hecke.IntegerUnion, even = true; pos::Int = -1) +function _find_L(pG::Int, nG::Int, r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::Hecke.IntegerUnion, even = true; pos::Int = -1) L = ZGenus[] if r == 0 && d == 1 return ZGenus[genus(Zlattice(gram = matrix(QQ, 0, 0, [])))] @@ -60,7 +60,7 @@ function _find_L(r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) else - for (s1,s2) in [(s,t) for s=0:r for t=0:r if s+t==r] + for (s1,s2) in [(s,t) for s=0:pG for t=0:nG if s+t==r] gen = Zgenera((s1,s2), d, even=even) filter!(G -> Hecke.divides(numerator(scale(G)), s)[1], gen) filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) @@ -164,8 +164,8 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) end ABr = direct_sum(Ar, Br) - - for i = 1:l + + for i = 0:l-1 s1 = symbol(ABr, i) s2 = symbol(Cp, i) if s1[2] != s2[2] @@ -221,7 +221,7 @@ function admissible_triples(G::ZGenus, p::Int64; 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" n = rank(G) - pG, _ = signature_pair(G) + pG, nG = signature_pair(G) if pA >= 0 @req pA <= pG "Wrong restrictions" if pB >= 0 @@ -248,8 +248,8 @@ function admissible_triples(G::ZGenus, p::Int64; pA::Int = -1, pB::Int = -1) m = min(ep, r1) D = _find_D(d, m, p) for (d1, dp) in D - L1 = _find_L(r1, d1, numerator(scale(G)), numerator(level(G)), p, even, pos = pA) - Lp = _find_L(rp, dp, numerator(scale(G)), numerator(level(G)), p, even, pos = pB) + L1 = _find_L(pG, nG, r1, d1, numerator(scale(G)), numerator(level(G)), p, even, pos = pA) + Lp = _find_L(pG, nG, rp, dp, numerator(scale(G)), numerator(level(G)), p, even, pos = pB) for (A, B) in [(A, B) for A in L1 for B in Lp] if is_admissible_triple(A, B, G, p) push!(L, (A, B)) @@ -402,7 +402,6 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) @vprint :LatWithIsom 1 "We have the different\n" ndE = d*inv(QQ(absolute_norm(DE)))^rk - println(ndE) detE = _ideals_of_norm(E, ndE) @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))\n" @@ -492,7 +491,6 @@ function _representative(t::Dict; check::Bool = true) DE = EabstoE(different(maximal_order(Eabs))) ndE = d*inv(QQ(absolute_norm(DE)))^rk - println(ndE) detE = _ideals_of_norm(E, ndE) @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))\n" From 8aa3d84b50be528c98ea4874ca05b58b09088d71 Mon Sep 17 00:00:00 2001 From: StevellM Date: Sat, 13 May 2023 15:38:49 +0200 Subject: [PATCH 50/76] fix --- experimental/LatticesWithIsometry/src/embeddings.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index 984021a3c6f4..9bbf0e5b58f1 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -687,8 +687,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, _glue = Vector{QQFieldElem}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(SA)] z = zero_matrix(QQ,0,degree(cover(D))) glue = reduce(vcat, [matrix(QQ, 1, degree(cover(D)),g) for g in _glue], init=z) - AB = lattice_in_same_ambient_space(cover(D), basis_matrix(A)*matrix(qAinD)) + lattice_in_same_ambient_space(cover(D), basis_matrix(B)*matrix(qBinD)) - glue = vcat(basis_matrix(AB), glue) + 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)) From 9356a81783f8a8ad1f6b3addad88b9f70436832f Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 18 May 2023 14:49:38 +0200 Subject: [PATCH 51/76] add README --- experimental/LatticesWithIsometry/README.md | 78 +++++++++++++++++++ .../LatticesWithIsometry/docs/docs.main | 0 .../LatticesWithIsometry/src/embeddings.jl | 3 +- .../LatticesWithIsometry/src/printings.jl | 4 +- 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 experimental/LatticesWithIsometry/docs/docs.main diff --git a/experimental/LatticesWithIsometry/README.md b/experimental/LatticesWithIsometry/README.md index e69de29bb2d1..82ebd3cf6ed7 100644 --- a/experimental/LatticesWithIsometry/README.md +++ b/experimental/LatticesWithIsometry/README.md @@ -0,0 +1,78 @@ +# Integer lattices with isometry + +This project is a complement to the code about *hermitian lattices* available +on Hecke. We aim here to connect Hecke and GAP to handle some algorithmic +methods regarding integer lattices with their isometries. In particular, +the integration of this code to Oscar is necessary to benefit all the +performance of GAP with respect to computations with groups and automorphisms in +general. + +## Content + +We introduce the new type `LatWithIsom` which parametrizes pairs $(L, f)$ where +$L$ is a non-denegerate $\mathbb{Z}$-lattice and $f$ is an isometry of $L$. One +of the main feature of this project is the enumeration of isomorphism classes of +pairs $(L, f)$ where $f$ is an isometry of finite order with at most two prime +divisors. The methods we resort to for this purpose are developed in the paper +[BH23]. + +We also provide some algorithms computing isomorphism classes of primitive +embeddings of even lattices following [Nikulin]. More precisely, the two +functions `primitive_embeddings_in_primary_lattice` and +`primitive_embeddings_of_elementary_lattice` offer, under certain conditions, +the possibility of obtaining representatives of such classes of primitive +embeddings. Note nonetheless that these functions are not efficient in the case +were the discriminant groups are large. + +## Status + +This project has been slightly tested on simple and known examples. It is +currently being tested on a larger scale to test its reliability. Moreover, +there are still computational bottlenecks due to non optimized algorithms. + +Among the possible improvements and extensions: +* Improve the lattice enumerations by upgrading the neighbor algorithms using + fast algorithms computing orbits of isotropic lines over finite fields; +* Improve the computation of the orthogonal group for definite lattices; +* Implement methods about for lattices with isometries of infinite order; +* Extend the methods for classification of primitive embeddings for the more + general case (knowing that we lose efficiency for large discriminant groups); +* Implement methods for all kinds of equivariant primitive extensions (not + necessarily admissibles); +* Improve some enumerations algorithms using theoretical results about types of + lattices with isometries of finite order; +* Import the methods for extending trivial discriminant actions on lattice whose + discriminant group is an abelian $p$-group. + +## Currently application of this project + +The project with initiated by S. Brandhorst and T. Hofmann for classifying +finite subgroups of automorphisms of K3 surfaces. Our current goal is to use +this code, and further extension of it, to classify finite subgroups of +automorphisms and birational transformations on *hyperkaehler manifolds*, which +are a higher dimensional analog of K3 surface. + +## Tutorials + + +## Notice to the user + +Since this project is still under development, feel free to try any feature and +report all the bugs you may have found. Any suggestions for improvements or +extensions are more than welcome. Refer to the next section to know who you +should contact and how. + +One may expect many things to vary within the next months: name of the functions, +available features, performance. This is due to the fact that the current +version of the code is still at an experimental stage. + +## Contact + +Please direct questions about this part of OSCAR to the following people: +* [Simon Brandhorst](https://www.math.uni-sb.de/ag/brandhorst/index.php?lang=en), +* [Tommy Hofmann](https://www.thofma.com/) +* [Stevell Muller](https://www.math.uni-sb.de/ag/brandhorst/index.php?option=com_content&view=article&id=26:muller-en-1&catid=18&lang=en&Itemid=114). + +You can ask questions in the [OSCAR Slack](https://www.oscar-system.org/community/#slack). + +Alternatively, you can [raise an issue on GitHub](https://github.com/oscar-system/Oscar.jl). diff --git a/experimental/LatticesWithIsometry/docs/docs.main b/experimental/LatticesWithIsometry/docs/docs.main new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index 9bbf0e5b58f1..b1258db216fb 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -206,13 +206,14 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu G::AutomorphismGroup{TorQuadModule}, ord::Hecke.IntegerUnion, f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq)), - l::Hecke.IntegerUnion = 0) + l::Hecke.IntegerUnion = -1) res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] V = domain(Vinq) q = codomain(Vinq) p = elementary_divisors(V)[1] pq, pqtoq = primary_part(q, p) + l = l < 0 ? valuation(order(pq), p) : l g = valuation(ord, p) if ord == 1 diff --git a/experimental/LatticesWithIsometry/src/printings.jl b/experimental/LatticesWithIsometry/src/printings.jl index a55d75955a22..0bb83d08689f 100644 --- a/experimental/LatticesWithIsometry/src/printings.jl +++ b/experimental/LatticesWithIsometry/src/printings.jl @@ -2,9 +2,9 @@ function Base.show(io::IO, ::MIME"text/plain", Lf::LatWithIsom) println(io, lattice(Lf)) n = order_of_isometry(Lf) if is_finite(n) - println(io, "with Isometry of finite order $n") + println(io, "with isometry of finite order $n") else - println(io, "with Isometry of infinite order") + println(io, "with isometry of infinite order") end println(io, "given by") print(IOContext(io, :compact => true), isometry(Lf)) From 00f7714703974bab1c8c53fa818360e68f229172 Mon Sep 17 00:00:00 2001 From: StevellM Date: Sun, 21 May 2023 15:32:15 +0200 Subject: [PATCH 52/76] start docs --- .../LatticesWithIsometry/docs/docs.main | 8 + .../docs/src/enumeration.md | 0 .../docs/src/introduction.md | 82 ++++++ .../docs/src/latwithisom.md | 257 ++++++++++++++++++ .../docs/src/primembed.md | 0 .../LatticesWithIsometry/src/printings.jl | 3 + 6 files changed, 350 insertions(+) create mode 100644 experimental/LatticesWithIsometry/docs/src/enumeration.md create mode 100644 experimental/LatticesWithIsometry/docs/src/introduction.md create mode 100644 experimental/LatticesWithIsometry/docs/src/latwithisom.md create mode 100644 experimental/LatticesWithIsometry/docs/src/primembed.md diff --git a/experimental/LatticesWithIsometry/docs/docs.main b/experimental/LatticesWithIsometry/docs/docs.main index e69de29bb2d1..ecd16f531959 100644 --- a/experimental/LatticesWithIsometry/docs/docs.main +++ b/experimental/LatticesWithIsometry/docs/docs.main @@ -0,0 +1,8 @@ +[ + "Lattices with isometry" => [ + "introduction.md", + "latwithisom.md", + "enumeration.m", + "primembed.md" + ] +] diff --git a/experimental/LatticesWithIsometry/docs/src/enumeration.md b/experimental/LatticesWithIsometry/docs/src/enumeration.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/experimental/LatticesWithIsometry/docs/src/introduction.md b/experimental/LatticesWithIsometry/docs/src/introduction.md new file mode 100644 index 000000000000..7796c579e5b8 --- /dev/null +++ b/experimental/LatticesWithIsometry/docs/src/introduction.md @@ -0,0 +1,82 @@ +```@meta +CurrentModue = Oscar +``` + +# Integer lattices and their isometries + +This project is a complement to the code about *hermitian lattices* available +on Hecke. We aim here to connect Hecke and GAP to handle some algorithmic +methods regarding integer lattices with their isometries. In particular, +the integration of this code to Oscar is necessary to benefit all the +performance of GAP with respect to computations with groups and automorphisms in +general. + +## Content + +We introduce the new type `LatWithIsom` which parametrizes pairs $(L, f)$ where +$L$ is a non-denegerate $\mathbb{Z}$-lattice and $f$ is an isometry of $L$. One +of the main feature of this project is the enumeration of isomorphism classes of +pairs $(L, f)$ where $f$ is an isometry of finite order with at most two prime +divisors. The methods we resort to for this purpose are developed in the paper +[BH23]. + +We also provide some algorithms computing isomorphism classes of primitive +embeddings of even lattices following [Nikulin]. More precisely, the two +functions `primitive_embeddings_in_primary_lattice` and +`primitive_embeddings_of_elementary_lattice` offer, under certain conditions, +the possibility of obtaining representatives of such classes of primitive +embeddings. Note nonetheless that these functions are not efficient in the case +were the discriminant groups are large. + +## Status + +This project has been slightly tested on simple and known examples. It is +currently being tested on a larger scale to test its reliability. Moreover, +there are still computational bottlenecks due to non optimized algorithms. + +Among the possible improvements and extensions: +* Improve the lattice enumerations by upgrading the neighbor algorithms using + fast algorithms computing orbits of isotropic lines over finite fields; +* Improve the computation of the orthogonal group for definite lattices; +* Implement methods about for lattices with isometries of infinite order; +* Extend the methods for classification of primitive embeddings for the more + general case (knowing that we lose efficiency for large discriminant groups); +* Implement methods for all kinds of equivariant primitive extensions (not + necessarily admissibles); +* Improve some enumerations algorithms using theoretical results about types of + lattices with isometries of finite order; +* Import the methods for extending trivial discriminant actions on lattice whose + discriminant group is an abelian $p$-group. + +## Currently application of this project + +The project with initiated by S. Brandhorst and T. Hofmann for classifying +finite subgroups of automorphisms of K3 surfaces. Our current goal is to use +this code, and further extension of it, to classify finite subgroups of +automorphisms and birational transformations on *hyperkaehler manifolds*, which +are a higher dimensional analog of K3 surface. + +## Tutorials + + +## Notice to the user + +Since this project is still under development, feel free to try any feature and +report all the bugs you may have found. Any suggestions for improvements or +extensions are more than welcome. Refer to the next section to know who you +should contact and how. + +One may expect many things to vary within the next months: name of the functions, +available features, performance. This is due to the fact that the current +version of the code is still at an experimental stage. + +## Contact + +Please direct questions about this part of OSCAR to the following people: +* [Simon Brandhorst](https://www.math.uni-sb.de/ag/brandhorst/index.php?lang=en), +* [Tommy Hofmann](https://www.thofma.com/) +* [Stevell Muller](https://www.math.uni-sb.de/ag/brandhorst/index.php?option=com_content&view=article&id=26:muller-en-1&catid=18&lang=en&Itemid=114). + +You can ask questions in the [OSCAR Slack](https://www.oscar-system.org/community/#slack). + +Alternatively, you can [raise an issue on GitHub](https://github.com/oscar-system/Oscar.jl). diff --git a/experimental/LatticesWithIsometry/docs/src/latwithisom.md b/experimental/LatticesWithIsometry/docs/src/latwithisom.md new file mode 100644 index 000000000000..cd1e3cbbd49c --- /dev/null +++ b/experimental/LatticesWithIsometry/docs/src/latwithisom.md @@ -0,0 +1,257 @@ +```@meta +CurrentModule = Oscar +``` + +# Lattice with isometry + +We call *lattice with isometry* any pair $(L, f)$ consisting of an integer +lattice $L$ together with an isometry $f \in O(L)$. We refer to the section +about integer lattices of the documentation for new users. + +On Oscar, such a pair is contained into a type called `LatWithIsom`: +```@docs +LatWithIsom +``` + +and it is seen as a quadruple $(L, f, f_a, n)$ where $n$ is the order of $f$ and +$f_a$ is an isometry of the ambient quadratic space of $L$ inducing $f$ on $L$. +Note that $f_a$ might not always be of order $n$. + +Given a lattice with isometry $(L, f)$, we provide the following accessors to the +elements of the previously described quadruple: + +```@docs +lattice(::LatWithIsom) +isometry(::LatWithIsom) +ambient_isometry(::LatWithIsom) +order_of_isometry(::LatWithIsom) +``` + +Note that for some computations, it is more convenient to work either with the +isometry of the lattice itself, or with an isometry of the ambient quadratic +space inducing it on the lattice. + +## Constructor + +For simplicity, we have gathered the main constructors under the same name +`lattice_with_isometry`. The user has then the choice on the parameters +depending on what they attend to do: + +```@docs +lattice_with_isometry(::ZZLat, ::QQMatrix) +lattice_with_isometry(::ZZLat) +``` + +By default, the first constructor will always check whether the entry matrix +defines an isometry of the lattice, or its ambient space. We recommend not to +disable this parameter to avoid any further issues. Both isometries of *finite +order* and *infinite order* are supported. + +### Examples + +```@repl 2 +using Oscar # hide +L = root_lattice(:E, 6); +f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; + -1 -2 -2 -2 -1 -1; + 0 1 0 0 0 0; + 1 0 0 0 0 0; + -1 -1 -1 0 0 -1; + 0 0 1 1 0 1]); +Lf = lattice_with_isometry(L, f) +``` + +## Attributes and first operations + +Given a lattice with isometry $Lf := (L, f)$, one can have access most of the +attributes of $L$ and $f$ by calling the similar function to the pair. For +instance, in order to know the genus of $L$, one can simply call `genus(Lf)`. +Here is a list of what are the current accessible attributes: + +```@docs +ambient_space(::LatWithIsom) +basis_matrix(::LatWithIsom) +charpoly(::LatWithIsom) +degree(::LatWithIsom) +det(::LatWithIsom) +discriminant(::LatWithIsom) +genus(::LatWithIsom) +gram_matrix(::LatWithIsom) +is_definite(::LatWithIsom) +is_even(::LatWithIsom) +is_integral(::LatWithIsom) +is_positive_definite(::LatWithIsom) +is_negative_definite(::LatWithIsom) +minimum(::LatWithIsom) +minpoly(::LatWithIsom) +norm(::LatWithIsom) +rank(::LatWithIsom) +rational_span(::LatWithIsom) +scale(::LatWithIsom) +signature_tuple(::LatWithIsom) +``` + +Similarly, some basic operations on $\mathbb Z$-lattices are available for +lattices with isometry. + +```@docs +biproduct(::Vector{LatWithIsom}) +direct_product(::Vector{LatWithIsom}) +direct_sum(::Vector{LatWithIsom}) +dual(::LatWithIsom) +lll(::LatWithIsom) +rescale(::LatWithIsom, ::RationalUnion) +``` + +## Type for finite order isometries + +Given a lattice with isometry $Lf := (L, f)$ where $f$ is of finite order $n$, +one can compute the *type* of $Lf$, which can be seen as an equivalent of the +*genus* used to classified single lattices. + +```@docs +type(::Lf) +``` + +Since determining whether two pairs of lattices with isometry are isomorphic is +a challenging task, one can perform a coarser comparison by looking at the type. +This set of data keep track of some local and global invariants of the pair $(L, +f)$ with the respect to the action of $f$ on $L$. + +```@docs +is_of_type(::LatWithIsom, t:Dict) +is_of_same_type(::LatWithIsom, ::LatWithIsom) +``` + +### Examples + +```@repl 2 +using Oscar # hide +L = root_lattice(:E, 6); +f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; + -1 -2 -2 -2 -1 -1; + 0 1 0 0 0 0; + 1 0 0 0 0 0; + -1 -1 -1 0 0 -1; + 0 0 1 1 0 1]); +Lf = lattice_with_isometry(L, f) +type(Lf) +``` + +Finally, if the minimal polynomial of $f$ is cyclotomic, i.e. the $n$-th +cyclotomic polynomial, then we say that the pair $(L, f)$ is *of hermitian +type*. The type of a lattice with isometry of hermitian type is called +*hermitian*. +These namings follow from the fact that, by the trace equivalence, one can +associate to the pair $(L, f)$ a hermitian lattice over the ring of integers of +the $n$-th cyclotomic field + +```@docs +is_of_hermitian_type(::LatWithIsom) +is_hermitian() +``` + +## Hermitian structure and trace equivalence + +As mentioned in the previous section, to a lattice with isometry $Lf := (L, f)$ +such that the minimal polynomial of $f$ is the $n$-th cyclotomic polynomial, one +can associate a hermitian lattice $\mathfrak{L}$ over the ring of integers of +the $n$-th cyclotomic field for which $Lf$ is the associated trace lattice (see +[`trace_lattice_with_isometry(::AbstractLat)`](@ref)). Hecke provides the tools +to perform the trace equivalence for lattices with isometry of hermitian type. + +```@docs +hermitian_structure(::LatWithIsom) +``` + +## Discriminant group + +Given an integral lattice with isometry $Lf := (L, f)$, if one denotes $D_L$ the +discriminant group of $L$, there exists a natural map $\pi\colon O(L) \to O(D_L)$ +sending any isometry to its induced action on the discriminant form of $L$. In +general, this map is neither injective nor surjective. If we denote $D_f := +\pi(f)$ then $\pi$ induces a map between centralizers $O(L, f)\to O(D_L, D_f)$. +Again, this induces map is in general neither injective nor surjective, and we +denote its image $G_{L,f}$. + +```@docs +discriminant_group(::LatWithIsom) +``` + +For simple cases as for definite lattices, $f$ being plus-or-minus the identity +or if the rank of $L$ is equal to the totient of the order of $f$ (in the +finite case), $G_{L,f}$ can be easily computed. The only other case which can +be currently handled is for lattices with isometry of hermitian type following +the *hermitian Miranda-Morisson theory* from [BH22]. This has been implemented +in this project and it can be indirectly used through the general following method: + +```@docs +image_centralizer_in_Oq(::LatWithIsom) +``` + +For an implementation of the regular Miranda-Morisson theory, we refer to the +function [`image_in_Oq(::ZZLat)`](@ref) which actually compute the image of +$\pi$ in both the definite and the indefinite case. + +We will see later in the section about enumeration of lattices with isometry +that one can compute $G_{L,f}$ in some particular cases arising from equivariant +primitive embeddings of lattices with isometries. + +## Kernel sublattices + +As for single integer lattices, it is possible to compute kernel sublattices of +some $\mathbb{Z}$-module homomorphisms. We provide here the possibility to +compute $\ker(p(f))$ as a sublattice of $L$ equipped with the induced action of +$f$, where $p$ is a polynomial with rational coefficients. + +```@docs +kernel_lattice(::LatWithIsom, ::Union{ZZPolyRingElem, QQPolyRingElem}) +kernel_lattice(::LatWithIsom, ::Integer) +``` + +Note that such sublattices are by definition primitive in $L$ since $L$ is +non-degenerate. As particular kernel sublattices of $L$, one can also compute +the so-called *invariant* and *coinvariant* lattices of $(L, f)$: + +```@docs +coinvariant_lattice(::LatWithIsom) +invariant_lattice(::LatWithIsom) +``` + +## Signatures + +We conclude this introduction to basic functionalities for lattices with +isometries by introducing a last invariant for lattices with isometry of +hermitain type $(L, f)$, called the *signatures*. These signatures are +are intrinsequely connected to the local archimedean invariants of the +hermitian structure associated to $(L, f)$ via the trace equivalence. + +```@docs +signatures(::LatWithIsom) +``` + +## Tips for users + +### Report an issue + +If you are working with some lattices with isometries, of type `LatWithIsom`, and +you need to report an issue, you can produce directly some lines of codes +helping to reconstruct your "non-working" example. We have implemented a method +`to_oscar` which prints 5 lines for reconstructing your example. + +```@repl 2 +using Oscar # hide +``` + +### Make the code more talkative + +Within the code, there are more hidden messages and testing which are disabled +by default. If you plan to experiment with the codes with your particular +examples, you may want to be able to detect some issues to be reported, as well +as knowing what the code is doing. Indeed, some functions might take time in +term of compilation but also computations. For this, you can enable these extras +tests and printings by seeting + +```julia +Oscar.set_lwi_level(2) +``` diff --git a/experimental/LatticesWithIsometry/docs/src/primembed.md b/experimental/LatticesWithIsometry/docs/src/primembed.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/experimental/LatticesWithIsometry/src/printings.jl b/experimental/LatticesWithIsometry/src/printings.jl index 0bb83d08689f..a90dc203c3d6 100644 --- a/experimental/LatticesWithIsometry/src/printings.jl +++ b/experimental/LatticesWithIsometry/src/printings.jl @@ -1,6 +1,8 @@ function Base.show(io::IO, ::MIME"text/plain", Lf::LatWithIsom) + io = AbstractAlgebra.pretty(io) println(io, lattice(Lf)) n = order_of_isometry(Lf) + print(io, AbstractAlgebra.Indent()) if is_finite(n) println(io, "with isometry of finite order $n") else @@ -8,6 +10,7 @@ function Base.show(io::IO, ::MIME"text/plain", Lf::LatWithIsom) end println(io, "given by") print(IOContext(io, :compact => true), isometry(Lf)) + print(io, AbstractAlgebra.Dedent()) end function Base.show(io::IO, Lf::LatWithIsom) From aa484667af1ec343758d45bca3f8dff4a314da5a Mon Sep 17 00:00:00 2001 From: StevellM Date: Sat, 27 May 2023 22:27:33 +0200 Subject: [PATCH 53/76] more improvements + renaming + better printing + first step of documentation --- experimental/LatticesWithIsometry/README.md | 2 +- .../docs/{docs.main => doc.main} | 0 .../docs/src/introduction.md | 10 +- .../docs/src/latwithisom.md | 109 ++-- .../src/LatticesWithIsometry.jl | 4 +- .../LatticesWithIsometry/src/embeddings.jl | 584 +++++++++--------- .../LatticesWithIsometry/src/enumeration.jl | 335 ++++++---- .../LatticesWithIsometry/src/exports.jl | 6 +- .../src/hermitian_miranda_morrison.jl | 74 +-- .../src/lattices_with_isometry.jl | 273 ++++---- .../LatticesWithIsometry/src/printings.jl | 6 +- .../LatticesWithIsometry/src/types.jl | 12 +- 12 files changed, 757 insertions(+), 658 deletions(-) rename experimental/LatticesWithIsometry/docs/{docs.main => doc.main} (100%) diff --git a/experimental/LatticesWithIsometry/README.md b/experimental/LatticesWithIsometry/README.md index 82ebd3cf6ed7..f929e09246a0 100644 --- a/experimental/LatticesWithIsometry/README.md +++ b/experimental/LatticesWithIsometry/README.md @@ -9,7 +9,7 @@ general. ## Content -We introduce the new type `LatWithIsom` which parametrizes pairs $(L, f)$ where +We introduce the new type `ZZLatWithIsom` which parametrizes pairs $(L, f)$ where $L$ is a non-denegerate $\mathbb{Z}$-lattice and $f$ is an isometry of $L$. One of the main feature of this project is the enumeration of isomorphism classes of pairs $(L, f)$ where $f$ is an isometry of finite order with at most two prime diff --git a/experimental/LatticesWithIsometry/docs/docs.main b/experimental/LatticesWithIsometry/docs/doc.main similarity index 100% rename from experimental/LatticesWithIsometry/docs/docs.main rename to experimental/LatticesWithIsometry/docs/doc.main diff --git a/experimental/LatticesWithIsometry/docs/src/introduction.md b/experimental/LatticesWithIsometry/docs/src/introduction.md index 7796c579e5b8..f6113d19aa81 100644 --- a/experimental/LatticesWithIsometry/docs/src/introduction.md +++ b/experimental/LatticesWithIsometry/docs/src/introduction.md @@ -13,7 +13,7 @@ general. ## Content -We introduce the new type `LatWithIsom` which parametrizes pairs $(L, f)$ where +We introduce the new type `ZZLatWithIsom` which parametrizes pairs $(L, f)$ where $L$ is a non-denegerate $\mathbb{Z}$-lattice and $f$ is an isometry of $L$. One of the main feature of this project is the enumeration of isomorphism classes of pairs $(L, f)$ where $f$ is an isometry of finite order with at most two prime @@ -23,7 +23,7 @@ divisors. The methods we resort to for this purpose are developed in the paper We also provide some algorithms computing isomorphism classes of primitive embeddings of even lattices following [Nikulin]. More precisely, the two functions `primitive_embeddings_in_primary_lattice` and -`primitive_embeddings_of_elementary_lattice` offer, under certain conditions, +`primitive_embeddings_of_primary_lattice` offer, under certain conditions, the possibility of obtaining representatives of such classes of primitive embeddings. Note nonetheless that these functions are not efficient in the case were the discriminant groups are large. @@ -42,11 +42,9 @@ Among the possible improvements and extensions: * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); * Implement methods for all kinds of equivariant primitive extensions (not - necessarily admissibles); -* Improve some enumerations algorithms using theoretical results about types of + necessarily admissible); +* Improve some enumeration algorithms using theoretical results about types of lattices with isometries of finite order; -* Import the methods for extending trivial discriminant actions on lattice whose - discriminant group is an abelian $p$-group. ## Currently application of this project diff --git a/experimental/LatticesWithIsometry/docs/src/latwithisom.md b/experimental/LatticesWithIsometry/docs/src/latwithisom.md index cd1e3cbbd49c..ea6dbca76f44 100644 --- a/experimental/LatticesWithIsometry/docs/src/latwithisom.md +++ b/experimental/LatticesWithIsometry/docs/src/latwithisom.md @@ -8,9 +8,9 @@ We call *lattice with isometry* any pair $(L, f)$ consisting of an integer lattice $L$ together with an isometry $f \in O(L)$. We refer to the section about integer lattices of the documentation for new users. -On Oscar, such a pair is contained into a type called `LatWithIsom`: +On Oscar, such a pair is contained into a type called `ZZLatWithIsom`: ```@docs -LatWithIsom +ZZLatWithIsom ``` and it is seen as a quadruple $(L, f, f_a, n)$ where $n$ is the order of $f$ and @@ -21,10 +21,10 @@ Given a lattice with isometry $(L, f)$, we provide the following accessors to th elements of the previously described quadruple: ```@docs -lattice(::LatWithIsom) -isometry(::LatWithIsom) -ambient_isometry(::LatWithIsom) -order_of_isometry(::LatWithIsom) +lattice(::ZZLatWithIsom) +isometry(::ZZLatWithIsom) +ambient_isometry(::ZZLatWithIsom) +order_of_isometry(::ZZLatWithIsom) ``` Note that for some computations, it is more convenient to work either with the @@ -69,38 +69,38 @@ instance, in order to know the genus of $L$, one can simply call `genus(Lf)`. Here is a list of what are the current accessible attributes: ```@docs -ambient_space(::LatWithIsom) -basis_matrix(::LatWithIsom) -charpoly(::LatWithIsom) -degree(::LatWithIsom) -det(::LatWithIsom) -discriminant(::LatWithIsom) -genus(::LatWithIsom) -gram_matrix(::LatWithIsom) -is_definite(::LatWithIsom) -is_even(::LatWithIsom) -is_integral(::LatWithIsom) -is_positive_definite(::LatWithIsom) -is_negative_definite(::LatWithIsom) -minimum(::LatWithIsom) -minpoly(::LatWithIsom) -norm(::LatWithIsom) -rank(::LatWithIsom) -rational_span(::LatWithIsom) -scale(::LatWithIsom) -signature_tuple(::LatWithIsom) +ambient_space(::ZZLatWithIsom) +basis_matrix(::ZZLatWithIsom) +charpoly(::ZZLatWithIsom) +degree(::ZZLatWithIsom) +det(::ZZLatWithIsom) +discriminant(::ZZLatWithIsom) +genus(::ZZLatWithIsom) +gram_matrix(::ZZLatWithIsom) +is_definite(::ZZLatWithIsom) +is_even(::ZZLatWithIsom) +is_integral(::ZZLatWithIsom) +is_positive_definite(::ZZLatWithIsom) +is_negative_definite(::ZZLatWithIsom) +minimum(::ZZLatWithIsom) +minpoly(::ZZLatWithIsom) +norm(::ZZLatWithIsom) +rank(::ZZLatWithIsom) +rational_span(::ZZLatWithIsom) +scale(::ZZLatWithIsom) +signature_tuple(::ZZLatWithIsom) ``` Similarly, some basic operations on $\mathbb Z$-lattices are available for lattices with isometry. ```@docs -biproduct(::Vector{LatWithIsom}) -direct_product(::Vector{LatWithIsom}) -direct_sum(::Vector{LatWithIsom}) -dual(::LatWithIsom) -lll(::LatWithIsom) -rescale(::LatWithIsom, ::RationalUnion) +biproduct(::Vector{ZZLatWithIsom}) +direct_product(::Vector{ZZLatWithIsom}) +direct_sum(::Vector{ZZLatWithIsom}) +dual(::ZZLatWithIsom) +lll(::ZZLatWithIsom) +rescale(::ZZLatWithIsom, ::RationalUnion) ``` ## Type for finite order isometries @@ -115,12 +115,12 @@ type(::Lf) Since determining whether two pairs of lattices with isometry are isomorphic is a challenging task, one can perform a coarser comparison by looking at the type. -This set of data keep track of some local and global invariants of the pair $(L, -f)$ with the respect to the action of $f$ on $L$. +This set of data keeps track of some local and global invariants of the pair $(L, +f)$ with respect to the action of $f$ on $L$. ```@docs -is_of_type(::LatWithIsom, t:Dict) -is_of_same_type(::LatWithIsom, ::LatWithIsom) +is_of_type(::ZZLatWithIsom, t:Dict) +is_of_same_type(::ZZLatWithIsom, ::ZZLatWithIsom) ``` ### Examples @@ -141,13 +141,14 @@ type(Lf) Finally, if the minimal polynomial of $f$ is cyclotomic, i.e. the $n$-th cyclotomic polynomial, then we say that the pair $(L, f)$ is *of hermitian type*. The type of a lattice with isometry of hermitian type is called -*hermitian*. +*hermitian*. + These namings follow from the fact that, by the trace equivalence, one can associate to the pair $(L, f)$ a hermitian lattice over the ring of integers of the $n$-th cyclotomic field ```@docs -is_of_hermitian_type(::LatWithIsom) +is_of_hermitian_type(::ZZLatWithIsom) is_hermitian() ``` @@ -161,7 +162,7 @@ the $n$-th cyclotomic field for which $Lf$ is the associated trace lattice (see to perform the trace equivalence for lattices with isometry of hermitian type. ```@docs -hermitian_structure(::LatWithIsom) +hermitian_structure(::ZZLatWithIsom) ``` ## Discriminant group @@ -171,22 +172,22 @@ discriminant group of $L$, there exists a natural map $\pi\colon O(L) \to O(D_L) sending any isometry to its induced action on the discriminant form of $L$. In general, this map is neither injective nor surjective. If we denote $D_f := \pi(f)$ then $\pi$ induces a map between centralizers $O(L, f)\to O(D_L, D_f)$. -Again, this induces map is in general neither injective nor surjective, and we +Again, this induced map is in general neither injective nor surjective, and we denote its image $G_{L,f}$. ```@docs -discriminant_group(::LatWithIsom) +discriminant_group(::ZZLatWithIsom) ``` For simple cases as for definite lattices, $f$ being plus-or-minus the identity or if the rank of $L$ is equal to the totient of the order of $f$ (in the finite case), $G_{L,f}$ can be easily computed. The only other case which can be currently handled is for lattices with isometry of hermitian type following -the *hermitian Miranda-Morisson theory* from [BH22]. This has been implemented +the *hermitian Miranda-Morisson theory* from [BH23]. This has been implemented in this project and it can be indirectly used through the general following method: ```@docs -image_centralizer_in_Oq(::LatWithIsom) +image_centralizer_in_Oq(::ZZLatWithIsom) ``` For an implementation of the regular Miranda-Morisson theory, we refer to the @@ -205,8 +206,8 @@ compute $\ker(p(f))$ as a sublattice of $L$ equipped with the induced action of $f$, where $p$ is a polynomial with rational coefficients. ```@docs -kernel_lattice(::LatWithIsom, ::Union{ZZPolyRingElem, QQPolyRingElem}) -kernel_lattice(::LatWithIsom, ::Integer) +kernel_lattice(::ZZLatWithIsom, ::Union{ZZPolyRingElem, QQPolyRingElem}) +kernel_lattice(::ZZLatWithIsom, ::Integer) ``` Note that such sublattices are by definition primitive in $L$ since $L$ is @@ -214,27 +215,27 @@ non-degenerate. As particular kernel sublattices of $L$, one can also compute the so-called *invariant* and *coinvariant* lattices of $(L, f)$: ```@docs -coinvariant_lattice(::LatWithIsom) -invariant_lattice(::LatWithIsom) +coinvariant_lattice(::ZZLatWithIsom) +invariant_lattice(::ZZLatWithIsom) ``` ## Signatures -We conclude this introduction to basic functionalities for lattices with -isometries by introducing a last invariant for lattices with isometry of +We conclude this introduction about standard functionalities for lattices with +isometry by introducing a last invariant for lattices with isometry of hermitain type $(L, f)$, called the *signatures*. These signatures are are intrinsequely connected to the local archimedean invariants of the hermitian structure associated to $(L, f)$ via the trace equivalence. ```@docs -signatures(::LatWithIsom) +signatures(::ZZLatWithIsom) ``` ## Tips for users ### Report an issue -If you are working with some lattices with isometries, of type `LatWithIsom`, and +If you are working with some lattices with isometry, of type `ZZLatWithIsom`, and you need to report an issue, you can produce directly some lines of codes helping to reconstruct your "non-working" example. We have implemented a method `to_oscar` which prints 5 lines for reconstructing your example. @@ -246,11 +247,11 @@ using Oscar # hide ### Make the code more talkative Within the code, there are more hidden messages and testing which are disabled -by default. If you plan to experiment with the codes with your particular +by default. If you plan to experiment with the codes with your favourite examples, you may want to be able to detect some issues to be reported, as well as knowing what the code is doing. Indeed, some functions might take time in -term of compilation but also computations. For this, you can enable these extras -tests and printings by seeting +term of compilation but also computations. For this, you can enable these extra +tests and printings by setting: ```julia Oscar.set_lwi_level(2) diff --git a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl index 22ec351d39b3..b51dab6dc466 100644 --- a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl +++ b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl @@ -1,6 +1,6 @@ function set_lwi_level(k::Int) - set_assertion_level(:LatWithIsom, k) - set_verbosity_level(:LatWithIsom, k) + set_assertion_level(:ZZLatWithIsom, k) + set_verbosity_level(:ZZLatWithIsom, k) end include("exports.jl") diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index b1258db216fb..62d8ec2d981c 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -16,7 +16,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu 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(v -> AinD(v[1])*BinD(v[2]) == 0, Hecke.cartesian_product_iterator([gens(A), gens(B)], inplace=true)) + @assert all(v -> AinD(v[1])*BinD(v[2]) == 0, Hecke.cartesian_product_iterator([gens(A), gens(B)], inplace=false)) OD = orthogonal_group(D) OA = orthogonal_group(A) OB = orthogonal_group(B) @@ -87,19 +87,21 @@ function _get_V(fq, mu, p) fpV_mu = evaluate(mu, fpV) K, _ = kernel(fpV_mu) Ktoq = hom(K, q, [q(lift(a)) for a in gens(K)]) - @hassert :LatWithIsom 1 is_injective(Ktoq) + @hassert :ZZLatWithIsom 1 is_injective(Ktoq) fK = restrict_endomorphism(fq, Ktoq) return K, Ktoq, fK end # This is the rho function as defined in Definition 4.8 of BH23. function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) - Nv = cover(q) - N = relations(q) + pq, pqtoq = primary_part(q, p) + pq = rescale(pq, QQ(p)^(l-1)) + Nv = cover(pq) + N = relations(pq) if l == 0 Gl = N Gm = intersect(1//p*N, Nv) - rholN = torsion_quadratic_module(Gl, p*Gm) + rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) else k = l-1 m = l+1 @@ -107,13 +109,9 @@ function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) Gl = intersect((1//(p^l))*N, Nv) Gm = intersect((1//(p^m))*N, Nv) B = Gk+p*Gm - rholN = torsion_quadratic_module(Gl, B) - end - rho = rescale(rholN, QQ(p)^(l-1)) - if order(rho) == 1 - rho = torsion_quadratic_module(cover(rho), relations(rho), modulus = QQ(1), modulus_qf = QQ(2)) + rholN = torsion_quadratic_module(Gl, B, modulus = QQ(1), modulus_qf = QQ(2)) end - return rho + return rholN end ############################################################################## @@ -129,7 +127,7 @@ end function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) - gene = TorQuadModuleElem[g(t) for t in gens(T)] + gene = TorQuadModuleElem[g(q(lift(t))) for t in gens(T)] return sub(q, gene)[1] end @@ -159,34 +157,27 @@ function _cokernel_as_Fp_vector_space(HinV, p, f) end H = domain(HinV) - HS, HStoH = snf(H) - HStoV = compose(HStoH, HinV) V = codomain(HinV) - VS, VStoV = snf(V) - VtoVS = inv(VStoV) - - n = ngens(VS) + n = ngens(V) F = GF(p) - MVS = matrix(compose(VStoV, compose(f, VtoVS))) Vp = VectorSpace(F, n) - function _VstoVp(x::TorQuadModuleElem) + function _VtoVp(x::TorQuadModuleElem) v = data(x).coeff return Vp(vec(collect(v))) end - function _VptoVs(v::ModuleElem{FpFieldElem}) + function _VptoV(v::ModuleElem{FpFieldElem}) x = lift.(v.v) - return VS(vec(collect(x))) + return sum([x[i]*V[i] for i in 1:ngens(V)]) end - VStoVp = Hecke.MapFromFunc(_VstoVp, _VptoVs, VS, Vp) - VtoVp = compose(VtoVS, VStoVp) - subgene = elem_type(Vp)[VtoVp(HStoV(a)) for a in gens(HS)] + VtoVp = Hecke.MapFromFunc(_VtoVp, _VptoV, V, Vp) + subgene = elem_type(Vp)[VtoVp(HinV(a)) for a in gens(H)] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) - fVp = change_base_ring(F, MVS) + fVp = change_base_ring(F, matrix(f)) ok, fQp = can_solve_with_solution(VptoQp.matrix, fVp*VptoQp.matrix) @assert ok @@ -221,11 +212,15 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu _, triv = sub(codomain(Vinq), TorQuadModuleElem[]) push!(res, (triv, G)) return res + elseif ord == order(V) + push!(res, (Vinq, G)) + return res end - @hassert :LatWithIsom 1 all(a -> has_preimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) + all(a -> has_preimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) || return res H0, H0inq = sub(q, [q(lift((p^l)*a)) for a in gens(pq)]) H0inV = hom(H0, V, [V(lift(a)) for a in gens(H0)]) + @hassert :ZZLatWithIsom 1 is_invariant(f, H0inV) if order(H0) >= ord order(H0) > ord && (return res) @@ -233,11 +228,6 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end - if ord == order(V) - push!(res, (Vinq, G)) - return res - end - Qp, VtoVp, VptoQp, fQp = _cokernel_as_Fp_vector_space(H0inV, p, f) Vp = codomain(VtoVp) @@ -249,10 +239,11 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(act_GV_Qp) - @hassert :LatWithIsom 1 fQp in MGp - MGptoGV = hom(MGp, GV, gens(GV), check = false) + @hassert :ZZLatWithIsom 1 fQp in MGp + GVtoMGp = hom(GV, MGp, MGp.(act_GV_Qp), check = false) + GtoMGp = compose(GtoGV, GVtoMGp) - @hassert :LatWithIsom g-ngens(snf(abelian_group(H0))[1]) < dim(Qp) + @hassert :ZZLatWithIsom g-ngens(snf(abelian_group(H0))[1]) < dim(Qp) F = base_ring(Qp) k, K = kernel(VptoQp.matrix, side = :left) @@ -268,17 +259,18 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu end end - gene_orb_in_Qp = elem_type(Qp)[Qp(vec(collect(i(v).v))) for v in gens(orb)] + gene_orb_in_Qp = elem_type(Qp)[Qp(vec(collect(i(v).v))) for v in gens(domain(i))] gene_orb_in_Vp = elem_type(Vp)[preimage(VptoQp, v) for v in gene_orb_in_Qp] gene_submod_in_Vp = vcat(gene_orb_in_Vp, gene_H0p) gene_submod_in_V = TorQuadModuleElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] gene_submod_in_q = TorQuadModuleElem[image(Vinq, v) for v in gene_submod_in_V] orbq, orbqinq = sub(q, gene_submod_in_q) - @hassert :LatWithIsom 1 order(orbq) == ord + @hassert :ZZLatWithIsom 1 order(orbq) == ord - stabq = AutomorphismGroup{TorQuadModule}[GtoGV\(MGptoGV(s)) for s in gens(stab)] + stabq = AutomorphismGroupElem{TorQuadModule}[GtoMGp\(s) for s in gens(stab)] stabq, _ = sub(G, union(stabq, satV)) + @hassert :ZZLatWithIsom 1 is_invariant(stabq, orbqinq) push!(res, (orbqinq, stabq)) @label non_fixed end @@ -308,7 +300,7 @@ function _subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{T return res end -function _classes_automorphic_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) +function _classes_automorphic_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) sors = _subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) end @@ -328,26 +320,37 @@ end # # We follow the second definition of Nikulin, i.e. we classify up to the actions # of O(M) and O(N). -function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadModule; GN = nothing, GM = nothing) - @hassert :LatWithIsom 1 is_one(basis_matrix(N)) - @hassert :LatWithIsom 1 is_one(basis_matrix(M)) - results = Tuple{ZLat, ZLat, ZLat}[] - GN = GN === nothing ? image_in_Oq(N)[1] : GN - GM = GM === nothing ? image_in_Oq(M)[1] : GM +function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQuadModule, el::Bool; first::Bool = false) + @hassert :ZZLatWithIsom 1 is_one(basis_matrix(N)) + @hassert :ZZLatWithIsom 1 is_one(basis_matrix(M)) + results = Tuple{ZZLat, ZZLat, ZZLat}[] + GN, _ = image_in_Oq(N) + GM, _ = image_in_Oq(M) qN = domain(GN) qM = domain(GM) D, inj = direct_sum(qN, qM) qNinD, qMinD = inj OD = orthogonal_group(D) - subsN = _classes_automorphic_subgroups(GN, rescale(H, -1)) - @hassert :LatWithIsom 1 !isempty(subsN) - subsM = _classes_automorphic_subgroups(GM, H) - @hassert :LatWithIsom 1 !isempty(subsM) + if el + VN, VNinqN, _ = _get_V(id_hom(qN), minpoly(identity_matrix(QQ,1)), p) + subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) + filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) + @assert !isempty(subsN) + VM, VMinqM, _ = _get_V(id_hom(qM), minpoly(identity_matrix(QQ, 1)), p) + subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(VMinqM, GM, p) + filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) + @assert !isempty(subsM) + else + subsN = _classes_automorphic_subgroups(GN, rescale(H, -1)) + @hassert :ZZLatWithIsom 1 !isempty(subsN) + subsM = _classes_automorphic_subgroups(GM, H) + @hassert :ZZLatWithIsom 1 !isempty(subsM) + end for H1 in subsN, H2 in subsM ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) - @hassert :LatWithIsom 1 ok + @hassert :ZZLatWithIsom 1 ok HNinqN, stabN = H1 HN = domain(HNinqN) @@ -365,7 +368,7 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) - @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" + @vprint :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" for g in reps g = representative(g) @@ -379,74 +382,95 @@ function _isomorphism_classes_primitive_extensions(N::ZLat, M::ZLat, H::TorQuadM _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) L = Hecke.lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) - @hassert :LatWithIsom 1 genus(N) == genus(N2) + @hassert :ZZLatWithIsom 1 genus(N) == genus(N2) M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) - @hassert :LatWithIsom 1 genus(M) == genus(M2) + @hassert :ZZLatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) - @vprint :LatWithIsom 1 "Gluing done\n" + @vprint :ZZLatWithIsom 1 "Gluing done\n" GC.gc() + first && return results end end return results end @doc raw""" - primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat) - -> Vector{Tuple{ZLat, ZLat, ZLat}} + primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; + classification::Bool = true, + first::Bool = false, + check::Bool = true) + -> Vector{Tuple{ZZLat, ZZLat, ZZLat}} Given a `p`-primary lattice `L`, unique in its genus, and a lattice `M`, -compute representatives for all isomorphism classes of primitive embeddings -of `M` in `L` up to the actions of $\bar{O}(M)$ and $\bar{O}(L)$. +return whether `M` embeds primitively in `L`. + +If `classification` is set to `true`, compute representatives for all +isomorphism classes of primitive embeddings of `M` in `L` up to the +actions of $\bar{O}(M)$ and $\bar{O}(L)$. + +If `first` is set to `true`, return the first primitive embedding of +`M` in `L` if it exists. -The output is given in terms of triples `(L', M', N')` where `L'` is -isometric to `L`, `M'` is a sublattice of `L'` isometric to `M` and -`N'` is the orthogonal complement of `M'` in `L'`. +In both the previous cases, the output is given in terms of triples +`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice +of `L'` isometric to `M` and `N'` is the orthogonal complement of +`M'` in `L'`. """ -function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; GL::AutomorphismGroup{TorQuadModule} = image_in_Oq(L)[1], GM::AutomorphismGroup{TorQuadModule} = image_in_Oq(M)[1], check::Bool = false) +function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Bool = true, first::Bool = false, check::Bool = false) pL, _, nL = signature_tuple(L) pM, _, nM = signature_tuple(M) @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" @req rank(M) < rank(L) "M must be of smaller rank than L" + bool, p = is_primary_with_prime(L) @req bool "L must be unimodular or primary" el = is_elementary(L, p) + if check @req length(genus_representatives(L)) == 1 "L must be unique in its genus" end - M = Zlattice(gram = gram_matrix(M)) + + results = Tuple{ZZLat, ZZLat, ZZLat}[] + + M = integer_lattice(gram = gram_matrix(M)) qM = discriminant_group(M) - ok, phi = is_isometric_with_isometry(qM, domain(GM)) - @assert ok - iphi = inv(phi) - GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(compose(phi, compose(hom(g), iphi))) for g in unique(gens(GM))]) - results = Tuple{ZLat, ZLat, ZLat}[] - qL = rescale(discriminant_group(L), -1) - GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) + GM, _ = image_in_Oq(M) + + + GL, _ = image_in_Oq(rescale(L, -1)) + qL = domain(GL) + D, inj, proj = biproduct(qM, qL) qMinD, qLinD = inj + if el VM, VMinqM, _ = _get_V(id_hom(qM), minpoly(identity_matrix(QQ,1)), p) else VM, VMinqM = primary_part(qM, p) end + for k in divisors(gcd(order(VM), order(qL))) - @vprint :LatWithIsom 1 "Glue order: $(k)\n" + @vprint :ZZLatWithIsom 1 "Glue order: $(k)\n" + if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), GL, k) else subsL = _subgroups_orbit_representatives_and_stabilizers(GL, order = k) end - @vprint :LatWithIsom 1 "$(length(subsL)) subgroup(s)\n" + + @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" for H in subsL HL = domain(H[1]) it = subgroups(abelian_group(VM), order = order(HL)) subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1])))))[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue - @vprint :LatWithIsom 1 "Possible gluings\n" + + @vprint :ZZLatWithIsom 1 "Possible gluings\n" HM = subsM[1] ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) - @hassert :LatWithIsom 1 ok + @hassert :ZZLatWithIsom 1 ok + _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) @@ -454,22 +478,136 @@ function primitive_embeddings_in_primary_lattice(L::ZLat, M::ZLat; GL::Automorph modulus_qf = modulus_quadratic_form(perp)) disc2 = rescale(disc, -1) !is_genus(disc2, (pL-pM, nL-nM)) && continue + + !classification && return true, results + G = genus(disc2, (pL-pM, nL-nM)) - @vprint :LatWithIsom 1 "We can glue: $G\n" + @vprint :ZZLatWithIsom 1 "We can glue: $G\n" Ns = representatives(G) - @vprint :LatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" + @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" Ns = lll.(Ns) - Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] + Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - append!(results, _isomorphism_classes_primitive_extensions(N, M, GM = GM, qM2)) + temp = _isomorphism_classes_primitive_extensions(N, M, qM2, el, first=first) + if !is_empty(temp) + first && return true, temp + append!(results, temps) + end + GC.gc() + end + end + end + @hassert :ZZLatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) + return (length(results) > 0), results +end + +@doc raw""" + primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; + classification::Bool = true, + first::Bool = false, + check::Bool = true) + -> Vector{Tuple{ZZLat, ZZLat, ZZLat}} + +Given a lattice `L`, unique in its genus, and a `p`-primary lattice `M`, +return whether `M` embeds primitively in `L`. + +If `classification` is set to `true`, compute representatives for all +isomorphism classes of primitive embeddings of `M` in `L` up to the +actions of $\bar{O}(M)$ and $\bar{O}(L)$. + +If `first` is set to `true`, return the first primitive embedding of +`M` in `L` if it exists. + +In both the previous cases, the output is given in terms of triples +`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice +of `L'` isometric to `M` and `N'` is the orthogonal complement of +`M'` in `L'`. +""" +function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Bool = true, first::Bool = false, check::Bool = false) + pL, _, nL = signature_tuple(L) + pM, _, nM = signature_tuple(M) + @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" + @req rank(M) < rank(L) "M must be of smaller rank than L" + + bool, p = is_primary_with_prime(M) + @req bool "M must be unimodular or primary" + el = is_elementary(M, p) + + if check + @req length(genus_representatives(L)) == 1 "L must be unique in its genus" + end + + results = Tuple{ZZLat, ZZLat, ZZLat}[] + + M = integer_lattice(gram = gram_matrix(M)) + qM = discriminant_group(M) + GM, _ = image_in_Oq(M) + + GL, _ = image_in_Oq(rescale(L, -1)) + qL = domain(GL) + + D, inj, proj = biproduct(qM, qL) + qMinD, qLinD = inj + + if el + VL, VLinqL, _ = _get_V(id_hom(qL), minpoly(identity_matrix(QQ,1)), p) + else + VL, VLinqL = primary_part(qL, p) + end + + for k in divisors(gcd(order(qM), order(VL))) + @info "Glue order: $(k)" + + if el + subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) + else + subsL = _subgroups_orbit_representatives_and_stabilizers(restrict_automorphism_group(GL, VLinqL), order = k) + end + + @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" + for H in subsL + HL = domain(H[1]) + it = subgroups(abelian_group(qM), order = order(HL)) + subsM = [sub(qM, (j[2].(gens(j[1]))))[2] for j in it] + filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) + isempty(subsM) && continue + + @vprint :ZZLatWithIsom 1 "Possible gluings\n" + HM = subsM[1] + ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) + @hassert :ZZLatWithIsom 1 ok + + _glue = [lift(qMinD(HM(g))) + lift(qLinD(VLinqL(H[1](phi(g))))) for g in gens(domain(HM))] + ext, _ = sub(D, D.(_glue)) + 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 = rescale(disc, -1) + !is_genus(disc, (pL-pM, nL-nM)) && continue + + !classification && return true, results + + G = genus(disc, (pL-pM, nL-nM)) + @info "We can glue: $G" + Ns = representatives(G) + @info "$(length(Ns)) possible orthogonal complement(s)" + Ns = lll.(Ns) + Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] + qM2, _ = orthogonal_submodule(qM, domain(HM)) + for N in Ns + temp = _isomorphism_classes_primitive_extensions(N, M, qM2, el, first=first) + if length(temp) > 0 + first && return true, temp + append!(results, temp) + end GC.gc() end GC.gc() end end - @hassert :LatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) - return results + @assert all(triple -> genus(triple[1]) == genus(L), results) + return (length(results) >0), results end #################################################################################### @@ -479,12 +617,12 @@ end #################################################################################### @doc raw""" - admissible_equivariant_primitive_extensions(Afa::LatWithIsom, - Bfb::LatWithIsom, - Cfc::LatWithIsom, + admissible_equivariant_primitive_extensions(Afa::ZZLatWithIsom, + Bfb::ZZLatWithIsom, + Cfc::ZZLatWithIsom, p::Integer, q::Integer = p; check=true) - -> Vector{LatWithIsom} + -> Vector{ZZLatWithIsom} Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a prime number `p`, such that `(A, B, C)` is `p`-admissible, return a set of @@ -506,13 +644,13 @@ of the associated isometries must be irreducible (and relatively coprime). See Algorithm 2 of [BH22]. """ -function admissible_equivariant_primitive_extensions(A::LatWithIsom, - B::LatWithIsom, - C::LatWithIsom, +function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, + B::ZZLatWithIsom, + C::ZZLatWithIsom, p::Integer, q::Integer = p; check=true) # requirement for the algorithm of BH22 - @req is_prime(p) "p must be a prime number" + @req is_prime(p) && is_prime(q) "p and q must be a prime number" amb = ambient_space(A) === ambient_space(B) === ambient_space(C) if check @@ -520,14 +658,14 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, chiA = minpoly(A) chiB = minpoly(parent(chiA), isometry(B)) @req gcd(chiA, chiB) == 1 "Minimal irreducible polynomials must be relatively coprime" - @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple" + @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple with respect to p" if amb G = gram_matrix(ambient_space(C)) - @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattice in same ambient space must be orthogonal" + @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattices in same ambient space must be orthogonal" end end - results = LatWithIsom[] + results = ZZLatWithIsom[] # this is the glue valuation: it is well-defined because the triple in input is admissible g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) @@ -536,16 +674,16 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, qB, fqB = discriminant_group(B) qC, _ = discriminant_group(C) GA = image_centralizer_in_Oq(A) - @hassert :LatWithIsom 1 fqA in GA + @hassert :ZZLatWithIsom 1 fqA in GA GB = image_centralizer_in_Oq(B) - @hassert :LatWithIsom 1 fqB in GB + @hassert :ZZLatWithIsom 1 fqB in GB # this is where we will perform the glueing if amb D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) else D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _direct_sum_with_embeddings_orthogonal_groups(qA, qB) - end + end OqA = domain(OqAinOD) OqB = domain(OqBinOD) @@ -562,20 +700,20 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) - @hassert :LatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) else _B = block_diagonal_matrix([basis_matrix(A), basis_matrix(B)]) C2 = lattice_in_same_ambient_space(cover(D), _B) fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) - @hassert :LatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end qC2 = discriminant_group(C2) phi2 = hom(qC2, D, [D(lift(x)) for x in gens(qC2)]) - @hassert :LatWithIsom 1 _is_isometry(phi2) + @hassert :ZZLatWithIsom 1 _is_isometry(phi2) if is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation=false), type(C)) GC = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GC2)]) C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - @hassert :LatWithIsom 1 discriminant_group(C2fc2)[2] in GC + @hassert :ZZLatWithIsom 1 discriminant_group(C2fc2)[2] in GC set_attribute!(C2fc2, :image_centralizer_in_Oq, GC) push!(results, C2fc2) end @@ -584,7 +722,6 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map VA, VAinqA, fVA = _get_V(hom(fqA), minpoly(B), p) VB, VBinqB, fVB = _get_V(hom(fqB), minpoly(A), p) - # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g return results @@ -593,16 +730,15 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, # scale of the dual: any glue kernel must contain the multiples of l of the respective # discriminant groups l = valuation(level(genus(C)), p) + spec = (p == 2) && (_is_free(qA, p, l+1)) && (_is_free(qB, p, l+1)) && (_is_even(_rho_functor(qC, p, l))) # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) - subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fVA, ZZ(l)) subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) - # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry R = Tuple{eltype(subsA), eltype(subsB), TorQuadModuleMor}[] @@ -611,7 +747,6 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, !ok && continue push!(R, (H1, H2, phi)) end - # now, for each pair of anti-isometric potential kernels, we need to see whether # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the # corresponding overlattice and check whether it satisfies the type condition @@ -624,7 +759,8 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, SB = domain(SBinqB) OSB = orthogonal_group(SB) - phi = _find_admissible_gluing(SAinqA, SBinqB, phi, p, spec) + phi = _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) + #!ok && continue # we compute the image of the stabalizers in the respective OS* and we keep track # of the elements of the stabilizers acting trivially in the respective S* actA = hom(stabA, OSA, [OSA(restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) @@ -640,19 +776,20 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fSB = OSB(restrict_automorphism(fqB, SBinqB)) OSBrB = _compute_double_stabilizer(SBinqB, l, spec) - @hassert :LatWithIsom 1 fSB in OSBrB - + (fSB in OSBrB) || continue # 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 glueing. # If not, we try the next potential pair. - fSAinOSBrB = OSBrB(compose(inv(phi), compose(hom(fSA), phi))) + fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + (fSAinOSB in OSBrB) || continue + fSAinOSBrB = OSBrB(fSAinOSB) bool, g0 = representative_action(OSBrB, fSAinOSBrB, OSBrB(fSB)) bool || continue phi = compose(phi, hom(OSB(g0))) fSAinOSBrB = OSB(compose(inv(phi), compose(hom(fSA), phi))) # Now the new phi is "sending" the restriction of fA to this of fB. # So we can glue SA and SB. - @hassert :LatWithIsom 1 fSAinOSBrB == fSB + @hassert :ZZLatWithIsom 1 fSAinOSBrB == fSB # 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 @@ -685,7 +822,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) fC2 = _B*fC2*inv(_B) else - _glue = Vector{QQFieldElem}[lift(qAinD(SAinqA(g))) + lift(qBinD(SBinqB(phig(g)))) for g in gens(SA)] + _glue = Vector{QQFieldElem}[lift(qAinD(SAinqA(s)))+ lift(qBinD(SBinqB(phig(s)))) for s in gens(domain(phig))] z = zero_matrix(QQ,0,degree(cover(D))) glue = reduce(vcat, [matrix(QQ, 1, degree(cover(D)),g) for g in _glue], init=z) glue = vcat(block_diagonal_matrix(basis_matrix.([A, B])), glue) @@ -696,8 +833,8 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) fC2 = __B*fC2*inv(__B) - @hassert :LatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end + @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) @@ -706,13 +843,12 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) - @hassert :LatWithIsom 1 _is_isometry(phi2) + @hassert :ZZLatWithIsom 1 _is_isometry(phi2) println(genus(C2)) println(multiplicative_order(fC2)) if !is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) continue end - C2 = lattice_with_isometry(C2, fC2, ambient_representation=false) geneOSA = AutomorphismGroupElem{TorQuadModule}[OSA(compose(phig, compose(hom(g1), inv(phig)))) for g1 in unique(gens(imB))] im2_phi, _ = sub(OSA, geneOSA) @@ -725,7 +861,7 @@ function admissible_equivariant_primitive_extensions(A::LatWithIsom, 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 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) - @hassert :LatWithIsom 1 discriminant_group(C2)[2] in stab + @hassert :ZZLatWithIsom 1 discriminant_group(C2)[2] in stab set_attribute!(C2, :image_centralizer_in_Oq, stab) push!(results, C2) end @@ -742,9 +878,17 @@ end function _on_modular_matrix_quad(M::QQMatrix, g::AutomorphismGroupElem) q = domain(parent(g)) - R = Hecke.value_module_quadratic_form(q) + R1 = Hecke.value_module(q) + R2 = Hecke.value_module_quadratic_form(q) m = matrix(g) - return map_entries(a -> lift(R(a)), m*M*transpose(m)) + m1 = map_entries(a -> lift(R2(a)), m*M*transpose(m)) + for i in 1:nrows(m1) + for j in 1:ncols(m1) + i == j && continue + m1[i,j] = lift(R1(m1[i,j])) + end + end + return m1 end function _compute_double_stabilizer(SBinqB, l, spec) @@ -752,36 +896,20 @@ function _compute_double_stabilizer(SBinqB, l, spec) qB = codomain(SBinqB) OSB = orthogonal_group(SB) p = elementary_divisors(SB)[1] - pqB, pqBinqB = primary_part(qB, p) - _HB, _ = sub(qB, [qB(lift((p^l)*a)) for a in gens(pqB)]) - HB, _ = snf(_HB) - HBinqB = hom(HB, qB, [qB(lift(a)) for a in gens(HB)]) - rB = _rho_functor(qB, p, l+1) - HBinSB = hom(HB, SB, TorQuadModuleElem[SB(lift(k)) for k in gens(HB)]) - @hassert :LatWithIsom 1 is_injective(HBinSB) - + rBtoSB = hom(rB, 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) K, _ = kernel(OSBHBtoOHB) - rBtoHB = hom(rB, HB, [HB(QQ(p^l)*lift(a)) for a in gens(rB)]) - @hassert :LatWithIsom 1 is_bijective(rBtoHB) - HBtorB = inv(rBtoHB) - d = modulus_bilinear_form(rB) - rBd = rescale(rB, 1//d) - rBtorBd = hom(rB, rBd, gens(rBd)) - HBtorBd = compose(HBtorB, rBtorBd) - rBdtorB = inv(rBtorBd) - rBdtoHB = compose(rBdtorB, rBtoHB) - @hassert :LatWithIsom 1 modulus_quadratic_form(rBd) == 2 if p != 2 - OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rBd), _on_modular_matrix) + OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rB), _on_modular_matrix) elseif spec - OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_quadratic(rBd), _on_modular_matrix_quad) + OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_quadratic(rB), _on_modular_matrix_quad) else - OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rBd), _on_modular_matrix) + OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rB), _on_modular_matrix) end - OSBrB, _ = sub(OSB, union(gens(K), [OSB(OSBHBtoOHB\(g)) for g in gens(OHBrB)])) + OSBrB, _ = sub(OSB, union(gens(K), OSB.(gens((OSBHBtoOHB\(OHBrB))[1])))) return OSBrB end @@ -818,21 +946,23 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) 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) - HA, _ = sub(qA, unique(TorQuadModuleElem[qA(QQ(p^l)*lift(a)) for a in gens(rA)])) - HB, _ = sub(qB, unique(TorQuadModuleElem[qB(QQ(p^l)*lift(b)) for b in gens(rB)])) - rAtoHA = hom(rA, HA, [HA(QQ(p^l)*lift(a)) for a in gens(rA)]) - rBtoHB = hom(rB, HB, [HB(QQ(p^l)*lift(b)) for b in gens(rB)]) + rAtoSA = hom(rA, SA, [SA(QQ(p^l)*lift(a)) for a in gens(rA)]) + HA, _ = sub(SA, rAtoSA.(gens(rA))) + rBtoSB = hom(rB, SB, [SB(QQ(p^l)*(lift(b))) for b in gens(rB)]) + HB, HBinSB = sub(SB, rBtoSB.(gens(rB))) if p != 2 - phi_0 = _anti_isometry_bilinear(rA, rB) + phi_0 = _anti_isometry_bilinear(rA, rB, p) elseif spec ok, phi_0 = is_anti_isometric_with_anti_isometry(rA, rB) - @hassert :LatWithIsom 1 ok + @hassert :ZZLatWithIsom 1 ok else - phi_0 = _anti_isometry_bilinear(rA, rB) + phi_0 = _anti_isometry_bilinear(rA, rB, p) end - phiHA, _ = sub(SB, [SB(lift(phi(rAtoHA\a))) for a in gens(HA)]) + phiHA, _ = sub(SB, [SB(lift(phi(SA(lift(a))))) for a in gens(HA)]) OSB = orthogonal_group(SB) g = one(OSB) for f in OSB @@ -841,23 +971,28 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) break end end - @hassert :LatWithIsom 1 cover(_on_subgroup_automorphic(phiHA, g)) == cover(HB) + #if cover(_on_subgroup_automorphic(phiHA, g)) != cover(HB) + # return false, phi + #end phi_1 = compose(phi, hom(g)) - HAinSA = hom(HA, SA, SA.(lift.(gens(HA)))) - HBinSB = hom(HB, SB, SB.(lift.(gens(HB)))) OSBHB, _ = stabilizer(OSB, HBinSB) g = one(OSBHB) for f in OSBHB g = f phif = compose(phi_1, hom(f)) - psi = hom(rA, rB, [rBtoHB\(HBinSB\(phif(HAinSA(rAtoHA(a))))) for a in gens(rA)]) + psi = hom(rA, rB, [rBtoSB\(phif(rAtoSA(a))) for a in gens(rA)]) if matrix(psi) == matrix(phi_0) break end end phig = compose(phi_1, hom(g)) - @hassert :LatWithIsom 1 cover(sub(SB, [SB(lift(phig(HAinSA(a)))) for a in HA])[1]) == cover(HB) - @hassert :LatWithIsom 1 matrix(hom(rA, rB, [rBtoHB\(HBinSB\(phig(HAinSA(rAtoHA(a))))) for a in gens(rA)])) == matrix(phi_0) + #if cover(sub(SB, [SB(lift(phig(SA(lift(a))))) for a in HA])[1]) != cover(HB) + # return false, phi + #elseif matrix(hom(rA, rB, [rBtoSB\(phig(rAtoSA(a))) for a in gens(rA)])) != matrix(phi_0) + # return false, phi + #else + # return true, phig + #end return phig end @@ -870,153 +1005,16 @@ function _is_free(T, p, l) return _is_even(_rho_functor(T, p, l-1)) && _is_even(_rho_functor(T, p, l+1)) end -function _anti_isometry_bilinear(r1, r2) - @hassert :LatWithIsom is_semi_regular(r1) === is_semi_regular(r2) === true +function _anti_isometry_bilinear(r1, r2, p) + @hassert :ZZLatWithIsom is_semi_regular(r1) === is_semi_regular(r2) === true r2m = rescale(r2, -1) + r2tor2m = hom(r2, r2m, identity_matrix(ZZ, ngens(r2))) r1N, r1tor1N = normal_form(r1) r2mN, r2mtor2mN = normal_form(r2m) - r2tor2mN = compose(hom(r2, r2m, gens(r2m)), r2mtor2mN) - @hassert :LatWithIsom modulus_bilinear_form(r1N) == modulus_bilinear_form(r2mN) - @hassert :LatWithIsom Hecke.gram_matrix_bilinear(r1N) == Hecke.gram_matrix_bilinear(r2mN) + @hassert :ZZLatWithIsom 1 Hecke.gram_matrix_bilinear(r1N) == Hecke.gram_matrix_bilinear(r2mN) T = hom(r1N, r2mN, identity_matrix(ZZ, ngens(r1N))) - T = compose(r1tor1N, compose(T, inv(r2tor2mN))) - @hassert :LatWithIsom _is_anti_isometry(T) + T = compose(r1tor1N, compose(T, compose(inv(r2mtor2mN), inv(r2tor2m)))) + @hassert :ZZLatWithIsom _is_anti_isometry(T) return T end -############################################################################### -# -# Extra -# -############################################################################### - -function _isomorphism_classes_primitive_extensions_along_elementary(N::ZLat, M::ZLat, H::TorQuadModule) - @hassert :LatWithIsom 1 is_one(basis_matrix(N)) - @hassert :LatWithIsom 1 is_one(basis_matrix(M)) - q = elementary_divisors(H)[end] - ok, p, _ = is_prime_power_with_data(q) - @hassert :LatWithIsom 1 ok - @hassert :LatWithIsom 1 is_elementary(M, p) - results = Tuple{ZLat, ZLat, ZLat}[] - GN, _ = image_in_Oq(N) - GM, _ = image_in_Oq(M) - qN = domain(GN) - qM = domain(GM) - - D, inj = direct_sum(qN, qM) - qNinD, qMinD = inj - OD = orthogonal_group(D) - VN, VNinqN, _ = _get_V(N, qN, identity_matrix(QQ, rank(N)), id_hom(qN), minpoly(identity_matrix(QQ,1)), p) - subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) - filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) - @assert !isempty(subsN) - subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), GM, p) - filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) - @assert !isempty(subsM) - - for H1 in subsN, H2 in subsM - ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) - @hassert :LatWithIsom 1 ok - - HNinqN, stabN = H1 - OHN = orthogonal_group(HN) - - HMinqM, stabM = H2 - OHM = orthogonal_group(HM) - - actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) - - actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) 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, _ = sub(OHM, stabNphi) - reps = double_cosets(OHM, stabNphi, imM) - @vprint :LatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" - for g in reps - g = representative(g) - phig = compose(phi, hom(g)) - _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] - z = zero_matrix(QQ, 0, degree(N)+degree(M)) - glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) - glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - L = lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) - N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) - @hassert :LatWithIsom 1 genus(N) == genus(N2) - M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) - @hassert :LatWithIsom 1 genus(M) == genus(M2) - push!(results, (L, M2, N2)) - @vprint :LatWithIsom 1 "Gluing done\n" - GC.gc() - end - end - return results -end - -function primitive_embeddings_of_elementary_lattice(L::ZLat, M::ZLat, GL::AutomorphismGroup{TorQuadModule} = orthogonal_group(discriminant_group(L)); classification::Bool = false, first::Bool = false, check::Bool = false) - bool, p = is_elementary_with_prime(M) - @req bool "M must be elementary" - if check - @req length(genus_representatives(L)) == 1 "L must be unique in its genus" - end - results = Tuple{ZLat, ZLat, ZLat}[] - qL = rescale(discriminant_group(L), -1) - GL = Oscar._orthogonal_group(qL, matrix.(gens(GL))) - pL, _, nL = signature_tuple(L) - pM, _, nM = signature_tuple(M) - @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" - if ((pL, nL) == (pM, nM)) - if genus(M) != genus(L) - return false, results - else - return true, [(M, M, lattice_in_same_ambient_space(M, zero_matrix(QQ, 0, degree(M))))] - end - end - qM = discriminant_group(M) - D, inj, proj = biproduct(qM, qL) - qMinD, qLinD = inj - VL, VLinqL, _ = _get_V(id_hom(qL), minpoly(identity_matrix(QQ,1)), p) - for k in divisors(gcd(order(qM), order(VL))) - @info "Glue order: $(k)" - subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) - @info "$(length(subsL)) subgroup(s)" - subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qM), orthogonal_group(qM), k) - for H in subsL - HL = domain(H[1]) - _subsM = filter(HM -> is_anti_isometric_with_anti_isometry(domain(HM[1]), HL)[1], subsM) - isempty(_subsM) && continue - @info "Possible gluing" - HM = _subsM[1][1] - ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) - @assert ok - _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] - ext, _ = sub(D, D.(_glue)) - 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 = rescale(disc, -1) - !is_genus(disc, (pL-pM, nL-nM)) && continue - G = genus(disc, (pL-pM, nL-nM)) - !classification && return true, G - @info "We can glue: $G" - Ns = representatives(G) - @info "$(length(Ns)) possible orthogonal complement(s)" - Ns = lll.(Ns) - Ns = ZLat[Zlattice(gram=gram_matrix(N)) for N in Ns] - qM2, _ = sub(qM, [proj[1](j(g)) for g in gens(perp)]) - for N in Ns - append!(results, _isomorphism_classes_primitive_extensions_along_elementary(N, M, qM2)) - if first - return results - end - GC.gc() - end - GC.gc() - end - end - @assert all(triple -> genus(triple[1]) == genus(L), results) - return (length(results) >0), results -end diff --git a/experimental/LatticesWithIsometry/src/enumeration.jl b/experimental/LatticesWithIsometry/src/enumeration.jl index a8a4b1f990b4..eb2a5f3ca577 100644 --- a/experimental/LatticesWithIsometry/src/enumeration.jl +++ b/experimental/LatticesWithIsometry/src/enumeration.jl @@ -23,8 +23,8 @@ end # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. function _find_D(d::T, m::Int, p::Int) where T <: Hecke.IntegerUnion - @hassert :LatWithIsom 1 is_prime(p) - @hassert :LatWithIsom 1 d != 0 + @hassert :ZZLatWithIsom 1 is_prime(p) + @hassert :ZZLatWithIsom 1 d != 0 # If m == 0, there are no conditions on the gcd of d1 and dp if m == 0 @@ -49,19 +49,19 @@ end # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C function _find_L(pG::Int, nG::Int, r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::Hecke.IntegerUnion, even = true; pos::Int = -1) - L = ZGenus[] + L = ZZGenus[] if r == 0 && d == 1 - return ZGenus[genus(Zlattice(gram = matrix(QQ, 0, 0, [])))] + return ZZGenus[genus(integer_lattice(gram = matrix(QQ, 0, 0, [])))] end if pos >= 0 neg = r-pos - gen = Zgenera((pos, neg), d, even=even) + gen = integer_genera((pos, neg), d, even=even) filter!(G -> Hecke.divides(numerator(scale(G)), s)[1], gen) filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) else for (s1,s2) in [(s,t) for s=0:pG for t=0:nG if s+t==r] - gen = Zgenera((s1,s2), d, even=even) + gen = integer_genera((s1,s2), d, even=even) filter!(G -> Hecke.divides(numerator(scale(G)), s)[1], gen) filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) @@ -71,15 +71,15 @@ function _find_L(pG::Int, nG::Int, r::Int, d::Hecke.RationalUnion, s::ZZRingElem end @doc raw""" - is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) -> Bool + is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) -> Bool Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such that the rank of `B` is divisible by $p-1$ and the level of `C` is a power of `p`, return whether `(A,B,C)` is `p`-admissible in the sense of Definition 4.13. [BH22] """ -function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) - zg = genus(Zlattice(gram = matrix(QQ, 0, 0, []))) +function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) + zg = genus(integer_lattice(gram = matrix(QQ, 0, 0, []))) AperpB = direct_sum(A, B) (signature_tuple(AperpB) == signature_tuple(C)) || (return false) if ((A == zg) && (B == C)) || ((B == zg) && (A == C)) @@ -152,13 +152,13 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) if a_max == g if length(symbol(Ap)) > 1 - Ar = ZpGenus(p, symbol(Ap)[1:end-1]) + Ar = LocalZZGenus(p, symbol(Ap)[1:end-1]) else Ar = genus(matrix(ZZ,0,0,[]), p) end if length(symbol(Bp)) > 1 - Br = ZpGenus(p, symbol(Bp)[1:end-1]) + Br = LocalZZGenus(p, symbol(Bp)[1:end-1]) else Br = genus(matrix(ZZ, 0, 0, []), p) end @@ -191,7 +191,7 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) for s in Cp s[1] += 2 end - Cp = ZpGenus(p, Cp) + Cp = LocalZZGenus(p, Cp) if !represents(local_symbol(AperpB, p), Cp) return false @@ -203,13 +203,13 @@ function is_admissible_triple(A::ZGenus, B::ZGenus, C::ZGenus, p::Integer) return true end -function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZLat, LatWithIsom} - L = ZGenus[genus(D) for D = (A, B, C)] +function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZZLat, ZZLatWithIsom} + L = ZZGenus[genus(D) for D = (A, B, C)] return is_admissible_triple(L[1], L[2], L[3], p) end @doc raw""" - admissible_triples(C::ZGenus, p::Integer) -> Vector{Tuple{ZGenus, ZGenus}} + admissible_triples(C::ZZGenus, p::Integer) -> Vector{Tuple{ZZGenus, ZZGenus}} Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and @@ -217,7 +217,7 @@ $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and See Algorithm 1 of [BH22]. """ -function admissible_triples(G::ZGenus, p::Int64; pA::Int = -1, pB::Int = -1) +function admissible_triples(G::ZZGenus, p::Int64; 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" n = rank(G) @@ -235,7 +235,7 @@ function admissible_triples(G::ZGenus, p::Int64; pA::Int = -1, pB::Int = -1) end d = numerator(det(G)) even = iseven(G) - L = Tuple{ZGenus, ZGenus}[] + L = Tuple{ZZGenus, ZZGenus}[] for ep in 0:div(n, p-1) rp = (p-1)*ep if pB >= 0 @@ -260,7 +260,7 @@ function admissible_triples(G::ZGenus, p::Int64; pA::Int = -1, pB::Int = -1) return L end -admissible_triples(L::T, p::Integer; pA::Int = -1, pB::Int = -1) where T <: Union{ZLat, LatWithIsom} = admissible_triples(genus(L), p, pA = pA, pB = pB) +admissible_triples(L::T, p::Integer; pA::Int = -1, pB::Int = -1) where T <: Union{ZZLat, ZZLatWithIsom} = admissible_triples(genus(L), p, pA = pA, pB = pB) ################################################################################## # @@ -276,13 +276,13 @@ function _ideals_of_norm(E, d::QQFieldElem) elseif numerator(d) == 1 return [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))])] + 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)] end end function _ideals_of_norm(E, d::ZZRingElem) isone(d) && return [fractional_ideal(maximal_order(E), one(E))] - @hassert :LatWithIsom 1 E isa Hecke.NfRel + @hassert :ZZLatWithIsom 1 E isa Hecke.NfRel K = base_field(E) OK = maximal_order(K) OE = maximal_order(E) @@ -302,7 +302,7 @@ function _ideals_of_norm(E, d::ZZRingElem) push!(primes, [P^e for e in 0:divrem(v, nv)[1]]) end end - for I in Hecke.cartesian_product_iterator(primes) + for I in Hecke.cartesian_product_iterator(primes, inplace=false) I = prod(I) if absolute_norm(I) == d push!(ids, fractional_ideal(OE, I)) @@ -316,11 +316,11 @@ end # E/K of rank rk, whose trace lattice has signature (s1, s2). function _possible_signatures(s1, s2, E, rk) - @hassert :LatWithIsom 1 E isa Hecke.NfRel + @hassert :ZZLatWithIsom 1 E isa Hecke.NfRel ok, q = Hecke.is_cyclotomic_type(E) - @hassert :LatWithIsom 1 ok - @hassert :LatWithIsom 1 iseven(s2) - @hassert :LatWithIsom 1 Hecke.divides(2*(s1+s2), euler_phi(q))[1] + @hassert :ZZLatWithIsom 1 ok + @hassert :ZZLatWithIsom 1 iseven(s2) + @hassert :ZZLatWithIsom 1 Hecke.divides(2*(s1+s2), euler_phi(q))[1] l = divexact(s2, 2) K = base_field(E) inf = real_places(K) @@ -350,36 +350,37 @@ function _possible_signatures(s1, s2, E, rk) end @doc raw""" - representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) - -> Vector{LatWithIsom} + representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) + -> Vector{ZZLatWithIsom} -Given a lattice with isometry $(L, f)$ of hermitian type (i.e. the minimal polynomial -of `f` is irreducible cyclotomic), and a positive integer `m`, return a set of -representatives of isomorphism classes of lattices with isometry of hermitian -type $(M, g)$ and such that the type of $(B, g^m)$ is equal to the type of -$(L, f)$. Note that in this case, the isometries `g`'s are of order $nm$. +Given a lattice with isometry $(L, f)$ of hermitian type (i.e. the minimal +polynomial of `f` is irreducible cyclotomic) and a positive integer `m`, return +a set of representatives of isomorphism classes of lattices with isometry of +hermitian type $(M, g)$ and such that the type of $(B, g^m)$ is equal to the +type of $(L, f)$. Note that in this case, the isometries `g`'s are of +order $nm$. See Algorithm 3 of [BH22]. """ -function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) - rank(Lf) == 0 && return LatWithIsom[Lf] +function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) + rank(Lf) == 0 && return ZZLatWithIsom[Lf] @req m >= 1 "m must be a positive integer" - @req is_of_hermitian_type(Lf) "Lf must be of hermitian" + @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" rk = rank(Lf) d = det(Lf) n = order_of_isometry(Lf) s1, _, s2 = signature_tuple(Lf) - reps = LatWithIsom[] + reps = ZZLatWithIsom[] if n*m < 3 - @vprint :LatWithIsom 1 "Order smaller than 3\n" + @vprint :ZZLatWithIsom 1 "Order smaller than 3\n" f = (-1)^(n*m+1)*identity_matrix(QQ, rk) G = genus(Lf) repre = representatives(G) - @vprint :LatWithIsom 1 "$(length(repre)) representative(s)\n" + @vprint :ZZLatWithIsom 1 "$(length(repre)) representative(s)\n" for LL in repre is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) end @@ -388,10 +389,8 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) !iseven(s2) && return reps - @vprint :LatWithIsom 1 "Order bigger than 3\n" - + @vprint :ZZLatWithIsom 1 "Order bigger than 3\n" ok, rk = Hecke.divides(rk, euler_phi(n*m)) - ok || return reps gene = Hecke.HermGenus[] @@ -399,24 +398,24 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) - @vprint :LatWithIsom 1 "We have the different\n" + @vprint :ZZLatWithIsom 1 "We have the different\n" ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) - @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))\n" + @vprint :ZZLatWithIsom 1 "All possible ideal dets: $(length(detE))\n" signatures = _possible_signatures(s1, s2, E, rk) - @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))\n" + @vprint :ZZLatWithIsom 1 "All possible signatures: $(length(signatures))\n" for dd in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(dd)*DE)) + append!(gene, hermitian_genera(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(dd)*DE)) end gene = unique(gene) - @vprint :LatWithIsom 1 "All possible genera: $(length(gene))\n" + @vprint :ZZLatWithIsom 1 "All possible genera: $(length(gene))\n" for g in gene - @vprint :LatWithIsom 1 "g = $g\n" + @vprint :ZZLatWithIsom 1 "g = $g\n" H = representative(g) if !is_integral(DE*scale(H)) continue @@ -424,12 +423,12 @@ function representatives_of_hermitian_type(Lf::LatWithIsom, m::Int = 1) if is_even(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) continue end - @vprint :LatWithIsom 1 "$H\n" + @vprint :ZZLatWithIsom 1 "$H\n" M, fM = Hecke.trace_lattice_with_isometry(H) det(M) == d || continue M = lattice_with_isometry(M, fM) - @hassert :LatWithIsom 1 is_of_hermitian_type(M) - @hassert :LatWithIsom 1 order_of_isometry(M) == n*m + @hassert :ZZLatWithIsom 1 is_of_hermitian_type(M) + @hassert :ZZLatWithIsom 1 order_of_isometry(M) == n*m if is_even(M) != is_even(Lf) continue end @@ -447,7 +446,7 @@ end @doc raw""" representatives_of_hermitian_type(t::Dict, m::Int = 1; check::Bool = true) - -> Vector{LatWithIsom} + -> Vector{ZZLatWithIsom} Given a hermitian type `t` for lattices with isometry (i.e. the minimal polymomial of the associated isometry is irreducible cyclotomic) and an intger @@ -461,12 +460,12 @@ See Algorithm 3 of [BH22]. """ function representatives_of_hermitian_type(t::Dict, m::Integer = 1; check::Bool = true) M = _representative(t, check = check) - M === nothing && return LatWithIsom[] + M === nothing && return ZZLatWithIsom[] return representatives_of_hermitian_type(M, m) end function _representative(t::Dict; check::Bool = true) - !check || is_hermitian(t) || error("t must be pure") + !check || is_hermitian(t) || error("t must be hermitian") ke = collect(keys(t)) n = maximum(ke) @@ -493,14 +492,14 @@ function _representative(t::Dict; check::Bool = true) ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) - @vprint :LatWithIsom 1 "All possible ideal dets: $(length(detE))\n" + @vprint :ZZLatWithIsom 1 "All possible ideal dets: $(length(detE))\n" signatures = _possible_signatures(s1, s2, E) - @vprint :LatWithIsom 1 "All possible signatures: $(length(signatures))\n" + @vprint :ZZLatWithIsom 1 "All possible signatures: $(length(signatures))\n" for dd in detE, sign in signatures - append!(gene, genera_hermitian(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) + append!(gene, hermitian_genera(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) end gene = unique(gene) @@ -515,8 +514,8 @@ function _representative(t::Dict; check::Bool = true) H = H M = trace_lattice(H) det(M) == d || continue - @hassert :LatWithIsom 1 is_of_hermitian_type(M) - @hassert :LatWithIsom 1 order_of_isometry(M) == n + @hassert :ZZLatWithIsom 1 is_of_hermitian_type(M) + @hassert :ZZLatWithIsom 1 order_of_isometry(M) == n if iseven(M) != iseven(G) continue end @@ -529,44 +528,42 @@ function _representative(t::Dict; check::Bool = true) end @doc raw""" - splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int) -> Vector{LatWithIsom} + splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int) -> Vector{ZZLatWithIsom} -Given a lattice with isometry $(L, f)$ of hermitian type with `f` of order $q^d$ +Given a lattice with isometry $(L, f)$ of hermitian type with `f` of order $q^e$ for some prime number `q`, and given another prime number $p \neq q$, return a set of representatives of the isomorphism classes of lattices with isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. -Note that `d` can be 0. +Note that `e` can be 0. See Algorithm 4 of [BH22]. """ -function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = -1, pB::Int = -1) - rank(Lf) == 0 && return LatWithIsom[Lf] +function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = -1, pB::Int = -1) + rank(Lf) == 0 && return ZZLatWithIsom[Lf] @req is_prime(p) "p must be a prime number" @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" - ok, q, d = is_prime_power_with_data(order_of_isometry(Lf)) + ok, q, e = is_prime_power_with_data(order_of_isometry(Lf)) - @req ok || d == 0 "Order of isometry must be a prime power" + @req ok || e == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" - reps = LatWithIsom[] - @vprint :LatWithIsom 1 "Compute admissible triples\n" + reps = ZZLatWithIsom[] + @vprint :ZZLatWithIsom 1 "Compute admissible triples\n" atp = admissible_triples(Lf, p, pA = pA, pB = pB) - @vprint :LatWithIsom 1 "$(length(atp)) admissible triple(s)\n" + @vprint :ZZLatWithIsom 1 "$(length(atp)) admissible triple(s)\n" for (A, B) in atp LB = lattice_with_isometry(representative(B)) - RB = representatives_of_hermitian_type(LB, p*q^d) + RB = representatives_of_hermitian_type(LB, p*q^e) if is_empty(RB) continue end LA = lattice_with_isometry(representative(A)) - RA = representatives_of_hermitian_type(LA, q^d) - for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]) - E = try admissible_equivariant_primitive_extensions(L1, L2, Lf, p) - catch e return L1, L2, Lf, p - end + RA = representatives_of_hermitian_type(LA, q^e) + for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB], inplace=false) + E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p) GC.gc() append!(reps, E) end @@ -575,14 +572,14 @@ function splitting_of_hermitian_prime_power(Lf::LatWithIsom, p::Int; pA::Int = - end @doc raw""" - splitting_of_hermitian_prime_power(t::Dict, p::Int) -> Vector{LatWithIsom} + splitting_of_hermitian_prime_power(t::Dict, p::Int) -> Vector{ZZLatWithIsom} Given a hermitian type `t` of lattice with isometry $(L, f)$ with `f` of order -$q^d$ for some prime number `q`, and given another prime number $p \neq q$, +$q^e$ for some prime number `q`, and given another prime number $p \neq q$, return a set of representatives of the isomorphisms classes of lattices with isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to `t`. -Note that `d` can be 0. +Note that `e` can be 0. See Algorithm 4 of [BH22]. """ @@ -590,26 +587,30 @@ function splitting_of_hermitian_prime_power(t::Dict, p::Int) @req is_prime(p) "p must be a prime number" @req is_hermitian(t) "t must be hermitian" Lf = _representative(t) + Lf === nothing && return ZZLatWithIsom[] return splitting_of_hermitian_prime_power(Lf, p) end @doc raw""" - splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) -> Vector{LatWithIsom} + splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) + -> Vector{ZZLatWithIsom} -Given a lattice with isometry $(L, f)$ with `f` of order $q^e$ for some prime number -`q`, a prime number $p \neq q$ and an integer $b = 0, 1$, return a set of representatives -of the isomorphism classes of lattices with isometry $(M, g)$ such that the type of -$(M, g^p)$ is equal to the type of $(L, f)$. If `b == 1`, return only the lattices -with isometry $(M, g)$ where `g` is of order $pq^e$. +Given a lattice with isometry $(L, f)$ with `f` of order $q^e$ for some +prime number `q`, a prime number $p \neq q$ and an integer $b = 0, 1$, return +a set of representatives of the isomorphism classes of lattices with isometry +$(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. + +If `b == 1`, return only the lattices with isometry $(M, g)$ where `g` is of +order $pq^e$. Note that `e` can be 0. See Algorithm 5 of [BH22]. """ -function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) +function splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) if rank(Lf) == 0 - (b == 0) && return LatWithIsom[Lf] - return LatWithIsom[] + (b == 0) && return ZZLatWithIsom[Lf] + return ZZLatWithIsom[] end @req is_prime(p) "p must be a prime number" @@ -620,7 +621,7 @@ function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) @req ok || e == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" - reps = LatWithIsom[] + reps = ZZLatWithIsom[] if e == 0 reps = splitting_of_hermitian_prime_power(Lf, p) @@ -634,33 +635,32 @@ function splitting_of_prime_power(Lf::LatWithIsom, p::Int, b::Int = 0) A = splitting_of_hermitian_prime_power(A0, p) is_empty(A) && return reps B = splitting_of_prime_power(B0, p) - for (L1, L2) in Hecke.cartesian_product_iterator([A, B]) + for (L1, L2) in Hecke.cartesian_product_iterator([A, B], inplace=false) b == 1 && !Hecke.divides(order_of_isometry(L1), p)[1] && !Hecke.divides(order_of_isometry(L2), p)[1] && continue - E = try admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p) - catch e return L2, L1, Lf, q, p - end - @hassert :LatWithIsom 1 b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) + E = admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p) + GC.gc() + @hassert :ZZLatWithIsom 1 b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) end return reps end @doc raw""" - splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) - -> Vector{LatWithIsom} + splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) + -> Vector{ZZLatWithIsom} Given a lattice with isometry $(L, f)$ and a prime number `p`, such that -the minimal polynomial of `f` divides $\prod_{i=0}^e\Phi_{p^dq^i}(f)$ for some -$d > 0$ and $e \geq 0$, return a set of representatives of the isomorphism classes -of lattices with isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to the type +$\prod_{i=0}^e\Phi_{p^dq^i}(f)$ is trivial for some $d > 0$ and $e \geq 0$, +return a set of representatives of the isomorphism classes of lattices with +isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. Note that `e` can be 0, while `d` has to be positive. See Algorithm 6 of [BH22]. """ -function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) - rank(Lf) == 0 && return LatWithIsom[] +function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) + rank(Lf) == 0 && return ZZLatWithIsom[Lf] @req is_prime(p) "p must be a prime number" @req is_finite(order_of_isometry(Lf)) "Isometry must be of finite order" @@ -668,7 +668,7 @@ function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) 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 factors" + @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] @@ -685,43 +685,49 @@ function splitting_of_partial_mixed_prime_power(Lf::LatWithIsom, p::Int) @req Hecke.divides(chi, phi)[1] "Minimal polynomial is not of the correct form" - reps = LatWithIsom[] + reps = ZZLatWithIsom[] if e == 0 - return splitting_of_hermitian_prime_power(Lf, p) + return representatives_of_hermitian_type(Lf, p) end A0 = kernel_lattice(Lf, p^d*q^e) bool, r = Hecke.divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) - @hassert :LatWithIsom 1 bool + @hassert :ZZLatWithIsom 1 bool B0 = kernel_lattice(Lf, r) - A = splitting_of_prime_power(A0, p) + A = representatives_of_hermitian_type(A0, p) is_empty(A) && return reps - B = splitting_of_partial_mixed_prime_power(B0, p) - for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) - E = admissible_equivariant_primitive_extensions(LB, LA, Lf, q, p) + B = splitting_of_pure_mixed_prime_power(B0, p) + for (LA, LB) in Hecke.cartesian_product_iterator([A, B], inplace=false) + E = admissible_equivariant_primitive_extensions(LA, LB, Lf, q, p) + GC.gc() append!(reps, E) end return reps end @doc raw""" - splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) - -> Vector{LatWithIsom} + splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) + -> Vector{ZZLatWithIsom} Given a lattice with isometry $(L, f)$ and a prime number `p` such that `f` is of order $p^dq^e$ for some prime number $q \neq p$, return a set of representatives of the isomorphism classes of lattices with isometry -$(M, g)$ of order $p^{d+1}q^e$ such that the type of $(M, g^p)$ is equal -to the type of $(L, f)$. +$(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. + +If `b == 1`, return only the lattices with isometry $(M, g)$ where `g` is +of order $p^{d+1}q^e$. Note that `d` and `e` can be both zero. See Algorithm 7 of [BH22]. """ -function splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) - rank(Lf) == 0 && return LatWithIsom[] +function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) + if rank(Lf) == 0 + b == 0 && return ZZLatWithIsom[Lf] + return ZZLatWithIsom[] + end n = order_of_isometry(Lf) @@ -732,7 +738,7 @@ function splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) @req length(pd) <= 2 "Order must have at most 2 prime divisors" if !(p in pd) - return splitting_of_prime_power(Lf, p, 1) + return splitting_of_prime_power(Lf, p, b) end d = valuation(n, p) @@ -743,19 +749,108 @@ function splitting_of_mixed_prime_power(Lf::LatWithIsom, p::Int) e = 0 end - reps = LatWithIsom[] + reps = ZZLatWithIsom[] x = gen(parent(minpoly(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])) - A = splitting_of_partial_mixed_prime_power(A0, p) + A = splitting_of_pure_mixed_prime_power(A0, p) isempty(A) && return reps - B = splitting_of_mixed_prime_power(B0, p) - for (LA, LB) in Hecke.cartesian_product_iterator([A, B]) - E = admissible_equivariant_primitive_extensions(LB, LA, Lf, p) - @hassert :LatWithIsom 1 all(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) + B = splitting_of_mixed_prime_power(B0, p, 0) + for (LA, LB) in Hecke.cartesian_product_iterator([A, B], inplace=false) + E = admissible_equivariant_primitive_extensions(LA, LB, Lf, p) + GC.gc() + if b == 1 + filter!(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) + end append!(reps, E) end return reps end +@doc raw""" + enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::IntegerUnion) + -> Vector{ZZLatWithIsom} + enumerate_classes_of_lattices_with_isometry(G::ZZGenus, order::IntegerUnion) + -> Vector{LocalZZGenus} + +Given an integral integer lattice `L`, return representatives of isomorphism classes +of lattice with isometry $(M ,g)$ where `M` is in the genus of `L`, and `g` has order +`order`. Alternatively, one can input a given genus symbol `G` for integral integer +lattices as an input - the function first computes a representative of `G`. + +Note that currently we support only orders which admit at most 2 prime divisors. +""" +function enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::Hecke.IntegerUnion) + @req is_finite(order) && order >= 1 "order must be positive and finite" + if order == 1 + return representatives_of_hermitian_type(lattice_with_isometry(L)) + 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) + end + p, q = sort!(pd) + vp = valuation(order, p) + vq = valuation(order, q) + Lq = _enumerate_prime_power(L, q, vq) + reps = ZZLatWithIsom[] + for N in Lq + append!(reps, _split_prime_power(N, p, vp)) + end + @hassert :ZZLatWithIsom 6 all(N -> order_of_isometry(N) == order, reps) + return reps +end + +enumerate_classes_of_lattices_with_isometry(G::ZZGenus, order::Hecke.IntegerUnion) = + enumerate_classes_of_lattices_with_isometry(representative(G), order) + +# We compute representatives of isomorphism classes of lattice with isometry in +# the genus of `L` and with prime power order q^vq. +function _enumerate_prime_power(L::ZZLat, q::Hecke.IntegerUnion, vq::Hecke.IntegerUnion) + @hassert :ZZLatWithIsom 1 is_prime(q) + @hassert :ZZLatWithIsom 1 vq >= 1 + Lq = splitting_of_prime_power(lattice_with_isometry(L), q, 1) + vq == 1 && return Lq + reps = ZZLatWithIsom[] + while !is_empty(Lq) + N = popfirst!(Lq) + v = valuation(order_of_isometry(N), q) + @hassert :ZZLatWithIsom 1 (1 <= v < vq) + Nq = splitting_of_mixed_prime_power(N, q) + @hassert :ZZLatWithIsom 1 all(NN -> valuation(order_of_isometry(NN), q) == v+1, Nq) + if v == vq -1 + append!(reps, Nq) + else + append!(Lq, Nq) + end + end + return reps +end + +# `N` is lattice with isometry of order q^vq for some prime number q different +# from p. Computes representatives of isomorphism classes of lattice with +# isometry in the genus of `N`, of order p^vq*q^vq and whose q-type is the same +# as `N`. +function _split_prime_power(N::ZZLatWithIsom, p::Hecke.IntegerUnion, vp::Hecke.IntegerUnion) + pd = prime_divisors(order_of_isometry(N)) + @hassert :ZZLatWithIsom 1 (length(pd) == 1 && !(p in pd)) + Np = splitting_of_prime_power(N, p, 1) + vp == 1 && return Np + reps = ZZLatWithIsom[] + while !is_empty(Np) + M = popfirst!(Np) + v = valuation(order_of_isometry(M), p) + @hassert :ZZLatWithIsom 1 (1 <= v < vp) + Mp = splitting_of_mixed_prime_power(M, p) + @hassert :ZZLatWithIsom 1 all(MM -> valuation(order_of_isometry(MM), p) == v+1, Mp) + if v == vp-1 + append!(reps, Mp) + else + append!(Np, Mp) + end + end + return reps +end diff --git a/experimental/LatticesWithIsometry/src/exports.jl b/experimental/LatticesWithIsometry/src/exports.jl index 44f18b06de87..f5734892290b 100644 --- a/experimental/LatticesWithIsometry/src/exports.jl +++ b/experimental/LatticesWithIsometry/src/exports.jl @@ -1,8 +1,9 @@ -export LatWithIsom +export ZZLatWithIsom export admissible_equivariant_primitive_extensions export admissible_triples export ambient_isometry +export enumerate_classes_of_lattices_with_isometry export image_centralizer_in_Oq export isometry export is_admissible_triple @@ -12,11 +13,12 @@ export is_of_type export is_hermitian export lattice_with_isometry export order_of_isometry +export primitive_embeddings_of_primary_lattice export primitive_embeddings_in_primary_lattice export representatives_of_hermitian_type export splitting_of_hermitian_prime_power export splitting_of_mixed_prime_power -export splitting_of_partial_mixed_prime_power +export splitting_of_pure_mixed_prime_power export splitting_of_prime_power export type diff --git a/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl b/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl index f2a151df2ce2..841063acee61 100644 --- a/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl +++ b/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl @@ -22,13 +22,13 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) URPabs, mURPabs = unit_group(RPabs) function dlog(x::Hecke.NfRelElem) - @hassert :LatWithIsom 1 parent(x) == E + @hassert :ZZLatWithIsom 1 parent(x) == E d = denominator(x, OE) xabs = d*(EabstoE\(x)) dabs = copy(d) F = prime_decomposition(OEabs, minimum(Pabs)) for PP in F - @hassert :LatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 + @hassert :ZZLatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp @@ -44,9 +44,9 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) end function exp(k::GrpAbFinGenElem) - @hassert :LatWithIsom 1 parent(k) === URPabs + @hassert :ZZLatWithIsom 1 parent(k) === URPabs x = EabstoE(Eabs(mRPabs\mURPabs(k))) - @hassert :LatWithIsom 1 dlog(x) == k + @hassert :ZZLatWithIsom 1 dlog(x) == k return x end @@ -77,18 +77,18 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) S, mS = snf(K) function exp(k::GrpAbFinGenElem) - @hassert :LatWithIsom 1 parent(k) === S + @hassert :ZZLatWithIsom 1 parent(k) === S return EabstoE(elem_in_nf(mRPabs\(mURPabs(mK(mS(k)))))) end function dlog(x::Hecke.NfRelElem) - @hassert :LatWithIsom 1 parent(x) === E + @hassert :ZZLatWithIsom 1 parent(x) === E d = denominator(x, OE) xabs = EabstoE\(d*x) dabs = copy(d) F = prime_decomposition(OEabs, minimum(Pabs)) for PP in F - @hassert :LatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 + @hassert :ZZLatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp @@ -148,18 +148,18 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) S, mS = snf(K) function exp(k::GrpAbFinGenElem) - @hassert :LatWithIsom 1 parent(k) === S + @hassert :ZZLatWithIsom 1 parent(k) === S return EabstoE(elem_in_nf(mRPabs\(mURPabs(mK(mS(k)))))) end function dlog(x::Hecke.NfRelElem) - @hassert :LatWithIsom 1 parent(x) === E + @hassert :ZZLatWithIsom 1 parent(x) === E d = denominator(x, OE) xabs = EabstoE\(d*x) dabs = copy(d) F = prime_decomposition(OEabs, minimum(EabstoE\P)) for PP in F - @hassert :LatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 + @hassert :ZZLatWithIsom 1 valuation(EabstoE\(x), PP[1]) >= 0 api = anti_uniformizer(PP[1]) exp = valuation(OEabs(d), PP[1]) dabs *= api^exp @@ -180,9 +180,9 @@ end # in O is to distribute to the appropriate function above. function _get_quotient(O::Hecke.NfRelOrd, p::Hecke.NfOrdIdl, i::Int) - @hassert :LatWithIsom 1 is_prime(p) - @hassert :LatWithIsom 1 is_maximal(order(p)) - @hassert :LatWithIsom 1 order(p) === base_ring(O) + @hassert :ZZLatWithIsom 1 is_prime(p) + @hassert :ZZLatWithIsom 1 is_maximal(order(p)) + @hassert :ZZLatWithIsom 1 order(p) === base_ring(O) E = nf(O) F = prime_decomposition(O, p) P = F[1][1] @@ -239,20 +239,20 @@ function _get_product_quotient(E::Hecke.NfRel, Fac) if length(x) == 1 return sum([inj[i](dlogs[i](x[1])) for i in 1:length(Fac)]) else - @hassert :LatWithIsom 1 length(x) == length(Fac) + @hassert :ZZLatWithIsom 1 length(x) == 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)] - @hassert :LatWithIsom 1 dlog(v) == x + @hassert :ZZLatWithIsom 1 dlog(v) == x return v end for i in 1:10 a = rand(G) - @hassert :LatWithIsom 1 dlog(exp(a)) == a + @hassert :ZZLatWithIsom 1 dlog(exp(a)) == a end return G, dlog, exp @@ -309,8 +309,8 @@ end # similar function on Magma by Tommy Hofmann. Only the last loop about # determinants approximations is new in this code. -function _local_determinants_morphism(Lf::LatWithIsom) - @hassert :LatWithIsom 1 is_of_hermitian_type(Lf) +function _local_determinants_morphism(Lf::ZZLatWithIsom) + @hassert :ZZLatWithIsom 1 is_of_hermitian_type(Lf) qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) @@ -333,7 +333,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) DEQ = DEK*DKQ H2 = inv(DEQ)*dual(H) - @hassert :LatWithIsom 1 is_sublattice(H2, H) # This should be true since the lattice in Lf is integral + @hassert :ZZLatWithIsom 1 is_sublattice(H2, H) # This should be true since the lattice in Lf is integral # This is the map used for the trace construction: it is stored on Lf when we # have constructed H. We need this map because it sets the rule of the @@ -419,7 +419,7 @@ function _local_determinants_morphism(Lf::LatWithIsom) SQ, SQtoQ = snf(Q) function dlog(x::Vector) - @hassert :LatWithIsom 1 length(x) == length(Fsharpdata) + @hassert :ZZLatWithIsom 1 length(x) == length(Fsharpdata) return SQtoQ\(mQ(j\(Fsharplog(x)))) end @@ -510,7 +510,7 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG Pabs = EabstoE\P OEabs = order(Pabs) q = domain(g) - @hassert :LatWithIsom 1 ambient_space(cover(q)) === domain(res) + @hassert :ZZLatWithIsom 1 ambient_space(cover(q)) === domain(res) # B2 will be a local basis at p of the image of D^{-1}H^# under the induced # action of g via res. @@ -561,20 +561,20 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG # If what we have done is correct then K*newBp == newB2 modulo O_p, so all # entries in the difference K*newBp-newB2 must have non-negative P-valuation. # Since it is the case, then K satisfies K*Bp == B2 mod H locally at p. - @hassert :LatWithIsom 1 _scale_valuation(K*newBp-newB2, P) >= 0 + @hassert :ZZLatWithIsom 1 _scale_valuation(K*newBp-newB2, P) >= 0 return K end # the minimum P-valuation among all the non-zero entries of M function _scale_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} - @hassert :LatWithIsom 1 nf(order(P)) === base_ring(M) + @hassert :ZZLatWithIsom 1 nf(order(P)) === base_ring(M) iszero(M) && return inf return minimum([valuation(v, P) for v in collect(M) if !iszero(v)]) end # the minimum P-valuation among all the non-zero diagonal entries of M function _norm_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} - @hassert :LatWithIsom 1 nf(order(P)) === base_ring(M) + @hassert :ZZLatWithIsom 1 nf(order(P)) === base_ring(M) iszero(diagonal(M)) && return inf r = minimum([valuation(v, P) for v in diagonal(M) if !iszero(v)]) return r @@ -590,20 +590,20 @@ end # looking at better representatives until we reach a good enough precision for # our purpose. function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::Hecke.NfRelOrdIdl, e::Int, a::Int; check = true) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} - @hassert :LatWithIsom 1 trace(rho) == 1 + @hassert :ZZLatWithIsom 1 trace(rho) == 1 E = base_ring(G) # G here is a local gram matrix - @hassert :LatWithIsom 1 G == map_entries(involution(E), transpose(G)) - @hassert :LatWithIsom 1 base_ring(F) === E + @hassert :ZZLatWithIsom 1 G == map_entries(involution(E), transpose(G)) + @hassert :ZZLatWithIsom 1 base_ring(F) === E # R represents the defect, how far F is to be an isometry of G R = G - F*G*map_entries(involution(E), transpose(F)) # These are the necessary conditions for the input of algorithm 8 in BH22 if check - @hassert :LatWithIsom 1 _scale_valuation(inv(G), P) >= 1+a - @hassert :LatWithIsom 1 _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a - @hassert :LatWithIsom 1 _scale_valuation(R, P) >= l-a - @hassert :LatWithIsom 1 _norm_valuation(R, P) + valuation(rho,P) >= l-a + @hassert :ZZLatWithIsom 1 _scale_valuation(inv(G), P) >= 1+a + @hassert :ZZLatWithIsom 1 _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a + @hassert :ZZLatWithIsom 1 _scale_valuation(R, P) >= l-a + @hassert :ZZLatWithIsom 1 _norm_valuation(R, P) + valuation(rho,P) >= l-a end # R is s-symmetric, where s is the canonical involution of E/K. We split R @@ -625,10 +625,10 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H l2 = 2*l+1 if check - @hassert :LatWithIsom 1 _scale_valuation(F-newF, P) >= l+1 + @hassert :ZZLatWithIsom 1 _scale_valuation(F-newF, P) >= l+1 R2 = G-newF*G*map_entries(involution(E), transpose(newF)) - @hassert :LatWithIsom 1 _scale_valuation(R2, P) >= l2-a - @hassert :LatWithIsom 1 _norm_valuation(R2, P) + valuation(rho, P) >= l2-a + @hassert :ZZLatWithIsom 1 _scale_valuation(R2, P) >= l2-a + @hassert :ZZLatWithIsom 1 _norm_valuation(R2, P) + valuation(rho, P) >= l2-a end return newF, l2 @@ -650,7 +650,7 @@ end # function _approximate_isometry(H::Hecke.HermLat, H2::Hecke.HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) E = base_field(H) - @hassert :LatWithIsom 1 nf(order(P)) === E + @hassert :ZZLatWithIsom 1 nf(order(P)) === E ok, b = is_modular(H, minimum(P)) if ok && b == -a return identity_matrix(E, 1) @@ -744,8 +744,8 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) rt = roots(t^2 - (g+1)*d^2, max_roots = 1, ispure = true, is_normal=true) if !is_empty(rt) rho = (1+rt[1])//2 - @hassert :LatWithIsom 1 valuation(rho, Pabs) == 1-e - @hassert :LatWithIsom 1 trace(EabstoE(rho)) == 1 + @hassert :ZZLatWithIsom 1 valuation(rho, Pabs) == 1-e + @hassert :ZZLatWithIsom 1 trace(EabstoE(rho)) == 1 return EabstoE(rho) end end diff --git a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl index f1b8eb5c381a..2171adff71fe 100644 --- a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl +++ b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl @@ -6,34 +6,34 @@ ############################################################################### @doc raw""" - lattice(Lf::LatWithIsom) -> ZLat + lattice(Lf::ZZLatWithIsom) -> ZZLat Given a lattice with isometry $(L, f)$, return the underlying lattice `L`. """ -lattice(Lf::LatWithIsom) = Lf.Lb +lattice(Lf::ZZLatWithIsom) = Lf.Lb @doc raw""" - isometry(Lf::LatWithIsom) -> QQMatrix + isometry(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. """ -isometry(Lf::LatWithIsom) = Lf.f +isometry(Lf::ZZLatWithIsom) = Lf.f @doc raw""" - ambient_isometry(Lf::LatWithIsom) -> QQMatrix + ambient_isometry(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return an isometry of underlying isometry of the ambient space of `L` inducing `f` on `L` """ -ambient_isometry(Lf::LatWithIsom) = Lf.f_ambient +ambient_isometry(Lf::ZZLatWithIsom) = Lf.f_ambient @doc raw""" - order_of_isometry(Lf::LatWithIsom) -> Integer + order_of_isometry(Lf::ZZLatWithIsom) -> Integer Given a lattice with isometry $(L, f)$, return the order of the underlying isometry `f`. """ -order_of_isometry(Lf::LatWithIsom) = Lf.n +order_of_isometry(Lf::ZZLatWithIsom) = Lf.n ############################################################################### # @@ -42,170 +42,170 @@ order_of_isometry(Lf::LatWithIsom) = Lf.n ############################################################################### @doc raw""" - rank(Lf::LatWithIsom) -> Integer + rank(Lf::ZZLatWithIsom) -> Integer Given a lattice with isometry $(L, f)$, return the rank of the underlying lattice `L`. """ -rank(Lf::LatWithIsom) = rank(lattice(Lf))::Integer +rank(Lf::ZZLatWithIsom) = rank(lattice(Lf))::Integer @doc raw""" - charpoly(Lf::LatWithIsom) -> QQPolyRingElem + charpoly(Lf::ZZLatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the characteristic polynomial of the underlying isometry `f`. """ -charpoly(Lf::LatWithIsom) = charpoly(isometry(Lf))::QQPolyRingElem +charpoly(Lf::ZZLatWithIsom) = charpoly(isometry(Lf))::QQPolyRingElem @doc raw""" - minpoly(Lf::LatWithIsom) -> QQPolyRingElem + minpoly(Lf::ZZLatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the minimal polynomial of the underlying isometry `f`. """ -minpoly(Lf::LatWithIsom) = minpoly(isometry(Lf))::QQPolyRingElem +minpoly(Lf::ZZLatWithIsom) = minpoly(isometry(Lf))::QQPolyRingElem @doc raw""" - genus(Lf::LatWithIsom) -> ZGenus + genus(Lf::ZZLatWithIsom) -> ZZGenus Given a lattice with isometry $(L, f)$, return the genus of the underlying -lattice `L` (see [`genus(::ZLat)`](@ref)). +lattice `L` (see [`genus(::ZZLat)`](@ref)). """ -genus(Lf::LatWithIsom) = genus(lattice(Lf))::ZGenus +genus(Lf::ZZLatWithIsom) = genus(lattice(Lf))::ZZGenus @doc raw""" - ambient_space(Lf::LatWithIsom) -> QuadSpace + ambient_space(Lf::ZZLatWithIsom) -> QuadSpace Given a lattice with isometry $(L, f)$, return the ambient space of the underlying -lattice `L` (see [`ambient_space(::ZLat)`](@ref)). +lattice `L` (see [`ambient_space(::ZZLat)`](@ref)). """ -ambient_space(Lf::LatWithIsom) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} +ambient_space(Lf::ZZLatWithIsom) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} @doc raw""" - basis_matrix(Lf::LatWithIsom) -> QQMatrix + basis_matrix(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the basis matrix of the underlying -lattice `L` (see [`basis_matrix(::ZLat)`](@ref)). +lattice `L` (see [`basis_matrix(::ZZLat)`](@ref)). """ -basis_matrix(Lf::LatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix +basis_matrix(Lf::ZZLatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix @doc raw""" - gram_matrix(Lf::LatWithIsom) -> QQMatrix + gram_matrix(Lf::ZZLatWithIsom) -> QQMatrix -Given a lattice with isometry $(L, f)$ with basis matric `B` (see [`basis_matrix(Lf::LatWithIsom)`](@ref)) -inside the space $(V, \Phi)$ (see [`ambient_space(Lf::LatWithIsom)`](@ref)), return the gram matrix +Given a lattice with isometry $(L, f)$ with basis matric `B` (see [`basis_matrix(Lf::ZZLatWithIsom)`](@ref)) +inside the space $(V, \Phi)$ (see [`ambient_space(Lf::ZZLatWithIsom)`](@ref)), return the gram matrix of lattice `L` associted to `B` with respect to $\Phi$. """ -gram_matrix(Lf::LatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix +gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix @doc raw""" - rational_span(Lf::LatWithIsom) -> QuadSpace + rational_span(Lf::ZZLatWithIsom) -> QuadSpace Given a lattice with isometry $(L, f)$, return the rational span $L \otimes \mathbb{Q}$ of the underlying lattice `L`. """ -rational_span(Lf::LatWithIsom) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} +rational_span(Lf::ZZLatWithIsom) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} @doc raw""" - det(Lf::LatWithIsom) -> QQFieldElem + det(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the determinant of the -underlying lattice `L` (see [`det(::ZLat)`](@ref)). +underlying lattice `L` (see [`det(::ZZLat)`](@ref)). """ -det(Lf::LatWithIsom) = det(lattice(Lf))::QQFieldElem +det(Lf::ZZLatWithIsom) = det(lattice(Lf))::QQFieldElem @doc raw""" - scale(Lf::LatWithIsom) -> QQFieldElem + scale(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the scale of the underlying -lattice `L` (see [`scale(::ZLat)`](@ref)). +lattice `L` (see [`scale(::ZZLat)`](@ref)). """ -scale(Lf::LatWithIsom) = scale(lattice(Lf))::QQFieldElem +scale(Lf::ZZLatWithIsom) = scale(lattice(Lf))::QQFieldElem @doc raw""" - norm(Lf::LatWithIsom) -> QQFieldElem + norm(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the norm of the underlying -lattice `L` (see [`norm(::ZLat)`](@ref)). +lattice `L` (see [`norm(::ZZLat)`](@ref)). """ -norm(Lf::LatWithIsom) = norm(lattice(Lf))::QQFieldElem +norm(Lf::ZZLatWithIsom) = norm(lattice(Lf))::QQFieldElem @doc raw""" - is_positive_definite(Lf::LatWithIsom) -> Bool + is_positive_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is positive definite (see [`is_positive_definite(::ZLat)`](@ref)). +lattice `L` is positive definite (see [`is_positive_definite(::ZZLat)`](@ref)). """ -is_positive_definite(Lf::LatWithIsom) = is_positive_definite(lattice(Lf))::Bool +is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf))::Bool @doc raw""" - is_negative_definite(Lf::LatWithIsom) -> Bool + is_negative_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is negative definite (see [`is_positive_definite(::ZLat)`](@ref)). +lattice `L` is negative definite (see [`is_positive_definite(::ZZLat)`](@ref)). """ -is_negative_definite(Lf::LatWithIsom) = is_negative_definite(lattice(Lf))::Bool +is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf))::Bool @doc raw""" - is_definite(Lf::LatWithIsom) -> Bool + is_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is definite (see [`is_definite(::ZLat)`](@ref)). +lattice `L` is definite (see [`is_definite(::ZZLat)`](@ref)). """ -is_definite(Lf::LatWithIsom) = is_definite(lattice(Lf))::Bool +is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf))::Bool @doc raw""" - minimum(Lf::LatWithIsom) -> QQFieldElem + minimum(Lf::ZZLatWithIsom) -> QQFieldElem Given a positive definite lattice with isometry $(L, f)$, return the minimum -of the underlying lattice `L` (see [`minimum(::ZLat)`](@ref)). +of the underlying lattice `L` (see [`minimum(::ZZLat)`](@ref)). """ -function minimum(Lf::LatWithIsom) +function minimum(Lf::ZZLatWithIsom) @req is_positive_definite(Lf) "Underlying lattice must be positive definite" return minimum(lattice(Lf)) end @doc raw""" - is_integral(Lf::LatWithIsom) -> Bool + is_integral(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice -is integral, i.e. whether its scale is an integer (see [`scale(::LatWithIsom)`](@ref)). +is integral, i.e. whether its scale is an integer (see [`scale(::ZZLatWithIsom)`](@ref)). """ -is_integral(Lf::LatWithIsom) = is_integral(lattice(Lf))::Bool +is_integral(Lf::ZZLatWithIsom) = is_integral(lattice(Lf))::Bool @doc raw""" - degree(Lf::LatWithIsom) -> Int + degree(Lf::ZZLatWithIsom) -> Int Given a lattice with isometry $(L, f)$ inside the quadratic space $(V, \Phi)$, return the dimension of `V` as a $\mathbb Q$ vector space. """ -degree(Lf::LatWithIsom) = degree(lattice(Lf))::Int +degree(Lf::ZZLatWithIsom) = degree(lattice(Lf))::Int @doc raw""" - is_even(Lf::LatWithIsom) -> Bool + is_even(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice -`L` is even, i.e. whether its norm is an even integer ([`norn(::LatWithIsom)`](@ref)). +`L` is even, i.e. whether its norm is an even integer ([`norn(::ZZLatWithIsom)`](@ref)). -Note that to be even, `L` must be integral (see [`is_integral(::ZLat)`](@ref)). +Note that to be even, `L` must be integral (see [`is_integral(::ZZLat)`](@ref)). """ -is_even(Lf::LatWithIsom) = iseven(lattice(Lf))::Bool +is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf))::Bool @doc raw""" - discriminant(Lf::LatWithIsom) -> QQFieldElem + discriminant(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the discriminant of the underlying -lattice `L` (see [`discriminant(::ZLat)`](@ref)). +lattice `L` (see [`discriminant(::ZZLat)`](@ref)). """ -discriminant(Lf::LatWithIsom) = discriminant(lattice(Lf))::QQFieldElem +discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf))::QQFieldElem @doc raw""" - signature_tuple(Lf::LatWithIsom) -> Tuple{Int, Int, Int} + signature_tuple(Lf::ZZLatWithIsom) -> Tuple{Int, Int, Int} Given a lattice with isometry $(L, f)$, return the signature tuple of the -underlying lattice `L` (see [`signature_tuple(::ZLat)`](@ref)). +underlying lattice `L` (see [`signature_tuple(::ZZLat)`](@ref)). """ -signature_tuple(Lf::LatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} +signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} ############################################################################### # @@ -214,9 +214,9 @@ signature_tuple(Lf::LatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, ############################################################################### @doc raw""" - lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, + lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, ambient_representation = true) - -> LatWithIsom + -> ZZLatWithIsom Given a $\mathbb Z$-lattice `L` and a matrix `f`, if `f` defines an isometry of `L` of order `n`, return the corresponding lattice with isometry pair $(L, f)$. @@ -226,10 +226,10 @@ ambient space of `L` and the induced isometry on `L` is automatically computed. Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, +function lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, ambient_representation::Bool = true) if rank(L) == 0 - return LatWithIsom(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) + return ZZLatWithIsom(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) end if check @@ -255,23 +255,28 @@ function lattice_with_isometry(L::ZLat, f::QQMatrix; check::Bool = true, if check @req f*gram_matrix(L)*transpose(f) == gram_matrix(L) "f does not define an isometry of L" @req f_ambient*gram_matrix(ambient_space(L))*transpose(f_ambient) == gram_matrix(ambient_space(L)) "f_ambient is not an isometry of the ambient space of L" - @hassert :LatWithIsom 1 basis_matrix(L)*f_ambient == f*basis_matrix(L) + @hassert :ZZLatWithIsom 1 basis_matrix(L)*f_ambient == f*basis_matrix(L) end - return LatWithIsom(L, f, f_ambient, n)::LatWithIsom + return ZZLatWithIsom(L, f, f_ambient, n)::ZZLatWithIsom end @doc raw""" - lattice_with_isometry(L::ZLat) -> LatWithIsom + lattice_with_isometry(L::ZZLat; neg::Bool = false) -> ZZLatWithIsom Given a $\mathbb Z$-lattice `L` return the lattice with isometry pair $(L, f)$, where `f` corresponds to the identity mapping of `L`. + +If `neg` is set to `true`, then the isometry `f` is negative the identity of `L`. """ -function lattice_with_isometry(L::ZLat) +function lattice_with_isometry(L::ZZLat; neg::Bool = false) d = degree(L) f = identity_matrix(QQ, d) - return lattice_with_isometry(L, f, check = false, ambient_representation = true)::LatWithIsom + if neg + f = -f + end + return lattice_with_isometry(L, f, check = false, ambient_representation = true)::ZZLatWithIsom end ############################################################################### @@ -281,97 +286,97 @@ end ############################################################################### @doc raw""" - rescale(Lf::LatWithIsom, a::RationalUnion) -> LatWithIsom + rescale(Lf::ZZLatWithIsom, a::RationalUnion) -> ZZLatWithIsom Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lattice -with isometry $(L(a), f)$ (see [`rescale(::ZLat, ::RationalUnion)`](@ref)). +with isometry $(L(a), f)$ (see [`rescale(::ZZLat, ::RationalUnion)`](@ref)). """ -function rescale(Lf::LatWithIsom, a::Hecke.RationalUnion) +function rescale(Lf::ZZLatWithIsom, a::Hecke.RationalUnion) return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), check=false) end @doc raw""" - dual(Lf::LatWithIsom) -> LatWithIsom + dual(Lf::ZZLatWithIsom) -> ZZLatWithIsom Given a lattice with isometry $(L, f)$ inside the space $(V, \Phi)$, such that `f` is induced by an isometry `g` of $(V, \Phi)$, return the lattice with isometry $(L^{\vee}, h)$ -where $L^{\vee}$ is the dual of `L` in $(V, \Phi)$ (see [`dual(::ZLat)`](@ref)) and `h` is +where $L^{\vee}$ is the dual of `L` in $(V, \Phi)$ (see [`dual(::ZZLat)`](@ref)) and `h` is induced by `g`. """ -function dual(Lf::LatWithIsom) +function dual(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), check = false) end @doc raw""" - lll(Lf::LatWithIsom) -> LatWithIsom + lll(Lf::ZZLatWithIsom) -> ZZLatWithIsom Given a lattice with isometry $(L, f)$, return the same lattice with isometry with a different -basis matrix for `L` (see [`basis_matrix(::ZLat)`](@ref)) obtained by performing an LLL-reduction -on the associated gram matrix of `L` (see [`gram_matrix(::ZLat)`](@ref)). +basis matrix for `L` (see [`basis_matrix(::ZZLat)`](@ref)) obtained by performing an LLL-reduction +on the associated gram matrix of `L` (see [`gram_matrix(::ZZLat)`](@ref)). Note that matrix representing the action of `f` on `L` changes but the global action on the ambient space of `L` stays the same. """ -function lll(Lf::LatWithIsom) +function lll(Lf::ZZLatWithIsom) f = ambient_isometry(Lf) L2 = lll(lattice(Lf), same_ambient=true) return lattice_with_isometry(L2, f, ambient_representation = true) end @doc raw""" - direct_sum(x::Vector{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} - direct_sum(x::Vararg{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} + direct_sum(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} + direct_sum(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} Given a collection of lattices with isometries $(L_1, f_1) \ldots, (L_n, f_n)$, return the lattice with isometry $(L, f)$ together with the injections $L_i \to L$, where `L` is the direct sum $L := L_1 \oplus \ldots \oplus L_n$ and `f` is the isometry of `L` induced by the diagonal actions of the $f_i$'s. -For objects of type `LatWithIsom`, finite direct sums and finite direct products +For objects of type `ZZLatWithIsom`, finite direct sums and finite direct products agree and they are therefore called biproducts. If one wants to obtain $(L, f)$ as a direct product with the projections $L \to L_i$, one should call `direct_product(x)`. If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ and the projections $L \to L_i$, one should call `biproduct(x)`. """ -function direct_sum(x::Vector{LatWithIsom}) +function direct_sum(x::Vector{ZZLatWithIsom}) @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" W, inj = direct_sum(lattice.(x)) f = block_diagonal_matrix(ambient_isometry.(x)) return lattice_with_isometry(W, f, check=false), inj end -direct_sum(x::Vararg{LatWithIsom}) = direct_sum(collect(x)) +direct_sum(x::Vararg{ZZLatWithIsom}) = direct_sum(collect(x)) @doc raw""" - direct_product(x::Vector{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} - direct_product(x::Vararg{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor} + direct_product(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} + direct_product(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} Given a collection of lattices with isometries $(L_1, f_1), \ldots, (L_n, f_n)$, return the lattice with isometry $(L, f)$ together with the projections $L \to L_i$, where `L` is the direct product $L := L_1 \times \ldots \times L_n$ and `f` is the isometry of `L` induced by the diagonal actions of the $f_i$'s. -For objects of type `LatWithIsom`, finite direct sums and finite direct products +For objects of type `ZZLatWithIsom`, finite direct sums and finite direct products agree and they are therefore called biproducts. If one wants to obtain $(L, f)$ as a direct sum with the injections $L_i \to L$, one should call `direct_sum(x)`. If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ and the projections $L \to L_i$, one should call `biproduct(x)`. """ -function direct_product(x::Vector{LatWithIsom}) +function direct_product(x::Vector{ZZLatWithIsom}) @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" W, proj = direct_product(lattice.(x)) f = block_diagonal_matrix(ambient_isometry.(x)) return lattice_with_isometry(W, f, check=false), proj end -direct_product(x::Vararg{LatWithIsom}) = direct_product(collect(x)) +direct_product(x::Vararg{ZZLatWithIsom}) = direct_product(collect(x)) @doc raw""" - biproduct(x::Vector{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} - biproduct(x::Vararg{LatWithIsom}) -> LatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} + biproduct(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} + biproduct(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} Given a collection of lattices with isometries $(L_1, f_1), \ldots, (L_n, f_n)$, return the lattice with isometry $(L, f)$ together with the injections @@ -379,21 +384,21 @@ $L_i \to L$ and the projections $L \to L_i$, where `L` is the biproduct $L := L_1 \oplus \ldots \oplus L_n$ and `f` is the isometry of `L` induced by the diagonal actions of the $f_i$'s. -For objects of type `LatWithIsom`, finite direct sums and finite direct products +For objects of type `ZZLatWithIsom`, finite direct sums and finite direct products agree and they are therefore called biproducts. If one wants to obtain $(L, f)$ as a direct sum with the injections $L_i \to L$, one should call `direct_sum(x)`. If one wants to obtain $(L, f)$ as a direct product with the projections $L \to L_i$, one should call `direct_product(x)`. """ -function biproduct(x::Vector{LatWithIsom}) +function biproduct(x::Vector{ZZLatWithIsom}) @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" W, inj, proj = biproduct(lattice.(x)) f = block_diagonal_matrix(ambient_isometry.(x)) return lattice_with_isometry(W, f, check=false), inj, proj end -biproduct(x::Vararg{LatWithIsom}) = biproduct(collect(x)) +biproduct(x::Vararg{ZZLatWithIsom}) = biproduct(collect(x)) ############################################################################### # @@ -402,7 +407,7 @@ biproduct(x::Vararg{LatWithIsom}) = biproduct(collect(x)) ############################################################################### @doc raw""" - is_of_hermitian_type(Lf::LatWithIsom) -> Bool + is_of_hermitian_type(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the minimal polynomial of the underlying isometry `f` is (irreducible) cyclotomic. @@ -411,7 +416,7 @@ Note that if $(L, f)$ is of hermitian type with `f` of order `n`, then `L` can be seen as a hermitian lattice over the order $\mathbb{Z}[\zeta_n]$ where $\zeta_n$ is a primitive $n$-th root of unity. """ -function is_of_hermitian_type(Lf::LatWithIsom) +function is_of_hermitian_type(Lf::ZZLatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" n = order_of_isometry(Lf) if n == -1 || !is_finite(n) @@ -421,7 +426,7 @@ function is_of_hermitian_type(Lf::LatWithIsom) end @doc raw""" - hermitian_structure(Lf::LatWithIsom) -> HermLat + hermitian_structure(Lf::ZZLatWithIsom) -> HermLat Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the underlying isometry `f` is cyclotomic, return the hermitian structure of the @@ -430,7 +435,7 @@ order of `f`. If it exists, the hermitian structure is stored. """ -@attr HermLat function hermitian_structure(Lf::LatWithIsom) +@attr HermLat function hermitian_structure(Lf::ZZLatWithIsom) @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" f = isometry(Lf) @@ -447,13 +452,13 @@ end ############################################################################### @doc raw""" - discriminant_group(Lf::LatWithIsom) -> TorQuadMod, AutomorphismGroupElem + discriminant_group(Lf::ZZLatWithIsom) -> TorQuadModule, AutomorphismGroupElem Given an integral lattice with isometry $(L, f)$, return the discriminant group `q` of the underlying lattice `L` as well as this image of the underlying isometry `f` inside $O(q)$. """ -function discriminant_group(Lf::LatWithIsom) +function discriminant_group(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" L = lattice(Lf) f = ambient_isometry(Lf) @@ -463,14 +468,14 @@ function discriminant_group(Lf::LatWithIsom) end @doc raw""" - image_centralizer_in_Oq(Lf::LatWithIsom) -> AutomorphismGroup{TorQuadModule} + image_centralizer_in_Oq(Lf::ZZLatWithIsom) -> AutomorphismGroup{TorQuadModule} Given an integral lattice with isometry $(L, f)$, return the image $G_L$ in $O(q_L, \bar{f})$ of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ denotes the discriminant group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. """ -@attr AutomorphismGroup{TorQuadModule} function image_centralizer_in_Oq(Lf::LatWithIsom) +@attr AutomorphismGroup{TorQuadModule} function image_centralizer_in_Oq(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" n = order_of_isometry(Lf) L = lattice(Lf) @@ -504,7 +509,7 @@ end # ############################################################################### -function _real_kernel_signatures(L::ZLat, M) +function _real_kernel_signatures(L::ZZLat, M) C = base_ring(M) bL = basis_matrix(L) GL = gram_matrix(ambient_space(L)) @@ -516,8 +521,8 @@ function _real_kernel_signatures(L::ZLat, M) newGC = Hecke._gram_schmidt(newGC, C)[1] diagC = diagonal(newGC) - @hassert :LatWithIsom 1 all(z -> isreal(z), diagC) - @hassert :LatWithIsom 1 all(z -> !iszero(z), diagC) + @hassert :ZZLatWithIsom 1 all(z -> isreal(z), diagC) + @hassert :ZZLatWithIsom 1 all(z -> !iszero(z), diagC) k1 = count(z -> z > 0, diagC) k2 = length(diagC) - k1 @@ -526,7 +531,7 @@ function _real_kernel_signatures(L::ZLat, M) end @doc raw""" - signatures(Lf::LatWithIsom) -> Dict{Int, Tuple{Int, Int}} + signatures(Lf::ZZLatWithIsom) -> Dict{Int, Tuple{Int, Int}} Given a lattice with isometry $(L, f)$ where the minimal polynomial of `f` is irreducible cyclotomic, return the signatures of $(L, f)$. @@ -536,7 +541,7 @@ is the order of `f`, then for each $1 \leq i \leq n/2$ such that $(i, n) = 1$, the $i$-th signature of $(L, f)$ is given by the signatures of the real quadratic form $\Ker(f + f^{-1} - z^i - z^{-i})$. """ -function signatures(Lf::LatWithIsom) +function signatures(Lf::ZZLatWithIsom) @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" L = lattice(Lf) f = isometry(Lf) @@ -567,16 +572,16 @@ function _divides(k::IntExt, n::Int) end @doc raw""" - kernel_lattice(Lf::LatWithIsom, p::Union{fmpz_poly, QQPolyRingElem}) - -> LatWithIsom + kernel_lattice(Lf::ZZLatWithIsom, p::Union{fmpz_poly, QQPolyRingElem}) + -> ZZLatWithIsom Given a lattice with isometry $(L, f)$ and a polynomial `p` with rational coefficients, return the sublattice $M := \ker(p(f))$ of the underlying lattice `L` with isometry `f`, together with the restriction $f_{\mid M}$. """ -kernel_lattice(Lf::LatWithIsom, p::Union{ZZPolyRingElem, QQPolyRingElem}) +kernel_lattice(Lf::ZZLatWithIsom, p::Union{ZZPolyRingElem, QQPolyRingElem}) -function kernel_lattice(Lf::LatWithIsom, p::QQPolyRingElem) +function kernel_lattice(Lf::ZZLatWithIsom, p::QQPolyRingElem) n = order_of_isometry(Lf) L = lattice(Lf) f = isometry(Lf) @@ -585,39 +590,39 @@ function kernel_lattice(Lf::LatWithIsom, p::QQPolyRingElem) k, K = left_kernel(change_base_ring(ZZ, d*M)) L2 = lattice_in_same_ambient_space(L, K*basis_matrix(L)) f2 = solve_left(change_base_ring(QQ, K), K*f) - @hassert :LatWithIsom 1 f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) + @hassert :ZZLatWithIsom 1 f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) chi = parent(p)(collect(coefficients(minpoly(f2)))) chif = parent(p)(collect(coefficients(minpoly(Lf)))) _chi = gcd(p, chif) - @hassert :LatWithIsom 1 (rank(L2) == 0) || (chi == _chi) + @hassert :ZZLatWithIsom 1 (rank(L2) == 0) || (chi == _chi) return lattice_with_isometry(L2, f2, ambient_representation = false) end -kernel_lattice(Lf::LatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change_base_ring(QQ, p)) +kernel_lattice(Lf::ZZLatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change_base_ring(QQ, p)) @doc raw""" - kernel_lattice(Lf::LatWithIsom, l::Integer) -> LatWithIsom + kernel_lattice(Lf::ZZLatWithIsom, l::Integer) -> ZZLatWithIsom Given a lattice with isometry $(L, f)$ and an integer `l`, return the kernel lattice of $(L, f)$ associated to the `l`-th cyclotomic polynomial. """ -function kernel_lattice(Lf::LatWithIsom, l::Integer) +function kernel_lattice(Lf::ZZLatWithIsom, l::Integer) @req _divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" p = cyclotomic_polynomial(l) return kernel_lattice(Lf, p) end @doc raw""" - invariant_lattice(Lf::LatWithIsom) -> LatWithIsom + invariant_lattice(Lf::ZZLatWithIsom) -> ZZLatWithIsom Given a lattice with isometry $(L, f)$, return the invariant lattice $L^f$ of $(L, f)$ together with the restriction of `f` to $L^f$ (which is the identity in this case) """ -invariant_lattice(Lf::LatWithIsom) = kernel_lattice(Lf, 1) +invariant_lattice(Lf::ZZLatWithIsom) = kernel_lattice(Lf, 1) @doc raw""" - coinvariant_lattice(Lf::LatWithIsom) -> LatWithIsom + coinvariant_lattice(Lf::ZZLatWithIsom) -> ZZLatWithIsom Given a lattice with isometry $(L, f)$, return the coinvariant lattice $L_f$ of $(L, f)$ together with the restriction of `f` to $L_f$. @@ -625,7 +630,7 @@ $(L, f)$ together with the restriction of `f` to $L_f$. The coinvariant lattice $L_f$ of $(L, f)$ is the orthogonal complement in `L` of the invariant lattice $L_f$. """ -function coinvariant_lattice(Lf::LatWithIsom) +function coinvariant_lattice(Lf::ZZLatWithIsom) chi = minpoly(Lf) if chi(1) == 0 R = parent(chi) @@ -642,8 +647,8 @@ end ############################################################################### @doc raw""" - type(Lf::LatWithIsom) - -> Dict{Int, Tuple{ <: Union{ZGenus, HermGenus}, ZGenus}} + type(Lf::ZZLatWithIsom) + -> Dict{Int, Tuple{ <: Union{ZZGenus, HermGenus}, ZZGenus}} Given a lattice with isometry $(L, f)$ with `f` of finite order `n`, return the type of $(L, f)$. @@ -654,7 +659,7 @@ $H_k$ of the lattice $\Ker(\Phi_k(f))$ viewed as a hermitian $\mathbb{Z}[\zeta_k lattice (so a $\mathbb{Z}$-lattice for k= 1, 2) and of the genus $A_k$ of the $\mathbb{Z}$-lattice $\Ker(f^k-1)$. """ -@attr function type(Lf::LatWithIsom) +@attr function type(Lf::ZZLatWithIsom) L = lattice(Lf) f = isometry(Lf) n = order_of_isometry(Lf) @@ -675,11 +680,11 @@ $\mathbb{Z}$-lattice $\Ker(f^k-1)$. end @doc raw""" - is_of_type(Lf::LatWithIsom, t::Dict) -> Bool + is_of_type(Lf::ZZLatWithIsom, t::Dict) -> Bool Given a lattice with isometry $(L, f)$, return whether $(L, f)$ is of type `t`. """ -function is_of_type(L::LatWithIsom, t::Dict) +function is_of_type(L::ZZLatWithIsom, t::Dict) @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" divs = sort(collect(keys(t))) x = gen(Hecke.Globals.Qx) @@ -697,12 +702,12 @@ function is_of_type(L::LatWithIsom, t::Dict) end @doc raw""" - is_of_same_type(Lf::LatWithIsom, Mg::LatWithIsom) -> Bool + is_of_same_type(Lf::ZZLatWithIsom, Mg::ZZLatWithIsom) -> Bool Given two lattices with isometry $(L, f)$ and $(M, g)$, return whether they are of the same type. """ -function is_of_same_type(L::LatWithIsom, M::LatWithIsom) +function is_of_same_type(L::ZZLatWithIsom, M::ZZLatWithIsom) @req is_finite(order_of_isometry(L)*order_of_isometry(M)) "Type is defined only for finite order isometries" order_of_isometry(L) != order_of_isometry(M) && return false genus(L) != genus(M) && return false @@ -727,12 +732,12 @@ end # ############################################################################### -function to_oscar(Lf::LatWithIsom) +function to_oscar(Lf::ZZLatWithIsom) L = lattice(Lf) f = ambient_isometry(Lf) println(stdout, "B = matrix(QQ, $(rank(L)), $(degree(L)), " , basis_matrix(L), " );") println(stdout, "G = matrix(QQ, $(degree(L)), $(degree(L)), ", gram_matrix(ambient_space(L)), " );") - println(stdout, "L = Zlattice(B, gram = G);") + println(stdout, "L = integer_lattice(B, gram = G);") println(stdout, "f = matrix(QQ, $(degree(L)), $(degree(L)), ", f, " );") println(stdout, "Lf = lattice_with_isometry(L, f);") end diff --git a/experimental/LatticesWithIsometry/src/printings.jl b/experimental/LatticesWithIsometry/src/printings.jl index a90dc203c3d6..db73656592d9 100644 --- a/experimental/LatticesWithIsometry/src/printings.jl +++ b/experimental/LatticesWithIsometry/src/printings.jl @@ -1,4 +1,4 @@ -function Base.show(io::IO, ::MIME"text/plain", Lf::LatWithIsom) +function Base.show(io::IO, ::MIME"text/plain", Lf::ZZLatWithIsom) io = AbstractAlgebra.pretty(io) println(io, lattice(Lf)) n = order_of_isometry(Lf) @@ -9,11 +9,11 @@ function Base.show(io::IO, ::MIME"text/plain", Lf::LatWithIsom) println(io, "with isometry of infinite order") end println(io, "given by") - print(IOContext(io, :compact => true), isometry(Lf)) + show(io, MIME"text/plain"(), isometry(Lf)) print(io, AbstractAlgebra.Dedent()) end -function Base.show(io::IO, Lf::LatWithIsom) +function Base.show(io::IO, Lf::ZZLatWithIsom) if get(io, :supercompact, false) print(io, "Integer lattice with isometry") else diff --git a/experimental/LatticesWithIsometry/src/types.jl b/experimental/LatticesWithIsometry/src/types.jl index a606a1ccfd10..cd0bc95092f3 100644 --- a/experimental/LatticesWithIsometry/src/types.jl +++ b/experimental/LatticesWithIsometry/src/types.jl @@ -1,23 +1,23 @@ @doc raw""" - LatWithIsom + ZZLatWithIsom A container type for pairs `(L, f)` consisting on an integer lattice `L` of -type `ZLat` and an isometry `f` given as a `QQMatrix` representing the action +type `ZZLat` and an isometry `f` given as a `QQMatrix` representing the action on a given basis of `L`. The associated action `f_ambient` on the ambient space of `L` as well as the order `n` of `f` are also stored. -To construct an object of type `LatWithIsom`, see the set of functions called +To construct an object of type `ZZLatWithIsom`, see the set of functions called [`lattice_with_isometry`](@ref) """ -@attributes mutable struct LatWithIsom - Lb::ZLat +@attributes mutable struct ZZLatWithIsom + Lb::ZZLat f::QQMatrix f_ambient::QQMatrix n::IntExt - function LatWithIsom(Lb::ZLat, f::QQMatrix, f_ambient::QQMatrix, n::IntExt) + function ZZLatWithIsom(Lb::ZZLat, f::QQMatrix, f_ambient::QQMatrix, n::IntExt) z = new() z.Lb = Lb z.f = f From 4f7117a3ca6d81853487c1b24123afe119a1c7ed Mon Sep 17 00:00:00 2001 From: StevellM Date: Sat, 27 May 2023 22:32:30 +0200 Subject: [PATCH 54/76] delete --- experimental/LatticesWithIsometry/README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/experimental/LatticesWithIsometry/README.md b/experimental/LatticesWithIsometry/README.md index f929e09246a0..659e72965d3a 100644 --- a/experimental/LatticesWithIsometry/README.md +++ b/experimental/LatticesWithIsometry/README.md @@ -19,7 +19,7 @@ divisors. The methods we resort to for this purpose are developed in the paper We also provide some algorithms computing isomorphism classes of primitive embeddings of even lattices following [Nikulin]. More precisely, the two functions `primitive_embeddings_in_primary_lattice` and -`primitive_embeddings_of_elementary_lattice` offer, under certain conditions, +`primitive_embeddings_of_primary_lattice` offer, under certain conditions, the possibility of obtaining representatives of such classes of primitive embeddings. Note nonetheless that these functions are not efficient in the case were the discriminant groups are large. @@ -31,16 +31,11 @@ currently being tested on a larger scale to test its reliability. Moreover, there are still computational bottlenecks due to non optimized algorithms. Among the possible improvements and extensions: -* Improve the lattice enumerations by upgrading the neighbor algorithms using - fast algorithms computing orbits of isotropic lines over finite fields; -* Improve the computation of the orthogonal group for definite lattices; * Implement methods about for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); * Implement methods for all kinds of equivariant primitive extensions (not necessarily admissibles); -* Improve some enumerations algorithms using theoretical results about types of - lattices with isometries of finite order; * Import the methods for extending trivial discriminant actions on lattice whose discriminant group is an abelian $p$-group. From 304bb6a8656badb8d3af71f4dbcd4cb737026986 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 30 May 2023 16:39:10 +0200 Subject: [PATCH 55/76] more stuff --- Project.toml | 2 +- experimental/LatticesWithIsometry/README.md | 2 +- .../docs/src/enumeration.md | 82 +++++++++++ .../docs/src/introduction.md | 9 +- .../docs/src/latwithisom.md | 10 ++ .../LatticesWithIsometry/src/embeddings.jl | 139 +++++++----------- .../LatticesWithIsometry/src/enumeration.jl | 9 +- .../LatticesWithIsometry/src/exports.jl | 1 + .../src/lattices_with_isometry.jl | 78 +++++++++- 9 files changed, 232 insertions(+), 100 deletions(-) diff --git a/Project.toml b/Project.toml index bbfe033e0c2f..3961c916efa0 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ AbstractAlgebra = "0.29.3" AlgebraicSolving = "0.3.0" DocStringExtensions = "0.8, 0.9" GAP = "0.9.4" -Hecke = "0.18.10" +Hecke = "0.18.14" JSON = "^0.20, ^0.21" Nemo = "0.33.7" Polymake = "0.9.0" diff --git a/experimental/LatticesWithIsometry/README.md b/experimental/LatticesWithIsometry/README.md index 659e72965d3a..c12aa8d8e73e 100644 --- a/experimental/LatticesWithIsometry/README.md +++ b/experimental/LatticesWithIsometry/README.md @@ -41,7 +41,7 @@ Among the possible improvements and extensions: ## Currently application of this project -The project with initiated by S. Brandhorst and T. Hofmann for classifying +The project was initiated by S. Brandhorst and T. Hofmann for classifying finite subgroups of automorphisms of K3 surfaces. Our current goal is to use this code, and further extension of it, to classify finite subgroups of automorphisms and birational transformations on *hyperkaehler manifolds*, which diff --git a/experimental/LatticesWithIsometry/docs/src/enumeration.md b/experimental/LatticesWithIsometry/docs/src/enumeration.md index e69de29bb2d1..092a53768cd3 100644 --- a/experimental/LatticesWithIsometry/docs/src/enumeration.md +++ b/experimental/LatticesWithIsometry/docs/src/enumeration.md @@ -0,0 +1,82 @@ +```@meta +CurrentModule = Oscar +``` + +One of the main feature of this project is the enumeration of lattices with +isometry of finite order with at most two prime divisors. This is the content +of [BH23] which has been implemented. We guide the user here to the global +aspects of the available theory, and we refer to the paper [BH23] for further +reference. + +# Admissible triples + +Roughly speaking, for a prime number $p$, a *$p$-admissible triple* `(A, B, C)` +is a triple of integer lattices such that `C` can be obtained has a primitive +extension $A \perp B \to C$ where on can glue along $p$-elementary subgroups of +the respective discriminant groups of `A` and `B`. For instance, if $f$ is an +isometry of `C` of prime order `p`, then for $A := \Ker \Phi_1(f)$ and +$B := \Ker \Phi_p(f)$, one had that `(A, B, C)` is $p$-admissible +(see [BH13, Lemma 4.15.]). + +We use Definition 4.13. and Algorithm 1 of [BH23] to implement the necessary +tools for working with admissible triples. Most of the computations consists of +genus symbol manipulations and combinatorics. The code also relies on +enumeration of integer genera with given signatures, determinant and bounded +scale valuations for the Jordan components at all the relevant primes (see +[`integer_genera`](@ref)). + +```@docs +admissible_triples(:::ZZGenus, p::Integer) +is_admissible_triple(::ZZGenus, ::ZZGenus, ::ZZGenus, ::Integer) +``` + +Note that admissible triples are mainly used for enumerating lattices with +isometry of a given order and in a given genus. + +# Enumeration functions + +We give an overview of the functions implemented for the enumeration of the +isometries of integral integer lattices. For more details such as the proof of +the algorithm or the theory behind them, we refer to our reference paper [BH23]. + +## Global function + +As we will see later, the algorithms from [BH23] are specialized on the +requirement for the input and regular users might not be able to choose between +the functions to choose there. We therefore provide a general function which +allows one to enumerate lattices with isometry of a given order and in a given +genus. The only requirements are to provide a genus symbol, or a lattice from +this genus, and the order wanted (as long as the number of distinct prime +divisors is at most 2). + +```@docs +enumerate_classes_of_lattices_with_isometry(::ZZLat, ::Hecke.IntegerUnion) +``` + +As a remark: of $n = p^dq^e$ is the chosen order, with $p < q$ prime numbers, +the previous function computes first iteratively representatives for all classes +with isometry in the given genus of order $q^e$. Then, the function increases +iteratively the order up to $p^dq^e$. + +## Underlying machinery + +Here is a list of the algorithmic machinery provided by [BH23] used previously +to enumerate lattices with isometry: + +```@docs +representatives_of_hermitian_type(::ZZLatWithIsom, ::Int) +representatives_of_hermitian_type(::Dict, ::Int) +splitting_of_hermitian_prime_power(::ZZLatWithIsom, ::Int) +splitting_of_hermitian_prime_power(::Dict, ::Int) +splitting_of_prime_power(::ZZLatWithIsom, ::Int, ::Int) +splitting_of_pure_mixed_prime_power(::ZZLatWithIsom, ::Int) +splitting_of_mixed_prime_power(::ZZLatWithIsom, ::Int, ::Int) +``` + +Note that an important feature from the [BH23]-program is the notion of +*admissible gluings* and equivariant primitive embeddings for admissible pairs. +In the next chapter, we will develop about the features regarding primitive +embeddings and their equivariant extension. We use this basis to introduce the +method `admissible_equivariant_primitive_extension` (Algorithm 2 in [BH23]) +which is the major tool making the previous enumeration possible and fast, from +an algorithmic point of view. diff --git a/experimental/LatticesWithIsometry/docs/src/introduction.md b/experimental/LatticesWithIsometry/docs/src/introduction.md index f6113d19aa81..9a0e6d72a78f 100644 --- a/experimental/LatticesWithIsometry/docs/src/introduction.md +++ b/experimental/LatticesWithIsometry/docs/src/introduction.md @@ -35,20 +35,17 @@ currently being tested on a larger scale to test its reliability. Moreover, there are still computational bottlenecks due to non optimized algorithms. Among the possible improvements and extensions: -* Improve the lattice enumerations by upgrading the neighbor algorithms using - fast algorithms computing orbits of isotropic lines over finite fields; -* Improve the computation of the orthogonal group for definite lattices; * Implement methods about for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); * Implement methods for all kinds of equivariant primitive extensions (not necessarily admissible); -* Improve some enumeration algorithms using theoretical results about types of - lattices with isometries of finite order; +* Import the methods for extending trivial discriminant actions on lattice whose + discriminant group is an abelian $p$-group. ## Currently application of this project -The project with initiated by S. Brandhorst and T. Hofmann for classifying +The project was initiated by S. Brandhorst and T. Hofmann for classifying finite subgroups of automorphisms of K3 surfaces. Our current goal is to use this code, and further extension of it, to classify finite subgroups of automorphisms and birational transformations on *hyperkaehler manifolds*, which diff --git a/experimental/LatticesWithIsometry/docs/src/latwithisom.md b/experimental/LatticesWithIsometry/docs/src/latwithisom.md index ea6dbca76f44..3e00f7568cbd 100644 --- a/experimental/LatticesWithIsometry/docs/src/latwithisom.md +++ b/experimental/LatticesWithIsometry/docs/src/latwithisom.md @@ -219,6 +219,16 @@ coinvariant_lattice(::ZZLatWithIsom) invariant_lattice(::ZZLatWithIsom) ``` +Similarly, we provide the possibility to compute invariant and coinvariant +sublattices given an orthogonal representation `G` in matrix form of a finite +group on a given lattice `L`: + +```@docs +coinvariant_lattice(::ZZLat, ::MatrixGroup) +invariant_lattice(::ZZLat, ::MatrixGroup) +invariant_coinvariant_pair(::ZZLat, ::MatrixGroup) +``` + ## Signatures We conclude this introduction about standard functionalities for lattices with diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index 62d8ec2d981c..da9685646fed 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -120,11 +120,6 @@ end # ############################################################################## -function _on_subgroup_automorphic(H::Oscar.GAPGroup, g::AutomorphismGroupElem) - G = domain(parent(g)) - return sub(G, g.(gens(H)))[1] -end - function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) gene = TorQuadModuleElem[g(q(lift(t))) for t in gens(T)] @@ -134,14 +129,28 @@ end function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) - togap = get_attribute(O, :to_gap) - tooscar = get_attribute(O, :to_oscar) - A = codomain(togap) - OA = automorphism_group(A) - OinOA, _ = sub(OA, OA.([g.X for g in gens(O)])) - N, _ = sub(A, togap.(i.(gens(domain(i))))) - stab, _ = stabilizer(OinOA, N, _on_subgroup_automorphic) - return sub(O, O.([h.X for h in gens(stab)])) + N, _ = sub(q, i.(gens(domain(i)))) + return stabilizer(O, N, _on_subgroup_automorphic) +end + +function _subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadModule}; order::Hecke.IntegerUnion = -1) + q = domain(O) + it = order == -1 ? subgroups(q) : subgroups(q, order=order) + subs = TorQuadModule[j[1] for j in it] + m = gset(O, _on_subgroup_automorphic, subs) + orbs = orbits(m) + res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] + for orb in orbs + rep = representative(orb) + stab, _ = stabilizer(O, rep, _on_subgroup_automorphic) + push!(res, (rep, stab)) + end + return res +end + +function _classes_automorphic_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) + sors = _subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) + return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) end # The underlying abelian groups of H and V are elementary abelian p-groups, f is @@ -277,34 +286,6 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end -function _subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadModule}; order::Hecke.IntegerUnion = -1) - togap = get_attribute(O, :to_gap) - tooscar = get_attribute(O, :to_oscar) - q = domain(O) - A = abelian_group(q) - it = order == -1 ? subgroups(A) : subgroups(A, order=order) - Agap = codomain(togap) - coAgap = [sub(Agap, togap.(q.(j[2].(gens(j[1])))))[1] for j in it] - OAgap = automorphism_group(Agap) - OinOAgap, j = sub(OAgap, OAgap.([g.X for g in gens(O)])) - m = gset(OinOAgap, _on_subgroup_automorphic, coAgap) - orbs = orbits(m) - res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] - for orb in orbs - rep = representative(orb) - stab, _ = stabilizer(OinOAgap, rep, _on_subgroup_automorphic) - _, rep = sub(q, TorQuadModuleElem[tooscar(Agap(g)) for g in gens(rep)]) - stab, _ = sub(O, O.([h.X for h in gens(stab)])) - push!(res, (rep, stab)) - end - return res -end - -function _classes_automorphic_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) - sors = _subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) - return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) -end - ################################################################################# # # Embeddings in elementary lattices @@ -320,7 +301,7 @@ end # # We follow the second definition of Nikulin, i.e. we classify up to the actions # of O(M) and O(N). -function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQuadModule, el::Bool; first::Bool = false) +function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat, M::ZZLat, H::TorQuadModule, el::Bool; first::Bool = false) @hassert :ZZLatWithIsom 1 is_one(basis_matrix(N)) @hassert :ZZLatWithIsom 1 is_one(basis_matrix(M)) results = Tuple{ZZLat, ZZLat, ZZLat}[] @@ -335,11 +316,11 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua if el VN, VNinqN, _ = _get_V(id_hom(qN), minpoly(identity_matrix(QQ,1)), p) subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) - filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H), subsN) + filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H)[1], subsN) @assert !isempty(subsN) VM, VMinqM, _ = _get_V(id_hom(qM), minpoly(identity_matrix(QQ, 1)), p) subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(VMinqM, GM, p) - filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H), subsM) + filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H)[1], subsM) @assert !isempty(subsM) else subsN = _classes_automorphic_subgroups(GN, rescale(H, -1)) @@ -399,7 +380,7 @@ end classification::Bool = true, first::Bool = false, check::Bool = true) - -> Vector{Tuple{ZZLat, ZZLat, ZZLat}} + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} Given a `p`-primary lattice `L`, unique in its genus, and a lattice `M`, return whether `M` embeds primitively in `L`. @@ -440,7 +421,7 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat GL, _ = image_in_Oq(rescale(L, -1)) qL = domain(GL) - D, inj, proj = biproduct(qM, qL) + D, inj = direct_sum(qM, qL) qMinD, qLinD = inj if el @@ -461,8 +442,8 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" for H in subsL HL = domain(H[1]) - it = subgroups(abelian_group(VM), order = order(HL)) - subsM = [sub(qM, VMinqM.(VM.(j[2].(gens(j[1])))))[2] for j in it] + it = subgroups(VM, order = order(HL)) + subsM = TorQuadModuleMor[sub(qM, lift.(gens(j[1])))[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue @@ -489,7 +470,7 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - temp = _isomorphism_classes_primitive_extensions(N, M, qM2, el, first=first) + temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, el, first=first) if !is_empty(temp) first && return true, temp append!(results, temps) @@ -507,7 +488,7 @@ end classification::Bool = true, first::Bool = false, check::Bool = true) - -> Vector{Tuple{ZZLat, ZZLat, ZZLat}} + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} Given a lattice `L`, unique in its genus, and a `p`-primary lattice `M`, return whether `M` embeds primitively in `L`. @@ -568,8 +549,8 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" for H in subsL HL = domain(H[1]) - it = subgroups(abelian_group(qM), order = order(HL)) - subsM = [sub(qM, (j[2].(gens(j[1]))))[2] for j in it] + it = subgroups(qM, order = order(HL)) + subsM = TorQuadModuleMor[H[2] for H in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue @@ -596,7 +577,7 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - temp = _isomorphism_classes_primitive_extensions(N, M, qM2, el, first=first) + temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, el, first=first) if length(temp) > 0 first && return true, temp append!(results, temp) @@ -760,7 +741,6 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, OSB = orthogonal_group(SB) phi = _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) - #!ok && continue # we compute the image of the stabalizers in the respective OS* and we keep track # of the elements of the stabilizers acting trivially in the respective S* actA = hom(stabA, OSA, [OSA(restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) @@ -806,8 +786,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, for g in reps g = representative(g) phig = compose(phi, hom(g)) - @assert _is_anti_isometry(phig) - @assert all(a -> Hecke.quadratic_product(a) + Hecke.quadratic_product(phig(a)) == 0, gens(domain(phig))) + @assert is_anti_isometry(phig) if amb _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] @@ -843,9 +822,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) - @hassert :ZZLatWithIsom 1 _is_isometry(phi2) - println(genus(C2)) - println(multiplicative_order(fC2)) + @hassert :ZZLatWithIsom 1 is_isometry(phi2) if !is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) continue end @@ -871,21 +848,24 @@ end function _on_modular_matrix(M::QQMatrix, g::AutomorphismGroupElem) q = domain(parent(g)) - R = Hecke.value_module(q) + R = value_module(q) m = matrix(g) return map_entries(a -> lift(R(a)), m*M*transpose(m)) end function _on_modular_matrix_quad(M::QQMatrix, g::AutomorphismGroupElem) q = domain(parent(g)) - R1 = Hecke.value_module(q) - R2 = Hecke.value_module_quadratic_form(q) + R1 = value_module(q) + R2 = value_module_quadratic_form(q) m = matrix(g) - m1 = map_entries(a -> lift(R2(a)), m*M*transpose(m)) + m1 = m*M*transpose(m) for i in 1:nrows(m1) for j in 1:ncols(m1) - i == j && continue - m1[i,j] = lift(R1(m1[i,j])) + if i == j + m1[i,j] = lift(R2(m1[i,j])) + else + m1[i,j] = lift(R1(m1[i,j])) + end end end return m1 @@ -903,17 +883,17 @@ function _compute_double_stabilizer(SBinqB, l, spec) OHB, OSBHBtoOHB = restrict_automorphism_group(OSBHB, HBinSB) K, _ = kernel(OSBHBtoOHB) if p != 2 - OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rB), _on_modular_matrix) + OHBrB, _ = stabilizer(OHB, gram_matrix_bilinear(rB), _on_modular_matrix) elseif spec - OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_quadratic(rB), _on_modular_matrix_quad) + OHBrB, _ = stabilizer(OHB, gram_matrix_quadratic(rB), _on_modular_matrix_quad) else - OHBrB, _ = stabilizer(OHB, Hecke.gram_matrix_bilinear(rB), _on_modular_matrix) + OHBrB, _ = stabilizer(OHB, gram_matrix_bilinear(rB), _on_modular_matrix) end OSBrB, _ = sub(OSB, union(gens(K), OSB.(gens((OSBHBtoOHB\(OHBrB))[1])))) return OSBrB end -function _is_isometry(f::TorQuadModuleMor) +function _is_isometry_bilinear(f::TorQuadModuleMor) !is_bijective(f) && return false for a in gens(domain(f)) for b in gens(domain(f)) @@ -921,14 +901,11 @@ function _is_isometry(f::TorQuadModuleMor) return false end end - if Hecke.quadratic_product(a) != Hecke.quadratic_product(f(a)) - return false - end end return true end -function _is_anti_isometry(f::TorQuadModuleMor) +function _is_anti_isometry_bilinear(f::TorQuadModuleMor) !is_bijective(f) && return false for a in gens(domain(f)) for b in gens(domain(f)) @@ -971,9 +948,6 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) break end end - #if cover(_on_subgroup_automorphic(phiHA, g)) != cover(HB) - # return false, phi - #end phi_1 = compose(phi, hom(g)) OSBHB, _ = stabilizer(OSB, HBinSB) g = one(OSBHB) @@ -986,18 +960,11 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) end end phig = compose(phi_1, hom(g)) - #if cover(sub(SB, [SB(lift(phig(SA(lift(a))))) for a in HA])[1]) != cover(HB) - # return false, phi - #elseif matrix(hom(rA, rB, [rBtoSB\(phig(rAtoSA(a))) for a in gens(rA)])) != matrix(phi_0) - # return false, phi - #else - # return true, phig - #end return phig end function _is_even(T) - B = Hecke.gram_matrix_bilinear(T) + B = gram_matrix_bilinear(T) return is_empty(B) || all(is_zero, diagonal(B)) end @@ -1011,10 +978,10 @@ function _anti_isometry_bilinear(r1, r2, p) r2tor2m = hom(r2, r2m, identity_matrix(ZZ, ngens(r2))) r1N, r1tor1N = normal_form(r1) r2mN, r2mtor2mN = normal_form(r2m) - @hassert :ZZLatWithIsom 1 Hecke.gram_matrix_bilinear(r1N) == Hecke.gram_matrix_bilinear(r2mN) + @hassert :ZZLatWithIsom 1 gram_matrix_bilinear(r1N) == gram_matrix_bilinear(r2mN) T = hom(r1N, r2mN, identity_matrix(ZZ, ngens(r1N))) T = compose(r1tor1N, compose(T, compose(inv(r2mtor2mN), inv(r2tor2m)))) - @hassert :ZZLatWithIsom _is_anti_isometry(T) + @hassert :ZZLatWithIsom _is_anti_isometry_bilinear(T) return T end diff --git a/experimental/LatticesWithIsometry/src/enumeration.jl b/experimental/LatticesWithIsometry/src/enumeration.jl index eb2a5f3ca577..2e66a35ea819 100644 --- a/experimental/LatticesWithIsometry/src/enumeration.jl +++ b/experimental/LatticesWithIsometry/src/enumeration.jl @@ -74,9 +74,8 @@ end is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) -> Bool Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such -that the rank of `B` is divisible by $p-1$ and the level of `C` is a power -of `p`, return whether `(A,B,C)` is `p`-admissible in the sense of -Definition 4.13. [BH22] +that the rank of `B` is divisible by $p-1$, return whether `(A,B,C)` is +`p`-admissible in the sense of Definition 4.13. [BH22] """ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) zg = genus(integer_lattice(gram = matrix(QQ, 0, 0, []))) @@ -217,7 +216,7 @@ $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and See Algorithm 1 of [BH22]. """ -function admissible_triples(G::ZZGenus, p::Int64; pA::Int = -1, pB::Int = -1) +function admissible_triples(G::ZZGenus, p::Integer; 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" n = rank(G) @@ -458,7 +457,7 @@ If `check === true`, then `t` is checked to be hermitian. Note that `n` can be 1 See Algorithm 3 of [BH22]. """ -function representatives_of_hermitian_type(t::Dict, m::Integer = 1; check::Bool = true) +function representatives_of_hermitian_type(t::Dict, m::Int = 1; check::Bool = true) M = _representative(t, check = check) M === nothing && return ZZLatWithIsom[] return representatives_of_hermitian_type(M, m) diff --git a/experimental/LatticesWithIsometry/src/exports.jl b/experimental/LatticesWithIsometry/src/exports.jl index f5734892290b..5454bedfc3a3 100644 --- a/experimental/LatticesWithIsometry/src/exports.jl +++ b/experimental/LatticesWithIsometry/src/exports.jl @@ -5,6 +5,7 @@ export admissible_triples export ambient_isometry export enumerate_classes_of_lattices_with_isometry export image_centralizer_in_Oq +export invariant_coinvariant_pair export isometry export is_admissible_triple export is_of_hermitian_type diff --git a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl index 2171adff71fe..26477f0713d6 100644 --- a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl +++ b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl @@ -621,6 +621,21 @@ in this case) """ invariant_lattice(Lf::ZZLatWithIsom) = kernel_lattice(Lf, 1) +@doc raw""" + invariant_lattice(L::ZZLat, G::MatrixGroup; + ambient_representation::Bool = true) -> ZZLat + +Given an integer lattice `L` and a group `G` of isometries of `L` in matrix, +return the invariant sublattice $L^G$ of `L`. + +If `ambient_representation` is set to true, the isometries in `G` are seen as +isometries of the ambient quadratic space of `L` preserving `L`. Otherwise, they +are considered as honnest isometries of `L`. +""" +function invariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) + return invariant_lattice(L, matrix.(gens(G)), ambient_representation = ambient_representation) +end + @doc raw""" coinvariant_lattice(Lf::ZZLatWithIsom) -> ZZLatWithIsom @@ -640,7 +655,68 @@ function coinvariant_lattice(Lf::ZZLatWithIsom) return kernel_lattice(Lf, chi) end -############################################################################### +@doc raw""" + coinvariant_lattice(L::ZZLat, G::MatrixGroup; + ambient_representation::Bool = true) + -> ZZLat, MatrixGroup + +Given an integer lattice `L` and a group `G` of isometries of `L` in matrix, +return the coinvariant sublattice $L_G$ of `L`, together with the subgroup `H` +of isometries of $L_G$ induced by the action of $G$. + +If `ambient_representation` is set to true, the isometries in `G` and `H` are seen +as isometries of the ambient quadratic space of `L` preserving `L`. Otherwise, +they are considered as honnest isometries of `L`. +""" +function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) + F = invariant_lattice(L, G, ambient_representation = ambient_representation) + C = orthogonal_submodule(L, F) + gene = QQMatrix[] + for g in gens(G) + if !ambient_representation + mL = matrix(g) + m_amb = solve(basis_matrix(L), mL*basis_matrix(L)) + mC = solve_left(basis_matrix(C), basis_matrix(C)*m_amb) + push!(gene, mc) + else + push!(gene, matrix(g)) + end + end + return C, matrix_group(gene) +end + +@doc raw""" + invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; + ambient_representation::Bool = true) + -> ZZLat, ZZLat, MatrixGroup + +Given an integer lattice `L` and a group `G` of isometries of `L` in matrix, +return the invariant sublattice $L^G$ of `L` and its coinvariant sublattice +$L_G$ together with the subgroup `H` of isometries of $L_G$ induced by the +action of $G$. + +If `ambient_representation` is set to true, the isometries in `G` and `H` are seen +as isometries of the ambient quadratic space of `L` preserving `L`. Otherwise, +they are considered as honnest isometries of `L`. +""" +function invariant_coinvariant_pair(L::ZZlat, G::MatrixGroup; ambient_representation::Bool = true) + F = invariant_lattice(L, G, ambient_representation = ambient_representation) + C = orthogonal_submodule(L, F) + gene = QQMatrix[] + for g in gens(G) + if !ambient_representation + mL = matrix(g) + m_amb = solve(basis_matrix(L), mL*basis_matrix(L)) + mC = solve_left(basis_matrix(C), basis_matrix(C)*m_amb) + push!(gene, mc) + else + push!(gene, matrix(g)) + end + end + return F, C, matrix_group(gene) +end + +############################################################################## # # Type # From 0b35d9886bbac1c98f2b4f046e2aafcdc5f9e901 Mon Sep 17 00:00:00 2001 From: StevellM Date: Sun, 4 Jun 2023 15:33:13 +0200 Subject: [PATCH 56/76] bla --- .../LatticesWithIsometry/src/embeddings.jl | 35 ++++++++++++------- .../LatticesWithIsometry/src/enumeration.jl | 7 ++-- .../src/lattices_with_isometry.jl | 8 ++--- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index da9685646fed..059097255e73 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -120,6 +120,11 @@ end # ############################################################################## +function _on_subgroup_automorphic(H::Oscar.GAPGroup, g::AutomorphismGroupElem) + G = domain(parent(g)) + return sub(G, g.(gens(H)))[1] +end + function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) gene = TorQuadModuleElem[g(q(lift(t))) for t in gens(T)] @@ -129,8 +134,14 @@ end function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) - N, _ = sub(q, i.(gens(domain(i)))) - return stabilizer(O, N, _on_subgroup_automorphic) + togap = get_attribute(O, :to_gap) + tooscar = get_attribute(O, :to_oscar) + A = codomain(togap) + OA = automorphism_group(A) + OinOA, _ = sub(OA, OA.([g.X for g in gens(O)])) + N, _ = sub(A, togap.(i.(gens(domain(i))))) + stab, _ = stabilizer(OinOA, N, _on_subgroup_automorphic) + return sub(O, O.([h.X for h in gens(stab)])) end function _subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadModule}; order::Hecke.IntegerUnion = -1) @@ -690,7 +701,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, end qC2 = discriminant_group(C2) phi2 = hom(qC2, D, [D(lift(x)) for x in gens(qC2)]) - @hassert :ZZLatWithIsom 1 _is_isometry(phi2) + @hassert :ZZLatWithIsom 1 is_isometry(phi2) if is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation=false), type(C)) GC = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GC2)]) C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) @@ -707,7 +718,6 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, if min(order(VA), order(VB)) < p^g return results end - # scale of the dual: any glue kernel must contain the multiples of l of the respective # discriminant groups l = valuation(level(genus(C)), p) @@ -752,28 +762,27 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, actB = hom(stabB, 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)) OSBrB = _compute_double_stabilizer(SBinqB, l, spec) - (fSB in OSBrB) || continue + #elm = gset(OSBrB, ^, OSB) + + fSB = OSBrB(fSB) # 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 glueing. # If not, we try the next potential pair. fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) - (fSAinOSB in OSBrB) || continue - fSAinOSBrB = OSBrB(fSAinOSB) - bool, g0 = representative_action(OSBrB, fSAinOSBrB, OSBrB(fSB)) + bool, g0 = representative_action(OSBrB, fSAinOSB, fSB) bool || continue phi = compose(phi, hom(OSB(g0))) - fSAinOSBrB = OSB(compose(inv(phi), compose(hom(fSA), phi))) + fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) # Now the new phi is "sending" the restriction of fA to this of fB. # So we can glue SA and SB. @hassert :ZZLatWithIsom 1 fSAinOSBrB == fSB # 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, OSBrB(fSB)) + center, _ = stabilizer(OSBrB, fSB) center, _ = sub(OSB, [OSB(c) for c in gens(center)]) stabSAphi, _ = sub(OSB, AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(g), phi))) for g in gens(imA)]) stabSAphi, _ = intersect(center, stabSAphi) @@ -936,11 +945,13 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) elseif 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 phi_0 = _anti_isometry_bilinear(rA, rB, p) end - phiHA, _ = sub(SB, [SB(lift(phi(SA(lift(a))))) for a in gens(HA)]) + phiHA, _ = sub(SB, [phi(SA(lift(a))) for a in gens(HA)]) OSB = orthogonal_group(SB) + @assert is_invariant(OSB, HBinSB) g = one(OSB) for f in OSB g = f diff --git a/experimental/LatticesWithIsometry/src/enumeration.jl b/experimental/LatticesWithIsometry/src/enumeration.jl index 2e66a35ea819..c5549e05b852 100644 --- a/experimental/LatticesWithIsometry/src/enumeration.jl +++ b/experimental/LatticesWithIsometry/src/enumeration.jl @@ -563,6 +563,9 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = RA = representatives_of_hermitian_type(LA, q^e) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB], inplace=false) E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p) + if !is_empty(E) && (E[1] isa AutomorphismGroup{TorQuadModule}) + return E + end GC.gc() append!(reps, E) end @@ -699,7 +702,7 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) is_empty(A) && return reps B = splitting_of_pure_mixed_prime_power(B0, p) for (LA, LB) in Hecke.cartesian_product_iterator([A, B], inplace=false) - E = admissible_equivariant_primitive_extensions(LA, LB, Lf, q, p) + E = admissible_equivariant_primitive_extensions(LB, LA, Lf, q, p) GC.gc() append!(reps, E) end @@ -757,7 +760,7 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) isempty(A) && return reps B = splitting_of_mixed_prime_power(B0, p, 0) for (LA, LB) in Hecke.cartesian_product_iterator([A, B], inplace=false) - E = admissible_equivariant_primitive_extensions(LA, LB, Lf, p) + E = admissible_equivariant_primitive_extensions(LB, LA, Lf, p) GC.gc() if b == 1 filter!(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) diff --git a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl index 26477f0713d6..4de1fd76d053 100644 --- a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl +++ b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl @@ -699,7 +699,7 @@ If `ambient_representation` is set to true, the isometries in `G` and `H` are se as isometries of the ambient quadratic space of `L` preserving `L`. Otherwise, they are considered as honnest isometries of `L`. """ -function invariant_coinvariant_pair(L::ZZlat, G::MatrixGroup; ambient_representation::Bool = true) +function invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) F = invariant_lattice(L, G, ambient_representation = ambient_representation) C = orthogonal_submodule(L, F) gene = QQMatrix[] @@ -811,10 +811,10 @@ end function to_oscar(Lf::ZZLatWithIsom) L = lattice(Lf) f = ambient_isometry(Lf) - println(stdout, "B = matrix(QQ, $(rank(L)), $(degree(L)), " , basis_matrix(L), " );") - println(stdout, "G = matrix(QQ, $(degree(L)), $(degree(L)), ", gram_matrix(ambient_space(L)), " );") + println(stdout, "B = matrix(QQ, $(rank(L)), $(degree(L)), " , basis_matrix(L), ");") + println(stdout, "G = matrix(QQ, $(degree(L)), $(degree(L)), ", gram_matrix(ambient_space(L)), ");") println(stdout, "L = integer_lattice(B, gram = G);") - println(stdout, "f = matrix(QQ, $(degree(L)), $(degree(L)), ", f, " );") + println(stdout, "f = matrix(QQ, $(degree(L)), $(degree(L)), ", f, ");") println(stdout, "Lf = lattice_with_isometry(L, f);") end From fe5ed84a432067699baa6d2c656bf839f44ff588 Mon Sep 17 00:00:00 2001 From: StevellM Date: Sun, 4 Jun 2023 15:47:45 +0200 Subject: [PATCH 57/76] bla --- .../LatticesWithIsometry/src/embeddings.jl | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index 059097255e73..70a9debf80f4 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -221,6 +221,11 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] V = domain(Vinq) + if order(V) == 1 + ord != 1 && (return res) + push!(res, (Vinq, G)) + return res + end q = codomain(Vinq) p = elementary_divisors(V)[1] pq, pqtoq = primary_part(q, p) @@ -312,7 +317,7 @@ end # # We follow the second definition of Nikulin, i.e. we classify up to the actions # of O(M) and O(N). -function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat, M::ZZLat, H::TorQuadModule, el::Bool; first::Bool = false) +function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat, M::ZZLat, H::TorQuadModule, p::Hecke.IntegerUnion, el::Bool; first::Bool = false) @hassert :ZZLatWithIsom 1 is_one(basis_matrix(N)) @hassert :ZZLatWithIsom 1 is_one(basis_matrix(M)) results = Tuple{ZZLat, ZZLat, ZZLat}[] @@ -334,9 +339,13 @@ function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H)[1], subsM) @assert !isempty(subsM) else - subsN = _classes_automorphic_subgroups(GN, rescale(H, -1)) + VN, VNinqN = primary_part(qN, p) + GVN, _ = restrict_automorphism_group(GN, VNinqN) + subsN = _classes_automorphic_subgroups(GVN, rescale(H, -1)) @hassert :ZZLatWithIsom 1 !isempty(subsN) - subsM = _classes_automorphic_subgroups(GM, H) + VM, VMinqM = primary_part(qM, p) + GVM, _ = restrict_automorphism_group(GM, VMinqM) + subsM = _classes_automorphic_subgroups(GVM, H) @hassert :ZZLatWithIsom 1 !isempty(subsM) end @@ -344,17 +353,17 @@ function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) @hassert :ZZLatWithIsom 1 ok - HNinqN, stabN = H1 - HN = domain(HNinqN) + HNinVN, stabN = H1 + HN = domain(HNinVN) OHN = orthogonal_group(HN) - HMinqM, stabM = H2 - HM = domain(HMinqM) + HMinVM, stabM = H2 + HM = domain(HMinVM) OHM = orthogonal_group(HM) - actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) + actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinVN)) for x in gens(stabN)]) - actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) for x in gens(stabM)]) + actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinVM)) 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)] @@ -365,7 +374,7 @@ function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat for g in reps g = representative(g) phig = compose(phi, hom(g)) - _glue = Vector{QQFieldElem}[lift(qNinD(HNinqN(g))) + lift(qMinD(HMinqM(phig(g)))) for g in gens(domain(phig))] + _glue = Vector{QQFieldElem}[lift(qNinD(VNinqN(HNinVN(g)))) + lift(qMinD(VMinqM(HMinVM(phig(g))))) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(N)+degree(M)) glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) @@ -481,7 +490,7 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, el, first=first) + temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, p, el, first=first) if !is_empty(temp) first && return true, temp append!(results, temps) @@ -490,7 +499,7 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat end end end - @hassert :ZZLatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) + #@hassert :ZZLatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) return (length(results) > 0), results end @@ -588,7 +597,7 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, el, first=first) + temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, p, el, first=first) if length(temp) > 0 first && return true, temp append!(results, temp) @@ -598,7 +607,7 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat GC.gc() end end - @assert all(triple -> genus(triple[1]) == genus(L), results) + #@hassert :ZZLatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) return (length(results) >0), results end From b966aae33af1dd96361827ae96822f0ce93f9649 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 12 Jun 2023 19:45:00 +0200 Subject: [PATCH 58/76] add spaces with isom and fix stabilizer --- .../src/LatticesWithIsometry.jl | 1 + .../LatticesWithIsometry/src/embeddings.jl | 411 +++++++++++------- .../LatticesWithIsometry/src/enumeration.jl | 119 +---- .../LatticesWithIsometry/src/exports.jl | 4 +- .../src/lattices_with_isometry.jl | 176 +++++--- .../LatticesWithIsometry/src/printings.jl | 28 ++ .../src/spaces_with_isometry.jl | 285 ++++++++++++ .../LatticesWithIsometry/src/types.jl | 40 +- src/Oscar.jl | 4 +- 9 files changed, 735 insertions(+), 333 deletions(-) create mode 100644 experimental/LatticesWithIsometry/src/spaces_with_isometry.jl diff --git a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl index b51dab6dc466..42b23601baaa 100644 --- a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl +++ b/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl @@ -5,6 +5,7 @@ end include("exports.jl") include("types.jl") +include("spaces_with_isometry.jl") include("lattices_with_isometry.jl") include("hermitian_miranda_morrison.jl") include("enumeration.jl") diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/LatticesWithIsometry/src/embeddings.jl index 70a9debf80f4..43c386ea3152 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/LatticesWithIsometry/src/embeddings.jl @@ -76,7 +76,7 @@ end # restricted to the p-elementary part of q (i.e. the submodule of q generated by # all the elements of order p) # -# This object is defined in Algorithm 2 of BH23, the glue kernel we aim to use +# 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, mu, p) @@ -88,20 +88,25 @@ function _get_V(fq, mu, p) K, _ = kernel(fpV_mu) Ktoq = hom(K, q, [q(lift(a)) for a in gens(K)]) @hassert :ZZLatWithIsom 1 is_injective(Ktoq) - fK = restrict_endomorphism(fq, Ktoq) - return K, Ktoq, fK + return K, Ktoq end # This is the rho function as defined in Definition 4.8 of BH23. -function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) +function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}; quad::Bool = (p == 2)) pq, pqtoq = primary_part(q, p) pq = rescale(pq, QQ(p)^(l-1)) Nv = cover(pq) N = relations(pq) + if quad && p == 2 + fr = _is_free(q, p, l) + else + fr = false + end + mqf = fr ? QQ(2) : QQ(1) if l == 0 Gl = N Gm = intersect(1//p*N, Nv) - rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = QQ(2)) + rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = mqf) else k = l-1 m = l+1 @@ -109,28 +114,45 @@ function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}) Gl = intersect((1//(p^l))*N, Nv) Gm = intersect((1//(p^m))*N, Nv) B = Gk+p*Gm - rholN = torsion_quadratic_module(Gl, B, modulus = QQ(1), modulus_qf = QQ(2)) + rholN = torsion_quadratic_module(Gl, B, modulus = QQ(1), modulus_qf = mqf) end return rholN end +# A finite bilinear module over the 2-adic integers is even if all square are +# zeros. +function _is_even(T, p, l) + B = gram_matrix_bilinear(_rho_functor(T, p, l, quad=false)) + return is_empty(B) || all(is_zero, diagonal(B)) +end + +function _is_free(T, p, l) + return _is_even(T, p, l-1) && _is_even(T, p, l+1) +end + ############################################################################## # # Orbits and stabilizers of discriminant subgroups # ############################################################################## +# Provisional waiting to be able to compare torsion quadratic modules in a same +# space function _on_subgroup_automorphic(H::Oscar.GAPGroup, g::AutomorphismGroupElem) G = domain(parent(g)) return sub(G, g.(gens(H)))[1] end +# Should eventually replace the one above function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) gene = TorQuadModuleElem[g(q(lift(t))) for t in gens(T)] return sub(q, gene)[1] end +# Compute stabilizer of a subgroup of a `TorQuadModule` under the action by +# automorphisms. For now we use the GAP infrastructure, we should turn it for +# using the Hecke groups part function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) @@ -144,23 +166,32 @@ function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) return sub(O, O.([h.X for h in gens(stab)])) end -function _subgroups_orbit_representatives_and_stabilizers(O::AutomorphismGroup{TorQuadModule}; order::Hecke.IntegerUnion = -1) - q = domain(O) - it = order == -1 ? subgroups(q) : subgroups(q, order=order) - subs = TorQuadModule[j[1] for j in it] +function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}; + order::Hecke.IntegerUnion = -1, + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq))) + fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) + V = domain(OV) + if order == -1 + subs = stable_submodules(V, [fV]) + else + subs = submodules(V, order=order) + filter!(s -> is_invariant(fV, s[2]), subs) + end + subs = TorQuadModule[s[1] for s in subs] m = gset(O, _on_subgroup_automorphic, subs) orbs = orbits(m) res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] for orb in orbs rep = representative(orb) stab, _ = stabilizer(O, rep, _on_subgroup_automorphic) + _, rep = sub(q, TorQuadModuleElem[q(lift(g)) for g in gens(rep)]) push!(res, (rep, stab)) - end + end return res end -function _classes_automorphic_subgroups(O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule) - sors = _subgroups_orbit_representatives_and_stabilizers(O, order=order(q)) +function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule; f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) + sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, order=order(q), f=f) return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) end @@ -171,11 +202,7 @@ end # This map return Qp := V/H as an Fp-vector space, the map which transforms V into a # Fp-vector space Vp, the quotient map Vp \to Qp, and the restriction fQp of f # to Qp -function _cokernel_as_Fp_vector_space(HinV, p, f) - if f isa AutomorphismGroupElem - f = hom(f) - end - +function _cokernel_as_Fp_vector_space(HinV, p) H = domain(HinV) V = codomain(HinV) @@ -197,12 +224,8 @@ function _cokernel_as_Fp_vector_space(HinV, p, f) subgene = elem_type(Vp)[VtoVp(HinV(a)) for a in gens(H)] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) - fVp = change_base_ring(F, matrix(f)) - ok, fQp = can_solve_with_solution(VptoQp.matrix, fVp*VptoQp.matrix) - @assert ok - - return Qp, VtoVp, VptoQp, fQp + return Qp, VtoVp, VptoQp end # Given an abelian group injection V \to q where the group structure on V is @@ -216,16 +239,18 @@ end function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMor, G::AutomorphismGroup{TorQuadModule}, ord::Hecke.IntegerUnion, - f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq)), + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq)), l::Hecke.IntegerUnion = -1) res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] V = domain(Vinq) + if order(V) == 1 ord != 1 && (return res) push!(res, (Vinq, G)) return res end + q = codomain(Vinq) p = elementary_divisors(V)[1] pq, pqtoq = primary_part(q, p) @@ -244,8 +269,9 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu all(a -> has_preimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) || return res H0, H0inq = sub(q, [q(lift((p^l)*a)) for a in gens(pq)]) + @hassert :ZZLatWithIsom 1 is_invariant(f, H0inq) H0inV = hom(H0, V, [V(lift(a)) for a in gens(H0)]) - @hassert :ZZLatWithIsom 1 is_invariant(f, H0inV) + @hassert :ZZLatWithIsom 1 is_injective(H0inV) if order(H0) >= ord order(H0) > ord && (return res) @@ -253,7 +279,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end - Qp, VtoVp, VptoQp, fQp = _cokernel_as_Fp_vector_space(H0inV, p, f) + Qp, VtoVp, VptoQp = _cokernel_as_Fp_vector_space(H0inV, p) Vp = codomain(VtoVp) dim(Qp) == 0 && (return res) @@ -264,7 +290,6 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(act_GV_Qp) - @hassert :ZZLatWithIsom 1 fQp in MGp GVtoMGp = hom(GV, MGp, MGp.(act_GV_Qp), check = false) GtoMGp = compose(GtoGV, GVtoMGp) @@ -276,14 +301,6 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) for (orb, stab) in orb_and_stab i = orb.map - _V = codomain(i) - for v in gens(orb) - vv = _V(i(v)*fQp) - if !can_solve(i.matrix, vv.v, side = :left) - @goto non_fixed - end - end - gene_orb_in_Qp = elem_type(Qp)[Qp(vec(collect(i(v).v))) for v in gens(domain(i))] gene_orb_in_Vp = elem_type(Vp)[preimage(VptoQp, v) for v in gene_orb_in_Qp] @@ -292,6 +309,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu gene_submod_in_q = TorQuadModuleElem[image(Vinq, v) for v in gene_submod_in_V] orbq, orbqinq = sub(q, gene_submod_in_q) @hassert :ZZLatWithIsom 1 order(orbq) == ord + is_invariant(f, orbqinq) || continue stabq = AutomorphismGroupElem{TorQuadModule}[GtoMGp\(s) for s in gens(stab)] stabq, _ = sub(G, union(stabq, satV)) @@ -302,33 +320,45 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end +function Base.:(==)(T1::TorQuadModule, T2::TorQuadModule) + relations(T1) != relations(T2) && return false + return cover(T1) == cover(T2) +end + ################################################################################# # -# Embeddings in elementary lattices +# Embeddings for primary lattices # ################################################################################# -# here for convenience, we choose in entry N and M to be of full rank and -# with basis matrix equal to the identity matrix -# # We compute representatives of isomorphism classes of primitive extensions # M \oplus N \to L, where we glue along HM and HN which are respectively # isometric and anti-isometric to H. # -# We follow the second definition of Nikulin, i.e. we classify up to the actions -# of O(M) and O(N). -function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat, M::ZZLat, H::TorQuadModule, p::Hecke.IntegerUnion, el::Bool; first::Bool = false) - @hassert :ZZLatWithIsom 1 is_one(basis_matrix(N)) - @hassert :ZZLatWithIsom 1 is_one(basis_matrix(M)) +# We follow the second definition of Nikulin, i.e. we classify up to the action +# of O(N). If `classification == :strong`, we also classify them up to the +# action of O(M). If `classification == :first`, we return the first embedding +# computed. +function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQuadModule, classification::Symbol) + @hassert :ZZLatWithIsom 1 classification in [:first, :weak, :strong] results = Tuple{ZZLat, ZZLat, ZZLat}[] + prim, p = is_primary_with_prime(H) + el = prim ? is_elementary(H, p) : false + + qN = discriminant_group(N) GN, _ = image_in_Oq(N) - GM, _ = image_in_Oq(M) - qN = domain(GN) - qM = domain(GM) + + qM = discriminant_group(M) + if classification == :weak + GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(id_hom(qM))]) + else + GM, _ = image_in_Oq(M) + end D, inj = direct_sum(qN, qM) qNinD, qMinD = inj OD = orthogonal_group(D) + if el VN, VNinqN, _ = _get_V(id_hom(qN), minpoly(identity_matrix(QQ,1)), p) subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) @@ -338,14 +368,19 @@ function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(VMinqM, GM, p) filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H)[1], subsM) @assert !isempty(subsM) - else + elseif prim VN, VNinqN = primary_part(qN, p) - GVN, _ = restrict_automorphism_group(GN, VNinqN) - subsN = _classes_automorphic_subgroups(GVN, rescale(H, -1)) + subsN = _classes_automorphic_subgroups(VNinqN, GN, rescale(H, -1)) @hassert :ZZLatWithIsom 1 !isempty(subsN) VM, VMinqM = primary_part(qM, p) - GVM, _ = restrict_automorphism_group(GM, VMinqM) - subsM = _classes_automorphic_subgroups(GVM, H) + subsM = _classes_automorphic_subgroups(VMinqM, GM, H) + @hassert :ZZLatWithIsom 1 !isempty(subsM) + else + VN, VNinqN = qN, id_hom(qN) + subsN = _classes_automorphic_subgroups(VNinqN, GN, rescale(H, -1)) + @hassert :ZZLatWithIsom 1 !isempty(subsN) + VM, VMinqM = qM, id_hom(qM) + subsM = _classes_automorphic_subgroups(VMinqM, GM, H) @hassert :ZZLatWithIsom 1 !isempty(subsM) end @@ -377,19 +412,19 @@ function _isomorphism_classes_primitive_extensions_along_primary_module(N::ZZLat _glue = Vector{QQFieldElem}[lift(qNinD(VNinqN(HNinVN(g)))) + lift(qMinD(VMinqM(HMinVM(phig(g))))) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(N)+degree(M)) glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) - glue = vcat(identity_matrix(QQ, rank(N)+rank(M)), glue) + glue = vcat(block_diagonal_matrix(basis_matrix.(N, M)), glue) glue = FakeFmpqMat(glue) _B = hnf(glue) _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) L = Hecke.lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) - N2 = lattice_in_same_ambient_space(L, identity_matrix(QQ, rank(L))[1:rank(N),:]) + N2 = lattice_in_same_ambient_space(L, hcat(basis_matrix(N), zero_matrix(QQ, rank(N), degree(M)))) @hassert :ZZLatWithIsom 1 genus(N) == genus(N2) - M2 = lattice_in_same_ambient_space(L, identity_matrix(QQ,rank(L))[rank(N)+1:end, :]) + M2 = lattice_in_same_ambient_space(L, hcat(zero_matrix(QQ, rank(M), degree(N)), basis_matrix(M))) @hassert :ZZLatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) @vprint :ZZLatWithIsom 1 "Gluing done\n" GC.gc() - first && return results + classification == :first && return results end end return results @@ -397,7 +432,7 @@ end @doc raw""" primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; - classification::Bool = true, + classification::Symbol = :strong, first::Bool = false, check::Bool = true) -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} @@ -405,19 +440,24 @@ end Given a `p`-primary lattice `L`, unique in its genus, and a lattice `M`, return whether `M` embeds primitively in `L`. -If `classification` is set to `true`, compute representatives for all -isomorphism classes of primitive embeddings of `M` in `L` up to the -actions of $\bar{O}(M)$ and $\bar{O}(L)$. - -If `first` is set to `true`, return the first primitive embedding of -`M` in `L` if it exists. - -In both the previous cases, the output is given in terms of triples -`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice -of `L'` isometric to `M` and `N'` is the orthogonal complement of -`M'` in `L'`. +The first input of the function is a boolean `T` stating whether or not `M` +embeds primitively in `L`. The second output `V` consists on triples +`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice of +`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. + +If `T == false`, then `V` will always be the empty list. If `T == true`, then +the content of `V` actually depends on the value of the symbol `classification`. +There are 4 possibilities: + - `classification = :none`: `V` is the empty list; + - `classification = :first`: V` consists on the first primitive embedding found; + - `classification = :strong`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in `L` up to the actions of $\bar{O}(M)$ + and $\bar{O}(L)$; + - `classification = :weak`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in `L` up to the action of $\bar{O}(L)$. """ -function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Bool = true, first::Bool = false, check::Bool = false) +function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :strong, check::Bool = false) + @req classification in [:none, :weak, :strong, :first] "Wrong symbol for classification" pL, _, nL = signature_tuple(L) pM, _, nM = signature_tuple(M) @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" @@ -433,11 +473,9 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat results = Tuple{ZZLat, ZZLat, ZZLat}[] - M = integer_lattice(gram = gram_matrix(M)) qM = discriminant_group(M) GM, _ = image_in_Oq(M) - GL, _ = image_in_Oq(rescale(L, -1)) qL = domain(GL) @@ -456,13 +494,13 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), GL, k) else - subsL = _subgroups_orbit_representatives_and_stabilizers(GL, order = k) + subsL = _subgroups_orbit_representatives_and_stabilizers(id_hom(qL), GL, order = k) end @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" for H in subsL HL = domain(H[1]) - it = subgroups(VM, order = order(HL)) + it = submodules(VM, order = order(HL)) subsM = TorQuadModuleMor[sub(qM, lift.(gens(j[1])))[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue @@ -480,19 +518,18 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat disc2 = rescale(disc, -1) !is_genus(disc2, (pL-pM, nL-nM)) && continue - !classification && return true, results + classification == :none && return true, results G = genus(disc2, (pL-pM, nL-nM)) @vprint :ZZLatWithIsom 1 "We can glue: $G\n" Ns = representatives(G) @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" Ns = lll.(Ns) - Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, p, el, first=first) + temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) if !is_empty(temp) - first && return true, temp + classification == :first && return true, temp append!(results, temps) end GC.gc() @@ -505,27 +542,31 @@ end @doc raw""" primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; - classification::Bool = true, - first::Bool = false, + classification::Symbol = :strong, check::Bool = true) -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} Given a lattice `L`, unique in its genus, and a `p`-primary lattice `M`, return whether `M` embeds primitively in `L`. -If `classification` is set to `true`, compute representatives for all -isomorphism classes of primitive embeddings of `M` in `L` up to the -actions of $\bar{O}(M)$ and $\bar{O}(L)$. - -If `first` is set to `true`, return the first primitive embedding of -`M` in `L` if it exists. - -In both the previous cases, the output is given in terms of triples -`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice -of `L'` isometric to `M` and `N'` is the orthogonal complement of -`M'` in `L'`. +The first input of the function is a boolean `T` stating whether or not `M` +embeds primitively in `L`. The second output `V` consists on triples +`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice of +`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. + +If `T == false`, then `V` will always be the empty list. If `T == true`, then +the content of `V` actually depends on the value of the symbol `classification`. +There are 4 possibilities: + - `classification = :none`: `V` is the empty list; + - `classification = :first`: V` consists on the first primitive embedding found; + - `classification = :strong`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in `L` up to the actions of $\bar{O}(M)$ + and $\bar{O}(L)$; + - `classification = :weak`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in `L` up to the action of $\bar{O}(L)$. """ -function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Bool = true, first::Bool = false, check::Bool = false) +function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :strong, check::Bool = false) + @req classification in [:none, :first, :weak, :strong] "Wrong symbol for classification" pL, _, nL = signature_tuple(L) pM, _, nM = signature_tuple(M) @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" @@ -541,7 +582,6 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat results = Tuple{ZZLat, ZZLat, ZZLat}[] - M = integer_lattice(gram = gram_matrix(M)) qM = discriminant_group(M) GM, _ = image_in_Oq(M) @@ -563,13 +603,13 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) else - subsL = _subgroups_orbit_representatives_and_stabilizers(restrict_automorphism_group(GL, VLinqL), order = k) + subsL = _subgroups_orbit_representatives_and_stabilizers(VLinqL, GL, order = k) end @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" for H in subsL HL = domain(H[1]) - it = subgroups(qM, order = order(HL)) + it = submodules(qM, order = order(HL)) subsM = TorQuadModuleMor[H[2] for H in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue @@ -587,19 +627,18 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat disc = rescale(disc, -1) !is_genus(disc, (pL-pM, nL-nM)) && continue - !classification && return true, results + classification == :none && return true, results G = genus(disc, (pL-pM, nL-nM)) @info "We can glue: $G" Ns = representatives(G) @info "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) - Ns = ZZLat[integer_lattice(gram=gram_matrix(N)) for N in Ns] qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns - temp = _isomorphism_classes_primitive_extensions_along_primary_module(N, M, qM2, p, el, first=first) + temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) if length(temp) > 0 - first && return true, temp + classification == :first && return true, temp append!(results, temp) end GC.gc() @@ -617,6 +656,10 @@ end # #################################################################################### +# The function is long and cannot be cut easily in subprocesses because we need +# to keep track of too many objects and parameters. So too avoid to have +# subfunctions with dozens of arguments, we just leave it as is and make a bunch +# of comments along the way. @doc raw""" admissible_equivariant_primitive_extensions(Afa::ZZLatWithIsom, Bfb::ZZLatWithIsom, @@ -650,10 +693,11 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, C::ZZLatWithIsom, p::Integer, q::Integer = p; check=true) - # requirement for the algorithm of BH22 + # p and q can be equal, and they will be most of the time @req is_prime(p) && is_prime(q) "p and q must be a prime number" - amb = ambient_space(A) === ambient_space(B) === ambient_space(C) + # Requirements for [BH23] + amb = ambient_space(lattice(A)) === ambient_space(lattice(B)) === ambient_space(lattice(C)) if check @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" chiA = minpoly(A) @@ -663,8 +707,8 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, if amb G = gram_matrix(ambient_space(C)) @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattices in same ambient space must be orthogonal" - end - end + end + end results = ZZLatWithIsom[] @@ -695,7 +739,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, geneA = AutomorphismGroupElem{TorQuadModule}[OqAinOD(OqA(a.X)) for a in gens(GA)] geneB = AutomorphismGroupElem{TorQuadModule}[OqBinOD(OqB(b.X)) for b in gens(GB)] gene = vcat(geneA, geneB) - GC2, _ = sub(OD, gene) + GCAB, _ = sub(OD, gene) if amb C2 = lattice(A)+lattice(B) fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) @@ -709,36 +753,49 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) end qC2 = discriminant_group(C2) - phi2 = hom(qC2, D, [D(lift(x)) for x in gens(qC2)]) + phi2 = hom(qC2, D, TorQuadModuleElem[D(lift(x)) for x in gens(qC2)]) @hassert :ZZLatWithIsom 1 is_isometry(phi2) - if is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation=false), type(C)) - GC = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GC2)]) - C2fc2 = lattice_with_isometry(C2, fC2, ambient_representation=false) - @hassert :ZZLatWithIsom 1 discriminant_group(C2fc2)[2] in GC - set_attribute!(C2fc2, :image_centralizer_in_Oq, GC) + if is_of_type(integer_lattice_with_isometry(C2, fC2^q, ambient_representation=false), type(C)) + GC2 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GCAB)]) + C2fc2 = integer_lattice_with_isometry(C2, fC2, ambient_representation=false) + @hassert :ZZLatWithIsom 1 discriminant_group(C2fc2)[2] in GC2 + set_attribute!(C2fc2, :image_centralizer_in_Oq, GC2) push!(results, C2fc2) end return results end - # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map - VA, VAinqA, fVA = _get_V(hom(fqA), minpoly(B), p) - VB, VBinqB, fVB = _get_V(hom(fqB), minpoly(A), p) + + # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map. + # VA and VB are submodules of the p-elementary parts of qA and qB + # respectively. + VA, VAinqA = _get_V(hom(fqA), minpoly(B), p) + VB, VBinqB = _get_V(hom(fqB), minpoly(A), p) + # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g return results end + # scale of the dual: any glue kernel must contain the multiples of l of the respective - # discriminant groups + # primary part of the discriminant groups l = valuation(level(genus(C)), p) - spec = (p == 2) && (_is_free(qA, p, l+1)) && (_is_free(qB, p, l+1)) && (_is_even(_rho_functor(qC, p, l))) + # In the special case where rho_{l+1}(A) and rho_{l+1}(B) are free, then we + # know that rho_l(C) is even if and only if any admissible gluing should + # induce an isometry of finite quadratic form between rho_{l+1}(A) and + # rho_{l+1}(B) (this works only when `p == 2`). In all the other cases, then + # admissible gluings only induce isometries of finite bilinear modules between + # rho_{l+1}(A) and rho_{l+1}(B). + spec = (p == 2) && (_is_free(qA, p, l+1)) && (_is_free(qB, p, l+1)) && (_is_even(qC, p, l)) # We look for the GA|GB-invariant and fA|fB-stable subgroups of VA|VB which respectively - # contained lqA|lqB. This is done by computing orbits and stabilisers of VA/lqA (resp VB/lqB) + # contained lpqA|lpqB, where pqA and pqB are respectively the p-primary parts of qA and qB. + # This is done by computing orbits and stabilisers of VA/lpqA (resp VB/lpqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) - subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fVA, ZZ(l)) - subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fVB, ZZ(l)) + subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fqA, ZZ(l)) + subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fqB, ZZ(l)) + # once we have the potential kernels, we create pairs of anti-isometric groups since glue # maps are anti-isometry R = Tuple{eltype(subsA), eltype(subsB), TorQuadModuleMor}[] @@ -747,9 +804,14 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, !ok && continue push!(R, (H1, H2, phi)) end - # now, for each pair of anti-isometric potential kernels, we need to see whether - # it is (fA,fB)-equivariant, up to conjugacy. For each working pair, we compute the - # corresponding overlattice and check whether it satisfies the type condition + + # now, for each pair of anti-isometric potential kernels, we need to massage the gluing + # computed to turn it into an admissible one. Then, we need to decide whether + # such an admissible gluing can be made (fA,fB)-equivariant, up to conjugacy. + # + # Each pair for which we can find such nice gluing, we create the double coset + # parametrising all such gluing up to certain conditions and we then compute the + # corresponding overlattice and check whether it satisfies the type conditions. for (H1, H2, phi) in R SAinqA, stabA = H1 SA = domain(SAinqA) @@ -759,53 +821,71 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, SB = domain(SBinqB) OSB = orthogonal_group(SB) + # 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 stabalizers in the respective OS* and we keep track + + # 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, [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))) + union!(kerA, OqAinOD(one(OqA))) fSA = OSA(restrict_automorphism(fqA, SAinqA)) actB = hom(stabB, 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])] + union!(kerB, OqBinOD(one(OqB))) fSB = OSB(restrict_automorphism(fqB, SBinqB)) + # 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 + # equiped with a quadratic form and we check isometries preserving it. + # Otherwise, only isometries preserving the underlying bilinear product. OSBrB = _compute_double_stabilizer(SBinqB, l, spec) - #elm = gset(OSBrB, ^, OSB) - + @hassert :ZZLatWithIsom 1 fSB in OSBrB # Should always hold since the construction of rho_{l+1}(B) is natural in B fSB = OSBrB(fSB) + # 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 glueing. + # 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))) - bool, g0 = representative_action(OSBrB, fSAinOSB, fSB) + @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 = representative_action(OSBrB, OSBrB(fSAinOSB), fSB) bool || continue + + # 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))) - fSAinOSB = OSB(compose(inv(phi), compose(hom(fSA), phi))) - # Now the new phi is "sending" the restriction of fA to this of fB. - # So we can glue SA and SB. - @hassert :ZZLatWithIsom 1 fSAinOSBrB == fSB + fSAinOSB = OSBrB(compose(inv(phi), compose(hom(fSA), phi))) + @hassert :ZZLatWithIsom 1 fSAinOSB == fSB # 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, _ = stabilizer(OSBrB, fSB) + center, _ = centralizer(OSBrB, fSB) center, _ = sub(OSB, [OSB(c) for c in gens(center)]) stabSAphi, _ = sub(OSB, AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(g), phi))) for g in gens(imA)]) stabSAphi, _ = intersect(center, stabSAphi) stabSB, _ = intersect(center, imB) reps = double_cosets(center, stabSB, stabSAphi) - # now we iterate over all double cosets and for each representative, we compute the - # corresponding overlattice in the glueing. If it has the wanted type, we compute - # the image of the centralizer in OD from the stabA and stabB. + # We iterate over all double cosets. Each representative, define a new + # classe of admissible gluing and so, for each such representative we compute the + # corresponding overlattice along the gluing. If it has the wanted type, we compute + # the image of the centralizer in OD from the stabA and stabB. for g in reps g = representative(g) phig = compose(phi, hom(g)) @assert is_anti_isometry(phig) + # The following is the regular procedure we use on Oscar/Hecke to compute + # overlattices of a gluing. We overwrite it here because we have too many + # parameters to keep track to in this function. We distinguish the case of + # similar ambient space since everything is made way easier as soon as + # we work in a unique fixed quadratic space. if amb _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] z = zero_matrix(QQ, 0, degree(A)) @@ -833,6 +913,15 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, end @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + # This is the type requirement: somehow, we want `(C2, fC2)` to be a "q-th root" of `(C, fC)`. + if !is_of_type(integer_lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) + continue + end + + # 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, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) disc = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), @@ -840,11 +929,11 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) - @hassert :ZZLatWithIsom 1 is_isometry(phi2) - if !is_of_type(lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) - continue - end - C2 = lattice_with_isometry(C2, fC2, ambient_representation=false) + @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 toi compute GC2 in a smart way. + C2 = integer_lattice_with_isometry(C2, fC2, ambient_representation=false) geneOSA = AutomorphismGroupElem{TorQuadModule}[OSA(compose(phig, compose(hom(g1), inv(phig)))) for g1 in unique(gens(imB))] im2_phi, _ = sub(OSA, geneOSA) gene_inter = AutomorphismGroupElem{TorQuadModule}[h for h in unique(gens(intersect(imA, im2_phi)[1]))] @@ -856,7 +945,11 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, 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 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(g, inv(phi2))).map_ab.map for g in stab]) + + # If we have done good things, the action of fC2 on qC2 should centralize + # itself... @hassert :ZZLatWithIsom 1 discriminant_group(C2)[2] in stab + set_attribute!(C2, :image_centralizer_in_Oq, stab) push!(results, C2) end @@ -864,18 +957,20 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, return results end +# Action of isometries on the gram matrix of a finite bilinear form function _on_modular_matrix(M::QQMatrix, g::AutomorphismGroupElem) q = domain(parent(g)) - R = value_module(q) - m = matrix(g) + R = Hecke.QmodnZ(QQ(1)) + m = matrix(inv(g)) return map_entries(a -> lift(R(a)), m*M*transpose(m)) end +# Action of isometries on the gram matrix of a finite quadratic form function _on_modular_matrix_quad(M::QQMatrix, g::AutomorphismGroupElem) q = domain(parent(g)) - R1 = value_module(q) - R2 = value_module_quadratic_form(q) - m = matrix(g) + R1 = Hecke.QmodnZ(QQ(1)) + R2 = Hecke.QmodnZ(QQ(2)) + m = matrix(inv(g)) m1 = m*M*transpose(m) for i in 1:nrows(m1) for j in 1:ncols(m1) @@ -889,6 +984,9 @@ function _on_modular_matrix_quad(M::QQMatrix, g::AutomorphismGroupElem) return m1 end +# We compute O(SB, rho_{l+1}(B)) where B has discriminant form qB. `spec` keep +# track whether rho_{l+1}(B) should be considered as a finite quadratic module +# or just a finite bilinear module (depends on the overlattice). function _compute_double_stabilizer(SBinqB, l, spec) SB = domain(SBinqB) qB = codomain(SBinqB) @@ -911,6 +1009,8 @@ function _compute_double_stabilizer(SBinqB, l, spec) return OSBrB end +# 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)) @@ -923,6 +1023,8 @@ function _is_isometry_bilinear(f::TorQuadModuleMor) 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)) @@ -935,6 +1037,10 @@ function _is_anti_isometry_bilinear(f::TorQuadModuleMor) return true end +# If we are given a gluing between SA and SB, given that an admissible gluing +# exist, we massage phi until we turn it into an admissible gluing. There might +# be ways to improve such search, but I would expect that both loops iterating +# on OSB and OSBHB are terminating after few tries. function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) SA = domain(SAinqA) SB = domain(SBinqB) @@ -950,17 +1056,16 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) rBtoSB = hom(rB, SB, [SB(QQ(p^l)*(lift(b))) for b in gens(rB)]) HB, HBinSB = sub(SB, rBtoSB.(gens(rB))) if p != 2 - phi_0 = _anti_isometry_bilinear(rA, rB, p) + phi_0 = _anti_isometry_bilinear(rA, rB) elseif 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 - phi_0 = _anti_isometry_bilinear(rA, rB, p) + phi_0 = _anti_isometry_bilinear(rA, rB) end phiHA, _ = sub(SB, [phi(SA(lift(a))) for a in gens(HA)]) OSB = orthogonal_group(SB) - @assert is_invariant(OSB, HBinSB) g = one(OSB) for f in OSB g = f @@ -983,16 +1088,8 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) return phig end -function _is_even(T) - B = gram_matrix_bilinear(T) - return is_empty(B) || all(is_zero, diagonal(B)) -end - -function _is_free(T, p, l) - return _is_even(_rho_functor(T, p, l-1)) && _is_even(_rho_functor(T, p, l+1)) -end - -function _anti_isometry_bilinear(r1, r2, p) +# Compute an anti-isometry between the two finite bilinear modules r1 and r2. +function _anti_isometry_bilinear(r1, r2) @hassert :ZZLatWithIsom is_semi_regular(r1) === is_semi_regular(r2) === true r2m = rescale(r2, -1) r2tor2m = hom(r2, r2m, identity_matrix(ZZ, ngens(r2))) diff --git a/experimental/LatticesWithIsometry/src/enumeration.jl b/experimental/LatticesWithIsometry/src/enumeration.jl index c5549e05b852..c493cfd816be 100644 --- a/experimental/LatticesWithIsometry/src/enumeration.jl +++ b/experimental/LatticesWithIsometry/src/enumeration.jl @@ -381,7 +381,7 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) repre = representatives(G) @vprint :ZZLatWithIsom 1 "$(length(repre)) representative(s)\n" for LL in repre - is_of_same_type(Lf, lattice_with_isometry(LL, f^m, check=false)) && push!(reps, lattice_with_isometry(LL, f, check=false)) + is_of_same_type(Lf, integer_lattice_with_isometry(LL, f^m, check=false)) && push!(reps, integer_lattice_with_isometry(LL, f, check=false)) end return reps end @@ -425,107 +425,24 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) @vprint :ZZLatWithIsom 1 "$H\n" M, fM = Hecke.trace_lattice_with_isometry(H) det(M) == d || continue - M = lattice_with_isometry(M, fM) + M = integer_lattice_with_isometry(M, fM) @hassert :ZZLatWithIsom 1 is_of_hermitian_type(M) @hassert :ZZLatWithIsom 1 order_of_isometry(M) == n*m if is_even(M) != is_even(Lf) continue end - if !is_of_same_type(Lf, lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) + if !is_of_same_type(Lf, integer_lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) continue end gr = genus_representatives(H) for HH in gr M, fM = Hecke.trace_lattice_with_isometry(HH) - push!(reps, lattice_with_isometry(M, fM)) + push!(reps, integer_lattice_with_isometry(M, fM)) end end return reps end -@doc raw""" - representatives_of_hermitian_type(t::Dict, m::Int = 1; check::Bool = true) - -> Vector{ZZLatWithIsom} - -Given a hermitian type `t` for lattices with isometry (i.e. the minimal -polymomial of the associated isometry is irreducible cyclotomic) and an intger -`m` (set to 1 by default), return a set of representatives of isomorphism -classes of lattices with isometry of hermitian type $(L, f)$ such that the -type of $(L, f^m)$ is equal to `t`. - -If `check === true`, then `t` is checked to be hermitian. Note that `n` can be 1. - -See Algorithm 3 of [BH22]. -""" -function representatives_of_hermitian_type(t::Dict, m::Int = 1; check::Bool = true) - M = _representative(t, check = check) - M === nothing && return ZZLatWithIsom[] - return representatives_of_hermitian_type(M, m) -end - -function _representative(t::Dict; check::Bool = true) - !check || is_hermitian(t) || error("t must be hermitian") - - ke = collect(keys(t)) - n = maximum(ke) - - G = t[n][2] - s1, s2 = signature_tuple(G) - rk = s1+s2 - d = det(G) - - if n < 3 - L = representative(G) - return trace_lattice(L, order = n) - end - - ok, rk = Hecke.divides(rk, euler_phi(n)) - - ok || reps - - gene = HermGenus[] - E, b = cyclotomic_field_as_cm_extension(n, cached=false) - Eabs, EabstoE = absolute_simple_field(E) - DE = EabstoE(different(maximal_order(Eabs))) - - ndE = d*inv(QQ(absolute_norm(DE)))^rk - detE = _ideals_of_norm(E, ndE) - - @vprint :ZZLatWithIsom 1 "All possible ideal dets: $(length(detE))\n" - - signatures = _possible_signatures(s1, s2, E) - - @vprint :ZZLatWithIsom 1 "All possible signatures: $(length(signatures))\n" - - for dd in detE, sign in signatures - append!(gene, hermitian_genera(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(DE*dd))) - end - gene = unique(gene) - - for g in gene - H = representative(g) - if !is_integral(DE*scale(H)) - continue - end - if iseven(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) - continue - end - H = H - M = trace_lattice(H) - det(M) == d || continue - @hassert :ZZLatWithIsom 1 is_of_hermitian_type(M) - @hassert :ZZLatWithIsom 1 order_of_isometry(M) == n - if iseven(M) != iseven(G) - continue - end - if !is_of_type(M, t) - continue - end - return M - end - return nothing -end - @doc raw""" splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int) -> Vector{ZZLatWithIsom} @@ -554,12 +471,12 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = atp = admissible_triples(Lf, p, pA = pA, pB = pB) @vprint :ZZLatWithIsom 1 "$(length(atp)) admissible triple(s)\n" for (A, B) in atp - LB = lattice_with_isometry(representative(B)) + LB = integer_lattice_with_isometry(representative(B)) RB = representatives_of_hermitian_type(LB, p*q^e) if is_empty(RB) continue end - LA = lattice_with_isometry(representative(A)) + LA = integer_lattice_with_isometry(representative(A)) RA = representatives_of_hermitian_type(LA, q^e) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB], inplace=false) E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p) @@ -573,26 +490,6 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = return reps end -@doc raw""" - splitting_of_hermitian_prime_power(t::Dict, p::Int) -> Vector{ZZLatWithIsom} - -Given a hermitian type `t` of lattice with isometry $(L, f)$ with `f` of order -$q^e$ for some prime number `q`, and given another prime number $p \neq q$, -return a set of representatives of the isomorphisms classes of lattices with -isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to `t`. - -Note that `e` can be 0. - -See Algorithm 4 of [BH22]. -""" -function splitting_of_hermitian_prime_power(t::Dict, p::Int) - @req is_prime(p) "p must be a prime number" - @req is_hermitian(t) "t must be hermitian" - Lf = _representative(t) - Lf === nothing && return ZZLatWithIsom[] - return splitting_of_hermitian_prime_power(Lf, p) -end - @doc raw""" splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) -> Vector{ZZLatWithIsom} @@ -786,7 +683,7 @@ Note that currently we support only orders which admit at most 2 prime divisors. function enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::Hecke.IntegerUnion) @req is_finite(order) && order >= 1 "order must be positive and finite" if order == 1 - return representatives_of_hermitian_type(lattice_with_isometry(L)) + return representatives_of_hermitian_type(integer_lattice_with_isometry(L)) end pd = prime_divisors(order) @req length(pd) in [1,2] "order must have at most two prime divisors" @@ -814,7 +711,7 @@ enumerate_classes_of_lattices_with_isometry(G::ZZGenus, order::Hecke.IntegerUnio function _enumerate_prime_power(L::ZZLat, q::Hecke.IntegerUnion, vq::Hecke.IntegerUnion) @hassert :ZZLatWithIsom 1 is_prime(q) @hassert :ZZLatWithIsom 1 vq >= 1 - Lq = splitting_of_prime_power(lattice_with_isometry(L), q, 1) + Lq = splitting_of_prime_power(integer_lattice_with_isometry(L), q, 1) vq == 1 && return Lq reps = ZZLatWithIsom[] while !is_empty(Lq) diff --git a/experimental/LatticesWithIsometry/src/exports.jl b/experimental/LatticesWithIsometry/src/exports.jl index 5454bedfc3a3..09b2d9a668d7 100644 --- a/experimental/LatticesWithIsometry/src/exports.jl +++ b/experimental/LatticesWithIsometry/src/exports.jl @@ -1,3 +1,4 @@ +export QuadSpaceWithIsom export ZZLatWithIsom export admissible_equivariant_primitive_extensions @@ -5,6 +6,7 @@ export admissible_triples export ambient_isometry export enumerate_classes_of_lattices_with_isometry export image_centralizer_in_Oq +export integer_lattice_with_isometry export invariant_coinvariant_pair export isometry export is_admissible_triple @@ -12,10 +14,10 @@ export is_of_hermitian_type export is_of_same_type export is_of_type export is_hermitian -export lattice_with_isometry export order_of_isometry export primitive_embeddings_of_primary_lattice export primitive_embeddings_in_primary_lattice +export quadratic_space_with_isometry export representatives_of_hermitian_type export splitting_of_hermitian_prime_power export splitting_of_mixed_prime_power diff --git a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl index 4de1fd76d053..d564c254b354 100644 --- a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl +++ b/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl @@ -19,13 +19,25 @@ Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. """ isometry(Lf::ZZLatWithIsom) = Lf.f +@doc raw""" + ambient_space(Lf::ZZLatWithIsom) -> QuadSpaceWithIsom + +Given a lattice with isometry $(L, f)$, return the pair $(V, g)$ where +`V` is the ambient quadratic space of `L` and `g` is an isometry of `V` +inducing `f` on `L`. + +Note that `g` might not be unique and we fix such a global isometry +together with `V` into a container type `QuadSpaceWithIsom`. +""" +ambient_space(Lf::ZZLatWithIsom) = Lf.Vf + @doc raw""" ambient_isometry(Lf::ZZLatWithIsom) -> QQMatrix -Given a lattice with isometry $(L, f)$, return an isometry of underlying isometry -of the ambient space of `L` inducing `f` on `L` +Given a lattice with isometry $(L, f)$, return an isometry of the ambient +space of `L` inducing `f` on `L` """ -ambient_isometry(Lf::ZZLatWithIsom) = Lf.f_ambient +ambient_isometry(Lf::ZZLatWithIsom) = isometry(ambient_space(Lf)) @doc raw""" order_of_isometry(Lf::ZZLatWithIsom) -> Integer @@ -73,14 +85,6 @@ lattice `L` (see [`genus(::ZZLat)`](@ref)). """ genus(Lf::ZZLatWithIsom) = genus(lattice(Lf))::ZZGenus -@doc raw""" - ambient_space(Lf::ZZLatWithIsom) -> QuadSpace - -Given a lattice with isometry $(L, f)$, return the ambient space of the underlying -lattice `L` (see [`ambient_space(::ZZLat)`](@ref)). -""" -ambient_space(Lf::ZZLatWithIsom) = ambient_space(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} - @doc raw""" basis_matrix(Lf::ZZLatWithIsom) -> QQMatrix @@ -99,12 +103,13 @@ of lattice `L` associted to `B` with respect to $\Phi$. gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix @doc raw""" - rational_span(Lf::ZZLatWithIsom) -> QuadSpace + rational_span(Lf::ZZLatWithIsom) -> QuadSpaceWithIsom -Given a lattice with isometry $(L, f)$, return the rational span $L \otimes \mathbb{Q}$ -of the underlying lattice `L`. +Given a lattice with isometry $(L, f)$, return the rational span +$L \otimes \mathbb{Q}$ of the underlying lattice `L` together with the +underlying isometry of `L`. """ -rational_span(Lf::ZZLatWithIsom) = rational_span(lattice(Lf))::Hecke.QuadSpace{FlintRationalField, QQMatrix} +rational_span(Lf::ZZLatWithIsom) = quadratic_space_with_isometry(rational_span(lattice(Lf)), isometry(Lf))::QuadSpaceWithIsom @doc raw""" det(Lf::ZZLatWithIsom) -> QQFieldElem @@ -209,12 +214,12 @@ signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, In ############################################################################### # -# Constructor +# Constructors # ############################################################################### @doc raw""" - lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, + integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, ambient_representation = true) -> ZZLatWithIsom @@ -226,10 +231,11 @@ ambient space of `L` and the induced isometry on `L` is automatically computed. Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity on the complement of the rational span of `L` if it is not of full rank. """ -function lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, - ambient_representation::Bool = true) +function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, + ambient_representation::Bool = true) if rank(L) == 0 - return ZZLatWithIsom(L, matrix(QQ,0,0,[]), identity_matrix(QQ, degree(L)), -1) + Vf = quadratic_space_with_isometry(ambient_space(L)) + return ZZLatWithIsom(Vf, L, matrix(QQ,0,0,[]), -1) end if check @@ -238,6 +244,7 @@ function lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, if ambient_representation f_ambient = f + Vf = quadratic_space_with_isometry(ambient_space(L), f_ambient, check=check) B = basis_matrix(L) ok, f = can_solve_with_solution(B, B*f_ambient, side = :left) @req ok "Isometry does not restrict to L" @@ -248,35 +255,78 @@ function lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, C = vcat(B, B2) f_ambient = block_diagonal_matrix([f, identity_matrix(QQ, nrows(B2))]) f_ambient = inv(C)*f_ambient*C + Vf = quadratic_space_with_isometry(V, f_ambient, check=check) end n = multiplicative_order(f) if check @req f*gram_matrix(L)*transpose(f) == gram_matrix(L) "f does not define an isometry of L" - @req f_ambient*gram_matrix(ambient_space(L))*transpose(f_ambient) == gram_matrix(ambient_space(L)) "f_ambient is not an isometry of the ambient space of L" @hassert :ZZLatWithIsom 1 basis_matrix(L)*f_ambient == f*basis_matrix(L) end - return ZZLatWithIsom(L, f, f_ambient, n)::ZZLatWithIsom + return ZZLatWithIsom(Vf, L, f, n)::ZZLatWithIsom end - @doc raw""" - lattice_with_isometry(L::ZZLat; neg::Bool = false) -> ZZLatWithIsom + integer_lattice_with_isometry(L::ZZLat; neg::Bool = false) -> ZZLatWithIsom Given a $\mathbb Z$-lattice `L` return the lattice with isometry pair $(L, f)$, where `f` corresponds to the identity mapping of `L`. If `neg` is set to `true`, then the isometry `f` is negative the identity of `L`. """ -function lattice_with_isometry(L::ZZLat; neg::Bool = false) +function integer_lattice_with_isometry(L::ZZLat; neg::Bool = false) d = degree(L) f = identity_matrix(QQ, d) if neg f = -f end - return lattice_with_isometry(L, f, check = false, ambient_representation = true)::ZZLatWithIsom + return integer_lattice_with_isometry(L, f, check = false, ambient_representation = true)::ZZLatWithIsom +end + +@doc raw""" + lattice(Vf::QuadSpaceWithIsom) -> ZZLatWithIsom + +Given a quadratic space with isometry $(V, f)$, return the full rank lattice `L` +in `V` with basis the standard basis, together with the induced action of `f` +on `L` +""" +lattice(Vf::QuadSpaceWithIsom) = ZZLatWithIsom(Vf, lattice(space(Vf)), isometry(Vf), order_of_isometry(Vf))::ZZLatWithIsom + +@doc raw""" + lattice(Vf::QuadSpaceWithIsom, B::MatElem{<:RationalUnion}; + isbasis::Bool = true, check::Bool = true) + -> ZZLatWithIsom + +Given a quadratic space with isometry $(V, f)$ and a matrix `B` generating a +lattice `L` in `V`, if `L` is preserved under the action of `f`, return the +lattice with isometry $(L, f_L)$ where $f_L$ is induced by the action of `f` +on `L`. +""" +function lattice(Vf::QuadSpaceWithIsom, B::MatElem{<:RationalUnion}; isbasis::Bool = true, check::Bool = true) + L = lattice(space(Vf), B, isbasis=isbasis, check=check) + ok, fB = can_solve_with_solution(basis_matrix(L), basis_matrix(L)*isometry(Vf), side = :left) + n = is_zero(fB) ? -1 : multiplicative_order(fB) + @req ok "The lattice defined by B is not preserved under the action of the isometry of Vf" + return ZZLatWithIsom(Vf, L, fB, n) +end + +@doc raw""" + lattice_in_same_ambient_space(L::ZZLatWithIsom, B::MatElem; + check::Bool = true) + -> ZZLatWithIsom + +Given a lattice with isometry $(L, f)$ and a matrix `B` whose rows define a free +system of vectors in the ambient space `V` of `L`, if the lattice `M` in `V` defined +by `B` is preserved under the fixed isometry `g` of `V` inducing `f` on `L`, return +the lattice with isometry pair $(M, f_M)$ where $f_M$ is induced by the action of +`g` on `M`. +""" +function lattice_in_same_ambient_space(L::ZZLatWithIsom, B::MatElem; check::Bool = true) + @req !check || (rank(B) == nrows(B)) "The rows of B must define a free system of vectors" + Vf = ambient_space(L) + return lattice(Vf, B, check = check) end ############################################################################### @@ -292,7 +342,7 @@ Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lat with isometry $(L(a), f)$ (see [`rescale(::ZZLat, ::RationalUnion)`](@ref)). """ function rescale(Lf::ZZLatWithIsom, a::Hecke.RationalUnion) - return lattice_with_isometry(rescale(lattice(Lf), a), ambient_isometry(Lf), check=false) + return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf), check=false) end @doc raw""" @@ -305,7 +355,7 @@ induced by `g`. """ function dual(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" - return lattice_with_isometry(dual(lattice(Lf)), ambient_isometry(Lf), check = false) + return lattice_in_same_ambient_space(Lf, basis_matrix(dual(lattice(Lf))), check = false) end @doc raw""" @@ -318,10 +368,13 @@ on the associated gram matrix of `L` (see [`gram_matrix(::ZZLat)`](@ref)). Note that matrix representing the action of `f` on `L` changes but the global action on the ambient space of `L` stays the same. """ -function lll(Lf::ZZLatWithIsom) - f = ambient_isometry(Lf) - L2 = lll(lattice(Lf), same_ambient=true) - return lattice_with_isometry(L2, f, ambient_representation = true) +function lll(Lf::ZZLatWithIsom; same_ambient::Bool = true) + L2 = lll(lattice(Lf), same_ambient = same_ambient) + if same_ambient + return lattice_in_same_ambient_space(Lf, basis_matrix(L2), check=false) + else + return integer_lattice_with_isometry(L2, isometry(Lf), check=false, ambient_representation=false) + end end @doc raw""" @@ -341,10 +394,9 @@ If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ a the projections $L \to L_i$, one should call `biproduct(x)`. """ function direct_sum(x::Vector{ZZLatWithIsom}) - @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" - W, inj = direct_sum(lattice.(x)) - f = block_diagonal_matrix(ambient_isometry.(x)) - return lattice_with_isometry(W, f, check=false), inj + Vf, inj = direct_sum(ambient_space.(x)) + Bs = diagonal_matrix(basis_matrix.(x)) + return lattice(Vf, Bs, check=false), inj end direct_sum(x::Vararg{ZZLatWithIsom}) = direct_sum(collect(x)) @@ -366,10 +418,9 @@ If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ a the projections $L \to L_i$, one should call `biproduct(x)`. """ function direct_product(x::Vector{ZZLatWithIsom}) - @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" - W, proj = direct_product(lattice.(x)) - f = block_diagonal_matrix(ambient_isometry.(x)) - return lattice_with_isometry(W, f, check=false), proj + Vf, proj = direct_product(ambient_space.(x)) + Bs = diagonal_matrix(basis_matrix.(x)) + return lattice(Vf, Bs, check=false), proj end direct_product(x::Vararg{ZZLatWithIsom}) = direct_product(collect(x)) @@ -392,14 +443,29 @@ If one wants to obtain $(L, f)$ as a direct product with the projections $L \to one should call `direct_product(x)`. """ function biproduct(x::Vector{ZZLatWithIsom}) - @req length(x) >= 2 "Input must consist of at least 2 lattices with isometries" - W, inj, proj = biproduct(lattice.(x)) - f = block_diagonal_matrix(ambient_isometry.(x)) - return lattice_with_isometry(W, f, check=false), inj, proj + Vf, inj, proj = biproduct(ambient_space.(x)) + Bs = diagonal_matrix(basis_matrix.(x)) + return lattice(Vf, Bs, check=false), inj, proj end biproduct(x::Vararg{ZZLatWithIsom}) = biproduct(collect(x)) +############################################################################### +# +# Equality and hash +# +############################################################################### + +function Base.:(==)(L1::ZZLatWithIsom, L2::ZZLatWithIsom) + ambient_space(L1) == ambient_space(L2) || return false + return lattice(L1) == lattice(L2) +end + +function Base.hash(L::ZZLatWithIsom, u::UInt) + u = Base.hash(ambient_space(L), u) + return Base.hash(lattice(L), u) +end + ############################################################################### # # Hermitian structure @@ -588,14 +654,14 @@ function kernel_lattice(Lf::ZZLatWithIsom, p::QQPolyRingElem) M = p(f) d = denominator(M) k, K = left_kernel(change_base_ring(ZZ, d*M)) - L2 = lattice_in_same_ambient_space(L, K*basis_matrix(L)) - f2 = solve_left(change_base_ring(QQ, K), K*f) - @hassert :ZZLatWithIsom 1 f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) - chi = parent(p)(collect(coefficients(minpoly(f2)))) - chif = parent(p)(collect(coefficients(minpoly(Lf)))) - _chi = gcd(p, chif) - @hassert :ZZLatWithIsom 1 (rank(L2) == 0) || (chi == _chi) - return lattice_with_isometry(L2, f2, ambient_representation = false) + return lattice(ambient_space(Lf), K*basis_matrix(L)) + #f2 = solve_left(change_base_ring(QQ, K), K*f) + #@hassert :ZZLatWithIsom 1 f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) + #chi = parent(p)(collect(coefficients(minpoly(f2)))) + #chif = parent(p)(collect(coefficients(minpoly(Lf)))) + #_chi = gcd(p, chif) + #@hassert :ZZLatWithIsom 1 (rank(L2) == 0) || (chi == _chi) + #return integer_lattice_with_isometry(L2, f2, ambient_representation = false) end kernel_lattice(Lf::ZZLatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change_base_ring(QQ, p)) @@ -677,7 +743,7 @@ function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::B mL = matrix(g) m_amb = solve(basis_matrix(L), mL*basis_matrix(L)) mC = solve_left(basis_matrix(C), basis_matrix(C)*m_amb) - push!(gene, mc) + push!(gene, mC) else push!(gene, matrix(g)) end @@ -708,7 +774,7 @@ function invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; ambient_representa mL = matrix(g) m_amb = solve(basis_matrix(L), mL*basis_matrix(L)) mC = solve_left(basis_matrix(C), basis_matrix(C)*m_amb) - push!(gene, mc) + push!(gene, mC) else push!(gene, matrix(g)) end @@ -815,6 +881,6 @@ function to_oscar(Lf::ZZLatWithIsom) println(stdout, "G = matrix(QQ, $(degree(L)), $(degree(L)), ", gram_matrix(ambient_space(L)), ");") println(stdout, "L = integer_lattice(B, gram = G);") println(stdout, "f = matrix(QQ, $(degree(L)), $(degree(L)), ", f, ");") - println(stdout, "Lf = lattice_with_isometry(L, f);") + println(stdout, "Lf = integer_lattice_with_isometry(L, f);") end diff --git a/experimental/LatticesWithIsometry/src/printings.jl b/experimental/LatticesWithIsometry/src/printings.jl index db73656592d9..fb9a400a9477 100644 --- a/experimental/LatticesWithIsometry/src/printings.jl +++ b/experimental/LatticesWithIsometry/src/printings.jl @@ -25,3 +25,31 @@ function Base.show(io::IO, Lf::ZZLatWithIsom) end end end + +function Base.show(io::IO, ::MIME"text/plain", Vf::QuadSpaceWithIsom) + io = AbstractAlgebra.pretty(io) + println(io, space(Vf)) + n = order_of_isometry(Vf) + print(io, AbstractAlgebra.Indent()) + if is_finite(n) + println(io, "with isometry of finite order $n") + else + println(io, "with isometry of infinite order") + end + println(io, "given by") + show(io, MIME"text/plain"(), isometry(Vf)) + print(io, AbstractAlgebra.Dedent()) +end + +function Base.show(io::IO, Vf::QuadSpaceWithIsom) + if get(io, :supercompact, false) + print(io, "Quadratic space with isometry") + else + n = order_of_isometry(Vf) + if is_finite(n) + print(io, "Quadratic space with isometry of finite order $n") + else + print(io, "Quadratic space with isometry of infinite order") + end + end +end diff --git a/experimental/LatticesWithIsometry/src/spaces_with_isometry.jl b/experimental/LatticesWithIsometry/src/spaces_with_isometry.jl new file mode 100644 index 000000000000..819875d8f402 --- /dev/null +++ b/experimental/LatticesWithIsometry/src/spaces_with_isometry.jl @@ -0,0 +1,285 @@ + +############################################################################### +# +# Accesors +# +############################################################################### + +@doc raw""" + space(Vf::QuadSpaceWithIsom) -> QuadSpace + +Given a quadratic space with isometry $(V, f)$, return the underlying space `V`. +""" +space(Vf::QuadSpaceWithIsom) = Vf.V + +@doc raw""" + isometry(Vf::QuadSpaceWithIsom) -> QQMatrix + +Given a quadratic space with isometry $(V, f)$, return the underlying isometry +`f`. +""" +isometry(Vf::QuadSpaceWithIsom) = Vf.f + +@doc raw""" + order_of_isometry(Vf::QuadSpaceWithIsom) -> IntExt + +Given a quadratic space with isometry $(V, f)$, return the order of the +underlying isometry `f` +""" +order_of_isometry(Vf::QuadSpaceWithIsom) = Vf.n + +############################################################################### +# +# Attributes +# +############################################################################### + +@doc raw""" + rank(Vf::QuadSpaceWithIsom) -> Integer + +Given a quadratic space with isometry $(V, f)$, return the rank of the underlying +space `V`. +""" +rank(Vf::QuadSpaceWithIsom) = rank(space(Vf))::Integer + +@doc raw""" + dim(Vf::QuadSpaceWithIsom) -> Integer + +Given a quadratic space with isometry $(V, f)$, return the dimension of the +underlying space of `V` +""" +dim(Vf::QuadSpaceWithIsom) = dim(space(Vf))::Integer + +@doc raw""" + charpoly(Vf::QuadSpaceWithIsom) -> QQPolyRingElem + +Given a quadratic space with isometry $(V, f)$, return the characteristic +polynomial of the underlying isometry `f` +""" +charpoly(Vf::QuadSpaceWithIsom) = charpoly(isometry(Vf))::QQPolyRingElem + +@doc raw""" + minpoly(Vf::QuadSpaceWithIsom) -> QQPolyRingElem + +Given a quadratic space with isometry $(V, f)$, return the minimal +polynomial of the underlying isometry `f`. +""" +minpoly(Vf) = minpoly(isometry(Vf))::QQPolyRingElem + +@doc raw""" + gram_matrix(Vf::QuadSpaceWithIsom) -> QQMatrix + +Given a quadratic space with isometry $(V, f)$, return the Gram matrix +of the underlying space `V` with respect to its standard basis. +""" +gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf))::QQMatrix + +@doc raw""" + det(Vf::QuadSpaceWithIsom) -> QQFieldElem + +Given a quadratic space with isometry $(V, f)$, return the determinant +of the underlying space `V`. +""" +det(Vf::QuadSpaceWithIsom) = det(space(Vf))::QQFieldElem + +@doc raw""" + discriminant(Vf::QuadSpaceWithIsom) -> QQFieldElem + +Given a quadratic space with isometry $(V, f)$, return the discriminant +of the underlying space `V`. +""" +discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf))::QQFieldElem + +@doc raw""" + is_positive_definite(Vf::QuadSpaceWithIsom) -> Bool + +Given a quadratic space with isometry $(V, f)$, return whether the underlying +space `V` is positive definite. +""" +is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf))::Bool + +@doc raw""" + is_negative_definite(Vf::QuadSpaceWithIsom) -> Bool + +Given a quadratic space with isometry $(V, f)$, return whether the underlying +space `V` is negative definite. +""" +is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf))::Bool + +@doc raw""" + is_definite(Vf::QuadSpaceWithIsom) -> Bool + +Given a quadratic space with isometry $(V, f)$, return whether the underlying +space `V` is definite. +""" +is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf))::Bool + +@doc raw""" + diagonal(Vf::QuadSpaceWithIsom) -> Vector{QQFieldElem} + +Given a quadratic space with isometry $(V, f)$, return the diagonal of the +underlying space `V`, that is a list of rational numbers $a_1, \ldots a_n$ +such that `V` is isometric to the space whose Gram matrix is diagonal with +entries $a_1,\ldots, a_n$. +""" +diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf))::Vector{QQFieldElem} + +@doc raw""" + signature_tuple(Vf::QuadSpaceWithIsom) -> Tuple{Int, Int, Int} + +Given a quadratic space with isometry $(V, f)$, return the signature +tuple of the underlying space `V`. +""" +signature_tuple(Vf::QuadSpaceWithIsom) = signature_tuple(space(Vf))::Tuple{Int, Int, Int} + +############################################################################### +# +# Constructors +# +############################################################################### + +@doc raw""" + quadratic_space_with_isometry(V:QuadSpace, f::QQMatrix; check::Bool = false) + -> QuadSpaceWithIsom + +Given a quadratic space `V` and a matrix `f`, if `f` defines an isometry of `V` +of order `n` (possibly infinite), return the corresponding quadratic space with +isometry pair $(V, f)$. +""" +function quadratic_space_with_isometry(V::Hecke.QuadSpace, f::QQMatrix; + check::Bool = true) + if rank(V) == 0 + return QuadSpaceWithIsom(V, zero_matrix(QQ, 0, 0), -1) + end + + if check + @req det(f) != 0 "Matrix must be invertible" + @req f*gram_matrix(V)*transpose(f) == gram_matrix(V) "Matrix does not define an isometry of the given quadratic space" + end + + n = multiplicative_order(f) + return QuadSpaceWithIsom(V, f, n) +end + +@doc raw""" + quadratic_space_with_isometry(V::QuadSpace; neg::Bool = false) -> QuadSpaceWithIsom + +Given a quadratic space `V`, return the quadratic space with isometry pair $(V, f)$ +where `f` is represented by the identity matrix. + +If `neg` is set to true, then the isometry `f` is negative the identity on `V`. +""" +function quadratic_space_with_isometry(V::Hecke.QuadSpace; neg::Bool = false) + f = identity_matrix(QQ, dim(V)) + f = neg ? -f : f + return quadratic_space_with_isometry(V, f, check=false) +end + +############################################################################### +# +# Operations on quadratic space with isometry +# +############################################################################### + +@doc raw""" + rescale(Vf::QuadSpaceWithIsom, a::RationalUnion) + +Given a quadratic space with isometry $(V, f)$, return the pair $(V^a, f$) where +$V^a$ is the same space as `V` with the associated quadratic form rescaled by `a`. +""" +function rescale(Vf::QuadSpaceWithIsom, a::Hecke.RationalUnion) + return quadratic_space_with_isometry(rescale(space(Vf), a), isometry(Vf), check = false) +end + +@doc raw""" + direct_sum(x::Vector{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor} + direct_sum(x::Vararg{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor} + +Given a collection of quadratic spaces with isometries $(V_1, f_1) \ldots, (V_n, f_n)$, +return the quadratic space with isometry $(V, f)$ together with the injections +$V_i \to V$, where `V` is the direct sum $V := V_1 \oplus \ldots \oplus V_n$ and +`f` is the isometry of `V` induced by the diagonal actions of the $f_i$'s. + +For objects of type `QuadSpaceWithIsom`, finite direct sums and finite direct products +agree and they are therefore called biproducts. +If one wants to obtain $(V, f)$ as a direct product with the projections $V \to V_i$, +one should call `direct_product(x)`. +If one wants to obtain $(V, f)$ as a biproduct with the injections $V_i \to V$ and +the projections $V \to V_i$, one should call `biproduct(x)`. +""" + +function direct_sum(x::Vector{T}) where T <: QuadSpaceWithIsom + V, inj = direct_sum(space.(x)) + f = block_diagonal_matrix(isometry.(x)) + return quadratic_space_with_isometry(V, f, check=false), inj +end + +direct_sum(x::Vararg{QuadSpaceWithIsom}) = direct_sum(collect(x)) + +@doc raw""" + direct_product(x::Vector{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor} + direct_product(x::Vararg{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor} + +Given a collection of quadratic spaces with isometries $(V_1, f_1), \ldots, (V_n, f_n)$, +return the quadratic space with isometry $(V, f)$ together with the projections +$V \to V_i$, where `V` is the direct product $V := V_1 \times \ldots \times V_n$ and +`f` is the isometry of `V` induced by the diagonal actions of the $f_i$'s. + +For objects of type `QuadSpaceWithIsom`, finite direct sums and finite direct products +agree and they are therefore called biproducts. +If one wants to obtain $(V, f)$ as a direct sum with the injections $V_i \to V$, +one should call `direct_sum(x)`. +If one wants to obtain $(V, f)$ as a biproduct with the injections $V_i \to V$ and +the projections $V \to V_i$, one should call `biproduct(x)`. +""" + +function direct_product(x::Vector{T}) where T <: QuadSpaceWithIsom + V, proj = direct_product(space.(x)) + f = block_diagonal_matrix(isometry.(x)) + return quadratic_space_with_isometry(V, f, check=false), proj +end + +direct_product(x::Vararg{QuadSpaceWithIsom}) = direct_product(collect(x)) + +@doc raw""" + biproduct(x::Vector{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} + biproduct(x::Vararg{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} + +Given a collection of quadratic spaces with isometries $(V_1, f_1), \ldots, (V_n, f_n)$, +return the quadratic space with isometry $(V, f)$ together with the injections +$V_i \to V$ and the projections $V \to V_i$, where `V` is the biproduct +$V := V_1 \oplus \ldots \oplus V_n$ and `f` is the isometry of `V` induced by the +diagonal actions of the $f_i$'s. + +For objects of type `QuadSpaceWithIsom`, finite direct sums and finite direct products +agree and they are therefore called biproducts. +If one wants to obtain $(V, f)$ as a direct sum with the injections $V_i \to V$, +one should call `direct_sum(x)`. +If one wants to obtain $(V, f)$ as a direct product with the projections $V \to V_i$, +one should call `direct_product(x)`. +""" + +function biproduct(x::Vector{T}) where T <: QuadSpaceWithIsom + V, inj, proj = biproduct(space.(x)) + f = block_diagonal_matrix(isometry.(x)) + return quadratic_space_with_isometry(V, f, check=false), inj, proj +end + +biproduct(x::Vararg{QuadSpaceWithIsom}) = biproduct(collect(x)) + +############################################################################### +# +# Equality and hash +# +############################################################################### + +function Base.:(==)(V1::QuadSpaceWithIsom, V2::QuadSpaceWithIsom) + space(V1) == space(V2) || return false + return isometry(V1) == isometry(V2) +end + +function Base.hash(V::QuadSpaceWithIsom, u::UInt) + u = Base.hash(space(V), u) + return Base.hash(isometry(V), u) +end + diff --git a/experimental/LatticesWithIsometry/src/types.jl b/experimental/LatticesWithIsometry/src/types.jl index cd0bc95092f3..d9a9c169371c 100644 --- a/experimental/LatticesWithIsometry/src/types.jl +++ b/experimental/LatticesWithIsometry/src/types.jl @@ -1,3 +1,29 @@ +@doc raw""" + QuadSpaceWithIsom + +A container type for pairs `(V, f)` consisting on an rational quadratic space +`V` of type `QuadSpace` and an isometry `f` given as a `QQMatrix` representing +the action on the standard basis of `V`. + +We store the order of `f` too, which can finite or of infinite order. + +To construct an object of type `QuadSpaceWithIsom`, see the set of functions +called [`quadratic_space_with_isometry`](@ref) +""" +@attributes mutable struct QuadSpaceWithIsom + V::Hecke.QuadSpace + f::QQMatrix + n::IntExt + + function QuadSpaceWithIsom(V::Hecke.QuadSpace, f::QQMatrix, n::IntExt) + z = new() + z.V = V + z.f = f + z.n = n + return z + end +end + @doc raw""" ZZLatWithIsom @@ -5,25 +31,25 @@ A container type for pairs `(L, f)` consisting on an integer lattice `L` of type `ZZLat` and an isometry `f` given as a `QQMatrix` representing the action on a given basis of `L`. -The associated action `f_ambient` on the ambient space of `L` as well as the order -`n` of `f` are also stored. +We store the ambient space `V` of `L` together with an isometry `f_ambient` +inducing `f` on `L` seen as a pair $(V, f_ambient)$ of type `QuadSpaceWithIsom`. +We moreover store the order `n` of `f`, which can be finite or infinite. To construct an object of type `ZZLatWithIsom`, see the set of functions called -[`lattice_with_isometry`](@ref) +[`integer_lattice_with_isometry`](@ref) """ @attributes mutable struct ZZLatWithIsom + Vf::QuadSpaceWithIsom Lb::ZZLat f::QQMatrix - f_ambient::QQMatrix n::IntExt - function ZZLatWithIsom(Lb::ZZLat, f::QQMatrix, f_ambient::QQMatrix, n::IntExt) + function ZZLatWithIsom(Vf::QuadSpaceWithIsom, Lb::ZZLat, f::QQMatrix, n::IntExt) z = new() z.Lb = Lb z.f = f - z.f_ambient = f_ambient + z.Vf = Vf z.n = n return z end end - diff --git a/src/Oscar.jl b/src/Oscar.jl index ff4ab0182a91..bcc133bf6799 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -98,8 +98,8 @@ function __init__() add_verbosity_scope(:LinearQuotients) - add_assertion_scope(:LatWithIsom) - add_verbosity_scope(:LatWithIsom) + add_assertion_scope(:ZZLatWithIsom) + add_verbosity_scope(:ZZLatWithIsom) end const PROJECT_TOML = Pkg.TOML.parsefile(joinpath(@__DIR__, "..", "Project.toml")) From 69e382ccf5bf114bb65c61e4e53611e955218ea5 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 26 Jun 2023 11:07:03 +0200 Subject: [PATCH 59/76] change names + finish doc + some fixes --- .../docs/src/primembed.md | 0 .../LatticesWithIsometry/src/types.jl | 55 --- .../LatticesWithIsometry/test/runtests.jl | 45 --- .../README.md | 47 +-- .../docs/doc.main | 3 +- .../docs/src/enumeration.md | 40 +- .../docs/src/introduction.md | 51 +-- .../docs/src/latwithisom.md | 59 ++- .../QuadFormAndIsom/docs/src/primembed.md | 94 +++++ .../QuadFormAndIsom/docs/src/spacewithisom.md | 100 +++++ .../src/QuadFormAndIsom.jl} | 0 .../src/embeddings.jl | 356 +++++++++++++----- .../src/enumeration.jl | 79 +++- .../src/exports.jl | 0 .../src/hermitian_miranda_morrison.jl | 5 +- .../src/lattices_with_isometry.jl | 156 +++++--- .../src/printings.jl | 12 + .../src/spaces_with_isometry.jl | 56 ++- experimental/QuadFormAndIsom/src/types.jl | 208 ++++++++++ experimental/QuadFormAndIsom/test/runtests.jl | 168 +++++++++ 20 files changed, 1175 insertions(+), 359 deletions(-) delete mode 100644 experimental/LatticesWithIsometry/docs/src/primembed.md delete mode 100644 experimental/LatticesWithIsometry/src/types.jl delete mode 100644 experimental/LatticesWithIsometry/test/runtests.jl rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/README.md (60%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/docs/doc.main (60%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/docs/src/enumeration.md (65%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/docs/src/introduction.md (60%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/docs/src/latwithisom.md (81%) create mode 100644 experimental/QuadFormAndIsom/docs/src/primembed.md create mode 100644 experimental/QuadFormAndIsom/docs/src/spacewithisom.md rename experimental/{LatticesWithIsometry/src/LatticesWithIsometry.jl => QuadFormAndIsom/src/QuadFormAndIsom.jl} (100%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/src/embeddings.jl (75%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/src/enumeration.jl (92%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/src/exports.jl (100%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/src/hermitian_miranda_morrison.jl (99%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/src/lattices_with_isometry.jl (88%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/src/printings.jl (80%) rename experimental/{LatticesWithIsometry => QuadFormAndIsom}/src/spaces_with_isometry.jl (87%) create mode 100644 experimental/QuadFormAndIsom/src/types.jl create mode 100644 experimental/QuadFormAndIsom/test/runtests.jl diff --git a/experimental/LatticesWithIsometry/docs/src/primembed.md b/experimental/LatticesWithIsometry/docs/src/primembed.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/experimental/LatticesWithIsometry/src/types.jl b/experimental/LatticesWithIsometry/src/types.jl deleted file mode 100644 index d9a9c169371c..000000000000 --- a/experimental/LatticesWithIsometry/src/types.jl +++ /dev/null @@ -1,55 +0,0 @@ -@doc raw""" - QuadSpaceWithIsom - -A container type for pairs `(V, f)` consisting on an rational quadratic space -`V` of type `QuadSpace` and an isometry `f` given as a `QQMatrix` representing -the action on the standard basis of `V`. - -We store the order of `f` too, which can finite or of infinite order. - -To construct an object of type `QuadSpaceWithIsom`, see the set of functions -called [`quadratic_space_with_isometry`](@ref) -""" -@attributes mutable struct QuadSpaceWithIsom - V::Hecke.QuadSpace - f::QQMatrix - n::IntExt - - function QuadSpaceWithIsom(V::Hecke.QuadSpace, f::QQMatrix, n::IntExt) - z = new() - z.V = V - z.f = f - z.n = n - return z - end -end - -@doc raw""" - ZZLatWithIsom - -A container type for pairs `(L, f)` consisting on an integer lattice `L` of -type `ZZLat` and an isometry `f` given as a `QQMatrix` representing the action -on a given basis of `L`. - -We store the ambient space `V` of `L` together with an isometry `f_ambient` -inducing `f` on `L` seen as a pair $(V, f_ambient)$ of type `QuadSpaceWithIsom`. -We moreover store the order `n` of `f`, which can be finite or infinite. - -To construct an object of type `ZZLatWithIsom`, see the set of functions called -[`integer_lattice_with_isometry`](@ref) -""" -@attributes mutable struct ZZLatWithIsom - Vf::QuadSpaceWithIsom - Lb::ZZLat - f::QQMatrix - n::IntExt - - function ZZLatWithIsom(Vf::QuadSpaceWithIsom, Lb::ZZLat, f::QQMatrix, n::IntExt) - z = new() - z.Lb = Lb - z.f = f - z.Vf = Vf - z.n = n - return z - end -end diff --git a/experimental/LatticesWithIsometry/test/runtests.jl b/experimental/LatticesWithIsometry/test/runtests.jl deleted file mode 100644 index e54683b211ca..000000000000 --- a/experimental/LatticesWithIsometry/test/runtests.jl +++ /dev/null @@ -1,45 +0,0 @@ -using test -using Oscar - -@testset "Constructors and accessors" begin - A4 = root_lattice(:A, 4) - agg = = automorphism_group_generators(A4, ambient_representation = false) - agg_ambient = automorphism_group_generators(A4, ambient_representation = true) - f = rand(agg) - g_ambient = rand(agg_ambient) - - L = @inferred lattice_with_isometry(A4) - @test isone(isometry(L)) - @test isone(ambient_isometry(L)) - @test isone(order_of_isometry(L)) - - for func in [rank, charpoly, minpoly, genus, ambient_space, basis_matrix, - gram_matrix, rational_span, det, scale, norm, is_integral, - degree, is_even, discriminant, signature_tuple] - out = @inferred func(L) - end - - @test minimum(rescale(L, -1)) == 2 - @test !is_positive_definite(L) - @test is_definite(L) - - nf = multiplicative_order(f) - @test_throws ArgumentError lattice_with_isometry(A4, zero_matrix(QQ, 0, 0)) - - L2 = @inferred lattice_with_isometry(A4, f, ambient_representation = false) - @test order_of_isometry(L2) == nf - L2v = @inferred dual(L2) - @test order_of_isometry(L2v) == nf - @test ambient_isometry(L2v) == ambient_isometry(L2) - - L3 = @inferred lattice_with_isometry(A4, g_ambient, ambient_representation = true) - @test order_of_isometry(L2) == multiplicative_order(g_ambient) - - L4 = @inferred rescale(L3, QQ(1//4)) - @test !is_integral(L4) - @test order_of_isometry(L4) == order_of_isometry(L3) - @test_throws ArgumentError dual(L4) - @test ambient_isometry(lll(L4)) == ambient_isometry(L4) - - @test order_of_isometry(biproduct(L2, L3)) == lcm(order_of_isometry(L2), order_of_isometry(L3)) -end diff --git a/experimental/LatticesWithIsometry/README.md b/experimental/QuadFormAndIsom/README.md similarity index 60% rename from experimental/LatticesWithIsometry/README.md rename to experimental/QuadFormAndIsom/README.md index c12aa8d8e73e..c303462b1ddf 100644 --- a/experimental/LatticesWithIsometry/README.md +++ b/experimental/QuadFormAndIsom/README.md @@ -1,28 +1,35 @@ -# Integer lattices with isometry +# Quadratic forms and isometries This project is a complement to the code about *hermitian lattices* available on Hecke. We aim here to connect Hecke and GAP to handle some algorithmic -methods regarding integer lattices with their isometries. In particular, +methods regarding quadratic forms with their isometries. In particular, the integration of this code to Oscar is necessary to benefit all the performance of GAP with respect to computations with groups and automorphisms in general. +For now, the project covers methods regarding rational and integral quadratic +forms. + ## Content -We introduce the new type `ZZLatWithIsom` which parametrizes pairs $(L, f)$ where -$L$ is a non-denegerate $\mathbb{Z}$-lattice and $f$ is an isometry of $L$. One -of the main feature of this project is the enumeration of isomorphism classes of -pairs $(L, f)$ where $f$ is an isometry of finite order with at most two prime -divisors. The methods we resort to for this purpose are developed in the paper -[BH23]. +We introduce two new structures +* `QuadSpaceWithIsom` +* `ZZLatWithIsom` +The former parametrizes pairs $(V, f)$ where $V$ is a rational quadratic form +and $f$ is an isometry of $V$. The latter parametrizes pairs $(L, f)$ where +$L$ is an integral quadratic form, also known as $\mathbb Z$-lattice and $f$ +is an isometry of $L$. One of the main feature of this project is the +enumeration of isomorphism classes of pairs $(L, f)$ where $f$ is an isometry +of finite order with at most two prime divisors. The methods we resort to +for this purpose are developed in the paper [BH23]. We also provide some algorithms computing isomorphism classes of primitive -embeddings of even lattices following [Nikulin]. More precisely, the two +embeddings of even lattices following Nikulin's theory. More precisely, the two functions `primitive_embeddings_in_primary_lattice` and `primitive_embeddings_of_primary_lattice` offer, under certain conditions, -the possibility of obtaining representatives of such classes of primitive -embeddings. Note nonetheless that these functions are not efficient in the case -were the discriminant groups are large. +the possibility to compute representatives of primitive embeddings and classify +them in different ways. Note nonetheless that these functions are not efficient +in the case were the discriminant groups have a large number of subgroups. ## Status @@ -34,18 +41,16 @@ Among the possible improvements and extensions: * Implement methods about for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); -* Implement methods for all kinds of equivariant primitive extensions (not - necessarily admissibles); -* Import the methods for extending trivial discriminant actions on lattice whose - discriminant group is an abelian $p$-group. +* Implement methods for primitive embeddings in odd integral lattices; +* Implement methods for more kinds of (equivariant) primitive extensions. ## Currently application of this project The project was initiated by S. Brandhorst and T. Hofmann for classifying finite subgroups of automorphisms of K3 surfaces. Our current goal is to use this code, and further extension of it, to classify finite subgroups of -automorphisms and birational transformations on *hyperkaehler manifolds*, which -are a higher dimensional analog of K3 surface. +bimeromorphic self-maps of *hyperkaehler manifolds*, which are a higher +dimensional analog of K3 surface. ## Tutorials @@ -57,9 +62,9 @@ report all the bugs you may have found. Any suggestions for improvements or extensions are more than welcome. Refer to the next section to know who you should contact and how. -One may expect many things to vary within the next months: name of the functions, -available features, performance. This is due to the fact that the current -version of the code is still at an experimental stage. +One may expect many things to vary within the next months: name of the +functions, available features, performance. This is due to the fact that the +current version of the code is still at an experimental stage. ## Contact diff --git a/experimental/LatticesWithIsometry/docs/doc.main b/experimental/QuadFormAndIsom/docs/doc.main similarity index 60% rename from experimental/LatticesWithIsometry/docs/doc.main rename to experimental/QuadFormAndIsom/docs/doc.main index ecd16f531959..b11cac1869a8 100644 --- a/experimental/LatticesWithIsometry/docs/doc.main +++ b/experimental/QuadFormAndIsom/docs/doc.main @@ -1,6 +1,7 @@ [ - "Lattices with isometry" => [ + "Quadratic forms and isometries" => [ "introduction.md", + "spacewithisom.md", "latwithisom.md", "enumeration.m", "primembed.md" diff --git a/experimental/LatticesWithIsometry/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md similarity index 65% rename from experimental/LatticesWithIsometry/docs/src/enumeration.md rename to experimental/QuadFormAndIsom/docs/src/enumeration.md index 092a53768cd3..45397a366d92 100644 --- a/experimental/LatticesWithIsometry/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -11,16 +11,22 @@ reference. # Admissible triples Roughly speaking, for a prime number $p$, a *$p$-admissible triple* `(A, B, C)` -is a triple of integer lattices such that `C` can be obtained has a primitive -extension $A \perp B \to C$ where on can glue along $p$-elementary subgroups of -the respective discriminant groups of `A` and `B`. For instance, if $f$ is an -isometry of `C` of prime order `p`, then for $A := \Ker \Phi_1(f)$ and -$B := \Ker \Phi_p(f)$, one had that `(A, B, C)` is $p$-admissible -(see [BH13, Lemma 4.15.]). +is a triple of integer lattices such that,in certain cases, `C` can be obtained +has a primitive extension $A \perp B \to C$ where one can glue along +$p$-elementary subgroups of the respective discriminant groups of `A` and `B`. +Note that not all admissible triples satisfy this extension property. + +For instance, if $f$ is an isometry of an integer lattice `C` of prime order +`p`, then for $A := \Ker \Phi_1(f)$ and $B := \Ker \Phi_p(f)$, one has that +`(A, B, C)` is $p$-admissible (see [BH13, Lemma 4.15.]). + +We say that a triple `(AA, BB, CC)` of genus symbols for integer lattices is +*$p$-admissible* if there are some lattices $A \in AA$, $B \in BB$ and +$C \in CC$ such that $(A, B, C)$ is $p$-admissible. We use Definition 4.13. and Algorithm 1 of [BH23] to implement the necessary tools for working with admissible triples. Most of the computations consists of -genus symbol manipulations and combinatorics. The code also relies on +local genus symbol manipulations and combinatorics. The code also relies on enumeration of integer genera with given signatures, determinant and bounded scale valuations for the Jordan components at all the relevant primes (see [`integer_genera`](@ref)). @@ -37,13 +43,13 @@ isometry of a given order and in a given genus. We give an overview of the functions implemented for the enumeration of the isometries of integral integer lattices. For more details such as the proof of -the algorithm or the theory behind them, we refer to our reference paper [BH23]. +the algorithms and the theory behind them, we refer to the reference paper [BH23]. ## Global function As we will see later, the algorithms from [BH23] are specialized on the requirement for the input and regular users might not be able to choose between -the functions to choose there. We therefore provide a general function which +the functions available. We therefore provide a general function which allows one to enumerate lattices with isometry of a given order and in a given genus. The only requirements are to provide a genus symbol, or a lattice from this genus, and the order wanted (as long as the number of distinct prime @@ -53,7 +59,7 @@ divisors is at most 2). enumerate_classes_of_lattices_with_isometry(::ZZLat, ::Hecke.IntegerUnion) ``` -As a remark: of $n = p^dq^e$ is the chosen order, with $p < q$ prime numbers, +As a remark: if $n = p^dq^e$ is the chosen order, with $p < q$ prime numbers, the previous function computes first iteratively representatives for all classes with isometry in the given genus of order $q^e$. Then, the function increases iteratively the order up to $p^dq^e$. @@ -65,18 +71,16 @@ to enumerate lattices with isometry: ```@docs representatives_of_hermitian_type(::ZZLatWithIsom, ::Int) -representatives_of_hermitian_type(::Dict, ::Int) splitting_of_hermitian_prime_power(::ZZLatWithIsom, ::Int) -splitting_of_hermitian_prime_power(::Dict, ::Int) splitting_of_prime_power(::ZZLatWithIsom, ::Int, ::Int) splitting_of_pure_mixed_prime_power(::ZZLatWithIsom, ::Int) splitting_of_mixed_prime_power(::ZZLatWithIsom, ::Int, ::Int) ``` -Note that an important feature from the [BH23]-program is the notion of -*admissible gluings* and equivariant primitive embeddings for admissible pairs. +Note that an important feature from the theory in [BH23] is the notion of +*admissible gluings* and equivariant primitive embeddings for admissible triples. In the next chapter, we will develop about the features regarding primitive -embeddings and their equivariant extension. We use this basis to introduce the -method `admissible_equivariant_primitive_extension` (Algorithm 2 in [BH23]) -which is the major tool making the previous enumeration possible and fast, from -an algorithmic point of view. +embeddings and their equivariant version. We use this basis to introduce the +method [`admissible_equivariant_primitive_extension`](@ref) (Algorithm 2 in +[BH23]) which is the major tool making the previous enumeration possible and +fast, from an algorithmic point of view. diff --git a/experimental/LatticesWithIsometry/docs/src/introduction.md b/experimental/QuadFormAndIsom/docs/src/introduction.md similarity index 60% rename from experimental/LatticesWithIsometry/docs/src/introduction.md rename to experimental/QuadFormAndIsom/docs/src/introduction.md index 9a0e6d72a78f..52ce87586e4b 100644 --- a/experimental/LatticesWithIsometry/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -1,32 +1,35 @@ -```@meta -CurrentModue = Oscar -``` - -# Integer lattices and their isometries +# Quadratic forms and isometries This project is a complement to the code about *hermitian lattices* available on Hecke. We aim here to connect Hecke and GAP to handle some algorithmic -methods regarding integer lattices with their isometries. In particular, +methods regarding quadratic forms with their isometries. In particular, the integration of this code to Oscar is necessary to benefit all the performance of GAP with respect to computations with groups and automorphisms in general. +For now, the project covers methods regarding rational and integral quadratic +forms. + ## Content -We introduce the new type `ZZLatWithIsom` which parametrizes pairs $(L, f)$ where -$L$ is a non-denegerate $\mathbb{Z}$-lattice and $f$ is an isometry of $L$. One -of the main feature of this project is the enumeration of isomorphism classes of -pairs $(L, f)$ where $f$ is an isometry of finite order with at most two prime -divisors. The methods we resort to for this purpose are developed in the paper -[BH23]. +We introduce two new structures +* `QuadSpaceWithIsom` +* `ZZLatWithIsom` +The former parametrizes pairs $(V, f)$ where $V$ is a rational quadratic form +and $f$ is an isometry of $V$. The latter parametrizes pairs $(L, f)$ where +$L$ is an integral quadratic form, also known as $\mathbb Z$-lattice and $f$ +is an isometry of $L$. One of the main feature of this project is the +enumeration of isomorphism classes of pairs $(L, f)$ where $f$ is an isometry +of finite order with at most two prime divisors. The methods we resort to +for this purpose are developed in the paper [BH23]. We also provide some algorithms computing isomorphism classes of primitive -embeddings of even lattices following [Nikulin]. More precisely, the two +embeddings of even lattices following Nikulin's theory. More precisely, the two functions `primitive_embeddings_in_primary_lattice` and `primitive_embeddings_of_primary_lattice` offer, under certain conditions, -the possibility of obtaining representatives of such classes of primitive -embeddings. Note nonetheless that these functions are not efficient in the case -were the discriminant groups are large. +the possibility to compute representatives of primitive embeddings and classify +them in different ways. Note nonetheless that these functions are not efficient +in the case were the discriminant groups have a large number of subgroups. ## Status @@ -38,18 +41,16 @@ Among the possible improvements and extensions: * Implement methods about for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); -* Implement methods for all kinds of equivariant primitive extensions (not - necessarily admissible); -* Import the methods for extending trivial discriminant actions on lattice whose - discriminant group is an abelian $p$-group. +* Implement methods for primitive embeddings in odd integral lattices; +* Implement methods for more kinds of equivariant primitive extensions. ## Currently application of this project The project was initiated by S. Brandhorst and T. Hofmann for classifying finite subgroups of automorphisms of K3 surfaces. Our current goal is to use this code, and further extension of it, to classify finite subgroups of -automorphisms and birational transformations on *hyperkaehler manifolds*, which -are a higher dimensional analog of K3 surface. +bimeromorphic self-maps of *hyperkaehler manifolds*, which are a higher +dimensional analog of K3 surface. ## Tutorials @@ -61,9 +62,9 @@ report all the bugs you may have found. Any suggestions for improvements or extensions are more than welcome. Refer to the next section to know who you should contact and how. -One may expect many things to vary within the next months: name of the functions, -available features, performance. This is due to the fact that the current -version of the code is still at an experimental stage. +One may expect many things to vary within the next months: name of the +functions, available features, performance. This is due to the fact that the +current version of the code is still at an experimental stage. ## Contact diff --git a/experimental/LatticesWithIsometry/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md similarity index 81% rename from experimental/LatticesWithIsometry/docs/src/latwithisom.md rename to experimental/QuadFormAndIsom/docs/src/latwithisom.md index 3e00f7568cbd..67906b82614c 100644 --- a/experimental/LatticesWithIsometry/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -9,44 +9,60 @@ lattice $L$ together with an isometry $f \in O(L)$. We refer to the section about integer lattices of the documentation for new users. On Oscar, such a pair is contained into a type called `ZZLatWithIsom`: + ```@docs ZZLatWithIsom ``` -and it is seen as a quadruple $(L, f, f_a, n)$ where $n$ is the order of $f$ and -$f_a$ is an isometry of the ambient quadratic space of $L$ inducing $f$ on $L$. -Note that $f_a$ might not always be of order $n$. +and it is seen as a quadruple $(Vf, L, f, n)$ where $Vf = (V, f_a)$ consists on +the ambient rational quadratic space $V$ of $L$ and an isometry $f_a$ of $V$ +preversing $L$ and inducing $f$ on $L$. $n$ is the order of $f$, which is a +divisor of the order of the isometry $f_a\in O(V)$. Given a lattice with isometry $(L, f)$, we provide the following accessors to the elements of the previously described quadruple: ```@docs -lattice(::ZZLatWithIsom) -isometry(::ZZLatWithIsom) ambient_isometry(::ZZLatWithIsom) +ambient_space(::ZZLatWithIsom) +isometry(::ZZLatWithIsom) +lattice(::ZZLatWithIsom) order_of_isometry(::ZZLatWithIsom) ``` Note that for some computations, it is more convenient to work either with the -isometry of the lattice itself, or with an isometry of the ambient quadratic -space inducing it on the lattice. +isometry of the lattice itself, or with the fixed isometry of the ambient +quadratic space inducing it on the lattice. ## Constructor -For simplicity, we have gathered the main constructors under the same name -`lattice_with_isometry`. The user has then the choice on the parameters -depending on what they attend to do: +We provide two ways to construct a pair $Lf = (L,f)$ consisting on an integer +lattice endowed with an isometry. One way to construct an object of type +`ZZLatWithIsom` is through the methods `integer_lattice_with_isometry`. These +two methods does not require as input an ambient quadratic space with isometry. ```@docs -lattice_with_isometry(::ZZLat, ::QQMatrix) -lattice_with_isometry(::ZZLat) +integer_lattice_with_isometry(::ZZLat, ::QQMatrix) +integer_lattice_with_isometry(::ZZLat) ``` By default, the first constructor will always check whether the entry matrix defines an isometry of the lattice, or its ambient space. We recommend not to -disable this parameter to avoid any further issues. Both isometries of *finite +disable this parameter to avoid any further issues. Note that as in the case of +quadratic space with isometries, both isometries of integer lattices of *finite order* and *infinite order* are supported. +Another way of constructing such lattices with isometry is by fixing an ambient +quadratic space with isometry, of type `QuadSpaceWithIsom`, and specify a basis +for an integral lattice in that space. If this lattice is preserved by the fixed +isometry of the quadratic space considered, then we endow it with the induced +action. + +```@docs +lattice(::QuadSpaceWithIsom) +lattice(::QuadSpaceWithIsom, ::MatElem{ <:RationalUnion}) +lattice_in_same_ambient_space(::ZZLatWithIsom, ::MatElem) +``` ### Examples ```@repl 2 @@ -58,7 +74,7 @@ f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; 1 0 0 0 0 0; -1 -1 -1 0 0 -1; 0 0 1 1 0 1]); -Lf = lattice_with_isometry(L, f) +Lf = integer_lattice_with_isometry(L, f) ``` ## Attributes and first operations @@ -69,9 +85,8 @@ instance, in order to know the genus of $L$, one can simply call `genus(Lf)`. Here is a list of what are the current accessible attributes: ```@docs -ambient_space(::ZZLatWithIsom) basis_matrix(::ZZLatWithIsom) -charpoly(::ZZLatWithIsom) +characteristic_polynomial(::ZZLatWithIsom) degree(::ZZLatWithIsom) det(::ZZLatWithIsom) discriminant(::ZZLatWithIsom) @@ -83,7 +98,7 @@ is_integral(::ZZLatWithIsom) is_positive_definite(::ZZLatWithIsom) is_negative_definite(::ZZLatWithIsom) minimum(::ZZLatWithIsom) -minpoly(::ZZLatWithIsom) +minimal_polynomial(::ZZLatWithIsom) norm(::ZZLatWithIsom) rank(::ZZLatWithIsom) rational_span(::ZZLatWithIsom) @@ -134,7 +149,7 @@ f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; 1 0 0 0 0 0; -1 -1 -1 0 0 -1; 0 0 1 1 0 1]); -Lf = lattice_with_isometry(L, f) +Lf = integer_lattice_with_isometry(L, f) type(Lf) ``` @@ -241,6 +256,14 @@ hermitian structure associated to $(L, f)$ via the trace equivalence. signatures(::ZZLatWithIsom) ``` +## Equality + +We choose as a convention that two pairs $(L, f)$ and $(L', f')$ of integer +lattices with isometries are *equal* if their ambient quadratic space with +isometry of type `QuadSpaceWithIsom` are equal, and if the underlying lattices +$L$ and $L'$ are equal as $\mathbb Z$-modules in the common ambient quadratic +space. + ## Tips for users ### Report an issue diff --git a/experimental/QuadFormAndIsom/docs/src/primembed.md b/experimental/QuadFormAndIsom/docs/src/primembed.md new file mode 100644 index 000000000000..52d4ad8033f5 --- /dev/null +++ b/experimental/QuadFormAndIsom/docs/src/primembed.md @@ -0,0 +1,94 @@ +```@meta +CurrentModule = Oscar +``` + +We introduce here the necessary definitions and results which lie behind the +methods presented. Most of the content is taken from [Nik79]. + +# Primitive embeddings between even lattices + +Given an embedding $i\colon S\hookrightarrow T$ of non-degenerate integral +integer lattices, we call $i$ *primitive* if its cokernel $T/i(S)$ is torsion +free. Two primitive embeddings $i_1\colon S\hookrightarrow M_1$ and +$i_2\colon S \hookrightarrow M_2$ of $S$ into two lattices $M_1$ and $M_2$ are +called *isomorphic* if there exists an isometry $M_1 \to M_2$ which restricts to +the identity of $S$. Moreover, if there exists an isometry between $M_1$ and +$M_2$ which maps $S$ to itself (non-necessarily identically), we say that $i_1$ +and $i_2$ defines *isomorphic primitive sublattices* [Nik79]. + +In his paper, V. V. Nikulin gives necessary and sufficient condition for an even +integral lattice $M$ to embed primitively into an even unimodular lattice with +given invariant ([Nik79, Theorem 1.12.2]). More generally, the author also +provides methods to compute primitive embeddings of any even lattice into an even +lattice in a given genus ([Nik79, Proposition 1.15.1]). In the latter +proposition, it is explain how to classify such embeddings as isomorphic +embeddings or as isomorphic sublattices. + +Such a method can be algorithmically implemented, however it tends to be slow +and inefficient in general for large rank or determinant. However, in the case +where the discriminant groups are (elementary) $p$-groups, the method can be +more efficient. + +The ultimate goal of the project is to make all kind of computations of +primitive embeddings available. For now, we only cover the case where one of +the two lattices involved is *$p$-primary*, i.e. its discriminant is an abelian +$p$-group. Note that this covers the case of unimodular lattices, of course. +We provide 4 kinds of output: +* A boolean, which only returns whether there exists a primitive embedding; +* A single primitive embedding as soon as the algorithm computes one; +* A list of representatives of isomorphism classes of primitive embeddings; +* A list of representatives of isomorphism classes of primitive sublattices. + +```@docs +primitive_embeddings_in_primary_lattice(::ZZLat, ::ZZLat) +primitive_embeddings_of_primary_lattice(::ZZLat, ::ZZLat) +``` + +Note that the previous two functions require the first lattice in input to be +unique in its genus. Otherwise, one can refer a genus, or its invariant, as a +first input: + +```@docs +primitive_embeddings_in_primary_lattice(::ZZgenus, ::ZZLat) +primitive_embeddings_in_primary_lattice(::TorQuadModule, ::Tuple{Int, Int}, +::ZZLat) +primitive_embeddings_of_primary_lattice(::ZZGenus, ::ZZLat) +primitive_embeddings_of_primary_lattice(::TorQuadModule, ::Tuple{Int, Int}, +::ZZLat) +``` + +In order to compute such primitive embeddings of a lattice `M` into a lattice +`L`, one first compute the possible genera for the orthogonal of `M` in `L` +(after embedding), and for each lattice `N` in such a genus, one compute +isomorphism classes of *primitive extensions* of $M \perp N$ modulo $\bar{O}(N)$ +(and $\bar{O}(M)$ in the case of classification of primitive sublattices of `L` +isometric to `M`). + +We recall that a *primitive extension* of the orthogonal direct sum of two +integral integer lattices `M` and `N` is an overlattice `L` of $M\perp N$ such +that both `M` and `N` embeds primitively in `L` (via the natural embeddings +$M,N \to M\perp N\subseteq L$). Such primitive extensions are obtained, and +classified, by looking for *gluings* between anti-isometric subgroups of the +respective discriminant groups of `M` and `N`. The construction of an +overlattice is determined by the graph of such glue map. + +# Admissible equivariant primitive extension + +The following function is an interesting tool provided by [BH23]. Given a triple +of integer lattices with isometry `((A, a), (B, b), (C, c))` and two prime +numbers `p` and `q` (possibly equal), if `(A, B, C)` is `p`-admissible, this +function returns representatives of isomorphism classes of equivariant primitive +extensions $(A, a)\perp (B, b)\to (D, d)$ such that the type of $(D, d^p)$ is +equal to the type of $(C, c)$ (see [`type(::ZZLatWithIsom)`](@ref)). + +```@docs +admissible_equivariant_primitive_extensions(::ZZLatWithIsom, ::ZZLatWithIsom, ::ZZLatWithIsom, ::Integer, ::Integer) +``` + +An *equivariant primitive extension* of a pair of integer lattices with +isometries $(M, f_M)$ and $(N, f_N)$ is a primitive extension of `M` and `N` +obtained by gluing two subgroups which are respectively $\bar{f_M}$ and +$\bar{f_N}$ stable along a glue map which commutes with these two actions. If +such a gluing exists, then the overlattice `L` of $M\perp N$ is equipped with +an isometry $f_L$ which preserves both `M` and `N`, and restricts to $f_M$ and +$f_N$ respectively. diff --git a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md new file mode 100644 index 000000000000..f7644bab4eea --- /dev/null +++ b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md @@ -0,0 +1,100 @@ +```@meta +CurrentModule = Oscar +``` + +# Quadratic space with isometry + +We call *quadratic space with isometry* any pair $(V, f)$ consisting of a +non-degenerate quadratic space $V$ together with an isometry $f\in O(V)$. +We refer to the section about quadratic spaces of the documentation for +new users. + +Note that currently, we support only rational quadratic forms, i.e. +quadratic spaces defined over the rational. + +On Oscar, such a pair is contained into a type called `QuadSpaceWithIsom`: + +```@docs +QuadSpaceWithIsom +``` + +and it is seen as a triple $(V, f, n)$ where $n$ is the order of $f$. We +actually support isometries of finite and infinite order. In the case where +$f$ is of infinite order, then `n = PosInf`. If $V$ has rank 0, then any +isometry $f$ of $V$ is trivial and we set by default `n = -1`. + +Given a quadratic space with isometry $(V, f)$, we provide the following +accessors to the elements of the previously described tripe: + +```@docs +isometry(::QuadSpaceWithIsom) +order_of_isometry(::QuadSpaceWithIsom) +space(::QuadSpaceWithIsom) +``` + +The main purpose of the definition of such objects is to defined a fix +contextual ambient space for quadratic lattices endowed with an isometry. +Indeed, as we will see in the next section, *lattices with isometry* are +attached to an ambient quadratic space with an isometry inducing the one on the +lattice. + +## Constructors + +For simplicity, we have gathered the main constructors for objects of type +`QuadSpaceWithIsom` under the same name `quadratic_space_with_isometry`. The +user has then the choice on the parameters depending on what they attend to do: + +```@docs +quadratic_space_with_isometry(::QuadSpace, ::QQMatrix) +quadratic_space_with_isometry(::QuadSpace) +``` + +By default, the first constructor always checks whether the entry matrix defines +an isometry of the quadratic space. We recommend not to disable this parameter +to avoid any complications. Note however that in the rank 0 case, the checks are +avoided since all isometries are necessarily trivial. + +### Examples + +```@repl2 +using Oscar # hide +``` + +## Attributes and first operations + +Given a quadratic space with isometry $Vf := (V, f)$, one can have access to +most of the attributes of $V$ and $f$ by calling the similar functions to the +pair $(V, f)$ itself. For instance, in order to know the rank of $V$, one can +simply `rank(Vf)`. Here is a list of what are the current accessible attributes: + +```@docs +characteristic_polynomial(::QuadSpaceWithIsom) +det(::QuadSpaceWithIsom) +diagonal(::QuadSpaceWithIsom) +dim(::QuadSpaceWithIsom) +discriminant(::QuadSpaceWithIsom) +gram_matrix(::QuadSpaceWithIsom) +is_definite(::QuadSpaceWithIsom) +is_positive_definite(::QuadSpaceWithIsom) +is_negative_definite(::QuadSpaceWithIsom) +minimal_polynomial(::QuadSpaceWithIsom) +rank(::QuadSpaceWithIsom) +signature_tuple(::QuadSpaceWithIsom) +``` + +Similarly, some basic operations on quadratic spaces are available for quadratic +spaces with isometry. + +```@docs +biproduct(::Vector{QuadSpaceWithIsom}) +direct_product(::Vector{QuadSpaceWithIsom}) +direct_sum(::Vector{QuadSpaceWithIsom}) +rescale(::QuadSpaceWithIsom, ::Hecke.RationalUnion) +``` + +## Equality + +We choose as a convention that two pairs $(V, f)$ and $(V', f')$ of quadratic +spaces with isometries are *equal* if and only if $V$ and $V'$ are the same +space, and $f$ and $f'$ are represented by the same matrix with respect to the +standard basis of $V = V'$. diff --git a/experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl b/experimental/QuadFormAndIsom/src/QuadFormAndIsom.jl similarity index 100% rename from experimental/LatticesWithIsometry/src/LatticesWithIsometry.jl rename to experimental/QuadFormAndIsom/src/QuadFormAndIsom.jl diff --git a/experimental/LatticesWithIsometry/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl similarity index 75% rename from experimental/LatticesWithIsometry/src/embeddings.jl rename to experimental/QuadFormAndIsom/src/embeddings.jl index 43c386ea3152..6261eabda808 100644 --- a/experimental/LatticesWithIsometry/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -123,7 +123,7 @@ end # zeros. function _is_even(T, p, l) B = gram_matrix_bilinear(_rho_functor(T, p, l, quad=false)) - return is_empty(B) || all(is_zero, diagonal(B)) + return is_empty(B) || (all(is_zero, diagonal(B)) && all(is_integral, 2*B)) end function _is_free(T, p, l) @@ -136,13 +136,6 @@ end # ############################################################################## -# Provisional waiting to be able to compare torsion quadratic modules in a same -# space -function _on_subgroup_automorphic(H::Oscar.GAPGroup, g::AutomorphismGroupElem) - G = domain(parent(g)) - return sub(G, g.(gens(H)))[1] -end - # Should eventually replace the one above function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) @@ -156,25 +149,21 @@ end function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) - togap = get_attribute(O, :to_gap) - tooscar = get_attribute(O, :to_oscar) - A = codomain(togap) - OA = automorphism_group(A) - OinOA, _ = sub(OA, OA.([g.X for g in gens(O)])) - N, _ = sub(A, togap.(i.(gens(domain(i))))) - stab, _ = stabilizer(OinOA, N, _on_subgroup_automorphic) + 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)])) end -function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}; +function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, order::Hecke.IntegerUnion = -1, f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq))) fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) - V = domain(OV) + V = domain(Vinq) + q = codomain(Vinq) if order == -1 - subs = stable_submodules(V, [fV]) + subs = collect(stable_submodules(V, [fV])) else - subs = submodules(V, order=order) + subs = submodules(V, order = order) filter!(s -> is_invariant(fV, s[2]), subs) end subs = TorQuadModule[s[1] for s in subs] @@ -190,9 +179,13 @@ function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor return res end -function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, q::TorQuadModule; f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) - sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, order=order(q), f=f) - return filter(d -> is_isometric_with_isometry(domain(d[1]), q)[1], sors) +function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, H::TorQuadModule; f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) + if is_elementary_with_prime(H)[1] + sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, order(H), f) + else + sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, order(H), f) + end + return filter(d -> is_isometric_with_isometry(domain(d[1]), H)[1], sors) end # The underlying abelian groups of H and V are elementary abelian p-groups, f is @@ -245,6 +238,8 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu V = domain(Vinq) + # If V is trivial, then we ignore f and l, we just need to ensure that the + # order wanted is also 1 if order(V) == 1 ord != 1 && (return res) push!(res, (Vinq, G)) @@ -257,6 +252,9 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu l = l < 0 ? valuation(order(pq), p) : l g = valuation(ord, p) + # some other trivial cases: if ord is 1, then l should be null (-1 by default) + # Otherwise, if ord == order(V), since V is preserved by f and contained the + # good subgroup of q, we just return V if ord == 1 l < valuation(order(pq), p) && (return res) _, triv = sub(codomain(Vinq), TorQuadModuleElem[]) @@ -267,35 +265,51 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end + # 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, [q(lift((p^l)*a)) for a in gens(pq)]) @hassert :ZZLatWithIsom 1 is_invariant(f, H0inq) + H0inV = hom(H0, V, [V(lift(a)) for a in gens(H0)]) @hassert :ZZLatWithIsom 1 is_injective(H0inV) + # H0 should be contained in the group we want. So either H0 is the only one + # and we return it, or if order(H0) > ord, there are no subgroups as wanted if order(H0) >= ord order(H0) > ord && (return res) push!(res, (H0inq, G)) return res end + # Since V and H0 are elementary p-groups, they can be seen as finite + # dimensional vector spaces over a finite field, and so is their quotient. + # Moreover, subgroups of V of order ord and containing H0 are in bijections + # with cosets in V/H0 of rank val_p(ord-order(H)) over the finite field F_p Qp, VtoVp, VptoQp = _cokernel_as_Fp_vector_space(H0inV, p) Vp = codomain(VtoVp) + # Should never happend, but who knows... dim(Qp) == 0 && (return res) + # We descend G to V for computing stabilizers later on GV, GtoGV = restrict_automorphism_group(G, Vinq) satV, j = kernel(GtoGV) + # Automorphisms in G preserved V and H0, since the construction of H0 is + # natural. Therefore, the action of G descends to the quotient and we look for + # invariants sub-vector spaces of given rank in the quotient (then lifting + # generators and putting them with H0 will give us invariant subgroups as + # wanted) act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] - MGp = matrix_group(act_GV_Qp) + MGp = matrix_group(dim(Qp), base_ring(Qp), act_GV_Qp) GVtoMGp = hom(GV, MGp, MGp.(act_GV_Qp), check = false) GtoMGp = compose(GtoGV, GVtoMGp) @hassert :ZZLatWithIsom g-ngens(snf(abelian_group(H0))[1]) < dim(Qp) F = base_ring(Qp) + # K is H0 but seen a subvector space of Vp (which is V) k, K = kernel(VptoQp.matrix, side = :left) gene_H0p = elem_type(Vp)[Vp(vec(collect(K[i,:]))) for i in 1:k] orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) @@ -309,17 +323,20 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu gene_submod_in_q = TorQuadModuleElem[image(Vinq, v) for v in gene_submod_in_V] orbq, orbqinq = sub(q, gene_submod_in_q) @hassert :ZZLatWithIsom 1 order(orbq) == ord + # We keep only f-stable subspaces is_invariant(f, orbqinq) || continue stabq = AutomorphismGroupElem{TorQuadModule}[GtoMGp\(s) for s in gens(stab)] - stabq, _ = sub(G, union(stabq, satV)) + stabq, _ = sub(G, union(stabq, 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) push!(res, (orbqinq, stabq)) - @label non_fixed end return res end +# Need to be discarded after next Hecke release function Base.:(==)(T1::TorQuadModule, T2::TorQuadModule) relations(T1) != relations(T2) && return false return cover(T1) == cover(T2) @@ -336,11 +353,11 @@ end # isometric and anti-isometric to H. # # We follow the second definition of Nikulin, i.e. we classify up to the action -# of O(N). If `classification == :strong`, we also classify them up to the +# of O(N). If `classification == :sublat`, we also classify them up to the # action of O(M). If `classification == :first`, we return the first embedding # computed. function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQuadModule, classification::Symbol) - @hassert :ZZLatWithIsom 1 classification in [:first, :weak, :strong] + @hassert :ZZLatWithIsom 1 classification in [:first, :emb, :sublat] results = Tuple{ZZLat, ZZLat, ZZLat}[] prim, p = is_primary_with_prime(H) el = prim ? is_elementary(H, p) : false @@ -349,7 +366,7 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua GN, _ = image_in_Oq(N) qM = discriminant_group(M) - if classification == :weak + if classification == :emb GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(id_hom(qM))]) else GM, _ = image_in_Oq(M) @@ -360,11 +377,11 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua OD = orthogonal_group(D) if el - VN, VNinqN, _ = _get_V(id_hom(qN), minpoly(identity_matrix(QQ,1)), p) + VN, VNinqN, _ = _get_V(id_hom(qN), minimal_polynomial(identity_matrix(QQ,1)), p) subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H)[1], subsN) @assert !isempty(subsN) - VM, VMinqM, _ = _get_V(id_hom(qM), minpoly(identity_matrix(QQ, 1)), p) + VM, VMinqM, _ = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), p) subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(VMinqM, GM, p) filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H)[1], subsM) @assert !isempty(subsM) @@ -432,17 +449,16 @@ end @doc raw""" primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; - classification::Symbol = :strong, - first::Bool = false, + classification::Symbol = :sublat, check::Bool = true) -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} -Given a `p`-primary lattice `L`, unique in its genus, and a lattice `M`, -return whether `M` embeds primitively in `L`. +Given a $p$-primary lattice `L`, which is unique in its genus, and an integer +lattice `M`, return whether `M` embeds primitively in `L`. The first input of the function is a boolean `T` stating whether or not `M` embeds primitively in `L`. The second output `V` consists on triples -`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice of +`(L', M', N')` where `L'` isometric to `L`, `M'` is a primtiive sublattice of `L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. If `T == false`, then `V` will always be the empty list. If `T == true`, then @@ -450,40 +466,112 @@ the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :strong`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in `L` up to the actions of $\bar{O}(M)$ - and $\bar{O}(L)$; - - `classification = :weak`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in `L` up to the action of $\bar{O}(L)$. + - `classification = :sublat`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ + and $O(q)$ where `q` is the discriminant group of `L`; + - `classification = :emd`: `V` consists on representatives for all isomorphism + classes of primitive sublattices of `L` isometric to `M` up to the action of + $O(q)$ where `q` is the discriminant group of `L`. + +If `check` is set to true, the function determines whether `L` is in fact unique +in its genus. """ -function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :strong, check::Bool = false) - @req classification in [:none, :weak, :strong, :first] "Wrong symbol for classification" - pL, _, nL = signature_tuple(L) - pM, _, nM = signature_tuple(M) - @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" - @req rank(M) < rank(L) "M must be of smaller rank than L" - - bool, p = is_primary_with_prime(L) - @req bool "L must be unimodular or primary" - el = is_elementary(L, p) - +function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = false) if check @req length(genus_representatives(L)) == 1 "L must be unique in its genus" end + return primitive_embeddings_in_primary_lattice(genus(L), M, classification = classification) +end + +@doc raw""" + primitive_embeddings_in_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; + classification::Symbol = :sublat) + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} + +Given a tuple `sign` of non-negative integers and a torsion quadratic module +`q` which define a genus symbol `G` for $p$-primary lattices, return whether the +integer lattice `M` embeds primitively in a lattice in `G`. + +The first input of the function is a boolean `T` stating whether or not `M` +embeds primitively in a lattice in `G`. The second output `V` consists on +triples `(L', M', N')` where `L'` is a lattice in `G`, `M'` is a sublattice of +`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. + +If `T == false`, then `V` will always be the empty list. If `T == true`, then +the content of `V` actually depends on the value of the symbol `classification`. +There are 4 possibilities: + - `classification = :none`: `V` is the empty list; + - `classification = :first`: V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in lattices in `G`, up to the actions of + $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; + - `classification = :emd`: `V` consists on representatives for all isomorphism + classes of primitive sublattices of lattices in `G` isometric to `M`, up to the + action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + +If the pair `(q, sign)` does not define a non-empty genus for integer lattices, +an error is thrown. +""" +function primitive_embeddings_in_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) + @req is_genus(q, sign) "Invariants define the empty genus" + G = genus(q, sign) + return primitive_embeddings_in_primary_lattice(G, M, classification = classification) +end + +@doc raw""" + primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; + classification::Symbol = :sublat) + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} + +Given a genus symbol `G` for $p$-primary integer lattices and an integer +lattice `M`, return whether `M` embeds primitively in a lattice in `G`. + +The first input of the function is a boolean `T` stating whether or not `M` +embeds primitively in a lattice in `G`. The second output `V` consists on +triples `(L', M', N')` where `L'` is a lattice in `G`, `M'` is a sublattice of +`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. + +If `T == false`, then `V` will always be the empty list. If `T == true`, then +the content of `V` actually depends on the value of the symbol `classification`. +There are 4 possibilities: + - `classification = :none`: `V` is the empty list; + - `classification = :first`: V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in lattices in `G`, up to the actions of + $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant of a lattice in `G`; + - `classification = :emb`: `V` consists on representatives for all isomorphism + classes of primitive sublattices of lattices in `G` isometric to `M`, up to the + action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. +""" +function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) + @req classification in [:none, :emb, :sublat, :first] "Wrong symbol for classification" + pL, _, nL = signature_tuple(G) + pM, _, nM = signature_tuple(M) + @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" results = Tuple{ZZLat, ZZLat, ZZLat}[] + if rank(M) == rank(G) + !(M in G) && return results + push!(results, (M, M, orthogonal_submodule(M, M))) + end + + @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" + + bool, p = is_primary_with_prime(G) + @req bool "G must be unimodular or primary" + el = is_elementary(G, p) qM = discriminant_group(M) GM, _ = image_in_Oq(M) - - GL, _ = image_in_Oq(rescale(L, -1)) - qL = domain(GL) + + qL = discriminant_group(rescale(G, -1)) + GL = orthogonal_group(qL) D, inj = direct_sum(qM, qL) qMinD, qLinD = inj if el - VM, VMinqM, _ = _get_V(id_hom(qM), minpoly(identity_matrix(QQ,1)), p) + VM, VMinqM, _ = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ,1)), p) else VM, VMinqM = primary_part(qM, p) end @@ -520,9 +608,9 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat classification == :none && return true, results - G = genus(disc2, (pL-pM, nL-nM)) + G2 = genus(disc2, (pL-pM, nL-nM)) @vprint :ZZLatWithIsom 1 "We can glue: $G\n" - Ns = representatives(G) + Ns = representatives(G2) @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" Ns = lll.(Ns) qM2, _ = orthogonal_submodule(qM, domain(HM)) @@ -536,22 +624,91 @@ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classificat end end end - #@hassert :ZZLatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) return (length(results) > 0), results end @doc raw""" primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; - classification::Symbol = :strong, + classification::Symbol = :sublat, check::Bool = true) -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} -Given a lattice `L`, unique in its genus, and a `p`-primary lattice `M`, -return whether `M` embeds primitively in `L`. +Given an integer lattice `L`, which is unique in its genus, and a `p`-primary +lattice `M`, return whether `M` embeds primitively in `L`. The first input of the function is a boolean `T` stating whether or not `M` embeds primitively in `L`. The second output `V` consists on triples -`(L', M', N')` where `L'` is isometric to `L`, `M'` is a sublattice of +`(L', M', N')` where `L'` isometric to `L`, `M'` is a primtiive sublattice of +`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. + +If `T == false`, then `V` will always be the empty list. If `T == true`, then +the content of `V` actually depends on the value of the symbol `classification`. +There are 4 possibilities: + - `classification = :none`: `V` is the empty list; + - `classification = :first`: V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ + and $O(q)$ where `q` is the discriminant group of `L`; + - `classification = :emd`: `V` consists on representatives for all isomorphism + classes of primitive sublattices of `L` isometric to `M` up to the action of + $O(q)$ where `q` is the discriminant group of `L`. + +If `check` is set to true, the function determines whether `L` is in fact unique +in its genus. +""" +function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = false) + if check + @req length(genus_representatives(L)) == 1 "L must be unique in its genus" + end + return primitive_embeddings_of_primary_lattice(genus(L), M, classification = classification) +end + +@doc raw""" + primitive_embeddings_of_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; + classification::Symbol = :sublat) + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} + +Given a tuple `sign` of non-negative integers and a torsion quadratic module +`q` which define a genus symbol `G` for integer lattices, return whether the +$p$-primary lattice `M` embeds primitively in a lattice in `G`. + +The first input of the function is a boolean `T` stating whether or not `M` +embeds primitively in a lattice in `G`. The second output `V` consists on +triples `(L', M', N')` where `L'` is a lattice in `G`, `M'` is a sublattice of +`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. + +If `T == false`, then `V` will always be the empty list. If `T == true`, then +the content of `V` actually depends on the value of the symbol `classification`. +There are 4 possibilities: + - `classification = :none`: `V` is the empty list; + - `classification = :first`: V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in lattices in `G`, up to the actions of + $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; + - `classification = :emd`: `V` consists on representatives for all isomorphism + classes of primitive sublattices of lattices in `G` isometric to `M`, up to the + action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + +If the pair `(q, sign)` does not define a non-empty genus for integer lattices, +an error is thrown. +""" +function primitive_embeddings_of_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) + @req is_genus(q, sign) "Invariants define the empty genus" + G = genus(q, sign) + return primitive_embeddings_of_primary_lattice(G, M, classification = classification) +end + +@doc raw""" + primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; + classification::Symbol = :sublat) + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} + +Given a genus symbol `G` for integer lattices and a `p`-primary lattice `M`, +return whether `M` embeds primitively in a lattice in `G`. + +The first input of the function is a boolean `T` stating whether or not `M` +embeds primitively in a lattice in `G`. The second output `V` consists on +triples `(L', M', N')` where `L'` is a lattice in `G`, `M'` is a sublattice of `L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. If `T == false`, then `V` will always be the empty list. If `T == true`, then @@ -559,40 +716,42 @@ the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :strong`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in `L` up to the actions of $\bar{O}(M)$ - and $\bar{O}(L)$; - - `classification = :weak`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in `L` up to the action of $\bar{O}(L)$. + - `classification = :sublat`: `V` consists on representatives for all isomorphism + classes of primitive embeddings of `M` in lattices in `G`, up to the actions of + $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; + - `classification = :emd`: `V` consists on representatives for all isomorphism + classes of primitive sublattices in lattices in `G` isometric to `M`, up to the + action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. """ -function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :strong, check::Bool = false) - @req classification in [:none, :first, :weak, :strong] "Wrong symbol for classification" +function primitive_embeddings_of_primary_lattice(G::ZZgenus, M::ZZLat; classification::Symbol = :sublat) + @req classification in [:none, :first, :emb, :sublat] "Wrong symbol for classification" pL, _, nL = signature_tuple(L) pM, _, nM = signature_tuple(M) @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" - @req rank(M) < rank(L) "M must be of smaller rank than L" + + results = Tuple{ZZLat, ZZLat, ZZLat}[] + if rank(M) == rank(G) + !(M in G) && return results + push!(results, (M, M, orthogonal_submodule(M, M))) + end + + @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" bool, p = is_primary_with_prime(M) @req bool "M must be unimodular or primary" el = is_elementary(M, p) - if check - @req length(genus_representatives(L)) == 1 "L must be unique in its genus" - end - - results = Tuple{ZZLat, ZZLat, ZZLat}[] - qM = discriminant_group(M) GM, _ = image_in_Oq(M) - GL, _ = image_in_Oq(rescale(L, -1)) - qL = domain(GL) + qL = discriminant_group(rescale(G, -1)) + GL = orthogonal_group(qL) D, inj, proj = biproduct(qM, qL) qMinD, qLinD = inj if el - VL, VLinqL, _ = _get_V(id_hom(qL), minpoly(identity_matrix(QQ,1)), p) + VL, VLinqL, _ = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ,1)), p) else VL, VLinqL = primary_part(qL, p) end @@ -629,9 +788,9 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat classification == :none && return true, results - G = genus(disc, (pL-pM, nL-nM)) + G2 = genus(disc, (pL-pM, nL-nM)) @info "We can glue: $G" - Ns = representatives(G) + Ns = representatives(G2) @info "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) qM2, _ = orthogonal_submodule(qM, domain(HM)) @@ -646,8 +805,7 @@ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classificat GC.gc() end end - #@hassert :ZZLatWithIsom 1 all(triple -> genus(triple[1]) == genus(L), results) - return (length(results) >0), results + return (length(results) > 0), results end #################################################################################### @@ -683,9 +841,6 @@ integral lattices (with isometry) with `fA` and `fB` having relatively coprime irreducible minimal polynomials and imposing that `A` and `B` are orthogonal if `A`, `B` and `C` lie in the same ambient quadratic space. -Note that `Afa` and `Bfb` must be of pure type, i.e. the minimal polynomials -of the associated isometries must be irreducible (and relatively coprime). - See Algorithm 2 of [BH22]. """ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, @@ -700,8 +855,8 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, amb = ambient_space(lattice(A)) === ambient_space(lattice(B)) === ambient_space(lattice(C)) if check @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" - chiA = minpoly(A) - chiB = minpoly(parent(chiA), isometry(B)) + chiA = minimal_polynomial(A) + chiB = minimal_polynomial(parent(chiA), isometry(B)) @req gcd(chiA, chiB) == 1 "Minimal irreducible polynomials must be relatively coprime" @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple with respect to p" if amb @@ -768,8 +923,8 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # these are GA|GB-invariant, fA|fB-stable, and should contain the kernels of any glue map. # VA and VB are submodules of the p-elementary parts of qA and qB # respectively. - VA, VAinqA = _get_V(hom(fqA), minpoly(B), p) - VB, VBinqB = _get_V(hom(fqB), minpoly(A), p) + VA, VAinqA = _get_V(hom(fqA), minimal_polynomial(B), p) + VB, VBinqB = _get_V(hom(fqB), minimal_polynomial(A), p) # since the glue kernels must have order p^g, in this condition, we have nothing if min(order(VA), order(VB)) < p^g @@ -832,13 +987,13 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, actA = hom(stabA, 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])] - union!(kerA, OqAinOD(one(OqA))) + push!(kerA, OqAinOD(one(OqA))) fSA = OSA(restrict_automorphism(fqA, SAinqA)) actB = hom(stabB, 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])] - union!(kerB, OqBinOD(one(OqB))) + push!(kerB, OqBinOD(one(OqB))) fSB = OSB(restrict_automorphism(fqB, SBinqB)) # We want all isometries of SB which preserves p^l*q_B and such that they @@ -998,9 +1153,7 @@ function _compute_double_stabilizer(SBinqB, l, spec) OSBHB, _ = stabilizer(OSB, HBinSB) OHB, OSBHBtoOHB = restrict_automorphism_group(OSBHB, HBinSB) K, _ = kernel(OSBHBtoOHB) - if p != 2 - OHBrB, _ = stabilizer(OHB, gram_matrix_bilinear(rB), _on_modular_matrix) - elseif spec + if spec OHBrB, _ = stabilizer(OHB, gram_matrix_quadratic(rB), _on_modular_matrix_quad) else OHBrB, _ = stabilizer(OHB, gram_matrix_bilinear(rB), _on_modular_matrix) @@ -1055,14 +1208,14 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) HA, _ = sub(SA, rAtoSA.(gens(rA))) rBtoSB = hom(rB, SB, [SB(QQ(p^l)*(lift(b))) for b in gens(rB)]) HB, HBinSB = sub(SB, rBtoSB.(gens(rB))) - if p != 2 - phi_0 = _anti_isometry_bilinear(rA, rB) - elseif spec + 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 - phi_0 = _anti_isometry_bilinear(rA, rB) + 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, [phi(SA(lift(a))) for a in gens(HA)]) OSB = orthogonal_group(SB) @@ -1091,14 +1244,15 @@ end # Compute an anti-isometry between the two finite bilinear modules r1 and r2. function _anti_isometry_bilinear(r1, r2) @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) - @hassert :ZZLatWithIsom 1 gram_matrix_bilinear(r1N) == gram_matrix_bilinear(r2mN) + 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 T + return true, T end diff --git a/experimental/LatticesWithIsometry/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl similarity index 92% rename from experimental/LatticesWithIsometry/src/enumeration.jl rename to experimental/QuadFormAndIsom/src/enumeration.jl index c493cfd816be..4129e7bd8866 100644 --- a/experimental/LatticesWithIsometry/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -1,7 +1,7 @@ ################################################################################## # -# This is an import to Oscar of the methods written following the paper [BH22] on +# This is an import to Oscar of the methods written following the paper [BH23] on # "Finite subgroups of automorphisms of K3 surfaces". # ################################################################################## @@ -198,8 +198,16 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) if !represents(C, AperpB) return false end - - return true + + qA, qB, qC = discriminant_group.([A, B, C]) + spec = (p == 2) && (_is_free(qA, p, l+1)) && (_is_free(qB, p, l+1)) && (_is_even(qC, p, l)) + rA = Oscar._rho_functor(qA, p, l+1) + rB = Oscar._rho_functor(qB, p, l+1) + if spec + return is_anti_isometric_with_anti_isometry(rA, rB)[1] + else + return Oscar._anti_isometry_bilinear(rA, rB)[1] + end end function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZZLat, ZZLatWithIsom} @@ -579,7 +587,7 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) e = 0 end - phi = minpoly(Lf) + phi = minimal_polynomial(Lf) chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) @req Hecke.divides(chi, phi)[1] "Minimal polynomial is not of the correct form" @@ -650,7 +658,7 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) reps = ZZLatWithIsom[] - x = gen(parent(minpoly(Lf))) + 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])) A = splitting_of_pure_mixed_prime_power(A0, p) @@ -753,3 +761,64 @@ function _split_prime_power(N::ZZLatWithIsom, p::Hecke.IntegerUnion, vp::Hecke.I end return reps end + +############################################################################### +# +# Testing functions +# +############################################################################### + +function _get_isometry_prime_power!(D, L, p, j) + if !haskey(D, p) + Dp = splitting_of_prime_power(integer_lattice_with_isometry(L), p, 1) + D[p] = Dp + end + for i in 2:j + if !haskey(D, p^i) + Dp = D[p^(i-1)] + Dpi = ZZLatWithIsom[] + for N in Dp + Np = splitting_of_mixed_prime_power(N, p) + filter!(NN -> valuation(order_of_isometry(NN), p) == i, Np) + append!(Dpi, Np) + end + D[p^i] = Dpi + end + end + return nothing +end + +function _get_isometry_composite!(D, n) + p, q = sort(prime_divisors(n)) + i, j = valuation(n, p), valuation(n, q) + for k in 1:i + Dq = D[p^(k-1)*q^j] + Dn = ZZLatWithIsom[] + for N in Dq + Np = splitting_of_mixed_prime_power(N, p) + filter!(NN -> order_of_isometry(NN) == p^k*q^j, Np) + append!(Dn, Np) + end + D[p^k*q^j] = Dn + end + return nothing +end + +function _test_isometry_enumeration(L::ZZLat) + n = rank(L) + ord = filter(m -> euler_phi(m) <= n && length(prime_divisors(m)) <= 2, 2:2*n^2) + pds = union(reduce(vcat, prime_divisors.(ord))) + vals = [maximum([valuation(x, p) for x in ord]) for p in pds] + D = Dict{Int, Vector{ZZLatWithIsom}}() + D[1] = ZZLatWithIsom[integer_lattice_with_isometry(L)] + for i in 1:length(vals) + p = pds[i] + j = vals[i] + _get_isometry_prime_power!(D, L, p, j) + end + for n in ord + is_prime_power_with_data(n)[1] && continue + _get_isometry_composite!(D, n) + end + return D +end diff --git a/experimental/LatticesWithIsometry/src/exports.jl b/experimental/QuadFormAndIsom/src/exports.jl similarity index 100% rename from experimental/LatticesWithIsometry/src/exports.jl rename to experimental/QuadFormAndIsom/src/exports.jl diff --git a/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl similarity index 99% rename from experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl rename to experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index 841063acee61..0ac37189f2eb 100644 --- a/experimental/LatticesWithIsometry/src/hermitian_miranda_morrison.jl +++ b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl @@ -1,7 +1,6 @@ - ############################################################################### # -# Computations of the finite quotients E_0/E^i: subsection 6.8. of BH23 +# Computations of the finite quotients E_0/E^i: subsection 6.8. of [BH23] # ============================================================================ # # The 5 following functions are an import of identical functions written by @@ -267,7 +266,7 @@ end # We collect all the prime ideals p for which the local quotient # (D^{-1}L^#/L)_p is not unimodular. # -# According to BH23, the quotient D^{-1}L^#/L is unimodular at p +# According to [BH23], the quotient D^{-1}L^#/L is unimodular at p # if and only if # - either L is unimodular at p, and D and p are coprime # - or D^{-1}L^# is P^a-modular where P is largest prime ideal diff --git a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl similarity index 88% rename from experimental/LatticesWithIsometry/src/lattices_with_isometry.jl rename to experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index d564c254b354..9cbfa37b81d7 100644 --- a/experimental/LatticesWithIsometry/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -58,30 +58,34 @@ order_of_isometry(Lf::ZZLatWithIsom) = Lf.n Given a lattice with isometry $(L, f)$, return the rank of the underlying lattice `L`. + +See [`rank(::ZZLat)`](@ref). """ rank(Lf::ZZLatWithIsom) = rank(lattice(Lf))::Integer @doc raw""" - charpoly(Lf::ZZLatWithIsom) -> QQPolyRingElem + characteristic_polynomial(Lf::ZZLatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the characteristic polynomial of the underlying isometry `f`. """ -charpoly(Lf::ZZLatWithIsom) = charpoly(isometry(Lf))::QQPolyRingElem +characteristic_polynomial(Lf::ZZLatWithIsom) = characteristic_polynomial(isometry(Lf))::QQPolyRingElem @doc raw""" - minpoly(Lf::ZZLatWithIsom) -> QQPolyRingElem + minimal_polynomial(Lf::ZZLatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the minimal polynomial of the underlying isometry `f`. """ -minpoly(Lf::ZZLatWithIsom) = minpoly(isometry(Lf))::QQPolyRingElem +minimal_polynomial(Lf::ZZLatWithIsom) = minimal_polynomial(isometry(Lf))::QQPolyRingElem @doc raw""" genus(Lf::ZZLatWithIsom) -> ZZGenus Given a lattice with isometry $(L, f)$, return the genus of the underlying -lattice `L` (see [`genus(::ZZLat)`](@ref)). +lattice `L`. + +See [`genus(::ZZLat)`](@ref). """ genus(Lf::ZZLatWithIsom) = genus(lattice(Lf))::ZZGenus @@ -89,16 +93,19 @@ genus(Lf::ZZLatWithIsom) = genus(lattice(Lf))::ZZGenus basis_matrix(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the basis matrix of the underlying -lattice `L` (see [`basis_matrix(::ZZLat)`](@ref)). +lattice `L`. + +See [`basis_matrix(::ZZLat)`](@ref). """ basis_matrix(Lf::ZZLatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix @doc raw""" gram_matrix(Lf::ZZLatWithIsom) -> QQMatrix -Given a lattice with isometry $(L, f)$ with basis matric `B` (see [`basis_matrix(Lf::ZZLatWithIsom)`](@ref)) -inside the space $(V, \Phi)$ (see [`ambient_space(Lf::ZZLatWithIsom)`](@ref)), return the gram matrix -of lattice `L` associted to `B` with respect to $\Phi$. +Given a lattice with isometry $(L, f)$, return the gram matrix of the underlying +lattice `L` with respect to its basis matrix. + +See [`gram_matrix(::ZZLat)`](@ref). """ gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix @@ -108,6 +115,8 @@ gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix Given a lattice with isometry $(L, f)$, return the rational span $L \otimes \mathbb{Q}$ of the underlying lattice `L` together with the underlying isometry of `L`. + +See [`rational_span(::ZZLat)`](@ref). """ rational_span(Lf::ZZLatWithIsom) = quadratic_space_with_isometry(rational_span(lattice(Lf)), isometry(Lf))::QuadSpaceWithIsom @@ -115,7 +124,9 @@ rational_span(Lf::ZZLatWithIsom) = quadratic_space_with_isometry(rational_span(l det(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the determinant of the -underlying lattice `L` (see [`det(::ZZLat)`](@ref)). +underlying lattice `L`. + +See [`det(::ZZLat)`](@ref). """ det(Lf::ZZLatWithIsom) = det(lattice(Lf))::QQFieldElem @@ -123,7 +134,9 @@ det(Lf::ZZLatWithIsom) = det(lattice(Lf))::QQFieldElem scale(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the scale of the underlying -lattice `L` (see [`scale(::ZZLat)`](@ref)). +lattice `L`. + +See [`scale(::ZZLat)`](@ref). """ scale(Lf::ZZLatWithIsom) = scale(lattice(Lf))::QQFieldElem @@ -131,7 +144,9 @@ scale(Lf::ZZLatWithIsom) = scale(lattice(Lf))::QQFieldElem norm(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the norm of the underlying -lattice `L` (see [`norm(::ZZLat)`](@ref)). +lattice `L`. + +See [`norm(::ZZLat)`](@ref). """ norm(Lf::ZZLatWithIsom) = norm(lattice(Lf))::QQFieldElem @@ -139,7 +154,9 @@ norm(Lf::ZZLatWithIsom) = norm(lattice(Lf))::QQFieldElem is_positive_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is positive definite (see [`is_positive_definite(::ZZLat)`](@ref)). +lattice `L` is positive definite. + +See [`is_positive_definite(::ZZLat)`](@ref). """ is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf))::Bool @@ -147,7 +164,9 @@ is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf))::Boo is_negative_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is negative definite (see [`is_positive_definite(::ZZLat)`](@ref)). +lattice `L` is negative definite. + +See [`is_positive_definite(::ZZLat)`](@ref). """ is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf))::Bool @@ -155,7 +174,9 @@ is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf))::Boo is_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is definite (see [`is_definite(::ZZLat)`](@ref)). +lattice `L` is definite. + +See [`is_definite(::ZZLat)`](@ref). """ is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf))::Bool @@ -163,7 +184,9 @@ is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf))::Bool minimum(Lf::ZZLatWithIsom) -> QQFieldElem Given a positive definite lattice with isometry $(L, f)$, return the minimum -of the underlying lattice `L` (see [`minimum(::ZZLat)`](@ref)). +of the underlying lattice `L`. + +See [`minimum(::ZZLat)`](@ref). """ function minimum(Lf::ZZLatWithIsom) @req is_positive_definite(Lf) "Underlying lattice must be positive definite" @@ -174,15 +197,19 @@ end is_integral(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice -is integral, i.e. whether its scale is an integer (see [`scale(::ZZLatWithIsom)`](@ref)). +`L` is integral. + +See [`is_integral(::ZZLat)`](@ref). """ is_integral(Lf::ZZLatWithIsom) = is_integral(lattice(Lf))::Bool @doc raw""" degree(Lf::ZZLatWithIsom) -> Int -Given a lattice with isometry $(L, f)$ inside the quadratic space $(V, \Phi)$, -return the dimension of `V` as a $\mathbb Q$ vector space. +Given a lattice with isometry $(L, f)$, return the degree of the underlying +lattice `L`. + +See [`degree(::ZZLat)`](@ref). """ degree(Lf::ZZLatWithIsom) = degree(lattice(Lf))::Int @@ -190,9 +217,9 @@ degree(Lf::ZZLatWithIsom) = degree(lattice(Lf))::Int is_even(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice -`L` is even, i.e. whether its norm is an even integer ([`norn(::ZZLatWithIsom)`](@ref)). +`L` is even. -Note that to be even, `L` must be integral (see [`is_integral(::ZZLat)`](@ref)). +See [`is_even(::ZZLat)`](@ref). """ is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf))::Bool @@ -200,7 +227,9 @@ is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf))::Bool discriminant(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the discriminant of the underlying -lattice `L` (see [`discriminant(::ZZLat)`](@ref)). +lattice `L`. + +See [`discriminant(::ZZLat)`](@ref). """ discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf))::QQFieldElem @@ -208,7 +237,9 @@ discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf))::QQFieldElem signature_tuple(Lf::ZZLatWithIsom) -> Tuple{Int, Int, Int} Given a lattice with isometry $(L, f)$, return the signature tuple of the -underlying lattice `L` (see [`signature_tuple(::ZZLat)`](@ref)). +underlying lattice `L`. + +See [`signature_tuple(::ZZLat)`](@ref). """ signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} @@ -338,8 +369,10 @@ end @doc raw""" rescale(Lf::ZZLatWithIsom, a::RationalUnion) -> ZZLatWithIsom -Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lattice -with isometry $(L(a), f)$ (see [`rescale(::ZZLat, ::RationalUnion)`](@ref)). +Given a lattice with isometry $(L, f)$ and a rational number `a`, return the +lattice with isometry $(L(a), f)$. + +See [`rescale(::ZZLat, ::RationalUnion)`](@ref). """ function rescale(Lf::ZZLatWithIsom, a::Hecke.RationalUnion) return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf), check=false) @@ -348,10 +381,12 @@ end @doc raw""" dual(Lf::ZZLatWithIsom) -> ZZLatWithIsom -Given a lattice with isometry $(L, f)$ inside the space $(V, \Phi)$, such that `f` is -induced by an isometry `g` of $(V, \Phi)$, return the lattice with isometry $(L^{\vee}, h)$ -where $L^{\vee}$ is the dual of `L` in $(V, \Phi)$ (see [`dual(::ZZLat)`](@ref)) and `h` is -induced by `g`. +Given a lattice with isometry $(L, f)$ inside the space $(V, \Phi)$, such that +`f` is induced by an isometry `g` of $(V, \Phi)$, return the lattice with +isometry $(L^{\vee}, h)$ where $L^{\vee}$ is the dual of `L` in $(V, \Phi)$ +and `h` is induced by `g`. + +See [`dual(::ZZLat)`](@ref). """ function dual(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" @@ -361,12 +396,14 @@ end @doc raw""" lll(Lf::ZZLatWithIsom) -> ZZLatWithIsom -Given a lattice with isometry $(L, f)$, return the same lattice with isometry with a different -basis matrix for `L` (see [`basis_matrix(::ZZLat)`](@ref)) obtained by performing an LLL-reduction -on the associated gram matrix of `L` (see [`gram_matrix(::ZZLat)`](@ref)). +Given a lattice with isometry $(L, f)$, return the same lattice with isometry +with a different basis matrix for `L` obtained by performing an LLL-reduction +on the associated gram matrix of `L`. Note that matrix representing the action of `f` on `L` changes but the global action on the ambient space of `L` stays the same. + +See [`lll(::ZZLat)`](@ref). """ function lll(Lf::ZZLatWithIsom; same_ambient::Bool = true) L2 = lll(lattice(Lf), same_ambient = same_ambient) @@ -378,8 +415,10 @@ function lll(Lf::ZZLatWithIsom; same_ambient::Bool = true) end @doc raw""" - direct_sum(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} - direct_sum(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} + direct_sum(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, + Vector{AbstractSpaceMor} + direct_sum(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, + Vector{AbstractSpaceMor} Given a collection of lattices with isometries $(L_1, f_1) \ldots, (L_n, f_n)$, return the lattice with isometry $(L, f)$ together with the injections $L_i \to L$, @@ -402,8 +441,10 @@ end direct_sum(x::Vararg{ZZLatWithIsom}) = direct_sum(collect(x)) @doc raw""" - direct_product(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} - direct_product(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor} + direct_product(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, + Vector{AbstractSpaceMor} + direct_product(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, + Vector{AbstractSpaceMor} Given a collection of lattices with isometries $(L_1, f_1), \ldots, (L_n, f_n)$, return the lattice with isometry $(L, f)$ together with the projections $L \to L_i$, @@ -426,8 +467,12 @@ end direct_product(x::Vararg{ZZLatWithIsom}) = direct_product(collect(x)) @doc raw""" - biproduct(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} - biproduct(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor} + biproduct(x::Vector{ZZLatWithIsom}) -> ZZLatWithIsom, + Vector{AbstractSpaceMor}, + Vector{AbstractSpaceMor} + biproduct(x::Vararg{ZZLatWithIsom}) -> ZZLatWithIsom, + Vector{AbstractSpaceMor}, + Vector{AbstractSpaceMor} Given a collection of lattices with isometries $(L_1, f_1), \ldots, (L_n, f_n)$, return the lattice with isometry $(L, f)$ together with the injections @@ -476,30 +521,27 @@ end is_of_hermitian_type(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the minimal polynomial of -the underlying isometry `f` is (irreducible) cyclotomic. +the underlying isometry `f` is irreducible. -Note that if $(L, f)$ is of hermitian type with `f` of order `n`, then `L` can -be seen as a hermitian lattice over the order $\mathbb{Z}[\zeta_n]$ where -$\zeta_n$ is a primitive $n$-th root of unity. +Note that if $(L, f)$ is of hermitian type with `f` of minimal polynomial $\chi$, +then `L` can be seen as a hermitian lattice over the order $\mathbb{Z}[\chi]$. """ function is_of_hermitian_type(Lf::ZZLatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" - n = order_of_isometry(Lf) - if n == -1 || !is_finite(n) - return false - end - return is_cyclotomic_polynomial(minpoly(isometry(Lf))) + return is_irreducible(minimal_polynomial(Lf)) end @doc raw""" hermitian_structure(Lf::ZZLatWithIsom) -> HermLat Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the -underlying isometry `f` is cyclotomic, return the hermitian structure of the -underlying lattice `L` over the $n$th cyclotomic field, where $n$ is the -order of `f`. +underlying isometry `f` is irreducible, return the hermitian structure of the +underlying lattice `L` over the equation order of the minimal polynomial of +`f`. -If it exists, the hermitian structure is stored. +If it exists, the hermitian structure is stored. For now, we only cover the case +where the equation order is maximal (which is always the case when the order is +finite, for instance, since the minimal polynomial is cyclotomic). """ @attr HermLat function hermitian_structure(Lf::ZZLatWithIsom) @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" @@ -523,6 +565,8 @@ end Given an integral lattice with isometry $(L, f)$, return the discriminant group `q` of the underlying lattice `L` as well as this image of the underlying isometry `f` inside $O(q)$. + +See [`discriminant_group(::ZZLat)`](@ref). """ function discriminant_group(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" @@ -540,6 +584,8 @@ Given an integral lattice with isometry $(L, f)$, return the image $G_L$ in $O(q_L, \bar{f})$ of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ denotes the discriminant group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. + +See [`image_in_Oq(::ZZLat)`](@ref). """ @attr AutomorphismGroup{TorQuadModule} function image_centralizer_in_Oq(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" @@ -608,7 +654,7 @@ the $i$-th signature of $(L, f)$ is given by the signatures of the real quadrati form $\Ker(f + f^{-1} - z^i - z^{-i})$. """ function signatures(Lf::ZZLatWithIsom) - @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" + @req is_cyclotomic_polynomial(minimal_polynomial(Lf)) "Lf must be of finite hermitian type" L = lattice(Lf) f = isometry(Lf) n = order_of_isometry(Lf) @@ -657,8 +703,8 @@ function kernel_lattice(Lf::ZZLatWithIsom, p::QQPolyRingElem) return lattice(ambient_space(Lf), K*basis_matrix(L)) #f2 = solve_left(change_base_ring(QQ, K), K*f) #@hassert :ZZLatWithIsom 1 f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) - #chi = parent(p)(collect(coefficients(minpoly(f2)))) - #chif = parent(p)(collect(coefficients(minpoly(Lf)))) + #chi = parent(p)(collect(coefficients(minimal_polynomial(f2)))) + #chif = parent(p)(collect(coefficients(minimal_polynomial(Lf)))) #_chi = gcd(p, chif) #@hassert :ZZLatWithIsom 1 (rank(L2) == 0) || (chi == _chi) #return integer_lattice_with_isometry(L2, f2, ambient_representation = false) @@ -712,7 +758,7 @@ The coinvariant lattice $L_f$ of $(L, f)$ is the orthogonal complement in `L` of the invariant lattice $L_f$. """ function coinvariant_lattice(Lf::ZZLatWithIsom) - chi = minpoly(Lf) + chi = minimal_polynomial(Lf) if chi(1) == 0 R = parent(chi) x = gen(R) diff --git a/experimental/LatticesWithIsometry/src/printings.jl b/experimental/QuadFormAndIsom/src/printings.jl similarity index 80% rename from experimental/LatticesWithIsometry/src/printings.jl rename to experimental/QuadFormAndIsom/src/printings.jl index fb9a400a9477..fb22a83b2e26 100644 --- a/experimental/LatticesWithIsometry/src/printings.jl +++ b/experimental/QuadFormAndIsom/src/printings.jl @@ -1,3 +1,9 @@ +############################################################################### +# +# Lattices with isometry +# +############################################################################### + function Base.show(io::IO, ::MIME"text/plain", Lf::ZZLatWithIsom) io = AbstractAlgebra.pretty(io) println(io, lattice(Lf)) @@ -26,6 +32,12 @@ function Base.show(io::IO, Lf::ZZLatWithIsom) end end +############################################################################### +# +# Quadratic space with isometry +# +############################################################################### + function Base.show(io::IO, ::MIME"text/plain", Vf::QuadSpaceWithIsom) io = AbstractAlgebra.pretty(io) println(io, space(Vf)) diff --git a/experimental/LatticesWithIsometry/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl similarity index 87% rename from experimental/LatticesWithIsometry/src/spaces_with_isometry.jl rename to experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 819875d8f402..cac3ea70a1ac 100644 --- a/experimental/LatticesWithIsometry/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -39,6 +39,8 @@ order_of_isometry(Vf::QuadSpaceWithIsom) = Vf.n Given a quadratic space with isometry $(V, f)$, return the rank of the underlying space `V`. + +See ['rank(::QuadSpace)'](@ref). """ rank(Vf::QuadSpaceWithIsom) = rank(space(Vf))::Integer @@ -46,31 +48,35 @@ rank(Vf::QuadSpaceWithIsom) = rank(space(Vf))::Integer dim(Vf::QuadSpaceWithIsom) -> Integer Given a quadratic space with isometry $(V, f)$, return the dimension of the -underlying space of `V` +underlying space of `V`. + +See [`dim(::QuadSpace)`](@ref). """ dim(Vf::QuadSpaceWithIsom) = dim(space(Vf))::Integer @doc raw""" - charpoly(Vf::QuadSpaceWithIsom) -> QQPolyRingElem + characteristic_polynomial(Vf::QuadSpaceWithIsom) -> QQPolyRingElem Given a quadratic space with isometry $(V, f)$, return the characteristic -polynomial of the underlying isometry `f` +polynomial of the underlying isometry `f`. """ -charpoly(Vf::QuadSpaceWithIsom) = charpoly(isometry(Vf))::QQPolyRingElem +characteristic_polynomial(Vf::QuadSpaceWithIsom) = characteristic_polynomial(isometry(Vf))::QQPolyRingElem @doc raw""" - minpoly(Vf::QuadSpaceWithIsom) -> QQPolyRingElem + minimal_polynomial(Vf::QuadSpaceWithIsom) -> QQPolyRingElem Given a quadratic space with isometry $(V, f)$, return the minimal polynomial of the underlying isometry `f`. """ -minpoly(Vf) = minpoly(isometry(Vf))::QQPolyRingElem +minimal_polynomial(Vf) = minimal_polynomial(isometry(Vf))::QQPolyRingElem @doc raw""" gram_matrix(Vf::QuadSpaceWithIsom) -> QQMatrix Given a quadratic space with isometry $(V, f)$, return the Gram matrix of the underlying space `V` with respect to its standard basis. + +See [`gram_matrix(::QuadSpace)`](@ref). """ gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf))::QQMatrix @@ -79,6 +85,8 @@ gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf))::QQMatrix Given a quadratic space with isometry $(V, f)$, return the determinant of the underlying space `V`. + +See [`det(::QuadSpace)`](@ref). """ det(Vf::QuadSpaceWithIsom) = det(space(Vf))::QQFieldElem @@ -87,6 +95,8 @@ det(Vf::QuadSpaceWithIsom) = det(space(Vf))::QQFieldElem Given a quadratic space with isometry $(V, f)$, return the discriminant of the underlying space `V`. + +See [`discriminant(::QuadSpace)`](@ref). """ discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf))::QQFieldElem @@ -95,6 +105,8 @@ discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf))::QQFieldElem Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is positive definite. + +See [`is_positive_definite(::QuadSpace)`](@ref). """ is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf))::Bool @@ -103,6 +115,8 @@ is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf))::B Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is negative definite. + +See [`is_negative_definite(::QuadSpace)`](@ref). """ is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf))::Bool @@ -111,6 +125,8 @@ is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf))::B Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is definite. + +See [`is_definite(::QuadSpace)`](@ref). """ is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf))::Bool @@ -118,9 +134,9 @@ is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf))::Bool diagonal(Vf::QuadSpaceWithIsom) -> Vector{QQFieldElem} Given a quadratic space with isometry $(V, f)$, return the diagonal of the -underlying space `V`, that is a list of rational numbers $a_1, \ldots a_n$ -such that `V` is isometric to the space whose Gram matrix is diagonal with -entries $a_1,\ldots, a_n$. +underlying space `V`. + +See [`diagonal(::QuadSpace)`](@ref). """ diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf))::Vector{QQFieldElem} @@ -129,6 +145,8 @@ diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf))::Vector{QQFieldElem} Given a quadratic space with isometry $(V, f)$, return the signature tuple of the underlying space `V`. + +See [`signature_tuple(::QuadSpace)`](@ref). """ signature_tuple(Vf::QuadSpaceWithIsom) = signature_tuple(space(Vf))::Tuple{Int, Int, Int} @@ -186,6 +204,8 @@ end Given a quadratic space with isometry $(V, f)$, return the pair $(V^a, f$) where $V^a$ is the same space as `V` with the associated quadratic form rescaled by `a`. + +See [`rescale(::QuadSpace, ::Hecke.RationalUnion)`](@ref). """ function rescale(Vf::QuadSpaceWithIsom, a::Hecke.RationalUnion) return quadratic_space_with_isometry(rescale(space(Vf), a), isometry(Vf), check = false) @@ -207,7 +227,6 @@ one should call `direct_product(x)`. If one wants to obtain $(V, f)$ as a biproduct with the injections $V_i \to V$ and the projections $V \to V_i$, one should call `biproduct(x)`. """ - function direct_sum(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj = direct_sum(space.(x)) f = block_diagonal_matrix(isometry.(x)) @@ -232,7 +251,6 @@ one should call `direct_sum(x)`. If one wants to obtain $(V, f)$ as a biproduct with the injections $V_i \to V$ and the projections $V \to V_i$, one should call `biproduct(x)`. """ - function direct_product(x::Vector{T}) where T <: QuadSpaceWithIsom V, proj = direct_product(space.(x)) f = block_diagonal_matrix(isometry.(x)) @@ -258,7 +276,6 @@ one should call `direct_sum(x)`. If one wants to obtain $(V, f)$ as a direct product with the projections $V \to V_i$, one should call `direct_product(x)`. """ - function biproduct(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj, proj = biproduct(space.(x)) f = block_diagonal_matrix(isometry.(x)) @@ -283,3 +300,18 @@ function Base.hash(V::QuadSpaceWithIsom, u::UInt) return Base.hash(isometry(V), u) end +############################################################################### +# +# Useful +# +############################################################################### + +function to_oscar(Vf::QuadSpaceWithIsom) + V = space(Vf) + f = isometry(Vf) + println(stdout, "G = matrix(QQ, $(dim(V)), $(dim(V)), ", gram_matrix(V), ");") + println(stdout, "V = quadratic_space(QQ, G);") + println(stdout, "f = matrix(QQ, $(dim(V)), $(dim(V)), ", f, ");") + println(stdout, "Vf = quadratic_space_with_isometry(V, f);") +end + diff --git a/experimental/QuadFormAndIsom/src/types.jl b/experimental/QuadFormAndIsom/src/types.jl new file mode 100644 index 000000000000..c07b6b4a2c41 --- /dev/null +++ b/experimental/QuadFormAndIsom/src/types.jl @@ -0,0 +1,208 @@ +@doc raw""" + QuadSpaceWithIsom + +A container type for pairs `(V, f)` consisting on an rational quadratic space +`V` of type `QuadSpace` and an isometry `f` given as a `QQMatrix` representing +the action on the standard basis of `V`. + +We store the order of `f` too, which can finite or of infinite order. + +To construct an object of type `QuadSpaceWithIsom`, see the set of functions +called [`quadratic_space_with_isometry`](@ref) + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 4); + +julia> quadratic_space_with_isometry(V, neg=true) +Quadratic space of dimension 4 + with isometry of finite order 2 + given by + [-1 0 0 0] + [ 0 -1 0 0] + [ 0 0 -1 0] + [ 0 0 0 -1] + +julia> L = root_lattice(:E, 6); + +julia> V = ambient_space(L); + +julia> f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; + -1 -2 -2 -2 -1 -1; + 0 1 0 0 0 0; + 1 0 0 0 0 0; + -1 -1 -1 0 0 -1; + 0 0 1 1 0 1]); + +julia> Vf = quadratic_space_with_isometry(V, f) +Quadratic space of dimension 6 + with isometry of finite order 8 + given by + [ 1 2 3 2 1 1] + [-1 -2 -2 -2 -1 -1] + [ 0 1 0 0 0 0] + [ 1 0 0 0 0 0] + [-1 -1 -1 0 0 -1] + [ 0 0 1 1 0 1] +``` +""" +@attributes mutable struct QuadSpaceWithIsom + V::Hecke.QuadSpace + f::QQMatrix + n::IntExt + + function QuadSpaceWithIsom(V::Hecke.QuadSpace, f::QQMatrix, n::IntExt) + z = new() + z.V = V + z.f = f + z.n = n + return z + end +end + +@doc raw""" + ZZLatWithIsom + +A container type for pairs `(L, f)` consisting on an integer lattice `L` of +type `ZZLat` and an isometry `f` given as a `QQMatrix` representing the action +on a given basis of `L`. + +We store the ambient space `V` of `L` together with an isometry `f_ambient` +inducing `f` on `L` seen as a pair $(V, f_ambient)$ of type `QuadSpaceWithIsom`. +We moreover store the order `n` of `f`, which can be finite or infinite. + +To construct an object of type `ZZLatWithIsom`, see the following examples: + +# Examples + +One first way to construct such object, is by entering directly the lattice with +an isometry. The isometry can be a honnest isometry of the lattice, or it can +be an isometry of the ambient space preserving the lattice. Depending on this +choice, one should enter the appropriate boolean value `ambient_representation`. +This direct construction is done through the constructors +[`integer_lattice_with_isometry`](@ref). + +```jldoctest +julia> L = root_lattice(:E, 6); + +julia> f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; + -1 -2 -2 -2 -1 -1; + 0 1 0 0 0 0; + 1 0 0 0 0 0; + -1 -1 -1 0 0 -1; + 0 0 1 1 0 1]); + +julia> Lf = integer_lattice_with_isometry(L, f, ambient_representation = false) +Integer lattice of rank 6 and degree 6 + with isometry of finite order 8 + given by + [ 1 2 3 2 1 1] + [-1 -2 -2 -2 -1 -1] + [ 0 1 0 0 0 0] + [ 1 0 0 0 0 0] + [-1 -1 -1 0 0 -1] + [ 0 0 1 1 0 1] + +julia> B = matrix(QQ,1,6, [1 2 3 1 -1 3]); + +julia> I = lattice_in_same_ambient_space(L, B); # This is the invariant sublattice L^f + +julia> If = integer_lattice_with_isometry(I, ambient_isometry(Lf)) +Integer lattice of rank 1 and degree 6 + with isometry of finite order 1 + given by + [1] + +julia> integer_lattice_with_isometry(I, neg=true) +Integer lattice of rank 1 and degree 6 + with isometry of finite order 2 + given by + [-1] +``` + +Another way to construct such objects is to see them as sub-objects of their +ambient space, of type `QuadSpaceWithIsom`. Through the constructors `lattice` +and `lattice_in_same_ambient_space`, one can then construct lattices with +isometry for free, in a given space, as long as the module they define is +preserved by the fixed isometry of the ambient space. + +# Examples +```jldoctest +julia> G = matrix(QQ, 6, 6 , [ 3 1 -1 1 0 0; + 1 3 1 1 1 1; + -1 1 3 0 0 1; + 1 1 0 4 2 2; + 0 1 0 2 4 2; + 0 1 1 2 2 4]); + +julia> V = quadratic_space(QQ, G); + +julia> f = matrix(QQ, 6, 6, [ 1 0 0 0 0 0 + 0 0 -1 0 0 0 + -1 1 -1 0 0 0 + 0 0 0 1 0 -1 + 0 0 0 0 0 -1 + 0 0 0 0 1 -1]); + +julia> Vf = quadratic_space_with_isometry(V, f); + +julia> Lf = lattice(Vf) +Integer lattice of rank 6 and degree 6 + with isometry of finite order 3 + given by + [ 1 0 0 0 0 0] + [ 0 0 -1 0 0 0] + [-1 1 -1 0 0 0] + [ 0 0 0 1 0 -1] + [ 0 0 0 0 0 -1] + [ 0 0 0 0 1 -1] + +julia> B = matrix(QQ, 4, 6, [1 0 3 0 0 0; + 0 1 1 0 0 0; + 0 0 0 0 1 0; + 0 0 0 0 0 1]); + +julia> Cf = lattice(V, B) # coinvariant sublattice L_f +Integer lattice of rank 4 and degree 6 + with isometry of finite order 3 + given by + [-2 3 0 0] + [-1 1 0 0] + [ 0 0 0 -1] + [ 0 0 1 -1] + +julia> Cf2 = lattice_in_same_ambient_space(Lf, B) +Integer lattice of rank 4 and degree 6 + with isometry of finite order 3 + given by + [-2 3 0 0] + [-1 1 0 0] + [ 0 0 0 -1] + [ 0 0 1 -1] + +julia> Cf == Cf2 +true +``` + +The last equality of the last example shows why we care about "ambient context": +the two pairs of lattice with isometry `Cf` and `Cf2` are basically the same +mathematical objects. Indeed, they lie in the same space, defines the same module +and their respective isometries are induced by the same isometry of the ambient +space. As for regular `ZZLat`, as soon as the lattices are in the same ambient +space, we can compare them as $\mathbb Z$-modules, endowed with an isometry. +""" +@attributes mutable struct ZZLatWithIsom + Vf::QuadSpaceWithIsom + Lb::ZZLat + f::QQMatrix + n::IntExt + + function ZZLatWithIsom(Vf::QuadSpaceWithIsom, Lb::ZZLat, f::QQMatrix, n::IntExt) + z = new() + z.Lb = Lb + z.f = f + z.Vf = Vf + z.n = n + return z + end +end diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl new file mode 100644 index 000000000000..fdd38eae20ec --- /dev/null +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -0,0 +1,168 @@ +using test +using Oscar + +@testset "Spaces with isometry" begin + D5 = root_lattice(:D, 5) + V = ambient_space(D5) + Vf = @inferred quadratic_space_with_isometry(V, neg=true) + @test is_one(-isometry(Vf)) + @test order_of_isometry(Vf) == 2 + @test space(Vf) === V + + for func in [rank, dim. gram_matrix, det, discriminant, is_positive_definite, + is_negative_definite, is_definite, diagonal, signature_tuple] + k = @inferred func(Vf) + @test k == func(V) + end + + @test evaluate(minimal_polynomial(Vf), -1) == 0 + @test evaluate(characteristic_polynomial(Vf), 0) == 1 + + G = matrix(FlintQQ, 6, 6 ,[3, 1, -1, 1, 0, 0, 1, 3, 1, 1, 1, 1, -1, 1, 3, 0, 0, 1, 1, 1, 0, 4, 2, 2, 0, 1, 0, 2, 4, 2, 0, 1, 1, 2, 2, 4]) + V = quadratic_space(QQ, G) + f = matrix(QQ, 6, 6, [1 0 0 0 0 0; 0 0 -1 0 0 0; -1 1 -1 0 0 0; 0 0 0 1 0 -1; 0 0 0 0 0 -1; 0 0 0 0 1 -1]) + Vf = @inferred quadratic_space_with_isometry(V, f) + @test order_of_isometry(Vf) == 3 + @test order_of_isometry(rescale(Vf, -3)) == 3 + L = lattice(rescale(Vf, -2)) + @test is_even(L) + @test is_negative_definite(L) == is_positive_definite(Vf) + + @test rank(biproduct(Vf, Vf)[1]) == 12 + @test order_of_isometry(direct_sum(Vf, Vf, Vf)[1]) == 3 + @test det(direct_product(Vf, Vf)[1]) == det(Vf)^2 + + @test Vf != quadratic_space_with_isometry(V, neg=true) + @test length(unique([Vf, quadratic_space_with_isometry(V, isometry(Vf))])) == 1 +end + +@testset "Lattices with isometry" begin + A4 = root_lattice(:A, 4) + agg = = automorphism_group_generators(A4, ambient_representation = false) + agg_ambient = automorphism_group_generators(A4, ambient_representation = true) + f = rand(agg) + g_ambient = rand(agg_ambient) + + L = @inferred integer_lattice_with_isometry(A4) + @test ambient_space(L) isa QuadSpaceWithIsom + @test isone(isometry(L)) + @test isone(ambient_isometry(L)) + @test isone(order_of_isometry(L)) + @test order(image_centralizer_in_Oq(L)) == 2 + + for func in [rank, genus, ambient_space, basis_matrix, is_positive_definite, + gram_matrix, det, scale, norm, is_integral, is_negative_definite, + degree, is_even, discriminant, signature_tuple, is_definite] + k = @inferred func(L) + @test k == func(A4) + end + + LfQ = @inferred rational_span(L) + @test LfQ isa QuadSpaceWithIsom + @test evaluate(minimal_polynomial(L), 1) == 0 + @test evaluate(characteristic_polynomial(L), 0) == 1 + + @test minimum(rescale(L, -1)) == 2 + @test !is_positive_definite(L) + @test is_definite(L) + + nf = multiplicative_order(f) + @test_throws ArgumentError integer_lattice_with_isometry(A4, zero_matrix(QQ, 0, 0)) + + L2 = @inferred integer_lattice_with_isometry(A4, f, ambient_representation = false) + @test order_of_isometry(L2) == nf + L2v = @inferred dual(L2) + @test order_of_isometry(L2v) == nf + @test ambient_isometry(L2v) == ambient_isometry(L2) + + L3 = @inferred integer_lattice_with_isometry(A4, g_ambient, ambient_representation = true) + @test order_of_isometry(L2) == multiplicative_order(g_ambient) + + L4 = @inferred rescale(L3, QQ(1//4)) + @test !is_integral(L4) + @test order_of_isometry(L4) == order_of_isometry(L3) + @test_throws ArgumentError dual(L4) + @test ambient_isometry(lll(L4)) == ambient_isometry(L4) + + @test order_of_isometry(biproduct(L2, L3)[1]) == lcm(order_of_isometry(L2), order_of_isometry(L3)) + @test rank(direct_sum(L2, L3)[1]) == rank(L2) + rank(L3) + @test genus(direct_product(L2, L3)[1]) == genus(L2) + genus(L3) + + L5 = @inferred lattice(ambient_space(L2)) + @test (L2 == L5) == (is_one(f)) + + B = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 1 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 0 0 1 0; 0 0 0 0 0 0 0 1]); + G = matrix(QQ, 8, 8, [-4 2 0 0 0 0 0 0; 2 -4 2 0 0 0 0 0; 0 2 -4 2 0 0 0 2; 0 0 2 -4 2 0 0 0; 0 0 0 2 -4 2 0 0; 0 0 0 0 2 -4 2 0; 0 0 0 0 0 2 -4 0; 0 0 2 0 0 0 0 -4]); + L = integer_lattice(B, gram = G); + f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; -2 -4 -6 -4 -3 -2 -1 -3; 2 4 6 5 4 3 2 3; -1 -2 -3 -3 -3 -2 -1 -1; 0 0 0 0 1 0 0 0; 1 2 3 3 2 1 0 2]); + Lf = integer_lattice_with_isometry(L, f); + + GLf = @inferred image_centralizer_in_Oq(Lf) + @test order(GLf) == 600 + + M = @inferred coinvariant_lattice(Lf) + @test is_of_hermitian_type(M) + H = @inferred hermitian_structure(M) + @test H isa HermLat + + qL, fqL = @inferred discriminant_group(Lf) + @test divides(order_of_isometry(M), order(fqL))[1] + @test is_elementary(qL, 2) + + S = @inferred collect(values(signatures(M))) + @test S[1] .+ S[2] == signature_pair(genus(M)) + + @test rank(invariant_lattice(M)) == 0 + @test rank(invariant_lattice(Lf)) == rank(Lf) - rank(M) + + t = @inferred type(Lf) + @test length(collect(keys(t))) == 2 + @test is_of_type(Lf, t) + @test !is_of_same_type(Lf, M) + @test is_hermitian(type(M)) + + # Add more image_centralizer_in_Oq for indefinite and test with comparison to + # Sage results + + E8 = root_lattice(:E, 8) + OE8 = orthogonal_group(E8) + F, C, _ = @inferred invariant_coinvariant_pair(E8, OE8) + @test rank(F) == 0 + @test C == E8 + + D5 = lll(root_lattice(:D, 5)) + OD5 = matrix_group(automorphism_group_generators(D5, ambient_representation = false)) + C, gene = @inferred coinvariant_lattice(D5, OD5, ambient_representation = false) + G = gram_matrix(C) + @test all(g -> g*G*transpose(g) == G, gene) + +end + +@testset "Enumeration of lattices with finite isometries" begin + E6 = root_lattice(:E, 6) + OE6 = orthogonal_group(E6) + cc = conjugacy_classes(E6) + + D = Oscar._test_isometry_enumeration(E6) + for n in collect(keys(D)) + @test length(D[n]) == length(filter(c -> order(representative(c)) == n, cc)) + end + + for N in D[12] + ONf = image_centralizer_in_Oq(integer_lattice_with_isometry(lattice(N), ambient_isometry(N))) + # for N, the image in OqN of the centralizer of fN in ON is directly + # computing during the construction of the admissible primitive extension. + # We compare if at least we obtain the same orders (we can't directly + # compare the groups since they do not act exactly on the same module... + # and isomorphism test might be slow) + @test order(ONf) == order(image_centralizer_in_Oq(N)) + end + + E8 = root_lattice(:E, 8) + @test length(enumerate_classes_of_lattices_with_isometry(E8, 20)) == 3 +end + +@testset "Primitive embeddings" begin + +end + From bc103ba5789105092e4165468710cecea3af392f Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 26 Jun 2023 16:47:19 +0200 Subject: [PATCH 60/76] add _overlattices methods and refactor big algo + power of quadspace/zzlat-withisom --- .../QuadFormAndIsom/docs/src/latwithisom.md | 1 + .../QuadFormAndIsom/docs/src/spacewithisom.md | 1 + .../QuadFormAndIsom/src/embeddings.jl | 340 +++++++++++------- .../src/lattices_with_isometry.jl | 10 + .../src/spaces_with_isometry.jl | 12 +- 5 files changed, 224 insertions(+), 140 deletions(-) diff --git a/experimental/QuadFormAndIsom/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md index 67906b82614c..ec0f6e8c24c3 100644 --- a/experimental/QuadFormAndIsom/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -110,6 +110,7 @@ Similarly, some basic operations on $\mathbb Z$-lattices are available for lattices with isometry. ```@docs +Base.:^(::ZZLatWithIsom, ::Int) biproduct(::Vector{ZZLatWithIsom}) direct_product(::Vector{ZZLatWithIsom}) direct_sum(::Vector{ZZLatWithIsom}) diff --git a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md index f7644bab4eea..08e56408c93e 100644 --- a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md @@ -86,6 +86,7 @@ Similarly, some basic operations on quadratic spaces are available for quadratic spaces with isometry. ```@docs +Base.:^(::QuadSpaceWithIsom, ::Int) biproduct(::Vector{QuadSpaceWithIsom}) direct_product(::Vector{QuadSpaceWithIsom}) direct_sum(::Vector{QuadSpaceWithIsom}) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 6261eabda808..6225643027a5 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -2,7 +2,7 @@ const GG = GAP.Globals ################################################################################ # -# Miscellaneous +# Orthogonal direct sums and embeddings of orthogonal groups # ################################################################################ @@ -16,7 +16,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu 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(v -> AinD(v[1])*BinD(v[2]) == 0, Hecke.cartesian_product_iterator([gens(A), gens(B)], inplace=false)) + @assert 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) @@ -72,6 +72,13 @@ function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQ return D, AinD, BinD, OD, OAtoOD, OBtoOD end +############################################################################### +# +# Local tools +# +############################################################################### + + # If fq is an isometry of the torsion module q, we compute the kernel of mu(fq) # restricted to the p-elementary part of q (i.e. the submodule of q generated by # all the elements of order p) @@ -130,13 +137,88 @@ function _is_free(T, p, l) return _is_even(T, p, l-1) && _is_even(T, p, l+1) end +############################################################################### +# +# Overlattices +# +############################################################################### + +# Compute the overlattice corresponding to the glue map gamma, in the ambient +# space of the covering lattice of the finite bilinear module D. As inputs, +# gamma must be an anti-isometry between HA and HB, both should embed in D and +# gamma commutes with the actions of fA and fB on HA and HB respectively. +# +# If `same_ambient = true`, then we consider all the problem in a same ambient +# quadratic space. In particular, the covering lattices of HA, HB and D are all +# in that same space. +# +# fA and fB here are considered as isometry of the relations lattice of HA and +# HB, respectively. +function _overlattice(gamma::TorQuadModuleMor, + HAinD::TorQuadModuleMor, + HBinD::TorQuadModuleMor, + fA::QQMatrix = identity_matrix(QQ, rank(relations(domain(HAinD)))), + fB::QQMatrix = identity_matrix(QQ, rank(relations(domain(HBinD)))); + same_ambient::Bool = false) + HA = domain(HAinD) + HB = domain(HBinD) + A = relations(HA) + B = relations(HB) + D = codomain(HAinD) + if same_ambient + _glue = Vector{QQFieldElem}[lift(g) + lift(gamma(g)) for g in gens(HA)] + z = zero_matrix(QQ, 0, degree(A)) + glue = reduce(vcat, [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)) + C = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) + fC = block_diagonal_matrix([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(HA)] + z = zero_matrix(QQ, 0, degree(cover(D))) + glue = reduce(vcat, [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)) + 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 = __B*fC*inv(__B) + end + @hassert :ZZLatWithIsom 1 fC*gram_matrix(C)*transpose(fC) == gram_matrix(C) + _, graph = sub(D, D.(_glue)) + return C, fC, graph +end + +# Same as above where we glue along the trivial subgroups of HA and HB. In that +# particular case, HA and HB are the discriminant group of the lattices +# considered (so HA = L^{\vee}/L for some lattice integral lattice L, and same +# for HB), and D is the orthogonal direct sum of HA and HB in an appropriate +# quadratic space. +function _overlattice(HAinD::TorQuadModuleMor, + HBinD::TorQuadModuleMor, + fA::QQMatrix = identity_matrix(QQ, rank(relations(domain(HAinD)))), + fB::QQMatrix = identity_matrix(QQ, rank(relations(domain(HBinD)))); + same_ambient::Bool = false) + HA = domain(HAinD) + HB = domain(HBinD) + zA, _ = sub(HA, TorQuadModuleElem[]) + zB, _ = sub(HB, TorQuadModuleElem[]) + gamma = hom(zA, zB, zero_matrix(ZZ, 0, 0)) + return _overlattice(gamma, HAinD, HBinD, fA, fB, same_ambient = same_ambient) +end + ############################################################################## # # Orbits and stabilizers of discriminant subgroups # ############################################################################## -# Should eventually replace the one above function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) gene = TorQuadModuleElem[g(q(lift(t))) for t in gens(T)] @@ -144,8 +226,7 @@ function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) end # Compute stabilizer of a subgroup of a `TorQuadModule` under the action by -# automorphisms. For now we use the GAP infrastructure, we should turn it for -# using the Hecke groups part +# automorphisms. function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) @@ -154,9 +235,10 @@ function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) return sub(O, O.([h.X for h in gens(stab)])) end -function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, - order::Hecke.IntegerUnion = -1, - f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq))) +function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, + O::AutomorphismGroup{TorQuadModule}, + order::Hecke.IntegerUnion = -1, + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq))) fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) V = domain(Vinq) q = codomain(Vinq) @@ -179,15 +261,6 @@ function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor return res end -function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, H::TorQuadModule; f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) - if is_elementary_with_prime(H)[1] - sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, order(H), f) - else - sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, order(H), f) - end - return filter(d -> is_isometric_with_isometry(domain(d[1]), H)[1], sors) -end - # The underlying abelian groups of H and V are elementary abelian p-groups, f is # an automorphism of V fixing H, so in particular it acts on the quotient V/H # whose abelian structure actually defines a finite dimensional Fp-vector space. @@ -342,9 +415,21 @@ function Base.:(==)(T1::TorQuadModule, T2::TorQuadModule) return cover(T1) == cover(T2) end +function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, + O::AutomorphismGroup{TorQuadModule}, + H::TorQuadModule; + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) + if is_elementary_with_prime(H)[1] + sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, order(H), f) + else + sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, order(H), f) + end + return filter(d -> is_isometric_with_isometry(domain(d[1]), H)[1], sors) +end + ################################################################################# # -# Embeddings for primary lattices +# Primitive embeddings for primary lattices # ################################################################################# @@ -377,63 +462,49 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua OD = orthogonal_group(D) if el - VN, VNinqN, _ = _get_V(id_hom(qN), minimal_polynomial(identity_matrix(QQ,1)), p) - subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, p) - filter!(HN -> is_anti_isometric_with_anti_isometry(domain(HN[1]), H)[1], subsN) - @assert !isempty(subsN) - VM, VMinqM, _ = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), p) - subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(VMinqM, GM, p) - filter!(HM -> is_isometric_with_isometry(domain(HM[1]), H)[1], subsM) - @assert !isempty(subsM) + VN, VNinqN = _get_V(id_hom(qN), minimal_polynomial(identity_matrix(QQ,1)), p) + VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), p) elseif prim VN, VNinqN = primary_part(qN, p) - subsN = _classes_automorphic_subgroups(VNinqN, GN, rescale(H, -1)) - @hassert :ZZLatWithIsom 1 !isempty(subsN) VM, VMinqM = primary_part(qM, p) - subsM = _classes_automorphic_subgroups(VMinqM, GM, H) - @hassert :ZZLatWithIsom 1 !isempty(subsM) else VN, VNinqN = qN, id_hom(qN) - subsN = _classes_automorphic_subgroups(VNinqN, GN, rescale(H, -1)) - @hassert :ZZLatWithIsom 1 !isempty(subsN) VM, VMinqM = qM, id_hom(qM) - subsM = _classes_automorphic_subgroups(VMinqM, GM, H) - @hassert :ZZLatWithIsom 1 !isempty(subsM) end + subsN = _classes_automorphic_subgroups(VNinqN, GN, rescale(H, -1)) + @hassert :ZZLatWithIsom 1 !isempty(subsN) + subsM = _classes_automorphic_subgroups(VMinqM, GM, H) + @hassert :ZZLatWithIsom 1 !isempty(subsM) + for H1 in subsN, H2 in subsM ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) @hassert :ZZLatWithIsom 1 ok - HNinVN, stabN = H1 - HN = domain(HNinVN) + HNinqN, stabN = H1 + HNinD = compose(HNinqN, qNinD) + HN = domain(HNinqN) OHN = orthogonal_group(HN) - HMinVM, stabM = H2 - HM = domain(HMinVM) + HMinqM, stabM = H2 + HMinD = compose(HMinqM, qMinD) + HM = domain(HMinqM) OHM = orthogonal_group(HM) - actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinVN)) for x in gens(stabN)]) - - actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinVM)) for x in gens(stabM)]) + actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) + actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) 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, _ = sub(OHM, stabNphi) + reps = double_cosets(OHM, stabNphi, imM) @vprint :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" for g in reps g = representative(g) phig = compose(phi, hom(g)) - _glue = Vector{QQFieldElem}[lift(qNinD(VNinqN(HNinVN(g)))) + lift(qMinD(VMinqM(HMinVM(phig(g))))) for g in gens(domain(phig))] - z = zero_matrix(QQ, 0, degree(N)+degree(M)) - glue = reduce(vcat, [matrix(QQ, 1, degree(N)+degree(M), g) for g in _glue], init = z) - glue = vcat(block_diagonal_matrix(basis_matrix.(N, M)), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - L = Hecke.lattice(ambient_space(cover(D)), _B[end-rank(N)-rank(M)+1:end, :]) + L, _, _ = _overlattice(phig, HNinD, HMinD) N2 = lattice_in_same_ambient_space(L, hcat(basis_matrix(N), zero_matrix(QQ, rank(N), degree(M)))) @hassert :ZZLatWithIsom 1 genus(N) == genus(N2) M2 = lattice_in_same_ambient_space(L, hcat(zero_matrix(QQ, rank(M), degree(N)), basis_matrix(M))) @@ -551,8 +622,9 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) - !(M in G) && return results + !(genus(M) == G) && return results push!(results, (M, M, orthogonal_submodule(M, M))) + return results end @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" @@ -571,7 +643,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific qMinD, qLinD = inj if el - VM, VMinqM, _ = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ,1)), p) + VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ,1)), p) else VM, VMinqM = primary_part(qM, p) end @@ -589,7 +661,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific for H in subsL HL = domain(H[1]) it = submodules(VM, order = order(HL)) - subsM = TorQuadModuleMor[sub(qM, lift.(gens(j[1])))[2] for j in it] + subsM = TorQuadModuleMor[j[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue @@ -609,7 +681,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific classification == :none && return true, results G2 = genus(disc2, (pL-pM, nL-nM)) - @vprint :ZZLatWithIsom 1 "We can glue: $G\n" + @vprint :ZZLatWithIsom 1 "We can glue: $(G2)\n" Ns = representatives(G2) @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" Ns = lll.(Ns) @@ -618,7 +690,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) if !is_empty(temp) classification == :first && return true, temp - append!(results, temps) + append!(results, temp) end GC.gc() end @@ -723,16 +795,17 @@ There are 4 possibilities: classes of primitive sublattices in lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. """ -function primitive_embeddings_of_primary_lattice(G::ZZgenus, M::ZZLat; classification::Symbol = :sublat) +function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) @req classification in [:none, :first, :emb, :sublat] "Wrong symbol for classification" - pL, _, nL = signature_tuple(L) + pL, _, nL = signature_tuple(G) pM, _, nM = signature_tuple(M) @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) - !(M in G) && return results + !(genus(M) == G) && return results push!(results, (M, M, orthogonal_submodule(M, M))) + return results end @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" @@ -751,7 +824,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZgenus, M::ZZLat; classific qMinD, qLinD = inj if el - VL, VLinqL, _ = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ,1)), p) + VL, VLinqL = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ,1)), p) else VL, VLinqL = primary_part(qL, p) end @@ -769,7 +842,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZgenus, M::ZZLat; classific for H in subsL HL = domain(H[1]) it = submodules(qM, order = order(HL)) - subsM = TorQuadModuleMor[H[2] for H in it] + subsM = TorQuadModuleMor[j[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue @@ -789,7 +862,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZgenus, M::ZZLat; classific classification == :none && return true, results G2 = genus(disc, (pL-pM, nL-nM)) - @info "We can glue: $G" + @info "We can glue: $(G2)" Ns = representatives(G2) @info "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) @@ -814,10 +887,6 @@ end # #################################################################################### -# The function is long and cannot be cut easily in subprocesses because we need -# to keep track of too many objects and parameters. So too avoid to have -# subfunctions with dozens of arguments, we just leave it as is and make a bunch -# of comments along the way. @doc raw""" admissible_equivariant_primitive_extensions(Afa::ZZLatWithIsom, Bfb::ZZLatWithIsom, @@ -889,34 +958,34 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, OqB = domain(OqBinOD) # if the glue valuation is zero, then we glue along the trivial group and we don't - # have much more to do. Since the triple is p-admissible, A+B = C + # have much more to do. 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)] gene = vcat(geneA, geneB) GCAB, _ = sub(OD, gene) - if amb - C2 = lattice(A)+lattice(B) - fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) - _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) - fC2 = _B*fC2*inv(_B) - @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) - else - _B = block_diagonal_matrix([basis_matrix(A), basis_matrix(B)]) - C2 = lattice_in_same_ambient_space(cover(D), _B) - fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) - @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) - end + + # We compute the overlattice in this context + C2, fC2, _ = _overlattice(qAinD, qBinD, isometry(A), isometry(B), same_ambient = amb) + C2fC2 = integer_lattice_with_isometry(C2, fC2, ambient_representation = false) + qC2 = discriminant_group(C2) phi2 = hom(qC2, D, TorQuadModuleElem[D(lift(x)) for x in gens(qC2)]) @hassert :ZZLatWithIsom 1 is_isometry(phi2) - if is_of_type(integer_lattice_with_isometry(C2, fC2^q, ambient_representation=false), type(C)) - GC2 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GCAB)]) - C2fc2 = integer_lattice_with_isometry(C2, fC2, ambient_representation=false) - @hassert :ZZLatWithIsom 1 discriminant_group(C2fc2)[2] in GC2 - set_attribute!(C2fc2, :image_centralizer_in_Oq, GC2) - push!(results, C2fc2) - end + + # If not of the good type, we discard it + !is_of_type(C2fC2^q, type(C)) && return results + + # This is the new image of the stabilizer, just a direct product of + # the previous ones + GC2 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GCAB)]) + + # This is mainly to check that we have not done anything inconsistent + @hassert :ZZLatWithIsom 1 discriminant_group(C2fC2)[2] in GC2 + set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) + push!(results, C2fC2) return results end @@ -970,10 +1039,12 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, for (H1, H2, phi) in R SAinqA, stabA = H1 SA = domain(SAinqA) + SAinD = compose(SAinqA, qAinD) OSA = orthogonal_group(SA) SBinqB, stabB = H2 SB = domain(SBinqB) + SBinD = compose(SBinqB, qBinD) OSB = orthogonal_group(SB) # we need a first admissible gluing. We know that such gluing exists because @@ -1036,48 +1107,19 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, phig = compose(phi, hom(g)) @assert is_anti_isometry(phig) - # The following is the regular procedure we use on Oscar/Hecke to compute - # overlattices of a gluing. We overwrite it here because we have too many - # parameters to keep track to in this function. We distinguish the case of - # similar ambient space since everything is made way easier as soon as - # we work in a unique fixed quadratic space. - if amb - _glue = Vector{QQFieldElem}[lift(g) + lift(phig(g)) for g in gens(domain(phig))] - z = zero_matrix(QQ, 0, degree(A)) - glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) - glue = vcat(basis_matrix(lattice(A)+lattice(B)), glue) - glue = FakeFmpqMat(glue) - _B = hnf(glue) - _B = QQ(1, denominator(glue))*change_base_ring(QQ, numerator(_B)) - C2 = lattice(ambient_space(A), _B[end-rank(A)-rank(B)+1:end, :]) - fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) - _B = solve_left(reduce(vcat, basis_matrix.([A,B])), basis_matrix(C2)) - fC2 = _B*fC2*inv(_B) - else - _glue = Vector{QQFieldElem}[lift(qAinD(SAinqA(s)))+ lift(qBinD(SBinqB(phig(s)))) for s in gens(domain(phig))] - z = zero_matrix(QQ,0,degree(cover(D))) - glue = reduce(vcat, [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)) - C2 = lattice(ambient_space(cover(D)), _B[end-rank(A)-rank(B)+1:end, :]) - fC2 = block_diagonal_matrix([isometry(A), isometry(B)]) - __B = solve_left(block_diagonal_matrix(basis_matrix.([A,B])), basis_matrix(C2)) - fC2 = __B*fC2*inv(__B) - end - @hassert :ZZLatWithIsom 1 fC2*gram_matrix(C2)*transpose(fC2) == gram_matrix(C2) + # We compute the overlattice in this context, keeping track whether we + # cork in a fixed ambient quadratic space + C2, fC2, extinD = _overlattice(phig, SAinD, SBinD, isometry(A), isometry(B), same_ambient = amb) + C2fC2 = integer_lattice_with_isometry(C2, fC2, ambient_representation = false) # This is the type requirement: somehow, we want `(C2, fC2)` to be a "q-th root" of `(C, fC)`. - if !is_of_type(integer_lattice_with_isometry(C2, fC2^q, ambient_representation = false), type(C)) - continue - end + !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, _ = sub(D, D.(_glue)) + 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)) @@ -1088,7 +1130,6 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # So now this new integer lattice with isometry `(C2, fC2)` is a good # output. Just remain toi compute GC2 in a smart way. - C2 = integer_lattice_with_isometry(C2, fC2, ambient_representation=false) geneOSA = AutomorphismGroupElem{TorQuadModule}[OSA(compose(phig, compose(hom(g1), inv(phig)))) for g1 in unique(gens(imB))] im2_phi, _ = sub(OSA, geneOSA) gene_inter = AutomorphismGroupElem{TorQuadModule}[h for h in unique(gens(intersect(imA, im2_phi)[1]))] @@ -1103,15 +1144,24 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # If we have done good things, the action of fC2 on qC2 should centralize # itself... - @hassert :ZZLatWithIsom 1 discriminant_group(C2)[2] in stab + @hassert :ZZLatWithIsom 1 discriminant_group(C2fC2)[2] in stab - set_attribute!(C2, :image_centralizer_in_Oq, stab) - push!(results, C2) + set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) + push!(results, C2fC2) end end return results end +############################################################################### +# +# Computation of O(H_B, rho_{l+1}(B)) +# =================================== +# [BH23, Definition 4.16] +# +############################################################################### + + # Action of isometries on the gram matrix of a finite bilinear form function _on_modular_matrix(M::QQMatrix, g::AutomorphismGroupElem) q = domain(parent(g)) @@ -1162,6 +1212,13 @@ function _compute_double_stabilizer(SBinqB, l, spec) 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) @@ -1190,6 +1247,27 @@ function _is_anti_isometry_bilinear(f::TorQuadModuleMor) return true end +# Compute an anti-isometry between the two finite bilinear modules r1 and r2. +function _anti_isometry_bilinear(r1, r2) + @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 +# +############################################################################### + # If we are given a gluing between SA and SB, given that an admissible gluing # exist, we massage phi until we turn it into an admissible gluing. There might # be ways to improve such search, but I would expect that both loops iterating @@ -1240,19 +1318,3 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) phig = compose(phi_1, hom(g)) return phig end - -# Compute an anti-isometry between the two finite bilinear modules r1 and r2. -function _anti_isometry_bilinear(r1, r2) - @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 - diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 9cbfa37b81d7..3e478b24f90d 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -378,6 +378,16 @@ function rescale(Lf::ZZLatWithIsom, a::Hecke.RationalUnion) return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf), check=false) end +@doc raw""" + Base.:^(Lf::ZZLatWithIsom, n::Int) + +Given a lattice with isometry $(L, f)$ and an integer $n$, return the pair +$(L, f^n)$. +""" +function Base.:^(Lf::ZZLatWithIsom, n::Int) + return lattice(ambient_space(Lf)^n, basis_matrix(Lf)) +end + @doc raw""" dual(Lf::ZZLatWithIsom) -> ZZLatWithIsom diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index cac3ea70a1ac..4ce6c62e1390 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -165,7 +165,7 @@ of order `n` (possibly infinite), return the corresponding quadratic space with isometry pair $(V, f)$. """ function quadratic_space_with_isometry(V::Hecke.QuadSpace, f::QQMatrix; - check::Bool = true) + check::Bool = true) if rank(V) == 0 return QuadSpaceWithIsom(V, zero_matrix(QQ, 0, 0), -1) end @@ -211,6 +211,16 @@ function rescale(Vf::QuadSpaceWithIsom, a::Hecke.RationalUnion) return quadratic_space_with_isometry(rescale(space(Vf), a), isometry(Vf), check = false) end +@doc raw""" + Base.:^(Vf::QuadSpaceWithIsom, n::Int) + +Given a quadratic space with isometry $(V, f)$ and an integer $n$, return the pair +$(V, f^n)$. +""" +function Base.:^(Vf::QuadSpaceWithIsom, n::Int) + return quadratic_space_with_isometry(space(Vf), isometry(Vf)^n, check=false) +end + @doc raw""" direct_sum(x::Vector{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor} direct_sum(x::Vararg{QuadSpaceWithIsom}) -> QuadSpaceWithIsom, Vector{AbstractSpaceMor} From d751b9db4df4dcbc398fc140a87813f890ea2978 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 29 Jun 2023 09:09:48 +0200 Subject: [PATCH 61/76] few minor changes --- experimental/QuadFormAndIsom/README.md | 4 +-- .../QuadFormAndIsom/docs/src/introduction.md | 6 ++-- .../QuadFormAndIsom/src/embeddings.jl | 4 +-- .../src/hermitian_miranda_morrison.jl | 32 ++++++++++++++---- .../src/lattices_with_isometry.jl | 4 +-- experimental/QuadFormAndIsom/test/runtests.jl | 33 +++++++++++++++++-- 6 files changed, 65 insertions(+), 18 deletions(-) diff --git a/experimental/QuadFormAndIsom/README.md b/experimental/QuadFormAndIsom/README.md index c303462b1ddf..f4e98a990132 100644 --- a/experimental/QuadFormAndIsom/README.md +++ b/experimental/QuadFormAndIsom/README.md @@ -38,10 +38,10 @@ currently being tested on a larger scale to test its reliability. Moreover, there are still computational bottlenecks due to non optimized algorithms. Among the possible improvements and extensions: -* Implement methods about for lattices with isometries of infinite order; +* Implement methods for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); -* Implement methods for primitive embeddings in odd integral lattices; +* Implement methods for more kinds of (equivariant) primitive embeddings; * Implement methods for more kinds of (equivariant) primitive extensions. ## Currently application of this project diff --git a/experimental/QuadFormAndIsom/docs/src/introduction.md b/experimental/QuadFormAndIsom/docs/src/introduction.md index 52ce87586e4b..f4e98a990132 100644 --- a/experimental/QuadFormAndIsom/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -38,11 +38,11 @@ currently being tested on a larger scale to test its reliability. Moreover, there are still computational bottlenecks due to non optimized algorithms. Among the possible improvements and extensions: -* Implement methods about for lattices with isometries of infinite order; +* Implement methods for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); -* Implement methods for primitive embeddings in odd integral lattices; -* Implement methods for more kinds of equivariant primitive extensions. +* Implement methods for more kinds of (equivariant) primitive embeddings; +* Implement methods for more kinds of (equivariant) primitive extensions. ## Currently application of this project diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 6225643027a5..73a100248654 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -166,7 +166,7 @@ function _overlattice(gamma::TorQuadModuleMor, B = relations(HB) D = codomain(HAinD) if same_ambient - _glue = Vector{QQFieldElem}[lift(g) + lift(gamma(g)) for g in gens(HA)] + _glue = Vector{QQFieldElem}[lift(g) + lift(gamma(g)) for g in gens(domain(gamma))] z = zero_matrix(QQ, 0, degree(A)) glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) glue = vcat(basis_matrix(A+B), glue) @@ -178,7 +178,7 @@ function _overlattice(gamma::TorQuadModuleMor, _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(HA)] + _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, [matrix(QQ, 1, degree(cover(D)), g) for g in _glue], init=z) glue = vcat(block_diagonal_matrix(basis_matrix.([A, B])), glue) diff --git a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index 0ac37189f2eb..4f99330aaec4 100644 --- a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl +++ b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl @@ -311,19 +311,39 @@ end function _local_determinants_morphism(Lf::ZZLatWithIsom) @hassert :ZZLatWithIsom 1 is_of_hermitian_type(Lf) + # We want to compute the image of the centralizer as a subgroup of OqL. For + # this, if Lf is not full, we need to consider an isomorphic pair Lf2 of full + # rank and then transport the generators along an appropriate map. 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) + qL2, fqL2 = discriminant_group(Lf2) + OqL2 = orthogonal_group(qL2) + ok, phi12 = is_isometric_with_isometry(qL, qL2) + @hassert :ZZLatWithIsom 1 ok + ok, g0 = representative_action(OqL, fqL, OqL(compose(phi12, compose(hom(fqL2), inv(phi12))))) + @hassert :ZZLatWithIsom 1 ok + phi12 = compose(hom(OqL(g0)), phi12) + @hassert :ZZLatWithIsom 1 is_isometry(phi12) + else + Lf2 = Lf + qL2, fqL2, OqL2 = qL, fqL, OqL + phi12 = id_hom(qL) + end # Since any isometry of L centralizing f induces an isometry of qL centralising # 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 BH22. - G, _ = centralizer(OqL, fqL) + G2, _ = centralizer(OqL2, fqL2) + G, _ = sub(OqL, [OqL(compose(phi12, compose(hom(g), inv(phi12)))) for g in gens(G2)]) + GtoG2 = hom(G, G2, gens(G), gens(G2)) # 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, # where D is the absolute different of the base algebra of H (a cyclotomic # field). - H = hermitian_structure(Lf) + H = hermitian_structure(Lf2) E = base_field(H) OE = maximal_order(E) @@ -339,8 +359,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # transfer between the quadratic world in which we study (L, f) and the # hermitian world in which H lives. In particular, the trace lattice of H2 # with respect to res will be exactly the dual of L. - res = get_attribute(Lf, :transfer_data) - + res = get_attribute(Lf2, :transfer_data) # We want only the prime ideal in O_K which divides the quotient H2/H. For # this, we collect all the primes dividing DEQ or for which H is not locally # unimodular. Then, we check for which prime ideals p, the local quotient @@ -429,7 +448,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(G) + for g in gens(G2) ds = elem_type(E)[] for p in S lp = prime_decomposition(OE, p) @@ -444,7 +463,8 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) end GSQ, SQtoGSQ, _ = Oscar._isomorphic_gap_group(SQ) - f = hom(G, GSQ, gens(G), SQtoGSQ.(imgs), check=false) + f2 = hom(G2, GSQ, gens(G2), SQtoGSQ.(imgs), check=false) + f = compose(GtoG2, f2) return f end diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 3e478b24f90d..fe9792847a04 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -609,12 +609,12 @@ See [`image_in_Oq(::ZZLat)`](@ref). f = OL(f) UL = QQMatrix[matrix(OL(s)) for s in gens(centralizer(OL, f)[1])] qL = discriminant_group(L) - UL = ZZMatrix[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in UL] + UL = ZZMatrix[matrix(hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)])) for g in UL] unique!(UL) GL = Oscar._orthogonal_group(qL, UL, check = false) elseif rank(L) == euler_phi(n) qL = discriminant_group(L) - UL = ZZMatrix[hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)]).map_ab.map for g in [-f^0, f]] + UL = ZZMatrix[matrix(hom(qL, qL, elem_type(qL)[qL(-lift(t)) for t in gens(qL)]))] unique!(UL) GL = Oscar._orthogonal_group(qL, UL, check = false) else diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index fdd38eae20ec..87ec8b09b778 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -34,6 +34,11 @@ using Oscar @test Vf != quadratic_space_with_isometry(V, neg=true) @test length(unique([Vf, quadratic_space_with_isometry(V, isometry(Vf))])) == 1 + @test Vf^(order_of_isometry(V)+1) == Vf + + V = quadratic_space(QQ, matrix(QQ, 0, 0, [])) + Vf = @inferred quadratic_space_with_isometry(V) + @test order_of_isometry(Vf) == -1 end @testset "Lattices with isometry" begin @@ -43,6 +48,10 @@ end f = rand(agg) g_ambient = rand(agg_ambient) + L = integer_lattice(gram = matrix(QQ, 0, 0, [])) + Lf = integer_lattice_with_isometry(L, neg = true) + @test order_of_isometry(Lf) == -1 + L = @inferred integer_lattice_with_isometry(A4) @test ambient_space(L) isa QuadSpaceWithIsom @test isone(isometry(L)) @@ -57,6 +66,9 @@ end @test k == func(A4) end + GL = image_centralizer_in_Oq(L) + @test order(GL) == 2 + LfQ = @inferred rational_span(L) @test LfQ isa QuadSpaceWithIsom @test evaluate(minimal_polynomial(L), 1) == 0 @@ -76,7 +88,8 @@ end @test ambient_isometry(L2v) == ambient_isometry(L2) L3 = @inferred integer_lattice_with_isometry(A4, g_ambient, ambient_representation = true) - @test order_of_isometry(L2) == multiplicative_order(g_ambient) + @test order_of_isometry(L3) == multiplicative_order(g_ambient) + @test L3^(order_of_isometry(L3)+1) == L3 L4 = @inferred rescale(L3, QQ(1//4)) @test !is_integral(L4) @@ -123,7 +136,22 @@ end # Add more image_centralizer_in_Oq for indefinite and test with comparison to # Sage results - + B = matrix(QQ, 4, 8, [0 0 0 0 3 0 0 0; 0 0 0 0 1 1 0 0; 0 0 0 0 1 0 1 0; 0 0 0 0 2 0 0 1]); + G = matrix(QQ, 8, 8, [-2 1 0 0 0 0 0 0; 1 -2 0 0 0 0 0 0; 0 0 2 -1 0 0 0 0; 0 0 -1 2 0 0 0 0; 0 0 0 0 -2 -1 0 0; 0 0 0 0 -1 -2 0 0; 0 0 0 0 0 0 2 1; 0 0 0 0 0 0 1 2]); + L = integer_lattice(B, gram = G); + f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 -1 1 0 0; 0 0 0 0 0 0 0 1; 0 0 0 0 0 0 -1 1]); + Lf = integer_lattice_with_isometry(L, f); + GL = image_centralizer_in_Oq(Lf) + @test order(GL) == 72 + + B = matrix(QQ, 4, 6, [0 0 0 0 -2 1; 0 0 0 0 3 -4; 0 0 1 0 -1 0; 0 0 0 1 0 -1]); + G = matrix(QQ, 6, 6, [2 1 0 0 0 0; 1 -2 0 0 0 0; 0 0 2//5 4//5 2//5 -1//5; 0 0 4//5 -2//5 -1//5 3//5; 0 0 2//5 -1//5 2//5 4//5; 0 0 -1//5 3//5 4//5 -2//5]); + L = integer_lattice(B, gram = G); + f = matrix(QQ, 6, 6, [1 0 0 0 0 0; 0 1 0 0 0 0; 0 0 0 0 1 0; 0 0 0 0 0 1; 0 0 -1 0 0 1; 0 0 0 -1 1 -1]); + Lf = integer_lattice_with_isometry(L, f); + GL = image_centralizer_in_Oq(Lf) + @test order(GL) == 2 + E8 = root_lattice(:E, 8) OE8 = orthogonal_group(E8) F, C, _ = @inferred invariant_coinvariant_pair(E8, OE8) @@ -135,7 +163,6 @@ end C, gene = @inferred coinvariant_lattice(D5, OD5, ambient_representation = false) G = gram_matrix(C) @test all(g -> g*G*transpose(g) == G, gene) - end @testset "Enumeration of lattices with finite isometries" begin From 569a6a79f185e4f6f13f17cc348f16ce7543c694 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 6 Jul 2023 12:14:38 +0200 Subject: [PATCH 62/76] update Hecke --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 303f53701bd0..354082410a82 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ AbstractAlgebra = "0.30.9" AlgebraicSolving = "0.3.0" DocStringExtensions = "0.8, 0.9" GAP = "0.9.4" -Hecke = "0.18.14" +Hecke = "0.18.16" JSON = "^0.20, ^0.21" Nemo = "0.34.3" Polymake = "0.10.0" From bbc14f6112088b090d25ba9e239329b8a7bc8eee Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 10 Jul 2023 09:49:06 +0200 Subject: [PATCH 63/76] minor fixes + more tests --- .../QuadFormAndIsom/src/embeddings.jl | 36 +++++++++---------- .../QuadFormAndIsom/src/enumeration.jl | 1 - experimental/QuadFormAndIsom/test/runtests.jl | 20 ++++++++++- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 73a100248654..55a7600b8e96 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -238,14 +238,14 @@ end function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, order::Hecke.IntegerUnion = -1, - f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(Vinq))) + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq))) fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) V = domain(Vinq) q = codomain(Vinq) if order == -1 subs = collect(stable_submodules(V, [fV])) else - subs = submodules(V, order = order) + subs = collect(submodules(V, order = order)) filter!(s -> is_invariant(fV, s[2]), subs) end subs = TorQuadModule[s[1] for s in subs] @@ -409,16 +409,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end -# Need to be discarded after next Hecke release -function Base.:(==)(T1::TorQuadModule, T2::TorQuadModule) - relations(T1) != relations(T2) && return false - return cover(T1) == cover(T2) -end - -function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, - O::AutomorphismGroup{TorQuadModule}, - H::TorQuadModule; - f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) +function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, H::TorQuadModule; f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) if is_elementary_with_prime(H)[1] sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, order(H), f) else @@ -654,7 +645,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), GL, k) else - subsL = _subgroups_orbit_representatives_and_stabilizers(id_hom(qL), GL, order = k) + subsL = _subgroups_orbit_representatives_and_stabilizers(id_hom(qL), GL, k) end @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" @@ -670,7 +661,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) @hassert :ZZLatWithIsom 1 ok - _glue = [lift(qMinD(HM(g))) + lift(qLinD(H[1](phi(g)))) for g in gens(domain(HM))] + _glue = [lift(qMinD(qM(lift((g))))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) disc = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), @@ -830,12 +821,12 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific end for k in divisors(gcd(order(qM), order(VL))) - @info "Glue order: $(k)" + @vprint :ZZLatWithIsom 1 "Glue order: $(k)" if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) else - subsL = _subgroups_orbit_representatives_and_stabilizers(VLinqL, GL, order = k) + subsL = _subgroups_orbit_representatives_and_stabilizers(VLinqL, GL, k) end @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" @@ -851,7 +842,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) @hassert :ZZLatWithIsom 1 ok - _glue = [lift(qMinD(HM(g))) + lift(qLinD(VLinqL(H[1](phi(g))))) for g in gens(domain(HM))] + _glue = [lift(qMinD(qM(lift(g)))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) disc = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), @@ -862,9 +853,9 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific classification == :none && return true, results G2 = genus(disc, (pL-pM, nL-nM)) - @info "We can glue: $(G2)" + @vprint :ZZLatWithIsom 1 "We can glue: $(G2)" Ns = representatives(G2) - @info "$(length(Ns)) possible orthogonal complement(s)" + @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns @@ -881,6 +872,13 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific return (length(results) > 0), results end +function Base.:(==)(S::TorQuadModule, T::TorQuadModule) + modulus_bilinear_form(S) != modulus_bilinear_form(T) && return false + modulus_quadratic_form(S) != modulus_quadratic_form(T) && return false + relations(S) != relations(T) && return false + return cover(S) == cover(T) +end + #################################################################################### # # Admissible equivariant primitive extensions diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 4129e7bd8866..d2c59d9b2e5d 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -1,4 +1,3 @@ - ################################################################################## # # This is an import to Oscar of the methods written following the paper [BH23] on diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index 87ec8b09b778..9cec1c8afea7 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -187,9 +187,27 @@ end E8 = root_lattice(:E, 8) @test length(enumerate_classes_of_lattices_with_isometry(E8, 20)) == 3 + @test length(enumerate_classes_of_lattices_with_isometry(E8, 18)) == 4 end @testset "Primitive embeddings" begin - + # Compute orbits of short vectors + k = integer_lattice(gram=matrix(QQ,1,1,[4])) + E8 = root_lattice(:E, 8) + ok, sv = primitive_embeddings_of_primary_lattice(E8, k, classification =:sublat, check=false) + @test ok + @test length(sv) == 1 + ok, sv = primitive_embeddings_in_primary_lattice(rescale(E8, 2), rescale(k, QQ(1//2)), check=false) + @test !ok + @test is_empty(sv) + @test_throws ArgumentError primitive_embeddings_of_primary_lattice(rescale(E8, -1), k, check=false) + + k = integer_lattice(gram=matrix(QQ,1,1,[6])) + E7 = root_lattice(:E, 7) + ok, sv = primitive_embeddings_in_primary_lattice(E7, k, classification = :emb, check=false) + @test ok + @test length(sv) == 2 + + @test !primitive_embeddings_in_primary_lattice(rescale(E7, 2), k, classification = :none, check = false)[1] end From 33d87f57e2d2bcf0240c719bd3224be57228fa62 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 12 Jul 2023 14:58:10 +0200 Subject: [PATCH 64/76] more adjustments --- docs/oscar_references.bib | 10 + experimental/QuadFormAndIsom/README.md | 13 +- .../QuadFormAndIsom/docs/src/enumeration.md | 35 +- .../QuadFormAndIsom/docs/src/introduction.md | 46 +- .../QuadFormAndIsom/docs/src/latwithisom.md | 81 +- .../QuadFormAndIsom/docs/src/primembed.md | 20 +- .../QuadFormAndIsom/docs/src/spacewithisom.md | 11 +- .../QuadFormAndIsom/src/embeddings.jl | 51 +- .../QuadFormAndIsom/src/enumeration.jl | 166 ++- .../src/hermitian_miranda_morrison.jl | 42 +- .../src/lattices_with_isometry.jl | 1218 ++++++++++++++++- .../src/spaces_with_isometry.jl | 492 ++++++- experimental/QuadFormAndIsom/test/runtests.jl | 48 +- 13 files changed, 2067 insertions(+), 166 deletions(-) diff --git a/docs/oscar_references.bib b/docs/oscar_references.bib index 0f243b8f4b4e..2f77630a8f2a 100644 --- a/docs/oscar_references.bib +++ b/docs/oscar_references.bib @@ -133,6 +133,16 @@ @Book{BH09 year = {2009} } +@Article{BH23, + author = {Brandhorst, Simon and Hofmann, Tommy}, + title = {Finite subgroups of automorphisms of K3 surfaces}, + series = {Forum of Mathematics, Sigma}, + volume = {11}, + publisher = {Cambridge University Press, Cambridge}, + year = {2023}, + doi = {10.1017/fms.2023.50} +} + @Misc{BHMPW20, author = {Braden, Tom and Huh, June and Matherne, Jacob P. and Proudfoot, Nicholas and Wang, Botong}, title = {A semi-small decomposition of the Chow ring of a matroid}, diff --git a/experimental/QuadFormAndIsom/README.md b/experimental/QuadFormAndIsom/README.md index f4e98a990132..ea4c29fa4f34 100644 --- a/experimental/QuadFormAndIsom/README.md +++ b/experimental/QuadFormAndIsom/README.md @@ -21,7 +21,7 @@ $L$ is an integral quadratic form, also known as $\mathbb Z$-lattice and $f$ is an isometry of $L$. One of the main feature of this project is the enumeration of isomorphism classes of pairs $(L, f)$ where $f$ is an isometry of finite order with at most two prime divisors. The methods we resort to -for this purpose are developed in the paper [BH23]. +for this purpose are developed in the paper [BH23](@cite). We also provide some algorithms computing isomorphism classes of primitive embeddings of even lattices following Nikulin's theory. More precisely, the two @@ -38,11 +38,10 @@ currently being tested on a larger scale to test its reliability. Moreover, there are still computational bottlenecks due to non optimized algorithms. Among the possible improvements and extensions: -* Implement methods for lattices with isometries of infinite order; +* Implement extra methods for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); -* Implement methods for more kinds of (equivariant) primitive embeddings; -* Implement methods for more kinds of (equivariant) primitive extensions. +* Extend existing methods for equivariant primitive embeddings/extensions. ## Currently application of this project @@ -52,15 +51,13 @@ this code, and further extension of it, to classify finite subgroups of bimeromorphic self-maps of *hyperkaehler manifolds*, which are a higher dimensional analog of K3 surface. -## Tutorials - - ## Notice to the user Since this project is still under development, feel free to try any feature and report all the bugs you may have found. Any suggestions for improvements or extensions are more than welcome. Refer to the next section to know who you -should contact and how. +should contact and how. Do not hesitate either to ask for new features - we +will be glad to add anything you may need for your research. One may expect many things to vary within the next months: name of the functions, available features, performance. This is due to the fact that the diff --git a/experimental/QuadFormAndIsom/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md index 45397a366d92..d980bc3f3810 100644 --- a/experimental/QuadFormAndIsom/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -2,29 +2,31 @@ CurrentModule = Oscar ``` +# Enumeration of isometries + One of the main feature of this project is the enumeration of lattices with isometry of finite order with at most two prime divisors. This is the content -of [BH23] which has been implemented. We guide the user here to the global -aspects of the available theory, and we refer to the paper [BH23] for further +of [BH23](@cite) which has been implemented. We guide the user here to the global +aspects of the available theory, and we refer to the paper [BH23](@cite) for further reference. -# Admissible triples +## Admissible triples Roughly speaking, for a prime number $p$, a *$p$-admissible triple* `(A, B, C)` -is a triple of integer lattices such that,in certain cases, `C` can be obtained +is a triple of integer lattices such that, in certain cases, `C` can be obtained has a primitive extension $A \perp B \to C$ where one can glue along $p$-elementary subgroups of the respective discriminant groups of `A` and `B`. Note that not all admissible triples satisfy this extension property. For instance, if $f$ is an isometry of an integer lattice `C` of prime order `p`, then for $A := \Ker \Phi_1(f)$ and $B := \Ker \Phi_p(f)$, one has that -`(A, B, C)` is $p$-admissible (see [BH13, Lemma 4.15.]). +`(A, B, C)` is $p$-admissible (see [BH13, Lemma 4.15.](@cite)). We say that a triple `(AA, BB, CC)` of genus symbols for integer lattices is *$p$-admissible* if there are some lattices $A \in AA$, $B \in BB$ and $C \in CC$ such that $(A, B, C)$ is $p$-admissible. -We use Definition 4.13. and Algorithm 1 of [BH23] to implement the necessary +We use Definition 4.13. and Algorithm 1 of [BH23](@cite) to implement the necessary tools for working with admissible triples. Most of the computations consists of local genus symbol manipulations and combinatorics. The code also relies on enumeration of integer genera with given signatures, determinant and bounded @@ -39,15 +41,16 @@ is_admissible_triple(::ZZGenus, ::ZZGenus, ::ZZGenus, ::Integer) Note that admissible triples are mainly used for enumerating lattices with isometry of a given order and in a given genus. -# Enumeration functions +## Enumeration functions We give an overview of the functions implemented for the enumeration of the isometries of integral integer lattices. For more details such as the proof of -the algorithms and the theory behind them, we refer to the reference paper [BH23]. +the algorithms and the theory behind them, we refer to the reference paper +[BH23](@cite). -## Global function +### Global function -As we will see later, the algorithms from [BH23] are specialized on the +As we will see later, the algorithms from [BH23](@cite) are specialized on the requirement for the input and regular users might not be able to choose between the functions available. We therefore provide a general function which allows one to enumerate lattices with isometry of a given order and in a given @@ -64,10 +67,10 @@ the previous function computes first iteratively representatives for all classes with isometry in the given genus of order $q^e$. Then, the function increases iteratively the order up to $p^dq^e$. -## Underlying machinery +### Underlying machinery -Here is a list of the algorithmic machinery provided by [BH23] used previously -to enumerate lattices with isometry: +Here is a list of the algorithmic machinery provided by [BH23](@cite) used +previously to enumerate lattices with isometry: ```@docs representatives_of_hermitian_type(::ZZLatWithIsom, ::Int) @@ -77,10 +80,10 @@ splitting_of_pure_mixed_prime_power(::ZZLatWithIsom, ::Int) splitting_of_mixed_prime_power(::ZZLatWithIsom, ::Int, ::Int) ``` -Note that an important feature from the theory in [BH23] is the notion of +Note that an important feature from the theory in [BH23](@cite) is the notion of *admissible gluings* and equivariant primitive embeddings for admissible triples. In the next chapter, we will develop about the features regarding primitive embeddings and their equivariant version. We use this basis to introduce the method [`admissible_equivariant_primitive_extension`](@ref) (Algorithm 2 in -[BH23]) which is the major tool making the previous enumeration possible and -fast, from an algorithmic point of view. +[BH23](@cite)) which is the major tool making the previous enumeration +possible and fast, from an algorithmic point of view. diff --git a/experimental/QuadFormAndIsom/docs/src/introduction.md b/experimental/QuadFormAndIsom/docs/src/introduction.md index f4e98a990132..4734aaac4004 100644 --- a/experimental/QuadFormAndIsom/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -38,11 +38,10 @@ currently being tested on a larger scale to test its reliability. Moreover, there are still computational bottlenecks due to non optimized algorithms. Among the possible improvements and extensions: -* Implement methods for lattices with isometries of infinite order; +* Implement extra methods for lattices with isometries of infinite order; * Extend the methods for classification of primitive embeddings for the more general case (knowing that we lose efficiency for large discriminant groups); -* Implement methods for more kinds of (equivariant) primitive embeddings; -* Implement methods for more kinds of (equivariant) primitive extensions. +* Extend existing methods for equivariant primitive embeddings/extensions. ## Currently application of this project @@ -54,18 +53,57 @@ dimensional analog of K3 surface. ## Tutorials +No tutorials available at the moment. + +## Examples + +No examples available at the moment. ## Notice to the user +### Disclaimer + Since this project is still under development, feel free to try any feature and report all the bugs you may have found. Any suggestions for improvements or extensions are more than welcome. Refer to the next section to know who you -should contact and how. +should contact and how. Do not hesitate either to ask for new features - we +will be glad to add anything you may need for your research. One may expect many things to vary within the next months: name of the functions, available features, performance. This is due to the fact that the current version of the code is still at an experimental stage. +### Report an issue + +If you are working with some objects of type `QuadSpaceWithIsom` or `ZZLatWithIsom` +and you need to report an issue, you can produce directly some lines of codes +helping to reconstruct your example. This can help the reviewers to understand +your issue and assist you. We have implemented a method `to_oscar` which +prints few lines of codes for reconstructing your example. + +```@repl 2 +using Oscar # hide +V = quadratic_space(QQ, 2); +Vf = quadratic_space_with_isometry(V, neg = true) +Oscar.to_oscar(Vf) + +Lf = lattice(Vf) +Oscar.to_oscar(Lf) +``` + +## Make the code more talkative + +Within the code, there are more hidden messages and testing which are disabled +by default. If you plan to experiment with the codes with your favourite +examples, you may want to be able to detect some issues to be reported, as well +as knowing what the code is doing. Indeed, some functions might take time in +term of compilation but also computations. For this, you can enable these extra +tests and printings by setting: + +```julia +Oscar.set_lwi_level(2) +``` + ## Contact Please direct questions about this part of OSCAR to the following people: diff --git a/experimental/QuadFormAndIsom/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md index ec0f6e8c24c3..b787f88112bc 100644 --- a/experimental/QuadFormAndIsom/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -16,7 +16,7 @@ ZZLatWithIsom and it is seen as a quadruple $(Vf, L, f, n)$ where $Vf = (V, f_a)$ consists on the ambient rational quadratic space $V$ of $L$ and an isometry $f_a$ of $V$ -preversing $L$ and inducing $f$ on $L$. $n$ is the order of $f$, which is a +preserving $L$ and inducing $f$ on $L$. $n$ is the order of $f$, which is a divisor of the order of the isometry $f_a\in O(V)$. Given a lattice with isometry $(L, f)$, we provide the following accessors to the @@ -63,19 +63,6 @@ lattice(::QuadSpaceWithIsom) lattice(::QuadSpaceWithIsom, ::MatElem{ <:RationalUnion}) lattice_in_same_ambient_space(::ZZLatWithIsom, ::MatElem) ``` -### Examples - -```@repl 2 -using Oscar # hide -L = root_lattice(:E, 6); -f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; - -1 -2 -2 -2 -1 -1; - 0 1 0 0 0 0; - 1 0 0 0 0 0; - -1 -1 -1 0 0 -1; - 0 0 1 1 0 1]); -Lf = integer_lattice_with_isometry(L, f) -``` ## Attributes and first operations @@ -139,29 +126,14 @@ is_of_type(::ZZLatWithIsom, t:Dict) is_of_same_type(::ZZLatWithIsom, ::ZZLatWithIsom) ``` -### Examples - -```@repl 2 -using Oscar # hide -L = root_lattice(:E, 6); -f = matrix(QQ, 6, 6, [ 1 2 3 2 1 1; - -1 -2 -2 -2 -1 -1; - 0 1 0 0 0 0; - 1 0 0 0 0 0; - -1 -1 -1 0 0 -1; - 0 0 1 1 0 1]); -Lf = integer_lattice_with_isometry(L, f) -type(Lf) -``` - -Finally, if the minimal polynomial of $f$ is cyclotomic, i.e. the $n$-th -cyclotomic polynomial, then we say that the pair $(L, f)$ is *of hermitian -type*. The type of a lattice with isometry of hermitian type is called -*hermitian*. +Finally, if the minimal polynomial of $f$ is irreducible, then we say that the +pair $(L, f)$ is *of hermitian type*. The type of a lattice with isometry of +hermitian type is called *hermitian* (note that the type is only defined for +finite order isometries). These namings follow from the fact that, by the trace equivalence, one can -associate to the pair $(L, f)$ a hermitian lattice over the ring of integers of -the $n$-th cyclotomic field +associate to the pair $(L, f)$ a hermitian lattice over the equation order of +$f$, if it is maximal in the associated number field $\mathbb{Q}[f]$. ```@docs is_of_hermitian_type(::ZZLatWithIsom) @@ -171,9 +143,9 @@ is_hermitian() ## Hermitian structure and trace equivalence As mentioned in the previous section, to a lattice with isometry $Lf := (L, f)$ -such that the minimal polynomial of $f$ is the $n$-th cyclotomic polynomial, one -can associate a hermitian lattice $\mathfrak{L}$ over the ring of integers of -the $n$-th cyclotomic field for which $Lf$ is the associated trace lattice (see +such that the minimal polynomial of $f$ is irreducible, one can associate a +hermitian lattice $\mathfrak{L}$ over the equation order of $f$, if it is +maximal, for which $Lf$ is the associated trace lattice (see [`trace_lattice_with_isometry(::AbstractLat)`](@ref)). Hecke provides the tools to perform the trace equivalence for lattices with isometry of hermitian type. @@ -199,7 +171,7 @@ For simple cases as for definite lattices, $f$ being plus-or-minus the identity or if the rank of $L$ is equal to the totient of the order of $f$ (in the finite case), $G_{L,f}$ can be easily computed. The only other case which can be currently handled is for lattices with isometry of hermitian type following -the *hermitian Miranda-Morisson theory* from [BH23]. This has been implemented +the *hermitian Miranda-Morisson theory* from [BH23](@cite). This has been implemented in this project and it can be indirectly used through the general following method: ```@docs @@ -207,7 +179,7 @@ image_centralizer_in_Oq(::ZZLatWithIsom) ``` For an implementation of the regular Miranda-Morisson theory, we refer to the -function [`image_in_Oq(::ZZLat)`](@ref) which actually compute the image of +function [`image_in_Oq(::ZZLat)`](@ref) which actually computes the image of $\pi$ in both the definite and the indefinite case. We will see later in the section about enumeration of lattices with isometry @@ -248,8 +220,8 @@ invariant_coinvariant_pair(::ZZLat, ::MatrixGroup) ## Signatures We conclude this introduction about standard functionalities for lattices with -isometry by introducing a last invariant for lattices with isometry of -hermitain type $(L, f)$, called the *signatures*. These signatures are +isometry by introducing a last invariant for lattices with finite isometry of +hermitian type $(L, f)$, called the *signatures*. These signatures are are intrinsequely connected to the local archimedean invariants of the hermitian structure associated to $(L, f)$ via the trace equivalence. @@ -265,28 +237,3 @@ isometry of type `QuadSpaceWithIsom` are equal, and if the underlying lattices $L$ and $L'$ are equal as $\mathbb Z$-modules in the common ambient quadratic space. -## Tips for users - -### Report an issue - -If you are working with some lattices with isometry, of type `ZZLatWithIsom`, and -you need to report an issue, you can produce directly some lines of codes -helping to reconstruct your "non-working" example. We have implemented a method -`to_oscar` which prints 5 lines for reconstructing your example. - -```@repl 2 -using Oscar # hide -``` - -### Make the code more talkative - -Within the code, there are more hidden messages and testing which are disabled -by default. If you plan to experiment with the codes with your favourite -examples, you may want to be able to detect some issues to be reported, as well -as knowing what the code is doing. Indeed, some functions might take time in -term of compilation but also computations. For this, you can enable these extra -tests and printings by setting: - -```julia -Oscar.set_lwi_level(2) -``` diff --git a/experimental/QuadFormAndIsom/docs/src/primembed.md b/experimental/QuadFormAndIsom/docs/src/primembed.md index 52d4ad8033f5..04bac0a48885 100644 --- a/experimental/QuadFormAndIsom/docs/src/primembed.md +++ b/experimental/QuadFormAndIsom/docs/src/primembed.md @@ -3,10 +3,12 @@ CurrentModule = Oscar ``` We introduce here the necessary definitions and results which lie behind the -methods presented. Most of the content is taken from [Nik79]. +methods presented. Most of the content is taken from [Nik79](@cite). # Primitive embeddings between even lattices +## Nikulin's theory + Given an embedding $i\colon S\hookrightarrow T$ of non-degenerate integral integer lattices, we call $i$ *primitive* if its cokernel $T/i(S)$ is torsion free. Two primitive embeddings $i_1\colon S\hookrightarrow M_1$ and @@ -14,18 +16,18 @@ $i_2\colon S \hookrightarrow M_2$ of $S$ into two lattices $M_1$ and $M_2$ are called *isomorphic* if there exists an isometry $M_1 \to M_2$ which restricts to the identity of $S$. Moreover, if there exists an isometry between $M_1$ and $M_2$ which maps $S$ to itself (non-necessarily identically), we say that $i_1$ -and $i_2$ defines *isomorphic primitive sublattices* [Nik79]. +and $i_2$ defines *isomorphic primitive sublattices* [Nik79](@cite). In his paper, V. V. Nikulin gives necessary and sufficient condition for an even integral lattice $M$ to embed primitively into an even unimodular lattice with -given invariant ([Nik79, Theorem 1.12.2]). More generally, the author also +given invariants ([Nik79, Theorem 1.12.2](@cite)). More generally, the author also provides methods to compute primitive embeddings of any even lattice into an even -lattice in a given genus ([Nik79, Proposition 1.15.1]). In the latter +lattice in a given genus ([Nik79, Proposition 1.15.1](@cite)). In the latter proposition, it is explain how to classify such embeddings as isomorphic embeddings or as isomorphic sublattices. Such a method can be algorithmically implemented, however it tends to be slow -and inefficient in general for large rank or determinant. However, in the case +and inefficient in general for large rank or determinant. But, in the case where the discriminant groups are (elementary) $p$-groups, the method can be more efficient. @@ -59,7 +61,7 @@ primitive_embeddings_of_primary_lattice(::TorQuadModule, ::Tuple{Int, Int}, In order to compute such primitive embeddings of a lattice `M` into a lattice `L`, one first compute the possible genera for the orthogonal of `M` in `L` -(after embedding), and for each lattice `N` in such a genus, one compute +(after embedding), and for each lattice `N` in such a genus, one computes isomorphism classes of *primitive extensions* of $M \perp N$ modulo $\bar{O}(N)$ (and $\bar{O}(M)$ in the case of classification of primitive sublattices of `L` isometric to `M`). @@ -72,10 +74,10 @@ classified, by looking for *gluings* between anti-isometric subgroups of the respective discriminant groups of `M` and `N`. The construction of an overlattice is determined by the graph of such glue map. -# Admissible equivariant primitive extension +## Admissible equivariant primitive extension -The following function is an interesting tool provided by [BH23]. Given a triple -of integer lattices with isometry `((A, a), (B, b), (C, c))` and two prime +The following function is an interesting tool provided by [BH23](2cite). Given +a triple of integer lattices with isometry `((A, a), (B, b), (C, c))` and two prime numbers `p` and `q` (possibly equal), if `(A, B, C)` is `p`-admissible, this function returns representatives of isomorphism classes of equivariant primitive extensions $(A, a)\perp (B, b)\to (D, d)$ such that the type of $(D, d^p)$ is diff --git a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md index 08e56408c93e..8dae5cceb95f 100644 --- a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md @@ -24,7 +24,7 @@ $f$ is of infinite order, then `n = PosInf`. If $V$ has rank 0, then any isometry $f$ of $V$ is trivial and we set by default `n = -1`. Given a quadratic space with isometry $(V, f)$, we provide the following -accessors to the elements of the previously described tripe: +accessors to the elements of the previously described triple: ```@docs isometry(::QuadSpaceWithIsom) @@ -54,18 +54,13 @@ an isometry of the quadratic space. We recommend not to disable this parameter to avoid any complications. Note however that in the rank 0 case, the checks are avoided since all isometries are necessarily trivial. -### Examples - -```@repl2 -using Oscar # hide -``` - ## Attributes and first operations Given a quadratic space with isometry $Vf := (V, f)$, one can have access to most of the attributes of $V$ and $f$ by calling the similar functions to the pair $(V, f)$ itself. For instance, in order to know the rank of $V$, one can -simply `rank(Vf)`. Here is a list of what are the current accessible attributes: +simply call `rank(Vf)`. Here is a list of what are the current accessible +attributes: ```@docs characteristic_polynomial(::QuadSpaceWithIsom) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 55a7600b8e96..5aad281e0052 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -537,6 +537,29 @@ There are 4 possibilities: If `check` is set to true, the function determines whether `L` is in fact unique in its genus. + +# Examples +We can use such primitive embeddings algorithm to classify embedding in unimodular +lattices + +```jldoctest +julia> E8 = root_lattice(:E,8); + +julia> A4 = root_lattice(:A,4); + +julia> bool, pe = primitive_embeddings_in_primary_lattice(E8, A4) +(true, Tuple{ZZLat, ZZLat, ZZLat}[(Integer lattice of rank 8 and degree 8, Integer lattice of rank 4 and degree 8, Integer lattice of rank 4 and degree 8)]) + +julia> pe +1-element Vector{Tuple{ZZLat, ZZLat, ZZLat}}: + (Integer lattice of rank 8 and degree 8, Integer lattice of rank 4 and degree 8, Integer lattice of rank 4 and degree 8) + +julia> genus(pe[1][2]) == genus(pe[1][3]) +true +``` +To be understood here that there exists a unique class of embedding of the root +lattice $A_4$ in the root lattice $E_8$, and the orthogonal primitive sublattice +is isometric to $A_4$. """ function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = false) if check @@ -718,6 +741,26 @@ There are 4 possibilities: If `check` is set to true, the function determines whether `L` is in fact unique in its genus. + +# Examples +We can use such primitive extensions algorithm to have orbits of some short +primitive vectors: + +```jldoctest +julia> L = root_lattice(:D, 5); + +julia> k = integer_lattice(gram=matrix(QQ,1,1,[4])); + +julia> bool, sv = primitive_embeddings_of_primary_lattice(L, k); + +julia> bool +true + +julia> sv +2-element Vector{Tuple{ZZLat, ZZLat, ZZLat}}: + (Integer lattice of rank 5 and degree 5, Integer lattice of rank 1 and degree 5, Integer lattice of rank 4 and degree 5) + (Integer lattice of rank 5 and degree 5, Integer lattice of rank 1 and degree 5, Integer lattice of rank 4 and degree 5) +``` """ function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = false) if check @@ -821,7 +864,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific end for k in divisors(gcd(order(qM), order(VL))) - @vprint :ZZLatWithIsom 1 "Glue order: $(k)" + @vprint :ZZLatWithIsom 1 "Glue order: $(k)\n" if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) @@ -853,9 +896,9 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific classification == :none && return true, results G2 = genus(disc, (pL-pM, nL-nM)) - @vprint :ZZLatWithIsom 1 "We can glue: $(G2)" + @vprint :ZZLatWithIsom 1 "We can glue: $(G2)\n" Ns = representatives(G2) - @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" + @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" Ns = lll.(Ns) qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns @@ -908,7 +951,7 @@ integral lattices (with isometry) with `fA` and `fB` having relatively coprime irreducible minimal polynomials and imposing that `A` and `B` are orthogonal if `A`, `B` and `C` lie in the same ambient quadratic space. -See Algorithm 2 of [BH22]. +See [BH23, Algorithm 2](@cite). """ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, B::ZZLatWithIsom, diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index d2c59d9b2e5d..41809f717707 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -74,7 +74,32 @@ end Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such that the rank of `B` is divisible by $p-1$, return whether `(A,B,C)` is -`p`-admissible in the sense of Definition 4.13. [BH22] +`p`-admissible in the sense of [BH23, Definition 4.13](@cite) + +# Examples +A standard example is the following: let $(L, f)$ be a lattice with isometry of +prime order $p$, let $F:= L^f$ and $C:= L_f$ be respectively the invariant +and coinvariant sublattices of $(L, f)$. Then, the triple of genera +$(g(F), g(C), g(L))$ is $p$-admissible according to [BH23, Lemma 4.15](@cite). + +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> F = invariant_lattice(Lf); + +julia> C = coinvariant_lattice(Lf); + +julia> is_admissible_triple(genus(F), genus(C), genus(Lf), 5) +true +``` """ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) zg = genus(integer_lattice(gram = matrix(QQ, 0, 0, []))) @@ -150,13 +175,13 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) if a_max == g if length(symbol(Ap)) > 1 - Ar = LocalZZGenus(p, symbol(Ap)[1:end-1]) + Ar = ZZLocalGenus(p, symbol(Ap)[1:end-1]) else Ar = genus(matrix(ZZ,0,0,[]), p) end if length(symbol(Bp)) > 1 - Br = LocalZZGenus(p, symbol(Bp)[1:end-1]) + Br = ZZLocalGenus(p, symbol(Bp)[1:end-1]) else Br = genus(matrix(ZZ, 0, 0, []), p) end @@ -189,7 +214,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) for s in Cp s[1] += 2 end - Cp = LocalZZGenus(p, Cp) + Cp = ZZLocalGenus(p, Cp) if !represents(local_symbol(AperpB, p), Cp) return false @@ -221,7 +246,35 @@ Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and `B` is of rank divisible by $p-1$. -See Algorithm 1 of [BH22]. +See [BH23, Algorithm 1](@cite). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> g = genus(L) +Genus symbol for integer lattices +Signatures: (5, 0, 0) +Local symbols: + Local genus symbol at 2: 1^-4 2^1_7 + Local genus symbol at 3: 1^-4 3^1 + +julia> admissible_triples(g, 5) +2-element Vector{Tuple{ZZGenus, ZZGenus}}: + (Genus symbol: II_(5, 0) 2^-1_3 3^1, Genus symbol: II_(0, 0)) + (Genus symbol: II_(1, 0) 2^1_7 3^1 5^1, Genus symbol: II_(4, 0) 5^1) + +julia> admissible_triples(g, 2) +8-element Vector{Tuple{ZZGenus, ZZGenus}}: + (Genus symbol: II_(5, 0) 2^-1_3 3^1, Genus symbol: II_(0, 0)) + (Genus symbol: II_(4, 0) 2^2_6 3^1, Genus symbol: II_(1, 0) 2^1_1) + (Genus symbol: II_(3, 0) 2^3_3, Genus symbol: II_(2, 0) 2^-2 3^1) + (Genus symbol: II_(3, 0) 2^-3_1 3^1, Genus symbol: II_(2, 0) 2^2_2) + (Genus symbol: II_(2, 0) 2^2_2, Genus symbol: II_(3, 0) 2^-3_1 3^1) + (Genus symbol: II_(2, 0) 2^-2 3^1, Genus symbol: II_(3, 0) 2^3_3) + (Genus symbol: II_(1, 0) 2^1_1, Genus symbol: II_(4, 0) 2^2_6 3^1) + (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) @req is_prime(p) "p must be a prime number" @@ -366,7 +419,21 @@ hermitian type $(M, g)$ and such that the type of $(B, g^m)$ is equal to the type of $(L, f)$. Note that in this case, the isometries `g`'s are of order $nm$. -See Algorithm 3 of [BH22]. +See [BH23, Algorithm 3](@cite). + +# Examples +```jldoctest +julia> L = root_lattice(:A,2); + +julia> Lf = integer_lattice_with_isometry(L); + +julia> reps = representatives_of_hermitian_type(Lf, 6) +1-element Vector{ZZLatWithIsom}: + Integer lattice with isometry of finite order 6 + +julia> is_of_hermitian_type(reps[1]) +true +``` """ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) rank(Lf) == 0 && return ZZLatWithIsom[Lf] @@ -460,7 +527,33 @@ $(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. Note that `e` can be 0. -See Algorithm 4 of [BH22]. +See [BH23, Algorithm 4](@cite). + +# Examples +```jldoctest +julia> L = root_lattice(:A,2); + +julia> f = matrix(QQ, 2, 2, [0 1; -1 -1]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> is_of_hermitian_type(Lf) +true + +julia> reps = splitting_of_hermitian_prime_power(Lf, 2) +2-element Vector{ZZLatWithIsom}: + Integer lattice with isometry of finite order 3 + Integer lattice with isometry of finite order 6 + +julia> all(is_of_hermitian_type, reps) +true + +julia> is_of_same_type(Lf, reps[1]^2) +true + +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) rank(Lf) == 0 && return ZZLatWithIsom[Lf] @@ -511,7 +604,25 @@ order $pq^e$. Note that `e` can be 0. -See Algorithm 5 of [BH22]. +See [BH23, Algorithm 5](@cite). + +# Examples +```jldoctest +julia> L = root_lattice(:A,2); + +julia> Lf = integer_lattice_with_isometry(L); + +julia> splitting_of_prime_power(Lf, 2) +4-element Vector{ZZLatWithIsom}: + Integer lattice with isometry of finite order 1 + Integer lattice with isometry of finite order 2 + Integer lattice with isometry of finite order 2 + Integer lattice with isometry of finite order 2 + +julia> splitting_of_prime_power(Lf, 3, 1) +1-element Vector{ZZLatWithIsom}: + Integer lattice with isometry of finite order 3 +``` """ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) if rank(Lf) == 0 @@ -563,7 +674,7 @@ of $(L, f)$. Note that `e` can be 0, while `d` has to be positive. -See Algorithm 6 of [BH22]. +See [BH23, Algorithm 6](@cite). """ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) rank(Lf) == 0 && return ZZLatWithIsom[Lf] @@ -627,7 +738,40 @@ of order $p^{d+1}q^e$. Note that `d` and `e` can be both zero. -See Algorithm 7 of [BH22]. +See [BH23, Algorithm 7](@cite). + +# Examples +```jldoctest +julia> L = root_lattice(:E,7); + +julia> f = matrix(QQ, 7, 7, [ 1 1 2 1 0 0 1; + -1 -2 -3 -2 -1 -1 -1; + 0 1 2 1 1 1 1; + 0 0 -1 -1 -1 -1 -1; + 1 1 2 2 2 1 1; + 0 0 -1 -1 -1 0 0; + 0 0 0 1 0 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 7 and degree 7 + with isometry of finite order 6 + given by + [ 1 1 2 1 0 0 1] + [-1 -2 -3 -2 -1 -1 -1] + [ 0 1 2 1 1 1 1] + [ 0 0 -1 -1 -1 -1 -1] + [ 1 1 2 2 2 1 1] + [ 0 0 -1 -1 -1 0 0] + [ 0 0 0 1 0 0 0] + +julia> reps = splitting_of_mixed_prime_power(Lf, 2) +2-element Vector{ZZLatWithIsom}: + Integer lattice with isometry of finite order 12 + Integer lattice with isometry of finite order 12 + +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) if rank(Lf) == 0 @@ -678,7 +822,7 @@ end enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::IntegerUnion) -> Vector{ZZLatWithIsom} enumerate_classes_of_lattices_with_isometry(G::ZZGenus, order::IntegerUnion) - -> Vector{LocalZZGenus} + -> Vector{ZZLocalGenus} Given an integral integer lattice `L`, return representatives of isomorphism classes of lattice with isometry $(M ,g)$ where `M` is in the genus of `L`, and `g` has order diff --git a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index 4f99330aaec4..60c7df31491f 100644 --- a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl +++ b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl @@ -269,7 +269,7 @@ end # According to [BH23], the quotient D^{-1}L^#/L is unimodular at p # if and only if # - either L is unimodular at p, and D and p are coprime -# - or D^{-1}L^# is P^a-modular where P is largest prime ideal +# - or L is P^{-a}-modular where P is largest prime ideal # over p fixed the canonical involution, and a is the valuation of D at P. function _elementary_divisors(L::HermLat, D::Hecke.NfRelOrdIdl) @@ -281,14 +281,15 @@ function _elementary_divisors(L::HermLat, D::Hecke.NfRelOrdIdl) ok && a == -valuation(D, P) && continue push!(primess, p) end + minPs = minimum.(Ps) for p in primes(genus(L)) - (p in primess) && continue + ((p in primess) || (p in minPs)) && continue push!(primess, p) end return primess end -# We compute here the map delta from Theorem 6.15 of BH22. Its kernel is +# We compute here the map delta from Theorem 6.15 of BH23. Its kernel is # precisely the image of the map O(L, f) \to O(D_L, D_f). # # This map is defined on the centralizer O(D_L, D_f) of D_f in O(D_L). For @@ -334,7 +335,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # Since any isometry of L centralizing f induces an isometry of qL centralising # 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 BH22. + # group G corresponds to U(D_L) in the notation of BH23. G2, _ = centralizer(OqL2, fqL2) G, _ = sub(OqL, [OqL(compose(phi12, compose(hom(g), inv(phi12)))) for g in gens(G2)]) GtoG2 = hom(G, G2, gens(G), gens(G2)) @@ -413,7 +414,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) f = hom(gens(RmodFsharp), A) FmodFsharp, j = kernel(f) - # Now according to Theorem 6.15 of BH22, it remains to quotient out the image + # Now according to Theorem 6.15 of BH23, it remains to quotient out the image # of the units in E of norm 1. Eabs, EabstoE = Hecke.absolute_simple_field(E) OEabs = maximal_order(Eabs) @@ -426,7 +427,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) gene_norm_one = Hecke.NfRelElem[EabstoE(Eabs(mUOEabs(jU(k)))) for k in gens(KU)] - # Now according to Theorem 6.15 of BH22, it remains to quotient out + # Now according to Theorem 6.15 of BH23, it remains to quotient out FOEmodFsharp, m = sub(RmodFsharp, elem_type(RmodFsharp)[Fsharplog([x for i in 1:length(S)]) for x in gene_norm_one]) I = intersect(FOEmodFsharp, FmodFsharp) @@ -444,20 +445,24 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) imgs = elem_type(SQ)[] # For each of our matrices in gene_herm, we do successive P-adic liftings in # order to approximate an isometry of D^{-1}H^#, up to a certain precision - # (given by Theorem 6.25 in BH22). We do this for all the primes we have to + # (given by Theorem 6.25 in BH23). We do this for all the primes we have to # 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) ds = elem_type(E)[] for p in S - lp = prime_decomposition(OE, p) - P = lp[1][1] - k = valuation(N, p) - a = valuation(DEQ, P) - e = valuation(DEK, P) - g_approx = _approximate_isometry(H, H2, g, P, e, a, k, res) - push!(ds, det(g_approx)) + if !_is_special(H, p) + push!(ds, one(E)) + else + lp = prime_decomposition(OE, p) + P = lp[1][1] + k = valuation(N, p) + a = valuation(DEQ, P) + e = valuation(DEK, P) + g_approx = _approximate_isometry(H, H2, g, P, e, a, k, res) + push!(ds, det(g_approx)) + end end push!(imgs, dlog(ds)) end @@ -599,7 +604,7 @@ function _norm_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke return r end -# This is algorithm 8 of BH22: under the good assumptions, then we can do a +# This is algorithm 8 of BH23: under the good assumptions, then we can do a # P-adic lifting of a matrix which represents an isometry up to a certain # precision. In this way, we approximate our matrix by another matrix, to a # given precision and the new matrix defines also an isometry up to a finer @@ -617,7 +622,7 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.NfRelElem, l::Int, P::H # R represents the defect, how far F is to be an isometry of G R = G - F*G*map_entries(involution(E), transpose(F)) - # These are the necessary conditions for the input of algorithm 8 in BH22 + # These are the necessary conditions for the input of algorithm 8 in BH23 if check @hassert :ZZLatWithIsom 1 _scale_valuation(inv(G), P) >= 1+a @hassert :ZZLatWithIsom 1 _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a @@ -744,7 +749,10 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) E = nf(OE) lp = prime_decomposition(OE, minimum(P)) dya = is_dyadic(P) - !dya && return E(1//2) + if !dya + length(lp) == 1 && return E(1//2) + return gen(E) + end lp = prime_decomposition(OE, minimum(P)) if lp[1][2] == 1 return Hecke._special_unit(P, minimum(P)) diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index fe9792847a04..21bbe9847d2f 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -9,6 +9,16 @@ lattice(Lf::ZZLatWithIsom) -> ZZLat Given a lattice with isometry $(L, f)$, return the underlying lattice `L`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> lattice(Lf) === L +true +``` """ lattice(Lf::ZZLatWithIsom) = Lf.Lb @@ -16,6 +26,20 @@ lattice(Lf::ZZLatWithIsom) = Lf.Lb isometry(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> isometry(Lf) +[-1 0 0 0 0] +[ 0 -1 0 0 0] +[ 0 0 -1 0 0] +[ 0 0 0 -1 0] +[ 0 0 0 0 -1] +``` """ isometry(Lf::ZZLatWithIsom) = Lf.f @@ -28,6 +52,26 @@ inducing `f` on `L`. Note that `g` might not be unique and we fix such a global isometry together with `V` into a container type `QuadSpaceWithIsom`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> Vf = ambient_space(Lf) +Quadratic space of dimension 5 + with isometry of finite order 2 + given by + [-1 0 0 0 0] + [ 0 -1 0 0 0] + [ 0 0 -1 0 0] + [ 0 0 0 -1 0] + [ 0 0 0 0 -1] + +julia> typeof(Vf) +QuadSpaceWithIsom +``` """ ambient_space(Lf::ZZLatWithIsom) = Lf.Vf @@ -35,15 +79,39 @@ ambient_space(Lf::ZZLatWithIsom) = Lf.Vf ambient_isometry(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return an isometry of the ambient -space of `L` inducing `f` on `L` +space of `L` inducing `f` on `L`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> ambient_isometry(Lf) +[-1 0 0 0 0] +[ 0 -1 0 0 0] +[ 0 0 -1 0 0] +[ 0 0 0 -1 0] +[ 0 0 0 0 -1] +``` """ ambient_isometry(Lf::ZZLatWithIsom) = isometry(ambient_space(Lf)) @doc raw""" - order_of_isometry(Lf::ZZLatWithIsom) -> Integer + order_of_isometry(Lf::ZZLatWithIsom) -> IntExt Given a lattice with isometry $(L, f)$, return the order of the underlying isometry `f`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> order_of_isometry(Lf) == 2 +true +``` """ order_of_isometry(Lf::ZZLatWithIsom) = Lf.n @@ -60,6 +128,16 @@ Given a lattice with isometry $(L, f)$, return the rank of the underlying lattic `L`. See [`rank(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> rank(Lf) +5 +``` """ rank(Lf::ZZLatWithIsom) = rank(lattice(Lf))::Integer @@ -68,6 +146,16 @@ rank(Lf::ZZLatWithIsom) = rank(lattice(Lf))::Integer Given a lattice with isometry $(L, f)$, return the characteristic polynomial of the underlying isometry `f`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> factor(characteristic_polynomial(Lf)) +1 * (x + 1)^5 +``` """ characteristic_polynomial(Lf::ZZLatWithIsom) = characteristic_polynomial(isometry(Lf))::QQPolyRingElem @@ -76,6 +164,16 @@ characteristic_polynomial(Lf::ZZLatWithIsom) = characteristic_polynomial(isometr Given a lattice with isometry $(L, f)$, return the minimal polynomial of the underlying isometry `f`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> minimal_polynomial(Lf) +x + 1 +``` """ minimal_polynomial(Lf::ZZLatWithIsom) = minimal_polynomial(isometry(Lf))::QQPolyRingElem @@ -86,6 +184,20 @@ Given a lattice with isometry $(L, f)$, return the genus of the underlying lattice `L`. See [`genus(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true); + +julia> genus(Lf) +Genus symbol for integer lattices +Signatures: (5, 0, 0) +Local symbols: + Local genus symbol at 2: 1^-4 2^1_7 + Local genus symbol at 3: 1^-4 3^1 +``` """ genus(Lf::ZZLatWithIsom) = genus(lattice(Lf))::ZZGenus @@ -96,6 +208,33 @@ Given a lattice with isometry $(L, f)$, return the basis matrix of the underlyin lattice `L`. See [`basis_matrix(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ,5,5,[ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]) +[ 1 0 0 0 0] +[-1 -1 -1 -1 -1] +[ 0 0 0 0 1] +[ 0 0 0 1 0] +[ 0 0 1 0 0] + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> invariant_lattice(Lf); + +julia> I = invariant_lattice(Lf); + +julia> basis_matrix(I) +[1 0 0 0 0] +[0 0 1 0 1] +[0 0 0 1 0] +``` """ basis_matrix(Lf::ZZLatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix @@ -106,6 +245,20 @@ Given a lattice with isometry $(L, f)$, return the gram matrix of the underlying lattice `L` with respect to its basis matrix. See [`gram_matrix(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> gram_matrix(Lf) +[ 2 -1 0 0 0] +[-1 2 -1 0 0] +[ 0 -1 2 -1 0] +[ 0 0 -1 2 -1] +[ 0 0 0 -1 2] +``` """ gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix @@ -117,6 +270,26 @@ $L \otimes \mathbb{Q}$ of the underlying lattice `L` together with the underlying isometry of `L`. See [`rational_span(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> Vf = rational_span(Lf) +Quadratic space of dimension 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> typeof(Vf) +QuadSpaceWithIsom +``` """ rational_span(Lf::ZZLatWithIsom) = quadratic_space_with_isometry(rational_span(lattice(Lf)), isometry(Lf))::QuadSpaceWithIsom @@ -127,6 +300,16 @@ Given a lattice with isometry $(L, f)$, return the determinant of the underlying lattice `L`. See [`det(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> det(Lf) +6 +``` """ det(Lf::ZZLatWithIsom) = det(lattice(Lf))::QQFieldElem @@ -137,6 +320,16 @@ Given a lattice with isometry $(L, f)$, return the scale of the underlying lattice `L`. See [`scale(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> scale(Lf) +1 +``` """ scale(Lf::ZZLatWithIsom) = scale(lattice(Lf))::QQFieldElem @@ -147,6 +340,16 @@ Given a lattice with isometry $(L, f)$, return the norm of the underlying lattice `L`. See [`norm(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> norm(Lf) +2 +``` """ norm(Lf::ZZLatWithIsom) = norm(lattice(Lf))::QQFieldElem @@ -157,6 +360,16 @@ Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is positive definite. See [`is_positive_definite(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> is_positive_definite(Lf) +true +``` """ is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf))::Bool @@ -167,6 +380,16 @@ Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is negative definite. See [`is_positive_definite(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> is_negative_definite(Lf) +false +``` """ is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf))::Bool @@ -177,6 +400,16 @@ Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is definite. See [`is_definite(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> is_definite(Lf) +true +``` """ is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf))::Bool @@ -187,6 +420,16 @@ Given a positive definite lattice with isometry $(L, f)$, return the minimum of the underlying lattice `L`. See [`minimum(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> minimum(Lf) +2 +``` """ function minimum(Lf::ZZLatWithIsom) @req is_positive_definite(Lf) "Underlying lattice must be positive definite" @@ -200,6 +443,16 @@ Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is integral. See [`is_integral(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> is_integral(Lf) +true +``` """ is_integral(Lf::ZZLatWithIsom) = is_integral(lattice(Lf))::Bool @@ -210,6 +463,16 @@ Given a lattice with isometry $(L, f)$, return the degree of the underlying lattice `L`. See [`degree(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> degree(Lf) +5 +``` """ degree(Lf::ZZLatWithIsom) = degree(lattice(Lf))::Int @@ -220,6 +483,16 @@ Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is even. See [`is_even(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> is_even(Lf) +true +``` """ is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf))::Bool @@ -230,6 +503,16 @@ Given a lattice with isometry $(L, f)$, return the discriminant of the underlyin lattice `L`. See [`discriminant(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> discriminant(Lf) == det(Lf) == 6 +true +``` """ discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf))::QQFieldElem @@ -240,6 +523,16 @@ Given a lattice with isometry $(L, f)$, return the signature tuple of the underlying lattice `L`. See [`signature_tuple(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> signature_tuple(Lf) +(5, 0, 0) +``` """ signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} @@ -261,6 +554,65 @@ If `ambient_representation` is set to true, `f` is consider as an isometry of th ambient space of `L` and the induced isometry on `L` is automatically computed. Otherwise, an isometry of the ambient space of `L` is constructed, setting the identity on the complement of the rational span of `L` if it is not of full rank. + +# Examples + +The way we construct the lattice can have an influence on the isometry of the +ambient space we store. Indeed, if one mentions an isometry of the lattice, +this isometry will be extended by the identity on the orthogonal complement +of the rational span of the lattice. In the following example, `Lf` and `Lf2` are +the same object, but the isometry of their ambient space stored are totally +different (one has order 2, the other one is the identity). + +```jldoctest +julia> B = matrix(QQ, 3, 5, [1 0 0 0 0; + 0 0 1 0 1; + 0 0 0 1 0]); + +julia> G = matrix(QQ, 5, 5, [ 2 -1 0 0 0; + -1 2 -1 0 0; + 0 -1 2 -1 0; + 0 0 -1 2 -1; + 0 0 0 -1 2]); + +julia> L = integer_lattice(B, gram = G); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 3 and degree 5 + with isometry of finite order 1 + given by + [1 0 0] + [0 1 0] + [0 0 1] + +julia> ambient_isometry(Lf) +[ 1 0 0 0 0] +[-1 -1 -1 -1 -1] +[ 0 0 0 0 1] +[ 0 0 0 1 0] +[ 0 0 1 0 0] + +julia> Lf2 = integer_lattice_with_isometry(L, isometry(Lf), ambient_representation=false) +Integer lattice of rank 3 and degree 5 + with isometry of finite order 1 + given by + [1 0 0] + [0 1 0] + [0 0 1] + +julia> ambient_isometry(Lf2) +[1 0 0 0 0] +[0 1 0 0 0] +[0 0 1 0 0] +[0 0 0 1 0] +[0 0 0 0 1] +``` """ function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true, ambient_representation::Bool = true) @@ -306,6 +658,20 @@ Given a $\mathbb Z$-lattice `L` return the lattice with isometry pair $(L, f)$, where `f` corresponds to the identity mapping of `L`. If `neg` is set to `true`, then the isometry `f` is negative the identity of `L`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> Lf = integer_lattice_with_isometry(L, neg=true) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [-1 0 0 0 0] + [ 0 -1 0 0 0] + [ 0 0 -1 0 0] + [ 0 0 0 -1 0] + [ 0 0 0 0 -1]``` """ function integer_lattice_with_isometry(L::ZZLat; neg::Bool = false) d = degree(L) @@ -321,7 +687,35 @@ end Given a quadratic space with isometry $(V, f)$, return the full rank lattice `L` in `V` with basis the standard basis, together with the induced action of `f` -on `L` +on `L`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, QQ[2 -1; -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> f = matrix(QQ, 2, 2, [1 1; 0 -1]) +[1 1] +[0 -1] + +julia> Vf = quadratic_space_with_isometry(V, f) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [1 1] + [0 -1] + +julia> Lf = lattice(Vf) +Integer lattice of rank 2 and degree 2 + with isometry of finite order 2 + given by + [1 1] + [0 -1] +``` """ lattice(Vf::QuadSpaceWithIsom) = ZZLatWithIsom(Vf, lattice(space(Vf)), isometry(Vf), order_of_isometry(Vf))::ZZLatWithIsom @@ -334,6 +728,38 @@ Given a quadratic space with isometry $(V, f)$ and a matrix `B` generating a lattice `L` in `V`, if `L` is preserved under the action of `f`, return the lattice with isometry $(L, f_L)$ where $f_L$ is induced by the action of `f` on `L`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, QQ[ 2 -1 0 0 0; + -1 2 -1 0 0; + 0 -1 2 -1 0; + 0 0 -1 2 -1; + 0 0 0 -1 2]); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Vf = quadratic_space_with_isometry(V, f); + +julia> B = matrix(QQ,3,5,[1 0 0 0 0; + 0 0 1 0 1; + 0 0 0 1 0]) +[1 0 0 0 0] +[0 0 1 0 1] +[0 0 0 1 0] + +julia> lattice(Vf, B) +Integer lattice of rank 3 and degree 5 + with isometry of finite order 1 + given by + [1 0 0] + [0 1 0] + [0 0 1] +``` """ function lattice(Vf::QuadSpaceWithIsom, B::MatElem{<:RationalUnion}; isbasis::Bool = true, check::Bool = true) L = lattice(space(Vf), B, isbasis=isbasis, check=check) @@ -353,6 +779,37 @@ system of vectors in the ambient space `V` of `L`, if the lattice `M` in `V` def by `B` is preserved under the fixed isometry `g` of `V` inducing `f` on `L`, return the lattice with isometry pair $(M, f_M)$ where $f_M$ is induced by the action of `g` on `M`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> B = matrix(QQ,3,5,[1 0 0 0 0; + 0 0 1 0 1; + 0 0 0 1 0]) +[1 0 0 0 0] +[0 0 1 0 1] +[0 0 0 1 0] + +julia> lattice_in_same_ambient_space(Lf, B) +Integer lattice of rank 3 and degree 5 + with isometry of finite order 1 + given by + [1 0 0] + [0 1 0] + [0 0 1] + +julia> ambient_space(I) === ambient_space(Lf) +true +``` """ function lattice_in_same_ambient_space(L::ZZLatWithIsom, B::MatElem; check::Bool = true) @req !check || (rank(B) == nrows(B)) "The rows of B must define a free system of vectors" @@ -373,6 +830,53 @@ Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lattice with isometry $(L(a), f)$. See [`rescale(::ZZLat, ::RationalUnion)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5) +Integer lattice of rank 5 and degree 5 +with gram matrix +[ 2 -1 0 0 0] +[-1 2 -1 0 0] +[ 0 -1 2 -1 0] +[ 0 0 -1 2 -1] +[ 0 0 0 -1 2] + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> Lf2 = rescale(Lf, 1//2) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> lattice(Lf2) +Integer lattice of rank 5 and degree 5 +with gram matrix +[ 1 -1//2 0 0 0] +[-1//2 1 -1//2 0 0] +[ 0 -1//2 1 -1//2 0] +[ 0 0 -1//2 1 -1//2] +[ 0 0 0 -1//2 1] +``` """ function rescale(Lf::ZZLatWithIsom, a::Hecke.RationalUnion) return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf), check=false) @@ -383,6 +887,37 @@ end Given a lattice with isometry $(L, f)$ and an integer $n$, return the pair $(L, f^n)$. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> Lf^0 +Integer lattice of rank 5 and degree 5 + with isometry of finite order 1 + given by + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + [0 0 0 0 1] +``` """ function Base.:^(Lf::ZZLatWithIsom, n::Int) return lattice(ambient_space(Lf)^n, basis_matrix(Lf)) @@ -397,6 +932,40 @@ isometry $(L^{\vee}, h)$ where $L^{\vee}$ is the dual of `L` in $(V, \Phi)$ and `h` is induced by `g`. See [`dual(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> Lfv = dual(Lf) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [1 -1 0 0 0] + [0 -1 0 0 0] + [0 -1 0 0 1] + [0 -1 0 1 0] + [0 -1 1 0 0] + +julia> ambient_space(Lfv) == ambient_space(Lf) +true +``` """ function dual(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" @@ -414,6 +983,40 @@ Note that matrix representing the action of `f` on `L` changes but the global ac on the ambient space of `L` stays the same. See [`lll(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> Lf2 = lll(Lf) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 0 0 0 -1] + [-1 0 0 -1 0] + [-1 0 -1 0 0] + [-1 -1 0 0 0] + +julia> ambient_space(Lf2) == ambient_space(Lf) +true +``` """ function lll(Lf::ZZLatWithIsom; same_ambient::Bool = true) L2 = lll(lattice(Lf), same_ambient = same_ambient) @@ -441,6 +1044,73 @@ If one wants to obtain $(L, f)$ as a direct product with the projections $L \to one should call `direct_product(x)`. If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ and the projections $L \to L_i$, one should call `biproduct(x)`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> g = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> Lg = integer_lattice_with_isometry(L, g) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 5 + given by + [1 1 1 1 1] + [0 -1 -1 -1 -1] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + +julia> Lh, inj = direct_sum(Lf, Lg) +(Integer lattice with isometry of finite order 10, AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 5 +Codomain: +========= +Quadratic space of dimension 10, Map with following data +Domain: +======= +Quadratic space of dimension 5 +Codomain: +========= +Quadratic space of dimension 10]) + +julia> Lh +Integer lattice of rank 10 and degree 10 + with isometry of finite order 10 + given by + [ 1 0 0 0 0 0 0 0 0 0] + [-1 -1 -1 -1 -1 0 0 0 0 0] + [ 0 0 0 0 1 0 0 0 0 0] + [ 0 0 0 1 0 0 0 0 0 0] + [ 0 0 1 0 0 0 0 0 0 0] + [ 0 0 0 0 0 1 1 1 1 1] + [ 0 0 0 0 0 0 -1 -1 -1 -1] + [ 0 0 0 0 0 0 1 0 0 0] + [ 0 0 0 0 0 0 0 1 0 0] + [ 0 0 0 0 0 0 0 0 1 0] +``` """ function direct_sum(x::Vector{ZZLatWithIsom}) Vf, inj = direct_sum(ambient_space.(x)) @@ -467,6 +1137,73 @@ If one wants to obtain $(L, f)$ as a direct sum with the injections $L_i \to L$, one should call `direct_sum(x)`. If one wants to obtain $(L, f)$ as a biproduct with the injections $L_i \to L$ and the projections $L \to L_i$, one should call `biproduct(x)`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> g = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> Lg = integer_lattice_with_isometry(L, g) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 5 + given by + [1 1 1 1 1] + [0 -1 -1 -1 -1] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + +julia> Lh, proj = direct_product(Lf, Lg) +(Integer lattice with isometry of finite order 10, AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 10 +Codomain: +========= +Quadratic space of dimension 5, Map with following data +Domain: +======= +Quadratic space of dimension 10 +Codomain: +========= +Quadratic space of dimension 5]) + +julia> Lh +Integer lattice of rank 10 and degree 10 + with isometry of finite order 10 + given by + [ 1 0 0 0 0 0 0 0 0 0] + [-1 -1 -1 -1 -1 0 0 0 0 0] + [ 0 0 0 0 1 0 0 0 0 0] + [ 0 0 0 1 0 0 0 0 0 0] + [ 0 0 1 0 0 0 0 0 0 0] + [ 0 0 0 0 0 1 1 1 1 1] + [ 0 0 0 0 0 0 -1 -1 -1 -1] + [ 0 0 0 0 0 0 1 0 0 0] + [ 0 0 0 0 0 0 0 1 0 0] + [ 0 0 0 0 0 0 0 0 1 0] +``` """ function direct_product(x::Vector{ZZLatWithIsom}) Vf, proj = direct_product(ambient_space.(x)) @@ -496,6 +1233,99 @@ If one wants to obtain $(L, f)$ as a direct sum with the injections $L_i \to L$, one should call `direct_sum(x)`. If one wants to obtain $(L, f)$ as a direct product with the projections $L \to L_i$, one should call `direct_product(x)`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> g = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 2 + given by + [ 1 0 0 0 0] + [-1 -1 -1 -1 -1] + [ 0 0 0 0 1] + [ 0 0 0 1 0] + [ 0 0 1 0 0] + +julia> Lg = integer_lattice_with_isometry(L, g) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 5 + given by + [1 1 1 1 1] + [0 -1 -1 -1 -1] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + +julia> Lh, inj, proj = biproduct(Lf, Lg) +(Integer lattice with isometry of finite order 10, AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 5 +Codomain: +========= +Quadratic space of dimension 10, Map with following data +Domain: +======= +Quadratic space of dimension 5 +Codomain: +========= +Quadratic space of dimension 10], AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 10 +Codomain: +========= +Quadratic space of dimension 5, Map with following data +Domain: +======= +Quadratic space of dimension 10 +Codomain: +========= +Quadratic space of dimension 5]) + +julia> Lh +Integer lattice of rank 10 and degree 10 + with isometry of finite order 10 + given by + [ 1 0 0 0 0 0 0 0 0 0] + [-1 -1 -1 -1 -1 0 0 0 0 0] + [ 0 0 0 0 1 0 0 0 0 0] + [ 0 0 0 1 0 0 0 0 0 0] + [ 0 0 1 0 0 0 0 0 0 0] + [ 0 0 0 0 0 1 1 1 1 1] + [ 0 0 0 0 0 0 -1 -1 -1 -1] + [ 0 0 0 0 0 0 1 0 0 0] + [ 0 0 0 0 0 0 0 1 0 0] + [ 0 0 0 0 0 0 0 0 1 0] + +julia> matrix(compose(inj[1], proj[1])) +[1 0 0 0 0] +[0 1 0 0 0] +[0 0 1 0 0] +[0 0 0 1 0] +[0 0 0 0 1] + +julia> matrix(compose(inj[1], proj[2])) +[0 0 0 0 0] +[0 0 0 0 0] +[0 0 0 0 0] +[0 0 0 0 0] +[0 0 0 0 0] +``` """ function biproduct(x::Vector{ZZLatWithIsom}) Vf, inj, proj = biproduct(ambient_space.(x)) @@ -535,6 +1365,33 @@ the underlying isometry `f` is irreducible. Note that if $(L, f)$ is of hermitian type with `f` of minimal polynomial $\chi$, then `L` can be seen as a hermitian lattice over the order $\mathbb{Z}[\chi]$. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f) +Integer lattice of rank 5 and degree 5 + with isometry of finite order 5 + given by + [1 1 1 1 1] + [0 -1 -1 -1 -1] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + +julia> is_of_hermitian_type(Lf) +false + +julia> is_of_hermitian_type(coinvariant_lattice(Lf)) +true +``` """ function is_of_hermitian_type(Lf::ZZLatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" @@ -552,6 +1409,49 @@ underlying lattice `L` over the equation order of the minimal polynomial of If it exists, the hermitian structure is stored. For now, we only cover the case where the equation order is maximal (which is always the case when the order is finite, for instance, since the minimal polynomial is cyclotomic). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> M = coinvariant_lattice(Lf) +Integer lattice of rank 4 and degree 5 + with isometry of finite order 5 + given by + [-1 -1 -1 -1] + [ 1 0 0 0] + [ 0 1 0 0] + [ 0 0 1 0] + +julia> H = hermitian_structure(M) +Hermitian lattice of rank 1 and degree 1 + over relative maximal order of Relative number field of degree 2 over maximal real subfield of cyclotomic field of order 5 + with pseudo-basis + (1, 1//1 * <1, 1>) + (z_5, 1//1 * <1, 1>) + +julia> res = get_attribute(M, :transfer_data) +Map of change of scalars + from quadratic space of dimension 4 + to hermitian space of dimension 1 + +julia> M2, f2 = trace_lattice_with_isometry(H, res) +(Integer lattice of rank 4 and degree 4, [-1 -1 -1 -1; 1 0 0 0; 0 1 0 0; 0 0 1 0]) + +julia> genus(M) == genus(M2) # One class in this genus, so they are isometric +true + +julia> f2 == isometry(M) +true +``` """ @attr HermLat function hermitian_structure(Lf::ZZLatWithIsom) @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" @@ -577,6 +1477,47 @@ of the underlying lattice `L` as well as this image of the underlying isometry `f` inside $O(q)$. See [`discriminant_group(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> qL, qf = discriminant_group(Lf) +(Finite quadratic module: Z/6 -> Q/2Z, [1]) + +julia> qL +Finite quadratic module + over integer ring +Abelian group: Z/6 +Bilinear value module: Q/Z +Quadratic value module: Q/2Z +Gram matrix quadratic form: +[5//6] + +julia> qf +Isometry of Finite quadratic module: Z/6 -> Q/2Z defined by +[1] + +julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; + -1 -1 -1 -1 -1; + 0 0 0 0 1; + 0 0 0 1 0; + 0 0 1 0 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> discriminant_group(Lf)[2] +Isometry of Finite quadratic module: Z/6 -> Q/2Z defined by +[5] +``` """ function discriminant_group(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" @@ -596,6 +1537,21 @@ denotes the discriminant group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. See [`image_in_Oq(::ZZLat)`](@ref). + +# Examples +```jldoctest +julia> L = root_lattice(:A,2); + +julia> f = matrix(QQ, 2, 2, [1 1; 0 -1]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> G = image_centralizer_in_Oq(Lf) +Group of isometries of Finite quadratic module: Z/3 -> Q/2Z generated by 2 elements + +julia> order(G) +2 +``` """ @attr AutomorphismGroup{TorQuadModule} function image_centralizer_in_Oq(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" @@ -662,6 +1618,26 @@ In this context, if we denote $z$ a primitive `n`-th root of unity, where `n` is the order of `f`, then for each $1 \leq i \leq n/2$ such that $(i, n) = 1$, the $i$-th signature of $(L, f)$ is given by the signatures of the real quadratic form $\Ker(f + f^{-1} - z^i - z^{-i})$. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> M = coinvariant_lattice(Lf); + +julia> signatures(M) +Dict{Integer, Tuple{Int64, Int64}} with 2 entries: + 2 => (2, 0) + 1 => (2, 0) +``` """ function signatures(Lf::ZZLatWithIsom) @req is_cyclotomic_polynomial(minimal_polynomial(Lf)) "Lf must be of finite hermitian type" @@ -700,6 +1676,43 @@ end Given a lattice with isometry $(L, f)$ and a polynomial `p` with rational coefficients, return the sublattice $M := \ker(p(f))$ of the underlying lattice `L` with isometry `f`, together with the restriction $f_{\mid M}$. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> Zx,x = ZZ["x"] +(Univariate polynomial ring in x over ZZ, x) + +julia> mf = minimal_polynomial(Lf) +x^5 - 1 + +julia> factor(mf) +1 * (x - 1) * (x^4 + x^3 + x^2 + x + 1) + +julia> kernel_lattice(Lf, x-1) +Integer lattice of rank 1 and degree 5 + with isometry of finite order 1 + given by + [1] + +julia> kernel_lattice(Lf, cyclotomic_polynomial(5)) +Integer lattice of rank 4 and degree 5 + with isometry of finite order 5 + given by + [-1 -1 -1 -1] + [ 1 0 0 0] + [ 0 1 0 0] + [ 0 0 1 0] +``` """ kernel_lattice(Lf::ZZLatWithIsom, p::Union{ZZPolyRingElem, QQPolyRingElem}) @@ -727,6 +1740,34 @@ kernel_lattice(Lf::ZZLatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change Given a lattice with isometry $(L, f)$ and an integer `l`, return the kernel lattice of $(L, f)$ associated to the `l`-th cyclotomic polynomial. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> kernel_lattice(Lf, 5) +Integer lattice of rank 4 and degree 5 + with isometry of finite order 5 + given by + [-1 -1 -1 -1] + [ 1 0 0 0] + [ 0 1 0 0] + [ 0 0 1 0] + +julia> kernel_lattice(Lf, 1) +Integer lattice of rank 1 and degree 5 + with isometry of finite order 1 + given by + [1] +``` """ function kernel_lattice(Lf::ZZLatWithIsom, l::Integer) @req _divides(order_of_isometry(Lf), l)[1] "l must divide the order of the underlying isometry" @@ -739,7 +1780,26 @@ end Given a lattice with isometry $(L, f)$, return the invariant lattice $L^f$ of $(L, f)$ together with the restriction of `f` to $L^f$ (which is the identity -in this case) +in this case). + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> invariant_lattice(Lf) +Integer lattice of rank 1 and degree 5 + with isometry of finite order 1 + given by + [1] +``` """ invariant_lattice(Lf::ZZLatWithIsom) = kernel_lattice(Lf, 1) @@ -753,6 +1813,18 @@ return the invariant sublattice $L^G$ of `L`. If `ambient_representation` is set to true, the isometries in `G` are seen as isometries of the ambient quadratic space of `L` preserving `L`. Otherwise, they are considered as honnest isometries of `L`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,2); + +julia> G = isometry_group(L); + +julia> invariant_lattice(L, G) +Integer lattice of rank 0 and degree 2 +with gram matrix +0 by 0 empty matrix +``` """ function invariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) return invariant_lattice(L, matrix.(gens(G)), ambient_representation = ambient_representation) @@ -766,6 +1838,28 @@ $(L, f)$ together with the restriction of `f` to $L_f$. The coinvariant lattice $L_f$ of $(L, f)$ is the orthogonal complement in `L` of the invariant lattice $L_f$. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> coinvariant_lattice(Lf) +Integer lattice of rank 4 and degree 5 + with isometry of finite order 5 + given by + [-1 -1 -1 -1] + [ 1 0 0 0] + [ 0 1 0 0] + [ 0 0 1 0] +``` """ function coinvariant_lattice(Lf::ZZLatWithIsom) chi = minimal_polynomial(Lf) @@ -789,6 +1883,22 @@ of isometries of $L_G$ induced by the action of $G$. If `ambient_representation` is set to true, the isometries in `G` and `H` are seen as isometries of the ambient quadratic space of `L` preserving `L`. Otherwise, they are considered as honnest isometries of `L`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,2); + +julia> G = isometry_group(L); + +julia> L2, G2 = coinvariant_lattice(L, G) +(Integer lattice of rank 2 and degree 2, Matrix group of degree 2 over Rational field) + +julia> L == L2 +true + +julia> G == G2 +true +``` """ function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) F = invariant_lattice(L, G, ambient_representation = ambient_representation) @@ -820,6 +1930,28 @@ action of $G$. If `ambient_representation` is set to true, the isometries in `G` and `H` are seen as isometries of the ambient quadratic space of `L` preserving `L`. Otherwise, they are considered as honnest isometries of `L`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,2); + +julia> G = isometry_group(L); + +julia> Gsub, _ = sub(G, [gens(G)[end]]); + +julia> F, C, G2 = invariant_coinvariant_pair(L, Gsub) +(Integer lattice of rank 1 and degree 2, Integer lattice of rank 1 and degree 2, Matrix group of degree 2 over Rational field) + +julia> F +Integer lattice of rank 1 and degree 2 +with gram matrix +[2] + +julia> C +Integer lattice of rank 1 and degree 2 +with gram matrix +[6] +``` """ function invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) F = invariant_lattice(L, G, ambient_representation = ambient_representation) @@ -856,6 +1988,27 @@ the `k`-type of $(L, f)$ is the tuple $(H_k, A_K)$ consisting of the genus $H_k$ of the lattice $\Ker(\Phi_k(f))$ viewed as a hermitian $\mathbb{Z}[\zeta_k]$- lattice (so a $\mathbb{Z}$-lattice for k= 1, 2) and of the genus $A_k$ of the $\mathbb{Z}$-lattice $\Ker(f^k-1)$. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> t = type(Lf) +Dict{Integer, Tuple} with 2 entries: + 5 => (Genus symbol for hermitian lattices of rank 1 over relative maximal order of Relative number field… + 1 => (Genus symbol: II_(1, 0) 2^1_7 3^1 5^1, Genus symbol: II_(1, 0) 2^1_7 3^1 5^1) + +julia> genus(invariant_lattice(Lf)) == t[1][1] +true +``` """ @attr function type(Lf::ZZLatWithIsom) L = lattice(Lf) @@ -881,6 +2034,24 @@ end is_of_type(Lf::ZZLatWithIsom, t::Dict) -> Bool Given a lattice with isometry $(L, f)$, return whether $(L, f)$ is of type `t`. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> t = type(Lf); + +julia> is_of_type(Lf, t) +true +``` """ function is_of_type(L::ZZLatWithIsom, t::Dict) @req is_finite(order_of_isometry(L)) "Type is defined only for finite order isometries" @@ -904,6 +2075,24 @@ end Given two lattices with isometry $(L, f)$ and $(M, g)$, return whether they are of the same type. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> M = coinvariant_lattice(Lf); + +julia> is_of_same_type(Lf, M) +false +``` """ function is_of_same_type(L::ZZLatWithIsom, M::ZZLatWithIsom) @req is_finite(order_of_isometry(L)*order_of_isometry(M)) "Type is defined only for finite order isometries" @@ -917,6 +2106,27 @@ end Given a type `t` of lattices with isometry, return whether `t` is hermitian, i.e. whether it defines the type of a hermitian lattice with isometry. + +# Examples +```jldoctest +julia> L = root_lattice(:A,5); + +julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; + 0 -1 -1 -1 -1; + 0 1 0 0 0; + 0 0 1 0 0; + 0 0 0 1 0]); + +julia> Lf = integer_lattice_with_isometry(L, f); + +julia> M = coinvariant_lattice(Lf); + +julia> is_hermitian(type(Lf)) +false + +julia> is_hermitian(type(M)) +true +``` """ function is_hermitian(t::Dict) ke = collect(keys(t)) diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 4ce6c62e1390..5f05aba16494 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -9,6 +9,16 @@ space(Vf::QuadSpaceWithIsom) -> QuadSpace Given a quadratic space with isometry $(V, f)$, return the underlying space `V`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> space(Vf) === V +true +``` """ space(Vf::QuadSpaceWithIsom) = Vf.V @@ -17,6 +27,17 @@ space(Vf::QuadSpaceWithIsom) = Vf.V Given a quadratic space with isometry $(V, f)$, return the underlying isometry `f`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> isometry(Vf) +[-1 0] +[ 0 -1] +``` """ isometry(Vf::QuadSpaceWithIsom) = Vf.f @@ -24,7 +45,17 @@ isometry(Vf::QuadSpaceWithIsom) = Vf.f order_of_isometry(Vf::QuadSpaceWithIsom) -> IntExt Given a quadratic space with isometry $(V, f)$, return the order of the -underlying isometry `f` +underlying isometry `f`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> order_of_isometry(Vf) == 2 +true +``` """ order_of_isometry(Vf::QuadSpaceWithIsom) = Vf.n @@ -41,6 +72,16 @@ Given a quadratic space with isometry $(V, f)$, return the rank of the underlyin space `V`. See ['rank(::QuadSpace)'](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> rank(Vf) == 2 +true +``` """ rank(Vf::QuadSpaceWithIsom) = rank(space(Vf))::Integer @@ -51,6 +92,16 @@ Given a quadratic space with isometry $(V, f)$, return the dimension of the underlying space of `V`. See [`dim(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> dim(Vf) == 2 +true +``` """ dim(Vf::QuadSpaceWithIsom) = dim(space(Vf))::Integer @@ -59,6 +110,16 @@ dim(Vf::QuadSpaceWithIsom) = dim(space(Vf))::Integer Given a quadratic space with isometry $(V, f)$, return the characteristic polynomial of the underlying isometry `f`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> characteristic_polynomial(Vf) +x^2 + 2*x + 1 +``` """ characteristic_polynomial(Vf::QuadSpaceWithIsom) = characteristic_polynomial(isometry(Vf))::QQPolyRingElem @@ -67,6 +128,16 @@ characteristic_polynomial(Vf::QuadSpaceWithIsom) = characteristic_polynomial(iso Given a quadratic space with isometry $(V, f)$, return the minimal polynomial of the underlying isometry `f`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> minimal_polynomial(Vf) +x + 1 +``` """ minimal_polynomial(Vf) = minimal_polynomial(isometry(Vf))::QQPolyRingElem @@ -77,6 +148,16 @@ Given a quadratic space with isometry $(V, f)$, return the Gram matrix of the underlying space `V` with respect to its standard basis. See [`gram_matrix(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> is_one(gram_matrix(Vf)) +true +``` """ gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf))::QQMatrix @@ -87,6 +168,16 @@ Given a quadratic space with isometry $(V, f)$, return the determinant of the underlying space `V`. See [`det(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> is_one(det(Vf)) +true +``` """ det(Vf::QuadSpaceWithIsom) = det(space(Vf))::QQFieldElem @@ -97,6 +188,16 @@ Given a quadratic space with isometry $(V, f)$, return the discriminant of the underlying space `V`. See [`discriminant(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> discriminant(Vf) +-1 +``` """ discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf))::QQFieldElem @@ -107,6 +208,16 @@ Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is positive definite. See [`is_positive_definite(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> is_positive_definite(Vf) +true +``` """ is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf))::Bool @@ -117,6 +228,16 @@ Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is negative definite. See [`is_negative_definite(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> is_negative_definite(Vf) +false +``` """ is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf))::Bool @@ -127,6 +248,16 @@ Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is definite. See [`is_definite(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> is_definite(Vf) +true +``` """ is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf))::Bool @@ -137,6 +268,18 @@ Given a quadratic space with isometry $(V, f)$, return the diagonal of the underlying space `V`. See [`diagonal(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> diagonal(Vf) +2-element Vector{QQFieldElem}: + 1 + 1 +``` """ diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf))::Vector{QQFieldElem} @@ -147,6 +290,16 @@ Given a quadratic space with isometry $(V, f)$, return the signature tuple of the underlying space `V`. See [`signature_tuple(::QuadSpace)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, 2); + +julia> Vf = quadratic_space_with_isometry(V, neg = true); + +julia> signature_tuple(Vf) +(2, 0, 0) +``` """ signature_tuple(Vf::QuadSpaceWithIsom) = signature_tuple(space(Vf))::Tuple{Int, Int, Int} @@ -163,6 +316,29 @@ signature_tuple(Vf::QuadSpaceWithIsom) = signature_tuple(space(Vf))::Tuple{Int, Given a quadratic space `V` and a matrix `f`, if `f` defines an isometry of `V` of order `n` (possibly infinite), return the corresponding quadratic space with isometry pair $(V, f)$. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, QQ[ 2 -1; + -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> f = matrix(QQ, 2, 2, [1 1; + 0 -1]) +[1 1] +[0 -1] + +julia> Vf = quadratic_space_with_isometry(V, f) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [1 1] + [0 -1] +``` """ function quadratic_space_with_isometry(V::Hecke.QuadSpace, f::QQMatrix; check::Bool = true) @@ -171,6 +347,7 @@ function quadratic_space_with_isometry(V::Hecke.QuadSpace, f::QQMatrix; end if check + @req !is_zero(f) "f must be non-zero" @req det(f) != 0 "Matrix must be invertible" @req f*gram_matrix(V)*transpose(f) == gram_matrix(V) "Matrix does not define an isometry of the given quadratic space" end @@ -186,6 +363,24 @@ Given a quadratic space `V`, return the quadratic space with isometry pair $(V, where `f` is represented by the identity matrix. If `neg` is set to true, then the isometry `f` is negative the identity on `V`. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, QQ[ 2 -1; + -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> Vf = quadratic_space_with_isometry(V) +Quadratic space of dimension 2 + with isometry of finite order 1 + given by + [1 0] + [0 1] +``` """ function quadratic_space_with_isometry(V::Hecke.QuadSpace; neg::Bool = false) f = identity_matrix(QQ, dim(V)) @@ -206,6 +401,38 @@ Given a quadratic space with isometry $(V, f)$, return the pair $(V^a, f$) where $V^a$ is the same space as `V` with the associated quadratic form rescaled by `a`. See [`rescale(::QuadSpace, ::Hecke.RationalUnion)`](@ref). + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, QQ[ 2 -1; + -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> Vf = quadratic_space_with_isometry(V) +Quadratic space of dimension 2 + with isometry of finite order 1 + given by + [1 0] + [0 1] + +julia> Vf2 = rescale(Vf, 1//2) +Quadratic space of dimension 2 + with isometry of finite order 1 + given by + [1 0] + [0 1] + +julia> space(Vf2) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 1 -1//2] +[-1//2 1] +``` """ function rescale(Vf::QuadSpaceWithIsom, a::Hecke.RationalUnion) return quadratic_space_with_isometry(rescale(space(Vf), a), isometry(Vf), check = false) @@ -216,6 +443,36 @@ end Given a quadratic space with isometry $(V, f)$ and an integer $n$, return the pair $(V, f^n)$. + +# Examples +```jldoctest +julia> V = quadratic_space(QQ, QQ[ 2 -1; + -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> f = matrix(QQ, 2, 2, [1 1; + 0 -1]) +[1 1] +[0 -1] + +julia> Vf = quadratic_space_with_isometry(V, f) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [1 1] + [0 -1] + +julia> Vf^2 +Quadratic space of dimension 2 + with isometry of finite order 1 + given by + [1 0] + [0 1] +``` """ function Base.:^(Vf::QuadSpaceWithIsom, n::Int) return quadratic_space_with_isometry(space(Vf), isometry(Vf)^n, check=false) @@ -236,6 +493,77 @@ If one wants to obtain $(V, f)$ as a direct product with the projections $V \to one should call `direct_product(x)`. If one wants to obtain $(V, f)$ as a biproduct with the injections $V_i \to V$ and the projections $V \to V_i$, one should call `biproduct(x)`. + +# Examples +```jldoctest +julia> V1 = quadratic_space(QQ, QQ[2 5; + 5 6]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[2 5] +[5 6] + +julia> Vf1 = quadratic_space_with_isometry(V1, neg=true) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [-1 0] + [ 0 -1] + +julia> V2 = quadratic_space(QQ, QQ[ 2 -1; + -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> f = matrix(QQ, 2, 2, [1 1; + 0 -1]) +[1 1] +[0 -1] + +julia> Vf2 = quadratic_space_with_isometry(V2, f) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [1 1] + [0 -1] + +julia> Vf3, inj = direct_sum(Vf1, Vf2) +(Quadratic space with isometry of finite order 2, AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 2 +Codomain: +========= +Quadratic space of dimension 4, Map with following data +Domain: +======= +Quadratic space of dimension 2 +Codomain: +========= +Quadratic space of dimension 4]) + +julia> Vf3 +Quadratic space of dimension 4 + with isometry of finite order 2 + given by + [-1 0 0 0] + [ 0 -1 0 0] + [ 0 0 1 1] + [ 0 0 0 -1] + +julia> space(Vf3) +Quadratic space of dimension 4 + over rational field +with gram matrix +[2 5 0 0] +[5 6 0 0] +[0 0 2 -1] +[0 0 -1 2] +``` """ function direct_sum(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj = direct_sum(space.(x)) @@ -260,6 +588,77 @@ If one wants to obtain $(V, f)$ as a direct sum with the injections $V_i \to V$, one should call `direct_sum(x)`. If one wants to obtain $(V, f)$ as a biproduct with the injections $V_i \to V$ and the projections $V \to V_i$, one should call `biproduct(x)`. + +# Examples +```jldoctest +julia> V1 = quadratic_space(QQ, QQ[2 5; + 5 6]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[2 5] +[5 6] + +julia> Vf1 = quadratic_space_with_isometry(V1, neg=true) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [-1 0] + [ 0 -1] + +julia> V2 = quadratic_space(QQ, QQ[ 2 -1; + -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> f = matrix(QQ, 2, 2, [1 1; + 0 -1]) +[1 1] +[0 -1] + +julia> Vf2 = quadratic_space_with_isometry(V2, f) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [1 1] + [0 -1] + +julia> Vf3, proj = direct_product(Vf1, Vf2) +(Quadratic space with isometry of finite order 2, AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 4 +Codomain: +========= +Quadratic space of dimension 2, Map with following data +Domain: +======= +Quadratic space of dimension 4 +Codomain: +========= +Quadratic space of dimension 2]) + +julia> Vf3 +Quadratic space of dimension 4 + with isometry of finite order 2 + given by + [-1 0 0 0] + [ 0 -1 0 0] + [ 0 0 1 1] + [ 0 0 0 -1] + +julia> space(Vf3) +Quadratic space of dimension 4 + over rational field +with gram matrix +[2 5 0 0] +[5 6 0 0] +[0 0 2 -1] +[0 0 -1 2] +``` """ function direct_product(x::Vector{T}) where T <: QuadSpaceWithIsom V, proj = direct_product(space.(x)) @@ -285,6 +684,97 @@ If one wants to obtain $(V, f)$ as a direct sum with the injections $V_i \to V$, one should call `direct_sum(x)`. If one wants to obtain $(V, f)$ as a direct product with the projections $V \to V_i$, one should call `direct_product(x)`. + +# Examples +```jldoctest +julia> V1 = quadratic_space(QQ, QQ[2 5; + 5 6]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[2 5] +[5 6] + +julia> Vf1 = quadratic_space_with_isometry(V1, neg=true) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [-1 0] + [ 0 -1] + +julia> V2 = quadratic_space(QQ, QQ[ 2 -1; + -1 2]) +Quadratic space of dimension 2 + over rational field +with gram matrix +[ 2 -1] +[-1 2] + +julia> f = matrix(QQ, 2, 2, [1 1; + 0 -1]) +[1 1] +[0 -1] + +julia> Vf2 = quadratic_space_with_isometry(V2, f) +Quadratic space of dimension 2 + with isometry of finite order 2 + given by + [1 1] + [0 -1] + +julia> Vf3, inj, proj = biproduct(Vf1, Vf2) +(Quadratic space with isometry of finite order 2, AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 2 +Codomain: +========= +Quadratic space of dimension 4, Map with following data +Domain: +======= +Quadratic space of dimension 2 +Codomain: +========= +Quadratic space of dimension 4], AbstractSpaceMor[Map with following data +Domain: +======= +Quadratic space of dimension 4 +Codomain: +========= +Quadratic space of dimension 2, Map with following data +Domain: +======= +Quadratic space of dimension 4 +Codomain: +========= +Quadratic space of dimension 2]) + +julia> Vf3 +Quadratic space of dimension 4 + with isometry of finite order 2 + given by + [-1 0 0 0] + [ 0 -1 0 0] + [ 0 0 1 1] + [ 0 0 0 -1] + +julia> space(Vf3) +Quadratic space of dimension 4 + over rational field +with gram matrix +[2 5 0 0] +[5 6 0 0] +[0 0 2 -1] +[0 0 -1 2] + +julia> matrix(compose(inj[1], proj[1])) +[1 0] +[0 1] + +julia> matrix(compose(inj[1], proj[2])) +[0 0] +[0 0] +``` """ function biproduct(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj, proj = biproduct(space.(x)) diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index 9cec1c8afea7..f5796849a3f2 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -1,4 +1,4 @@ -using test +using Test using Oscar @testset "Spaces with isometry" begin @@ -9,9 +9,9 @@ using Oscar @test order_of_isometry(Vf) == 2 @test space(Vf) === V - for func in [rank, dim. gram_matrix, det, discriminant, is_positive_definite, + for func in [rank, dim, gram_matrix, det, discriminant, is_positive_definite, is_negative_definite, is_definite, diagonal, signature_tuple] - k = @inferred func(Vf) + k = func(Vf) @test k == func(V) end @@ -34,7 +34,7 @@ using Oscar @test Vf != quadratic_space_with_isometry(V, neg=true) @test length(unique([Vf, quadratic_space_with_isometry(V, isometry(Vf))])) == 1 - @test Vf^(order_of_isometry(V)+1) == Vf + @test Vf^(order_of_isometry(Vf)+1) == Vf V = quadratic_space(QQ, matrix(QQ, 0, 0, [])) Vf = @inferred quadratic_space_with_isometry(V) @@ -43,7 +43,7 @@ end @testset "Lattices with isometry" begin A4 = root_lattice(:A, 4) - agg = = automorphism_group_generators(A4, ambient_representation = false) + agg = automorphism_group_generators(A4, ambient_representation = false) agg_ambient = automorphism_group_generators(A4, ambient_representation = true) f = rand(agg) g_ambient = rand(agg_ambient) @@ -59,7 +59,7 @@ end @test isone(order_of_isometry(L)) @test order(image_centralizer_in_Oq(L)) == 2 - for func in [rank, genus, ambient_space, basis_matrix, is_positive_definite, + for func in [rank, genus, basis_matrix, is_positive_definite, gram_matrix, det, scale, norm, is_integral, is_negative_definite, degree, is_even, discriminant, signature_tuple, is_definite] k = @inferred func(L) @@ -74,8 +74,8 @@ end @test evaluate(minimal_polynomial(L), 1) == 0 @test evaluate(characteristic_polynomial(L), 0) == 1 - @test minimum(rescale(L, -1)) == 2 - @test !is_positive_definite(L) + @test minimum(L) == 2 + @test is_positive_definite(L) @test is_definite(L) nf = multiplicative_order(f) @@ -99,10 +99,10 @@ end @test order_of_isometry(biproduct(L2, L3)[1]) == lcm(order_of_isometry(L2), order_of_isometry(L3)) @test rank(direct_sum(L2, L3)[1]) == rank(L2) + rank(L3) - @test genus(direct_product(L2, L3)[1]) == genus(L2) + genus(L3) + @test genus(direct_product(L2, L3)[1]) == direct_sum(genus(L2), genus(L3)) L5 = @inferred lattice(ambient_space(L2)) - @test (L2 == L5) == (is_one(f)) + @test (L2 == L5) B = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 1 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 0 0 1 0; 0 0 0 0 0 0 0 1]); G = matrix(QQ, 8, 8, [-4 2 0 0 0 0 0 0; 2 -4 2 0 0 0 0 0; 0 2 -4 2 0 0 0 2; 0 0 2 -4 2 0 0 0; 0 0 0 2 -4 2 0 0; 0 0 0 0 2 -4 2 0; 0 0 0 0 0 2 -4 0; 0 0 2 0 0 0 0 -4]); @@ -115,11 +115,11 @@ end M = @inferred coinvariant_lattice(Lf) @test is_of_hermitian_type(M) - H = @inferred hermitian_structure(M) + H = hermitian_structure(M) @test H isa HermLat qL, fqL = @inferred discriminant_group(Lf) - @test divides(order_of_isometry(M), order(fqL))[1] + @test divides(ZZ(order_of_isometry(M)), order(fqL))[1] @test is_elementary(qL, 2) S = @inferred collect(values(signatures(M))) @@ -128,7 +128,7 @@ end @test rank(invariant_lattice(M)) == 0 @test rank(invariant_lattice(Lf)) == rank(Lf) - rank(M) - t = @inferred type(Lf) + t = type(Lf) @test length(collect(keys(t))) == 2 @test is_of_type(Lf, t) @test !is_of_same_type(Lf, M) @@ -143,6 +143,8 @@ end Lf = integer_lattice_with_isometry(L, f); GL = image_centralizer_in_Oq(Lf) @test order(GL) == 72 + GL = image_centralizer_in_Oq(rescale(Lf, -14)) + @test order(GL) == 870912 B = matrix(QQ, 4, 6, [0 0 0 0 -2 1; 0 0 0 0 3 -4; 0 0 1 0 -1 0; 0 0 0 1 0 -1]); G = matrix(QQ, 6, 6, [2 1 0 0 0 0; 1 -2 0 0 0 0; 0 0 2//5 4//5 2//5 -1//5; 0 0 4//5 -2//5 -1//5 3//5; 0 0 2//5 -1//5 2//5 4//5; 0 0 -1//5 3//5 4//5 -2//5]); @@ -154,21 +156,21 @@ end E8 = root_lattice(:E, 8) OE8 = orthogonal_group(E8) - F, C, _ = @inferred invariant_coinvariant_pair(E8, OE8) + F, C, _ = invariant_coinvariant_pair(E8, OE8) @test rank(F) == 0 @test C == E8 D5 = lll(root_lattice(:D, 5)) OD5 = matrix_group(automorphism_group_generators(D5, ambient_representation = false)) - C, gene = @inferred coinvariant_lattice(D5, OD5, ambient_representation = false) + C, gene = coinvariant_lattice(D5, OD5, ambient_representation = false) G = gram_matrix(C) - @test all(g -> g*G*transpose(g) == G, gene) + @test all(g -> matrix(g)*G*transpose(matrix(g)) == G, gene) end @testset "Enumeration of lattices with finite isometries" begin E6 = root_lattice(:E, 6) OE6 = orthogonal_group(E6) - cc = conjugacy_classes(E6) + cc = conjugacy_classes(OE6) D = Oscar._test_isometry_enumeration(E6) for n in collect(keys(D)) @@ -209,5 +211,17 @@ end @test length(sv) == 2 @test !primitive_embeddings_in_primary_lattice(rescale(E7, 2), k, classification = :none, check = false)[1] + + B = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 1 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 0 0 1 0; 0 0 0 0 0 0 0 1]); + G = matrix(QQ, 8, 8, [-4 2 0 0 0 0 0 0; 2 -4 2 0 0 0 0 0; 0 2 -4 2 0 0 0 2; 0 0 2 -4 2 0 0 0; 0 0 0 2 -4 2 0 0; 0 0 0 0 2 -4 2 0; 0 0 0 0 0 2 -4 0; 0 0 2 0 0 0 0 -4]); + L = integer_lattice(B, gram = G); + f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; -2 -4 -6 -4 -3 -2 -1 -3; 2 4 6 5 4 3 2 3; -1 -2 -3 -3 -3 -2 -1 -1; 0 0 0 0 1 0 0 0; 1 2 3 3 2 1 0 2]); + Lf = integer_lattice_with_isometry(L, f); + F = invariant_lattice(Lf) + C = coinvariant_lattice(Lf) + reps = @inferred admissible_equivariant_primitive_extensions(F, C, Lf^0, 5) + @test length(reps) == 1 + @test is_of_same_type(Lf, reps[1]) + end From 60250e1c5db47a804e326cb4a383121287e7f892 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 13 Jul 2023 09:43:05 +0200 Subject: [PATCH 65/76] adapt to recent chanegs --- experimental/QuadFormAndIsom/src/embeddings.jl | 9 +-------- experimental/QuadFormAndIsom/src/enumeration.jl | 6 +++--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 5aad281e0052..4f03346e4278 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -286,7 +286,7 @@ function _cokernel_as_Fp_vector_space(HinV, p) return sum([x[i]*V[i] for i in 1:ngens(V)]) end - VtoVp = Hecke.MapFromFunc(_VtoVp, _VptoV, V, Vp) + VtoVp = Hecke.MapFromFunc(V, Vp, _VtoVp, _VptoV) subgene = elem_type(Vp)[VtoVp(HinV(a)) for a in gens(H)] Hp, _ = sub(Vp, subgene) Qp, VptoQp = quo(Vp, Hp) @@ -915,13 +915,6 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific return (length(results) > 0), results end -function Base.:(==)(S::TorQuadModule, T::TorQuadModule) - modulus_bilinear_form(S) != modulus_bilinear_form(T) && return false - modulus_quadratic_form(S) != modulus_quadratic_form(T) && return false - relations(S) != relations(T) && return false - return cover(S) == cover(T) -end - #################################################################################### # # Admissible equivariant primitive extensions diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 41809f717707..ab56ef9cf9d3 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -561,7 +561,7 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = @req is_prime(p) "p must be a prime number" @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" - ok, q, e = is_prime_power_with_data(order_of_isometry(Lf)) + ok, e, q = is_prime_power_with_data(order_of_isometry(Lf)) @req ok || e == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" @@ -633,7 +633,7 @@ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) @req is_prime(p) "p must be a prime number" @req b in [0, 1] "b must be an integer equal to 0 or 1" - ok, q, e = is_prime_power_with_data(order_of_isometry(Lf)) + ok, e, q = is_prime_power_with_data(order_of_isometry(Lf)) @req ok || e == 0 "Order of isometry must be a prime power" @req p != q "Prime numbers must be distinct" @@ -793,7 +793,7 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) d = valuation(n, p) if n != p^d - _, q, e = is_prime_power_with_data(divexact(n, p^d)) + _, e, q = is_prime_power_with_data(divexact(n, p^d)) else q = 1 e = 0 From 97559130241a19bb10ce359a780d9fbee24c78bf Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 14 Jul 2023 17:04:35 +0200 Subject: [PATCH 66/76] last polish before first draft PR --- Project.toml | 2 +- docs/oscar_references.bib | 3 +- experimental/QuadFormAndIsom/docs/doc.main | 2 +- .../QuadFormAndIsom/docs/src/enumeration.md | 6 +- .../QuadFormAndIsom/docs/src/latwithisom.md | 6 +- .../QuadFormAndIsom/docs/src/primembed.md | 20 ++--- .../QuadFormAndIsom/docs/src/spacewithisom.md | 4 +- .../QuadFormAndIsom/src/embeddings.jl | 2 - .../QuadFormAndIsom/src/enumeration.jl | 16 +--- .../src/lattices_with_isometry.jl | 89 +++++-------------- .../src/spaces_with_isometry.jl | 22 ----- experimental/QuadFormAndIsom/src/types.jl | 2 +- experimental/QuadFormAndIsom/test/runtests.jl | 30 ++++--- 13 files changed, 64 insertions(+), 140 deletions(-) diff --git a/Project.toml b/Project.toml index 8973bcdf99c9..77b3fc6ebb3f 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ AbstractAlgebra = "0.31.0" AlgebraicSolving = "0.3.3" DocStringExtensions = "0.8, 0.9" GAP = "0.9.4" -Hecke = "0.19.2" +Hecke = "0.19.6" JSON = "^0.20, ^0.21" Nemo = "0.35.1" Polymake = "0.11.1" diff --git a/docs/oscar_references.bib b/docs/oscar_references.bib index 569484c2f9c4..a5e61c07ef54 100644 --- a/docs/oscar_references.bib +++ b/docs/oscar_references.bib @@ -136,8 +136,9 @@ @Book{BH09 @Article{BH23, author = {Brandhorst, Simon and Hofmann, Tommy}, title = {Finite subgroups of automorphisms of K3 surfaces}, - series = {Forum of Mathematics, Sigma}, + journal = {Forum of Mathematics, Sigma}, volume = {11}, + pages = {e54 1--57}, publisher = {Cambridge University Press, Cambridge}, year = {2023}, doi = {10.1017/fms.2023.50} diff --git a/experimental/QuadFormAndIsom/docs/doc.main b/experimental/QuadFormAndIsom/docs/doc.main index b11cac1869a8..1bf4938eb9e4 100644 --- a/experimental/QuadFormAndIsom/docs/doc.main +++ b/experimental/QuadFormAndIsom/docs/doc.main @@ -3,7 +3,7 @@ "introduction.md", "spacewithisom.md", "latwithisom.md", - "enumeration.m", + "enumeration.md", "primembed.md" ] ] diff --git a/experimental/QuadFormAndIsom/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md index d980bc3f3810..5b86f6bf2c71 100644 --- a/experimental/QuadFormAndIsom/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -20,7 +20,7 @@ Note that not all admissible triples satisfy this extension property. For instance, if $f$ is an isometry of an integer lattice `C` of prime order `p`, then for $A := \Ker \Phi_1(f)$ and $B := \Ker \Phi_p(f)$, one has that -`(A, B, C)` is $p$-admissible (see [BH13, Lemma 4.15.](@cite)). +`(A, B, C)` is $p$-admissible (see Lemma 4.15. in [BH23](@cite)). We say that a triple `(AA, BB, CC)` of genus symbols for integer lattices is *$p$-admissible* if there are some lattices $A \in AA$, $B \in BB$ and @@ -34,7 +34,7 @@ scale valuations for the Jordan components at all the relevant primes (see [`integer_genera`](@ref)). ```@docs -admissible_triples(:::ZZGenus, p::Integer) +admissible_triples(::ZZGenus, p::Integer) is_admissible_triple(::ZZGenus, ::ZZGenus, ::ZZGenus, ::Integer) ``` @@ -84,6 +84,6 @@ Note that an important feature from the theory in [BH23](@cite) is the notion of *admissible gluings* and equivariant primitive embeddings for admissible triples. In the next chapter, we will develop about the features regarding primitive embeddings and their equivariant version. We use this basis to introduce the -method [`admissible_equivariant_primitive_extension`](@ref) (Algorithm 2 in +method `admissible_equivariant_primitive_extension` (Algorithm 2 in [BH23](@cite)) which is the major tool making the previous enumeration possible and fast, from an algorithmic point of view. diff --git a/experimental/QuadFormAndIsom/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md index b787f88112bc..261d9893fd40 100644 --- a/experimental/QuadFormAndIsom/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -113,7 +113,7 @@ one can compute the *type* of $Lf$, which can be seen as an equivalent of the *genus* used to classified single lattices. ```@docs -type(::Lf) +type(::ZZLatWithIsom) ``` Since determining whether two pairs of lattices with isometry are isomorphic is @@ -122,7 +122,7 @@ This set of data keeps track of some local and global invariants of the pair $(L f)$ with respect to the action of $f$ on $L$. ```@docs -is_of_type(::ZZLatWithIsom, t:Dict) +is_of_type(::ZZLatWithIsom, t::Dict) is_of_same_type(::ZZLatWithIsom, ::ZZLatWithIsom) ``` @@ -137,7 +137,7 @@ $f$, if it is maximal in the associated number field $\mathbb{Q}[f]$. ```@docs is_of_hermitian_type(::ZZLatWithIsom) -is_hermitian() +is_hermitian(::Dict) ``` ## Hermitian structure and trace equivalence diff --git a/experimental/QuadFormAndIsom/docs/src/primembed.md b/experimental/QuadFormAndIsom/docs/src/primembed.md index 04bac0a48885..fef85ef83a1b 100644 --- a/experimental/QuadFormAndIsom/docs/src/primembed.md +++ b/experimental/QuadFormAndIsom/docs/src/primembed.md @@ -5,7 +5,7 @@ CurrentModule = Oscar We introduce here the necessary definitions and results which lie behind the methods presented. Most of the content is taken from [Nik79](@cite). -# Primitive embeddings between even lattices +# Primitive embeddings in even lattices ## Nikulin's theory @@ -20,11 +20,11 @@ and $i_2$ defines *isomorphic primitive sublattices* [Nik79](@cite). In his paper, V. V. Nikulin gives necessary and sufficient condition for an even integral lattice $M$ to embed primitively into an even unimodular lattice with -given invariants ([Nik79, Theorem 1.12.2](@cite)). More generally, the author also -provides methods to compute primitive embeddings of any even lattice into an even -lattice in a given genus ([Nik79, Proposition 1.15.1](@cite)). In the latter -proposition, it is explain how to classify such embeddings as isomorphic -embeddings or as isomorphic sublattices. +given invariants (see Theorem 1.12.2 in [Nik79](@cite)). More generally, the +author also provides methods to compute primitive embeddings of any even lattice +into an even lattice in a given genus (see Proposition 1.15.1 in [Nik79](@cite)). +In the latter proposition, it is explain how to classify such embeddings as +isomorphic embeddings or as isomorphic sublattices. Such a method can be algorithmically implemented, however it tends to be slow and inefficient in general for large rank or determinant. But, in the case @@ -47,11 +47,11 @@ primitive_embeddings_of_primary_lattice(::ZZLat, ::ZZLat) ``` Note that the previous two functions require the first lattice in input to be -unique in its genus. Otherwise, one can refer a genus, or its invariant, as a +unique in its genus. Otherwise, one can refer a genus, or its invariants, as a first input: ```@docs -primitive_embeddings_in_primary_lattice(::ZZgenus, ::ZZLat) +primitive_embeddings_in_primary_lattice(::ZZGenus, ::ZZLat) primitive_embeddings_in_primary_lattice(::TorQuadModule, ::Tuple{Int, Int}, ::ZZLat) primitive_embeddings_of_primary_lattice(::ZZGenus, ::ZZLat) @@ -74,9 +74,9 @@ classified, by looking for *gluings* between anti-isometric subgroups of the respective discriminant groups of `M` and `N`. The construction of an overlattice is determined by the graph of such glue map. -## Admissible equivariant primitive extension +## Admissible equivariant primitive extensions -The following function is an interesting tool provided by [BH23](2cite). Given +The following function is an interesting tool provided by [BH23](@cite). Given a triple of integer lattices with isometry `((A, a), (B, b), (C, c))` and two prime numbers `p` and `q` (possibly equal), if `(A, B, C)` is `p`-admissible, this function returns representatives of isomorphism classes of equivariant primitive diff --git a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md index 8dae5cceb95f..2bcf89ad61a6 100644 --- a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md @@ -45,8 +45,8 @@ For simplicity, we have gathered the main constructors for objects of type user has then the choice on the parameters depending on what they attend to do: ```@docs -quadratic_space_with_isometry(::QuadSpace, ::QQMatrix) -quadratic_space_with_isometry(::QuadSpace) +quadratic_space_with_isometry(::Hecke.QuadSpace, ::QQMatrix) +quadratic_space_with_isometry(::Hecke.QuadSpace) ``` By default, the first constructor always checks whether the entry matrix defines diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 4f03346e4278..a5c8ef06af91 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -943,8 +943,6 @@ If `check == true` the input triple is checked to a `p`-admissible triple of integral lattices (with isometry) with `fA` and `fB` having relatively coprime irreducible minimal polynomials and imposing that `A` and `B` are orthogonal if `A`, `B` and `C` lie in the same ambient quadratic space. - -See [BH23, Algorithm 2](@cite). """ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, B::ZZLatWithIsom, diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index ab56ef9cf9d3..d0fcafcdc7f3 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -74,13 +74,13 @@ end Given a triple of $\mathbb Z$-genera `(A,B,C)` and a prime number `p`, such that the rank of `B` is divisible by $p-1$, return whether `(A,B,C)` is -`p`-admissible in the sense of [BH23, Definition 4.13](@cite) +`p`-admissible. # Examples A standard example is the following: let $(L, f)$ be a lattice with isometry of prime order $p$, let $F:= L^f$ and $C:= L_f$ be respectively the invariant and coinvariant sublattices of $(L, f)$. Then, the triple of genera -$(g(F), g(C), g(L))$ is $p$-admissible according to [BH23, Lemma 4.15](@cite). +$(g(F), g(C), g(L))$ is $p$-admissible. ```jldoctest julia> L = root_lattice(:A,5); @@ -246,8 +246,6 @@ Given a $\mathbb Z$-genus `C` and a prime number `p`, return all tuples of $\mathbb Z$-genera `(A, B)` such that `(A, B, C)` is `p`-admissible and `B` is of rank divisible by $p-1$. -See [BH23, Algorithm 1](@cite). - # Examples ```jldoctest julia> L = root_lattice(:A,5); @@ -419,8 +417,6 @@ hermitian type $(M, g)$ and such that the type of $(B, g^m)$ is equal to the type of $(L, f)$. Note that in this case, the isometries `g`'s are of order $nm$. -See [BH23, Algorithm 3](@cite). - # Examples ```jldoctest julia> L = root_lattice(:A,2); @@ -527,8 +523,6 @@ $(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. Note that `e` can be 0. -See [BH23, Algorithm 4](@cite). - # Examples ```jldoctest julia> L = root_lattice(:A,2); @@ -604,8 +598,6 @@ order $pq^e$. Note that `e` can be 0. -See [BH23, Algorithm 5](@cite). - # Examples ```jldoctest julia> L = root_lattice(:A,2); @@ -673,8 +665,6 @@ isometry $(M, g)$ such that the type of $(M, g^p)$ is equal to the type of $(L, f)$. Note that `e` can be 0, while `d` has to be positive. - -See [BH23, Algorithm 6](@cite). """ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) rank(Lf) == 0 && return ZZLatWithIsom[Lf] @@ -738,8 +728,6 @@ of order $p^{d+1}q^e$. Note that `d` and `e` can be both zero. -See [BH23, Algorithm 7](@cite). - # Examples ```jldoctest julia> L = root_lattice(:E,7); diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 21bbe9847d2f..d529dbc2bb8d 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -127,8 +127,6 @@ order_of_isometry(Lf::ZZLatWithIsom) = Lf.n Given a lattice with isometry $(L, f)$, return the rank of the underlying lattice `L`. -See [`rank(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); @@ -183,8 +181,6 @@ minimal_polynomial(Lf::ZZLatWithIsom) = minimal_polynomial(isometry(Lf))::QQPoly Given a lattice with isometry $(L, f)$, return the genus of the underlying lattice `L`. -See [`genus(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); @@ -207,8 +203,6 @@ genus(Lf::ZZLatWithIsom) = genus(lattice(Lf))::ZZGenus Given a lattice with isometry $(L, f)$, return the basis matrix of the underlying lattice `L`. -See [`basis_matrix(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); @@ -244,13 +238,11 @@ basis_matrix(Lf::ZZLatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix Given a lattice with isometry $(L, f)$, return the gram matrix of the underlying lattice `L` with respect to its basis matrix. -See [`gram_matrix(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> gram_matrix(Lf) [ 2 -1 0 0 0] @@ -269,23 +261,21 @@ Given a lattice with isometry $(L, f)$, return the rational span $L \otimes \mathbb{Q}$ of the underlying lattice `L` together with the underlying isometry of `L`. -See [`rational_span(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> Vf = rational_span(Lf) Quadratic space of dimension 5 - with isometry of finite order 2 + with isometry of finite order 1 given by - [ 1 0 0 0 0] - [-1 -1 -1 -1 -1] - [ 0 0 0 0 1] - [ 0 0 0 1 0] - [ 0 0 1 0 0] + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + [0 0 0 0 1] julia> typeof(Vf) QuadSpaceWithIsom @@ -299,13 +289,11 @@ rational_span(Lf::ZZLatWithIsom) = quadratic_space_with_isometry(rational_span(l Given a lattice with isometry $(L, f)$, return the determinant of the underlying lattice `L`. -See [`det(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> det(Lf) 6 @@ -319,13 +307,11 @@ det(Lf::ZZLatWithIsom) = det(lattice(Lf))::QQFieldElem Given a lattice with isometry $(L, f)$, return the scale of the underlying lattice `L`. -See [`scale(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> scale(Lf) 1 @@ -339,13 +325,11 @@ scale(Lf::ZZLatWithIsom) = scale(lattice(Lf))::QQFieldElem Given a lattice with isometry $(L, f)$, return the norm of the underlying lattice `L`. -See [`norm(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> norm(Lf) 2 @@ -359,13 +343,11 @@ norm(Lf::ZZLatWithIsom) = norm(lattice(Lf))::QQFieldElem Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is positive definite. -See [`is_positive_definite(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> is_positive_definite(Lf) true @@ -379,13 +361,11 @@ is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf))::Boo Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is negative definite. -See [`is_positive_definite(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> is_negative_definite(Lf) false @@ -399,13 +379,11 @@ is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf))::Boo Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is definite. -See [`is_definite(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> is_definite(Lf) true @@ -419,13 +397,11 @@ is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf))::Bool Given a positive definite lattice with isometry $(L, f)$, return the minimum of the underlying lattice `L`. -See [`minimum(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> minimum(Lf) 2 @@ -442,13 +418,11 @@ end Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is integral. -See [`is_integral(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> is_integral(Lf) true @@ -462,13 +436,11 @@ is_integral(Lf::ZZLatWithIsom) = is_integral(lattice(Lf))::Bool Given a lattice with isometry $(L, f)$, return the degree of the underlying lattice `L`. -See [`degree(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> degree(Lf) 5 @@ -482,13 +454,11 @@ degree(Lf::ZZLatWithIsom) = degree(lattice(Lf))::Int Given a lattice with isometry $(L, f)$, return whether the underlying lattice `L` is even. -See [`is_even(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> is_even(Lf) true @@ -502,13 +472,11 @@ is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf))::Bool Given a lattice with isometry $(L, f)$, return the discriminant of the underlying lattice `L`. -See [`discriminant(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> discriminant(Lf) == det(Lf) == 6 true @@ -522,13 +490,11 @@ discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf))::QQFieldElem Given a lattice with isometry $(L, f)$, return the signature tuple of the underlying lattice `L`. -See [`signature_tuple(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, f); +julia> Lf = integer_lattice_with_isometry(L); julia> signature_tuple(Lf) (5, 0, 0) @@ -671,7 +637,8 @@ Integer lattice of rank 5 and degree 5 [ 0 -1 0 0 0] [ 0 0 -1 0 0] [ 0 0 0 -1 0] - [ 0 0 0 0 -1]``` + [ 0 0 0 0 -1] +``` """ function integer_lattice_with_isometry(L::ZZLat; neg::Bool = false) d = degree(L) @@ -799,7 +766,7 @@ julia> B = matrix(QQ,3,5,[1 0 0 0 0; [0 0 1 0 1] [0 0 0 1 0] -julia> lattice_in_same_ambient_space(Lf, B) +julia> I = lattice_in_same_ambient_space(Lf, B) Integer lattice of rank 3 and degree 5 with isometry of finite order 1 given by @@ -829,8 +796,6 @@ end Given a lattice with isometry $(L, f)$ and a rational number `a`, return the lattice with isometry $(L(a), f)$. -See [`rescale(::ZZLat, ::RationalUnion)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5) @@ -931,8 +896,6 @@ Given a lattice with isometry $(L, f)$ inside the space $(V, \Phi)$, such that isometry $(L^{\vee}, h)$ where $L^{\vee}$ is the dual of `L` in $(V, \Phi)$ and `h` is induced by `g`. -See [`dual(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); @@ -982,8 +945,6 @@ on the associated gram matrix of `L`. Note that matrix representing the action of `f` on `L` changes but the global action on the ambient space of `L` stays the same. -See [`lll(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); @@ -1476,8 +1437,6 @@ Given an integral lattice with isometry $(L, f)$, return the discriminant group of the underlying lattice `L` as well as this image of the underlying isometry `f` inside $O(q)$. -See [`discriminant_group(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,5); @@ -1536,8 +1495,6 @@ $O(q_L, \bar{f})$ of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ denotes the discriminant group of `L` and $\bar{f}$ is the isometry of $q_L$ induced by `f`. -See [`image_in_Oq(::ZZLat)`](@ref). - # Examples ```jldoctest julia> L = root_lattice(:A,2); diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 5f05aba16494..66161add26dc 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -71,8 +71,6 @@ order_of_isometry(Vf::QuadSpaceWithIsom) = Vf.n Given a quadratic space with isometry $(V, f)$, return the rank of the underlying space `V`. -See ['rank(::QuadSpace)'](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -91,8 +89,6 @@ rank(Vf::QuadSpaceWithIsom) = rank(space(Vf))::Integer Given a quadratic space with isometry $(V, f)$, return the dimension of the underlying space of `V`. -See [`dim(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -147,8 +143,6 @@ minimal_polynomial(Vf) = minimal_polynomial(isometry(Vf))::QQPolyRingElem Given a quadratic space with isometry $(V, f)$, return the Gram matrix of the underlying space `V` with respect to its standard basis. -See [`gram_matrix(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -167,8 +161,6 @@ gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf))::QQMatrix Given a quadratic space with isometry $(V, f)$, return the determinant of the underlying space `V`. -See [`det(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -187,8 +179,6 @@ det(Vf::QuadSpaceWithIsom) = det(space(Vf))::QQFieldElem Given a quadratic space with isometry $(V, f)$, return the discriminant of the underlying space `V`. -See [`discriminant(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -207,8 +197,6 @@ discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf))::QQFieldElem Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is positive definite. -See [`is_positive_definite(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -227,8 +215,6 @@ is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf))::B Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is negative definite. -See [`is_negative_definite(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -247,8 +233,6 @@ is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf))::B Given a quadratic space with isometry $(V, f)$, return whether the underlying space `V` is definite. -See [`is_definite(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -267,8 +251,6 @@ is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf))::Bool Given a quadratic space with isometry $(V, f)$, return the diagonal of the underlying space `V`. -See [`diagonal(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -289,8 +271,6 @@ diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf))::Vector{QQFieldElem} Given a quadratic space with isometry $(V, f)$, return the signature tuple of the underlying space `V`. -See [`signature_tuple(::QuadSpace)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, 2); @@ -400,8 +380,6 @@ end Given a quadratic space with isometry $(V, f)$, return the pair $(V^a, f$) where $V^a$ is the same space as `V` with the associated quadratic form rescaled by `a`. -See [`rescale(::QuadSpace, ::Hecke.RationalUnion)`](@ref). - # Examples ```jldoctest julia> V = quadratic_space(QQ, QQ[ 2 -1; diff --git a/experimental/QuadFormAndIsom/src/types.jl b/experimental/QuadFormAndIsom/src/types.jl index c07b6b4a2c41..7f44c407e0fc 100644 --- a/experimental/QuadFormAndIsom/src/types.jl +++ b/experimental/QuadFormAndIsom/src/types.jl @@ -162,7 +162,7 @@ julia> B = matrix(QQ, 4, 6, [1 0 3 0 0 0; 0 0 0 0 1 0; 0 0 0 0 0 1]); -julia> Cf = lattice(V, B) # coinvariant sublattice L_f +julia> Cf = lattice(Vf, B) # coinvariant sublattice L_f Integer lattice of rank 4 and degree 6 with isometry of finite order 3 given by diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index f5796849a3f2..08edfacadc71 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -143,8 +143,10 @@ end Lf = integer_lattice_with_isometry(L, f); GL = image_centralizer_in_Oq(Lf) @test order(GL) == 72 - GL = image_centralizer_in_Oq(rescale(Lf, -14)) - @test order(GL) == 870912 + + # Long test + #GL = image_centralizer_in_Oq(rescale(Lf, -14)) + #@test order(GL) == 870912 B = matrix(QQ, 4, 6, [0 0 0 0 -2 1; 0 0 0 0 3 -4; 0 0 1 0 -1 0; 0 0 0 1 0 -1]); G = matrix(QQ, 6, 6, [2 1 0 0 0 0; 1 -2 0 0 0 0; 0 0 2//5 4//5 2//5 -1//5; 0 0 4//5 -2//5 -1//5 3//5; 0 0 2//5 -1//5 2//5 4//5; 0 0 -1//5 3//5 4//5 -2//5]); @@ -154,30 +156,30 @@ end GL = image_centralizer_in_Oq(Lf) @test order(GL) == 2 - E8 = root_lattice(:E, 8) - OE8 = orthogonal_group(E8) - F, C, _ = invariant_coinvariant_pair(E8, OE8) + A3 = root_lattice(:A, 3) + OA3 = orthogonal_group(A3) + F, C, _ = invariant_coinvariant_pair(A3, OA3) @test rank(F) == 0 - @test C == E8 + @test C == A3 - D5 = lll(root_lattice(:D, 5)) - OD5 = matrix_group(automorphism_group_generators(D5, ambient_representation = false)) - C, gene = coinvariant_lattice(D5, OD5, ambient_representation = false) + D4 = lll(root_lattice(:D, 4)) + OD4 = matrix_group(automorphism_group_generators(D4, ambient_representation = false)) + C, gene = coinvariant_lattice(D4, OD4, ambient_representation = false) G = gram_matrix(C) @test all(g -> matrix(g)*G*transpose(matrix(g)) == G, gene) end @testset "Enumeration of lattices with finite isometries" begin - E6 = root_lattice(:E, 6) - OE6 = orthogonal_group(E6) - cc = conjugacy_classes(OE6) + A4 = root_lattice(:A, 4) + OA4 = orthogonal_group(A4) + cc = conjugacy_classes(OA4) - D = Oscar._test_isometry_enumeration(E6) + D = Oscar._test_isometry_enumeration(A4) for n in collect(keys(D)) @test length(D[n]) == length(filter(c -> order(representative(c)) == n, cc)) end - for N in D[12] + for N in D[6] ONf = image_centralizer_in_Oq(integer_lattice_with_isometry(lattice(N), ambient_isometry(N))) # for N, the image in OqN of the centralizer of fN in ON is directly # computing during the construction of the admissible primitive extension. From dfb5922395b73737e388b114924137e53a5a6c38 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 18 Jul 2023 08:53:19 +0200 Subject: [PATCH 67/76] fixes for tests to pass --- docs/oscar_references.bib | 2 +- experimental/QuadFormAndIsom/docs/src/latwithisom.md | 5 ++--- experimental/QuadFormAndIsom/src/lattices_with_isometry.jl | 5 +---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/oscar_references.bib b/docs/oscar_references.bib index a5e61c07ef54..8c9bdf8f9609 100644 --- a/docs/oscar_references.bib +++ b/docs/oscar_references.bib @@ -138,8 +138,8 @@ @Article{BH23 title = {Finite subgroups of automorphisms of K3 surfaces}, journal = {Forum of Mathematics, Sigma}, volume = {11}, - pages = {e54 1--57}, publisher = {Cambridge University Press, Cambridge}, + pages = {e54 1--57}, year = {2023}, doi = {10.1017/fms.2023.50} } diff --git a/experimental/QuadFormAndIsom/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md index 261d9893fd40..bb7cefe65155 100644 --- a/experimental/QuadFormAndIsom/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -145,8 +145,7 @@ is_hermitian(::Dict) As mentioned in the previous section, to a lattice with isometry $Lf := (L, f)$ such that the minimal polynomial of $f$ is irreducible, one can associate a hermitian lattice $\mathfrak{L}$ over the equation order of $f$, if it is -maximal, for which $Lf$ is the associated trace lattice (see -[`trace_lattice_with_isometry(::AbstractLat)`](@ref)). Hecke provides the tools +maximal, for which $Lf$ is the associated trace lattice. Hecke provides the tools to perform the trace equivalence for lattices with isometry of hermitian type. ```@docs @@ -179,7 +178,7 @@ image_centralizer_in_Oq(::ZZLatWithIsom) ``` For an implementation of the regular Miranda-Morisson theory, we refer to the -function [`image_in_Oq(::ZZLat)`](@ref) which actually computes the image of +function `image_in_Oq` which actually computes the image of $\pi$ in both the definite and the indefinite case. We will see later in the section about enumeration of lattices with isometry diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index d529dbc2bb8d..a7135b34573e 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -1958,10 +1958,7 @@ julia> f = matrix(QQ, 5, 5, [1 1 1 1 1; julia> Lf = integer_lattice_with_isometry(L, f); -julia> t = type(Lf) -Dict{Integer, Tuple} with 2 entries: - 5 => (Genus symbol for hermitian lattices of rank 1 over relative maximal order of Relative number field… - 1 => (Genus symbol: II_(1, 0) 2^1_7 3^1 5^1, Genus symbol: II_(1, 0) 2^1_7 3^1 5^1) +julia> t = type(Lf); julia> genus(invariant_lattice(Lf)) == t[1][1] true From e297bb93f4b274d63d72cf657c307fb2aa2af0fa Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 18 Jul 2023 17:47:22 +0200 Subject: [PATCH 68/76] more tests and few fixes --- .../QuadFormAndIsom/src/embeddings.jl | 8 +-- .../QuadFormAndIsom/src/enumeration.jl | 9 +-- .../src/lattices_with_isometry.jl | 14 +++-- experimental/QuadFormAndIsom/src/printings.jl | 12 ++-- .../src/spaces_with_isometry.jl | 11 ++-- experimental/QuadFormAndIsom/test/runtests.jl | 60 ++++++++++++++----- 6 files changed, 73 insertions(+), 41 deletions(-) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index a5c8ef06af91..0447791b6394 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -636,9 +636,9 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) - !(genus(M) == G) && return results + !(genus(M) == G) && return false, results push!(results, (M, M, orthogonal_submodule(M, M))) - return results + return true, results end @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" @@ -837,9 +837,9 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) - !(genus(M) == G) && return results + !(genus(M) == G) && return false, results push!(results, (M, M, orthogonal_submodule(M, M))) - return results + return true, results end @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index d0fcafcdc7f3..61e7a794438a 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -501,7 +501,7 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) if is_even(M) != is_even(Lf) continue end - if !is_of_same_type(Lf, integer_lattice_with_isometry(lattice(M), ambient_isometry(M)^m)) + if !is_of_same_type(Lf, M^m) continue end gr = genus_representatives(H) @@ -574,11 +574,8 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = RA = representatives_of_hermitian_type(LA, q^e) for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB], inplace=false) E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p) - if !is_empty(E) && (E[1] isa AutomorphismGroup{TorQuadModule}) - return E - end - GC.gc() append!(reps, E) + GC.gc() end end return reps @@ -647,9 +644,9 @@ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) for (L1, L2) in Hecke.cartesian_product_iterator([A, B], inplace=false) b == 1 && !Hecke.divides(order_of_isometry(L1), p)[1] && !Hecke.divides(order_of_isometry(L2), p)[1] && continue E = admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p) - GC.gc() @hassert :ZZLatWithIsom 1 b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) + GC.gc() end return reps end diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index a7135b34573e..393aec87d680 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -2094,13 +2094,15 @@ end # ############################################################################### -function to_oscar(Lf::ZZLatWithIsom) +function to_oscar(io::IO, Lf::ZZLatWithIsom) L = lattice(Lf) f = ambient_isometry(Lf) - println(stdout, "B = matrix(QQ, $(rank(L)), $(degree(L)), " , basis_matrix(L), ");") - println(stdout, "G = matrix(QQ, $(degree(L)), $(degree(L)), ", gram_matrix(ambient_space(L)), ");") - println(stdout, "L = integer_lattice(B, gram = G);") - println(stdout, "f = matrix(QQ, $(degree(L)), $(degree(L)), ", f, ");") - println(stdout, "Lf = integer_lattice_with_isometry(L, f);") + println(io, "B = matrix(QQ, $(rank(L)), $(degree(L)), " , basis_matrix(L), ");") + println(io, "G = matrix(QQ, $(degree(L)), $(degree(L)), ", gram_matrix(ambient_space(L)), ");") + println(io, "L = integer_lattice(B, gram = G);") + println(io, "f = matrix(QQ, $(degree(L)), $(degree(L)), ", f, ");") + println(io, "Lf = integer_lattice_with_isometry(L, f);") end +to_oscar(Lf::ZZLatWithIsom) = to_oscar(stdout, Lf) + diff --git a/experimental/QuadFormAndIsom/src/printings.jl b/experimental/QuadFormAndIsom/src/printings.jl index fb22a83b2e26..6fa59d025d08 100644 --- a/experimental/QuadFormAndIsom/src/printings.jl +++ b/experimental/QuadFormAndIsom/src/printings.jl @@ -5,10 +5,10 @@ ############################################################################### function Base.show(io::IO, ::MIME"text/plain", Lf::ZZLatWithIsom) - io = AbstractAlgebra.pretty(io) + io = pretty(io) println(io, lattice(Lf)) n = order_of_isometry(Lf) - print(io, AbstractAlgebra.Indent()) + print(io, Indent()) if is_finite(n) println(io, "with isometry of finite order $n") else @@ -16,7 +16,7 @@ function Base.show(io::IO, ::MIME"text/plain", Lf::ZZLatWithIsom) end println(io, "given by") show(io, MIME"text/plain"(), isometry(Lf)) - print(io, AbstractAlgebra.Dedent()) + println(io) end function Base.show(io::IO, Lf::ZZLatWithIsom) @@ -39,10 +39,10 @@ end ############################################################################### function Base.show(io::IO, ::MIME"text/plain", Vf::QuadSpaceWithIsom) - io = AbstractAlgebra.pretty(io) + io = pretty(io) println(io, space(Vf)) n = order_of_isometry(Vf) - print(io, AbstractAlgebra.Indent()) + print(io, Indent()) if is_finite(n) println(io, "with isometry of finite order $n") else @@ -50,7 +50,7 @@ function Base.show(io::IO, ::MIME"text/plain", Vf::QuadSpaceWithIsom) end println(io, "given by") show(io, MIME"text/plain"(), isometry(Vf)) - print(io, AbstractAlgebra.Dedent()) + print(io, Dedent()) end function Base.show(io::IO, Vf::QuadSpaceWithIsom) diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 66161add26dc..52579726024f 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -784,12 +784,13 @@ end # ############################################################################### -function to_oscar(Vf::QuadSpaceWithIsom) +function to_oscar(io::IO, Vf::QuadSpaceWithIsom) V = space(Vf) f = isometry(Vf) - println(stdout, "G = matrix(QQ, $(dim(V)), $(dim(V)), ", gram_matrix(V), ");") - println(stdout, "V = quadratic_space(QQ, G);") - println(stdout, "f = matrix(QQ, $(dim(V)), $(dim(V)), ", f, ");") - println(stdout, "Vf = quadratic_space_with_isometry(V, f);") + println(io, "G = matrix(QQ, $(dim(V)), $(dim(V)), ", gram_matrix(V), ");") + println(io, "V = quadratic_space(QQ, G);") + println(io, "f = matrix(QQ, $(dim(V)), $(dim(V)), ", f, ");") + println(io, "Vf = quadratic_space_with_isometry(V, f);") end +to_oscar(Vf::QuadSpaceWithIsom) = to_oscar(stdout, Vf) diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index 08edfacadc71..66b88892d542 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -1,6 +1,21 @@ using Test using Oscar +@testset "Printings" begin + function _show_details(io::IO, X::Union{ZZLatWithIsom, QuadSpaceWithIsom}) + return show(io, MIME"text/plain"(), Lf) + end + L = root_lattice(:A, 2) + Lf = integer_lattice_with_isometry(L) + Vf = ambient_space(Lf) + for X in [Lf, Vf] + @test sprint(_show_details, X) isa String + @test sprint(Oscar.to_oscar, X) isa String + @test sprint(show, X) isa String + @test sprint(show, X, context=:supercompact => true) isa String + end +end + @testset "Spaces with isometry" begin D5 = root_lattice(:D, 5) V = ambient_space(D5) @@ -42,9 +57,9 @@ using Oscar end @testset "Lattices with isometry" begin - A4 = root_lattice(:A, 4) - agg = automorphism_group_generators(A4, ambient_representation = false) - agg_ambient = automorphism_group_generators(A4, ambient_representation = true) + A3 = root_lattice(:A, 3) + agg = automorphism_group_generators(A3, ambient_representation = false) + agg_ambient = automorphism_group_generators(A3, ambient_representation = true) f = rand(agg) g_ambient = rand(agg_ambient) @@ -52,7 +67,8 @@ end Lf = integer_lattice_with_isometry(L, neg = true) @test order_of_isometry(Lf) == -1 - L = @inferred integer_lattice_with_isometry(A4) + L = @inferred integer_lattice_with_isometry(A3) + @test length(unique([L, L, L])) == 1 @test ambient_space(L) isa QuadSpaceWithIsom @test isone(isometry(L)) @test isone(ambient_isometry(L)) @@ -63,33 +79,31 @@ end gram_matrix, det, scale, norm, is_integral, is_negative_definite, degree, is_even, discriminant, signature_tuple, is_definite] k = @inferred func(L) - @test k == func(A4) + @test k == func(A3) end - GL = image_centralizer_in_Oq(L) - @test order(GL) == 2 - LfQ = @inferred rational_span(L) @test LfQ isa QuadSpaceWithIsom @test evaluate(minimal_polynomial(L), 1) == 0 - @test evaluate(characteristic_polynomial(L), 0) == 1 + @test evaluate(characteristic_polynomial(L), 0) == -1 @test minimum(L) == 2 @test is_positive_definite(L) @test is_definite(L) nf = multiplicative_order(f) - @test_throws ArgumentError integer_lattice_with_isometry(A4, zero_matrix(QQ, 0, 0)) + @test_throws ArgumentError integer_lattice_with_isometry(A3, zero_matrix(QQ, 0, 0)) - L2 = @inferred integer_lattice_with_isometry(A4, f, ambient_representation = false) + L2 = @inferred integer_lattice_with_isometry(A3, f, ambient_representation = false) @test order_of_isometry(L2) == nf L2v = @inferred dual(L2) @test order_of_isometry(L2v) == nf @test ambient_isometry(L2v) == ambient_isometry(L2) - L3 = @inferred integer_lattice_with_isometry(A4, g_ambient, ambient_representation = true) + L3 = @inferred integer_lattice_with_isometry(A3, g_ambient, ambient_representation = true) @test order_of_isometry(L3) == multiplicative_order(g_ambient) @test L3^(order_of_isometry(L3)+1) == L3 + @test genus(lll(L3, same_ambient=false)) == genus(L3) L4 = @inferred rescale(L3, QQ(1//4)) @test !is_integral(L4) @@ -161,6 +175,8 @@ end F, C, _ = invariant_coinvariant_pair(A3, OA3) @test rank(F) == 0 @test C == A3 + _, _, G = invariant_coinvariant_pair(A3, OA3, ambient_representation = false) + @test order(G) == order(OA3) D4 = lll(root_lattice(:D, 4)) OD4 = matrix_group(automorphism_group_generators(D4, ambient_representation = false)) @@ -192,13 +208,18 @@ end E8 = root_lattice(:E, 8) @test length(enumerate_classes_of_lattices_with_isometry(E8, 20)) == 3 @test length(enumerate_classes_of_lattices_with_isometry(E8, 18)) == 4 + @test length(enumerate_classes_of_lattices_with_isometry(genus(E8), 1)) == 1 + + @test length(admissible_triples(E8, 2, pA=2)) == 1 + @test length(admissible_triples(rescale(E8, 2), 2, pB = 4)) == 2 + @test length(admissible_triples(E8, 3, pA=4, pB = 4)) == 1 end @testset "Primitive embeddings" begin # Compute orbits of short vectors k = integer_lattice(gram=matrix(QQ,1,1,[4])) E8 = root_lattice(:E, 8) - ok, sv = primitive_embeddings_of_primary_lattice(E8, k, classification =:sublat, check=false) + ok, sv = primitive_embeddings_of_primary_lattice(E8, k, classification =:sublat, check=true) @test ok @test length(sv) == 1 ok, sv = primitive_embeddings_in_primary_lattice(rescale(E8, 2), rescale(k, QQ(1//2)), check=false) @@ -208,9 +229,20 @@ end k = integer_lattice(gram=matrix(QQ,1,1,[6])) E7 = root_lattice(:E, 7) - ok, sv = primitive_embeddings_in_primary_lattice(E7, k, classification = :emb, check=false) + ok, sv = primitive_embeddings_in_primary_lattice(E7, k, classification = :emb, check=true) @test ok @test length(sv) == 2 + q = discriminant_group(E7) + p, z, n = signature_tuple(E7) + ok, _ = primitive_embeddings_in_primary_lattice(q, (p,n), E7, classification = :none) + @test ok + k = integer_lattice(gram=matrix(QQ,1,1,[2])) + ok, sv = primitive_embeddings_of_primary_lattice(E7, k, check=true) + @test ok + @test length(sv) == 1 + + ok, _ = primitive_embeddings_of_primary_lattice(q, (p,n), E7, classification = :none) + @test ok @test !primitive_embeddings_in_primary_lattice(rescale(E7, 2), k, classification = :none, check = false)[1] From 8914f093867eb009206370d6a81f5fa31d440f6c Mon Sep 17 00:00:00 2001 From: Stevell Muller <78619134+StevellM@users.noreply.github.com> Date: Tue, 18 Jul 2023 23:06:27 +0200 Subject: [PATCH 69/76] Update runtests.jl --- experimental/QuadFormAndIsom/test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index 66b88892d542..9e5680b767c5 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -3,7 +3,7 @@ using Oscar @testset "Printings" begin function _show_details(io::IO, X::Union{ZZLatWithIsom, QuadSpaceWithIsom}) - return show(io, MIME"text/plain"(), Lf) + return show(io, MIME"text/plain"(), X) end L = root_lattice(:A, 2) Lf = integer_lattice_with_isometry(L) From c027c7766f0acac07117a1dd26274d4c23b9481c Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 19 Jul 2023 09:42:58 +0200 Subject: [PATCH 70/76] fix html docs --- .../QuadFormAndIsom/docs/src/enumeration.md | 2 +- .../QuadFormAndIsom/src/embeddings.jl | 67 ++++++------------- .../src/lattices_with_isometry.jl | 6 +- 3 files changed, 24 insertions(+), 51 deletions(-) diff --git a/experimental/QuadFormAndIsom/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md index 5b86f6bf2c71..c4bf7cd06096 100644 --- a/experimental/QuadFormAndIsom/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -19,7 +19,7 @@ $p$-elementary subgroups of the respective discriminant groups of `A` and `B`. Note that not all admissible triples satisfy this extension property. For instance, if $f$ is an isometry of an integer lattice `C` of prime order -`p`, then for $A := \Ker \Phi_1(f)$ and $B := \Ker \Phi_p(f)$, one has that +`p`, then for $A := \ker \Phi_1(f)$ and $B := \ker \Phi_p(f)$, one has that `(A, B, C)` is $p$-admissible (see Lemma 4.15. in [BH23](@cite)). We say that a triple `(AA, BB, CC)` of genus symbols for integer lattices is diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 0447791b6394..0b701913b4f2 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -527,13 +527,9 @@ If `T == false`, then `V` will always be the empty list. If `T == true`, then the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ - and $O(q)$ where `q` is the discriminant group of `L`; - - `classification = :emd`: `V` consists on representatives for all isomorphism - classes of primitive sublattices of `L` isometric to `M` up to the action of - $O(q)$ where `q` is the discriminant group of `L`. + - `classification = :first`: `V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of `L`; + - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of `L` isometric to `M` up to the action of $O(q)$ where `q` is the discriminant group of `L`. If `check` is set to true, the function determines whether `L` is in fact unique in its genus. @@ -586,13 +582,9 @@ If `T == false`, then `V` will always be the empty list. If `T == true`, then the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in lattices in `G`, up to the actions of - $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emd`: `V` consists on representatives for all isomorphism - classes of primitive sublattices of lattices in `G` isometric to `M`, up to the - action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + - `classification = :first`: `V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; + - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. If the pair `(q, sign)` does not define a non-empty genus for integer lattices, an error is thrown. @@ -620,13 +612,9 @@ If `T == false`, then `V` will always be the empty list. If `T == true`, then the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in lattices in `G`, up to the actions of - $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant of a lattice in `G`; - - `classification = :emb`: `V` consists on representatives for all isomorphism - classes of primitive sublattices of lattices in `G` isometric to `M`, up to the - action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + - `classification = :first`: `V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant of a lattice in `G`; + - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. """ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) @req classification in [:none, :emb, :sublat, :first] "Wrong symbol for classification" @@ -731,13 +719,9 @@ If `T == false`, then `V` will always be the empty list. If `T == true`, then the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ - and $O(q)$ where `q` is the discriminant group of `L`; - - `classification = :emd`: `V` consists on representatives for all isomorphism - classes of primitive sublattices of `L` isometric to `M` up to the action of - $O(q)$ where `q` is the discriminant group of `L`. + - `classification = :first`: `V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of `L`; + - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of `L` isometric to `M` up to the action of $O(q)$ where `q` is the discriminant group of `L`. If `check` is set to true, the function determines whether `L` is in fact unique in its genus. @@ -787,13 +771,9 @@ If `T == false`, then `V` will always be the empty list. If `T == true`, then the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in lattices in `G`, up to the actions of - $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emd`: `V` consists on representatives for all isomorphism - classes of primitive sublattices of lattices in `G` isometric to `M`, up to the - action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + - `classification = :first`: `V` consists on the first primitive embedding found; + - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; + - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. If the pair `(q, sign)` does not define a non-empty genus for integer lattices, an error is thrown. @@ -822,12 +802,8 @@ the content of `V` actually depends on the value of the symbol `classification`. There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism - classes of primitive embeddings of `M` in lattices in `G`, up to the actions of - $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emd`: `V` consists on representatives for all isomorphism - classes of primitive sublattices in lattices in `G` isometric to `M`, up to the - action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; + - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices in lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. """ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) @req classification in [:none, :first, :emb, :sublat] "Wrong symbol for classification" @@ -931,13 +907,10 @@ end Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a prime number `p`, such that `(A, B, C)` is `p`-admissible, return a set of -representatives of the double coset $G_B\backslash S\slash/G_A$ where: +representatives of the double coset $G_B\backslash S/G_A$ where: - - $G_A$ and $G_B$ are the respective images of the morphisms - $O(A, fa) -> O(q_A, \bar{fa})$ and $O(B, fb) -> O(q_B, \bar{fb})$; - - $S$ is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry - $fc'$ where $p\cdot C' \subseteq A\perpB$ and such that the type of $(C', fc'^q)$ - is equal to the type of `(C, fc)`. + - ``G_A`` and ``G_B`` are the respective images of the morphisms $O(A, fa) \to O(q_A, \bar{fa})$ and $O(B, fb) \to O(q_B, \bar{fb})$; + - ``S`` is the set of all primitive extensions $A \perp B \subseteq C'$ with isometry $fc'$ where $p\cdot C' \subseteq A\perp B$ and such that the type of $(C', fc'^q)$ is equal to the type of `(C, fc)`. If `check == true` the input triple is checked to a `p`-admissible triple of integral lattices (with isometry) with `fA` and `fB` having relatively coprime diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 393aec87d680..3d4111227558 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -1574,7 +1574,7 @@ is irreducible cyclotomic, return the signatures of $(L, f)$. In this context, if we denote $z$ a primitive `n`-th root of unity, where `n` is the order of `f`, then for each $1 \leq i \leq n/2$ such that $(i, n) = 1$, the $i$-th signature of $(L, f)$ is given by the signatures of the real quadratic -form $\Ker(f + f^{-1} - z^i - z^{-i})$. +form $\ker(f + f^{-1} - z^i - z^{-i})$. # Examples ```jldoctest @@ -1942,9 +1942,9 @@ type of $(L, f)$. In this context, the type is defined as follows: for each divisor `k` of `n`, the `k`-type of $(L, f)$ is the tuple $(H_k, A_K)$ consisting of the genus -$H_k$ of the lattice $\Ker(\Phi_k(f))$ viewed as a hermitian $\mathbb{Z}[\zeta_k]$- +$H_k$ of the lattice $\ker(\Phi_k(f))$ viewed as a hermitian $\mathbb{Z}[\zeta_k]$- lattice (so a $\mathbb{Z}$-lattice for k= 1, 2) and of the genus $A_k$ of the -$\mathbb{Z}$-lattice $\Ker(f^k-1)$. +$\mathbb{Z}$-lattice $\ker(f^k-1)$. # Examples ```jldoctest From e37629ff1fff72fbb9358cf398acecf0163a03d3 Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 19 Jul 2023 22:17:05 +0200 Subject: [PATCH 71/76] apply suggestions and update project.toml and shorten few tests --- Project.toml | 2 +- .../QuadFormAndIsom/docs/src/enumeration.md | 2 +- .../QuadFormAndIsom/docs/src/introduction.md | 4 +- .../QuadFormAndIsom/docs/src/spacewithisom.md | 2 +- experimental/QuadFormAndIsom/script.sh | 25 +++++ .../QuadFormAndIsom/src/embeddings.jl | 93 +++++++++-------- .../QuadFormAndIsom/src/enumeration.jl | 99 +++++++++---------- .../src/hermitian_miranda_morrison.jl | 22 ++--- .../src/lattices_with_isometry.jl | 76 +++++++------- experimental/QuadFormAndIsom/src/printings.jl | 2 +- .../src/spaces_with_isometry.jl | 44 ++++----- experimental/QuadFormAndIsom/src/types.jl | 13 +-- experimental/QuadFormAndIsom/test/runtests.jl | 67 +++++++------ 13 files changed, 232 insertions(+), 219 deletions(-) create mode 100755 experimental/QuadFormAndIsom/script.sh diff --git a/Project.toml b/Project.toml index 77b3fc6ebb3f..b6b2cc700995 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,7 @@ AbstractAlgebra = "0.31.0" AlgebraicSolving = "0.3.3" DocStringExtensions = "0.8, 0.9" GAP = "0.9.4" -Hecke = "0.19.6" +Hecke = "0.19.7" JSON = "^0.20, ^0.21" Nemo = "0.35.1" Polymake = "0.11.1" diff --git a/experimental/QuadFormAndIsom/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md index c4bf7cd06096..6af48b1fc997 100644 --- a/experimental/QuadFormAndIsom/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -59,7 +59,7 @@ this genus, and the order wanted (as long as the number of distinct prime divisors is at most 2). ```@docs -enumerate_classes_of_lattices_with_isometry(::ZZLat, ::Hecke.IntegerUnion) +enumerate_classes_of_lattices_with_isometry(::ZZLat, ::IntegerUnion) ``` As a remark: if $n = p^dq^e$ is the chosen order, with $p < q$ prime numbers, diff --git a/experimental/QuadFormAndIsom/docs/src/introduction.md b/experimental/QuadFormAndIsom/docs/src/introduction.md index 4734aaac4004..62b3bfc45f05 100644 --- a/experimental/QuadFormAndIsom/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -13,8 +13,8 @@ forms. ## Content We introduce two new structures -* `QuadSpaceWithIsom` -* `ZZLatWithIsom` +* [`QuadSpaceWithIsom`](@ref) +* [`ZZLatWithIsom`](@ref) The former parametrizes pairs $(V, f)$ where $V$ is a rational quadratic form and $f$ is an isometry of $V$. The latter parametrizes pairs $(L, f)$ where $L$ is an integral quadratic form, also known as $\mathbb Z$-lattice and $f$ diff --git a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md index 2bcf89ad61a6..dc8d2c828a57 100644 --- a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md @@ -85,7 +85,7 @@ Base.:^(::QuadSpaceWithIsom, ::Int) biproduct(::Vector{QuadSpaceWithIsom}) direct_product(::Vector{QuadSpaceWithIsom}) direct_sum(::Vector{QuadSpaceWithIsom}) -rescale(::QuadSpaceWithIsom, ::Hecke.RationalUnion) +rescale(::QuadSpaceWithIsom, ::RationalUnion) ``` ## Equality diff --git a/experimental/QuadFormAndIsom/script.sh b/experimental/QuadFormAndIsom/script.sh new file mode 100755 index 000000000000..208dd1bd12e2 --- /dev/null +++ b/experimental/QuadFormAndIsom/script.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# + +# some settings that avoid weirdness in sed when it tries to +# adapt to your locale (e.g. if your system uses German as system language) +export LANG=C +export LC_CTYPE=C +export LC_ALL=C + +# Files to modify (default uses all files known to git, +# but obviously you can modify it) +FILES=$(git ls-files) + +# on macOS, you may need to change the following +SED_I="sed -i" +#SED_I="gsed -i" +#SED_I="sed -i ''" + + +# AbstractAlgebra constructors +$SED_I -e "s;\bHecke.absolute_simple_field\b;absolute_simple_field;g" $FILES + + + +echo DONE diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 0b701913b4f2..f38402640160 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -1,4 +1,3 @@ -const GG = GAP.Globals ################################################################################ # @@ -37,8 +36,8 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu fD = OD(hom(D, D, fab.map)) push!(geneOBinOD, fD) end - OAtoOD = hom(OA, OD, geneOAinOD, check = false) - OBtoOD = hom(OB, OD, geneOBinOD, check = false) + OAtoOD = hom(OA, OD, geneOAinOD; check = false) + OBtoOD = hom(OB, OD, geneOBinOD; check = false) return D, AinD, BinD, OD, OAtoOD, OBtoOD end @@ -57,18 +56,18 @@ function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQ geneOAinOD = elem_type(OD)[] for f in gens(OA) m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) - fD = OD(hom(D, D, m), check=false) + fD = OD(hom(D, D, m); check=false) push!(geneOAinOD, fD) end geneOBinOD = elem_type(OD)[] for f in gens(OB) m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) - fD = OD(hom(D, D, m), check=false) + fD = OD(hom(D, D, m); check=false) push!(geneOBinOD, fD) end - OAtoOD = hom(OA, OD, geneOAinOD, check = false) - OBtoOD = hom(OB, OD, geneOBinOD, check = false) + OAtoOD = hom(OA, OD, geneOAinOD; check = false) + OBtoOD = hom(OB, OD, geneOBinOD; check = false) return D, AinD, BinD, OD, OAtoOD, OBtoOD end @@ -113,7 +112,7 @@ function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}; quad::Bool = if l == 0 Gl = N Gm = intersect(1//p*N, Nv) - rholN = torsion_quadratic_module(Gl, p*Gm, modulus = QQ(1), modulus_qf = mqf) + rholN = torsion_quadratic_module(Gl, p*Gm; modulus = QQ(1), modulus_qf = mqf) else k = l-1 m = l+1 @@ -121,7 +120,7 @@ function _rho_functor(q::TorQuadModule, p, l::Union{Integer, fmpz}; quad::Bool = Gl = intersect((1//(p^l))*N, Nv) Gm = intersect((1//(p^m))*N, Nv) B = Gk+p*Gm - rholN = torsion_quadratic_module(Gl, B, modulus = QQ(1), modulus_qf = mqf) + rholN = torsion_quadratic_module(Gl, B; modulus = QQ(1), modulus_qf = mqf) end return rholN end @@ -129,7 +128,7 @@ end # A finite bilinear module over the 2-adic integers is even if all square are # zeros. function _is_even(T, p, l) - B = gram_matrix_bilinear(_rho_functor(T, p, l, quad=false)) + 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 @@ -168,7 +167,7 @@ function _overlattice(gamma::TorQuadModuleMor, if same_ambient _glue = Vector{QQFieldElem}[lift(g) + lift(gamma(g)) for g in gens(domain(gamma))] z = zero_matrix(QQ, 0, degree(A)) - glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue], init=z) + glue = reduce(vcat, [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) @@ -180,7 +179,7 @@ function _overlattice(gamma::TorQuadModuleMor, 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, [matrix(QQ, 1, degree(cover(D)), g) for g in _glue], init=z) + glue = reduce(vcat, [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) @@ -210,7 +209,7 @@ function _overlattice(HAinD::TorQuadModuleMor, zA, _ = sub(HA, TorQuadModuleElem[]) zB, _ = sub(HB, TorQuadModuleElem[]) gamma = hom(zA, zB, zero_matrix(ZZ, 0, 0)) - return _overlattice(gamma, HAinD, HBinD, fA, fB, same_ambient = same_ambient) + return _overlattice(gamma, HAinD, HBinD, fA, fB; same_ambient = same_ambient) end ############################################################################## @@ -237,7 +236,7 @@ end function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, - order::Hecke.IntegerUnion = -1, + order::IntegerUnion = -1, f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq))) fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) V = domain(Vinq) @@ -245,7 +244,7 @@ function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor if order == -1 subs = collect(stable_submodules(V, [fV])) else - subs = collect(submodules(V, order = order)) + subs = collect(submodules(V; order = order)) filter!(s -> is_invariant(fV, s[2]), subs) end subs = TorQuadModule[s[1] for s in subs] @@ -304,9 +303,9 @@ end # same orbit if they are G-automorphic). function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMor, G::AutomorphismGroup{TorQuadModule}, - ord::Hecke.IntegerUnion, + ord::IntegerUnion, f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq)), - l::Hecke.IntegerUnion = -1) + l::IntegerUnion = -1) res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] V = domain(Vinq) @@ -376,7 +375,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] MGp = matrix_group(dim(Qp), base_ring(Qp), act_GV_Qp) - GVtoMGp = hom(GV, MGp, MGp.(act_GV_Qp), check = false) + GVtoMGp = hom(GV, MGp, MGp.(act_GV_Qp); check = false) GtoMGp = compose(GtoGV, GVtoMGp) @hassert :ZZLatWithIsom g-ngens(snf(abelian_group(H0))[1]) < dim(Qp) @@ -490,7 +489,7 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua stabNphi, _ = sub(OHM, stabNphi) reps = double_cosets(OHM, stabNphi, imM) - @vprint :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions\n" + @vprintln :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" for g in reps g = representative(g) @@ -501,7 +500,7 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua M2 = lattice_in_same_ambient_space(L, hcat(zero_matrix(QQ, rank(M), degree(N)), basis_matrix(M))) @hassert :ZZLatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) - @vprint :ZZLatWithIsom 1 "Gluing done\n" + @vprintln :ZZLatWithIsom 1 "Gluing done" GC.gc() classification == :first && return results end @@ -557,11 +556,11 @@ To be understood here that there exists a unique class of embedding of the root lattice $A_4$ in the root lattice $E_8$, and the orthogonal primitive sublattice is isometric to $A_4$. """ -function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = false) +function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = true) if check @req length(genus_representatives(L)) == 1 "L must be unique in its genus" end - return primitive_embeddings_in_primary_lattice(genus(L), M, classification = classification) + return primitive_embeddings_in_primary_lattice(genus(L), M; classification = classification) end @doc raw""" @@ -592,7 +591,7 @@ an error is thrown. function primitive_embeddings_in_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) @req is_genus(q, sign) "Invariants define the empty genus" G = genus(q, sign) - return primitive_embeddings_in_primary_lattice(G, M, classification = classification) + return primitive_embeddings_in_primary_lattice(G, M; classification = classification) end @doc raw""" @@ -651,7 +650,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific end for k in divisors(gcd(order(VM), order(qL))) - @vprint :ZZLatWithIsom 1 "Glue order: $(k)\n" + @vprintln :ZZLatWithIsom 1 "Glue order: $(k)" if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), GL, k) @@ -659,15 +658,15 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific subsL = _subgroups_orbit_representatives_and_stabilizers(id_hom(qL), GL, k) end - @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" + @vprintln :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)" for H in subsL HL = domain(H[1]) - it = submodules(VM, order = order(HL)) + it = submodules(VM; order = order(HL)) subsM = TorQuadModuleMor[j[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue - @vprint :ZZLatWithIsom 1 "Possible gluings\n" + @vprintln :ZZLatWithIsom 1 "Possible gluings" HM = subsM[1] ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) @hassert :ZZLatWithIsom 1 ok @@ -675,7 +674,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific _glue = [lift(qMinD(qM(lift((g))))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) - disc = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), + disc = torsion_quadratic_module(cover(perp), cover(ext); modulus = modulus_bilinear_form(perp), modulus_qf = modulus_quadratic_form(perp)) disc2 = rescale(disc, -1) !is_genus(disc2, (pL-pM, nL-nM)) && continue @@ -683,9 +682,9 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific classification == :none && return true, results G2 = genus(disc2, (pL-pM, nL-nM)) - @vprint :ZZLatWithIsom 1 "We can glue: $(G2)\n" + @vprintln :ZZLatWithIsom 1 "We can glue: $(G2)" Ns = representatives(G2) - @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" + @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns @@ -746,11 +745,11 @@ julia> sv (Integer lattice of rank 5 and degree 5, Integer lattice of rank 1 and degree 5, Integer lattice of rank 4 and degree 5) ``` """ -function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = false) +function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = true) if check @req length(genus_representatives(L)) == 1 "L must be unique in its genus" end - return primitive_embeddings_of_primary_lattice(genus(L), M, classification = classification) + return primitive_embeddings_of_primary_lattice(genus(L), M; classification = classification) end @doc raw""" @@ -781,7 +780,7 @@ an error is thrown. function primitive_embeddings_of_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) @req is_genus(q, sign) "Invariants define the empty genus" G = genus(q, sign) - return primitive_embeddings_of_primary_lattice(G, M, classification = classification) + return primitive_embeddings_of_primary_lattice(G, M; classification = classification) end @doc raw""" @@ -840,7 +839,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific end for k in divisors(gcd(order(qM), order(VL))) - @vprint :ZZLatWithIsom 1 "Glue order: $(k)\n" + @vprintln :ZZLatWithIsom 1 "Glue order: $(k)" if el subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) @@ -848,15 +847,15 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific subsL = _subgroups_orbit_representatives_and_stabilizers(VLinqL, GL, k) end - @vprint :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)\n" + @vprintln :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)" for H in subsL HL = domain(H[1]) - it = submodules(qM, order = order(HL)) + it = submodules(qM; order = order(HL)) subsM = TorQuadModuleMor[j[2] for j in it] filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) isempty(subsM) && continue - @vprint :ZZLatWithIsom 1 "Possible gluings\n" + @vprintln :ZZLatWithIsom 1 "Possible gluings" HM = subsM[1] ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) @hassert :ZZLatWithIsom 1 ok @@ -864,7 +863,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific _glue = [lift(qMinD(qM(lift(g)))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] ext, _ = sub(D, D.(_glue)) perp, j = orthogonal_submodule(D, ext) - disc = torsion_quadratic_module(cover(perp), cover(ext), modulus = modulus_bilinear_form(perp), + disc = torsion_quadratic_module(cover(perp), cover(ext); modulus = modulus_bilinear_form(perp), modulus_qf = modulus_quadratic_form(perp)) disc = rescale(disc, -1) !is_genus(disc, (pL-pM, nL-nM)) && continue @@ -872,9 +871,9 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific classification == :none && return true, results G2 = genus(disc, (pL-pM, nL-nM)) - @vprint :ZZLatWithIsom 1 "We can glue: $(G2)\n" + @vprintln :ZZLatWithIsom 1 "We can glue: $(G2)" Ns = representatives(G2) - @vprint :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)\n" + @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) qM2, _ = orthogonal_submodule(qM, domain(HM)) for N in Ns @@ -902,7 +901,7 @@ end Bfb::ZZLatWithIsom, Cfc::ZZLatWithIsom, p::Integer, - q::Integer = p; check=true) + q::Integer = p; check::Bool = true) -> Vector{ZZLatWithIsom} Given a triple of lattices with isometry `(A, fa)`, `(B, fb)` and `(C, fc)` and a @@ -921,7 +920,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, B::ZZLatWithIsom, C::ZZLatWithIsom, p::Integer, - q::Integer = p; check=true) + q::Integer = p; check::Bool = true) # p and q can be equal, and they will be most of the time @req is_prime(p) && is_prime(q) "p and q must be a prime number" @@ -973,8 +972,8 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, GCAB, _ = sub(OD, gene) # We compute the overlattice in this context - C2, fC2, _ = _overlattice(qAinD, qBinD, isometry(A), isometry(B), same_ambient = amb) - C2fC2 = integer_lattice_with_isometry(C2, fC2, ambient_representation = false) + C2, fC2, _ = _overlattice(qAinD, qBinD, isometry(A), isometry(B); same_ambient = amb) + C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false) qC2 = discriminant_group(C2) phi2 = hom(qC2, D, TorQuadModuleElem[D(lift(x)) for x in gens(qC2)]) @@ -1114,8 +1113,8 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # We compute the overlattice in this context, keeping track whether we # cork in a fixed ambient quadratic space - C2, fC2, extinD = _overlattice(phig, SAinD, SBinD, isometry(A), isometry(B), same_ambient = amb) - C2fC2 = integer_lattice_with_isometry(C2, fC2, ambient_representation = false) + C2, fC2, extinD = _overlattice(phig, SAinD, SBinD, isometry(A), isometry(B); same_ambient = amb) + C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false) # 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 @@ -1126,7 +1125,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # 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), + disc = torsion_quadratic_module(cover(perp), cover(ext); modulus = modulus_bilinear_form(perp), modulus_qf = modulus_quadratic_form(perp)) qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 61e7a794438a..aff313dd387b 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -12,7 +12,7 @@ ################################################################################## # The tuples in output are pairs of positive integers! -function _tuples_divisors(d::T) where T <: Hecke.IntegerUnion +function _tuples_divisors(d::T) where T <: IntegerUnion div = divisors(d) return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] end @@ -21,7 +21,7 @@ end # discriminant for the genera A and B to glue to fit in C. d is # the determinant of C, m the maximal p-valuation of the gcd of # d1 and dp. -function _find_D(d::T, m::Int, p::Int) where T <: Hecke.IntegerUnion +function _find_D(d::T, m::Int, p::Int) where T <: IntegerUnion @hassert :ZZLatWithIsom 1 is_prime(p) @hassert :ZZLatWithIsom 1 d != 0 @@ -47,22 +47,22 @@ end # This is line 10 of Algorithm 1. We need the condition on the even-ness of # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C -function _find_L(pG::Int, nG::Int, r::Int, d::Hecke.RationalUnion, s::ZZRingElem, l::ZZRingElem, p::Hecke.IntegerUnion, even = true; pos::Int = -1) +function _find_L(pG::Int, nG::Int, r::Int, d::RationalUnion, s::ZZRingElem, l::ZZRingElem, p::IntegerUnion, even = true; pos::Int = -1) L = ZZGenus[] if r == 0 && d == 1 return ZZGenus[genus(integer_lattice(gram = matrix(QQ, 0, 0, [])))] end if pos >= 0 neg = r-pos - gen = integer_genera((pos, neg), d, even=even) - filter!(G -> Hecke.divides(numerator(scale(G)), s)[1], gen) - filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) + gen = integer_genera((pos, neg), d; even=even) + filter!(G -> divides(numerator(scale(G)), s)[1], gen) + filter!(G -> divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) else for (s1,s2) in [(s,t) for s=0:pG for t=0:nG if s+t==r] - gen = integer_genera((s1,s2), d, even=even) - filter!(G -> Hecke.divides(numerator(scale(G)), s)[1], gen) - filter!(G -> Hecke.divides(p*l, numerator(level(G)))[1], gen) + gen = integer_genera((s1,s2), d; even=even) + filter!(G -> divides(numerator(scale(G)), s)[1], gen) + filter!(G -> divides(p*l, numerator(level(G)))[1], gen) append!(L, gen) end end @@ -113,7 +113,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) return false end - @req Hecke.divides(rank(B), p-1)[1] "p-1 must divide the rank of B" + @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" lA = ngens(discriminant_group(A)) lB = ngens(discriminant_group(B)) @@ -137,7 +137,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) return false end - if !(Hecke.divides(scale(AperpB), scale(C))[1] && Hecke.divides(p*level(C), level(AperpB))[1]) + if !(divides(scale(AperpB), scale(C))[1] && divides(p*level(C), level(AperpB))[1]) return false end @@ -145,7 +145,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B qA = discriminant_group(A) qB = discriminant_group(B) - if !Hecke.divides(numerator(det(C)), p)[1] + if !divides(numerator(det(C)), p)[1] return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1])[1] end @@ -235,8 +235,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) end function is_admissible_triple(A::T, B::T, C::T, p::Integer) where T <: Union{ZZLat, ZZLatWithIsom} - L = ZZGenus[genus(D) for D = (A, B, C)] - return is_admissible_triple(L[1], L[2], L[3], p) + return is_admissible_triple(genus(A), genus(B), genus(C), p) end @doc raw""" @@ -305,8 +304,8 @@ function admissible_triples(G::ZZGenus, p::Integer; pA::Int = -1, pB::Int = -1) m = min(ep, r1) D = _find_D(d, m, p) for (d1, dp) in D - L1 = _find_L(pG, nG, r1, d1, numerator(scale(G)), numerator(level(G)), p, even, pos = pA) - Lp = _find_L(pG, nG, rp, dp, numerator(scale(G)), numerator(level(G)), p, even, pos = pB) + L1 = _find_L(pG, nG, r1, d1, numerator(scale(G)), numerator(level(G)), p, even; pos = pA) + Lp = _find_L(pG, nG, rp, dp, numerator(scale(G)), numerator(level(G)), p, even; pos = pB) for (A, B) in [(A, B) for A in L1 for B in Lp] if is_admissible_triple(A, B, G, p) push!(L, (A, B)) @@ -317,7 +316,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 = pA, pB = pB) +admissible_triples(L::T, p::Integer; pA::Int = -1, pB::Int = -1) where T <: Union{ZZLat, ZZLatWithIsom} = admissible_triples(genus(L), p; pA, pB) ################################################################################## # @@ -333,7 +332,7 @@ function _ideals_of_norm(E, d::QQFieldElem) elseif numerator(d) == 1 return [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 [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)] end end @@ -344,8 +343,8 @@ function _ideals_of_norm(E, d::ZZRingElem) OK = maximal_order(K) OE = maximal_order(E) DE = different(OE) - ids = [] - primes = [] + ids = Hecke.fractional_ideal_type(OE)[] + primes = Vector{typeof(1*OE)}[] for p in prime_divisors(d) v = valuation(d, p) pd = [P[1] for P in prime_decomposition(OK, p)] @@ -359,7 +358,7 @@ function _ideals_of_norm(E, d::ZZRingElem) push!(primes, [P^e for e in 0:divrem(v, nv)[1]]) end end - for I in Hecke.cartesian_product_iterator(primes, inplace=false) + for I in Hecke.cartesian_product_iterator(primes; inplace=false) I = prod(I) if absolute_norm(I) == d push!(ids, fractional_ideal(OE, I)) @@ -377,7 +376,7 @@ function _possible_signatures(s1, s2, E, rk) ok, q = Hecke.is_cyclotomic_type(E) @hassert :ZZLatWithIsom 1 ok @hassert :ZZLatWithIsom 1 iseven(s2) - @hassert :ZZLatWithIsom 1 Hecke.divides(2*(s1+s2), euler_phi(q))[1] + @hassert :ZZLatWithIsom 1 divides(2*(s1+s2), euler_phi(q))[1] l = divexact(s2, 2) K = base_field(E) inf = real_places(K) @@ -445,21 +444,21 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) reps = ZZLatWithIsom[] if n*m < 3 - @vprint :ZZLatWithIsom 1 "Order smaller than 3\n" + @vprintln :ZZLatWithIsom 1 "Order smaller than 3" f = (-1)^(n*m+1)*identity_matrix(QQ, rk) G = genus(Lf) repre = representatives(G) - @vprint :ZZLatWithIsom 1 "$(length(repre)) representative(s)\n" + @vprintln :ZZLatWithIsom 1 "$(length(repre)) representative(s)" for LL in repre - is_of_same_type(Lf, integer_lattice_with_isometry(LL, f^m, check=false)) && push!(reps, integer_lattice_with_isometry(LL, f, check=false)) + is_of_same_type(Lf, integer_lattice_with_isometry(LL, f^m; check=false)) && push!(reps, integer_lattice_with_isometry(LL, f; check=false)) end return reps end !iseven(s2) && return reps - @vprint :ZZLatWithIsom 1 "Order bigger than 3\n" - ok, rk = Hecke.divides(rk, euler_phi(n*m)) + @vprintln :ZZLatWithIsom 1 "Order bigger than 3" + ok, rk = divides(rk, euler_phi(n*m)) ok || return reps gene = Hecke.HermGenus[] @@ -467,24 +466,24 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) - @vprint :ZZLatWithIsom 1 "We have the different\n" + @vprintln :ZZLatWithIsom 1 "We have the different" ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) - @vprint :ZZLatWithIsom 1 "All possible ideal dets: $(length(detE))\n" + @vprintln :ZZLatWithIsom 1 "All possible ideal dets: $(length(detE))" signatures = _possible_signatures(s1, s2, E, rk) - @vprint :ZZLatWithIsom 1 "All possible signatures: $(length(signatures))\n" + @vprintln :ZZLatWithIsom 1 "All possible signatures: $(length(signatures))" for dd in detE, sign in signatures - append!(gene, hermitian_genera(E, rk, sign, dd, min_scale = inv(DE), max_scale = numerator(dd)*DE)) + append!(gene, hermitian_genera(E, rk, sign, dd; min_scale = inv(DE), max_scale = numerator(dd)*DE)) end - gene = unique(gene) + unique!(gene) - @vprint :ZZLatWithIsom 1 "All possible genera: $(length(gene))\n" + @vprintln :ZZLatWithIsom 1 "All possible genera: $(length(gene))" for g in gene - @vprint :ZZLatWithIsom 1 "g = $g\n" + @vprintln :ZZLatWithIsom 1 "g = $g" H = representative(g) if !is_integral(DE*scale(H)) continue @@ -492,7 +491,7 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) if is_even(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) continue end - @vprint :ZZLatWithIsom 1 "$H\n" + @vprintln :ZZLatWithIsom 1 "$H" M, fM = Hecke.trace_lattice_with_isometry(H) det(M) == d || continue M = integer_lattice_with_isometry(M, fM) @@ -506,7 +505,7 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) end gr = genus_representatives(H) for HH in gr - M, fM = Hecke.trace_lattice_with_isometry(HH) + M, fM = trace_lattice_with_isometry(HH) push!(reps, integer_lattice_with_isometry(M, fM)) end end @@ -561,9 +560,9 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = @req p != q "Prime numbers must be distinct" reps = ZZLatWithIsom[] - @vprint :ZZLatWithIsom 1 "Compute admissible triples\n" - atp = admissible_triples(Lf, p, pA = pA, pB = pB) - @vprint :ZZLatWithIsom 1 "$(length(atp)) admissible triple(s)\n" + @vprintln :ZZLatWithIsom 1 "Compute admissible triples" + atp = admissible_triples(Lf, p; pA, pB) + @vprintln :ZZLatWithIsom 1 "$(length(atp)) admissible triple(s)" for (A, B) in atp LB = integer_lattice_with_isometry(representative(B)) RB = representatives_of_hermitian_type(LB, p*q^e) @@ -572,7 +571,7 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = end LA = integer_lattice_with_isometry(representative(A)) RA = representatives_of_hermitian_type(LA, q^e) - for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB], inplace=false) + for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]; inplace=false) E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p) append!(reps, E) GC.gc() @@ -641,8 +640,8 @@ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) A = splitting_of_hermitian_prime_power(A0, p) is_empty(A) && return reps B = splitting_of_prime_power(B0, p) - for (L1, L2) in Hecke.cartesian_product_iterator([A, B], inplace=false) - b == 1 && !Hecke.divides(order_of_isometry(L1), p)[1] && !Hecke.divides(order_of_isometry(L2), p)[1] && continue + for (L1, L2) in Hecke.cartesian_product_iterator([A, B]; inplace=false) + b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue E = admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p) @hassert :ZZLatWithIsom 1 b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) @@ -687,7 +686,7 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) phi = minimal_polynomial(Lf) chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) - @req Hecke.divides(chi, phi)[1] "Minimal polynomial is not of the correct form" + @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" reps = ZZLatWithIsom[] @@ -696,14 +695,14 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) end A0 = kernel_lattice(Lf, p^d*q^e) - bool, r = Hecke.divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) + bool, r = divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) @hassert :ZZLatWithIsom 1 bool B0 = kernel_lattice(Lf, r) A = representatives_of_hermitian_type(A0, p) is_empty(A) && return reps B = splitting_of_pure_mixed_prime_power(B0, p) - for (LA, LB) in Hecke.cartesian_product_iterator([A, B], inplace=false) + for (LA, LB) in Hecke.cartesian_product_iterator([A, B]; inplace=false) E = admissible_equivariant_primitive_extensions(LB, LA, Lf, q, p) GC.gc() append!(reps, E) @@ -792,7 +791,7 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) A = splitting_of_pure_mixed_prime_power(A0, p) isempty(A) && return reps B = splitting_of_mixed_prime_power(B0, p, 0) - for (LA, LB) in Hecke.cartesian_product_iterator([A, B], inplace=false) + for (LA, LB) in Hecke.cartesian_product_iterator([A, B]; inplace=false) E = admissible_equivariant_primitive_extensions(LB, LA, Lf, p) GC.gc() if b == 1 @@ -816,7 +815,7 @@ lattices as an input - the function first computes a representative of `G`. Note that currently we support only orders which admit at most 2 prime divisors. """ -function enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::Hecke.IntegerUnion) +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)) @@ -839,12 +838,12 @@ function enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::Hecke.Inte return reps end -enumerate_classes_of_lattices_with_isometry(G::ZZGenus, order::Hecke.IntegerUnion) = +enumerate_classes_of_lattices_with_isometry(G::ZZGenus, order::IntegerUnion) = enumerate_classes_of_lattices_with_isometry(representative(G), order) # We compute representatives of isomorphism classes of lattice with isometry in # the genus of `L` and with prime power order q^vq. -function _enumerate_prime_power(L::ZZLat, q::Hecke.IntegerUnion, vq::Hecke.IntegerUnion) +function _enumerate_prime_power(L::ZZLat, q::IntegerUnion, vq::IntegerUnion) @hassert :ZZLatWithIsom 1 is_prime(q) @hassert :ZZLatWithIsom 1 vq >= 1 Lq = splitting_of_prime_power(integer_lattice_with_isometry(L), q, 1) @@ -869,7 +868,7 @@ end # from p. Computes representatives of isomorphism classes of lattice with # isometry in the genus of `N`, of order p^vq*q^vq and whose q-type is the same # as `N`. -function _split_prime_power(N::ZZLatWithIsom, p::Hecke.IntegerUnion, vp::Hecke.IntegerUnion) +function _split_prime_power(N::ZZLatWithIsom, p::IntegerUnion, vp::IntegerUnion) pd = prime_divisors(order_of_isometry(N)) @hassert :ZZLatWithIsom 1 (length(pd) == 1 && !(p in pd)) Np = splitting_of_prime_power(N, p, 1) diff --git a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index 60c7df31491f..cc31521b465b 100644 --- a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl +++ b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl @@ -13,7 +13,7 @@ function _get_quotient_split(P::Hecke.NfRelOrdIdl, i::Int) OE = order(P) E = nf(OE) - Eabs, EabstoE = Hecke.absolute_simple_field(E) + Eabs, EabstoE = absolute_simple_field(E) Pabs = EabstoE\P OEabs = order(Pabs) @@ -61,7 +61,7 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) K = base_field(E) p = minimum(P) - Eabs, EabstoE = Hecke.absolute_simple_field(E) + Eabs, EabstoE = absolute_simple_field(E) Pabs = EabstoE\P OEabs = order(Pabs) Rp, mRp = quo(OK, p^i) @@ -130,7 +130,7 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) end j = Int(ceil(jj)) - Eabs, EabstoE = Hecke.absolute_simple_field(E) + Eabs, EabstoE = absolute_simple_field(E) OK = order(p) Rp, mRp = quo(OK, p^j) URp, mURp = unit_group(Rp) @@ -318,7 +318,7 @@ 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) qL2, fqL2 = discriminant_group(Lf2) OqL2 = orthogonal_group(qL2) ok, phi12 = is_isometric_with_isometry(qL, qL2) @@ -416,7 +416,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # Now according to Theorem 6.15 of BH23, it remains to quotient out the image # of the units in E of norm 1. - Eabs, EabstoE = Hecke.absolute_simple_field(E) + Eabs, EabstoE = absolute_simple_field(E) OEabs = maximal_order(Eabs) UOEabs, mUOEabs = unit_group(OEabs) OK = base_ring(OE) @@ -468,7 +468,7 @@ 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, gens(G2), SQtoGSQ.(imgs); check=false) f = compose(GtoG2, f2) return f @@ -530,7 +530,7 @@ Oscar.canonical_unit(x::NfOrdQuoRingElem) = one(parent(x)) function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T, P::Hecke.NfRelOrdIdl, BHp::T) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} E = base_ring(codomain(res)) - Eabs, EabstoE = Hecke.absolute_simple_field(E) + Eabs, EabstoE = absolute_simple_field(E) Pabs = EabstoE\P OEabs = order(Pabs) q = domain(g) @@ -680,7 +680,7 @@ function _approximate_isometry(H::Hecke.HermLat, H2::Hecke.HermLat, g::Automorph return identity_matrix(E, 1) end - BHp = local_basis_matrix(H, minimum(P), type = :submodule) + BHp = local_basis_matrix(H, minimum(P); type = :submodule) BHp_inv = inv(BHp) Bps = _local_basis_modular_submodules(H2, minimum(P), a, res) Bp = reduce(vcat, Bps) @@ -727,7 +727,7 @@ function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfOrdIdl, a: B2 = basis_matrix(L2) gene = [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) + b = local_basis_matrix(H2, p; type = :submodule) end push!(subs, b) end @@ -758,7 +758,7 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) return Hecke._special_unit(P, minimum(P)) end K = base_field(E) - Eabs, EabstoE = Hecke.absolute_simple_field(E) + Eabs, EabstoE = absolute_simple_field(E) Pabs = EabstoE\P OEabs = order(Pabs) while true @@ -768,7 +768,7 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) nug = valuation(g, Pabs) if nu == nug d = denominator(g+1, OEabs) - rt = roots(t^2 - (g+1)*d^2, max_roots = 1, ispure = true, is_normal=true) + rt = roots(t^2 - (g+1)*d^2; max_roots = 1, ispure = true, is_normal=true) if !is_empty(rt) rho = (1+rt[1])//2 @hassert :ZZLatWithIsom 1 valuation(rho, Pabs) == 1-e diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 3d4111227558..4a696ac522c5 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -14,7 +14,7 @@ Given a lattice with isometry $(L, f)$, return the underlying lattice `L`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> lattice(Lf) === L true @@ -31,7 +31,7 @@ Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> isometry(Lf) [-1 0 0 0 0] @@ -57,7 +57,7 @@ together with `V` into a container type `QuadSpaceWithIsom`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> Vf = ambient_space(Lf) Quadratic space of dimension 5 @@ -85,7 +85,7 @@ space of `L` inducing `f` on `L`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> ambient_isometry(Lf) [-1 0 0 0 0] @@ -107,7 +107,7 @@ isometry `f`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> order_of_isometry(Lf) == 2 true @@ -131,7 +131,7 @@ Given a lattice with isometry $(L, f)$, return the rank of the underlying lattic ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> rank(Lf) 5 @@ -149,7 +149,7 @@ underlying isometry `f`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> factor(characteristic_polynomial(Lf)) 1 * (x + 1)^5 @@ -167,7 +167,7 @@ underlying isometry `f`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> minimal_polynomial(Lf) x + 1 @@ -185,7 +185,7 @@ lattice `L`. ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true); +julia> Lf = integer_lattice_with_isometry(L; neg=true); julia> genus(Lf) Genus symbol for integer lattices @@ -541,7 +541,7 @@ julia> G = matrix(QQ, 5, 5, [ 2 -1 0 0 0; 0 0 -1 2 -1; 0 0 0 -1 2]); -julia> L = integer_lattice(B, gram = G); +julia> L = integer_lattice(B; gram = G); julia> f = matrix(QQ, 5, 5, [ 1 0 0 0 0; -1 -1 -1 -1 -1; @@ -564,7 +564,7 @@ julia> ambient_isometry(Lf) [ 0 0 0 1 0] [ 0 0 1 0 0] -julia> Lf2 = integer_lattice_with_isometry(L, isometry(Lf), ambient_representation=false) +julia> Lf2 = integer_lattice_with_isometry(L, isometry(Lf); ambient_representation=false) Integer lattice of rank 3 and degree 5 with isometry of finite order 1 given by @@ -593,9 +593,9 @@ function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true if ambient_representation f_ambient = f - Vf = quadratic_space_with_isometry(ambient_space(L), f_ambient, check=check) + Vf = quadratic_space_with_isometry(ambient_space(L), f_ambient; check=check) B = basis_matrix(L) - ok, f = can_solve_with_solution(B, B*f_ambient, side = :left) + ok, f = can_solve_with_solution(B, B*f_ambient; side = :left) @req ok "Isometry does not restrict to L" else V = ambient_space(L) @@ -604,7 +604,7 @@ function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true C = vcat(B, B2) f_ambient = block_diagonal_matrix([f, identity_matrix(QQ, nrows(B2))]) f_ambient = inv(C)*f_ambient*C - Vf = quadratic_space_with_isometry(V, f_ambient, check=check) + Vf = quadratic_space_with_isometry(V, f_ambient; check=check) end n = multiplicative_order(f) @@ -629,7 +629,7 @@ If `neg` is set to `true`, then the isometry `f` is negative the identity of `L` ```jldoctest julia> L = root_lattice(:A,5); -julia> Lf = integer_lattice_with_isometry(L, neg=true) +julia> Lf = integer_lattice_with_isometry(L; neg=true) Integer lattice of rank 5 and degree 5 with isometry of finite order 2 given by @@ -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)::ZZLatWithIsom end @doc raw""" @@ -729,8 +729,8 @@ Integer lattice of rank 3 and degree 5 ``` """ function lattice(Vf::QuadSpaceWithIsom, B::MatElem{<:RationalUnion}; isbasis::Bool = true, check::Bool = true) - L = lattice(space(Vf), B, isbasis=isbasis, check=check) - ok, fB = can_solve_with_solution(basis_matrix(L), basis_matrix(L)*isometry(Vf), side = :left) + L = lattice(space(Vf), B; isbasis=isbasis, check=check) + ok, fB = can_solve_with_solution(basis_matrix(L), basis_matrix(L)*isometry(Vf); side = :left) n = is_zero(fB) ? -1 : multiplicative_order(fB) @req ok "The lattice defined by B is not preserved under the action of the isometry of Vf" return ZZLatWithIsom(Vf, L, fB, n) @@ -781,7 +781,7 @@ true function lattice_in_same_ambient_space(L::ZZLatWithIsom, B::MatElem; check::Bool = true) @req !check || (rank(B) == nrows(B)) "The rows of B must define a free system of vectors" Vf = ambient_space(L) - return lattice(Vf, B, check = check) + return lattice(Vf, B; check = check) end ############################################################################### @@ -843,8 +843,8 @@ with gram matrix [ 0 0 0 -1//2 1] ``` """ -function rescale(Lf::ZZLatWithIsom, a::Hecke.RationalUnion) - return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf), check=false) +function rescale(Lf::ZZLatWithIsom, a::RationalUnion) + return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf); check=false) end @doc raw""" @@ -932,7 +932,7 @@ true """ function dual(Lf::ZZLatWithIsom) @req is_integral(Lf) "Underlying lattice must be integral" - return lattice_in_same_ambient_space(Lf, basis_matrix(dual(lattice(Lf))), check = false) + return lattice_in_same_ambient_space(Lf, basis_matrix(dual(lattice(Lf))); check = false) end @doc raw""" @@ -980,11 +980,11 @@ true ``` """ function lll(Lf::ZZLatWithIsom; same_ambient::Bool = true) - L2 = lll(lattice(Lf), same_ambient = same_ambient) + L2 = lll(lattice(Lf); same_ambient = same_ambient) if same_ambient - return lattice_in_same_ambient_space(Lf, basis_matrix(L2), check=false) + return lattice_in_same_ambient_space(Lf, basis_matrix(L2); check=false) else - return integer_lattice_with_isometry(L2, isometry(Lf), check=false, ambient_representation=false) + return integer_lattice_with_isometry(L2, isometry(Lf); check=false, ambient_representation=false) end end @@ -1076,7 +1076,7 @@ Integer lattice of rank 10 and degree 10 function direct_sum(x::Vector{ZZLatWithIsom}) Vf, inj = direct_sum(ambient_space.(x)) Bs = diagonal_matrix(basis_matrix.(x)) - return lattice(Vf, Bs, check=false), inj + return lattice(Vf, Bs; check=false), inj end direct_sum(x::Vararg{ZZLatWithIsom}) = direct_sum(collect(x)) @@ -1169,7 +1169,7 @@ Integer lattice of rank 10 and degree 10 function direct_product(x::Vector{ZZLatWithIsom}) Vf, proj = direct_product(ambient_space.(x)) Bs = diagonal_matrix(basis_matrix.(x)) - return lattice(Vf, Bs, check=false), proj + return lattice(Vf, Bs; check=false), proj end direct_product(x::Vararg{ZZLatWithIsom}) = direct_product(collect(x)) @@ -1291,7 +1291,7 @@ julia> matrix(compose(inj[1], proj[2])) function biproduct(x::Vector{ZZLatWithIsom}) Vf, inj, proj = biproduct(ambient_space.(x)) Bs = diagonal_matrix(basis_matrix.(x)) - return lattice(Vf, Bs, check=false), inj, proj + return lattice(Vf, Bs; check=false), inj, proj end biproduct(x::Vararg{ZZLatWithIsom}) = biproduct(collect(x)) @@ -1418,7 +1418,7 @@ true @req is_of_hermitian_type(Lf) "Lf is not of hermitian type" f = isometry(Lf) - H, res = Hecke.hermitian_structure_with_transfer_data(lattice(Lf), f, ambient_representation = false) + H, res = hermitian_structure_with_transfer_data(lattice(Lf), f; ambient_representation = false) set_attribute!(Lf, :transfer_data, res) return H @@ -1484,7 +1484,7 @@ function discriminant_group(Lf::ZZLatWithIsom) f = ambient_isometry(Lf) q = discriminant_group(L) Oq = orthogonal_group(q) - return (q, Oq(gens(matrix_group(f))[1], check = false))::Tuple{TorQuadModule, AutomorphismGroupElem{TorQuadModule}} + return (q, Oq(gens(matrix_group(f))[1]; check = false))::Tuple{TorQuadModule, AutomorphismGroupElem{TorQuadModule}} end @doc raw""" @@ -1524,12 +1524,12 @@ julia> order(G) 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) - GL = Oscar._orthogonal_group(qL, UL, check = false) + GL = Oscar._orthogonal_group(qL, UL; check = false) 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)]))] unique!(UL) - GL = Oscar._orthogonal_group(qL, UL, check = false) + GL = Oscar._orthogonal_group(qL, UL; check = false) 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) @@ -1622,7 +1622,7 @@ end ############################################################################### function _divides(k::IntExt, n::Int) - is_finite(k) && return Hecke.divides(k, n)[1] + is_finite(k) && return divides(k, n)[1] return true end @@ -1784,7 +1784,7 @@ with gram matrix ``` """ function invariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) - return invariant_lattice(L, matrix.(gens(G)), ambient_representation = ambient_representation) + return invariant_lattice(L, matrix.(gens(G)); ambient_representation = ambient_representation) end @doc raw""" @@ -1858,7 +1858,7 @@ true ``` """ function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) - F = invariant_lattice(L, G, ambient_representation = ambient_representation) + F = invariant_lattice(L, G; ambient_representation = ambient_representation) C = orthogonal_submodule(L, F) gene = QQMatrix[] for g in gens(G) @@ -1911,7 +1911,7 @@ with gram matrix ``` """ function invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) - F = invariant_lattice(L, G, ambient_representation = ambient_representation) + F = invariant_lattice(L, G; ambient_representation = ambient_representation) C = orthogonal_submodule(L, F) gene = QQMatrix[] for g in gens(G) @@ -1976,7 +1976,7 @@ true for l in divs Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl), check=false, ambient_representation=false) + Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl); check=false, ambient_representation=false) end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) @@ -2015,7 +2015,7 @@ function is_of_type(L::ZZLatWithIsom, t::Dict) Hl = kernel_lattice(L, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1, 1, 2]) t[l][1] isa Hecke.HermGenus || return false - Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl), check=false, ambient_representation=false, E = base_field(t[l][1])) + Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl); check=false, ambient_representation=false, E = base_field(t[l][1])) end genus(Hl) == t[l][1] || return false Al = kernel_lattice(L, x^l-1) diff --git a/experimental/QuadFormAndIsom/src/printings.jl b/experimental/QuadFormAndIsom/src/printings.jl index 6fa59d025d08..5e1b334b2400 100644 --- a/experimental/QuadFormAndIsom/src/printings.jl +++ b/experimental/QuadFormAndIsom/src/printings.jl @@ -16,7 +16,7 @@ function Base.show(io::IO, ::MIME"text/plain", Lf::ZZLatWithIsom) end println(io, "given by") show(io, MIME"text/plain"(), isometry(Lf)) - println(io) + print(io, Dedent()) end function Base.show(io::IO, Lf::ZZLatWithIsom) diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 52579726024f..95773f071c1a 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -14,7 +14,7 @@ Given a quadratic space with isometry $(V, f)$, return the underlying space `V`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> space(Vf) === V true @@ -32,7 +32,7 @@ Given a quadratic space with isometry $(V, f)$, return the underlying isometry ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> isometry(Vf) [-1 0] @@ -51,7 +51,7 @@ underlying isometry `f`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> order_of_isometry(Vf) == 2 true @@ -75,7 +75,7 @@ space `V`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> rank(Vf) == 2 true @@ -93,7 +93,7 @@ underlying space of `V`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> dim(Vf) == 2 true @@ -111,7 +111,7 @@ polynomial of the underlying isometry `f`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> characteristic_polynomial(Vf) x^2 + 2*x + 1 @@ -129,7 +129,7 @@ polynomial of the underlying isometry `f`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> minimal_polynomial(Vf) x + 1 @@ -147,7 +147,7 @@ of the underlying space `V` with respect to its standard basis. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> is_one(gram_matrix(Vf)) true @@ -165,7 +165,7 @@ of the underlying space `V`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> is_one(det(Vf)) true @@ -183,7 +183,7 @@ of the underlying space `V`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> discriminant(Vf) -1 @@ -201,7 +201,7 @@ space `V` is positive definite. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> is_positive_definite(Vf) true @@ -219,7 +219,7 @@ space `V` is negative definite. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> is_negative_definite(Vf) false @@ -237,7 +237,7 @@ space `V` is definite. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> is_definite(Vf) true @@ -255,7 +255,7 @@ underlying space `V`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> diagonal(Vf) 2-element Vector{QQFieldElem}: @@ -275,7 +275,7 @@ tuple of the underlying space `V`. ```jldoctest julia> V = quadratic_space(QQ, 2); -julia> Vf = quadratic_space_with_isometry(V, neg = true); +julia> Vf = quadratic_space_with_isometry(V; neg = true); julia> signature_tuple(Vf) (2, 0, 0) @@ -365,7 +365,7 @@ Quadratic space of dimension 2 function quadratic_space_with_isometry(V::Hecke.QuadSpace; neg::Bool = false) f = identity_matrix(QQ, dim(V)) f = neg ? -f : f - return quadratic_space_with_isometry(V, f, check=false) + return quadratic_space_with_isometry(V, f; check=false) end ############################################################################### @@ -412,8 +412,8 @@ with gram matrix [-1//2 1] ``` """ -function rescale(Vf::QuadSpaceWithIsom, a::Hecke.RationalUnion) - return quadratic_space_with_isometry(rescale(space(Vf), a), isometry(Vf), check = false) +function rescale(Vf::QuadSpaceWithIsom, a::RationalUnion) + return quadratic_space_with_isometry(rescale(space(Vf), a), isometry(Vf); check = false) end @doc raw""" @@ -453,7 +453,7 @@ Quadratic space of dimension 2 ``` """ function Base.:^(Vf::QuadSpaceWithIsom, n::Int) - return quadratic_space_with_isometry(space(Vf), isometry(Vf)^n, check=false) + return quadratic_space_with_isometry(space(Vf), isometry(Vf)^n; check=false) end @doc raw""" @@ -546,7 +546,7 @@ with gram matrix function direct_sum(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj = direct_sum(space.(x)) f = block_diagonal_matrix(isometry.(x)) - return quadratic_space_with_isometry(V, f, check=false), inj + return quadratic_space_with_isometry(V, f; check=false), inj end direct_sum(x::Vararg{QuadSpaceWithIsom}) = direct_sum(collect(x)) @@ -641,7 +641,7 @@ with gram matrix function direct_product(x::Vector{T}) where T <: QuadSpaceWithIsom V, proj = direct_product(space.(x)) f = block_diagonal_matrix(isometry.(x)) - return quadratic_space_with_isometry(V, f, check=false), proj + return quadratic_space_with_isometry(V, f; check=false), proj end direct_product(x::Vararg{QuadSpaceWithIsom}) = direct_product(collect(x)) @@ -757,7 +757,7 @@ julia> matrix(compose(inj[1], proj[2])) function biproduct(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj, proj = biproduct(space.(x)) f = block_diagonal_matrix(isometry.(x)) - return quadratic_space_with_isometry(V, f, check=false), inj, proj + return quadratic_space_with_isometry(V, f; check=false), inj, proj end biproduct(x::Vararg{QuadSpaceWithIsom}) = biproduct(collect(x)) diff --git a/experimental/QuadFormAndIsom/src/types.jl b/experimental/QuadFormAndIsom/src/types.jl index 7f44c407e0fc..eeae424c0e39 100644 --- a/experimental/QuadFormAndIsom/src/types.jl +++ b/experimental/QuadFormAndIsom/src/types.jl @@ -52,11 +52,7 @@ Quadratic space of dimension 6 n::IntExt function QuadSpaceWithIsom(V::Hecke.QuadSpace, f::QQMatrix, n::IntExt) - z = new() - z.V = V - z.f = f - z.n = n - return z + return new(V, f, n) end end @@ -198,11 +194,6 @@ space, we can compare them as $\mathbb Z$-modules, endowed with an isometry. n::IntExt function ZZLatWithIsom(Vf::QuadSpaceWithIsom, Lb::ZZLat, f::QQMatrix, n::IntExt) - z = new() - z.Lb = Lb - z.f = f - z.Vf = Vf - z.n = n - return z + return new(Vf, Lb, f, n) end end diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index 9e5680b767c5..ea9dc718412b 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -12,14 +12,14 @@ using Oscar @test sprint(_show_details, X) isa String @test sprint(Oscar.to_oscar, X) isa String @test sprint(show, X) isa String - @test sprint(show, X, context=:supercompact => true) isa String + @test sprint(show, X; context=:supercompact => true) isa String end end @testset "Spaces with isometry" begin D5 = root_lattice(:D, 5) V = ambient_space(D5) - Vf = @inferred quadratic_space_with_isometry(V, neg=true) + Vf = @inferred quadratic_space_with_isometry(V; neg=true) @test is_one(-isometry(Vf)) @test order_of_isometry(Vf) == 2 @test space(Vf) === V @@ -47,7 +47,7 @@ end @test order_of_isometry(direct_sum(Vf, Vf, Vf)[1]) == 3 @test det(direct_product(Vf, Vf)[1]) == det(Vf)^2 - @test Vf != quadratic_space_with_isometry(V, neg=true) + @test Vf != quadratic_space_with_isometry(V; neg=true) @test length(unique([Vf, quadratic_space_with_isometry(V, isometry(Vf))])) == 1 @test Vf^(order_of_isometry(Vf)+1) == Vf @@ -58,13 +58,13 @@ end @testset "Lattices with isometry" begin A3 = root_lattice(:A, 3) - agg = automorphism_group_generators(A3, ambient_representation = false) - agg_ambient = automorphism_group_generators(A3, ambient_representation = true) + agg = automorphism_group_generators(A3; ambient_representation = false) + agg_ambient = automorphism_group_generators(A3; ambient_representation = true) f = rand(agg) g_ambient = rand(agg_ambient) L = integer_lattice(gram = matrix(QQ, 0, 0, [])) - Lf = integer_lattice_with_isometry(L, neg = true) + Lf = integer_lattice_with_isometry(L; neg = true) @test order_of_isometry(Lf) == -1 L = @inferred integer_lattice_with_isometry(A3) @@ -94,16 +94,16 @@ end nf = multiplicative_order(f) @test_throws ArgumentError integer_lattice_with_isometry(A3, zero_matrix(QQ, 0, 0)) - L2 = @inferred integer_lattice_with_isometry(A3, f, ambient_representation = false) + L2 = @inferred integer_lattice_with_isometry(A3, f; ambient_representation = false) @test order_of_isometry(L2) == nf L2v = @inferred dual(L2) @test order_of_isometry(L2v) == nf @test ambient_isometry(L2v) == ambient_isometry(L2) - L3 = @inferred integer_lattice_with_isometry(A3, g_ambient, ambient_representation = true) + L3 = @inferred integer_lattice_with_isometry(A3, g_ambient; ambient_representation = true) @test order_of_isometry(L3) == multiplicative_order(g_ambient) @test L3^(order_of_isometry(L3)+1) == L3 - @test genus(lll(L3, same_ambient=false)) == genus(L3) + @test genus(lll(L3; same_ambient=false)) == genus(L3) L4 = @inferred rescale(L3, QQ(1//4)) @test !is_integral(L4) @@ -120,7 +120,7 @@ end B = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 1 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 0 0 1 0; 0 0 0 0 0 0 0 1]); G = matrix(QQ, 8, 8, [-4 2 0 0 0 0 0 0; 2 -4 2 0 0 0 0 0; 0 2 -4 2 0 0 0 2; 0 0 2 -4 2 0 0 0; 0 0 0 2 -4 2 0 0; 0 0 0 0 2 -4 2 0; 0 0 0 0 0 2 -4 0; 0 0 2 0 0 0 0 -4]); - L = integer_lattice(B, gram = G); + L = integer_lattice(B; gram = G); f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; -2 -4 -6 -4 -3 -2 -1 -3; 2 4 6 5 4 3 2 3; -1 -2 -3 -3 -3 -2 -1 -1; 0 0 0 0 1 0 0 0; 1 2 3 3 2 1 0 2]); Lf = integer_lattice_with_isometry(L, f); @@ -152,7 +152,7 @@ end # Sage results B = matrix(QQ, 4, 8, [0 0 0 0 3 0 0 0; 0 0 0 0 1 1 0 0; 0 0 0 0 1 0 1 0; 0 0 0 0 2 0 0 1]); G = matrix(QQ, 8, 8, [-2 1 0 0 0 0 0 0; 1 -2 0 0 0 0 0 0; 0 0 2 -1 0 0 0 0; 0 0 -1 2 0 0 0 0; 0 0 0 0 -2 -1 0 0; 0 0 0 0 -1 -2 0 0; 0 0 0 0 0 0 2 1; 0 0 0 0 0 0 1 2]); - L = integer_lattice(B, gram = G); + L = integer_lattice(B; gram = G); f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 -1 1 0 0; 0 0 0 0 0 0 0 1; 0 0 0 0 0 0 -1 1]); Lf = integer_lattice_with_isometry(L, f); GL = image_centralizer_in_Oq(Lf) @@ -164,7 +164,7 @@ end B = matrix(QQ, 4, 6, [0 0 0 0 -2 1; 0 0 0 0 3 -4; 0 0 1 0 -1 0; 0 0 0 1 0 -1]); G = matrix(QQ, 6, 6, [2 1 0 0 0 0; 1 -2 0 0 0 0; 0 0 2//5 4//5 2//5 -1//5; 0 0 4//5 -2//5 -1//5 3//5; 0 0 2//5 -1//5 2//5 4//5; 0 0 -1//5 3//5 4//5 -2//5]); - L = integer_lattice(B, gram = G); + L = integer_lattice(B; gram = G); f = matrix(QQ, 6, 6, [1 0 0 0 0 0; 0 1 0 0 0 0; 0 0 0 0 1 0; 0 0 0 0 0 1; 0 0 -1 0 0 1; 0 0 0 -1 1 -1]); Lf = integer_lattice_with_isometry(L, f); GL = image_centralizer_in_Oq(Lf) @@ -175,11 +175,11 @@ end F, C, _ = invariant_coinvariant_pair(A3, OA3) @test rank(F) == 0 @test C == A3 - _, _, G = invariant_coinvariant_pair(A3, OA3, ambient_representation = false) + _, _, G = invariant_coinvariant_pair(A3, OA3; ambient_representation = false) @test order(G) == order(OA3) D4 = lll(root_lattice(:D, 4)) - OD4 = matrix_group(automorphism_group_generators(D4, ambient_representation = false)) + OD4 = matrix_group(automorphism_group_generators(D4; ambient_representation = false)) C, gene = coinvariant_lattice(D4, OD4, ambient_representation = false) G = gram_matrix(C) @test all(g -> matrix(g)*G*transpose(matrix(g)) == G, gene) @@ -195,60 +195,59 @@ end @test length(D[n]) == length(filter(c -> order(representative(c)) == n, cc)) end - for N in D[6] - ONf = image_centralizer_in_Oq(integer_lattice_with_isometry(lattice(N), ambient_isometry(N))) + N = rand(D[6]) + ONf = image_centralizer_in_Oq(integer_lattice_with_isometry(lattice(N), ambient_isometry(N))) # for N, the image in OqN of the centralizer of fN in ON is directly # computing during the construction of the admissible primitive extension. # We compare if at least we obtain the same orders (we can't directly # compare the groups since they do not act exactly on the same module... # and isomorphism test might be slow) - @test order(ONf) == order(image_centralizer_in_Oq(N)) - end + @test order(ONf) == order(image_centralizer_in_Oq(N)) - E8 = root_lattice(:E, 8) - @test length(enumerate_classes_of_lattices_with_isometry(E8, 20)) == 3 - @test length(enumerate_classes_of_lattices_with_isometry(E8, 18)) == 4 - @test length(enumerate_classes_of_lattices_with_isometry(genus(E8), 1)) == 1 + E6 = root_lattice(:E, 6) + @test length(enumerate_classes_of_lattices_with_isometry(E6, 10)) == 3 + @test length(enumerate_classes_of_lattices_with_isometry(E6, 18)) == 1 + @test length(enumerate_classes_of_lattices_with_isometry(genus(E6), 1)) == 1 - @test length(admissible_triples(E8, 2, pA=2)) == 1 - @test length(admissible_triples(rescale(E8, 2), 2, pB = 4)) == 2 - @test length(admissible_triples(E8, 3, pA=4, pB = 4)) == 1 + @test length(admissible_triples(E6, 2; pA=2)) == 2 + @test length(admissible_triples(rescale(E6, 2), 2; pB = 4)) == 2 + @test length(admissible_triples(E6, 3; pA=2, pB = 4)) == 1 end @testset "Primitive embeddings" begin # Compute orbits of short vectors k = integer_lattice(gram=matrix(QQ,1,1,[4])) E8 = root_lattice(:E, 8) - ok, sv = primitive_embeddings_of_primary_lattice(E8, k, classification =:sublat, check=true) + ok, sv = primitive_embeddings_of_primary_lattice(E8, k; classification =:sublat) @test ok @test length(sv) == 1 - ok, sv = primitive_embeddings_in_primary_lattice(rescale(E8, 2), rescale(k, QQ(1//2)), check=false) + ok, sv = primitive_embeddings_in_primary_lattice(rescale(E8, 2), rescale(k, QQ(1//2)); check=false) @test !ok @test is_empty(sv) - @test_throws ArgumentError primitive_embeddings_of_primary_lattice(rescale(E8, -1), k, check=false) + @test_throws ArgumentError primitive_embeddings_of_primary_lattice(rescale(E8, -1), k; check=false) k = integer_lattice(gram=matrix(QQ,1,1,[6])) E7 = root_lattice(:E, 7) - ok, sv = primitive_embeddings_in_primary_lattice(E7, k, classification = :emb, check=true) + ok, sv = primitive_embeddings_in_primary_lattice(E7, k; classification = :emb) @test ok @test length(sv) == 2 q = discriminant_group(E7) p, z, n = signature_tuple(E7) - ok, _ = primitive_embeddings_in_primary_lattice(q, (p,n), E7, classification = :none) + ok, _ = primitive_embeddings_in_primary_lattice(q, (p,n), E7; classification = :none) @test ok k = integer_lattice(gram=matrix(QQ,1,1,[2])) - ok, sv = primitive_embeddings_of_primary_lattice(E7, k, check=true) + ok, sv = primitive_embeddings_of_primary_lattice(E7, k) @test ok @test length(sv) == 1 - ok, _ = primitive_embeddings_of_primary_lattice(q, (p,n), E7, classification = :none) + ok, _ = primitive_embeddings_of_primary_lattice(q, (p,n), E7; classification = :none) @test ok - @test !primitive_embeddings_in_primary_lattice(rescale(E7, 2), k, classification = :none, check = false)[1] + @test !primitive_embeddings_in_primary_lattice(rescale(E7, 2), k; classification = :none, check = false)[1] B = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 1 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 0 0 1 0; 0 0 0 0 0 0 0 1]); G = matrix(QQ, 8, 8, [-4 2 0 0 0 0 0 0; 2 -4 2 0 0 0 0 0; 0 2 -4 2 0 0 0 2; 0 0 2 -4 2 0 0 0; 0 0 0 2 -4 2 0 0; 0 0 0 0 2 -4 2 0; 0 0 0 0 0 2 -4 0; 0 0 2 0 0 0 0 -4]); - L = integer_lattice(B, gram = G); + L = integer_lattice(B; gram = G); f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; -2 -4 -6 -4 -3 -2 -1 -3; 2 4 6 5 4 3 2 3; -1 -2 -3 -3 -3 -2 -1 -1; 0 0 0 0 1 0 0 0; 1 2 3 3 2 1 0 2]); Lf = integer_lattice_with_isometry(L, f); F = invariant_lattice(Lf) From 00987b8b6ede58a23b701c4efd4d5cfac8b162f2 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 20 Jul 2023 16:26:52 +0200 Subject: [PATCH 72/76] another round of suggestions --- experimental/QuadFormAndIsom/README.md | 16 +++--- .../QuadFormAndIsom/docs/src/enumeration.md | 6 +-- .../QuadFormAndIsom/docs/src/introduction.md | 16 +++--- .../QuadFormAndIsom/docs/src/latwithisom.md | 30 +++++------ .../QuadFormAndIsom/docs/src/primembed.md | 12 ++--- .../QuadFormAndIsom/docs/src/spacewithisom.md | 12 ++--- experimental/QuadFormAndIsom/script.sh | 25 --------- .../QuadFormAndIsom/src/embeddings.jl | 28 +++++----- .../QuadFormAndIsom/src/enumeration.jl | 8 +-- .../src/lattices_with_isometry.jl | 51 ++++++++----------- .../src/spaces_with_isometry.jl | 26 +++++----- src/Groups/spinor_norms.jl | 2 +- 12 files changed, 102 insertions(+), 130 deletions(-) delete mode 100755 experimental/QuadFormAndIsom/script.sh diff --git a/experimental/QuadFormAndIsom/README.md b/experimental/QuadFormAndIsom/README.md index ea4c29fa4f34..8623c6d96572 100644 --- a/experimental/QuadFormAndIsom/README.md +++ b/experimental/QuadFormAndIsom/README.md @@ -1,9 +1,9 @@ # Quadratic forms and isometries This project is a complement to the code about *hermitian lattices* available -on Hecke. We aim here to connect Hecke and GAP to handle some algorithmic +in Hecke. We aim here to connect Hecke and GAP to handle some algorithmic methods regarding quadratic forms with their isometries. In particular, -the integration of this code to Oscar is necessary to benefit all the +the integration of this code within Oscar is necessary to benefit from all the performance of GAP with respect to computations with groups and automorphisms in general. @@ -18,8 +18,8 @@ We introduce two new structures The former parametrizes pairs $(V, f)$ where $V$ is a rational quadratic form and $f$ is an isometry of $V$. The latter parametrizes pairs $(L, f)$ where $L$ is an integral quadratic form, also known as $\mathbb Z$-lattice and $f$ -is an isometry of $L$. One of the main feature of this project is the -enumeration of isomorphism classes of pairs $(L, f)$ where $f$ is an isometry +is an isometry of $L$. One of the main features of this project is the +enumeration of isomorphism classes of pairs $(L, f)$, where $f$ is an isometry of finite order with at most two prime divisors. The methods we resort to for this purpose are developed in the paper [BH23](@cite). @@ -35,11 +35,11 @@ in the case were the discriminant groups have a large number of subgroups. This project has been slightly tested on simple and known examples. It is currently being tested on a larger scale to test its reliability. Moreover, -there are still computational bottlenecks due to non optimized algorithms. +there are still computational bottlenecks due to non-optimized algorithms. Among the possible improvements and extensions: * Implement extra methods for lattices with isometries of infinite order; -* Extend the methods for classification of primitive embeddings for the more +* Extend the methods for classification of primitive embeddings to the more general case (knowing that we lose efficiency for large discriminant groups); * Extend existing methods for equivariant primitive embeddings/extensions. @@ -47,9 +47,9 @@ Among the possible improvements and extensions: The project was initiated by S. Brandhorst and T. Hofmann for classifying finite subgroups of automorphisms of K3 surfaces. Our current goal is to use -this code, and further extension of it, to classify finite subgroups of +this code, and further extensions of it, to classify finite subgroups of bimeromorphic self-maps of *hyperkaehler manifolds*, which are a higher -dimensional analog of K3 surface. +dimensional analogues of K3 surface. ## Notice to the user diff --git a/experimental/QuadFormAndIsom/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md index 6af48b1fc997..4e773c7deef8 100644 --- a/experimental/QuadFormAndIsom/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -4,7 +4,7 @@ CurrentModule = Oscar # Enumeration of isometries -One of the main feature of this project is the enumeration of lattices with +One of the main features of this project is the enumeration of lattices with isometry of finite order with at most two prime divisors. This is the content of [BH23](@cite) which has been implemented. We guide the user here to the global aspects of the available theory, and we refer to the paper [BH23](@cite) for further @@ -14,7 +14,7 @@ reference. Roughly speaking, for a prime number $p$, a *$p$-admissible triple* `(A, B, C)` is a triple of integer lattices such that, in certain cases, `C` can be obtained -has a primitive extension $A \perp B \to C$ where one can glue along +as a primitive extension $A \perp B \to C$ where one can glue along $p$-elementary subgroups of the respective discriminant groups of `A` and `B`. Note that not all admissible triples satisfy this extension property. @@ -82,7 +82,7 @@ splitting_of_mixed_prime_power(::ZZLatWithIsom, ::Int, ::Int) Note that an important feature from the theory in [BH23](@cite) is the notion of *admissible gluings* and equivariant primitive embeddings for admissible triples. -In the next chapter, we will develop about the features regarding primitive +In the next chapter, we present the methods regarding Nikulins's theory on primitive embeddings and their equivariant version. We use this basis to introduce the method `admissible_equivariant_primitive_extension` (Algorithm 2 in [BH23](@cite)) which is the major tool making the previous enumeration diff --git a/experimental/QuadFormAndIsom/docs/src/introduction.md b/experimental/QuadFormAndIsom/docs/src/introduction.md index 62b3bfc45f05..42dea1b5200d 100644 --- a/experimental/QuadFormAndIsom/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -1,9 +1,9 @@ # Quadratic forms and isometries This project is a complement to the code about *hermitian lattices* available -on Hecke. We aim here to connect Hecke and GAP to handle some algorithmic +in Hecke. We aim here to connect Hecke and GAP to handle some algorithmic methods regarding quadratic forms with their isometries. In particular, -the integration of this code to Oscar is necessary to benefit all the +the integration of this code within Oscar is necessary to benefit from all the performance of GAP with respect to computations with groups and automorphisms in general. @@ -18,8 +18,8 @@ We introduce two new structures The former parametrizes pairs $(V, f)$ where $V$ is a rational quadratic form and $f$ is an isometry of $V$. The latter parametrizes pairs $(L, f)$ where $L$ is an integral quadratic form, also known as $\mathbb Z$-lattice and $f$ -is an isometry of $L$. One of the main feature of this project is the -enumeration of isomorphism classes of pairs $(L, f)$ where $f$ is an isometry +is an isometry of $L$. One of the main features of this project is the +enumeration of isomorphism classes of pairs $(L, f)$, where $f$ is an isometry of finite order with at most two prime divisors. The methods we resort to for this purpose are developed in the paper [BH23]. @@ -35,11 +35,11 @@ in the case were the discriminant groups have a large number of subgroups. This project has been slightly tested on simple and known examples. It is currently being tested on a larger scale to test its reliability. Moreover, -there are still computational bottlenecks due to non optimized algorithms. +there are still computational bottlenecks due to non-optimized algorithms. Among the possible improvements and extensions: * Implement extra methods for lattices with isometries of infinite order; -* Extend the methods for classification of primitive embeddings for the more +* Extend the methods for classification of primitive embeddings to the more general case (knowing that we lose efficiency for large discriminant groups); * Extend existing methods for equivariant primitive embeddings/extensions. @@ -47,9 +47,9 @@ Among the possible improvements and extensions: The project was initiated by S. Brandhorst and T. Hofmann for classifying finite subgroups of automorphisms of K3 surfaces. Our current goal is to use -this code, and further extension of it, to classify finite subgroups of +this code, and further extensions of it, to classify finite subgroups of bimeromorphic self-maps of *hyperkaehler manifolds*, which are a higher -dimensional analog of K3 surface. +dimensional analogues of K3 surface. ## Tutorials diff --git a/experimental/QuadFormAndIsom/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md index bb7cefe65155..6b85cef397d0 100644 --- a/experimental/QuadFormAndIsom/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -8,16 +8,16 @@ We call *lattice with isometry* any pair $(L, f)$ consisting of an integer lattice $L$ together with an isometry $f \in O(L)$. We refer to the section about integer lattices of the documentation for new users. -On Oscar, such a pair is contained into a type called `ZZLatWithIsom`: +In Oscar, such a pair is encoded in the type called `ZZLatWithIsom`: ```@docs ZZLatWithIsom ``` -and it is seen as a quadruple $(Vf, L, f, n)$ where $Vf = (V, f_a)$ consists on +and it is seen as a quadruple $(Vf, L, f, n)$ where $Vf = (V, f_a)$ consists of the ambient rational quadratic space $V$ of $L$ and an isometry $f_a$ of $V$ -preserving $L$ and inducing $f$ on $L$. $n$ is the order of $f$, which is a -divisor of the order of the isometry $f_a\in O(V)$. +preserving $L$ and inducing $f$ on $L$. The integer $n$ is the order of $f$, +which is a divisor of the order of the isometry $f_a\in O(V)$. Given a lattice with isometry $(L, f)$, we provide the following accessors to the elements of the previously described quadruple: @@ -36,24 +36,24 @@ quadratic space inducing it on the lattice. ## Constructor -We provide two ways to construct a pair $Lf = (L,f)$ consisting on an integer +We provide two ways to construct a pair $Lf = (L,f)$ consisting of an integer lattice endowed with an isometry. One way to construct an object of type `ZZLatWithIsom` is through the methods `integer_lattice_with_isometry`. These -two methods does not require as input an ambient quadratic space with isometry. +two methods do not require as input an ambient quadratic space with isometry. ```@docs integer_lattice_with_isometry(::ZZLat, ::QQMatrix) integer_lattice_with_isometry(::ZZLat) ``` -By default, the first constructor will always check whether the entry matrix +By default, the first constructor will always check whether the matrix defines an isometry of the lattice, or its ambient space. We recommend not to disable this parameter to avoid any further issues. Note that as in the case of -quadratic space with isometries, both isometries of integer lattices of *finite +quadratic spaces with isometry, both isometries of integer lattices of *finite order* and *infinite order* are supported. Another way of constructing such lattices with isometry is by fixing an ambient -quadratic space with isometry, of type `QuadSpaceWithIsom`, and specify a basis +quadratic space with isometry, of type `QuadSpaceWithIsom`, and specifying a basis for an integral lattice in that space. If this lattice is preserved by the fixed isometry of the quadratic space considered, then we endow it with the induced action. @@ -67,7 +67,7 @@ lattice_in_same_ambient_space(::ZZLatWithIsom, ::MatElem) ## Attributes and first operations Given a lattice with isometry $Lf := (L, f)$, one can have access most of the -attributes of $L$ and $f$ by calling the similar function to the pair. For +attributes of $L$ and $f$ by calling the similar function for the pair. For instance, in order to know the genus of $L$, one can simply call `genus(Lf)`. Here is a list of what are the current accessible attributes: @@ -127,7 +127,7 @@ is_of_same_type(::ZZLatWithIsom, ::ZZLatWithIsom) ``` Finally, if the minimal polynomial of $f$ is irreducible, then we say that the -pair $(L, f)$ is *of hermitian type*. The type of a lattice with isometry of +pair $(L, f)$ is of *hermitian type*. The type of a lattice with isometry of hermitian type is called *hermitian* (note that the type is only defined for finite order isometries). @@ -154,13 +154,13 @@ hermitian_structure(::ZZLatWithIsom) ## Discriminant group -Given an integral lattice with isometry $Lf := (L, f)$, if one denotes $D_L$ the +Given an integral lattice with isometry $Lf := (L, f)$, if one denotes by $D_L$ the discriminant group of $L$, there exists a natural map $\pi\colon O(L) \to O(D_L)$ -sending any isometry to its induced action on the discriminant form of $L$. In -general, this map is neither injective nor surjective. If we denote $D_f := +sending any isometry to its induced action on the discriminant group of $L$. In +general, this map is neither injective nor surjective. If we denote by $D_f := \pi(f)$ then $\pi$ induces a map between centralizers $O(L, f)\to O(D_L, D_f)$. Again, this induced map is in general neither injective nor surjective, and we -denote its image $G_{L,f}$. +denote its image by $G_{L,f}$. ```@docs discriminant_group(::ZZLatWithIsom) diff --git a/experimental/QuadFormAndIsom/docs/src/primembed.md b/experimental/QuadFormAndIsom/docs/src/primembed.md index fef85ef83a1b..efb48edd5217 100644 --- a/experimental/QuadFormAndIsom/docs/src/primembed.md +++ b/experimental/QuadFormAndIsom/docs/src/primembed.md @@ -15,7 +15,7 @@ free. Two primitive embeddings $i_1\colon S\hookrightarrow M_1$ and $i_2\colon S \hookrightarrow M_2$ of $S$ into two lattices $M_1$ and $M_2$ are called *isomorphic* if there exists an isometry $M_1 \to M_2$ which restricts to the identity of $S$. Moreover, if there exists an isometry between $M_1$ and -$M_2$ which maps $S$ to itself (non-necessarily identically), we say that $i_1$ +$M_2$ which maps $S$ to itself (not necessarily identically), we say that $i_1$ and $i_2$ defines *isomorphic primitive sublattices* [Nik79](@cite). In his paper, V. V. Nikulin gives necessary and sufficient condition for an even @@ -23,7 +23,7 @@ integral lattice $M$ to embed primitively into an even unimodular lattice with given invariants (see Theorem 1.12.2 in [Nik79](@cite)). More generally, the author also provides methods to compute primitive embeddings of any even lattice into an even lattice in a given genus (see Proposition 1.15.1 in [Nik79](@cite)). -In the latter proposition, it is explain how to classify such embeddings as +In the latter proposition, it is explained how to classify such embeddings as isomorphic embeddings or as isomorphic sublattices. Such a method can be algorithmically implemented, however it tends to be slow @@ -46,8 +46,8 @@ primitive_embeddings_in_primary_lattice(::ZZLat, ::ZZLat) primitive_embeddings_of_primary_lattice(::ZZLat, ::ZZLat) ``` -Note that the previous two functions require the first lattice in input to be -unique in its genus. Otherwise, one can refer a genus, or its invariants, as a +Note that the previous two functions require the first lattice of the input to be +unique in its genus. Otherwise, one can specify a genus, or its invariants, as a first input: ```@docs @@ -60,7 +60,7 @@ primitive_embeddings_of_primary_lattice(::TorQuadModule, ::Tuple{Int, Int}, ``` In order to compute such primitive embeddings of a lattice `M` into a lattice -`L`, one first compute the possible genera for the orthogonal of `M` in `L` +`L`, one first computes the possible genera for the orthogonal of `M` in `L` (after embedding), and for each lattice `N` in such a genus, one computes isomorphism classes of *primitive extensions* of $M \perp N$ modulo $\bar{O}(N)$ (and $\bar{O}(M)$ in the case of classification of primitive sublattices of `L` @@ -68,7 +68,7 @@ isometric to `M`). We recall that a *primitive extension* of the orthogonal direct sum of two integral integer lattices `M` and `N` is an overlattice `L` of $M\perp N$ such -that both `M` and `N` embeds primitively in `L` (via the natural embeddings +that both `M` and `N` embed primitively in `L` (via the natural embeddings $M,N \to M\perp N\subseteq L$). Such primitive extensions are obtained, and classified, by looking for *gluings* between anti-isometric subgroups of the respective discriminant groups of `M` and `N`. The construction of an diff --git a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md index dc8d2c828a57..fc47bba45f3f 100644 --- a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md @@ -12,7 +12,7 @@ new users. Note that currently, we support only rational quadratic forms, i.e. quadratic spaces defined over the rational. -On Oscar, such a pair is contained into a type called `QuadSpaceWithIsom`: +In Oscar, such a pair is encoded by the type called `QuadSpaceWithIsom`: ```@docs QuadSpaceWithIsom @@ -32,7 +32,7 @@ order_of_isometry(::QuadSpaceWithIsom) space(::QuadSpaceWithIsom) ``` -The main purpose of the definition of such objects is to defined a fix +The main purpose of the definition of such objects is to define a contextual ambient space for quadratic lattices endowed with an isometry. Indeed, as we will see in the next section, *lattices with isometry* are attached to an ambient quadratic space with an isometry inducing the one on the @@ -42,22 +42,22 @@ lattice. For simplicity, we have gathered the main constructors for objects of type `QuadSpaceWithIsom` under the same name `quadratic_space_with_isometry`. The -user has then the choice on the parameters depending on what they attend to do: +user has then the choice on the parameters depending on what they intend to do: ```@docs quadratic_space_with_isometry(::Hecke.QuadSpace, ::QQMatrix) quadratic_space_with_isometry(::Hecke.QuadSpace) ``` -By default, the first constructor always checks whether the entry matrix defines +By default, the first constructor always checks whether the matrix defines an isometry of the quadratic space. We recommend not to disable this parameter to avoid any complications. Note however that in the rank 0 case, the checks are avoided since all isometries are necessarily trivial. ## Attributes and first operations -Given a quadratic space with isometry $Vf := (V, f)$, one can have access to -most of the attributes of $V$ and $f$ by calling the similar functions to the +Given a quadratic space with isometry $Vf := (V, f)$, one has access to +most of the attributes of $V$ and $f$ by calling the similar functions on the pair $(V, f)$ itself. For instance, in order to know the rank of $V$, one can simply call `rank(Vf)`. Here is a list of what are the current accessible attributes: diff --git a/experimental/QuadFormAndIsom/script.sh b/experimental/QuadFormAndIsom/script.sh deleted file mode 100755 index 208dd1bd12e2..000000000000 --- a/experimental/QuadFormAndIsom/script.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -# - -# some settings that avoid weirdness in sed when it tries to -# adapt to your locale (e.g. if your system uses German as system language) -export LANG=C -export LC_CTYPE=C -export LC_ALL=C - -# Files to modify (default uses all files known to git, -# but obviously you can modify it) -FILES=$(git ls-files) - -# on macOS, you may need to change the following -SED_I="sed -i" -#SED_I="gsed -i" -#SED_I="sed -i ''" - - -# AbstractAlgebra constructors -$SED_I -e "s;\bHecke.absolute_simple_field\b;absolute_simple_field;g" $FILES - - - -echo DONE diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index f38402640160..33df3bc17779 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -348,7 +348,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu # H0 should be contained in the group we want. So either H0 is the only one # and we return it, or if order(H0) > ord, there are no subgroups as wanted if order(H0) >= ord - order(H0) > ord && (return res) + order(H0) > ord && return res push!(res, (H0inq, G)) return res end @@ -361,7 +361,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu Vp = codomain(VtoVp) # Should never happend, but who knows... - dim(Qp) == 0 && (return res) + dim(Qp) == 0 && return res # We descend G to V for computing stabilizers later on GV, GtoGV = restrict_automorphism_group(G, Vinq) @@ -528,7 +528,7 @@ There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: `V` consists on the first primitive embedding found; - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of `L`; - - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of `L` isometric to `M` up to the action of $O(q)$ where `q` is the discriminant group of `L`. + - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of `L` isometric to `M` up to the action of $O(q)$ where `q` is the discriminant group of `L`. If `check` is set to true, the function determines whether `L` is in fact unique in its genus. @@ -583,7 +583,7 @@ There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: `V` consists on the first primitive embedding found; - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. If the pair `(q, sign)` does not define a non-empty genus for integer lattices, an error is thrown. @@ -623,7 +623,7 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) - !(genus(M) == G) && return false, results + genus(M) != G && return false, results push!(results, (M, M, orthogonal_submodule(M, M))) return true, results end @@ -640,8 +640,9 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific qL = discriminant_group(rescale(G, -1)) GL = orthogonal_group(qL) - D, inj = direct_sum(qM, qL) + D, inj, proj = biproduct(qM, qL) qMinD, qLinD = inj + DtoqM, _ = proj if el VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ,1)), p) @@ -686,7 +687,8 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific Ns = representatives(G2) @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) - qM2, _ = orthogonal_submodule(qM, domain(HM)) + + qM2, _ = sub(qM, DtoqM.(D.(lift.(gens(disc))))) for N in Ns temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) if !is_empty(temp) @@ -720,7 +722,7 @@ There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: `V` consists on the first primitive embedding found; - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of `L`; - - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of `L` isometric to `M` up to the action of $O(q)$ where `q` is the discriminant group of `L`. + - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of `L` isometric to `M` up to the action of $O(q)$ where `q` is the discriminant group of `L`. If `check` is set to true, the function determines whether `L` is in fact unique in its genus. @@ -772,7 +774,7 @@ There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: `V` consists on the first primitive embedding found; - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. If the pair `(q, sign)` does not define a non-empty genus for integer lattices, an error is thrown. @@ -802,7 +804,7 @@ There are 4 possibilities: - `classification = :none`: `V` is the empty list; - `classification = :first`: V` consists on the first primitive embedding found; - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emd`: `V` consists on representatives for all isomorphism classes of primitive sublattices in lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. + - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices in lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. """ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) @req classification in [:none, :first, :emb, :sublat] "Wrong symbol for classification" @@ -812,7 +814,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) - !(genus(M) == G) && return false, results + genus(M) != G && return false, results push!(results, (M, M, orthogonal_submodule(M, M))) return true, results end @@ -831,6 +833,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific D, inj, proj = biproduct(qM, qL) qMinD, qLinD = inj + DtoqM, _ = proj if el VL, VLinqL = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ,1)), p) @@ -875,7 +878,8 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific Ns = representatives(G2) @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) - qM2, _ = orthogonal_submodule(qM, domain(HM)) + + qM2, _ = sub(qM, DtoqM.(D.(lift.(gens(disc))))) for N in Ns temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) if length(temp) > 0 diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index aff313dd387b..5aa4793fad0e 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -32,7 +32,7 @@ function _find_D(d::T, m::Int, p::Int) where T <: IntegerUnion D = Tuple{T, T}[] # We try all the values of g possible, from 1 to p^m - for j=0:m + for j in 0:m g = p^j dj = _tuples_divisors(d*g^2) for (d1,dp) in dj @@ -225,12 +225,12 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) qA, qB, qC = discriminant_group.([A, B, C]) spec = (p == 2) && (_is_free(qA, p, l+1)) && (_is_free(qB, p, l+1)) && (_is_even(qC, p, l)) - rA = Oscar._rho_functor(qA, p, l+1) - rB = Oscar._rho_functor(qB, p, l+1) + 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 Oscar._anti_isometry_bilinear(rA, rB)[1] + return _anti_isometry_bilinear(rA, rB)[1] end end diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 4a696ac522c5..f802e1acc00d 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -137,7 +137,7 @@ julia> rank(Lf) 5 ``` """ -rank(Lf::ZZLatWithIsom) = rank(lattice(Lf))::Integer +rank(Lf::ZZLatWithIsom) = rank(lattice(Lf)) @doc raw""" characteristic_polynomial(Lf::ZZLatWithIsom) -> QQPolyRingElem @@ -155,7 +155,7 @@ julia> factor(characteristic_polynomial(Lf)) 1 * (x + 1)^5 ``` """ -characteristic_polynomial(Lf::ZZLatWithIsom) = characteristic_polynomial(isometry(Lf))::QQPolyRingElem +characteristic_polynomial(Lf::ZZLatWithIsom) = characteristic_polynomial(isometry(Lf)) @doc raw""" minimal_polynomial(Lf::ZZLatWithIsom) -> QQPolyRingElem @@ -173,7 +173,7 @@ julia> minimal_polynomial(Lf) x + 1 ``` """ -minimal_polynomial(Lf::ZZLatWithIsom) = minimal_polynomial(isometry(Lf))::QQPolyRingElem +minimal_polynomial(Lf::ZZLatWithIsom) = minimal_polynomial(isometry(Lf)) @doc raw""" genus(Lf::ZZLatWithIsom) -> ZZGenus @@ -195,7 +195,7 @@ Local symbols: Local genus symbol at 3: 1^-4 3^1 ``` """ -genus(Lf::ZZLatWithIsom) = genus(lattice(Lf))::ZZGenus +genus(Lf::ZZLatWithIsom) = genus(lattice(Lf)) @doc raw""" basis_matrix(Lf::ZZLatWithIsom) -> QQMatrix @@ -230,7 +230,7 @@ julia> basis_matrix(I) [0 0 0 1 0] ``` """ -basis_matrix(Lf::ZZLatWithIsom) = basis_matrix(lattice(Lf))::QQMatrix +basis_matrix(Lf::ZZLatWithIsom) = basis_matrix(lattice(Lf)) @doc raw""" gram_matrix(Lf::ZZLatWithIsom) -> QQMatrix @@ -252,7 +252,7 @@ julia> gram_matrix(Lf) [ 0 0 0 -1 2] ``` """ -gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf))::QQMatrix +gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf)) @doc raw""" rational_span(Lf::ZZLatWithIsom) -> QuadSpaceWithIsom @@ -281,7 +281,7 @@ julia> typeof(Vf) QuadSpaceWithIsom ``` """ -rational_span(Lf::ZZLatWithIsom) = quadratic_space_with_isometry(rational_span(lattice(Lf)), isometry(Lf))::QuadSpaceWithIsom +rational_span(Lf::ZZLatWithIsom) = quadratic_space_with_isometry(rational_span(lattice(Lf)), isometry(Lf)) @doc raw""" det(Lf::ZZLatWithIsom) -> QQFieldElem @@ -299,7 +299,7 @@ julia> det(Lf) 6 ``` """ -det(Lf::ZZLatWithIsom) = det(lattice(Lf))::QQFieldElem +det(Lf::ZZLatWithIsom) = det(lattice(Lf)) @doc raw""" scale(Lf::ZZLatWithIsom) -> QQFieldElem @@ -317,7 +317,7 @@ julia> scale(Lf) 1 ``` """ -scale(Lf::ZZLatWithIsom) = scale(lattice(Lf))::QQFieldElem +scale(Lf::ZZLatWithIsom) = scale(lattice(Lf)) @doc raw""" norm(Lf::ZZLatWithIsom) -> QQFieldElem @@ -335,7 +335,7 @@ julia> norm(Lf) 2 ``` """ -norm(Lf::ZZLatWithIsom) = norm(lattice(Lf))::QQFieldElem +norm(Lf::ZZLatWithIsom) = norm(lattice(Lf)) @doc raw""" is_positive_definite(Lf::ZZLatWithIsom) -> Bool @@ -353,7 +353,7 @@ julia> is_positive_definite(Lf) true ``` """ -is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf))::Bool +is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf)) @doc raw""" is_negative_definite(Lf::ZZLatWithIsom) -> Bool @@ -371,7 +371,7 @@ julia> is_negative_definite(Lf) false ``` """ -is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf))::Bool +is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf)) @doc raw""" is_definite(Lf::ZZLatWithIsom) -> Bool @@ -389,7 +389,7 @@ julia> is_definite(Lf) true ``` """ -is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf))::Bool +is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf)) @doc raw""" minimum(Lf::ZZLatWithIsom) -> QQFieldElem @@ -428,7 +428,7 @@ julia> is_integral(Lf) true ``` """ -is_integral(Lf::ZZLatWithIsom) = is_integral(lattice(Lf))::Bool +is_integral(Lf::ZZLatWithIsom) = is_integral(lattice(Lf)) @doc raw""" degree(Lf::ZZLatWithIsom) -> Int @@ -446,7 +446,7 @@ julia> degree(Lf) 5 ``` """ -degree(Lf::ZZLatWithIsom) = degree(lattice(Lf))::Int +degree(Lf::ZZLatWithIsom) = degree(lattice(Lf)) @doc raw""" is_even(Lf::ZZLatWithIsom) -> Bool @@ -464,7 +464,7 @@ julia> is_even(Lf) true ``` """ -is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf))::Bool +is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf)) @doc raw""" discriminant(Lf::ZZLatWithIsom) -> QQFieldElem @@ -482,7 +482,7 @@ julia> discriminant(Lf) == det(Lf) == 6 true ``` """ -discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf))::QQFieldElem +discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf)) @doc raw""" signature_tuple(Lf::ZZLatWithIsom) -> Tuple{Int, Int, Int} @@ -500,7 +500,7 @@ julia> signature_tuple(Lf) (5, 0, 0) ``` """ -signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf))::Tuple{Int, Int, Int} +signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf)) ############################################################################### # @@ -527,7 +527,7 @@ The way we construct the lattice can have an influence on the isometry of the ambient space we store. Indeed, if one mentions an isometry of the lattice, this isometry will be extended by the identity on the orthogonal complement of the rational span of the lattice. In the following example, `Lf` and `Lf2` are -the same object, but the isometry of their ambient space stored are totally +the same object, but the isometry of their ambient space stored are different (one has order 2, the other one is the identity). ```jldoctest @@ -848,7 +848,7 @@ function rescale(Lf::ZZLatWithIsom, a::RationalUnion) end @doc raw""" - Base.:^(Lf::ZZLatWithIsom, n::Int) + ^(Lf::ZZLatWithIsom, n::Int) -> ZZLatWithIsom Given a lattice with isometry $(L, f)$ and an integer $n$, return the pair $(L, f^n)$. @@ -1562,7 +1562,7 @@ function _real_kernel_signatures(L::ZZLat, M) k1 = count(z -> z > 0, diagC) k2 = length(diagC) - k1 - return (k1, k2) + return k1, k2 end @doc raw""" @@ -1622,7 +1622,7 @@ end ############################################################################### function _divides(k::IntExt, n::Int) - is_finite(k) && return divides(k, n)[1] + is_finite(k) && return is_divisible_by(k, n) return true end @@ -1681,13 +1681,6 @@ function kernel_lattice(Lf::ZZLatWithIsom, p::QQPolyRingElem) d = denominator(M) k, K = left_kernel(change_base_ring(ZZ, d*M)) return lattice(ambient_space(Lf), K*basis_matrix(L)) - #f2 = solve_left(change_base_ring(QQ, K), K*f) - #@hassert :ZZLatWithIsom 1 f2*gram_matrix(L2)*transpose(f2) == gram_matrix(L2) - #chi = parent(p)(collect(coefficients(minimal_polynomial(f2)))) - #chif = parent(p)(collect(coefficients(minimal_polynomial(Lf)))) - #_chi = gcd(p, chif) - #@hassert :ZZLatWithIsom 1 (rank(L2) == 0) || (chi == _chi) - #return integer_lattice_with_isometry(L2, f2, ambient_representation = false) end kernel_lattice(Lf::ZZLatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change_base_ring(QQ, p)) diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 95773f071c1a..042373b5e72f 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -81,7 +81,7 @@ julia> rank(Vf) == 2 true ``` """ -rank(Vf::QuadSpaceWithIsom) = rank(space(Vf))::Integer +rank(Vf::QuadSpaceWithIsom) = rank(space(Vf)) @doc raw""" dim(Vf::QuadSpaceWithIsom) -> Integer @@ -99,7 +99,7 @@ julia> dim(Vf) == 2 true ``` """ -dim(Vf::QuadSpaceWithIsom) = dim(space(Vf))::Integer +dim(Vf::QuadSpaceWithIsom) = dim(space(Vf)) @doc raw""" characteristic_polynomial(Vf::QuadSpaceWithIsom) -> QQPolyRingElem @@ -117,7 +117,7 @@ julia> characteristic_polynomial(Vf) x^2 + 2*x + 1 ``` """ -characteristic_polynomial(Vf::QuadSpaceWithIsom) = characteristic_polynomial(isometry(Vf))::QQPolyRingElem +characteristic_polynomial(Vf::QuadSpaceWithIsom) = characteristic_polynomial(isometry(Vf)) @doc raw""" minimal_polynomial(Vf::QuadSpaceWithIsom) -> QQPolyRingElem @@ -135,7 +135,7 @@ julia> minimal_polynomial(Vf) x + 1 ``` """ -minimal_polynomial(Vf) = minimal_polynomial(isometry(Vf))::QQPolyRingElem +minimal_polynomial(Vf) = minimal_polynomial(isometry(Vf)) @doc raw""" gram_matrix(Vf::QuadSpaceWithIsom) -> QQMatrix @@ -153,7 +153,7 @@ julia> is_one(gram_matrix(Vf)) true ``` """ -gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf))::QQMatrix +gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf)) @doc raw""" det(Vf::QuadSpaceWithIsom) -> QQFieldElem @@ -171,7 +171,7 @@ julia> is_one(det(Vf)) true ``` """ -det(Vf::QuadSpaceWithIsom) = det(space(Vf))::QQFieldElem +det(Vf::QuadSpaceWithIsom) = det(space(Vf)) @doc raw""" discriminant(Vf::QuadSpaceWithIsom) -> QQFieldElem @@ -189,7 +189,7 @@ julia> discriminant(Vf) -1 ``` """ -discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf))::QQFieldElem +discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf)) @doc raw""" is_positive_definite(Vf::QuadSpaceWithIsom) -> Bool @@ -207,7 +207,7 @@ julia> is_positive_definite(Vf) true ``` """ -is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf))::Bool +is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf)) @doc raw""" is_negative_definite(Vf::QuadSpaceWithIsom) -> Bool @@ -225,7 +225,7 @@ julia> is_negative_definite(Vf) false ``` """ -is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf))::Bool +is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf)) @doc raw""" is_definite(Vf::QuadSpaceWithIsom) -> Bool @@ -243,7 +243,7 @@ julia> is_definite(Vf) true ``` """ -is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf))::Bool +is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf)) @doc raw""" diagonal(Vf::QuadSpaceWithIsom) -> Vector{QQFieldElem} @@ -263,7 +263,7 @@ julia> diagonal(Vf) 1 ``` """ -diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf))::Vector{QQFieldElem} +diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf)) @doc raw""" signature_tuple(Vf::QuadSpaceWithIsom) -> Tuple{Int, Int, Int} @@ -281,7 +281,7 @@ julia> signature_tuple(Vf) (2, 0, 0) ``` """ -signature_tuple(Vf::QuadSpaceWithIsom) = signature_tuple(space(Vf))::Tuple{Int, Int, Int} +signature_tuple(Vf::QuadSpaceWithIsom) = signature_tuple(space(Vf)) ############################################################################### # @@ -417,7 +417,7 @@ function rescale(Vf::QuadSpaceWithIsom, a::RationalUnion) end @doc raw""" - Base.:^(Vf::QuadSpaceWithIsom, n::Int) + ^(Vf::QuadSpaceWithIsom, n::Int) -> QuadSpaceWithIsom Given a quadratic space with isometry $(V, f)$ and an integer $n$, return the pair $(V, f^n)$. diff --git a/src/Groups/spinor_norms.jl b/src/Groups/spinor_norms.jl index 7400b1d1a598..ff6573665d84 100644 --- a/src/Groups/spinor_norms.jl +++ b/src/Groups/spinor_norms.jl @@ -478,7 +478,7 @@ julia> order(Oq) # we can compute the orthogonal group of L Oq = orthogonal_group(discriminant_group(L)) G = orthogonal_group(L) - return sub(Oq, unique([Oq(g, check=false) for g in gens(G)])) + return sub(Oq, unique!([Oq(g, check=false) for g in gens(G)])) end @attr function image_in_Oq_signed(L::ZZLat)::Tuple{AutomorphismGroup{Hecke.TorQuadModule}, GAPGroupHomomorphism{AutomorphismGroup{Hecke.TorQuadModule}, AutomorphismGroup{Hecke.TorQuadModule}}} From a8f4712129ec23fc51e5081bb2a2fa52aad34be2 Mon Sep 17 00:00:00 2001 From: StevellM Date: Thu, 20 Jul 2023 19:40:26 +0200 Subject: [PATCH 73/76] fix properly primitive embeddings --- .../QuadFormAndIsom/src/embeddings.jl | 81 ++++++++++++++----- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 33df3bc17779..f8c68ab9fa37 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -640,9 +640,8 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific qL = discriminant_group(rescale(G, -1)) GL = orthogonal_group(qL) - D, inj, proj = biproduct(qM, qL) + D, inj = direct_sum(qM, qL) qMinD, qLinD = inj - DtoqM, _ = proj if el VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ,1)), p) @@ -659,17 +658,18 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific subsL = _subgroups_orbit_representatives_and_stabilizers(id_hom(qL), GL, k) end + VMinD = compose(VMinqM, qMinD) @vprintln :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)" for H in subsL - HL = domain(H[1]) - it = submodules(VM; order = order(HL)) + HL = H[1] + it = submodules(VM; order = order(domain(HL))) subsM = TorQuadModuleMor[j[2] for j in it] - filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) + filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), domain(HL))[1], subsM) isempty(subsM) && continue @vprintln :ZZLatWithIsom 1 "Possible gluings" HM = subsM[1] - ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) + ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), domain(HL)) @hassert :ZZLatWithIsom 1 ok _glue = [lift(qMinD(qM(lift((g))))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] @@ -687,8 +687,30 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific Ns = representatives(G2) @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) - - qM2, _ = sub(qM, DtoqM.(D.(lift.(gens(disc))))) + + # We want to compute the subgroup of `qM` which is identified with a + # subgroup if `disc`. For this, we need to project `disc` into `qM`. + # Here `qL` is given as $L1/L2$ for some lattice $L2 \subset L1$. We + # construct an overlattice `S` of $M\perp L2$ inside $Mv\perp L1$ with + # respect to `phi`, and we have + # $M\perp L2 \subset S\subset Sv\subset Mv\perpL1$ + # + # To have the subgroup, we embed `qM` in `D`. This gives a group `TT` + # which is of the form $T/M\perp L2$ for some overlattice + # $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 + # quotient is isometric to the quotient of their preimage. + # The quotient of their preimage is precisely the subgroup of `qM` + # we look for. + S, _, _ = _overlattice(phi, compose(HM, VMinD), compose(HL, qLinD)) + TT, _ = sub(D, qMinD.(gens(qM))) + qM2 = torsion_quadratic_module(intersect(cover(TT), dual(S)), + intersect(cover(TT), S); + modulus = QQ(1), + modulus_qf = QQ(2)) + for N in Ns temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) if !is_empty(temp) @@ -831,9 +853,8 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific qL = discriminant_group(rescale(G, -1)) GL = orthogonal_group(qL) - D, inj, proj = biproduct(qM, qL) + D, inj = direct_sum(qM, qL) qMinD, qLinD = inj - DtoqM, _ = proj if el VL, VLinqL = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ,1)), p) @@ -852,15 +873,15 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific @vprintln :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)" for H in subsL - HL = domain(H[1]) - it = submodules(qM; order = order(HL)) + HL = H[1] + it = submodules(qM; order = order(domain(HL))) subsM = TorQuadModuleMor[j[2] for j in it] - filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), HL)[1], subsM) + filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), domain(HL))[1], subsM) isempty(subsM) && continue @vprintln :ZZLatWithIsom 1 "Possible gluings" HM = subsM[1] - ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), HL) + ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), domain(HL)) @hassert :ZZLatWithIsom 1 ok _glue = [lift(qMinD(qM(lift(g)))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] @@ -868,18 +889,40 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific 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 = rescale(disc, -1) - !is_genus(disc, (pL-pM, nL-nM)) && continue + disc2 = rescale(disc, -1) + !is_genus(disc2, (pL-pM, nL-nM)) && continue classification == :none && return true, results - G2 = genus(disc, (pL-pM, nL-nM)) + G2 = genus(disc2, (pL-pM, nL-nM)) @vprintln :ZZLatWithIsom 1 "We can glue: $(G2)" Ns = representatives(G2) @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" Ns = lll.(Ns) - - qM2, _ = sub(qM, DtoqM.(D.(lift.(gens(disc))))) + + # We want to compute the subgroup of `qM` which is identified with a + # subgroup if `disc`. For this, we need to project `disc` into `qM`. + # Here `qL` is given as $L1/L2$ for some lattice $L2 \subset L1$. We + # construct an overlattice `S` of $M\perp L2$ inside $Mv\perp L1$ with + # respect to `phi`, and we have + # $M\perp L2 \subset S\subset Sv\subset Mv\perpL1$ + # + # To have the subgroup, we embed `qM` in `D`. This gives a group `TT` + # which is of the form $T/M\perp L2$ for some overlattice + # $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 + # quotient is isometric to the quotient of their preimage. + # The quotient of their preimage is precisely the subgroup of `qM` + # we look for. S, _, _ = _overlattice(phi, compose(HM, qMinD), compose(HL, qLinD)) + S, _, _ = _overlattice(phi, compose(HM, qMinD), compose(HL, qLinD)) + TT, _ = sub(D, qMinD.(gens(qM))) + qM2 = torsion_quadratic_module(intersect(cover(TT), dual(S)), + intersect(cover(TT), S); + modulus = QQ(1), + modulus_qf = QQ(2)) + for N in Ns temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) if length(temp) > 0 From e0515e55a9f3afc1902f13655b91ea3e1e008485 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 21 Jul 2023 11:17:36 +0200 Subject: [PATCH 74/76] fix hmm + try to reduce a bit more the tests... --- .../QuadFormAndIsom/src/enumeration.jl | 4 +- .../src/hermitian_miranda_morrison.jl | 12 ++---- experimental/QuadFormAndIsom/test/runtests.jl | 42 ++++++++++--------- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 5aa4793fad0e..2074c65f20fa 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -931,9 +931,9 @@ function _get_isometry_composite!(D, n) return nothing end -function _test_isometry_enumeration(L::ZZLat) +function _test_isometry_enumeration(L::ZZLat, k::Int = 2*rank(L)^2) n = rank(L) - ord = filter(m -> euler_phi(m) <= n && length(prime_divisors(m)) <= 2, 2:2*n^2) + ord = filter(m -> euler_phi(m) <= n && length(prime_divisors(m)) <= 2, 2:k) pds = union(reduce(vcat, prime_divisors.(ord))) vals = [maximum([valuation(x, p) for x in ord]) for p in pds] D = Dict{Int, Vector{ZZLatWithIsom}}() diff --git a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index cc31521b465b..5def8b23aebd 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) if length(Fac) == 0 A = abelian_group() - function dlog_0(x::Hecke.NfRelElem); return id(A); end; + function dlog_0(x::Vector); return id(A); end; function exp_0(x::GrpAbFinGenElem); return one(E); end; return A, dlog_0, exp_0 end @@ -228,10 +228,6 @@ function _get_product_quotient(E::Hecke.NfRel, Fac) push!(Ps, P) end - if length(groups) == 1 - return groups[1], dlogs[1], exps[1] - end - G, proj, inj = biproduct(groups...) function dlog(x::Vector) @@ -410,8 +406,8 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) RmodF, Flog, _ = _get_product_quotient(E, Fdata) - A = [Flog(Fsharpexp(g)) for g in gens(RmodFsharp)] - f = hom(gens(RmodFsharp), A) + A = elem_type(RmodF)[Flog(Fsharpexp(g)) for g in gens(RmodFsharp)] + f = hom(RmodFsharp, RmodF, A) FmodFsharp, j = kernel(f) # Now according to Theorem 6.15 of BH23, it remains to quotient out the image @@ -422,7 +418,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) OK = base_ring(OE) UOK, mUOK = unit_group(OK) - fU = hom(UOEabs, 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)]) KU, jU = kernel(fU) gene_norm_one = Hecke.NfRelElem[EabstoE(Eabs(mUOEabs(jU(k)))) for k in gens(KU)] diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index ea9dc718412b..1616e6b88511 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -58,10 +58,17 @@ end @testset "Lattices with isometry" begin A3 = root_lattice(:A, 3) - agg = automorphism_group_generators(A3; ambient_representation = false) - agg_ambient = automorphism_group_generators(A3; ambient_representation = true) + agg = QQMatrix[ + matrix(QQ, 3, 3, [-1 0 0; 0 -1 0; 0 0 -1]), + matrix(QQ, 3, 3, [1 1 1; 0 -1 -1; 0 1 0]), + matrix(QQ, 3, 3, [0 1 1; -1 -1 -1; 1 1 0]), + matrix(QQ, 3, 3, [1 0 0; -1 -1 -1; 0 0 1]), + matrix(QQ, 3, 3, [1 0 0; 0 1 1; 0 0 -1]) + ] + OA3 = matrix_group(agg) + set_attribute!(A3, :isometry_group, OA3) f = rand(agg) - g_ambient = rand(agg_ambient) + g = rand(agg) L = integer_lattice(gram = matrix(QQ, 0, 0, [])) Lf = integer_lattice_with_isometry(L; neg = true) @@ -100,8 +107,8 @@ end @test order_of_isometry(L2v) == nf @test ambient_isometry(L2v) == ambient_isometry(L2) - L3 = @inferred integer_lattice_with_isometry(A3, g_ambient; ambient_representation = true) - @test order_of_isometry(L3) == multiplicative_order(g_ambient) + L3 = @inferred integer_lattice_with_isometry(A3, g; ambient_representation = true) + @test order_of_isometry(L3) == multiplicative_order(g) @test L3^(order_of_isometry(L3)+1) == L3 @test genus(lll(L3; same_ambient=false)) == genus(L3) @@ -148,8 +155,6 @@ end @test !is_of_same_type(Lf, M) @test is_hermitian(type(M)) - # Add more image_centralizer_in_Oq for indefinite and test with comparison to - # Sage results B = matrix(QQ, 4, 8, [0 0 0 0 3 0 0 0; 0 0 0 0 1 1 0 0; 0 0 0 0 1 0 1 0; 0 0 0 0 2 0 0 1]); G = matrix(QQ, 8, 8, [-2 1 0 0 0 0 0 0; 1 -2 0 0 0 0 0 0; 0 0 2 -1 0 0 0 0; 0 0 -1 2 0 0 0 0; 0 0 0 0 -2 -1 0 0; 0 0 0 0 -1 -2 0 0; 0 0 0 0 0 0 2 1; 0 0 0 0 0 0 1 2]); L = integer_lattice(B; gram = G); @@ -158,10 +163,6 @@ end GL = image_centralizer_in_Oq(Lf) @test order(GL) == 72 - # Long test - #GL = image_centralizer_in_Oq(rescale(Lf, -14)) - #@test order(GL) == 870912 - B = matrix(QQ, 4, 6, [0 0 0 0 -2 1; 0 0 0 0 3 -4; 0 0 1 0 -1 0; 0 0 0 1 0 -1]); G = matrix(QQ, 6, 6, [2 1 0 0 0 0; 1 -2 0 0 0 0; 0 0 2//5 4//5 2//5 -1//5; 0 0 4//5 -2//5 -1//5 3//5; 0 0 2//5 -1//5 2//5 4//5; 0 0 -1//5 3//5 4//5 -2//5]); L = integer_lattice(B; gram = G); @@ -170,27 +171,28 @@ end GL = image_centralizer_in_Oq(Lf) @test order(GL) == 2 - A3 = root_lattice(:A, 3) - OA3 = orthogonal_group(A3) F, C, _ = invariant_coinvariant_pair(A3, OA3) @test rank(F) == 0 @test C == A3 _, _, G = invariant_coinvariant_pair(A3, OA3; ambient_representation = false) @test order(G) == order(OA3) - D4 = lll(root_lattice(:D, 4)) - OD4 = matrix_group(automorphism_group_generators(D4; ambient_representation = false)) - C, gene = coinvariant_lattice(D4, OD4, ambient_representation = false) - G = gram_matrix(C) - @test all(g -> matrix(g)*G*transpose(matrix(g)) == G, gene) end @testset "Enumeration of lattices with finite isometries" begin A4 = root_lattice(:A, 4) - OA4 = orthogonal_group(A4) + OA4 = matrix_group([ + matrix(QQ, 4, 4, [-1 0 0 0; 0 -1 0 0; 0 0 -1 0; 0 0 0 -1]), + matrix(QQ, 4, 4, [1 1 1 1; 0 -1 -1 -1; 0 1 0 0; 0 0 1 0]), + matrix(QQ, 4, 4, [0 1 1 1; -1 -1 -1 -1; 1 1 0 0; 0 0 1 0]), + matrix(QQ, 4, 4, [1 0 0 0; -1 -1 -1 -1; 0 0 0 1; 0 0 1 0]), + matrix(QQ, 4, 4, [1 0 0 0; 0 1 1 1; 0 0 0 -1; 0 0 -1 0]), + matrix(QQ, 4, 4, [1 0 0 0; -1 -1 -1 0; 0 0 0 -1; 0 0 1 1]), + matrix(QQ, 4, 4, [1 0 0 0; 0 1 0 0; 0 0 1 1; 0 0 0 -1]) + ]) cc = conjugacy_classes(OA4) - D = Oscar._test_isometry_enumeration(A4) + D = Oscar._test_isometry_enumeration(A4, 6) for n in collect(keys(D)) @test length(D[n]) == length(filter(c -> order(representative(c)) == n, cc)) end From 36205c22a5683610f448167258da45c53ff40a99 Mon Sep 17 00:00:00 2001 From: StevellM Date: Fri, 11 Aug 2023 13:20:23 +0200 Subject: [PATCH 75/76] First patch of `QuadFormWithIsom`: - Re-enable some previously deleted tests; - Extend the primitive extensions methods for general even lattices; - Fix the output of `image_centralizer_in_Oq` to be consistent with its cousin function `image_in_Oq`; - Apply further suggestions of the last PR: namely add the semi-colon for optional arguments, remove the instances of `Hecke.` whenever possible, etc...; - Try to modify some parts of the codes to improve memory allocations. Hopefully this should help to improve testing of the code, outside the compilation issues; - Fix some minors issues found using the package. --- experimental/QuadFormAndIsom/README.md | 7 +- .../QuadFormAndIsom/docs/src/introduction.md | 7 +- .../QuadFormAndIsom/docs/src/primembed.md | 14 +- .../QuadFormAndIsom/src/embeddings.jl | 726 ++++++++---------- .../QuadFormAndIsom/src/enumeration.jl | 136 ++-- experimental/QuadFormAndIsom/src/exports.jl | 3 +- .../src/hermitian_miranda_morrison.jl | 75 +- .../src/lattices_with_isometry.jl | 123 +-- .../src/spaces_with_isometry.jl | 10 +- experimental/QuadFormAndIsom/test/runtests.jl | 51 +- 10 files changed, 546 insertions(+), 606 deletions(-) diff --git a/experimental/QuadFormAndIsom/README.md b/experimental/QuadFormAndIsom/README.md index 8623c6d96572..cf4d5f9d3676 100644 --- a/experimental/QuadFormAndIsom/README.md +++ b/experimental/QuadFormAndIsom/README.md @@ -24,9 +24,8 @@ of finite order with at most two prime divisors. The methods we resort to for this purpose are developed in the paper [BH23](@cite). We also provide some algorithms computing isomorphism classes of primitive -embeddings of even lattices following Nikulin's theory. More precisely, the two -functions `primitive_embeddings_in_primary_lattice` and -`primitive_embeddings_of_primary_lattice` offer, under certain conditions, +embeddings of even lattices following Nikulin's theory. More precisely, the +function `primitive_embeddings` offers, under certain conditions, the possibility to compute representatives of primitive embeddings and classify them in different ways. Note nonetheless that these functions are not efficient in the case were the discriminant groups have a large number of subgroups. @@ -39,8 +38,6 @@ there are still computational bottlenecks due to non-optimized algorithms. Among the possible improvements and extensions: * Implement extra methods for lattices with isometries of infinite order; -* Extend the methods for classification of primitive embeddings to the more - general case (knowing that we lose efficiency for large discriminant groups); * Extend existing methods for equivariant primitive embeddings/extensions. ## Currently application of this project diff --git a/experimental/QuadFormAndIsom/docs/src/introduction.md b/experimental/QuadFormAndIsom/docs/src/introduction.md index 42dea1b5200d..aab132295360 100644 --- a/experimental/QuadFormAndIsom/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -24,9 +24,8 @@ of finite order with at most two prime divisors. The methods we resort to for this purpose are developed in the paper [BH23]. We also provide some algorithms computing isomorphism classes of primitive -embeddings of even lattices following Nikulin's theory. More precisely, the two -functions `primitive_embeddings_in_primary_lattice` and -`primitive_embeddings_of_primary_lattice` offer, under certain conditions, +embeddings of even lattices following Nikulin's theory. More precisely, the +function `primitive_embeddings` offers, under certain conditions, the possibility to compute representatives of primitive embeddings and classify them in different ways. Note nonetheless that these functions are not efficient in the case were the discriminant groups have a large number of subgroups. @@ -39,8 +38,6 @@ there are still computational bottlenecks due to non-optimized algorithms. Among the possible improvements and extensions: * Implement extra methods for lattices with isometries of infinite order; -* Extend the methods for classification of primitive embeddings to the more - general case (knowing that we lose efficiency for large discriminant groups); * Extend existing methods for equivariant primitive embeddings/extensions. ## Currently application of this project diff --git a/experimental/QuadFormAndIsom/docs/src/primembed.md b/experimental/QuadFormAndIsom/docs/src/primembed.md index efb48edd5217..1a4403dd8d8c 100644 --- a/experimental/QuadFormAndIsom/docs/src/primembed.md +++ b/experimental/QuadFormAndIsom/docs/src/primembed.md @@ -31,10 +31,6 @@ and inefficient in general for large rank or determinant. But, in the case where the discriminant groups are (elementary) $p$-groups, the method can be more efficient. -The ultimate goal of the project is to make all kind of computations of -primitive embeddings available. For now, we only cover the case where one of -the two lattices involved is *$p$-primary*, i.e. its discriminant is an abelian -$p$-group. Note that this covers the case of unimodular lattices, of course. We provide 4 kinds of output: * A boolean, which only returns whether there exists a primitive embedding; * A single primitive embedding as soon as the algorithm computes one; @@ -42,8 +38,7 @@ We provide 4 kinds of output: * A list of representatives of isomorphism classes of primitive sublattices. ```@docs -primitive_embeddings_in_primary_lattice(::ZZLat, ::ZZLat) -primitive_embeddings_of_primary_lattice(::ZZLat, ::ZZLat) +primitive_embeddings(::ZZLat, ::ZZLat) ``` Note that the previous two functions require the first lattice of the input to be @@ -51,11 +46,8 @@ unique in its genus. Otherwise, one can specify a genus, or its invariants, as a first input: ```@docs -primitive_embeddings_in_primary_lattice(::ZZGenus, ::ZZLat) -primitive_embeddings_in_primary_lattice(::TorQuadModule, ::Tuple{Int, Int}, -::ZZLat) -primitive_embeddings_of_primary_lattice(::ZZGenus, ::ZZLat) -primitive_embeddings_of_primary_lattice(::TorQuadModule, ::Tuple{Int, Int}, +primitive_embeddings(::ZZGenus, ::ZZLat) +primitive_embeddings(::TorQuadModule, ::Tuple{Int, Int}, ::ZZLat) ``` diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index f716feec045d..c7c9ed013579 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -55,15 +55,15 @@ function _direct_sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQ geneOAinOD = elem_type(OD)[] for f in gens(OA) - m = block_diagonal_matrix([matrix(f), identity_matrix(ZZ, ngens(B))]) - fD = OD(hom(D, D, m); check=false) + m = block_diagonal_matrix(ZZMatrix[matrix(f), identity_matrix(ZZ, ngens(B))]) + fD = OD(hom(D, D, m); check = false) push!(geneOAinOD, fD) end geneOBinOD = elem_type(OD)[] for f in gens(OB) - m = block_diagonal_matrix([identity_matrix(ZZ, ngens(A)), matrix(f)]) - fD = OD(hom(D, D, m); check=false) + m = block_diagonal_matrix(ZZMatrix[identity_matrix(ZZ, ngens(A)), matrix(f)]) + fD = OD(hom(D, D, m); check = false) push!(geneOBinOD, fD) end OAtoOD = hom(OA, OD, geneOAinOD; check = false) @@ -85,16 +85,16 @@ 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, mu, p) +function _get_V(fq::TorQuadModuleMor, mu, p::IntegerUnion) q = domain(fq) - V, Vinq = primary_part(q, p) - pV, pVinq = sub(q, [q(lift(divexact(order(g), p)*g)) for g in gens(V) if !(order(g)==1)]) - fpV = restrict_endomorphism(fq, pVinq) - fpV_mu = evaluate(mu, fpV) - K, _ = kernel(fpV_mu) - Ktoq = hom(K, q, [q(lift(a)) for a in gens(K)]) - @hassert :ZZLatWithIsom 1 is_injective(Ktoq) - return K, Ktoq + 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 = evaluate(mu, fpV) + V, _ = kernel(fpV) + Vinq = hom(V, q, elem_type(q)[q(lift(a)) for a in gens(V)]) + @hassert :ZZLatWithIsom 1 is_injective(Vinq) + return V, Vinq end # This is the rho function as defined in Definition 4.8 of BH23. @@ -167,7 +167,7 @@ function _overlattice(gamma::TorQuadModuleMor, if same_ambient _glue = Vector{QQFieldElem}[lift(g) + lift(gamma(g)) for g in gens(domain(gamma))] z = zero_matrix(QQ, 0, degree(A)) - glue = reduce(vcat, [matrix(QQ, 1, degree(A), g) for g in _glue]; init=z) + 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) @@ -179,15 +179,15 @@ function _overlattice(gamma::TorQuadModuleMor, 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, [matrix(QQ, 1, degree(cover(D)), g) for g in _glue]; init=z) + 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)) 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 = __B*fC*inv(__B) + _B = solve_left(block_diagonal_matrix(basis_matrix.([A, B])), basis_matrix(C)) + fC = _B*fC*inv(_B) end @hassert :ZZLatWithIsom 1 fC*gram_matrix(C)*transpose(fC) == gram_matrix(C) _, graph = sub(D, D.(_glue)) @@ -209,7 +209,7 @@ function _overlattice(HAinD::TorQuadModuleMor, zA, _ = sub(HA, TorQuadModuleElem[]) zB, _ = sub(HB, TorQuadModuleElem[]) gamma = hom(zA, zB, zero_matrix(ZZ, 0, 0)) - return _overlattice(gamma, HAinD, HBinD, fA, fB; same_ambient = same_ambient) + return _overlattice(gamma, HAinD, HBinD, fA, fB; same_ambient) end ############################################################################## @@ -236,24 +236,36 @@ end function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, - order::IntegerUnion = -1, - f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq))) - fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) + ord::IntegerUnion = -1, + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq)); + compute_stab::Bool = true) + res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] + V = domain(Vinq) q = codomain(Vinq) - if order == -1 - subs = collect(stable_submodules(V, [fV])) + + if !is_divisible_by(order(V), ord) + return res + end + + fV = f isa TorQuadModuleMor ? restrict_endomorphism(f, Vinq) : restrict_endomorphism(hom(f), Vinq) + if ord == -1 + subs = collect(stable_submodules(V, TorQuadModuleMor[fV])) else - subs = collect(submodules(V; order = order)) + subs = collect(submodules(V; order = ord)) filter!(s -> is_invariant(fV, s[2]), subs) end + subs = TorQuadModule[s[1] for s in subs] m = gset(O, _on_subgroup_automorphic, subs) orbs = orbits(m) - res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] for orb in orbs rep = representative(orb) - stab, _ = stabilizer(O, rep, _on_subgroup_automorphic) + if compute_stab + stab, _ = stabilizer(O, rep, _on_subgroup_automorphic) + else + stab = O + end _, rep = sub(q, TorQuadModuleElem[q(lift(g)) for g in gens(rep)]) push!(res, (rep, stab)) end @@ -267,7 +279,7 @@ end # This map return Qp := V/H as an Fp-vector space, the map which transforms V into a # Fp-vector space Vp, the quotient map Vp \to Qp, and the restriction fQp of f # to Qp -function _cokernel_as_Fp_vector_space(HinV, p) +function _cokernel_as_Fp_vector_space(HinV::TorQuadModuleMor, p::IntegerUnion) H = domain(HinV) V = codomain(HinV) @@ -282,7 +294,7 @@ function _cokernel_as_Fp_vector_space(HinV, p) function _VptoV(v::ModuleElem{FpFieldElem}) x = lift.(v.v) - return sum([x[i]*V[i] for i in 1:ngens(V)]) + return sum(TorQuadModuleElem[x[i]*V[i] for i in 1:ngens(V)]) end VtoVp = Hecke.MapFromFunc(V, Vp, _VtoVp, _VptoV) @@ -293,6 +305,20 @@ function _cokernel_as_Fp_vector_space(HinV, p) return Qp, VtoVp, VptoQp 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 + 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)] +end + # Given an abelian group injection V \to q where the group structure on V is # abelian p-elementary, compute orbits and stabilizers of subgroups of V of # order ord, which contains p^l*q and which are fixed under the action of f, under @@ -305,7 +331,8 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu G::AutomorphismGroup{TorQuadModule}, ord::IntegerUnion, f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq)), - l::IntegerUnion = -1) + l::IntegerUnion = -1; + compute_stab::Bool = true) res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] V = domain(Vinq) @@ -339,12 +366,9 @@ 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, [q(lift((p^l)*a)) for a in gens(pq)]) + H0, H0inq = sub(q, TorQuadModuleElem[q(lift((p^l)*a)) for a in gens(pq)]) @hassert :ZZLatWithIsom 1 is_invariant(f, H0inq) - H0inV = hom(H0, V, [V(lift(a)) for a in gens(H0)]) - @hassert :ZZLatWithIsom 1 is_injective(H0inV) - # H0 should be contained in the group we want. So either H0 is the only one # and we return it, or if order(H0) > ord, there are no subgroups as wanted if order(H0) >= ord @@ -353,6 +377,9 @@ 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)]) + @hassert :ZZLatWithIsom 1 is_injective(H0inV) + # Since V and H0 are elementary p-groups, they can be seen as finite # dimensional vector spaces over a finite field, and so is their quotient. # Moreover, subgroups of V of order ord and containing H0 are in bijections @@ -365,61 +392,163 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu # We descend G to V for computing stabilizers later on GV, GtoGV = restrict_automorphism_group(G, Vinq) - satV, j = kernel(GtoGV) + if compute_stab + satV, j = kernel(GtoGV) + end # Automorphisms in G preserved V and H0, since the construction of H0 is # natural. Therefore, the action of G descends to the quotient and we look for # invariants sub-vector spaces of given rank in the quotient (then lifting # generators and putting them with H0 will give us invariant subgroups as # wanted) - act_GV_Vp = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] - act_GV_Qp = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV_Vp] - MGp = matrix_group(base_ring(Qp), dim(Qp), act_GV_Qp) - GVtoMGp = hom(GV, MGp, MGp.(act_GV_Qp); check = false) + act_GV = FpMatrix[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] + act_GV = FpMatrix[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV] + MGp = matrix_group(base_ring(Qp), dim(Qp), act_GV) + GVtoMGp = hom(GV, MGp, MGp.(act_GV); check = false) GtoMGp = compose(GtoGV, GVtoMGp) @hassert :ZZLatWithIsom g-ngens(snf(abelian_group(H0))[1]) < dim(Qp) F = base_ring(Qp) # K is H0 but seen a subvector space of Vp (which is V) - k, K = kernel(VptoQp.matrix, side = :left) + k, K = kernel(VptoQp.matrix; side = :left) gene_H0p = elem_type(Vp)[Vp(vec(collect(K[i,:]))) for i in 1:k] - orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) + if compute_stab + orb_and_stab = orbit_representatives_and_stabilizers(MGp, g-k) + else + orb_and_stab = _orbit_representatives(MGp, g-k, G) + end + for (orb, stab) in orb_and_stab i = orb.map - gene_orb_in_Qp = elem_type(Qp)[Qp(vec(collect(i(v).v))) for v in gens(domain(i))] - gene_orb_in_Vp = elem_type(Vp)[preimage(VptoQp, v) for v in gene_orb_in_Qp] + 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_submod_in_Vp = vcat(gene_orb_in_Vp, gene_H0p) - gene_submod_in_V = TorQuadModuleElem[preimage(VtoVp, Vp(v)) for v in gene_submod_in_Vp] - gene_submod_in_q = TorQuadModuleElem[image(Vinq, v) for v in gene_submod_in_V] - orbq, orbqinq = sub(q, gene_submod_in_q) + 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) @hassert :ZZLatWithIsom 1 order(orbq) == ord # We keep only f-stable subspaces is_invariant(f, orbqinq) || continue - stabq = AutomorphismGroupElem{TorQuadModule}[GtoMGp\(s) for s in gens(stab)] - stabq, _ = sub(G, union(stabq, 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) + if compute_stab + stabq = AutomorphismGroupElem{TorQuadModule}[GtoMGp\(s) for s in gens(stab)] + stabq, _ = sub(G, union(stabq, 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) + else + stabq = G + end push!(res, (orbqinq, stabq)) end return res end -function _classes_automorphic_subgroups(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, H::TorQuadModule; f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O))) - if is_elementary_with_prime(H)[1] - sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, order(H), f) +function _classes_automorphic_subgroups(q::TorQuadModule, + O::AutomorphismGroup{TorQuadModule}, + H::TorQuadModule; + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O)), + compute_stab::Bool = true) + res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] + + # Trivial case: we look for subgroups in a given primary part of q + ok, p = is_primary_with_prime(H) + if ok + if is_elementary(H, p) + _, Vinq = _get_V(id_hom(q), minimal_polynomial(identity_matrix(QQ, 1)), p) + sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, order(H), f; compute_stab) + else + _, Vinq = primary_part(q, p) + sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, order(H), f; compute_stab) + end + filter!(d -> is_isometric_with_isometry(domain(d[1]), H)[1], sors) + return sors + end + + # We inspect each primary part of q and look for orbit representatives and + # stabilizers of automorphic subgroups which will be isometric to the given + # primary part of H. + # + # First, we cut q as an orthogonal direct sum of its primary parts + pds = sort(prime_divisors(order(q))) + if compute_stab + blocks = TorQuadModuleMor[primary_part(q, pds[1])[2]] + ni = Int[ngens(domain(blocks[1]))] + for i in 2:length(pds) + _f = blocks[end] + _, j = has_complement(_f) + _T = domain(j) + __f = primary_part(_T, pds[i])[2] + push!(blocks, compose(__f, j)) + push!(ni, ngens(domain(__f))) + end + D, inj, proj = biproduct(domain.(blocks)) else - sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, order(H), f) + 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) + + list_can = Vector{Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}}[] + # We collect the possible subgroups for each primary part, with the stabilizer + for i in 1:length(pds) + p = pds[i] + qpinq = blocks[i] + qp = domain(qpinq) + T, _ = primary_part(H, p) + Oqp = restrict_automorphism_group(O, qpinq)[1] + fqp = restrict_endomorphism(f, qpinq) + 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) + else + sors = _subgroups_orbit_representatives_and_stabilizers(id_hom(qp), Oqp, order(T), fqp; compute_stab) + end + filter!(d -> is_isometric_with_isometry(domain(d[1]), T)[1], sors) + is_empty(sors) && return res + push!(list_can, sors) end - return filter(d -> is_isometric_with_isometry(domain(d[1]), H)[1], sors) + + # We gather together: we do a big cartesian product, and we remember to + # reconstruct the stabilizer. Since primary parts do not talk to each other, + # we concatenate generators on an orthogonal direct sum of q into its primary + # parts (as we do for computations of orthogonal groups in the non split + # degenrate case) + for lis in Hecke.cartesian_product_iterator(list_can; inplace = false) + 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)]) + if compute_stab + stabs = AutomorphismGroup{TorQuadModule}[l[2] for l in lis] + genestab = ZZMatrix[] + + for i in 1:length(ni) + nb = sum(ni[1:i-1]) + na = sum(ni[(i+1):end]) + Inb = identity_matrix(ZZ, nb) + Ina = identity_matrix(ZZ, na) + append!(genestab, ZZMatrix[block_diagonal_matrix([Inb, matrix(f), Ina]) for f in gens(stabs[i])]) + end + + genestab = TorQuadModuleMor[hom(D, D, g) for g in genestab] + genestas = ZZMatrix[matrix(compose(compose(inv(phi), g), phi)) for g in genestab] + stab = Oscar._orthogonal_group(q, unique(genestas); check = false) + @hassert :ZZLatWithIsom is_invariant(stab, H2inq) + else + stab = O + end + push!(res, (H2inq, stab)) + end + + return res end ################################################################################# # -# Primitive embeddings for primary lattices +# Primitive embeddings for even lattices # ################################################################################# @@ -431,18 +560,19 @@ end # of O(N). If `classification == :sublat`, we also classify them up to the # action of O(M). If `classification == :first`, we return the first embedding # computed. -function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQuadModule, classification::Symbol) +function _isomorphism_classes_primitive_extensions(N::ZZLat, + M::ZZLat, + H::TorQuadModule, + classification::Symbol) @hassert :ZZLatWithIsom 1 classification in [:first, :emb, :sublat] results = Tuple{ZZLat, ZZLat, ZZLat}[] - prim, p = is_primary_with_prime(H) - el = prim ? is_elementary(H, p) : false qN = discriminant_group(N) GN, _ = image_in_Oq(N) qM = discriminant_group(M) if classification == :emb - GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(id_hom(qM))]) + GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(id_hom(qM))]; check = false) else GM, _ = image_in_Oq(M) end @@ -451,20 +581,9 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua qNinD, qMinD = inj OD = orthogonal_group(D) - if el - VN, VNinqN = _get_V(id_hom(qN), minimal_polynomial(identity_matrix(QQ,1)), p) - VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), p) - elseif prim - VN, VNinqN = primary_part(qN, p) - VM, VMinqM = primary_part(qM, p) - else - VN, VNinqN = qN, id_hom(qN) - VM, VMinqM = qM, id_hom(qM) - end - - subsN = _classes_automorphic_subgroups(VNinqN, GN, rescale(H, -1)) + subsN = _classes_automorphic_subgroups(qN, GN, rescale(H, -1)) @hassert :ZZLatWithIsom 1 !isempty(subsN) - subsM = _classes_automorphic_subgroups(VMinqM, GM, H) + subsM = _classes_automorphic_subgroups(qM, GM, H) @hassert :ZZLatWithIsom 1 !isempty(subsM) for H1 in subsN, H2 in subsM @@ -481,18 +600,23 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua HM = domain(HMinqM) OHM = orthogonal_group(HM) - actN = hom(stabN, OHN, [OHN(restrict_automorphism(x, HNinqN)) for x in gens(stabN)]) - actM = hom(stabM, OHM, [OHM(restrict_automorphism(x, HMinqM)) for x in gens(stabM)]) + 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)]) imM, _ = image(actM) stabNphi = AutomorphismGroupElem{TorQuadModule}[OHM(compose(inv(phi), compose(hom(actN(g)), phi))) for g in gens(stabN)] stabNphi, _ = sub(OHM, stabNphi) - reps = double_cosets(OHM, stabNphi, imM) + if is_elementary_with_prime(HM)[1] + iso = isomorphism(PermGroup, OHM) + else + iso = id_hom(OHM) + end + reps = double_cosets(codomain(iso), iso(stabNphi)[1], iso(imM)[1]) @vprintln :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" for g in reps - g = representative(g) + g = iso\(representative(g)) phig = compose(phi, hom(g)) L, _, _ = _overlattice(phig, HNinD, HMinD) N2 = lattice_in_same_ambient_space(L, hcat(basis_matrix(N), zero_matrix(QQ, rank(N), degree(M)))) @@ -501,7 +625,6 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua @hassert :ZZLatWithIsom 1 genus(M) == genus(M2) push!(results, (L, M2, N2)) @vprintln :ZZLatWithIsom 1 "Gluing done" - GC.gc() classification == :first && return results end end @@ -509,18 +632,17 @@ function _isomorphism_classes_primitive_extensions(N::ZZLat, M::ZZLat, H::TorQua end @doc raw""" - primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; - classification::Symbol = :sublat, - check::Bool = true) - -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} + primitive_embeddings(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, + check::Bool = true) + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} -Given a $p$-primary lattice `L`, which is unique in its genus, and an integer -lattice `M`, return whether `M` embeds primitively in `L`. +Given an even integer lattice `L`, which is unique in its genus, and an even +integer lattice `M`, return whether `M` embeds primitively in `L`. The first input of the function is a boolean `T` stating whether or not `M` embeds primitively in `L`. The second output `V` consists on triples -`(L', M', N')` where `L'` isometric to `L`, `M'` is a primtiive sublattice of -`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. +`(L', M', N')` where `L'` is isometric to `L`, `M'` is a primtiive sublattice +of `L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. If `T == false`, then `V` will always be the empty list. If `T == true`, then the content of `V` actually depends on the value of the symbol `classification`. @@ -542,7 +664,7 @@ julia> E8 = root_lattice(:E,8); julia> A4 = root_lattice(:A,4); -julia> bool, pe = primitive_embeddings_in_primary_lattice(E8, A4) +julia> bool, pe = primitive_embeddings(E8, A4) (true, Tuple{ZZLat, ZZLat, ZZLat}[(Integer lattice of rank 8 and degree 8, Integer lattice of rank 4 and degree 8, Integer lattice of rank 4 and degree 8)]) julia> pe @@ -556,21 +678,22 @@ To be understood here that there exists a unique class of embedding of the root lattice $A_4$ in the root lattice $E_8$, and the orthogonal primitive sublattice is isometric to $A_4$. """ -function primitive_embeddings_in_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = true) +function primitive_embeddings(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = true) + @req is_even(L) && is_even(M) "At the moment, only primitive embeddings into even integer lattices are computable" if check @req length(genus_representatives(L)) == 1 "L must be unique in its genus" end - return primitive_embeddings_in_primary_lattice(genus(L), M; classification = classification) + return primitive_embeddings(genus(L), M; classification) end @doc raw""" - primitive_embeddings_in_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; + primitive_embeddings(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} Given a tuple `sign` of non-negative integers and a torsion quadratic module -`q` which define a genus symbol `G` for $p$-primary lattices, return whether the -integer lattice `M` embeds primitively in a lattice in `G`. +`q` which define a genus symbol `G` for even integer lattices, return whether the +even integer lattice `M` embeds primitively in a lattice in `G`. The first input of the function is a boolean `T` stating whether or not `M` embeds primitively in a lattice in `G`. The second output `V` consists on @@ -588,18 +711,19 @@ There are 4 possibilities: If the pair `(q, sign)` does not define a non-empty genus for integer lattices, an error is thrown. """ -function primitive_embeddings_in_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) +function primitive_embeddings(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) + @req is_even(M) "At the moment, only primitive embeddings into even integer lattices are computable" @req is_genus(q, sign) "Invariants define the empty genus" G = genus(q, sign) - return primitive_embeddings_in_primary_lattice(G, M; classification = classification) + @req is_even(G) "At the moment, only primitive embeddings into even integer lattices are computable" + return primitive_embeddings(G, M; classification) end @doc raw""" - primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; - classification::Symbol = :sublat) - -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} + primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) + -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} -Given a genus symbol `G` for $p$-primary integer lattices and an integer +Given a genus symbol `G` for even integer lattices and an even integer lattice `M`, return whether `M` embeds primitively in a lattice in `G`. The first input of the function is a boolean `T` stating whether or not `M` @@ -615,11 +739,12 @@ There are 4 possibilities: - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant of a lattice in `G`; - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. """ -function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) +function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) + @req is_even(G) && is_even(M) "At the moment, only primitive embeddings into even integer lattices are computable" @req classification in [:none, :emb, :sublat, :first] "Wrong symbol for classification" - pL, _, nL = signature_tuple(G) - pM, _, nM = signature_tuple(M) - @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" + posL, _, negL = signature_tuple(G) + posM, _, negM = signature_tuple(M) + @req (posL-posM >= 0 && negL-negM >= 0) "Impossible embedding" results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) @@ -630,11 +755,8 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" - bool, p = is_primary_with_prime(G) - @req bool "G must be unimodular or primary" - el = is_elementary(G, p) - qM = discriminant_group(M) + OqM = orthogonal_group(qM) GM, _ = image_in_Oq(M) qL = discriminant_group(rescale(G, -1)) @@ -643,258 +765,86 @@ function primitive_embeddings_in_primary_lattice(G::ZZGenus, M::ZZLat; classific D, inj = direct_sum(qM, qL) qMinD, qLinD = inj - if el - VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ,1)), p) - else - VM, VMinqM = primary_part(qM, p) - end + prG, pG = is_primary_with_prime(G) + elG = is_elementary(G, pG) - for k in divisors(gcd(order(VM), order(qL))) + prM, pM = is_primary_with_prime(M) + elM = is_elementary(M, pM) + + for k in divisors(gcd(order(qM), order(qL))) + ok, ek, pk = is_prime_power_with_data(k) @vprintln :ZZLatWithIsom 1 "Glue order: $(k)" - if el - subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(id_hom(qL), GL, k) + if elG || elM + _, VLinqL = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ, 1)), max(pG, pM)) + subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k; compute_stab = false) + elseif ok && (ek == 1) + _, VLinqL = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ, 1)), k) + subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k; compute_stab = false) else - subsL = _subgroups_orbit_representatives_and_stabilizers(id_hom(qL), GL, k) + if prG || prM + _, VLinqL = primary_part(qL, max(pG, pM)) + elseif ok + _, VLinqL = primary_part(qL, pk) + else + VLinqL = id_hom(qL) + end + subsL = _subgroups_orbit_representatives_and_stabilizers(VLinqL, GL, k; compute_stab = false) end + isempty(subsL) && continue - VMinD = compose(VMinqM, qMinD) - @vprintln :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)" - for H in subsL - HL = H[1] - it = submodules(VM; order = order(domain(HL))) - subsM = TorQuadModuleMor[j[2] for j in it] - filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), domain(HL))[1], subsM) - isempty(subsM) && continue - - @vprintln :ZZLatWithIsom 1 "Possible gluings" - HM = subsM[1] - ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), domain(HL)) - @hassert :ZZLatWithIsom 1 ok - - _glue = [lift(qMinD(qM(lift((g))))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] - ext, _ = sub(D, D.(_glue)) - 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)) - disc2 = rescale(disc, -1) - !is_genus(disc2, (pL-pM, nL-nM)) && continue - - classification == :none && return true, results - - G2 = genus(disc2, (pL-pM, nL-nM)) - @vprintln :ZZLatWithIsom 1 "We can glue: $(G2)" - Ns = representatives(G2) - @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" - Ns = lll.(Ns) - - # We want to compute the subgroup of `qM` which is identified with a - # subgroup if `disc`. For this, we need to project `disc` into `qM`. - # Here `qL` is given as $L1/L2$ for some lattice $L2 \subset L1$. We - # construct an overlattice `S` of $M\perp L2$ inside $Mv\perp L1$ with - # respect to `phi`, and we have - # $M\perp L2 \subset S\subset Sv\subset Mv\perpL1$ - # - # To have the subgroup, we embed `qM` in `D`. This gives a group `TT` - # which is of the form $T/M\perp L2$ for some overlattice - # $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 - # quotient is isometric to the quotient of their preimage. - # The quotient of their preimage is precisely the subgroup of `qM` - # we look for. - S, _, _ = _overlattice(phi, compose(HM, VMinD), compose(HL, qLinD)) - TT, _ = sub(D, qMinD.(gens(qM))) - qM2 = torsion_quadratic_module(intersect(cover(TT), dual(S)), - intersect(cover(TT), S); - modulus = QQ(1), - modulus_qf = QQ(2)) - - for N in Ns - temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) - if !is_empty(temp) - classification == :first && return true, temp - append!(results, temp) - end - GC.gc() + if elG || elM + VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), max(pG, pM)) + elseif ok && (ek == 1) + VM, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), k) + else + if prG || prM + VM, VMinqM = primary_part(qM, max(pG, pM)) + elseif ok + VM, VMinqM = primary_part(qM, k) + else + VM = qM + VMinqM = id_hom(qM) end end - end - return (length(results) > 0), results -end - -@doc raw""" - primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; - classification::Symbol = :sublat, - check::Bool = true) - -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} - -Given an integer lattice `L`, which is unique in its genus, and a `p`-primary -lattice `M`, return whether `M` embeds primitively in `L`. - -The first input of the function is a boolean `T` stating whether or not `M` -embeds primitively in `L`. The second output `V` consists on triples -`(L', M', N')` where `L'` isometric to `L`, `M'` is a primtiive sublattice of -`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. - -If `T == false`, then `V` will always be the empty list. If `T == true`, then -the content of `V` actually depends on the value of the symbol `classification`. -There are 4 possibilities: - - `classification = :none`: `V` is the empty list; - - `classification = :first`: `V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in `L`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of `L`; - - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of `L` isometric to `M` up to the action of $O(q)$ where `q` is the discriminant group of `L`. - -If `check` is set to true, the function determines whether `L` is in fact unique -in its genus. - -# Examples -We can use such primitive extensions algorithm to have orbits of some short -primitive vectors: - -```jldoctest -julia> L = root_lattice(:D, 5); - -julia> k = integer_lattice(gram=matrix(QQ,1,1,[4])); - -julia> bool, sv = primitive_embeddings_of_primary_lattice(L, k); - -julia> bool -true - -julia> sv -2-element Vector{Tuple{ZZLat, ZZLat, ZZLat}}: - (Integer lattice of rank 5 and degree 5, Integer lattice of rank 1 and degree 5, Integer lattice of rank 4 and degree 5) - (Integer lattice of rank 5 and degree 5, Integer lattice of rank 1 and degree 5, Integer lattice of rank 4 and degree 5) -``` -""" -function primitive_embeddings_of_primary_lattice(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = true) - if check - @req length(genus_representatives(L)) == 1 "L must be unique in its genus" - end - return primitive_embeddings_of_primary_lattice(genus(L), M; classification = classification) -end - -@doc raw""" - primitive_embeddings_of_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; - classification::Symbol = :sublat) - -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} - -Given a tuple `sign` of non-negative integers and a torsion quadratic module -`q` which define a genus symbol `G` for integer lattices, return whether the -$p$-primary lattice `M` embeds primitively in a lattice in `G`. - -The first input of the function is a boolean `T` stating whether or not `M` -embeds primitively in a lattice in `G`. The second output `V` consists on -triples `(L', M', N')` where `L'` is a lattice in `G`, `M'` is a sublattice of -`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. - -If `T == false`, then `V` will always be the empty list. If `T == true`, then -the content of `V` actually depends on the value of the symbol `classification`. -There are 4 possibilities: - - `classification = :none`: `V` is the empty list; - - `classification = :first`: `V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices of lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. - -If the pair `(q, sign)` does not define a non-empty genus for integer lattices, -an error is thrown. -""" -function primitive_embeddings_of_primary_lattice(q::TorQuadModule, sign::Tuple{Int, Int}, M::ZZLat; classification::Symbol = :sublat) - @req is_genus(q, sign) "Invariants define the empty genus" - G = genus(q, sign) - return primitive_embeddings_of_primary_lattice(G, M; classification = classification) -end - -@doc raw""" - primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; - classification::Symbol = :sublat) - -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} - -Given a genus symbol `G` for integer lattices and a `p`-primary lattice `M`, -return whether `M` embeds primitively in a lattice in `G`. + !is_divisible_by(order(VM), k) && continue -The first input of the function is a boolean `T` stating whether or not `M` -embeds primitively in a lattice in `G`. The second output `V` consists on -triples `(L', M', N')` where `L'` is a lattice in `G`, `M'` is a sublattice of -`L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. - -If `T == false`, then `V` will always be the empty list. If `T == true`, then -the content of `V` actually depends on the value of the symbol `classification`. -There are 4 possibilities: - - `classification = :none`: `V` is the empty list; - - `classification = :first`: V` consists on the first primitive embedding found; - - `classification = :sublat`: `V` consists on representatives for all isomorphism classes of primitive embeddings of `M` in lattices in `G`, up to the actions of $\bar{O}(M)$ and $O(q)$ where `q` is the discriminant group of a lattice in `G`; - - `classification = :emb`: `V` consists on representatives for all isomorphism classes of primitive sublattices in lattices in `G` isometric to `M`, up to the action of $O(q)$ where `q` is the discriminant group of a lattice in `G`. -""" -function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) - @req classification in [:none, :first, :emb, :sublat] "Wrong symbol for classification" - pL, _, nL = signature_tuple(G) - pM, _, nM = signature_tuple(M) - @req (pL-pM >= 0 && nL-nM >= 0) "Impossible embedding" - - results = Tuple{ZZLat, ZZLat, ZZLat}[] - if rank(M) == rank(G) - genus(M) != G && return false, results - push!(results, (M, M, orthogonal_submodule(M, M))) - return true, results - end - - @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" - - bool, p = is_primary_with_prime(M) - @req bool "M must be unimodular or primary" - el = is_elementary(M, p) - - qM = discriminant_group(M) - GM, _ = image_in_Oq(M) - - qL = discriminant_group(rescale(G, -1)) - GL = orthogonal_group(qL) - - D, inj = direct_sum(qM, qL) - qMinD, qLinD = inj + itM = submodules(VM; order = k) + st = iterate(itM) + @hassert :ZZLatWithIsom !isnothing(st) - if el - VL, VLinqL = _get_V(id_hom(qL), minimal_polynomial(identity_matrix(QQ,1)), p) - else - VL, VLinqL = primary_part(qL, p) - end - - for k in divisors(gcd(order(qM), order(VL))) - @vprintln :ZZLatWithIsom 1 "Glue order: $(k)" - - if el - subsL = _subgroups_orbit_representatives_and_stabilizers_elementary(VLinqL, GL, k) - else - subsL = _subgroups_orbit_representatives_and_stabilizers(VLinqL, GL, k) - end - @vprintln :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)" for H in subsL HL = H[1] - it = submodules(qM; order = order(domain(HL))) - subsM = TorQuadModuleMor[j[2] for j in it] - filter!(HM -> is_anti_isometric_with_anti_isometry(domain(HM), domain(HL))[1], subsM) - isempty(subsM) && continue + _st = st + HM, state = st + while !isnothing(HM) && !is_anti_isometric_with_anti_isometry(HM[1], domain(HL))[1] + _st = iterate(itM, state) + if isnothing(_st) + HM = _st + else + HM, state = _st + end + end + isnothing(HM) && continue + + HM = compose(HM[2], VMinqM) @vprintln :ZZLatWithIsom 1 "Possible gluings" - HM = subsM[1] ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), domain(HL)) @hassert :ZZLatWithIsom 1 ok - _glue = [lift(qMinD(qM(lift(g)))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] - ext, _ = sub(D, D.(_glue)) + ext = Vector{QQFieldElem}[lift(qMinD(qM(lift((g))))) + lift(qLinD(qL(lift(phi(g))))) for g in gens(domain(HM))] + ext, _ = sub(D, D.(ext)) 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)) disc2 = rescale(disc, -1) - !is_genus(disc2, (pL-pM, nL-nM)) && continue + !is_genus(disc2, (posL-posM, negL-negM)) && continue classification == :none && return true, results - G2 = genus(disc2, (pL-pM, nL-nM)) + G2 = genus(disc2, (posL-posM, negL-negM)) @vprintln :ZZLatWithIsom 1 "We can glue: $(G2)" Ns = representatives(G2) @vprintln :ZZLatWithIsom 1 "$(length(Ns)) possible orthogonal complement(s)" @@ -915,7 +865,7 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific # $(T\cap Sv)/(M\perpL2) 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. S, _, _ = _overlattice(phi, compose(HM, qMinD), compose(HL, qLinD)) + # we look for. S, _, _ = _overlattice(phi, compose(HM, qMinD), compose(HL, qLinD)) TT, _ = sub(D, qMinD.(gens(qM))) qM2 = torsion_quadratic_module(intersect(cover(TT), dual(S)), @@ -925,13 +875,11 @@ function primitive_embeddings_of_primary_lattice(G::ZZGenus, M::ZZLat; classific for N in Ns temp = _isomorphism_classes_primitive_extensions(N, M, qM2, classification) - if length(temp) > 0 + if !is_empty(temp) classification == :first && return true, temp append!(results, temp) end - GC.gc() end - GC.gc() end end return (length(results) > 0), results @@ -972,14 +920,14 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, @req is_prime(p) && is_prime(q) "p and q must be a prime number" # Requirements for [BH23] - amb = ambient_space(lattice(A)) === ambient_space(lattice(B)) === ambient_space(lattice(C)) + same_ambient = ambient_space(lattice(A)) === ambient_space(lattice(B)) === ambient_space(lattice(C)) if check @req all(L -> is_integral(L), [A, B, C]) "Underlying lattices must be integral" chiA = minimal_polynomial(A) chiB = minimal_polynomial(parent(chiA), isometry(B)) @req gcd(chiA, chiB) == 1 "Minimal irreducible polynomials must be relatively coprime" @req is_admissible_triple(A, B, C, p) "Entries, in this order, do not define an admissible triple with respect to p" - if amb + if same_ambient G = gram_matrix(ambient_space(C)) @req iszero(basis_matrix(A)*G*transpose(basis_matrix(B))) "Lattices in same ambient space must be orthogonal" end @@ -988,18 +936,18 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, results = ZZLatWithIsom[] # this is the glue valuation: it is well-defined because the triple in input is admissible - g = div(valuation(divexact(det(A)*det(B), det(C)), p), 2) + g = divexact(valuation(divexact(det(A)*det(B), det(C)), p), 2) qA, fqA = discriminant_group(A) qB, fqB = discriminant_group(B) - qC, _ = discriminant_group(C) - GA = image_centralizer_in_Oq(A) + qC = discriminant_group(lattice(C)) + GA, _ = image_centralizer_in_Oq(A) @hassert :ZZLatWithIsom 1 fqA in GA - GB = image_centralizer_in_Oq(B) + GB, _ = image_centralizer_in_Oq(B) @hassert :ZZLatWithIsom 1 fqB in GB # this is where we will perform the glueing - if amb + if same_ambient D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _sum_with_embeddings_orthogonal_groups(qA, qB) else D, qAinD, qBinD, OD, OqAinOD, OqBinOD = _direct_sum_with_embeddings_orthogonal_groups(qA, qB) @@ -1015,26 +963,26 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # (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)] - gene = vcat(geneA, geneB) - GCAB, _ = sub(OD, gene) + union!(geneA, geneB) # We compute the overlattice in this context - C2, fC2, _ = _overlattice(qAinD, qBinD, isometry(A), isometry(B); same_ambient = amb) + C2, fC2, _ = _overlattice(qAinD, qBinD, isometry(A), isometry(B); same_ambient) C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false) + # If not of the good type, we discard it + !is_of_type(C2fC2^q, type(C)) && return results + qC2 = discriminant_group(C2) + OqC2 = orthogonal_group(qC2) phi2 = hom(qC2, D, TorQuadModuleElem[D(lift(x)) for x in gens(qC2)]) @hassert :ZZLatWithIsom 1 is_isometry(phi2) - # If not of the good type, we discard it - !is_of_type(C2fC2^q, type(C)) && return results - # This is the new image of the stabilizer, just a direct product of # the previous ones - GC2 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(hom(g), inv(phi2))).map_ab.map for g in gens(GCAB)]) + GC2 = sub(OqC2, elem_type(OqC2)[OqC2(compose(phi2, compose(hom(g), inv(phi2))); check = false) for g in geneA]) # This is mainly to check that we have not done anything inconsistent - @hassert :ZZLatWithIsom 1 discriminant_group(C2fC2)[2] in GC2 + @hassert :ZZLatWithIsom 1 discriminant_group(C2fC2)[2] in GC2[1] set_attribute!(C2fC2, :image_centralizer_in_Oq, GC2) push!(results, C2fC2) return results @@ -1071,15 +1019,6 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fqA, ZZ(l)) subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fqB, ZZ(l)) - # once we have the potential kernels, we create pairs of anti-isometric groups since glue - # maps are anti-isometry - R = Tuple{eltype(subsA), eltype(subsB), TorQuadModuleMor}[] - for H1 in subsA, H2 in subsB - ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) - !ok && continue - push!(R, (H1, H2, phi)) - end - # now, for each pair of anti-isometric potential kernels, we need to massage the gluing # computed to turn it into an admissible one. Then, we need to decide whether # such an admissible gluing can be made (fA,fB)-equivariant, up to conjugacy. @@ -1087,7 +1026,9 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # Each pair for which we can find such nice gluing, we create the double coset # parametrising all such gluing up to certain conditions and we then compute the # corresponding overlattice and check whether it satisfies the type conditions. - for (H1, H2, phi) in R + 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) @@ -1106,13 +1047,13 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # 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, [OSA(restrict_automorphism(x, SAinqA)) for x in gens(stabA)]) + 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, [OSB(restrict_automorphism(x, SBinqB)) for x in gens(stabB)]) + 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))) @@ -1137,30 +1078,31 @@ 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))) - fSAinOSB = OSBrB(compose(inv(phi), compose(hom(fSA), phi))) - @hassert :ZZLatWithIsom 1 fSAinOSB == fSB + @hassert :ZZLatWithIsom OSBrB(compose(inv(phi), compose(hom(fSA), phi))) == fSB # 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, [OSB(c) for c in gens(center)]) - stabSAphi, _ = sub(OSB, AutomorphismGroupElem{TorQuadModule}[OSB(compose(inv(phi), compose(hom(g), phi))) for g in gens(imA)]) + 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, _ = intersect(center, stabSAphi) stabSB, _ = intersect(center, imB) - reps = double_cosets(center, stabSB, stabSAphi) + + iso = isomorphism(PermGroup, center) + reps = double_cosets(codomain(iso), iso(stabSB)[1], iso(stabSAphi)[1]) # We iterate over all double cosets. Each representative, define a new # classe of admissible gluing and so, for each such representative we compute the # corresponding overlattice along the gluing. If it has the wanted type, we compute # the image of the centralizer in OD from the stabA and stabB. for g in reps - g = representative(g) + g = iso\(representative(g)) phig = compose(phi, hom(g)) - @assert is_anti_isometry(phig) + @hassert :ZZLatWithIsom 1 is_anti_isometry(phig) # We compute the overlattice in this context, keeping track whether we # cork in a fixed ambient quadratic space - C2, fC2, extinD = _overlattice(phig, SAinD, SBinD, isometry(A), isometry(B); same_ambient = amb) + C2, fC2, extinD = _overlattice(phig, SAinD, SBinD, isometry(A), isometry(B); same_ambient) C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false) # This is the type requirement: somehow, we want `(C2, fC2)` to be a "q-th root" of `(C, fC)`. @@ -1176,26 +1118,24 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, modulus_qf = modulus_quadratic_form(perp)) qC2 = discriminant_group(C2) OqC2 = orthogonal_group(qC2) - phi2 = hom(qC2, disc, [disc(lift(x)) for x in gens(qC2)]) + phi2 = hom(qC2, disc, TorQuadModuleElem[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 toi 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) - gene_inter = AutomorphismGroupElem{TorQuadModule}[h for h in unique(gens(intersect(imA, im2_phi)[1]))] - im3, _ = sub(imA, gene_inter) - stab = Tuple{AutomorphismGroupElem{TorQuadModule}, AutomorphismGroupElem{TorQuadModule}}[(actA\x, actB\(imB(compose(inv(phig), compose(hom(x), phig))))) for x in gens(im3)] - stab = AutomorphismGroupElem{TorQuadModule}[OqAinOD(x[1])*OqBinOD(x[2]) for x in stab] - stab = union(stab, kerA) - stab = union(stab, kerB) + 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 = Oscar._orthogonal_group(qC2, [compose(phi2, compose(g, inv(phi2))).map_ab.map 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 # itself... - @hassert :ZZLatWithIsom 1 discriminant_group(C2fC2)[2] in stab + @hassert :ZZLatWithIsom 1 discriminant_group(C2fC2)[2] in stab[1] set_attribute!(C2fC2, :image_centralizer_in_Oq, stab) push!(results, C2fC2) @@ -1243,13 +1183,13 @@ end # We compute O(SB, rho_{l+1}(B)) where B has discriminant form qB. `spec` keep # track whether rho_{l+1}(B) should be considered as a finite quadratic module # or just a finite bilinear module (depends on the overlattice). -function _compute_double_stabilizer(SBinqB, l, spec) +function _compute_double_stabilizer(SBinqB::TorQuadModuleMor, l::IntegerUnion, spec::Bool) SB = domain(SBinqB) qB = codomain(SBinqB) OSB = orthogonal_group(SB) p = elementary_divisors(SB)[1] rB = _rho_functor(qB, p, l+1) - rBtoSB = hom(rB, SB, [SB(QQ(p^l)*lift(a)) for a in gens(rB)]) + rBtoSB = hom(rB, SB, TorQuadModuleElem[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) @@ -1259,7 +1199,7 @@ function _compute_double_stabilizer(SBinqB, l, spec) else OHBrB, _ = stabilizer(OHB, gram_matrix_bilinear(rB), _on_modular_matrix) end - OSBrB, _ = sub(OSB, union(gens(K), OSB.(gens((OSBHBtoOHB\(OHBrB))[1])))) + OSBrB, _ = sub(OSB, union!(OSB.(gens((OSBHBtoOHB\(OHBrB))[1])), gens(K))) return OSBrB end @@ -1299,7 +1239,7 @@ function _is_anti_isometry_bilinear(f::TorQuadModuleMor) end # Compute an anti-isometry between the two finite bilinear modules r1 and r2. -function _anti_isometry_bilinear(r1, 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) @@ -1323,7 +1263,11 @@ end # exist, we massage phi until we turn it into an admissible gluing. There might # be ways to improve such search, but I would expect that both loops iterating # on OSB and OSBHB are terminating after few tries. -function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) +function _find_admissible_gluing(SAinqA::TorQuadModuleMor, + SBinqB::TorQuadModuleMor, + phi::TorQuadModuleMor, + l::IntegerUnion, + spec::Bool) SA = domain(SAinqA) SB = domain(SBinqB) p = elementary_divisors(SA)[1] @@ -1333,9 +1277,9 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) pqB, pqBtoqB = primary_part(qB, p) rA = _rho_functor(qA, p, l+1) rB = _rho_functor(qB, p, l+1) - rAtoSA = hom(rA, SA, [SA(QQ(p^l)*lift(a)) for a in gens(rA)]) + 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, [SB(QQ(p^l)*(lift(b))) for b in gens(rB)]) + rBtoSB = hom(rB, SB, TorQuadModuleElem[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) @@ -1346,7 +1290,7 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) @hassert :ZZLatWithIsom 1 ok @hassert :ZZLatWithIsom 1 _is_anti_isometry_bilinear(phi_0) end - phiHA, _ = sub(SB, [phi(SA(lift(a))) for a in gens(HA)]) + phiHA, _ = sub(SB, TorQuadModuleElem[phi(SA(lift(a))) for a in gens(HA)]) OSB = orthogonal_group(SB) g = one(OSB) for f in OSB @@ -1361,7 +1305,7 @@ function _find_admissible_gluing(SAinqA, SBinqB, phi, l, spec) for f in OSBHB g = f phif = compose(phi_1, hom(f)) - psi = hom(rA, rB, [rBtoSB\(phif(rAtoSA(a))) for a in gens(rA)]) + psi = hom(rA, rB, TorQuadModuleElem[rBtoSB\(phif(rAtoSA(a))) for a in gens(rA)]) if matrix(psi) == matrix(phi_0) break end diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 2074c65f20fa..16167eff5952 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -13,8 +13,7 @@ # The tuples in output are pairs of positive integers! function _tuples_divisors(d::T) where T <: IntegerUnion - div = divisors(d) - return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in div] + return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in divisors(d)] end # This is line 8 of Algorithm 1, they correspond to the possible @@ -32,12 +31,11 @@ function _find_D(d::T, m::Int, p::Int) where T <: IntegerUnion D = Tuple{T, T}[] # We try all the values of g possible, from 1 to p^m - for j in 0:m - g = p^j + for g in powers(p, m) dj = _tuples_divisors(d*g^2) - for (d1,dp) in dj + for (d1, dp) in dj if mod(d1,g) == mod(dp,g) == 0 - push!(D,(d1,dp)) + push!(D, (d1, dp)) end end end @@ -48,25 +46,27 @@ end # C since subgenera of an even genus are even too. r is the rank of # the subgenus, d its determinant, s and l the scale and level of C function _find_L(pG::Int, nG::Int, r::Int, d::RationalUnion, s::ZZRingElem, l::ZZRingElem, p::IntegerUnion, even = true; pos::Int = -1) - L = ZZGenus[] + def = ZZGenus[genus(integer_lattice(; gram = matrix(QQ, 0, 0, [])))] if r == 0 && d == 1 - return ZZGenus[genus(integer_lattice(gram = matrix(QQ, 0, 0, [])))] + return def end if pos >= 0 + pos > pG && return def neg = r-pos - gen = integer_genera((pos, neg), d; even=even) - filter!(G -> divides(numerator(scale(G)), s)[1], gen) - filter!(G -> divides(p*l, numerator(level(G)))[1], gen) - append!(L, gen) + neg > nG && return def + gen = integer_genera((pos, neg), d; even) + filter!(G -> is_divisible_by(numerator(scale(G)), s), gen) + filter!(G -> is_divisible_by(p*l, numerator(level(G))), gen) else - for (s1,s2) in [(s,t) for s=0:pG for t=0:nG if s+t==r] - gen = integer_genera((s1,s2), d; even=even) - filter!(G -> divides(numerator(scale(G)), s)[1], gen) - filter!(G -> divides(p*l, numerator(level(G)))[1], gen) - append!(L, gen) + gen = ZZGenus[] + for (s1, s2) in Tuple{Int, Int}[(s,t) for s=0:pG for t=0:nG if s+t==r] + L = integer_genera((s1,s2), d; even) + filter!(G -> is_divisible_by(numerator(scale(G)), s), L) + filter!(G -> is_divisible_by(p*l, numerator(level(G))), L) + append!(gen, L) end end - return L + return gen end @doc raw""" @@ -102,7 +102,7 @@ true ``` """ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) - zg = genus(integer_lattice(gram = matrix(QQ, 0, 0, []))) + zg = genus(integer_lattice(; gram = matrix(QQ, 0, 0, []))) AperpB = direct_sum(A, B) (signature_tuple(AperpB) == signature_tuple(C)) || (return false) if ((A == zg) && (B == C)) || ((B == zg) && (A == C)) @@ -113,7 +113,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) return false end - @req divides(rank(B), p-1)[1] "p-1 must divide the rank of B" + @req is_divisible_by(rank(B), p-1) "p-1 must divide the rank of B" lA = ngens(discriminant_group(A)) lB = ngens(discriminant_group(B)) @@ -122,8 +122,8 @@ 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))) - if local_symbol(AperpB,q) != local_symbol(C,q) + for q in filter(qq -> qq != p, union!([2], primes(AperpB), primes(C))) + if local_symbol(AperpB, q) != local_symbol(C, q) return false end end @@ -137,7 +137,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) return false end - if !(divides(scale(AperpB), scale(C))[1] && divides(p*level(C), level(AperpB))[1]) + if !is_divisible_by(scale(AperpB), scale(C)) || !is_divisible_by(p*level(C), level(AperpB)) return false end @@ -145,7 +145,7 @@ function is_admissible_triple(A::ZZGenus, B::ZZGenus, C::ZZGenus, p::Integer) # an anti-isometry between the p-part of the (quadratic) discriminant forms of A and B qA = discriminant_group(A) qB = discriminant_group(B) - if !divides(numerator(det(C)), p)[1] + if !is_divisible_by(numerator(det(C)), p) return is_anti_isometric_with_anti_isometry(primary_part(qA, p)[1], primary_part(qB, p)[1])[1] end @@ -265,10 +265,10 @@ julia> admissible_triples(g, 2) 8-element Vector{Tuple{ZZGenus, ZZGenus}}: (Genus symbol: II_(5, 0) 2^-1_3 3^1, Genus symbol: II_(0, 0)) (Genus symbol: II_(4, 0) 2^2_6 3^1, Genus symbol: II_(1, 0) 2^1_1) - (Genus symbol: II_(3, 0) 2^3_3, Genus symbol: II_(2, 0) 2^-2 3^1) (Genus symbol: II_(3, 0) 2^-3_1 3^1, Genus symbol: II_(2, 0) 2^2_2) - (Genus symbol: II_(2, 0) 2^2_2, Genus symbol: II_(3, 0) 2^-3_1 3^1) + (Genus symbol: II_(3, 0) 2^3_3, Genus symbol: II_(2, 0) 2^-2 3^1) (Genus symbol: II_(2, 0) 2^-2 3^1, Genus symbol: II_(3, 0) 2^3_3) + (Genus symbol: II_(2, 0) 2^2_2, Genus symbol: II_(3, 0) 2^-3_1 3^1) (Genus symbol: II_(1, 0) 2^1_1, Genus symbol: II_(4, 0) 2^2_6 3^1) (Genus symbol: II_(0, 0), Genus symbol: II_(5, 0) 2^-1_3 3^1) ``` @@ -276,7 +276,9 @@ julia> admissible_triples(g, 2) function admissible_triples(G::ZZGenus, p::Integer; 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" - n = rank(G) + rG = rank(G) + sG = numerator(scale(G)) + lG = numerator(level(G)) pG, nG = signature_pair(G) if pA >= 0 @req pA <= pG "Wrong restrictions" @@ -289,24 +291,25 @@ function admissible_triples(G::ZZGenus, p::Integer; pA::Int = -1, pB::Int = -1) @req pB <= pG "Wrong restrictions" pA = pG - pB end - d = numerator(det(G)) + dG = numerator(det(G)) even = iseven(G) L = Tuple{ZZGenus, ZZGenus}[] - for ep in 0:div(n, p-1) + for ep in 0:div(rG, p-1) rp = (p-1)*ep if pB >= 0 rp >= pB || continue end - r1 = n - rp + r1 = rG - rp if pA >= 0 r1 >= pA || continue end m = min(ep, r1) - D = _find_D(d, m, p) - for (d1, dp) in D - L1 = _find_L(pG, nG, r1, d1, numerator(scale(G)), numerator(level(G)), p, even; pos = pA) - Lp = _find_L(pG, nG, rp, dp, numerator(scale(G)), numerator(level(G)), p, even; pos = pB) - for (A, B) in [(A, B) for A in L1 for B in Lp] + D = _find_D(dG, m, p) + while !is_empty(D) + d1, dp = pop!(D) + L1 = _find_L(pG, nG, r1, d1, sG, lG, p, even; pos = pA) + Lp = _find_L(pG, nG, rp, dp, sG, lG, p, even; pos = pB) + for (A, B) in Hecke.cartesian_product_iterator([L1, Lp]; inplace = false) if is_admissible_triple(A, B, G, p) push!(L, (A, B)) end @@ -326,7 +329,7 @@ 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, d::QQFieldElem) +function _ideals_of_norm(E::Field, d::QQFieldElem) if denominator(d) == 1 return _ideals_of_norm(E, numerator(d)) elseif numerator(d) == 1 @@ -336,18 +339,18 @@ function _ideals_of_norm(E, d::QQFieldElem) end end -function _ideals_of_norm(E, d::ZZRingElem) - isone(d) && return [fractional_ideal(maximal_order(E), one(E))] +function _ideals_of_norm(E::Field, d::ZZRingElem) + OE = maximal_order(E) + isone(d) && return Hecke.fractional_ideal_type(OE)[fractional_ideal(OE, one(E))] @hassert :ZZLatWithIsom 1 E isa Hecke.NfRel K = base_field(E) OK = maximal_order(K) - OE = maximal_order(E) DE = different(OE) ids = Hecke.fractional_ideal_type(OE)[] - primes = Vector{typeof(1*OE)}[] + primes = Vector{Hecke.ideal_type(OE)}[] for p in prime_divisors(d) v = valuation(d, p) - pd = [P[1] for P in prime_decomposition(OK, p)] + pd = Hecke.ideal_type(OK)[P[1] for P in prime_decomposition(OK, p)] for i in 1:length(pd) if !is_coprime(DE, ideal(OE, pd[i])) P = prime_decomposition(OE, pd[i])[1][1] @@ -355,7 +358,7 @@ function _ideals_of_norm(E, d::ZZRingElem) P = ideal(OE, pd[i]) end nv = valuation(norm(P), pd[i]) - push!(primes, [P^e for e in 0:divrem(v, nv)[1]]) + push!(primes, Hecke.ideal_type(OE)[P^e for e in 0:divrem(v, nv)[1]]) end end for I in Hecke.cartesian_product_iterator(primes; inplace=false) @@ -371,7 +374,7 @@ end # the possible signatures dictionnaries of any hermitian lattice over # E/K of rank rk, whose trace lattice has signature (s1, s2). -function _possible_signatures(s1, s2, E, rk) +function _possible_signatures(s1::IntegerUnion, s2::IntegerUnion, E::Field, rk::IntegerUnion) @hassert :ZZLatWithIsom 1 E isa Hecke.NfRel ok, q = Hecke.is_cyclotomic_type(E) @hassert :ZZLatWithIsom 1 ok @@ -449,8 +452,9 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) G = genus(Lf) repre = representatives(G) @vprintln :ZZLatWithIsom 1 "$(length(repre)) representative(s)" - for LL in repre - is_of_same_type(Lf, integer_lattice_with_isometry(LL, f^m; check=false)) && push!(reps, integer_lattice_with_isometry(LL, f; check=false)) + while !is_empty(repre) + LL = pop!(repre) + is_of_same_type(integer_lattice_with_isometry(LL, f^m; check = false), Lf) && push!(reps, integer_lattice_with_isometry(LL, f; check = false)) end return reps end @@ -461,7 +465,7 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) ok, rk = divides(rk, euler_phi(n*m)) ok || return reps - gene = Hecke.HermGenus[] + gene = HermGenus[] E, b = cyclotomic_field_as_cm_extension(n*m) Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) @@ -492,7 +496,7 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) continue end @vprintln :ZZLatWithIsom 1 "$H" - M, fM = Hecke.trace_lattice_with_isometry(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) @@ -500,7 +504,7 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) if is_even(M) != is_even(Lf) continue end - if !is_of_same_type(Lf, M^m) + if !is_of_same_type(M^m, Lf) continue end gr = genus_representatives(H) @@ -535,8 +539,8 @@ true julia> reps = splitting_of_hermitian_prime_power(Lf, 2) 2-element Vector{ZZLatWithIsom}: - Integer lattice with isometry of finite order 3 Integer lattice with isometry of finite order 6 + Integer lattice with isometry of finite order 3 julia> all(is_of_hermitian_type, reps) true @@ -563,18 +567,17 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::Int; pA::Int = @vprintln :ZZLatWithIsom 1 "Compute admissible triples" atp = admissible_triples(Lf, p; pA, pB) @vprintln :ZZLatWithIsom 1 "$(length(atp)) admissible triple(s)" - for (A, B) in atp + while !is_empty(atp) + A, B = pop!(atp) LB = integer_lattice_with_isometry(representative(B)) RB = representatives_of_hermitian_type(LB, p*q^e) - if is_empty(RB) - continue - end + is_empty(RB) && continue LA = integer_lattice_with_isometry(representative(A)) RA = representatives_of_hermitian_type(LA, q^e) + is_empty(RA) && continue for (L1, L2) in Hecke.cartesian_product_iterator([RA, RB]; inplace=false) E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p) append!(reps, E) - GC.gc() end end return reps @@ -602,10 +605,10 @@ julia> Lf = integer_lattice_with_isometry(L); julia> splitting_of_prime_power(Lf, 2) 4-element Vector{ZZLatWithIsom}: - Integer lattice with isometry of finite order 1 Integer lattice with isometry of finite order 2 Integer lattice with isometry of finite order 2 Integer lattice with isometry of finite order 2 + Integer lattice with isometry of finite order 1 julia> splitting_of_prime_power(Lf, 3, 1) 1-element Vector{ZZLatWithIsom}: @@ -640,12 +643,12 @@ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 0) A = splitting_of_hermitian_prime_power(A0, p) is_empty(A) && return reps B = splitting_of_prime_power(B0, p) + is_empty(B) && return reps for (L1, L2) in Hecke.cartesian_product_iterator([A, B]; inplace=false) - b == 1 && !divides(order_of_isometry(L1), p)[1] && !divides(order_of_isometry(L2), p)[1] && continue + b == 1 && !is_divisible_by(order_of_isometry(L1), p) && !is_divisible_by(order_of_isometry(L2), p) && continue E = admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p) @hassert :ZZLatWithIsom 1 b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) - GC.gc() end return reps end @@ -686,7 +689,7 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) phi = minimal_polynomial(Lf) chi = prod([cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e]) - @req divides(chi, phi)[1] "Minimal polynomial is not of the correct form" + @req is_divisible_by(chi, phi) "Minimal polynomial is not of the correct form" reps = ZZLatWithIsom[] @@ -702,9 +705,9 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, p::Int) A = representatives_of_hermitian_type(A0, p) is_empty(A) && return reps B = splitting_of_pure_mixed_prime_power(B0, p) + is_empty(B) && return reps for (LA, LB) in Hecke.cartesian_product_iterator([A, B]; inplace=false) E = admissible_equivariant_primitive_extensions(LB, LA, Lf, q, p) - GC.gc() append!(reps, E) end return reps @@ -791,12 +794,10 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::Int, b::Int = 1) A = splitting_of_pure_mixed_prime_power(A0, p) isempty(A) && return reps B = splitting_of_mixed_prime_power(B0, p, 0) + is_empty(B) && return reps for (LA, LB) in Hecke.cartesian_product_iterator([A, B]; inplace=false) E = admissible_equivariant_primitive_extensions(LB, LA, Lf, p) - GC.gc() - if b == 1 - filter!(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) - end + b == 1 && filter!(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) append!(reps, E) end return reps @@ -831,7 +832,8 @@ function enumerate_classes_of_lattices_with_isometry(L::ZZLat, order::IntegerUni vq = valuation(order, q) Lq = _enumerate_prime_power(L, q, vq) reps = ZZLatWithIsom[] - for N in Lq + while !is_empty(Lq) + N = pop!(Lq) append!(reps, _split_prime_power(N, p, vp)) end @hassert :ZZLatWithIsom 6 all(N -> order_of_isometry(N) == order, reps) @@ -895,7 +897,7 @@ end # ############################################################################### -function _get_isometry_prime_power!(D, L, p, j) +function _get_isometry_prime_power!(D::Dict, L::ZZLat, p::IntegerUnion, j::IntegerUnion) if !haskey(D, p) Dp = splitting_of_prime_power(integer_lattice_with_isometry(L), p, 1) D[p] = Dp @@ -915,7 +917,7 @@ function _get_isometry_prime_power!(D, L, p, j) return nothing end -function _get_isometry_composite!(D, n) +function _get_isometry_composite!(D::Dict, n::IntegerUnion) p, q = sort(prime_divisors(n)) i, j = valuation(n, p), valuation(n, q) for k in 1:i @@ -935,7 +937,7 @@ function _test_isometry_enumeration(L::ZZLat, k::Int = 2*rank(L)^2) n = rank(L) ord = filter(m -> euler_phi(m) <= n && length(prime_divisors(m)) <= 2, 2:k) pds = union(reduce(vcat, prime_divisors.(ord))) - vals = [maximum([valuation(x, p) for x in ord]) for p in pds] + vals = Int[maximum([valuation(x, p) for x in ord]) for p in pds] D = Dict{Int, Vector{ZZLatWithIsom}}() D[1] = ZZLatWithIsom[integer_lattice_with_isometry(L)] for i in 1:length(vals) diff --git a/experimental/QuadFormAndIsom/src/exports.jl b/experimental/QuadFormAndIsom/src/exports.jl index 09b2d9a668d7..6bfd7915d826 100644 --- a/experimental/QuadFormAndIsom/src/exports.jl +++ b/experimental/QuadFormAndIsom/src/exports.jl @@ -15,8 +15,7 @@ export is_of_same_type export is_of_type export is_hermitian export order_of_isometry -export primitive_embeddings_of_primary_lattice -export primitive_embeddings_in_primary_lattice +export primitive_embeddings export quadratic_space_with_isometry export representatives_of_hermitian_type export splitting_of_hermitian_prime_power diff --git a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index 20d945fa5c65..a67feb5b9bb8 100644 --- a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl +++ b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl @@ -69,7 +69,7 @@ function _get_quotient_inert(P::Hecke.NfRelOrdIdl, i::Int) RPabs, mRPabs = quo(OEabs, Pabs^i) URPabs, mURPabs = unit_group(RPabs) - f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(x)))))))) for x in gens(URPabs)]) + f = hom(URPabs, URp, elem_type(URp)[mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(x)))))))) for x in gens(URPabs)]) K, mK = kernel(f) @@ -114,7 +114,7 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) e = valuation(different(OE), P) if i < e - S = abelian_group(Int[]) + S = abelian_group() return S, x -> one(E), x -> id(S) end @@ -140,7 +140,7 @@ function _get_quotient_ramified(P::Hecke.NfRelOrdIdl, i::Int) RPabs, mRPabs = quo(OEabs, Pabs^i) URPabs, mURPabs = unit_group(RPabs) - f = hom(URPabs, URp, [mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(x)))))))) for x in gens(URPabs)]) + f = hom(URPabs, URp, elem_type(URp)[mURp\(mRp(OK(norm(EabstoE(elem_in_nf(mRPabs\(mURPabs(x)))))))) for x in gens(URPabs)]) K, mK = kernel(f) @@ -205,12 +205,12 @@ end # abelian group where one do the local determinants computations # for the hermitian version of Miranda-Morrison theory -function _get_product_quotient(E::Hecke.NfRel, Fac) +function _get_product_quotient(E::Hecke.NfRel, Fac::Vector{Tuple{NfOrdIdl, Int}}) OE = maximal_order(E) groups = GrpAbFinGen[] - exps = [] - dlogs = [] - Ps = [] + exps = Function[] + dlogs = Function[] + Ps = Hecke.ideal_type(OE)[] if length(Fac) == 0 A = abelian_group() @@ -333,7 +333,7 @@ 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, [OqL(compose(phi12, compose(hom(g), inv(phi12)))) for g in gens(G2)]) + 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)) # This is the associated hermitian O_E-lattice to (L, f): we want to make qL @@ -424,7 +424,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) gene_norm_one = Hecke.NfRelElem[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([x for i in 1:length(S)]) for x in gene_norm_one]) + FOEmodFsharp, m = sub(RmodFsharp, elem_type(RmodFsharp)[Fsharplog(typeof(x)[x for i in 1:length(S)]) for x in gene_norm_one]) I = intersect(FOEmodFsharp, FmodFsharp) # Q is where the determinant of our lifts to good precision will live. So @@ -464,7 +464,7 @@ 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, gens(G2), SQtoGSQ.(imgs); check = false) f = compose(GtoG2, f2) return f @@ -482,15 +482,15 @@ end # This function is an import of a function written by Markus Kirschmer in the # Magma package about hermitian lattices. -function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) +function _is_special(L::HermLat, p::NfOrdIdl) OE = base_ring(L) E = nf(OE) lp = prime_decomposition(OE, p) - if lp[1][2] != 2 || !Oscar.iseven(rank(L)) + if lp[1][2] != 2 || !iseven(rank(L)) return false end - _, _R, S = jordan_decomposition(L, p) - R = [nrows(m) for m in _R] + _, R, S = jordan_decomposition(L, p) + R = Int[nrows(m) for m in R] for r in R if r != 2 return false @@ -507,7 +507,7 @@ function _is_special(L::Hecke.HermLat, p::Hecke.NfOrdIdl) 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)]) - return is_locally_isometric(L, hermitian_lattice(E, gram = H), p) + return is_locally_isometric(L, hermitian_lattice(E; gram = H), p) end ############################################################################### @@ -524,7 +524,11 @@ Oscar.canonical_unit(x::NfOrdQuoRingElem) = one(parent(x)) # integer (for the given local field at p) entries which defines an isometry of # D^{-1}H^# modulo H. -function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T, P::Hecke.NfRelOrdIdl, BHp::T) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} +function _transfer_discriminant_isometry(res::AbstractSpaceRes, + g::AutomorphismGroupElem{TorQuadModule}, + Bp::T, + P::Hecke.NfRelOrdIdl, + BHp::T) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} E = base_ring(codomain(res)) Eabs, EabstoE = absolute_simple_field(E) Pabs = EabstoE\P @@ -536,13 +540,7 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG # action of g via res. B2 = zero(Bp) for i in 1:nrows(Bp) - vE = vec(collect(Bp[i, :])) - vQ = res\vE - vq = q(vQ) - gvq = g(vq) - gvQ = lift(gvq) - gvE = res(gvQ) - B2[i, :] = gvE + B2[i, :] = res(lift(g(q(res\(vec(collect(Bp[i, :]))))))) end # Here BHp is the inverse of a local basis matrix of H at p. Since we look for @@ -554,34 +552,32 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismG # P-valuation i of d, and we then map everything in OEabs/Pabs^i. There, there # should be a modular solution KQ such that KQ*BpQ == B2Q. We lift this matrix # in OEabs and map it back to OE. - newBp = Bp*BHp - newB2 = B2*BHp - Bpabs = map_entries(a -> EabstoE\a, newBp) - B2abs = map_entries(a -> EabstoE\a, newB2) + Bpabs = map_entries(a -> EabstoE\a, Bp*BHp) + B2abs = map_entries(a -> EabstoE\a, B2*BHp) # Here d is not necessarily well defined in O_E, but as it is implemented, # each of the denominator(a, O_E) return the smallest positive integer d such # that d*a lies in O_E, while `a` might be a non-integral element in E. d = lcm(lcm([denominator(a, OEabs) for a in Bpabs]), lcm([denominator(a, OEabs) for a in B2abs])) - dBpabs = change_base_ring(OEabs, d*Bpabs) - dB2abs = change_base_ring(OEabs, d*B2abs) + Bpabs = change_base_ring(OEabs, d*Bpabs) + B2abs = change_base_ring(OEabs, d*B2abs) # We would need to solve the equation modulo OE, but we multiplied by d, so we # need to keep track of d. Note that with the precaution we took earlier, the # Pabs-valuation of d is necessarily positive, so this quotient cannot be # trivial. Q, p = quo(OEabs, Pabs^(valuation(d, Pabs))) - BpQ = map_entries(p, dBpabs) - B2Q = map_entries(p, dB2abs) + Bpabs = map_entries(p, Bpabs) + B2abs = map_entries(p, B2abs) # Our local modular solution we have to lift - KQ = solve_left(BpQ, B2Q) - K = map_entries(a -> EabstoE(Eabs(p\a)), KQ) + K = solve_left(Bpabs, B2abs) + K = map_entries(a -> EabstoE(Eabs(p\a)), K) # If what we have done is correct then K*newBp == newB2 modulo O_p, so all # entries in the difference K*newBp-newB2 must have non-negative P-valuation. # Since it is the case, then K satisfies K*Bp == B2 mod H locally at p. - @hassert :ZZLatWithIsom 1 _scale_valuation(K*newBp-newB2, P) >= 0 + @hassert :ZZLatWithIsom 1 _scale_valuation((K*Bp-B2)*BHp, P) >= 0 return K end @@ -589,14 +585,14 @@ end function _scale_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} @hassert :ZZLatWithIsom 1 nf(order(P)) === base_ring(M) iszero(M) && return inf - return minimum([valuation(v, P) for v in collect(M) if !iszero(v)]) + return minimum(valuation(v, P) for v in collect(M) if !iszero(v)) end # the minimum P-valuation among all the non-zero diagonal entries of M function _norm_valuation(M::T, P::Hecke.NfRelOrdIdl) where T <: MatrixElem{Hecke.NfRelElem{nf_elem}} @hassert :ZZLatWithIsom 1 nf(order(P)) === base_ring(M) iszero(diagonal(M)) && return inf - r = minimum([valuation(v, P) for v in diagonal(M) if !iszero(v)]) + r = minimum(valuation(v, P) for v in diagonal(M) if !iszero(v)) return r end @@ -668,7 +664,7 @@ end # D_L (H is the hermitian structure associated to (L, f) along res) which # aims to approximately transfer to H2 along res at P # -function _approximate_isometry(H::Hecke.HermLat, H2::Hecke.HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) +function _approximate_isometry(H::HermLat, H2::HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.NfRelOrdIdl, e::Int, a::Int, k::Int, res::AbstractSpaceRes) E = base_field(H) @hassert :ZZLatWithIsom 1 nf(order(P)) === E ok, b = is_modular(H, minimum(P)) @@ -707,7 +703,7 @@ end # the jordan decomposition of D^{-1}H^# at p. From that point, we massage a bit # the basis matrices of the other jordan blocks to obtain local basis matrices # which span sublattices of D^{-1}H^#. -function _local_basis_modular_submodules(H::Hecke.HermLat, p::Hecke.NfOrdIdl, a::Int, res::AbstractSpaceRes) +function _local_basis_modular_submodules(H::HermLat, p::NfOrdIdl, a::Int, res::AbstractSpaceRes) L = restrict_scalars(H, res) B, _ , exps = jordan_decomposition(H, p) if exps[end] == -a @@ -764,7 +760,7 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) nug = valuation(g, Pabs) if nu == nug d = denominator(g+1, OEabs) - rt = roots(t^2 - (g+1)*d^2; max_roots = 1, ispure = true, is_normal=true) + rt = roots(t^2 - (g+1)*d^2; max_roots = 1, ispure = true, is_normal = true) if !is_empty(rt) rho = (1+rt[1])//2 @hassert :ZZLatWithIsom 1 valuation(rho, Pabs) == 1-e @@ -774,4 +770,3 @@ function _find_rho(P::Hecke.NfRelOrdIdl, e) end end end - diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 0e5e42f5c568..f584a5cfc311 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -593,7 +593,7 @@ function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true if ambient_representation f_ambient = f - Vf = quadratic_space_with_isometry(ambient_space(L), f_ambient; check=check) + Vf = quadratic_space_with_isometry(ambient_space(L), f_ambient; check) B = basis_matrix(L) ok, f = can_solve_with_solution(B, B*f_ambient; side = :left) @req ok "Isometry does not restrict to L" @@ -602,9 +602,9 @@ function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true B = basis_matrix(L) B2 = orthogonal_complement(V, B) C = vcat(B, B2) - f_ambient = block_diagonal_matrix([f, identity_matrix(QQ, nrows(B2))]) + f_ambient = block_diagonal_matrix(QQMatrix[f, identity_matrix(QQ, nrows(B2))]) f_ambient = inv(C)*f_ambient*C - Vf = quadratic_space_with_isometry(V, f_ambient; check=check) + Vf = quadratic_space_with_isometry(V, f_ambient; check) end n = multiplicative_order(f) @@ -729,7 +729,7 @@ Integer lattice of rank 3 and degree 5 ``` """ function lattice(Vf::QuadSpaceWithIsom, B::MatElem{<:RationalUnion}; isbasis::Bool = true, check::Bool = true) - L = lattice(space(Vf), B; isbasis=isbasis, check=check) + L = lattice(space(Vf), B; isbasis, check) ok, fB = can_solve_with_solution(basis_matrix(L), basis_matrix(L)*isometry(Vf); side = :left) n = is_zero(fB) ? -1 : multiplicative_order(fB) @req ok "The lattice defined by B is not preserved under the action of the isometry of Vf" @@ -781,7 +781,7 @@ true function lattice_in_same_ambient_space(L::ZZLatWithIsom, B::MatElem; check::Bool = true) @req !check || (rank(B) == nrows(B)) "The rows of B must define a free system of vectors" Vf = ambient_space(L) - return lattice(Vf, B; check = check) + return lattice(Vf, B; check) end ############################################################################### @@ -844,7 +844,7 @@ with gram matrix ``` """ function rescale(Lf::ZZLatWithIsom, a::RationalUnion) - return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf); check=false) + return lattice(rescale(ambient_space(Lf), a), basis_matrix(Lf); check = false) end @doc raw""" @@ -885,7 +885,7 @@ Integer lattice of rank 5 and degree 5 ``` """ function Base.:^(Lf::ZZLatWithIsom, n::Int) - return lattice(ambient_space(Lf)^n, basis_matrix(Lf)) + return lattice(ambient_space(Lf)^n, basis_matrix(Lf); check = false) end @doc raw""" @@ -980,11 +980,11 @@ true ``` """ function lll(Lf::ZZLatWithIsom; same_ambient::Bool = true) - L2 = lll(lattice(Lf); same_ambient = same_ambient) + L2 = lll(lattice(Lf); same_ambient) if same_ambient - return lattice_in_same_ambient_space(Lf, basis_matrix(L2); check=false) + return lattice_in_same_ambient_space(Lf, basis_matrix(L2); check = false) else - return integer_lattice_with_isometry(L2, isometry(Lf); check=false, ambient_representation=false) + return integer_lattice_with_isometry(L2, isometry(Lf); check = false, ambient_representation = false) end end @@ -1075,8 +1075,8 @@ Integer lattice of rank 10 and degree 10 """ function direct_sum(x::Vector{ZZLatWithIsom}) Vf, inj = direct_sum(ambient_space.(x)) - Bs = diagonal_matrix(basis_matrix.(x)) - return lattice(Vf, Bs; check=false), inj + Bs = block_diagonal_matrix(basis_matrix.(x)) + return lattice(Vf, Bs; check = false), inj end direct_sum(x::Vararg{ZZLatWithIsom}) = direct_sum(collect(x)) @@ -1168,7 +1168,7 @@ Integer lattice of rank 10 and degree 10 """ function direct_product(x::Vector{ZZLatWithIsom}) Vf, proj = direct_product(ambient_space.(x)) - Bs = diagonal_matrix(basis_matrix.(x)) + Bs = block_diagonal_matrix(basis_matrix.(x)) return lattice(Vf, Bs; check=false), proj end @@ -1290,7 +1290,7 @@ julia> matrix(compose(inj[1], proj[2])) """ function biproduct(x::Vector{ZZLatWithIsom}) Vf, inj, proj = biproduct(ambient_space.(x)) - Bs = diagonal_matrix(basis_matrix.(x)) + Bs = block_diagonal_matrix(basis_matrix.(x)) return lattice(Vf, Bs; check=false), inj, proj end @@ -1483,12 +1483,14 @@ function discriminant_group(Lf::ZZLatWithIsom) L = lattice(Lf) f = ambient_isometry(Lf) q = discriminant_group(L) - Oq = orthogonal_group(q) - return (q, Oq(gens(matrix_group(f))[1]; check = false))::Tuple{TorQuadModule, AutomorphismGroupElem{TorQuadModule}} + 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}} end @doc raw""" - image_centralizer_in_Oq(Lf::ZZLatWithIsom) -> AutomorphismGroup{TorQuadModule} + image_centralizer_in_Oq(Lf::ZZLatWithIsom) -> AutomorphismGroup{TorQuadModule}, + GAPGroupHomomorphism Given an integral lattice with isometry $(L, f)$, return the image $G_L$ in $O(q_L, \bar{f})$ of the centralizer $O(L, f)$ of `f` in $O(L)$. Here $q_L$ @@ -1503,20 +1505,23 @@ julia> f = matrix(QQ, 2, 2, [1 1; 0 -1]); julia> Lf = integer_lattice_with_isometry(L, f); -julia> G = image_centralizer_in_Oq(Lf) +julia> G, _ = image_centralizer_in_Oq(Lf) +(Group of isometries of Finite quadratic module: Z/3 -> Q/2Z generated by 2 elements, Group homomorphism from Group of isometries of Finite quadratic module: Z/3 -> Q/2Z generated by 2 elements +to +Group of isometries of Finite quadratic module: Z/3 -> Q/2Z generated by 1 elements) julia> order(G) 2 ``` """ -@attr AutomorphismGroup{TorQuadModule} function image_centralizer_in_Oq(Lf::ZZLatWithIsom) +@attr Tuple{AutomorphismGroup{TorQuadModule}, GAPGroupHomomorphism{AutomorphismGroup{TorQuadModule}, AutomorphismGroup{TorQuadModule}}} function image_centralizer_in_Oq(Lf::ZZLatWithIsom) @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))) - GL, _ = image_in_Oq(L) + return image_in_Oq(L) elseif is_definite(L) OL = orthogonal_group(L) f = OL(f) @@ -1524,18 +1529,20 @@ julia> order(G) 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) - GL = Oscar._orthogonal_group(qL, UL; check = false) + 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)]))] - unique!(UL) - GL = Oscar._orthogonal_group(qL, UL; check = false) + 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) - GL, _ = kernel(dets) + return kernel(dets) end - return GL::AutomorphismGroup{TorQuadModule} end ############################################################################### @@ -1544,23 +1551,21 @@ end # ############################################################################### -function _real_kernel_signatures(L::ZZLat, M) +function _real_kernel_signatures(L::ZZLat, M::MatElem) C = base_ring(M) - bL = basis_matrix(L) - GL = gram_matrix(ambient_space(L)) - bLC = change_base_ring(C, bL) - GLC = change_base_ring(C, GL) - k, KC = left_kernel(M) - newGC = KC*bLC*GLC*transpose(KC*bLC) + G = gram_matrix(L) + G = change_base_ring(C, G) + _, K = left_kernel(M) + diag = K*G*transpose(K) - newGC = Hecke._gram_schmidt(newGC, C)[1] - diagC = diagonal(newGC) + diag = Hecke._gram_schmidt(diag, C)[1] + diag = diagonal(diag) - @hassert :ZZLatWithIsom 1 all(z -> isreal(z), diagC) - @hassert :ZZLatWithIsom 1 all(z -> !iszero(z), diagC) + @hassert :ZZLatWithIsom 1 all(z -> isreal(z), diag) + @hassert :ZZLatWithIsom 1 all(z -> !iszero(z), diag) - k1 = count(z -> z > 0, diagC) - k2 = length(diagC) - k1 + k1 = count(z -> z > 0, diag) + k2 = length(diag) - k1 return k1, k2 end @@ -1605,11 +1610,11 @@ function signatures(Lf::ZZLatWithIsom) eig = eigenvalues(f, QQBar) j = findfirst(z -> findfirst(k -> isone(z^k), 1:n) == n, eig) lambda = C(eig[j]) - Sq = [i for i in 1:div(n,2) if gcd(i,n) == 1] - D = Dict{Integer, Tuple{Int64, Int64}}() - fC = change_base_ring(C, f) + Sq = Int[i for i in 1:div(n,2) if gcd(i,n) == 1] + D = Dict{Integer, Tuple{Int, Int}}() + f = change_base_ring(C, f) for i in Sq - M = fC + inv(fC) - lambda^i - lambda^(-i) + M = f + inv(f) - lambda^i - lambda^(-i) D[i] = _real_kernel_signatures(L, M) end return D @@ -1777,7 +1782,7 @@ with gram matrix ``` """ function invariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) - return invariant_lattice(L, matrix.(gens(G)); ambient_representation = ambient_representation) + return invariant_lattice(L, matrix.(gens(G)); ambient_representation) end @doc raw""" @@ -1816,7 +1821,9 @@ function coinvariant_lattice(Lf::ZZLatWithIsom) if chi(1) == 0 R = parent(chi) x = gen(R) - chi = divexact(chi, x-1) + while chi(1) == 0 + chi = divexact(chi, x-1) + end end return kernel_lattice(Lf, chi) end @@ -1851,15 +1858,14 @@ true ``` """ function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) - F = invariant_lattice(L, G; ambient_representation = ambient_representation) + F = invariant_lattice(L, G; ambient_representation) C = orthogonal_submodule(L, F) gene = QQMatrix[] for g in gens(G) if !ambient_representation - mL = matrix(g) - m_amb = solve(basis_matrix(L), mL*basis_matrix(L)) - mC = solve_left(basis_matrix(C), basis_matrix(C)*m_amb) - push!(gene, mC) + m = solve(basis_matrix(L), matrix(g)*basis_matrix(L)) + m = solve_left(basis_matrix(C), basis_matrix(C)*m) + push!(gene, m) else push!(gene, matrix(g)) end @@ -1904,15 +1910,14 @@ with gram matrix ``` """ function invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; ambient_representation::Bool = true) - F = invariant_lattice(L, G; ambient_representation = ambient_representation) + F = invariant_lattice(L, G; ambient_representation) C = orthogonal_submodule(L, F) gene = QQMatrix[] for g in gens(G) if !ambient_representation - mL = matrix(g) - m_amb = solve(basis_matrix(L), mL*basis_matrix(L)) - mC = solve_left(basis_matrix(C), basis_matrix(C)*m_amb) - push!(gene, mC) + m = solve(basis_matrix(L), matrix(g)*basis_matrix(L)) + m = solve_left(basis_matrix(C), basis_matrix(C)*m) + push!(gene, m) else push!(gene, matrix(g)) end @@ -1962,14 +1967,14 @@ true f = isometry(Lf) n = order_of_isometry(Lf) @req is_finite(n) "Isometry must be of finite order" - divs = divisors(n) + divs = sort!(divisors(n)) Qx = Hecke.Globals.Qx x = gen(Qx) t = Dict{Integer, Tuple}() for l in divs Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl); check=false, ambient_representation=false) + Hl = hermitian_structure(lattice(Hl), isometry(Hl); check = false, ambient_representation = false) end Al = kernel_lattice(Lf, x^l-1) t[l] = (genus(Hl), genus(Al)) @@ -2007,8 +2012,8 @@ function is_of_type(L::ZZLatWithIsom, t::Dict) for l in divs Hl = kernel_lattice(L, cyclotomic_polynomial(l)) if !(order_of_isometry(Hl) in [-1, 1, 2]) - t[l][1] isa Hecke.HermGenus || return false - Hl = Hecke.hermitian_structure(lattice(Hl), isometry(Hl); check=false, ambient_representation=false, E = base_field(t[l][1])) + t[l][1] isa HermGenus || return false + Hl = hermitian_structure(lattice(Hl), isometry(Hl); check = false, ambient_representation = false, E = base_field(t[l][1])) end genus(Hl) == t[l][1] || return false Al = kernel_lattice(L, x^l-1) @@ -2078,7 +2083,7 @@ true function is_hermitian(t::Dict) ke = collect(keys(t)) n = maximum(ke) - return all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, [i for i in ke if i != n]) + return all(i -> rank(t[i][1]) == rank(t[i][2]) == 0, Int[i for i in ke if i != n]) end ############################################################################### diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 37a5a60ca0cc..fa3f7a496874 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -365,7 +365,7 @@ Quadratic space of dimension 2 function quadratic_space_with_isometry(V::Hecke.QuadSpace; neg::Bool = false) f = identity_matrix(QQ, dim(V)) f = neg ? -f : f - return quadratic_space_with_isometry(V, f; check=false) + return quadratic_space_with_isometry(V, f; check = false) end ############################################################################### @@ -453,7 +453,7 @@ Quadratic space of dimension 2 ``` """ function Base.:^(Vf::QuadSpaceWithIsom, n::Int) - return quadratic_space_with_isometry(space(Vf), isometry(Vf)^n; check=false) + return quadratic_space_with_isometry(space(Vf), isometry(Vf)^n; check = false) end @doc raw""" @@ -546,7 +546,7 @@ with gram matrix function direct_sum(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj = direct_sum(space.(x)) f = block_diagonal_matrix(isometry.(x)) - return quadratic_space_with_isometry(V, f; check=false), inj + return quadratic_space_with_isometry(V, f; check = false), inj end direct_sum(x::Vararg{QuadSpaceWithIsom}) = direct_sum(collect(x)) @@ -641,7 +641,7 @@ with gram matrix function direct_product(x::Vector{T}) where T <: QuadSpaceWithIsom V, proj = direct_product(space.(x)) f = block_diagonal_matrix(isometry.(x)) - return quadratic_space_with_isometry(V, f; check=false), proj + return quadratic_space_with_isometry(V, f; check = false), proj end direct_product(x::Vararg{QuadSpaceWithIsom}) = direct_product(collect(x)) @@ -757,7 +757,7 @@ julia> matrix(compose(inj[1], proj[2])) function biproduct(x::Vector{T}) where T <: QuadSpaceWithIsom V, inj, proj = biproduct(space.(x)) f = block_diagonal_matrix(isometry.(x)) - return quadratic_space_with_isometry(V, f; check=false), inj, proj + return quadratic_space_with_isometry(V, f; check = false), inj, proj end biproduct(x::Vararg{QuadSpaceWithIsom}) = biproduct(collect(x)) diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index 1616e6b88511..31c2dc04c09b 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -1,6 +1,9 @@ using Test using Oscar +Oscar.set_lwi_level(2) +set_verbosity_level(:ZZLatWithIsom, -1) + @testset "Printings" begin function _show_details(io::IO, X::Union{ZZLatWithIsom, QuadSpaceWithIsom}) return show(io, MIME"text/plain"(), X) @@ -67,8 +70,8 @@ end ] OA3 = matrix_group(agg) set_attribute!(A3, :isometry_group, OA3) - f = rand(agg) - g = rand(agg) + f = agg[2] + g = agg[4] L = integer_lattice(gram = matrix(QQ, 0, 0, [])) Lf = integer_lattice_with_isometry(L; neg = true) @@ -80,7 +83,7 @@ end @test isone(isometry(L)) @test isone(ambient_isometry(L)) @test isone(order_of_isometry(L)) - @test order(image_centralizer_in_Oq(L)) == 2 + @test order(image_centralizer_in_Oq(L)[1]) == 2 for func in [rank, genus, basis_matrix, is_positive_definite, gram_matrix, det, scale, norm, is_integral, is_negative_definite, @@ -131,7 +134,7 @@ end f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; -2 -4 -6 -4 -3 -2 -1 -3; 2 4 6 5 4 3 2 3; -1 -2 -3 -3 -3 -2 -1 -1; 0 0 0 0 1 0 0 0; 1 2 3 3 2 1 0 2]); Lf = integer_lattice_with_isometry(L, f); - GLf = @inferred image_centralizer_in_Oq(Lf) + GLf, _ = @inferred image_centralizer_in_Oq(Lf) @test order(GLf) == 600 M = @inferred coinvariant_lattice(Lf) @@ -160,7 +163,7 @@ end L = integer_lattice(B; gram = G); f = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 -1 1 0 0; 0 0 0 0 0 0 0 1; 0 0 0 0 0 0 -1 1]); Lf = integer_lattice_with_isometry(L, f); - GL = image_centralizer_in_Oq(Lf) + GL = image_centralizer_in_Oq(Lf)[1] @test order(GL) == 72 B = matrix(QQ, 4, 6, [0 0 0 0 -2 1; 0 0 0 0 3 -4; 0 0 1 0 -1 0; 0 0 0 1 0 -1]); @@ -168,7 +171,7 @@ end L = integer_lattice(B; gram = G); f = matrix(QQ, 6, 6, [1 0 0 0 0 0; 0 1 0 0 0 0; 0 0 0 0 1 0; 0 0 0 0 0 1; 0 0 -1 0 0 1; 0 0 0 -1 1 -1]); Lf = integer_lattice_with_isometry(L, f); - GL = image_centralizer_in_Oq(Lf) + GL = image_centralizer_in_Oq(Lf)[1] @test order(GL) == 2 F, C, _ = invariant_coinvariant_pair(A3, OA3) @@ -176,6 +179,8 @@ end @test C == A3 _, _, G = invariant_coinvariant_pair(A3, OA3; ambient_representation = false) @test order(G) == order(OA3) + C, _ = coinvariant_lattice(A3, sub(OA3, elem_type(OA3)[OA3(agg[2]), OA3(agg[4])])[1]) + @test is_sublattice(A3, C) end @@ -198,16 +203,17 @@ end end N = rand(D[6]) - ONf = image_centralizer_in_Oq(integer_lattice_with_isometry(lattice(N), ambient_isometry(N))) + ONf = image_centralizer_in_Oq(integer_lattice_with_isometry(lattice(N), ambient_isometry(N)))[1] # for N, the image in OqN of the centralizer of fN in ON is directly # computing during the construction of the admissible primitive extension. # We compare if at least we obtain the same orders (we can't directly # compare the groups since they do not act exactly on the same module... # and isomorphism test might be slow) - @test order(ONf) == order(image_centralizer_in_Oq(N)) + @test order(ONf) == order(image_centralizer_in_Oq(N)[1]) E6 = root_lattice(:E, 6) @test length(enumerate_classes_of_lattices_with_isometry(E6, 10)) == 3 + @test length(enumerate_classes_of_lattices_with_isometry(E6, 20)) == 0 @test length(enumerate_classes_of_lattices_with_isometry(E6, 18)) == 1 @test length(enumerate_classes_of_lattices_with_isometry(genus(E6), 1)) == 1 @@ -218,34 +224,39 @@ end @testset "Primitive embeddings" begin # Compute orbits of short vectors - k = integer_lattice(gram=matrix(QQ,1,1,[4])) + k = integer_lattice(; gram=matrix(QQ,1,1,[4])) E8 = root_lattice(:E, 8) - ok, sv = primitive_embeddings_of_primary_lattice(E8, k; classification =:sublat) + ok, sv = primitive_embeddings(E8, k; classification =:sublat) @test ok @test length(sv) == 1 - ok, sv = primitive_embeddings_in_primary_lattice(rescale(E8, 2), rescale(k, QQ(1//2)); check=false) + ok, sv = primitive_embeddings(rescale(E8, 2), rescale(k, QQ(1//2)); check=false) @test !ok @test is_empty(sv) - @test_throws ArgumentError primitive_embeddings_of_primary_lattice(rescale(E8, -1), k; check=false) + @test_throws ArgumentError primitive_embeddings(rescale(E8, -1), k; check=false) - k = integer_lattice(gram=matrix(QQ,1,1,[6])) + k = integer_lattice(; gram=matrix(QQ,1,1,[6])) E7 = root_lattice(:E, 7) - ok, sv = primitive_embeddings_in_primary_lattice(E7, k; classification = :emb) + ok, sv = primitive_embeddings(E7, k; classification = :emb) @test ok @test length(sv) == 2 q = discriminant_group(E7) p, z, n = signature_tuple(E7) - ok, _ = primitive_embeddings_in_primary_lattice(q, (p,n), E7; classification = :none) + ok, _ = primitive_embeddings(q, (p,n), E7; classification = :none) + @test ok + A5 = root_lattice(:A, 5) + ok, sv = primitive_embeddings(A5, k) @test ok - k = integer_lattice(gram=matrix(QQ,1,1,[2])) - ok, sv = primitive_embeddings_of_primary_lattice(E7, k) + @test length(sv) == 2 + + k = integer_lattice(; gram=matrix(QQ,1,1,[2])) + ok, sv = primitive_embeddings(E7, k) @test ok @test length(sv) == 1 - ok, _ = primitive_embeddings_of_primary_lattice(q, (p,n), E7; classification = :none) + ok, _ = primitive_embeddings(q, (p,n), E7; classification = :none) @test ok - @test !primitive_embeddings_in_primary_lattice(rescale(E7, 2), k; classification = :none, check = false)[1] + @test !primitive_embeddings(rescale(E7, 2), k; classification = :none, check = false)[1] B = matrix(QQ, 8, 8, [1 0 0 0 0 0 0 0; 0 1 0 0 0 0 0 0; 0 0 1 0 0 0 0 0; 0 0 0 1 0 0 0 0; 0 0 0 0 1 0 0 0; 0 0 0 0 0 1 0 0; 0 0 0 0 0 0 1 0; 0 0 0 0 0 0 0 1]); G = matrix(QQ, 8, 8, [-4 2 0 0 0 0 0 0; 2 -4 2 0 0 0 0 0; 0 2 -4 2 0 0 0 2; 0 0 2 -4 2 0 0 0; 0 0 0 2 -4 2 0 0; 0 0 0 0 2 -4 2 0; 0 0 0 0 0 2 -4 0; 0 0 2 0 0 0 0 -4]); @@ -257,6 +268,4 @@ end reps = @inferred admissible_equivariant_primitive_extensions(F, C, Lf^0, 5) @test length(reps) == 1 @test is_of_same_type(Lf, reps[1]) - end - From c809506be2a343141677671bfe583570c520aef2 Mon Sep 17 00:00:00 2001 From: StevellM Date: Mon, 14 Aug 2023 15:07:03 +0200 Subject: [PATCH 76/76] suggestion + minor extra changes --- experimental/QuadFormAndIsom/src/embeddings.jl | 10 +++++----- experimental/QuadFormAndIsom/src/enumeration.jl | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index c7c9ed013579..40e8cfe67e01 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -20,10 +20,10 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu OA = orthogonal_group(A) OB = orthogonal_group(B) - gene = data.(union(AinD.(gens(A)), BinD.(gens(B)))) + gene = data.(union!(AinD.(gens(A)), BinD.(gens(B)))) geneOAinOD = elem_type(OD)[] for f in gens(OA) - imgf = data.(union(AinD.(f.(gens(A))), BinD.(gens(B)))) + imgf = data.(union!(AinD.(f.(gens(A))), BinD.(gens(B)))) fab = hom(gene, imgf) fD = OD(hom(D, D, fab.map)) push!(geneOAinOD, fD) @@ -31,7 +31,7 @@ function _sum_with_embeddings_orthogonal_groups(A::TorQuadModule, B::TorQuadModu geneOBinOD = elem_type(OD)[] for f in gens(OB) - imgf = data.(union(AinD.(gens(A)), BinD.(f.(gens(B))))) + imgf = data.(union!(AinD.(gens(A)), BinD.(f.(gens(B))))) fab = hom(gene, imgf) fD = OD(hom(D, D, fab.map)) push!(geneOBinOD, fD) @@ -434,7 +434,7 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu if compute_stab stabq = AutomorphismGroupElem{TorQuadModule}[GtoMGp\(s) for s in gens(stab)] - stabq, _ = sub(G, union(stabq, gens(satV))) + stabq, _ = sub(G, union!(stabq, 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) @@ -641,7 +641,7 @@ integer lattice `M`, return whether `M` embeds primitively in `L`. The first input of the function is a boolean `T` stating whether or not `M` embeds primitively in `L`. The second output `V` consists on triples -`(L', M', N')` where `L'` is isometric to `L`, `M'` is a primtiive sublattice +`(L', M', N')` where `L'` is isometric to `L`, `M'` is a primitive sublattice of `L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. If `T == false`, then `V` will always be the empty list. If `T == true`, then diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 16167eff5952..4f0960910f85 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -465,8 +465,8 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) ok, rk = divides(rk, euler_phi(n*m)) ok || return reps - gene = HermGenus[] E, b = cyclotomic_field_as_cm_extension(n*m) + gene = Hecke.genus_herm_type(E)[] Eabs, EabstoE = absolute_simple_field(E) DE = EabstoE(different(maximal_order(Eabs))) @@ -936,7 +936,7 @@ end function _test_isometry_enumeration(L::ZZLat, k::Int = 2*rank(L)^2) n = rank(L) ord = filter(m -> euler_phi(m) <= n && length(prime_divisors(m)) <= 2, 2:k) - pds = union(reduce(vcat, prime_divisors.(ord))) + pds = unique!(reduce(vcat, prime_divisors.(ord))) vals = Int[maximum([valuation(x, p) for x in ord]) for p in pds] D = Dict{Int, Vector{ZZLatWithIsom}}() D[1] = ZZLatWithIsom[integer_lattice_with_isometry(L)]