From e8589999b0a59cbb6c30916d9bd6a5d868de11b0 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Fri, 13 Oct 2023 08:48:40 +0200 Subject: [PATCH 1/6] Update to GAP.jl 0.10 (#2912) It redefines hash(::GapObj) to produce an error, hence is a breaking update. --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 9dc12de54d6a..65ea1e2bbd2d 100644 --- a/Project.toml +++ b/Project.toml @@ -29,8 +29,8 @@ cohomCalg_jll = "5558cf25-a90e-53b0-b813-cadaa3ae7ade" AbstractAlgebra = "0.33.0" AlgebraicSolving = "0.3.6" DocStringExtensions = "0.8, 0.9" -GAP = "0.9.4" -Hecke = "0.22.2" +GAP = "0.10.0" +Hecke = "0.22.3" JSON = "^0.20, ^0.21" Nemo = "0.37.1" Polymake = "0.11.6" From 3835e45d2ccbd2383f141e5032c6ce524aced9ab Mon Sep 17 00:00:00 2001 From: Claus Fieker Date: Fri, 13 Oct 2023 09:29:36 +0200 Subject: [PATCH 2/6] Cf work in progress (#2870) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * replace FreeModule -> FPModule to allow more cases some work... * try to support FunField in solve... rel ext is missing * trensor product, natural_module and minor * trivia and tests * fix examples * first working version of H^3 * minimal changes to get it to work (again, partly) * make Martin happy: monomials_of_degree * revert the bad commits * re-arrange dim shift * lattice in shrink * typo * trivia and tests * fix examples * arrange tensor prod for direct sum module should eventually move to AA I guess? allows: G = dihedral_group(8) z = irreducible_modules(G) (z[1] ⊕ z[2]) ⊗ z[3] * ... and add test for it * add trivial module and progress towards THE interface restriction_of_scalars * trivial typo * test for it * use error tolerant lifting in hom_base * typo in test * add export * fix example... * more typos... * fix test * rename mat -> matrix * add rref for hom_base --- experimental/GModule/Cohomology.jl | 121 +++++++++++++++--- experimental/GModule/GModule.jl | 156 +++++++++++++++++++---- experimental/GModule/GaloisCohomology.jl | 9 +- experimental/GModule/test/runtests.jl | 16 ++- experimental/GaloisGrp/src/Solve.jl | 1 - 5 files changed, 253 insertions(+), 50 deletions(-) diff --git a/experimental/GModule/Cohomology.jl b/experimental/GModule/Cohomology.jl index 10504fa1a639..3ddc7ad79757 100644 --- a/experimental/GModule/Cohomology.jl +++ b/experimental/GModule/Cohomology.jl @@ -343,8 +343,8 @@ function Oscar.quo(C::GModule{<:Any, <:Generic.FreeModule}, mDC::Generic.ModuleH return S, mq end -function Oscar.quo(C::GModule, mDC::Map{GrpAbFinGen, GrpAbFinGen}) - q, mq = Oscar.quo(C.M, image(mDC)[1]) +function Oscar.quo(C::GModule, mDC::Map{GrpAbFinGen, GrpAbFinGen}, add_to_lattice::Bool = true) + q, mq = Oscar.quo(C.M, image(mDC)[1], add_to_lattice) S = GModule(C.G, [GrpAbFinGenMap(pseudo_inv(mq)*x*mq) for x = C.ac]) if isdefined(C, :iac) S.iac = [GrpAbFinGenMap(pseudo_inv(mq)*x*mq) for x = C.iac] @@ -394,7 +394,7 @@ function Oscar.tensor_product(C::GModule{<:Any, GrpAbFinGen}...; task::Symbol = end end -function Oscar.tensor_product(C::GModule{S, M}...; task::Symbol = :map) where S <: Oscar.GAPGroup where M <: AbstractAlgebra.Generic.FreeModule{<:Any} +function Oscar.tensor_product(C::GModule{S, <:AbstractAlgebra.FPModule{<:Any}}...; task::Symbol = :map) where S <: Oscar.GAPGroup @assert all(x->x.G == C[1].G, C) @assert all(x->base_ring(x) == base_ring(C[1]), C) @@ -412,9 +412,10 @@ end import Hecke.⊗ ⊗(C::GModule...) = Oscar.tensor_product(C...; task = :none) -function Oscar.tensor_product(F::Generic.FreeModule{T}...; task = :none) where {T} + +function Oscar.tensor_product(F::AbstractAlgebra.FPModule{T}...; task = :none) where {T} @assert all(x->base_ring(x) == base_ring(F[1]), F) - d = prod(rank(x) for x = F) + d = prod(dim(x) for x = F) G = free_module(base_ring(F[1]), d) if task == :none return G @@ -422,7 +423,7 @@ function Oscar.tensor_product(F::Generic.FreeModule{T}...; task = :none) where { g = vec(collect(Base.Iterators.ProductIterator(Tuple(gens(g) for g = reverse(F))))) - function pure(g::Generic.FreeModuleElem...) + function pure(g...) @assert length(g) == length(F) @assert all(i-> parent(g[i]) == F[i], 1:length(F)) @@ -1005,6 +1006,11 @@ function Base.collect(w::Vector{Int}, C::CollectCtx) return w end +function H_two_maps(C::GModule; force_rws::Bool = false, redo::Bool = false) + H_two(C; force_rws, redo) + return get_attribute(C, :H_two_maps) +end + #= Hulpke-Dietrich: UNIVERSAL COVERS OF FINITE GROUPS https://arxiv.org/pdf/1910.11453.pdf @@ -1486,20 +1492,19 @@ iszero(a) || (@show g, h, k, a ; return false) end """ -Computes H^3 via dimension-shifting: -There is a short exact sequence - 1 -> A -> Hom(Z[G], A) -> B -> 1 -thus - H^3(G, A) = H^2(G, B) -as Hom(Z[G], A) is induced hence has trivial cohomology. -Currently only the group is returned +Compute + 0 -> C -I-> hom(Z[G], C) -q-> B -> 0 +To allow "dimension shifting": H^(n+1)(G, C) - H^n(G, q) +returns (I, q), (hom(Z[G], C), B) """ -function H_three(C::GModule{<:Oscar.GAPGroup, <:Any}) +function dimension_shift(C::GModule) G = C.G if isa(C.M, GrpAbFinGen) zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(GrpAbFinGen, G, ZZ) + Z = Hecke.zero_obj(zg.M) elseif isa(C.M, AbstractAlgebra.FPModule{<:FieldElem}) zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(G, base_ring(C)) + Z = free_module(base_ring(C), 0) else error("unsupported module") end @@ -1512,14 +1517,96 @@ function H_three(C::GModule{<:Oscar.GAPGroup, <:Any}) #the augmentation map on the (canonical) generators is 1 inj = hom(C.M, H.M, [preimage(mH, hom(zg.M, C.M, [c for g = gens(zg.M)])) for c = gens(C.M)]) @assert is_G_hom(C, H, inj) - q, mq = quo(H, image(inj)[2]) + B, q = quo(H, image(inj)[2]) + + return (inj, q), (H, B) + + #would be nice, but looses the G-operation. + #XXX: we don't have homs for GModules + # : sice we also don't have elements + # : do we need elements for homs? + Z1 = hom(Z, C.M, elem_type(C.M)[zero(C.M) for i = gens(Z)]) + Z2 = hom(q.M, Z, [zero(Z) for x = gens(q.M)]) + return cochain_complex([Z1, inj, mq, Z2]) +end + +function fixed_module(H::GModule) + K = H.M + id = hom(K, K, gens(K)) + mK = hom(K, K, gens(K)) + for x = H.ac + k = intersect(K, kernel(x-id)[1]) + fl, mk = is_sub_with_data(k, K) + K = k + mK = mk*mK + end + return gmodule(H.G, [GrpAbFinGenMap(mK*g*pseudo_inv(mK)) for g = H.ac]), mK +end + +function dimension_shift_left(C::GModule) + G = C.G + if isa(C.M, GrpAbFinGen) + zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(GrpAbFinGen, G, ZZ) + Z = Hecke.zero_obj(zg.M) + elseif isa(C.M, AbstractAlgebra.FPModule{<:FieldElem}) + zg, ac, em = Oscar.GModuleFromGap.natural_gmodule(G, base_ring(C)) + Z = free_module(base_ring(C), 0) + else + error("unsupported module") + end + @assert is_consistent(zg) + H, mH = tensor_product(zg, C) + @assert is_consistent(H) + #XXX broken from here onwards + pro = hom(H.M, C.M, [pseudo_inv(mH)(x)[2] for x = gens(H.M)]) + @assert is_G_hom(H, C, pro) + k, mk = kernel(pro) + if isa(C.M, GrpAbFinGen) + K = gmodule(C.G, [GrpAbFinGenMap(mk*g*pseudo_inv(mk)) for g = H.ac]) + else + K = gmodule(C.G, [mk*g*pseudo_inv(mk) for g = H.ac]) + end + + return (mk, pro), (K, H) + + #would be nice, but looses the G-operation. + #XXX: we don't have homs for GModules + # : sice we also don't have elements + # : do we need elements for homs? + Z1 = hom(Z, C.M, elem_type(C.M)[zero(C.M) for i = gens(Z)]) + Z2 = hom(q.M, Z, [zero(Z) for x = gens(q.M)]) + return cochain_complex([Z1, inj, mq, Z2]) +end + + +""" +Computes H^3 via dimension-shifting: +There is a short exact sequence + 1 -> A -> Hom(Z[G], A) -> B -> 1 +thus + H^3(G, A) = H^2(G, B) +as Hom(Z[G], A) is induced hence has trivial cohomology. +Currently only the group is returned +""" +function H_three(C::GModule{<:Oscar.GAPGroup, <:Any}) + (inj, mq), (H, q) = dimension_shift(C) + # return q, mq, inj, H #possibly, to get 3-chains: # 2 chain in q # preimage mq 2 chain in H # differential 3 chain in H # preimage inj 3 chain in C - return H_two(q)[1] + H, mH, _ = H_two(q) + function chain(a) + @assert parent(a) == H + c = mH(a) + d = map_entries(pseudo_inv(mq), c, parent = H) + e = differential(d) + f = map_entries(pseudo_inv(inj), e, parent = C) + end + #preimage under the differential is hard... + return H, chain end function is_right_G_module(C::GModule) @@ -1538,7 +1625,7 @@ end For a gmodule `C` compute the `i`-th cohomology group where `i` can be `0`, `1` or `2`. (or `3` ...) Together with the abstract module, a map is provided that will - produce explicit cochains. +produce explicit cochains. """ function cohomology_group(C::GModule, i::Int; Tate::Bool = false) #should also allow modules... diff --git a/experimental/GModule/GModule.jl b/experimental/GModule/GModule.jl index 43ac48d5d9ff..2ca57f489a79 100644 --- a/experimental/GModule/GModule.jl +++ b/experimental/GModule/GModule.jl @@ -3,6 +3,39 @@ using Oscar using Hecke import Hecke: data +#XXX: have a type for an implicit field - in Hecke? +# add all(?) the other functions to it +function relative_field(m::Map{<:AbstractAlgebra.Field, <:AbstractAlgebra.Field}) + k = domain(m) + K = codomain(m) + kt, t = polynomial_ring(k, cached = false) + f = defining_polynomial(K) + Qt = parent(f) + #the Trager construction, works for extensions of the same field given + #via primitive elements + h = gcd(gen(k) - map_coefficients(k, Qt(m(gen(k))), parent = kt), map_coefficients(k, f, parent = kt)) + coordinates = function(x::FieldElem) + @assert parent(x) == K + c = collect(Hecke.coefficients(map_coefficients(k, Qt(x), parent = kt) % h)) + c = vcat(c, zeros(k, degree(h)-length(c))) + return c + end + rep_mat = function(x::FieldElem) + @assert parent(x) == K + c = map_coefficients(k, Qt(x), parent = kt) % h + m = collect(Hecke.coefficients(c)) + m = vcat(m, zeros(k, degree(h) - length(m))) + r = m + for i=2:degree(h) + c = shift_left(c, 1) % h + m = collect(Hecke.coefficients(c)) + m = vcat(m, zeros(k, degree(h) - length(m))) + r = hcat(r, m) + end + return transpose(matrix(r)) + end + return h, coordinates, rep_mat +end """ restriction_of_scalars(M::GModule, phi::Map) @@ -14,11 +47,39 @@ If `R` has `S`-rank `d` and `M` has rank `n` then the returned module has rank `d*n`. # Examples +```jldoctest +julia> G = dihedral_group(20); + +julia> T = character_table(G); + +julia> C = gmodule(T[8]); + +julia> C = gmodule(CyclotomicField, C); + +julia> h = subfields(base_ring(C), degree = 2)[1][2]; + +julia> restriction_of_scalars(C, h) +G-module for G acting on Vector space of dimension 4 over number field of degree 2 over QQ + +julia> restriction_of_scalars(C, QQ) +G-module for G acting on Vector space of dimension 8 over rational field -(cases that `R` is a finite field or a number field, and `S` is a subfield) +``` """ -function restriction_of_scalars(M::GModule, phi::Map) - error("not yet ...") +function restriction_of_scalars(M::GModule{<:Oscar.GAPGroup, <:AbstractAlgebra.FPModule{<:FieldElem}}, phi::Map) + #works iff relative_field above works. At least for AnticNumberField and + #finite fields + @assert codomain(phi) == base_ring(M) + d = divexact(degree(codomain(phi)), degree(domain(phi))) + F = free_module(domain(phi), dim(M)*d) + _, _, rep = relative_field(phi) + + return GModule(F, group(M), [hom(F, F, hvcat(dim(M), [rep(x) for x = transpose(matrix(y))]...)) for y = M.ac]) +end + +function restriction_of_scalars(C::GModule{<:Any, <:AbstractAlgebra.FPModule{nf_elem}}, ::QQField) + F = free_module(QQ, dim(C)*degree(base_ring(C))) + return GModule(F, group(C), [hom(F, F, hvcat(dim(C), [representation_matrix(x) for x = transpose(matrix(y))]...)) for y = C.ac]) end @@ -201,6 +262,14 @@ function irreducible_modules(G::Oscar.GAPGroup) return IM end +""" + G acting on M trivially, ie. g(m) == m. +""" +function trivial_gmodule(G::Oscar.GAPGroup, M::Union{GrpAbFinGen, AbstractAlgebra.FPModule}) + I = hom(M, M, gens(M)) + return Oscar.gmodule(M, G, typeof(I)[I for x = gens(G)]) +end + function Oscar.gmodule(::Type{AnticNumberField}, M::GModule{<:Oscar.GAPGroup, <:AbstractAlgebra.FPModule{nf_elem}}) k, mk = Hecke.subfield(base_ring(M), vec(collect(vcat(map(matrix, M.ac)...)))) if k != base_ring(M) @@ -272,6 +341,7 @@ function irreducible_modules(::typeof(CyclotomicField), G::Oscar.GAPGroup) end function irreducible_modules(::QQField, G::Oscar.GAPGroup) + #if cyclo is not minimal, this is not irreducible z = irreducible_modules(CyclotomicField, G) return [gmodule(QQ, m) for m in z] end @@ -589,7 +659,7 @@ end """ Compute the factor set or 2-cochain defined by `C` as a Galois -module of the autmorphism group over the character field. +module of the automorphism group over the character field. If `mA` is given, it needs to map the automorphism group over the character field into the the automorphisms of the base ring. """ @@ -737,7 +807,7 @@ function istwo_cocycle(X::Dict, mA, op = *) for g = G for h = G for k = G - #= if (g*h)(x) = h(g(x)), then the cocycle should be + #= if (g*h)(x) = g(h(x)), then the cocycle should be X[(g*h, k)] X[(g, h)] == mA(g)(X[(h, k)]) X[(g, hk)] if (g*h)(x) = h(g(x)) then we should get X[(g, hk)] X[(h, k)] == mA(k)(X[(g, h)]) X[(gh, k)] @@ -926,7 +996,7 @@ function indecomposition(C::GModule{<:Any, <:AbstractAlgebra.FPModule{<:FinField end """ -The group of Z[G]-homomorphisms as a k-module, not a k[G] one. (The G opetation +The group of Z[G]-homomorphisms as a k-module, not a k[G] one. (The G operation is trivial) """ function Oscar.hom(C::T, D::T) where T <: GModule{<:Any, <:AbstractAlgebra.FPModule{<:FieldElem}} @@ -988,7 +1058,8 @@ function hom_base(C::GModule{S, <:AbstractAlgebra.FPModule{T}}, D::GModule{S, <: h = Oscar.iso_oscar_gap(base_ring(C)) hb = GAP.Globals.MTX.BasisModuleHomomorphisms(Gap(C, h), Gap(D, h)) n = length(hb) - b = [matrix([preimage(h, x[i, j]) for i in 1:GAPWrap.NrRows(x), j in 1:GAPWrap.NrCols(x)]) for x in hb] + b = dense_matrix_type(base_ring(C))[matrix([preimage(h, x[i, j]) for i in 1:GAPWrap.NrRows(x), j in 1:GAPWrap.NrCols(x)]) for x in hb] + # @show [matrix(C.ac[i])*b[1] == b[1]*matrix(D.ac[i]) for i=1:length(C.ac)] return b end @@ -1014,6 +1085,36 @@ function hom_base(C::T, D::T) where T <: GModule{<:Any, <:Generic.FreeModule{<:A return map(x->map_entries(base_ring(C), x), h) end +#TODO: in ctx of MeatAxe & Gap: we're mostly having a rref, +# but for a different ordering of entries +function _rref!(V::Vector{<:MatElem{<:FieldElem}}) + @show :in, V + @assert all(x->size(x) == size(V[1]), V) + n = nrows(V[1]) + @assert ncols(V[1]) == n + + o = 1 + for i = CartesianIndices((1:n, 1:n)) + j = findall(x->!iszero(x[i]), V) + isempty(j) && continue + if j[1] != o + V[o], V[j[1]] = V[j[1]], V[o] + j[1] = o + end + if !isone(V[o][i]) + V[o] *= inv(V[o][i]) + end + for k=o+1:length(V) + iszero(V[k][i]) && continue + V[k] -= V[k][i] * V[o] + end + o += 1 + if o>length(V) + return + end + end +end + """ C*T[i] = T[i]*D on return. @@ -1045,28 +1146,22 @@ function hom_base(C::GModule{<:Any, <:AbstractAlgebra.FPModule{nf_elem}}, D::GMo end t = [] for i=1:length(z1) - push!(t, hom_base(z1[i], z2[i])) + mp = hom_base(z1[i], z2[i]) + if isempty(mp) + return dense_matrix_type(base_ring(C))[] + end + _rref!(mp) + push!(t, mp) end #should actually compute an rref of the hom base to make sure if any(x->length(x) != length(t[1]), t) + @show :BP #bad prime... continue end if length(t[1]) == 0 return [] end - pv = [findfirst(!iszero, x) for x = t[1]] - if any(!isequal(pv[1]), pv) - continue - end - for i=1:length(pv) - for j=1:length(t) - if !isone(t[j][i][pv[i]]) - t[j][i] *= inv(t[j][i][pv[i]]) - end - end - end - @assert all(i->all(x->x[pv[i]] == 1, t[i]), 1:length(pv)) tt = [Hecke.modular_lift([t[i][j] for i=1:length(z1)], me) for j=1:length(t[1])] @assert base_ring(tt[1]) == k @@ -1079,7 +1174,7 @@ function hom_base(C::GModule{<:Any, <:AbstractAlgebra.FPModule{nf_elem}}, D::GMo pp *= p S = [] for t = T - fl, s = induce_rational_reconstruction(t, pp)# , ErrorTolerant = true) + fl, s = induce_rational_reconstruction(t, pp, ErrorTolerant = true) fl || break push!(S, s) end @@ -1121,10 +1216,10 @@ function hom_base(C::_T, D::_T) where _T <: GModule{<:Any, <:AbstractAlgebra.FPM z2 = gmodule(GF(p), D) end - t = hom_base(z1, z2) #TODO: here and elsewhere: get a rref of the hom - #base to combine!!!! - #(replaced by using fault tolerant lifting) - #should use vector reconstruction - once we have it + t = hom_base(z1, z2) + + isempty(t) && return QQMatrix[] + _rref!(t) tt = [lift(s) for s=t] @assert base_ring(tt[1]) == ZZ if isone(pp) @@ -1277,8 +1372,6 @@ function Oscar.gmodule(::Type{GrpAbFinGen}, C::GModule{T, <:AbstractAlgebra.FPMo return Oscar.gmodule(A, Group(C), [hom(A, A, matrix(x)) for x = C.ac]) end - - function Oscar.abelian_group(M::AbstractAlgebra.FPModule{fqPolyRepFieldElem}) k = base_ring(M) A = abelian_group([characteristic(k) for i = 1:dim(M)*degree(k)]) @@ -1312,6 +1405,10 @@ function Oscar.gmodule(chi::Oscar.GAPGroupClassFunction) return gmodule(group(chi), [hom(F, F, x) for x = z]) end +function Oscar.gmodule(T::Union{Type{CyclotomicField}, Type{AnticNumberField}}, chi::Oscar.GAPGroupClassFunction) + return gmodule(T, gmodule(chi)) +end + function Oscar.gmodule(::Type{GrpAbFinGen}, C::GModule{T, AbstractAlgebra.FPModule{fqPolyRepFieldElem}}) where {T <: Oscar.GAPGroup} k = base_ring(C) A, mA = abelian_group(C.M) @@ -1334,6 +1431,7 @@ end function Base.vec(M::MatElem) r = elem_type(base_ring(M))[] + sizehint!(r, nrows(M) * ncols(M)) for j=1:ncols(M) for i=1:nrows(M) push!(r, M[i, j]) @@ -1413,6 +1511,8 @@ export indecomposition export irreducible_modules export is_decomposable export is_G_hom +export restriction_of_scalars +export trivial_gmodule ## Fill in some stubs for Hecke @@ -1464,4 +1564,6 @@ export indecomposition export irreducible_modules export is_decomposable export is_G_hom +export restriction_of_scalars +export trivial_gmodule diff --git a/experimental/GModule/GaloisCohomology.jl b/experimental/GModule/GaloisCohomology.jl index 0493e61518e6..015b07ad15e1 100644 --- a/experimental/GModule/GaloisCohomology.jl +++ b/experimental/GModule/GaloisCohomology.jl @@ -85,7 +85,7 @@ end The natural `ZZ[H]` module where `H`, a subgroup of the automorphism group acts on the ray class group. """ -function Oscar.gmodule(H::PermGroup, mR::MapRayClassGrp, mG = automorphism_group(PermGroup, k)[2]) +function Oscar.gmodule(H::PermGroup, mR::MapRayClassGrp, mG = automorphism_group(PermGroup, nf(order(codomain((mR)))))[2]) k = nf(order(codomain(mR))) G = domain(mG) @@ -1846,6 +1846,7 @@ function Oscar.orbit(C::GModule, o) return orbit(C.G, (x,y) -> action(C, y, x), o) end +#TODO: reduce torsion: the part coprime to |G| can go... """ shrink(C::GModule{PermGroup, GrpAbFinGen}, attempts::Int = 10) @@ -1854,7 +1855,7 @@ Returns a cohomologically equivalent module with fewer generators and the quotient map. """ function shrink(C::GModule{PermGroup, GrpAbFinGen}, attempts::Int = 10) - local mq + mq = hom(C.M, C.M, gens(C.M)) q = C first = true while true @@ -1862,9 +1863,9 @@ function shrink(C::GModule{PermGroup, GrpAbFinGen}, attempts::Int = 10) for i=1:attempts o = Oscar.orbit(q, rand(gens(q.M))) if length(o) == order(group(q)) - s, ms = sub(q.M, collect(o)) + s, ms = sub(q.M, collect(o), false) if rank(s) == length(o) - q, _mq = quo(q, ms) + q, _mq = quo(q, ms, false) if first mq = _mq first = false diff --git a/experimental/GModule/test/runtests.jl b/experimental/GModule/test/runtests.jl index 8ccb44873a66..1b28e22fa494 100644 --- a/experimental/GModule/test/runtests.jl +++ b/experimental/GModule/test/runtests.jl @@ -146,6 +146,14 @@ end C = gmodule(GF(5), C) i = indecomposition(C) @test length(i) == 8 + + G = dihedral_group(8) + z = irreducible_modules(G) + @test dim((z[1] ⊕ z[2]) ⊗ z[3]) == 2 + + k, a = quadratic_field(3) + r, mr = ray_class_group(7*5*maximal_order(k), n_quo = 2) + z = gmodule(automorphism_group(PermGroup, k)[1], mr) end @testset "H^3" begin @@ -158,7 +166,13 @@ end F = free_abelian_group(7); M1, M2 = matrix(ZZ, 7, 7, mats[1]), matrix(ZZ, 7, 7, mats[2]); C = gmodule(G, [hom(F, F, M1), hom(F, F, M2)]); - q = cohomology_group(C, 3) + q = cohomology_group(C, 3)[1] @test order(q) == 8 @test is_cyclic(q) + C = gmodule(GF(5), C) + i = indecomposition(C) + @test length(i) == 5 + + C, _ = Oscar.GModuleFromGap.ghom(C, C) + @test dim(C) == 49 end diff --git a/experimental/GaloisGrp/src/Solve.jl b/experimental/GaloisGrp/src/Solve.jl index 5b259abe05d9..61907bd31472 100644 --- a/experimental/GaloisGrp/src/Solve.jl +++ b/experimental/GaloisGrp/src/Solve.jl @@ -246,7 +246,6 @@ function Oscar.extension_field(f::AbstractAlgebra.Generic.Poly{<:NumFieldElem}; return number_field(f; cached, check) end - function refined_derived_series(G::PermGroup) s = GAP.Globals.PcSeries(GAP.Globals.Pcgs(G.X)) return Oscar._as_subgroups(G,s) From 643129e42af364b9c48b863f05393834b33c17fd Mon Sep 17 00:00:00 2001 From: Max Horn Date: Thu, 5 Oct 2023 22:14:08 +0200 Subject: [PATCH 3/6] Fix sheaf_cohomology_bgg docstring, sheafCohTable printing --- src/Modules/ModulesGraded.jl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Modules/ModulesGraded.jl b/src/Modules/ModulesGraded.jl index 4e97a1e9bf43..9d6f23fc3689 100644 --- a/src/Modules/ModulesGraded.jl +++ b/src/Modules/ModulesGraded.jl @@ -1509,20 +1509,20 @@ function Base.show(io::IO, table::sheafCohTable) # header header = [lpad(v, val_space_length, " ") for v in table.twist_range] - pushfirst!(header, rpad(" ", row_label_length, " ")) + pushfirst!(header, rpad(".", row_label_length, " ")) - println(io, prod(header)) + println(io, header...) size_row = sum(length, first(print_rows)) println(io, repeat("-", size_row)) for rw in print_rows - println(io, prod(rw)) + println(io, rw...) end println(io, repeat("-", size_row)) - println(io, prod(chi_print)) + print(io, chi_print...) end @doc raw""" - function sheaf_cohomology_bgg(M::ModuleFP{T}, l::Int, h::Int) where {T <: MPolyDecRingElem} + sheaf_cohomology_bgg(M::ModuleFP{T}, l::Int, h::Int) where {T <: MPolyDecRingElem} Compute the cohomology of twists of of the coherent sheaf on projective space associated to `M`. The range of twists is between `l` and `h`. @@ -1533,7 +1533,7 @@ The values of the returned table can be accessed by indexing it with a cohomological index and a value between `l` and `h` as shown in the example below. - +```jldoctest julia> R, x = polynomial_ring(QQ, "x" => 1:4); julia> S, _= grade(R); @@ -1549,7 +1549,7 @@ S^4 <---- S^6 <---- S^4 <---- S^1 <---- 0 julia> M = cokernel(map(FI, 2)); julia> tbl = sheaf_cohomology_bgg(M, -6, 2) - -6 -5 -4 -3 -2 -1 0 1 2 +. -6 -5 -4 -3 -2 -1 0 1 2 ----------------------------------------- 0: 70 36 15 4 - - - - * 1: * - - - - - - - - @@ -1564,14 +1564,14 @@ julia> tbl[0, -6] julia> tbl[2, 0] 1 -julia> R, x = polynomial_ring(QQ, "x" => (1:5, )); +julia> R, x = polynomial_ring(QQ, "x" => 1:5); julia> R, x = grade(R); julia> F = graded_free_module(R, 1); julia> sheaf_cohomology_bgg(F, -7, 2) - -7 -6 -5 -4 -3 -2 -1 0 1 2 +. -7 -6 -5 -4 -3 -2 -1 0 1 2 --------------------------------------------- 0: 15 5 1 - - - * * * * 1: * - - - - - - * * * @@ -1580,6 +1580,7 @@ julia> sheaf_cohomology_bgg(F, -7, 2) 4: * * * * - - - 1 5 15 --------------------------------------------- chi: * * * * - - * * * * +``` """ function sheaf_cohomology_bgg(M::ModuleFP{T}, l::Int, From 17c57310c42887b60fee206d1fcf9af52bd48ebc Mon Sep 17 00:00:00 2001 From: ederc Date: Thu, 12 Oct 2023 10:51:33 +0200 Subject: [PATCH 4/6] introduces "twist: " as header for first row --- src/Modules/ModulesGraded.jl | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Modules/ModulesGraded.jl b/src/Modules/ModulesGraded.jl index 9d6f23fc3689..bd87f1f77fb1 100644 --- a/src/Modules/ModulesGraded.jl +++ b/src/Modules/ModulesGraded.jl @@ -1501,7 +1501,7 @@ function Base.show(io::IO, table::sheafCohTable) chi_print = [_shcoh_string_rep(v, val_space_length) for v in chi] # row labels - row_label_length = max(_ndigits(nrows - 1), 3) + 2 + row_label_length = max(_ndigits(nrows - 1), 3) + 3 for i in 1:nrows pushfirst!(print_rows[i], rpad("$(i-1): ", row_label_length, " ")) end @@ -1509,7 +1509,7 @@ function Base.show(io::IO, table::sheafCohTable) # header header = [lpad(v, val_space_length, " ") for v in table.twist_range] - pushfirst!(header, rpad(".", row_label_length, " ")) + pushfirst!(header, rpad("twist:", row_label_length, " ")) println(io, header...) size_row = sum(length, first(print_rows)) @@ -1549,14 +1549,14 @@ S^4 <---- S^6 <---- S^4 <---- S^1 <---- 0 julia> M = cokernel(map(FI, 2)); julia> tbl = sheaf_cohomology_bgg(M, -6, 2) -. -6 -5 -4 -3 -2 -1 0 1 2 ------------------------------------------ -0: 70 36 15 4 - - - - * -1: * - - - - - - - - -2: * * - - - - 1 - - -3: * * * - - - - - 6 ------------------------------------------ -chi: * * * 4 - - 1 - * +twist: -6 -5 -4 -3 -2 -1 0 1 2 +------------------------------------------ +0: 70 36 15 4 - - - - * +1: * - - - - - - - - +2: * * - - - - 1 - - +3: * * * - - - - - 6 +------------------------------------------ +chi: * * * 4 - - 1 - * julia> tbl[0, -6] 70 @@ -1571,15 +1571,15 @@ julia> R, x = grade(R); julia> F = graded_free_module(R, 1); julia> sheaf_cohomology_bgg(F, -7, 2) -. -7 -6 -5 -4 -3 -2 -1 0 1 2 ---------------------------------------------- -0: 15 5 1 - - - * * * * -1: * - - - - - - * * * -2: * * - - - - - - * * -3: * * * - - - - - - * -4: * * * * - - - 1 5 15 ---------------------------------------------- -chi: * * * * - - * * * * +twist: -7 -6 -5 -4 -3 -2 -1 0 1 2 +---------------------------------------------- +0: 15 5 1 - - - * * * * +1: * - - - - - - * * * +2: * * - - - - - - * * +3: * * * - - - - - - * +4: * * * * - - - 1 5 15 +---------------------------------------------- +chi: * * * * - - * * * * ``` """ function sheaf_cohomology_bgg(M::ModuleFP{T}, From 74c07cebdaaaf8d8f6c2b0b85fce5b4d4968305b Mon Sep 17 00:00:00 2001 From: Claus Fieker Date: Fri, 13 Oct 2023 14:09:38 +0200 Subject: [PATCH 5/6] fix iso to GrpGen (#2917) in particular fixes: Qx, x = QQ["x"]; k5,a5=number_field( x^5 - 2 ,"a") K5,i5=normal_closure(k5) G5,mG5=automorphism_group(PermGroup, K5) map(mG5, G5) The map used to fail. The test does it without the number theory --- src/Groups/homomorphisms.jl | 2 +- test/Groups/homomorphisms.jl | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Groups/homomorphisms.jl b/src/Groups/homomorphisms.jl index 879c17e1d9c6..9b6d64fdbb83 100644 --- a/src/Groups/homomorphisms.jl +++ b/src/Groups/homomorphisms.jl @@ -775,7 +775,7 @@ function isomorphism(::Type{T}, A::GrpGen) where T <: GAPGroup newgens = elem_type(S)[] for g in gensA j = g.i - p = S(A.mult_table[j, :]) + p = S(A.mult_table[:, j]) push!(newgens, p) end diff --git a/test/Groups/homomorphisms.jl b/test/Groups/homomorphisms.jl index 6ce3e4e7325a..b920e0b0af71 100644 --- a/test/Groups/homomorphisms.jl +++ b/test/Groups/homomorphisms.jl @@ -270,16 +270,19 @@ end end @testset "GrpGen to GAPGroups" begin - G = Hecke.small_group(64, 14, DB = Hecke.DefaultSmallGroupDB()) - for T in [FPGroup, PcGroup, PermGroup] - iso = @inferred isomorphism(T, G) - for x in gens(G), y in gens(G) - z = x * y - @test iso(x) * iso(y) == iso(z) - @test all(a -> preimage(iso, iso(a)) == a, [x, y, z]) + for G in [Hecke.small_group(64, 14, DB = Hecke.DefaultSmallGroupDB()), + Hecke.small_group(20, 3, DB = Hecke.DefaultSmallGroupDB())] + for T in [FPGroup, PcGroup, PermGroup] + iso = @inferred isomorphism(T, G) + for x in gens(G), y in gens(G) + z = x * y + @test iso(x) * iso(y) == iso(z) + @test all(a -> preimage(iso, iso(a)) == a, [x, y, z]) + end end - end + end + G = Hecke.small_group(64, 14, DB = Hecke.DefaultSmallGroupDB()) H = small_group(64, 14) @test isisomorphic(G, H) f = isomorphism(G, H) From 588abce5a7b7ce114c8dcc2f3aeaeda52d6fab6a Mon Sep 17 00:00:00 2001 From: Stevell Muller <78619134+StevellM@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:32:07 +0200 Subject: [PATCH 6/6] `QuadFormWithIsom`: further cleanups + preparation for incoming new features (#2847) * further cleanups + fixes + preparation for new incoming features --------- Co-authored-by: Max Horn --- experimental/QuadFormAndIsom/README.md | 3 +- .../QuadFormAndIsom/docs/src/enumeration.md | 25 +- .../QuadFormAndIsom/docs/src/introduction.md | 7 +- .../QuadFormAndIsom/docs/src/latwithisom.md | 47 +- .../QuadFormAndIsom/docs/src/primembed.md | 60 +- .../QuadFormAndIsom/docs/src/spacewithisom.md | 18 +- .../QuadFormAndIsom/src/embeddings.jl | 801 ++++++++++++------ .../QuadFormAndIsom/src/enumeration.jl | 135 +-- experimental/QuadFormAndIsom/src/exports.jl | 2 + .../src/lattices_with_isometry.jl | 633 ++++++++++---- .../src/spaces_with_isometry.jl | 54 +- experimental/QuadFormAndIsom/src/types.jl | 22 +- experimental/QuadFormAndIsom/test/runtests.jl | 34 +- 13 files changed, 1242 insertions(+), 599 deletions(-) diff --git a/experimental/QuadFormAndIsom/README.md b/experimental/QuadFormAndIsom/README.md index cf4d5f9d3676..9dee8c943904 100644 --- a/experimental/QuadFormAndIsom/README.md +++ b/experimental/QuadFormAndIsom/README.md @@ -15,6 +15,7 @@ forms. 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$ @@ -40,7 +41,7 @@ Among the possible improvements and extensions: * Implement extra methods for lattices with isometries of infinite order; * Extend existing methods for equivariant primitive embeddings/extensions. -## Currently application of this project +## Current applications 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 diff --git a/experimental/QuadFormAndIsom/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md index 4e773c7deef8..06fa5245e73a 100644 --- a/experimental/QuadFormAndIsom/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -12,19 +12,19 @@ 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, in certain cases, `C` can be obtained -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`. +Roughly speaking, for a prime number $p$, a *$p$-admissible triple* $(A, B, C)$ +is a triple of integer lattices such that, in some cases, $C$ can be obtained +as a primitive extension $A \oplus 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 Lemma 4.15. in [BH23](@cite)). +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 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 -$C \in CC$ such that $(A, B, C)$ is $p$-admissible. +We say that a triple $(G_A, G_B, G_C)$ of genus symbols for integer lattices is +*$p$-admissible* if there are some lattices $A \in G_A$, $B \in G_B$ and +$C \in G_C$ such that $(A, B, C)$ is $p$-admissible. 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 @@ -70,7 +70,8 @@ iteratively the order up to $p^dq^e$. ### Underlying machinery Here is a list of the algorithmic machinery provided by [BH23](@cite) used -previously to enumerate lattices with isometry: +previously to enumerate lattices with isometry. The following correspond +respectively to Algorithms 3, 4, 5, 6 and 7 of the aforementioned paper: ```@docs representatives_of_hermitian_type(::ZZLatWithIsom, ::Int) @@ -84,6 +85,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 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 +method [`admissible_equivariant_primitive_extensions`](@ref) (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/introduction.md b/experimental/QuadFormAndIsom/docs/src/introduction.md index aab132295360..0ae54b585527 100644 --- a/experimental/QuadFormAndIsom/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -15,17 +15,18 @@ forms. We introduce two new structures * [`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$ 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]. +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 -function `primitive_embeddings` offers, under certain conditions, +function [`primitive_embeddings`](@ref) 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. @@ -40,7 +41,7 @@ Among the possible improvements and extensions: * Implement extra methods for lattices with isometries of infinite order; * Extend existing methods for equivariant primitive embeddings/extensions. -## Currently application of this project +## Current applications 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 diff --git a/experimental/QuadFormAndIsom/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md index 6b85cef397d0..3fe938892aa2 100644 --- a/experimental/QuadFormAndIsom/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -2,11 +2,11 @@ CurrentModule = Oscar ``` -# Lattice with isometry +# Lattices 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. +about [Integer Lattices](@ref) of the documentation for new users. In Oscar, such a pair is encoded in the type called `ZZLatWithIsom`: @@ -14,7 +14,7 @@ In Oscar, such a pair is encoded in the type called `ZZLatWithIsom`: ZZLatWithIsom ``` -and it is seen as a quadruple $(Vf, L, f, n)$ where $Vf = (V, f_a)$ consists of +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$. The integer $n$ is the order of $f$, which is a divisor of the order of the isometry $f_a\in O(V)$. @@ -34,7 +34,7 @@ Note that for some computations, it is more convenient to work either with the isometry of the lattice itself, or with the fixed isometry of the ambient quadratic space inducing it on the lattice. -## Constructor +## Constructors 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 @@ -81,9 +81,14 @@ genus(::ZZLatWithIsom) gram_matrix(::ZZLatWithIsom) is_definite(::ZZLatWithIsom) is_even(::ZZLatWithIsom) +is_elementary(::ZZLatWithIsom, ::IntegerUnion) +is_elementary_with_prime(::ZZLatWithIsom) is_integral(::ZZLatWithIsom) is_positive_definite(::ZZLatWithIsom) +is_primary(::ZZLatWithIsom, ::IntegerUnion) +is_primary_with_prime(::ZZLatWithIsom) is_negative_definite(::ZZLatWithIsom) +is_unimodular(::ZZLatWithIsom) minimum(::ZZLatWithIsom) minimal_polynomial(::ZZLatWithIsom) norm(::ZZLatWithIsom) @@ -93,8 +98,8 @@ scale(::ZZLatWithIsom) signature_tuple(::ZZLatWithIsom) ``` -Similarly, some basic operations on $\mathbb Z$-lattices are available for -lattices with isometry. +Similarly, some basic operations on $\mathbb Z$-lattices and matrices are +available for integer lattices with isometry. ```@docs Base.:^(::ZZLatWithIsom, ::Int) @@ -103,6 +108,7 @@ direct_product(::Vector{ZZLatWithIsom}) direct_sum(::Vector{ZZLatWithIsom}) dual(::ZZLatWithIsom) lll(::ZZLatWithIsom) +orthogonal_submodule(::ZZLatWithIsom, ::QQMatrix) rescale(::ZZLatWithIsom, ::RationalUnion) ``` @@ -131,7 +137,7 @@ 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 +These names follow from the fact that, by the trace equivalence, one can 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]$. @@ -140,7 +146,7 @@ is_of_hermitian_type(::ZZLatWithIsom) is_hermitian(::Dict) ``` -## Hermitian structure and trace equivalence +## Hermitian structures 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 irreducible, one can associate a @@ -152,7 +158,7 @@ to perform the trace equivalence for lattices with isometry of hermitian type. hermitian_structure(::ZZLatWithIsom) ``` -## Discriminant group +## Discriminant groups 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)$ @@ -168,19 +174,26 @@ 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 [BH23](@cite). This has been implemented -in this project and it can be indirectly used through the general following method: +finite case), $G_{L,f}$ can be easily computed. For the remaining cases, we use +the hermitian version of *Miranda-Morrison theory* as presented in +[BH23](@cite). The general computation of $G_{L, f}$ has been implemented in this +project and it can be indirectly used through the general following method: ```@docs image_centralizer_in_Oq(::ZZLatWithIsom) ``` -For an implementation of the regular Miranda-Morisson theory, we refer to the +For an implementation of the regular Miranda-Morrison theory, we refer to the function `image_in_Oq` which actually computes the image of $\pi$ in both the definite and the indefinite case. +More generally, for a finitely generated subgroup $G$ of $O(L)$, we have +implemented a function which computes the representation of $G$ on $D_L$: + +```@docs +discriminant_representation(::ZZLat, ::MatrixGroup) +``` + 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. @@ -204,6 +217,7 @@ the so-called *invariant* and *coinvariant* lattices of $(L, f)$: ```@docs coinvariant_lattice(::ZZLatWithIsom) invariant_lattice(::ZZLatWithIsom) +invariant_coinvariant_pair(::ZZLatWithIsom) ``` Similarly, we provide the possibility to compute invariant and coinvariant @@ -221,7 +235,7 @@ invariant_coinvariant_pair(::ZZLat, ::MatrixGroup) We conclude this introduction about standard functionalities for lattices with 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 +intrinsically connected to the local archimedean invariants of the hermitian structure associated to $(L, f)$ via the trace equivalence. ```@docs @@ -232,7 +246,6 @@ signatures(::ZZLatWithIsom) 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 +isometry of type [`QuadSpaceWithIsom`](@ref) are equal, and if the underlying lattices $L$ and $L'$ are equal as $\mathbb Z$-modules in the common ambient quadratic space. - diff --git a/experimental/QuadFormAndIsom/docs/src/primembed.md b/experimental/QuadFormAndIsom/docs/src/primembed.md index 1a4403dd8d8c..fc8e3006080f 100644 --- a/experimental/QuadFormAndIsom/docs/src/primembed.md +++ b/experimental/QuadFormAndIsom/docs/src/primembed.md @@ -3,11 +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](@cite). +methods about primitive embeddings. Most of the content is taken from +[Nik79](@cite). -# Primitive embeddings in even lattices +# Nikulin's theory on primitive embeddings -## Nikulin's theory +## Primitive embeddings 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 @@ -22,7 +23,7 @@ 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 (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)). +into an even lattice of a given genus (see Proposition 1.15.1 in [Nik79](@cite)). In the latter proposition, it is explained how to classify such embeddings as isomorphic embeddings or as isomorphic sublattices. @@ -51,38 +52,45 @@ primitive_embeddings(::TorQuadModule, ::Tuple{Int, Int}, ::ZZLat) ``` -In order to compute such primitive embeddings of a lattice `M` into a lattice -`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` -isometric to `M`). +In order to compute such primitive embeddings of a lattice $M$ into a lattice +$L$, we follow the proof of Proposition 1.15.1 of [Nik79](@cite). + +Note: for the implementation of the algorithm, we construct an even lattice +$T := M(-1)\oplus U$ where $U$ is a hyperbolic plane - $T$ is unique in its +genus and $O(T)\to O(D_T)$ is surjective. We then classify all primitive +extensions of $M\oplus T$ modulo $O(D_T)$ (and modulo $O(M)$ for a +classification of primitive sublattices). To classify such primitive +extensions, we use Proposition 1.5.1 of [Nik79](@cite): + +```@docs +primitive_extensions(::ZZLat, ::ZZLat) +``` 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` 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 -overlattice is determined by the graph of such glue map. +integral integer lattices $M$ and $N$ is an overlattice $L$ of $M\oplus N$ such +that both $M$ and $N$ embed primitively in $L$ (via the natural embeddings +$M,N \to M\oplus N\subseteq L$). Such primitive extensions are obtained, and +classified, by classifying *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 gluing. ## Admissible equivariant primitive extensions -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 +The following function is a major 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 -extensions $(A, a)\perp (B, b)\to (D, d)$ such that the type of $(D, d^p)$ is +extensions $(A, a)\oplus (B, b)\to (D, d)$ such that the type of $(D, d^q)$ is equal to the type of $(C, c)$ (see [`type(::ZZLatWithIsom)`](@ref)). ```@docs -admissible_equivariant_primitive_extensions(::ZZLatWithIsom, ::ZZLatWithIsom, ::ZZLatWithIsom, ::Integer, ::Integer) +admissible_equivariant_primitive_extensions(::ZZLatWithIsom, ::ZZLatWithIsom, ::ZZLatWithIsom, ::IntegerUnion, ::IntegerUnion) ``` 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 +isometries $(M, f_M)$ and $(N, f_N)$ is a primitive extension of $M$ and $N$ +obtained by gluing two subgroups which are respectively $\overline{f_M}$ and +$\overline{f_N}$ stable along a glue map which commutes with these two actions. +If such a gluing exists, then the overlattice $L$ of $M\oplus 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 index fc47bba45f3f..427137bc0352 100644 --- a/experimental/QuadFormAndIsom/docs/src/spacewithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/spacewithisom.md @@ -2,15 +2,15 @@ CurrentModule = Oscar ``` -# Quadratic space with isometry +# Quadratic spaces 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 +We refer to the section about [Spaces](@ref) of the documentation for new users. Note that currently, we support only rational quadratic forms, i.e. -quadratic spaces defined over the rational. +quadratic spaces defined over $\mathbb{Q}$. In Oscar, such a pair is encoded by the type called `QuadSpaceWithIsom`: @@ -18,7 +18,7 @@ In Oscar, such a pair is encoded by the type called `QuadSpaceWithIsom`: QuadSpaceWithIsom ``` -and it is seen as a triple $(V, f, n)$ where $n$ is the order of $f$. We +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`. @@ -77,8 +77,8 @@ rank(::QuadSpaceWithIsom) signature_tuple(::QuadSpaceWithIsom) ``` -Similarly, some basic operations on quadratic spaces are available for quadratic -spaces with isometry. +Similarly, some basic operations on quadratic spaces and matrices are available +for quadratic spaces with isometry. ```@docs Base.:^(::QuadSpaceWithIsom, ::Int) @@ -91,6 +91,6 @@ rescale(::QuadSpaceWithIsom, ::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'$. +spaces with isometries are *equal* 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/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index c60691cb1091..b6f6fbaa8dc8 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -1,13 +1,14 @@ -################################################################################ +############################################################################### # # Orthogonal direct sums and embeddings of orthogonal groups # -################################################################################ +############################################################################### -# this function is used whenever A and B are in direct orthogonal sum in a +# This function is called whenever A and B are in orthogonal direct 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). +# direct sum in D, we can embed O(A) and O(B) in O(D) by setting the identity on +# the complement. # # 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) @@ -212,13 +213,16 @@ function _overlattice(HAinD::TorQuadModuleMor, return _overlattice(gamma, HAinD, HBinD, fA, fB; same_ambient) end -############################################################################## +############################################################################### # # Orbits and stabilizers of discriminant subgroups # -############################################################################## +############################################################################### -function _on_subgroup_automorphic(T::TorQuadModule, g::AutomorphismGroupElem) +# T is a submodule of the domain q of g, and the function return the submodule +# g(T) of q (which is isomorphic to T as torsion quadratic module by definition +# of g). +function _on_subgroups(T::TorQuadModule, g::AutomorphismGroupElem) q = domain(parent(g)) gene = elem_type(q)[g(q(lift(t))) for t in gens(T)] return sub(q, gene)[1] @@ -230,10 +234,17 @@ function stabilizer(O::AutomorphismGroup{TorQuadModule}, i::TorQuadModuleMor) @req domain(O) === codomain(i) "Incompatible arguments" q = domain(O) N, _ = sub(q, i.(gens(domain(i)))) - stab, _ = stabilizer(O, N, _on_subgroup_automorphic) + stab, _ = stabilizer(O, N, _on_subgroups) return sub(O, elem_type(O)[O(h) for h in gens(stab)]) end +# Given an embedding of an `(O, f)`-stable finite quadratic module `V` of `q`, +# compute representatives of `O`-orbits of `f`-stable submodules of `V` of order +# `ord`. If `compute_stab = true`, then the stabilizers in `O` is also computed. +# Otherwise, we set as "fake stabilizers" the full group `O`. +# +# Note that any torsion quadratic module `H` in output is given by an embedding +# of `H` in `q`. function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor, O::AutomorphismGroup{TorQuadModule}, ord::IntegerUnion = -1, @@ -257,12 +268,12 @@ function _subgroups_orbit_representatives_and_stabilizers(Vinq::TorQuadModuleMor end subs = TorQuadModule[s[1] for s in subs] - m = gset(O, _on_subgroup_automorphic, subs) + m = gset(O, _on_subgroups, subs) orbs = orbits(m) for orb in orbs rep = representative(orb) if compute_stab - stab, _ = stabilizer(O, rep, _on_subgroup_automorphic) + stab, _ = stabilizer(O, rep, _on_subgroups) else stab = O end @@ -319,14 +330,20 @@ function _orbit_representatives(G::MatrixGroup{E}, k::Int, O::AutomorphismGroup{ return [(orbs3[i], O) for i in 1:length(orbs3)] end -# Given an abelian group injection V \to q where the group structure on V is -# 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 -# the action by automorphisms of q in G. +# Given an embedding of an `(O, f)`-stable finite quadratic module `V` of `q`, +# where the abelian group structure on `V` is `p`-elementary, compute +# representatives of `G`-orbit of `f`-stable subgroups of `V` of order `ord`, +# which contains `p^l*q_p` where `q_p` is the `p`-primary part of `q`. +# +# Note that `G` must lie in the centralizer of `f` in `O(q)` and `G` is seen +# as a set of outer automorphisms (so two subgroups are in the +# same orbit if they are `G`-isomorphic). # -# 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). +# If `compute_stab = true`, then the stabilizers in `G` is also computed. +# Otherwise, we set as "fake stabilizers" the full group `G`. +# +# Note that any torsion quadratic module `H` in output is given by an embedding +# of `H` in `q`. function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMor, G::AutomorphismGroup{TorQuadModule}, ord::IntegerUnion, @@ -446,11 +463,20 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end -function _classes_automorphic_subgroups(q::TorQuadModule, - O::AutomorphismGroup{TorQuadModule}, - H::TorQuadModule; - f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O)), - compute_stab::Bool = true) +# Compute `O`-orbits of `f`-stable submodules of `q` which are isometric, as +# torsion quadratic modules, to `H`. If `compute_stab = true`, it also computes +# the stabilizers in `O` of such subgroups. Otherwise, it returns as "fake +# stabilziers" the full group `O`. +# +# The outputs are given by embeddings of such submodules in `q`. +# +# The code splits the computations into primary part since they are orthogonal +# on to the others. +function _classes_isomorphic_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 @@ -468,7 +494,7 @@ function _classes_automorphic_subgroups(q::TorQuadModule, 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 + # stabilizers of isomorphic 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 @@ -517,7 +543,7 @@ function _classes_automorphic_subgroups(q::TorQuadModule, # 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 # degenerate case) - for lis in Hecke.cartesian_product_iterator(list_can; inplace = false) + for lis in Hecke.cartesian_product_iterator(list_can) 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)...) @@ -547,86 +573,348 @@ function _classes_automorphic_subgroups(q::TorQuadModule, return res end -################################################################################# +# Compute `O`-orbits of `f`-stable submodules of `q` of order `ordH`. +# If `compute_stab = true`, it also computes the stabilizers in `O` +# of such subgroups. Otherwise, it returns as "fake stabilziers" the +# full group `O`. # -# Primitive embeddings for even lattices +# The outputs are given by embeddings of such submodules in `q`. # -################################################################################# +# The code splits the computations into primary part since they are orthogonal +# on to the others. +function _classes_isomorphic_subgroups(q::TorQuadModule, + O::AutomorphismGroup{TorQuadModule}, + ordH::IntegerUnion, + f::Union{TorQuadModuleMor, AutomorphismGroupElem{TorQuadModule}} = id_hom(domain(O)), + compute_stab::Bool = true) + res = Tuple{TorQuadModuleMor, AutomorphismGroup{TorQuadModule}}[] -# 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 action -# 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, :emb, :sublat] - results = Tuple{ZZLat, ZZLat, ZZLat}[] + !is_divisible_by(order(q), ordH) && return res - qN = discriminant_group(N) - GN, _ = image_in_Oq(N) + # Trivial case: we look for subgroups in a given primary part of q + ok, e, p = is_prime_power_with_data(ordH) + if ok + if e == 1 + _, Vinq = _get_V(id_hom(q), minimal_polynomial(identity_matrix(QQ, 1)), p) + sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, ordH, f; compute_stab) + else + _, Vinq = primary_part(q, p) + sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, ordH, f; compute_stab) + end + return sors + end - qM = discriminant_group(M) - if classification == :emb - GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(id_hom(qM))]; check = false) + # We inspect each primary part of q and look for orbit representatives and + # stabilizers of isomorphic subgroups of the good order wrt to `ordH` + # + # 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 - GM, _ = image_in_Oq(M) + blocks = TorQuadModuleMor[primary_part(q, p)[2] for p in pds] + D, inj, proj = biproduct(domain.(blocks)) end + phi = hom(D, q, TorQuadModuleElem[sum([blocks[i](proj[i](a)) for i in 1:length(pds)]) for a in gens(D)]) + @hassert :ZZLatWithIsom 1 is_isometry(phi) - D, inj = direct_sum(qN, qM) - qNinD, qMinD = inj - OD = orthogonal_group(D) + 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) + ordHp = p^valuation(ordH, p) + Oqp, _ = restrict_automorphism_group(O, qpinq; check = false) + fqp = restrict_endomorphism(f, qpinq; check = false) + if ordHp == p + _, j = _get_V(id_hom(qp), minimal_polynomial(identity_matrix(QQ, 1)), p) + sors = _subgroups_orbit_representatives_and_stabilizers_elementary(j, Oqp, ordHp, fqp; compute_stab) + else + sors = _subgroups_orbit_representatives_and_stabilizers(id_hom(qp), Oqp, ordHp, fqp; compute_stab) + end + is_empty(sors) && return res + push!(list_can, sors) + end - subsN = _classes_automorphic_subgroups(qN, GN, rescale(H, -1)) - @hassert :ZZLatWithIsom 1 !isempty(subsN) - subsM = _classes_automorphic_subgroups(qM, GM, H) - @hassert :ZZLatWithIsom 1 !isempty(subsM) + # 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 + # degenerate case) + for lis in Hecke.cartesian_product_iterator(list_can) + 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, elem_type(q)[sum([embs[i](_proj[i](g)) for i in 1:length(lis)]) for g in gens(H2)]) + if compute_stab + stabs = AutomorphismGroup{TorQuadModule}[l[2] for l in lis] + genestab = ZZMatrix[] - 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 + 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 - HNinqN, stabN = H1 - HNinD = compose(HNinqN, qNinD) - HN = domain(HNinqN) - OHN = orthogonal_group(HN) + 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 - HMinqM, stabM = H2 - HMinD = compose(HMinqM, qMinD) - HM = domain(HMinqM) - OHM = orthogonal_group(HM) +############################################################################### +# +# Primitive embeddings and extensions for even lattices +# +############################################################################### - actN = hom(stabN, OHN, elem_type(OHN)[OHN(restrict_automorphism(x, HNinqN; check = false); check = false) for x in gens(stabN)]) - actM = hom(stabM, OHM, elem_type(OHM)[OHM(restrict_automorphism(x, HMinqM; check = false); check = false) for x in gens(stabM)]) - imM, _ = image(actM) +@doc raw""" + primitive_extensions(M::ZZLat, N::ZZLat; x::Union{IntegerUnion, Nothing} = nothing, + q::Union{TorQuadModule, Nothing} = nothing, + classification::Symbol = :sublat) + -> Vector{Tuple{ZZLat, ZZLat, ZZLat}} + +Given two even integer lattices $M$ and $N$, return a list $V$ of representatives +of isomorphism classes of primitive extensions $M \oplus N \subseteq L$. + +One can decide to choose the index of $[L:(M\oplus N)]$, which should be a +positive integer by setting `x` to the desired value. +One can also decide on the isometry class of the discriminant form of the +primitive extension by setting `q` to the desired value. +If there are no primitive extensions of $M\oplus N$ satisfying the conditions +imposed by the choice of `x` or `q`, the function returns the empty list. + +Otherwise, $V$ consists of triple $(L, M', N')$ such that $M'$ is isometric to $M$, +$N'$ is isometric to $N$ and $L$ is a primitive extension of $M'\oplus N'$ satisfying +conditions `x` or `q` if assigned. + +The content of $V$ depends on the value of the symbol `classification`. There +are 3 possibilities: + - `classification == :first`: $V$ consists of the first primitive extension computed; + - `classification == :sublat`: $V$ consists of representatives for all isomorphism classes of primitive extensions of $M\oplus N$, up to the actions of $O(M)$ and $O(N)$; + - `classification == :emb`: $V$ consists of representatives for all isomorphism classes of primitive extensions of $M\oplus N$, up to the action $O(N)$; + +The classification methods for the symbols `:sublat` and `:emb` correspond +to the different classes of primitive embeddings defined by V. V. Nikulin in +Proposition 1.5.1 of [Nik79](@cite). Indeed, we can see the classification +of primitive extensions as a classification of primitive embeddings of $M$ +into some even lattices, with orthogonal complement isometric to $N$. + +For the classifications of type `:emb`, if one wants a classification up to the +action of $O(M)$ only, one should instead call +`primitive_extensions(N, M, rescale(H, -1)); classification = :emb)`. +""" +function primitive_extensions(M::ZZLat, N::ZZLat; x::Union{IntegerUnion, Nothing} = nothing, + q::Union{TorQuadModule, Nothing} = nothing, + classification::Symbol = :sublat) + @req classification in Symbol[:first, :emb, :sublat] "Wrong symbol for classification" - stabNphi = elem_type(OHM)[OHM(compose(inv(phi), compose(hom(actN(g)), phi)); check = false) for g in gens(stabN)] - stabNphi, _ = sub(OHM, stabNphi) + results = Tuple{ZZLat, ZZLat, ZZLat}[] - if is_elementary_with_prime(HM)[1] - iso = isomorphism(PermGroup, OHM) - else - iso = id_hom(OHM) + @req is_even(M) && is_even(N) "Only implemented for pairs of even integer lattices" + + # We check the initial conditions to make sense for having a primitive + # extensions with the potential given requirements + if !isnothing(x) + !is_divisible_by(numerator(gcd(det(M), det(N))), x) && return results + if !isnothing(q) + @req x^2*order(q) == det(M)*det(N) "Wrong requirements: the square of the index `x` should be equal to (det(M)*det(N)/order(q))" end - reps = double_cosets(codomain(iso), iso(stabNphi)[1], iso(imM)[1]) - @vprintln :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" + elseif !isnothing(q) + aM, _, bM = signature_tuple(M) + aN, _, bN = signature_tuple(N) + !is_genus(q, (aM+aN, bM+bN)) && return results + @req modulus_quadratic_form(q) == 2 "q does not define the discriminant form of an even lattice" + G = genus(q, (aM+aN, bM+bN)) + ok, x = divides(numerator(det(M)*det(N)), order(q)) + !ok && return results + ok, x = is_square_with_sqrt(x) + !ok && return results + end - for g in reps - 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)))) - @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))) - @hassert :ZZLatWithIsom 1 genus(M) == genus(M2) - push!(results, (L, M2, N2)) - @vprintln :ZZLatWithIsom 1 "Gluing done" - classification == :first && return results + # Methods are simpler if we work in a fixed space + same_ambient = ambient_space(M) === ambient_space(N) + + # Decide on a subgroup of O(qM) depending on the kind of classification + qM = discriminant_group(M) + if classification == :emb + GM = Oscar._orthogonal_group(qM, ZZMatrix[matrix(id_hom(qM))]; check = false) + else + GM, _ = image_in_Oq(M) + end + + # On the other side we do always with respect to O(N) + qN = discriminant_group(N) + GN, _ = image_in_Oq(N) + + if same_ambient + D = qM + qN + qMinD = hom(qM, D, TorQuadModuleElem[D(lift(x)) for x in gens(qM)]) + qNinD = hom(qN, D, TorQuadModuleElem[D(lift(x)) for x in gens(qN)]) + else + D, inj = direct_sum(qM, qN) + qMinD, qNinD = inj + end + OD = orthogonal_group(D) + + if !isnothing(x) + # First case: we know the order of the subgroup along which we glue! + # We first enumerate respective of isometry classes of subgroups of qN of + # order x + subsN = _classes_isomorphic_subgroups(qN, GN, x) + isempty(subsN) && return results + for H2 in subsN + # Then for each class, we look for representative of isometry classes of + # subgroups of M anti-isometric to them + subsM = _classes_isomorphic_subgroups(qM, GM, rescale(domain(H2[1]), -1)) + isempty(subsM) && continue + for H1 in subsM + ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) + @hassert :ZZLatWithIsom 1 ok + + HMinqM, stabM = H1 + HMinD = compose(HMinqM, qMinD) + HM = domain(HMinqM) + OHM = orthogonal_group(HM) + + HNinqN, stabN = H2 + HNinD = compose(HNinqN, qNinD) + HN = domain(HNinqN) + OHN = orthogonal_group(HN) + + actM = hom(stabM, OHM, elem_type(OHM)[OHM(restrict_automorphism(x, HMinqM; check = false); check = false) for x in gens(stabM)]) + actN = hom(stabN, OHN, elem_type(OHN)[OHN(restrict_automorphism(x, HNinqN; check = false); check = false) for x in gens(stabN)]) + imN, _ = image(actN) + + stabMphi = elem_type(OHN)[OHN(compose(inv(phi), compose(hom(actM(g)), phi)); check = false) for g in gens(stabM)] + stabMphi, _ = sub(OHN, stabMphi) + + if is_elementary_with_prime(HN)[1] + iso = isomorphism(PermGroup, OHN) + else + iso = id_hom(OHN) + end + reps = double_cosets(codomain(iso), iso(stabMphi)[1], iso(imN)[1]) + @vprintln :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" + + for g in reps + g = iso\(representative(g)) + phig = compose(phi, hom(g)) + L, _, _ = _overlattice(phig, HMinD, HNinD; same_ambient) + + # L might not be in the good genus if one is fixed + if !isnothing(q) + genus(L) == G || continue + end + M2 = lattice_in_same_ambient_space(L, hcat(basis_matrix(M), zero_matrix(QQ, rank(M), degree(L)-degree(M)))) + @hassert :ZZLatWithIsom 1 genus(M) == genus(M2) + N2 = lattice_in_same_ambient_space(L, hcat(zero_matrix(QQ, rank(N), degree(L)-degree(N)), basis_matrix(N))) + @hassert :ZZLatWithIsom 1 genus(N) == genus(N2) + push!(results, (L, M2, N2)) + @vprintln :ZZLatWithIsom 1 "Gluing done" + classification == :first && return results + end + end + end + else + # second case: we do not know the order of the glue so we basically go on + # with every possible orders. + + # In the primary and elementary cases, we can simplify many things. + prN, pN = is_primary_with_prime(N) + elN = is_elementary(N, pN) + + prM, pM = is_primary_with_prime(M) + elM = is_elementary(M, pM) + + for k in divisors(gcd(order(qM), order(qN))) + ok, ek, pk = is_prime_power_with_data(k) + @vprintln :ZZLatWithIsom 1 "Glue order: $(k)" + + if elN || elM + _, VNinqN = _get_V(id_hom(qN), minimal_polynomial(identity_matrix(QQ, 1)), max(pN, pM)) + subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, k; compute_stab = false) + elseif ok && (ek == 1) + _, VNinqN = _get_V(id_hom(qN), minimal_polynomial(identity_matrix(QQ, 1)), k) + subsN = _subgroups_orbit_representatives_and_stabilizers_elementary(VNinqN, GN, k; compute_stab = false) + else + if prN || prM + _, VNinqN = primary_part(qN, max(pN, pM)) + elseif ok + _, VNinqN = primary_part(qN, pk) + else + VNinqN = id_hom(qN) + end + subsN = _subgroups_orbit_representatives_and_stabilizers(VNinqN, GN, k; compute_stab = false) + end + isempty(subsN) && continue + + for H2 in subsN + subsM = _classes_isomorphic_subgroups(qM, GM, rescale(domain(H2[1]), -1)) + isempty(subsM) && continue + for H1 in subsM + ok, phi = is_anti_isometric_with_anti_isometry(domain(H1[1]), domain(H2[1])) + @hassert :ZZLatWithIsom 1 ok + + HMinqM, stabM = H1 + HMinD = compose(HMinqM, qMinD) + HM = domain(HMinqM) + OHM = orthogonal_group(HM) + + HNinqN, stabN = H2 + HNinD = compose(HNinqN, qNinD) + HN = domain(HNinqN) + OHN = orthogonal_group(HN) + + actM = hom(stabM, OHM, elem_type(OHM)[OHM(restrict_automorphism(x, HMinqM; check = false); check = false) for x in gens(stabM)]) + actN = hom(stabN, OHN, elem_type(OHN)[OHN(restrict_automorphism(x, HNinqN; check = false); check = false) for x in gens(stabN)]) + imN, _ = image(actN) + + stabMphi = elem_type(OHN)[OHN(compose(inv(phi), compose(hom(actM(g)), phi)); check = false) for g in gens(stabM)] + stabMphi, _ = sub(OHN, stabMphi) + + if is_elementary_with_prime(HN)[1] + iso = isomorphism(PermGroup, OHN) + else + iso = id_hom(OHN) + end + reps = double_cosets(codomain(iso), iso(stabMphi)[1], iso(imN)[1]) + @vprintln :ZZLatWithIsom 1 "$(length(reps)) isomorphism classe(s) of primitive extensions" + + for g in reps + g = iso\(representative(g)) + phig = compose(phi, hom(g)) + L, _, _ = _overlattice(phig, HMinD, HNinD; same_ambient) + M2 = lattice_in_same_ambient_space(L, hcat(basis_matrix(M), zero_matrix(QQ, rank(M), degree(L)-degree(M)))) + @hassert :ZZLatWithIsom 1 genus(M) == genus(M2) + N2 = lattice_in_same_ambient_space(L, hcat(zero_matrix(QQ, rank(N), degree(L)-degree(N)), basis_matrix(N))) + @hassert :ZZLatWithIsom 1 genus(N) == genus(N2) + push!(results, (L, M2, N2)) + @vprintln :ZZLatWithIsom 1 "Gluing done" + classification == :first && return results + end + end + end end end return results @@ -637,25 +925,32 @@ end check::Bool = true) -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} -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`. +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'` is isometric to `L`, `M'` is a primitive sublattice -of `L'` isometric to `M`, and `N'` is the orthogonal complement of `M'` in `L'`. +The first output of the function is a boolean `T` stating whether $M$ embeds +primitively in $L$. The second output $V$ consists of triples $(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 -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 `T == false`, then $V$ will always be the empty list. If `T == true`, then +the content of $V$ depends on the value of the symbol `classification`. There +are 4 possibilities: + - `classification == :none`: $V$ is the empty list; + - `classification == :first`: $V$ consists of the first primitive embedding found; + - `classification == :sublat`: $V$ consists of representatives for all isomorphism classes of primitive embeddings of $M$ in $L$, up to the actions of $O(M)$ and $O(q)$ where $q$ is the discriminant group of $L$; + - `classification == :emb`: $V$ consists of representatives for all isomorphism classes of primitive embeddings of $M$ in $L$ 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 +If `check` is set to `true`, the function determines whether $L$ is in fact unique in its genus. +We follow the algorithm described in the proof of Proposition 1.15.1 of +[Nik79](@cite). The classification methods for the symbols `:sublat` and `:emb` +correspond to the different classes of primitive embeddings defined in +the same proposition: for `:sublat` we classify sublattices of $L$ which are +isometric to $M$, and for `:emb` we classify the different embeddings of $M$ +into $L$. + # Examples We can use such primitive embeddings algorithm to classify embedding in unimodular lattices @@ -675,8 +970,8 @@ julia> pe 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 +To be understood: there exists a unique class of embedding of the root lattice +$A_4$ into the root lattice $E_8$, and the orthogonal primitive sublattice is isometric to $A_4$. """ function primitive_embeddings(L::ZZLat, M::ZZLat; classification::Symbol = :sublat, check::Bool = true) @@ -693,24 +988,31 @@ end -> 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 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 -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`. +$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 output of the function is a boolean `T` stating whether $M$ embeds +primitively in a lattice in $G$. The second output $V$ consists of 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$ depends on the value of the symbol `classification`. There +are 4 possibilities: + - `classification == :none`: $V$ is the empty list; + - `classification == :first`: $V$ consists of the first primitive embedding found; + - `classification == :sublat`: $V$ consists of representatives for all isomorphism classes of primitive embeddings of $M$ in lattices in $G$, up to the actions of $O(M)$ and $O(q)$; + - `classification == :emb`: $V$ consists of representatives for all isomorphism classes of primitive embeddings of $M$ in lattices in $G$ $G$, up to the action of $O(q)$. If the pair `(q, sign)` does not define a non-empty genus for integer lattices, an error is thrown. + +We follow the algorithm described in the proof of Proposition 1.15.1 of +[Nik79](@cite). The classification methods for the symbols `:sublat` and `:emb` +correspond to the different classes of primitive embeddings defined in +the same proposition: for `:sublat` we classify sublattices of lattices in $G$ +which are isometric to $M$, and for `:emb` we classify the different embeddings +of $M$ into lattices in $G$. """ 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" @@ -724,28 +1026,35 @@ end primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :sublat) -> Bool, Vector{Tuple{ZZLat, ZZLat, ZZLat}} -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` -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`. +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 output of the function is a boolean `T` stating whether $M$ embeds +primitively in a lattice in $G$. The second output $V$ consists of 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$ depends on the value of the symbol `classification`. There +are 4 possibilities: + - `classification == :none`: $V$ is the empty list; + - `classification == :first`: $V$ consists of the first primitive embedding found; + - `classification == :sublat`: $V$ consists of representatives for all isomorphism classes of primitive embeddings of $M$ in lattices in $G$, up to the actions of $O(M)$ and $O(q)$ where $q$ is the discriminant group of a lattice in $G$; + - `classification == :emb`: $V$ consists of representatives for all isomorphism classes of primitive embeddings of $M$ in of lattices in $G$, up to the action of $O(q)$ where $q$ is the discriminant group of a lattice in $G$. + +We follow the algorithm described in the proof of Proposition 1.15.1 of +[Nik79](@cite). The classification methods for the symbols `:sublat` and `:emb` +correspond to the different classes of primitive embeddings defined in +the same proposition: for `:sublat` we classify sublattices of lattices in $G$ +which are isometric to $M$, and for `:emb` we classify the different embeddings +of $M$ into lattices in $G$. """ 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" + @req classification in Symbol[:none, :emb, :sublat, :first] "Wrong symbol for classification" posL, _, negL = signature_tuple(G) posM, _, negM = signature_tuple(M) - @req (posL-posM >= 0 && negL-negM >= 0) "Impossible embedding" + @req (posL-posM >= 0 && negL-negM >= 0) "Incompatible signatures for the embeddings" results = Tuple{ZZLat, ZZLat, ZZLat}[] if rank(M) == rank(G) @@ -756,167 +1065,109 @@ function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :su @req rank(M) < rank(G) "The rank of M must be smaller or equal than the one of the lattices in G" - qM = discriminant_group(M) - OqM = orthogonal_group(qM) - GM, _ = image_in_Oq(M) - - qL = discriminant_group(rescale(G, -1)) - GL = orthogonal_group(qL) - - D, inj = direct_sum(qM, qL) - qMinD, qLinD = inj - - prG, pG = is_primary_with_prime(G) - elG = is_elementary(G, pG) - - 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 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 - 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 - - 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) + # We can carry out the unimodular case apart thanks to Nikulin, + # Proposition 1.6.1 [Nik79] + # + # In that case, the genus of the complement is uniquely determined, and after + # enumerating it, we just have to classify primitive extensions into G + if is_unimodular(G) + qM = discriminant_group(M) + !is_genus(rescale(qM, -1), (posL-posM, negL-negM)) && return false, results + GK = genus(rescale(qM, -1), (posL-posM, negL-negM)) + + # Now for each K in GK, we compute primitive extensions of M and K into a + # unimodular lattice in G + for K in representatives(GK) + pe = primitive_extensions(M, K; x = order(qM), classification) + if !isempty(pe) + append!(results, pe) + classification == :first && return results end end - !is_divisible_by(order(VM), k) && continue - - itM = submodules(VM; order = k) - st = iterate(itM) - @hassert :ZZLatWithIsom !isnothing(st) - - @vprintln :ZZLatWithIsom 1 "$(length(subsL)) subgroup(s)" - for H in subsL - HL = H[1] - _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 + return (length(results) > 0), results + end - isnothing(HM) && continue - - HM = compose(HM[2], VMinqM) - @vprintln :ZZLatWithIsom 1 "Possible gluings" - ok, phi = is_anti_isometric_with_anti_isometry(domain(HM), domain(HL)) - @hassert :ZZLatWithIsom 1 ok - - 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, (posL-posM, negL-negM)) && continue - - classification == :none && return true, results - - 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)" - 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\perp L2) lies in the embedding of $qM \to D$. Their - # quotient is isometric to the quotient of their preimage. - # The quotient of their preimage is precisely the subgroup of `qM` - # we look for. - 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 !is_empty(temp) - classification == :first && return true, temp - append!(results, temp) - end - end + # Now we go on the harder case, which relies on the previous one + # We follow the proof of Nikulin: we create `T` unique in its genus and with + # surjective O(T) -> O(qT), and such that qT and q are anti-isometric + # The easiest way to do this is to add a hyperbolic plane to a representative + # of the genus G, rescale by -1. + T, _ = direct_sum(rescale(lll(representative(G)), -1), hyperbolic_plane_lattice()) + + # The algorithm goes on with finding primitive extensions of M+T and then + # embeddings such in a big unimodular lattice (which we take unique in its + # genus) + Vs = primitive_extensions(M, T) + + # GL is our big unimodular genus where we embed each of the V in Vs + GL = genus(torsion_quadratic_module(QQ[0;]), (rank(G)+1, rank(G)+1)) + # M2 is M seen in V, and T2 is T seen in V + for (V, M2, T2) in Vs + okV, resV = primitive_embeddings(GL, V; classification = :sublat) + !okV && continue + for (S, V2, W2) in resV + # This is T seen in S + T3 = lattice_in_same_ambient_space(S, hcat(basis_matrix(T2), zero_matrix(QQ, rank(T2), degree(W2)-degree(T2)))) + @hassert :ZZLatWithIsom 1 genus(T3) == genus(T) + # This is one of the lattice in G in which we embed primitively + L = orthogonal_submodule(S, T3) + @hassert :ZZLatWithIsom 1 genus(L) == G + # This is M seen in L + M3 = lattice_in_same_ambient_space(S, hcat(basis_matrix(M2), zero_matrix(QQ, rank(M2), degree(W2)-degree(M2)))) + @hassert :ZZLatWithIsom 1 is_sublattice(L, M3) + @hassert :ZZLatWithIsom 1 is_primitive(L, M3) + # And this is the orthogonal complement of M in L + N = orthogonal_submodule(L, M3) + # L, M3 and N live in a very big ambient space: we redescribed them in the + # rational span of L so that L has full rank and we keep only the + # necessary information. + bM = solve_left(basis_matrix(L), basis_matrix(M3)) + bN = solve_left(basis_matrix(L), basis_matrix(N)) + L = integer_lattice(; gram = gram_matrix(L)) + M3 = lattice_in_same_ambient_space(L, bM) + N = lattice_in_same_ambient_space(L, bN) + push!(results, (L, M3, N)) + classification == :first && return true, results end end return (length(results) > 0), results end -#################################################################################### +############################################################################### # # Admissible equivariant primitive extensions # -#################################################################################### +############################################################################### @doc raw""" admissible_equivariant_primitive_extensions(Afa::ZZLatWithIsom, Bfb::ZZLatWithIsom, Cfc::ZZLatWithIsom, - p::Integer, - q::Integer = p; check::Bool = true) + p::IntegerUnion, + q::IntegerUnion = p; check::Bool = true) -> 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 +Given a triple of lattices with isometry $(A, f_A)$, $(B, f_B)$ and $(C, f_C)$, +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/G_A$ where: - - ``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)`. + - ``G_A`` and ``G_B`` are the respective images of the morphisms $O(A, f_A) \to O(D_A, D_{f_A})$ and $O(B, f_B) \to O(D_B, D_{f_B})$; + - ``S`` is the set of all primitive extensions $A \oplus B \subseteq C'$ with isometry $f_C'$ where $p\cdot C' \subseteq A\oplus B$ and such that the type of $(C', (f_C')^q)$ is equal to the type of $(C, f_C)$. -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. +If `check == true` the input triple is checked to a $p$-admissible triple of +integral lattices (with isometry) with $f_A$ and $f_B$ having relatively coprime +irreducible minimal polynomials. Moreover, the function checks that $A$ and $B$ +are orthogonal if $A$, $B$ and $C$ lie in the same ambient quadratic space. + +Note moreover that the function computes the image of the natural map +$O(C, f_C) \to O(D_C, D_{f_C})$ along the primitive extension +$A\oplus B\subseteq C$ (see Algorithm 2, Line 22 of [BH23](@cite)). """ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, B::ZZLatWithIsom, C::ZZLatWithIsom, - p::Integer, - q::Integer = p; check::Bool = true) + p::IntegerUnion, + q::IntegerUnion = 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" @@ -968,7 +1219,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # We compute the overlattice in this context C2, fC2, _ = _overlattice(qAinD, qBinD, isometry(A), isometry(B); same_ambient) - C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false) + C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false, check) # If not of the good type, we discard it !is_of_type(C2fC2^q, type(C)) && return results @@ -1101,7 +1352,7 @@ 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) - C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false) + C2fC2 = integer_lattice_with_isometry(C2, fC2; ambient_representation = false, check) # 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 @@ -1230,7 +1481,7 @@ function _find_admissible_gluing(SAinqA::TorQuadModuleMor, # We first massage phi such that it maps HA to HB phiHA, _ = sub(SB, elem_type(SB)[phi(SA(lift(a))) for a in gens(HA)]) OSB = orthogonal_group(SB) - G = GSetByElements(OSB, _on_subgroup_automorphic, TorQuadModule[HB]) + G = GSetByElements(OSB, _on_subgroups, TorQuadModule[HB]) ok, g = representative_action(G, phiHA, HB) @hassert :ZZLatWithIsom 1 ok phi_1 = compose(phi, hom(g)) @@ -1298,6 +1549,6 @@ function _glue_stabilizers(phi::TorQuadModuleMor, union!(stab, kerB) stab = TorQuadModuleMor[restrict_automorphism(g, j; check = false) for g in stab] stab = TorQuadModuleMor[hom(disc, disc, elem_type(disc)[disc(lift(g(perp(lift(l))))) for l in gens(disc)]) for g in stab] - + unique!(stab) return disc, stab end diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index 97f6f702b87f..0b3756513250 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -11,9 +11,9 @@ # ################################################################################## -# The tuples in output are pairs of positive integers! +# We collect the pairs `(d', d/d')` for all divisors `d'` of `d`. function _tuples_divisors(d::T) where T <: IntegerUnion - return Tuple{T, T}[(dd,abs(divexact(d,dd))) for dd in divisors(d)] + 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 @@ -59,7 +59,8 @@ function _find_L(pG::Int, nG::Int, r::Int, d::RationalUnion, s::ZZRingElem, l::Z filter!(G -> is_divisible_by(p*l, numerator(level(G))), gen) else gen = ZZGenus[] - for (s1, s2) in Tuple{Int, Int}[(s,t) for s=0:pG for t=0:nG if s+t==r] + for s1 in max(0, r-nG):min(pG, r) + s2 = r-s1 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) @@ -72,9 +73,9 @@ end @doc raw""" 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$, return whether `(A,B,C)` is -`p`-admissible. +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. # Examples A standard example is the following: let $(L, f)$ be a lattice with isometry of @@ -235,11 +236,17 @@ function is_admissible_triple(A::T, B::T, C::T, p::IntegerUnion) where T <: Unio end @doc raw""" - admissible_triples(C::ZZGenus, p::Integer) -> Vector{Tuple{ZZGenus, ZZGenus}} + admissible_triples(C::ZZGenus, p::Integer; pA::Int = -1 + pB::Int = -1) + -> 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 -`B` is of rank divisible by $p-1$. +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$. + +One can choose the positive signatures for the genera $A$ and $B$ in output +respectively by setting `pA` and `pB` to the desired values. The function +returns an error if the choice of these values is inconsistent. # Examples ```jldoctest @@ -305,10 +312,8 @@ function admissible_triples(G::ZZGenus, p::IntegerUnion; pA::Int = -1, pB::Int = 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 + for A in L1, B in Lp + is_admissible_triple(A, B, G, p) && push!(L, (A, B)) end end end @@ -317,13 +322,15 @@ end admissible_triples(L::T, p::IntegerUnion; pA::Int = -1, pB::Int = -1) where T <: Union{ZZLat, ZZLatWithIsom} = admissible_triples(genus(L), p; pA, pB) -################################################################################## +############################################################################### # # Representatives of lattices with isometry # -################################################################################## +############################################################################### # we compute ideals of E/K whose absolute norm is equal to d +# The first function covers the general case and dispatch to the second function +# for `d` integral. function _ideals_of_norm(E::Field, d::QQFieldElem) OE = maximal_order(E) @@ -332,7 +339,7 @@ function _ideals_of_norm(E::Field, d::QQFieldElem) elseif numerator(d) == 1 return Hecke.fractional_ideal_type(OE)[inv(I) for I in _ideals_of_norm(E, denominator(d))] else - return Hecke.fractional_ideal_type(OE)[I*inv(J) for (I, J) in Hecke.cartesian_product_iterator(Vector{Hecke.fractional_ideal_type(OE)}[_ideals_of_norm(E, numerator(d)), _ideals_of_norm(E, denominator(d))]; inplace=false)] + return Hecke.fractional_ideal_type(OE)[I*inv(J) for I in _ideals_of_norm(E, numerator(d)), J in _ideals_of_norm(E, denominator(d))] end end @@ -358,7 +365,7 @@ function _ideals_of_norm(E::Field, d::ZZRingElem) 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) + for I in Hecke.cartesian_product_iterator(primes) I = prod(I) if absolute_norm(I) == d push!(ids, fractional_ideal(OE, I)) @@ -409,12 +416,13 @@ end 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 finite 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 +$(M, g)$ of finite hermitian type such that the type of $(M, g^m)$ is equal to the +type of $(L, f)$. + +Note that in this case, the isometries $g$'s are of order $nm$. # Examples ```jldoctest @@ -516,12 +524,12 @@ end @doc raw""" 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^e$ -for some prime number `q`, and given another prime number $p \neq q$, return a +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 `e` can be 0. +Note that $e$ can be `0`. # Examples ```jldoctest @@ -572,8 +580,8 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion; 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) + for L1 in RA, L2 in RB + E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p; check = false) append!(reps, E) end end @@ -584,15 +592,15 @@ end 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 +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 +If `b == 1`, return only the lattices with isometry $(M, g)$ where $g$ has order $pq^e$. -Note that `e` can be 0. +Note that $e$ can be `0`. # Examples ```jldoctest @@ -640,13 +648,13 @@ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion, b::Int = 0 x = gen(Hecke.Globals.Qx) A0 = kernel_lattice(Lf, q^e) 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) - is_empty(B) && return reps - for (L1, L2) in Hecke.cartesian_product_iterator([A, B]; inplace=false) + RA = splitting_of_hermitian_prime_power(A0, p) + is_empty(RA) && return reps + RB = splitting_of_prime_power(B0, p) + is_empty(RB) && return reps + for L1 in RA, L2 in RB 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) + E = admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p; check = false) @hassert :ZZLatWithIsom 1 b == 0 || all(LL -> order_of_isometry(LL) == p*q^e, E) append!(reps, E) end @@ -657,13 +665,13 @@ end 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 +Given a lattice with isometry $(L, f)$ and a prime number $p$, such that $\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. +Note that $e$ can be `0`, while $d$ has to be positive. """ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, _p::IntegerUnion) rank(Lf) == 0 && return ZZLatWithIsom[Lf] @@ -705,12 +713,12 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, _p::IntegerUnion @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) - 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) + RA = representatives_of_hermitian_type(A0, p) + is_empty(RA) && return reps + RB = splitting_of_pure_mixed_prime_power(B0, p) + is_empty(RB) && return reps + for L1 in RA, L2 in RB + E = admissible_equivariant_primitive_extensions(L2, L1, Lf, q, p; check = false) append!(reps, E) end return reps @@ -720,15 +728,15 @@ end 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 +Given a lattice with isometry $(L, f)$ and a prime number $p$ such that +$f$ has 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)$ 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$. +If `b == 1`, return only the lattices with isometry $(M, g)$ where $g$ has +order $p^{d+1}q^e$. -Note that `d` and `e` can be both zero. +Note that $d$ and $e$ can be both `0`. # Examples ```jldoctest @@ -789,12 +797,12 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion, b::I 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) - 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) + RA = splitting_of_pure_mixed_prime_power(A0, p) + isempty(RA) && return reps + RB = splitting_of_mixed_prime_power(B0, p, 0) + is_empty(RB) && return reps + for L1 in RA, L2 in RB + E = admissible_equivariant_primitive_extensions(L2, L1, Lf, p; check = false) b == 1 && filter!(LL -> order_of_isometry(LL) == p^(d+1)*q^e, E) append!(reps, E) end @@ -807,14 +815,15 @@ end enumerate_classes_of_lattices_with_isometry(G::ZZGenus, order::IntegerUnion) -> 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 -`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`. +Given an even 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 even 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::IntegerUnion) + @req is_even(L) "For now we support only the case where L is even" @req is_finite(order) && order >= 1 "order must be positive and finite" if order == 1 reps = representatives_of_hermitian_type(integer_lattice_with_isometry(L)) @@ -937,7 +946,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 = unique!(reduce(vcat, prime_divisors.(ord))) - vals = Int[maximum([valuation(x, p) for x in ord]) for p in pds] + vals = Int[maximum(Int[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 6bfd7915d826..987460581037 100644 --- a/experimental/QuadFormAndIsom/src/exports.jl +++ b/experimental/QuadFormAndIsom/src/exports.jl @@ -4,6 +4,7 @@ export ZZLatWithIsom export admissible_equivariant_primitive_extensions export admissible_triples export ambient_isometry +export discriminant_representation export enumerate_classes_of_lattices_with_isometry export image_centralizer_in_Oq export integer_lattice_with_isometry @@ -16,6 +17,7 @@ export is_of_type export is_hermitian export order_of_isometry export primitive_embeddings +export primitive_extensions export quadratic_space_with_isometry export representatives_of_hermitian_type export splitting_of_hermitian_prime_power diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index b7e8b90c5459..24f74a4e3537 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -8,7 +8,7 @@ @doc raw""" lattice(Lf::ZZLatWithIsom) -> ZZLat -Given a lattice with isometry $(L, f)$, return the underlying lattice `L`. +Given a lattice with isometry $(L, f)$, return the underlying lattice $L$. # Examples ```jldoctest @@ -25,7 +25,7 @@ lattice(Lf::ZZLatWithIsom) = Lf.Lb @doc raw""" isometry(Lf::ZZLatWithIsom) -> QQMatrix -Given a lattice with isometry $(L, f)$, return the underlying isometry `f`. +Given a lattice with isometry $(L, f)$, return the underlying isometry $f$. # Examples ```jldoctest @@ -47,11 +47,11 @@ isometry(Lf::ZZLatWithIsom) = Lf.f 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`. +$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`. +Note that $g$ might not be unique and we fix such a global isometry +together with $V$ into a container type `QuadSpaceWithIsom`. # Examples ```jldoctest @@ -79,7 +79,7 @@ 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 @@ -101,7 +101,7 @@ ambient_isometry(Lf::ZZLatWithIsom) = isometry(ambient_space(Lf)) order_of_isometry(Lf::ZZLatWithIsom) -> IntExt Given a lattice with isometry $(L, f)$, return the order of the underlying -isometry `f`. +isometry $f$. # Examples ```jldoctest @@ -125,7 +125,7 @@ order_of_isometry(Lf::ZZLatWithIsom) = Lf.n rank(Lf::ZZLatWithIsom) -> Integer Given a lattice with isometry $(L, f)$, return the rank of the underlying lattice -`L`. +$L$. # Examples ```jldoctest @@ -143,7 +143,7 @@ rank(Lf::ZZLatWithIsom) = rank(lattice(Lf)) characteristic_polynomial(Lf::ZZLatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the characteristic polynomial of the -underlying isometry `f`. +underlying isometry $f$. # Examples ```jldoctest @@ -161,7 +161,7 @@ characteristic_polynomial(Lf::ZZLatWithIsom) = characteristic_polynomial(isometr minimal_polynomial(Lf::ZZLatWithIsom) -> QQPolyRingElem Given a lattice with isometry $(L, f)$, return the minimal polynomial of the -underlying isometry `f`. +underlying isometry $f$. # Examples ```jldoctest @@ -179,7 +179,7 @@ minimal_polynomial(Lf::ZZLatWithIsom) = minimal_polynomial(isometry(Lf)) genus(Lf::ZZLatWithIsom) -> ZZGenus Given a lattice with isometry $(L, f)$, return the genus of the underlying -lattice `L`. +lattice $L$. # Examples ```jldoctest @@ -201,7 +201,7 @@ genus(Lf::ZZLatWithIsom) = genus(lattice(Lf)) basis_matrix(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the basis matrix of the underlying -lattice `L`. +lattice $L$. # Examples ```jldoctest @@ -236,7 +236,7 @@ basis_matrix(Lf::ZZLatWithIsom) = basis_matrix(lattice(Lf)) gram_matrix(Lf::ZZLatWithIsom) -> QQMatrix Given a lattice with isometry $(L, f)$, return the gram matrix of the underlying -lattice `L` with respect to its basis matrix. +lattice $L$ with respect to its basis matrix. # Examples ```jldoctest @@ -258,8 +258,8 @@ gram_matrix(Lf::ZZLatWithIsom) = gram_matrix(lattice(Lf)) 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` together with the -underlying isometry of `L`. +$L \otimes \mathbb{Q}$ of the underlying lattice $L$ together with the +underlying isometry of $L$. # Examples ```jldoctest @@ -287,7 +287,7 @@ 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`. +underlying lattice $L$. # Examples ```jldoctest @@ -305,7 +305,7 @@ det(Lf::ZZLatWithIsom) = det(lattice(Lf)) scale(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the scale of the underlying -lattice `L`. +lattice $L$. # Examples ```jldoctest @@ -323,7 +323,7 @@ scale(Lf::ZZLatWithIsom) = scale(lattice(Lf)) norm(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the norm of the underlying -lattice `L`. +lattice $L$. # Examples ```jldoctest @@ -341,7 +341,7 @@ norm(Lf::ZZLatWithIsom) = norm(lattice(Lf)) is_positive_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is positive definite. +lattice $L$ is positive definite. # Examples ```jldoctest @@ -359,7 +359,7 @@ is_positive_definite(Lf::ZZLatWithIsom) = is_positive_definite(lattice(Lf)) is_negative_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is negative definite. +lattice $L$ is negative definite. # Examples ```jldoctest @@ -377,7 +377,7 @@ is_negative_definite(Lf::ZZLatWithIsom) = is_negative_definite(lattice(Lf)) is_definite(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying -lattice `L` is definite. +lattice $L$ is definite. # Examples ```jldoctest @@ -395,7 +395,7 @@ is_definite(Lf::ZZLatWithIsom) = is_definite(lattice(Lf)) minimum(Lf::ZZLatWithIsom) -> QQFieldElem Given a positive definite lattice with isometry $(L, f)$, return the minimum -of the underlying lattice `L`. +of the underlying lattice $L$. # Examples ```jldoctest @@ -416,7 +416,7 @@ end is_integral(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice -`L` is integral. +$L$ is integral. # Examples ```jldoctest @@ -434,7 +434,7 @@ is_integral(Lf::ZZLatWithIsom) = is_integral(lattice(Lf)) degree(Lf::ZZLatWithIsom) -> Int Given a lattice with isometry $(L, f)$, return the degree of the underlying -lattice `L`. +lattice $L$. # Examples ```jldoctest @@ -452,7 +452,7 @@ degree(Lf::ZZLatWithIsom) = degree(lattice(Lf)) is_even(Lf::ZZLatWithIsom) -> Bool Given a lattice with isometry $(L, f)$, return whether the underlying lattice -`L` is even. +$L$ is even. # Examples ```jldoctest @@ -470,7 +470,7 @@ is_even(Lf::ZZLatWithIsom) = iseven(lattice(Lf)) discriminant(Lf::ZZLatWithIsom) -> QQFieldElem Given a lattice with isometry $(L, f)$, return the discriminant of the underlying -lattice `L`. +lattice $L$. # Examples ```jldoctest @@ -488,7 +488,7 @@ discriminant(Lf::ZZLatWithIsom) = discriminant(lattice(Lf)) signature_tuple(Lf::ZZLatWithIsom) -> Tuple{Int, Int, Int} Given a lattice with isometry $(L, f)$, return the signature tuple of the -underlying lattice `L`. +underlying lattice $L$. # Examples ```jldoctest @@ -502,6 +502,145 @@ julia> signature_tuple(Lf) """ signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf)) +@doc raw""" + is_primary_with_prime(Lf::ZZLatWithIsom) -> Bool, ZZRingElem + +Given a lattice with isometry $(L, f)$, return whether $L$ is primary, +that is whether $L$ is integral and its discriminant group is a +$p$-group for some prime number $p$. In case it is, $p$ is also returned as +second output. + +Note that for unimodular lattices, this function returns `(true, 1)`. If the +lattice is not primary, the second return value is `-1` by default. + +# Examples +```jldoctest +julia> L = root_lattice(:A, 5); + +julia> Lf = integer_lattice_with_isometry(L); + +julia> is_primary_with_prime(Lf) +(false, -1) + +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 +``` +""" +is_primary_with_prime(Lf::ZZLatWithIsom) = is_primary_with_prime(lattice(Lf)) + +@doc raw""" + is_primary(Lf::ZZLatWithIsom, p::IntegerUnion) -> Bool + +Given a lattice with isometry $(L, f)$ and a prime number $p$, +return whether $L$ is $p$-primary, that is whether its discriminant group +is a $p$-group. + +# Examples +```jldoctest +julia> L = root_lattice(:A, 6); + +julia> Lf = integer_lattice_with_isometry(L); + +julia> is_primary(Lf, 7) +true + +julia> genus(Lf) +Genus symbol for integer lattices +Signatures: (6, 0, 0) +Local symbols: + Local genus symbol at 2: 1^6 + Local genus symbol at 7: 1^-5 7^-1 +``` +""" +is_primary(Lf::ZZLatWithIsom, p::IntegerUnion) = is_primary(lattice(Lf), p) + +@doc raw""" + is_unimodular(Lf::ZZLatWithIsom) -> Bool + +Given a lattice with isometry $(L, f)$, return whether `$L$ is unimodular, +that is whether its discriminant group is trivial. + +# Examples +```jldoctest +julia> L = root_lattice(:E, 8); + +julia> Lf = integer_lattice_with_isometry(L); + +julia> is_unimodular(Lf) +true + +julia> genus(Lf) +Genus symbol for integer lattices +Signatures: (8, 0, 0) +Local symbol: + Local genus symbol at 2: 1^8 +``` +""" +is_unimodular(Lf::ZZLatWithIsom) = is_unimodular(lattice(Lf)) + +@doc raw""" + is_elementary_with_prime(Lf::ZZLatWithIsom) -> Bool, ZZRingElem + +Given a lattice with isometry $(L, f)$, return whether $L$ is elementary, that is +whether $L$ is integral and its discriminant group is an elemenentary $p$-group for +some prime number $p$. In case it is, $p$ is also returned as second output. + +Note that for unimodular lattices, this function returns `(true, 1)`. If the lattice +is not elementary, the second return value is `-1` by default. + +# Examples +```jldoctest +julia> L = root_lattice(:A, 7); + +julia> Lf = integer_lattice_with_isometry(L); + +julia> is_elementary_with_prime(Lf) +(false, -1) + +julia> is_primary(Lf, 2) +true + +julia> genus(Lf) +Genus symbol for integer lattices +Signatures: (7, 0, 0) +Local symbol: + Local genus symbol at 2: 1^6 8^1_7 +``` +""" +is_elementary_with_prime(Lf::ZZLatWithIsom) = is_elementary_with_prime(lattice(Lf)) + +@doc raw""" + is_elementary(Lf::ZZLatWithIsom, p::IntegerUnion) -> Bool + +Given a lattice with isometry $(L, f)$ and a prime number $p$, return whether +$L$ is $p$-elementary, that is whether its discriminant group is an elementary +$p$-group. + +# Examples +```jldoctest +julia> L = root_lattice(:E, 7); + +julia> Lf = integer_lattice_with_isometry(L); + +julia> is_elementary(Lf, 3) +false + +julia> is_elementary(Lf, 2) +true + +julia> genus(Lf) +Genus symbol for integer lattices +Signatures: (7, 0, 0) +Local symbol: + Local genus symbol at 2: 1^6 2^1_7 +``` +""" +is_elementary(Lf::ZZLatWithIsom, p::IntegerUnion) = is_elementary(lattice(Lf), p) + ############################################################################### # # Constructors @@ -513,13 +652,15 @@ signature_tuple(Lf::ZZLatWithIsom) = signature_tuple(lattice(Lf)) ambient_representation = true) -> 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)$. +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)$. -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. +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 as long as $f$ preserves $L$. + +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 @@ -596,7 +737,7 @@ function integer_lattice_with_isometry(L::ZZLat, f::QQMatrix; check::Bool = true 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" + @req ok "Isometry does not preserve the lattice" else V = ambient_space(L) B = basis_matrix(L) @@ -620,10 +761,10 @@ end @doc raw""" 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`. +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`. +If `neg` is set to `true`, then the isometry $f$ is negative the identity of $L$. # Examples ```jldoctest @@ -652,9 +793,9 @@ 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`. +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$. # Examples ```jldoctest @@ -691,10 +832,10 @@ lattice(Vf::QuadSpaceWithIsom) = ZZLatWithIsom(Vf, lattice(space(Vf)), isometry( 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`. +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 @@ -741,11 +882,11 @@ end 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 +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`. +$g$ on $M$. # Examples ```jldoctest @@ -790,10 +931,50 @@ end # ############################################################################### +@doc raw""" + orthogonal_submodule(Lf::ZZLatWithIsom, B::QQMatrix) -> ZZLatWithIsom + +Given a lattice with isometry $(L, f)$ and a matrix $B$ with rational entries +defining an $f$-stable sublattice of $L$, return the largest submodule of $L$ +orthogonal to each row of $B$, equipped with the induced action from $f$. + +# 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> orthogonal_submodule(Lf, B) +Integer lattice of rank 2 and degree 5 + with isometry of finite order 2 + given by + [-1 0] + [ 0 -1] +``` +""" +function orthogonal_submodule(Lf::ZZLatWithIsom, B::QQMatrix) + @req ncols(B) == degree(Lf) "The rows of B should represent vectors in the ambient space of Lf" + B2 = basis_matrix(orthogonal_submodule(lattice(Lf), B)) + return lattice_in_same_ambient_space(Lf, B2; check = false) +end + @doc raw""" rescale(Lf::ZZLatWithIsom, a::RationalUnion) -> ZZLatWithIsom -Given a lattice with isometry $(L, f)$ and a rational number `a`, return the +Given a lattice with isometry $(L, f)$ and a rational number $a$, return the lattice with isometry $(L(a), f)$. # Examples @@ -892,9 +1073,9 @@ end 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)$ -and `h` is induced by `g`. +$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$. # Examples ```jldoctest @@ -939,11 +1120,11 @@ end lll(Lf::ZZLatWithIsom) -> ZZLatWithIsom 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`. +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. +Note that matrix representing the action of $f$ on $L$ changes but the global action +on the ambient space of $L$ stays the same. # Examples ```jldoctest @@ -996,8 +1177,8 @@ end 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. +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 `ZZLatWithIsom`, finite direct sums and finite direct products agree and they are therefore called biproducts. @@ -1077,8 +1258,8 @@ direct_sum(x::Vararg{ZZLatWithIsom}) = direct_sum(collect(x)) 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. +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 `ZZLatWithIsom`, finite direct sums and finite direct products agree and they are therefore called biproducts. @@ -1160,8 +1341,8 @@ direct_product(x::Vararg{ZZLatWithIsom}) = direct_product(collect(x)) 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 +$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 `ZZLatWithIsom`, finite direct sums and finite direct products @@ -1274,10 +1455,10 @@ 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. +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]$. +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 @@ -1306,7 +1487,7 @@ julia> is_of_hermitian_type(coinvariant_lattice(Lf)) true ``` """ -function is_of_hermitian_type(Lf::ZZLatWithIsom) +@attr function is_of_hermitian_type(Lf::ZZLatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" return is_irreducible(minimal_polynomial(Lf)) end @@ -1315,9 +1496,9 @@ end hermitian_structure(Lf::ZZLatWithIsom) -> HermLat Given a lattice with isometry $(L, f)$ such that the minimal polynomial of the -underlying isometry `f` is irreducible, return the hermitian structure of the -underlying lattice `L` over the equation order of the minimal polynomial 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. For now, we only cover the case where the equation order is maximal (which is always the case when the order is @@ -1378,16 +1559,16 @@ end ############################################################################### # -# Image centralizer in discriminant group +# Discriminant actions # ############################################################################### @doc raw""" 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)$. +Given an integral lattice with isometry $(L, f)$, return the discriminant group +$D_L$ of the underlying lattice $L$ as well as the image $D_f$ of the underlying +isometry $f$ inside $O(D_L)$. # Examples ```jldoctest @@ -1436,8 +1617,51 @@ function discriminant_group(Lf::ZZLatWithIsom) f = ambient_isometry(Lf) q = discriminant_group(L) f = hom(q, q, elem_type(q)[q(lift(t)*f) for t in gens(q)]) - f = gens(Oscar._orthogonal_group(q, ZZMatrix[matrix(f)]; check = false))[1] - return (q, f) + fq = gens(Oscar._orthogonal_group(q, ZZMatrix[matrix(f)]; check = false))[1] + return (q, fq) +end + +@doc raw""" + discriminant_representation(L::ZZLat, G::MatrixGroup; + ambient_representation::Bool = true, + check::Bool = true) -> GAPGroupHomomorphism + +Given an integer lattice $L$ and a group $G$ of isometries of $L$, return the +orthogonal representation $G\to O(D_L)$ of $G$ on the discriminant group $D_L$ +of $L$. + +If `ambient_representation` is set to `true`, then the isometries in $G$ are +considered as matrix representation of their action on the standard basis of +the ambient space of $L$. Otherwise, they are considered as matrix representation +of their action on the basis matrix of $L$. +""" +function discriminant_representation(L::ZZLat, G::MatrixGroup; + ambient_representation::Bool = true, + check::Bool = true) + V = ambient_space(L) + if ambient_representation + @check all(g -> matrix(g)*gram_matrix(V)*transpose(matrix(g)) == gram_matrix(V), gens(G)) "G does not define a group of isometries of the ambient space of L" + else + @check all(g -> matrix(g)*gram_matrix(L)*transpose(matrix(g)) == gram_matrix(L), gens(G)) "G does not define a group of isometries of L" + B = basis_matrix(L) + B2 = orthogonal_complement(V, B) + C = vcat(B, B2) + iC = inv(C) + end + q = discriminant_group(L) + Oq = orthogonal_group(q) + imag_lis = elem_type(Oq)[] + geneG = gens(G) + for g in geneG + if !ambient_representation + mg = block_diagonal_matrix(QQMatrix[matrix(g), identity_matrix(QQ, nrows(B2))]) + mg = iC*mg*C + else + mg = matrix(g) + end + push!(imag_lis, Oq(hom(q, q, TorQuadModuleElem[q(lift(a)*mg) for a in gens(q)]); check = false)) + end + return hom(G, Oq, geneG, imag_lis; check = false) end @doc raw""" @@ -1445,9 +1669,9 @@ end 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$ -denotes the discriminant group of `L` and $\bar{f}$ is the isometry of -$q_L$ induced by `f`. +$O(D_L, D_f)$ of the centralizer $O(L, f)$ of $f$ in $O(L)$. Here $D_L$ +denotes the discriminant group of $L$ and $D_f$ is the isometry of +$D_L$ induced by $f$. # Examples ```jldoctest @@ -1468,46 +1692,128 @@ julia> order(G) @req is_integral(Lf) "Underlying lattice must be integral" n = order_of_isometry(Lf) L = lattice(Lf) - if (n in [1, -1]) || (isometry(Lf) == -identity_matrix(QQ, rank(L))) - # Trivial cases: the lattice has rank 0, or is endowed with +- identity + f = isometry(Lf) + chi = minpoly(Lf) + if is_unimodular(L) + # If L is unimodular we do not have to do any wizard's trick since the + # discriminant group is trivial + qL = discriminant_group(L) + OqL = orthogonal_group(qL) + return OqL, id_hom(OqL) + elseif (n in [1, -1]) || (isometry(Lf) == -identity_matrix(QQ, rank(L))) + # Trivial cases: the lattice has rank 0 or 1, or it is endowed with +- identity return image_in_Oq(L) - elseif rank(L) == euler_phi(n) - # this should also cover the case of rank 1 - # The image of -id and multiplication by primitive root of unity + elseif rank(L) == degree(chi) + # Hermitian type with hermitian structure of rank 1 + # The image consists of -id and multiplication by a primitive element on the + # hermitian side, which itself corresponds to f along the trace construction qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) - UL = ZZMatrix[-matrix(one(OqL)), matrix(fqL)] - UL = elem_type(OqL)[OqL(m; check = false) for m in UL] + UL = elem_type(OqL)[OqL(m; check = false) for m in ZZMatrix[-matrix(one(OqL)), matrix(fqL)]] return sub(OqL, unique!(UL)) - elseif is_definite(L) || rank(L) == 2 - # We can compute the orthogonal groups and centralizer with GAP - OL = orthogonal_group(L) - f = isometry(Lf) - V = ambient_space(L) - B = basis_matrix(L) - B2 = orthogonal_complement(V, B) - C = vcat(B, B2) - f = block_diagonal_matrix(QQMatrix[f, identity_matrix(QQ, nrows(B2))]) - f = inv(C)*f*C - f = OL(f; check = false) - UL = QQMatrix[matrix(s) for s in gens(centralizer(OL, f)[1])] - qL = discriminant_group(L) - UL = ZZMatrix[matrix(hom(qL, qL, elem_type(qL)[qL(lift(t)*g) for t in gens(qL)])) for g in UL] - unique!(UL) - OqL = orthogonal_group(qL) - UL = elem_type(OqL)[OqL(m; check = false) for m in UL] - return sub(OqL, UL) + elseif is_of_hermitian_type(Lf) + if is_definite(L) || rank(L) == 2 + # If L is definite or of rank 2 and we use the correspondance between + # O(L, f) and O(L, h) via the trace equivalence, where (L, h) is the + # hermitian structure on (L, f). Preferable choice than computing O(L, f) + # directly because the rank of the hermitian structure is a strict divisor + # of the rank of L (lower the rank is, the better!) + H, res = Hecke.hermitian_structure_with_transfer_data(integer_lattice(; gram = gram_matrix(L)), f) + @hassert :ZZLatWithIsom 1 is_definite(H) # Should always be the case since L is definite, even for infinite isometries + OH = isometry_group(H) # This is identified with O(L, f) using `res` + geneOH = gens(OH) + geneUL = QQMatrix[_transfer_isometry(res, matrix(g)) for g in geneOH] + @hassert :ZZLatWithIsom 1 all(g -> g*gram_matrix(Lf)*transpose(g) == gram_matrix(Lf), geneUL) + @hassert :ZZLatWithIsom 1 all(g -> g*f == f*g, geneUL) + UL = matrix_group(unique!(geneUL)) + disc = discriminant_representation(L, UL; check = false, ambient_representation = false) + return image(disc) + else + # If L is indefinite of rank >=3, then we use the hermitian version of + # Miranda-Morrison theory to compute the image of the centralizer f directly + # in the centralizer of D_f. + dets, j = Oscar._local_determinants_morphism(Lf) + _, jj = kernel(dets) + jj = compose(jj, j) + return image(jj) + end else - @req is_of_hermitian_type(Lf) "Not yet implemented for indefinite lattices with isometry which are not of hermitian type" - # indefinite of rank bigger or equal to 3 - # We use Hermitian Miranda-Morrison (see [BH23, Part 6]) - dets, j = Oscar._local_determinants_morphism(Lf) - _, jj = kernel(dets) - jj = compose(jj, j) - return image(jj) + # For this last case, we cut our lattice into two orthogonal parts and we + # proceed by induction by gluing the stabilizers. For now we do naively, + # and we split the "largest non trivial exponent of the isometry". + # + # TODO: make a "smart search", for instance for some particular stable + # kernel sublattices of small rank or for which rank(L) == euler_phi(n) + # + # We use the similar method as used for extending stabilizers along + # equivariant primitive extensions as seen in the function + # `admissible_equivariant_primitive_extensions`. + divs = typeof(chi)[a for (a, _) in factor(chi)] + sort!(divs; lt = (a, b) -> degree(a) <= degree(b)) + psi = divs[end] + + M = kernel_lattice(Lf, psi) + qM, fqM = discriminant_group(M) + GM, _ = image_centralizer_in_Oq(M) + + N = orthogonal_submodule(Lf, basis_matrix(M)) + qN, fqN = discriminant_group(N) + GN, _ = image_centralizer_in_Oq(N) + + phi, HMinqM, HNinqN = glue_map(L, lattice(M), lattice(N); check = false) + + # Since M and N are obtained by cutting some parts of `f \in O(L)`, the glue + # map should be equivariant! + @hassert :ZZLatWithIsom 1 is_invariant(fqM, HMinqM) + @hassert :ZZLatWithIsom 1 is_invariant(fqN, HNinqN) + @hassert :ZZLatWithIsom 1 matrix(compose(restrict_automorphism(fqM, HMinqM; check = false), phi)) == matrix(compose(phi, restrict_automorphism(fqN, HNinqN; check = false))) + + HM = domain(HMinqM) + OHM = orthogonal_group(HM) + + HN = domain(HNinqN) + OHN = orthogonal_group(HN) + + _, qMinD, qNinD, _, OqMinOD, OqNinOD = _sum_with_embeddings_orthogonal_groups(qM, qN) + HMinD = compose(HMinqM, qMinD) + HNinD = compose(HNinqN, qNinD) + + stabM, _ = stabilizer(GM, HMinqM) + stabN, _ = stabilizer(GN, HNinqN) + + actM = hom(stabM, OHM, elem_type(OHM)[OHM(restrict_automorphism(x, HMinqM; check = false)) for x in gens(stabM)]) + actN = hom(stabN, OHN, elem_type(OHN)[OHN(restrict_automorphism(x, HNinqN; check = false)) for x in gens(stabN)]) + + _, _, graph = _overlattice(phi, HMinD, HNinD, isometry(M), isometry(N); same_ambient = true) + disc, stab = _glue_stabilizers(phi, actM, actN, OqMinOD, OqNinOD, graph) + qL, fqL = discriminant_group(Lf) + OqL = orthogonal_group(qL) + phi = hom(qL, disc, TorQuadModuleElem[disc(lift(x)) for x in gens(qL)]) + @hassert :ZZLatWithIsom 1 is_isometry(phi) + @hassert :ZZLatWithIsom 1 qL == disc + + stab = sub(OqL, elem_type(OqL)[OqL(compose(phi, compose(g, inv(phi))); check = false) for g in stab]) + + @hassert :ZZLatWithIsom 1 fqL in stab[1] + return stab end end +function _transfer_isometry(res::AbstractSpaceRes, g::T) where T <: MatElem + E = base_ring(codomain(res)) + rk = rank(codomain(res)) + n = rank(domain(res)) + gQ = zero_matrix(QQ, n, n) + vQ = zero(gQ[1,:]) + for i in 1:n + vQ[i] = one(QQ) + vE = matrix(E, 1, rk, res(vec(collect(vQ)))) + gQ[i, :] = res\(vec(collect(vE*g))) + vQ[i] = zero(QQ) + end + return gQ +end + ############################################################################### # # Signatures @@ -1517,9 +1823,9 @@ end function _real_kernel_signatures(L::ZZLat, M::MatElem) C = base_ring(M) G = gram_matrix(L) - G = change_base_ring(C, G) + GC = change_base_ring(C, G) _, K = left_kernel(M) - diag = K*G*transpose(K) + diag = K*GC*transpose(K) diag = Hecke._gram_schmidt(diag, C)[1] diag = diagonal(diag) @@ -1536,11 +1842,11 @@ end @doc raw""" 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)$. +Given a lattice with isometry $(L, f)$ where the minimal polynomial of $f$ +is irreducible cyclotomic, return the signatures of the pair $(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$, +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})$. @@ -1575,9 +1881,10 @@ function signatures(Lf::ZZLatWithIsom) lambda = C(eig[j]) 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) + fC = change_base_ring(C, f) + ifC = inv(fC) for i in Sq - M = f + inv(f) - lambda^i - lambda^(-i) + M = fC + ifC - lambda^i - lambda^(-i) D[i] = _real_kernel_signatures(L, M) end return D @@ -1598,9 +1905,9 @@ end kernel_lattice(Lf::ZZLatWithIsom, p::Union{fmpz_poly, QQPolyRingElem}) -> ZZLatWithIsom -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}$. +$L$ with isometry $f$, together with the restriction $f_{\mid M}$. # Examples ```jldoctest @@ -1656,8 +1963,8 @@ kernel_lattice(Lf::ZZLatWithIsom, p::ZZPolyRingElem) = kernel_lattice(Lf, change @doc raw""" 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. +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 @@ -1697,7 +2004,7 @@ end 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 +$(L, f)$ together with the restriction of $f$ to $L^f$ (which is the identity in this case). # Examples @@ -1725,12 +2032,13 @@ invariant_lattice(Lf::ZZLatWithIsom) = kernel_lattice(Lf, 1) 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`. +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`. +If `ambient_representation` is set to `true`, the isometries in $G$ are considered +as matrix representation of their action on the standard basis of the ambient +space of $L$. Otherwise, they are considered as matrix representation of their +action on the basis matrix of $L$. # Examples ```jldoctest @@ -1752,10 +2060,10 @@ end 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$. +$(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$. +$L$ of the invariant lattice $L_f$. # Examples ```jldoctest @@ -1796,13 +2104,14 @@ end 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$. +Given an integer lattice $L$ and a group $G$ of isometries of $L$, 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`. +If `ambient_representation` is set to `true`, the isometries in $G$ and $H$ are +considered as matrix representation of their action on the standard basis of the +ambient space of $L$. Otherwise, they are considered as matrix representation of +their action on the basis matrices of $L$ and $L_G$ respectively. # Examples ```jldoctest @@ -1828,12 +2137,13 @@ function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::B B = basis_matrix(L) B2 = orthogonal_complement(V, B) B3 = vcat(B, B2) + iB3 = inv(B3) end gene = QQMatrix[] for g in gens(G) if !ambient_representation g_ambient = block_diagonal_matrix(QQMatrix[matrix(g), identity_matrix(QQ, nrows(B2))]) - g_ambient = inv(B3)*g_ambient*B3 + g_ambient = iB3*g_ambient*B3 m = solve_left(basis_matrix(C), basis_matrix(C)*g_ambient) push!(gene, m) else @@ -1843,19 +2153,34 @@ function coinvariant_lattice(L::ZZLat, G::MatrixGroup; ambient_representation::B return C, matrix_group(gene) end +@doc raw""" + invariant_coinvariant_pair(Lf::ZZLatWithIsom) -> ZZLatWithIsom, ZZLatWithIsom + +Given a lattice with isometry $(L, f)$, return the pair of lattices with +isometries consisting of $(L^f, f_{\mid L^f})$ and $(L_f, f_{\mid L_f})$, +the invariant and coinvariant sublattices with isometry of $(L, f)$. +""" +function invariant_coinvariant_pair(Lf::ZZLatWithIsom) + F = invariant_lattice(Lf) + B = basis_matrix(F) + C = orthogonal_submodule(Lf, B) + return F, C +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$. +Given an integer lattice $L$ and a group $G$ of isometries of $L$, 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$ on $L$. -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`. +If `ambient_representation` is set to `true`, the isometries in $G$ and $H$ are +considered as matrix representation of their action on the standard basis of the +ambient space of $L$. Otherwise, they are considered as matrix representation of +their action on the basis matrices of $L$ and $L_G$ respectively. # Examples ```jldoctest @@ -1887,12 +2212,13 @@ function invariant_coinvariant_pair(L::ZZLat, G::MatrixGroup; ambient_representa B = basis_matrix(L) B2 = orthogonal_complement(V, B) B3 = vcat(B, B2) + iB3 = inv(B3) end gene = QQMatrix[] for g in gens(G) if !ambient_representation g_ambient = block_diagonal_matrix(QQMatrix[matrix(g), identity_matrix(QQ, nrows(B2))]) - g_ambient = inv(B3)*g_ambient*B3 + g_ambient = iB3*g_ambient*B3 m = solve_left(basis_matrix(C), basis_matrix(C)*g_ambient) push!(gene, m) else @@ -1912,13 +2238,13 @@ end 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)$. +Given a lattice with isometry $(L, f)$ with $f$ of finite order $n$, return the +type of the pair $(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 +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 +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 @@ -1962,7 +2288,7 @@ end @doc raw""" is_of_type(Lf::ZZLatWithIsom, 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$. # Examples ```jldoctest @@ -2033,7 +2359,7 @@ end @doc raw""" is_hermitian(t::Dict) -> Bool -Given a type `t` of lattices with isometry, return whether `t` is hermitian, i.e. +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 @@ -2080,4 +2406,3 @@ function to_oscar(io::IO, Lf::ZZLatWithIsom) end to_oscar(Lf::ZZLatWithIsom) = to_oscar(stdout, Lf) - diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 21428b7138fd..14be54ec9613 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -8,7 +8,7 @@ @doc raw""" space(Vf::QuadSpaceWithIsom) -> QuadSpace -Given a quadratic space with isometry $(V, f)$, return the underlying space `V`. +Given a quadratic space with isometry $(V, f)$, return the underlying space $V$. # Examples ```jldoctest @@ -26,7 +26,7 @@ space(Vf::QuadSpaceWithIsom) = Vf.V isometry(Vf::QuadSpaceWithIsom) -> QQMatrix Given a quadratic space with isometry $(V, f)$, return the underlying isometry -`f`. +$f$. # Examples ```jldoctest @@ -45,7 +45,7 @@ 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 @@ -69,7 +69,7 @@ order_of_isometry(Vf::QuadSpaceWithIsom) = Vf.n rank(Vf::QuadSpaceWithIsom) -> Integer Given a quadratic space with isometry $(V, f)$, return the rank of the underlying -space `V`. +space $V$. # Examples ```jldoctest @@ -87,7 +87,7 @@ rank(Vf::QuadSpaceWithIsom) = rank(space(Vf)) 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$. # Examples ```jldoctest @@ -105,7 +105,7 @@ dim(Vf::QuadSpaceWithIsom) = dim(space(Vf)) 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$. # Examples ```jldoctest @@ -123,7 +123,7 @@ characteristic_polynomial(Vf::QuadSpaceWithIsom) = characteristic_polynomial(iso minimal_polynomial(Vf::QuadSpaceWithIsom) -> QQPolyRingElem Given a quadratic space with isometry $(V, f)$, return the minimal -polynomial of the underlying isometry `f`. +polynomial of the underlying isometry $f$. # Examples ```jldoctest @@ -141,7 +141,7 @@ minimal_polynomial(Vf) = minimal_polynomial(isometry(Vf)) 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. +of the underlying space $V$ with respect to its standard basis. # Examples ```jldoctest @@ -159,7 +159,7 @@ gram_matrix(Vf::QuadSpaceWithIsom) = gram_matrix(space(Vf)) det(Vf::QuadSpaceWithIsom) -> QQFieldElem Given a quadratic space with isometry $(V, f)$, return the determinant -of the underlying space `V`. +of the underlying space $V$. # Examples ```jldoctest @@ -177,7 +177,7 @@ det(Vf::QuadSpaceWithIsom) = det(space(Vf)) discriminant(Vf::QuadSpaceWithIsom) -> QQFieldElem Given a quadratic space with isometry $(V, f)$, return the discriminant -of the underlying space `V`. +of the underlying space $V$. # Examples ```jldoctest @@ -195,7 +195,7 @@ discriminant(Vf::QuadSpaceWithIsom) = discriminant(space(Vf)) is_positive_definite(Vf::QuadSpaceWithIsom) -> Bool Given a quadratic space with isometry $(V, f)$, return whether the underlying -space `V` is positive definite. +space $V$ is positive definite. # Examples ```jldoctest @@ -213,7 +213,7 @@ is_positive_definite(Vf::QuadSpaceWithIsom) = is_positive_definite(space(Vf)) is_negative_definite(Vf::QuadSpaceWithIsom) -> Bool Given a quadratic space with isometry $(V, f)$, return whether the underlying -space `V` is negative definite. +space $V$ is negative definite. # Examples ```jldoctest @@ -231,7 +231,7 @@ is_negative_definite(Vf::QuadSpaceWithIsom) = is_negative_definite(space(Vf)) is_definite(Vf::QuadSpaceWithIsom) -> Bool Given a quadratic space with isometry $(V, f)$, return whether the underlying -space `V` is definite. +space $V$ is definite. # Examples ```jldoctest @@ -249,7 +249,7 @@ is_definite(Vf::QuadSpaceWithIsom) = is_definite(space(Vf)) diagonal(Vf::QuadSpaceWithIsom) -> Vector{QQFieldElem} Given a quadratic space with isometry $(V, f)$, return the diagonal of the -underlying space `V`. +underlying space $V$. # Examples ```jldoctest @@ -269,7 +269,7 @@ diagonal(Vf::QuadSpaceWithIsom) = diagonal(space(Vf)) 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`. +tuple of the underlying space $V$. # Examples ```jldoctest @@ -293,8 +293,8 @@ signature_tuple(Vf::QuadSpaceWithIsom) = signature_tuple(space(Vf)) 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 +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 @@ -339,10 +339,10 @@ 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. +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`. +If `neg` is set to `true`, then the isometry $f$ is negative the identity on $V$. # Examples ```jldoctest @@ -378,7 +378,7 @@ end 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`. +$V^a$ is the same space as $V$ with the associated quadratic form rescaled by $a$. # Examples ```jldoctest @@ -462,8 +462,8 @@ end 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. +$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. @@ -545,8 +545,8 @@ direct_sum(x::Vararg{QuadSpaceWithIsom}) = direct_sum(collect(x)) 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. +$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. @@ -628,8 +628,8 @@ direct_product(x::Vararg{QuadSpaceWithIsom}) = direct_product(collect(x)) 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 +$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 diff --git a/experimental/QuadFormAndIsom/src/types.jl b/experimental/QuadFormAndIsom/src/types.jl index eeae424c0e39..11dcb84498bc 100644 --- a/experimental/QuadFormAndIsom/src/types.jl +++ b/experimental/QuadFormAndIsom/src/types.jl @@ -1,11 +1,11 @@ @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`. +A container type for pairs $(V, f)$ consisting of a 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. +We store the order of $f$ too, which can finite or infinite. To construct an object of type `QuadSpaceWithIsom`, see the set of functions called [`quadratic_space_with_isometry`](@ref) @@ -59,13 +59,13 @@ 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`. +A container type for pairs $(L, f)$ consisting of an integer lattice $L$ of +type `ZZLat` and an isometry $f$ given as a `QQMatrix` representing the action +on the basis matrix 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. +We store the ambient space $V$ of $L$ together with an isometry $f_a$ +inducing $f$ on $L$ seen as a pair $(V, f_a)$ 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: @@ -180,7 +180,7 @@ julia> Cf == Cf2 true ``` -The last equality of the last example shows why we care about "ambient context": +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 diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index bfe04fb2edad..6c39e86c48f5 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -8,6 +8,7 @@ set_verbosity_level(:ZZLatWithIsom, -1) function _show_details(io::IO, X::Union{ZZLatWithIsom, QuadSpaceWithIsom}) return show(io, MIME"text/plain"(), X) end + # Finite order L = root_lattice(:A, 2) Lf = integer_lattice_with_isometry(L) Vf = ambient_space(Lf) @@ -17,6 +18,17 @@ set_verbosity_level(:ZZLatWithIsom, -1) @test sprint(show, X) isa String @test sprint(show, X; context=:supercompact => true) isa String end + # Infinite order + L = integer_lattice(; gram = QQ[1 2; 2 1]) + f = QQ[4 -1; 1 0] + Lf = integer_lattice_with_isometry(L, f) + 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 @@ -77,6 +89,15 @@ end Lf = integer_lattice_with_isometry(L; neg = true) @test order_of_isometry(Lf) == -1 + F, C = invariant_coinvariant_pair(Lf) + @test rank(F) + rank(C) == rank(Lf) + @test order_of_isometry(C) == order_of_isometry(Lf) + @test is_one(isometry(F)) + + @test is_primary_with_prime(integer_lattice_with_isometry(root_lattice(:E, 6)))[1] + @test is_elementary_with_prime(integer_lattice_with_isometry(root_lattice(:E, 7)))[1] + @test is_unimodular(integer_lattice_with_isometry(hyperbolic_plane_lattice())) + isdefined(Main, :test_save_load_roundtrip) || include( joinpath(Oscar.oscardir, "test", "Serialization", "test_save_load_roundtrip.jl") ) @@ -88,6 +109,8 @@ end end L = @inferred integer_lattice_with_isometry(A3) + @test is_primary(L, 2) + @test !is_elementary(L, 2) @test length(unique([L, L, L])) == 1 @test ambient_space(L) isa QuadSpaceWithIsom @test isone(isometry(L)) @@ -195,9 +218,15 @@ 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]) + C, _ = coinvariant_lattice(A3, sub(OA3, elem_type(OA3)[OA3(agg[2]), OA3(agg[4])])[1]; ambient_representation = false) @test is_sublattice(A3, C) + B = matrix(QQ, 3, 3, [1 0 0; 0 1 0; 0 0 1]); + G = matrix(QQ, 3, 3, [2 -1 0; -1 2 0; 0 0 -4]); + L = integer_lattice(B, gram = G); + f = matrix(QQ, 3, 3, [0 -1 0; 1 1 0; 0 0 1]); + Lf = integer_lattice_with_isometry(L, f); + @test is_bijective(image_centralizer_in_Oq(Lf)[2]) end @testset "Enumeration of lattices with finite isometries" begin @@ -284,4 +313,7 @@ end reps = @inferred admissible_equivariant_primitive_extensions(F, C, Lf^0, 5) @test length(reps) == 1 @test is_of_same_type(Lf, reps[1]) + reps = @inferred primitive_extensions(lattice(F), lattice(C); q = discriminant_group(L), classification = :emb) + @test length(reps) == 1 + @test reps[1] == (L, lattice(F), lattice(C)) end