diff --git a/experimental/QuadFormAndIsom/README.md b/experimental/QuadFormAndIsom/README.md index f8c0db4399e6..d5b293719d42 100644 --- a/experimental/QuadFormAndIsom/README.md +++ b/experimental/QuadFormAndIsom/README.md @@ -25,7 +25,7 @@ of finite order with at most two prime divisors. The methods we resort to for this purpose are developed in the paper [BH23](@cite). We also provide some algorithms computing isomorphism classes of primitive -embeddings of even lattices following Nikulin's theory. More precisely, the +embeddings of integral lattices following Nikulin's theory. More precisely, the function `primitive_embeddings` offers, under certain conditions, the possibility to compute representatives of primitive embeddings and classify them in different ways. Note nonetheless that these functions are not efficient @@ -33,12 +33,13 @@ in the case were the discriminant groups have a large number of subgroups. ## Status -This project has been slightly tested on simple and known examples. It is -currently being tested on a larger scale to test its reliability. Moreover, -there are still computational bottlenecks due to non-optimized algorithms. +Currently, the project features the following: -Among the possible improvements and extensions: -* Implement extra methods for lattices with isometries of infinite order; +- enumeration of conjugacy classes of isometries of finite order for even lattices (in the case of at most 2 prime divisors); +- enumeration of conjugacy classes of isometries with irreducible and reciprocal minimal polynomial for integral lattices (with maximal equation order); +- primitive embeddings/extensions for integral lattices; +- equivariant primitive extensions for integral lattices; +- miscellaneous operations on integral/rational quadratic form endowed with an isometry. ## Current applications of this project diff --git a/experimental/QuadFormAndIsom/docs/src/enumeration.md b/experimental/QuadFormAndIsom/docs/src/enumeration.md index 06fa5245e73a..8f7bff774581 100644 --- a/experimental/QuadFormAndIsom/docs/src/enumeration.md +++ b/experimental/QuadFormAndIsom/docs/src/enumeration.md @@ -4,7 +4,7 @@ CurrentModule = Oscar # Enumeration of isometries -One of the main features of this project is the enumeration of lattices with +One of the main features of this project is the enumeration of even lattices with isometry of finite order with at most two prime divisors. This is the content of [BH23](@cite) which has been implemented. We guide the user here to the global aspects of the available theory, and we refer to the paper [BH23](@cite) for further @@ -48,7 +48,25 @@ isometries of integral integer lattices. For more details such as the proof of the algorithms and the theory behind them, we refer to the reference paper [BH23](@cite). -### Global function +### The hermitian case + +For an irreducible reciprocal polynomial $\chi$ and a genus symbol $G$ +of integral integer lattices, if the equation order $\mathbb{Z}[\chi]$ is maximal, +one can compute representatives of isomorphism classes of lattices with isometry +$(L, f)$ such that $L\in G$ and $\chi(f) = 0$. + +```@docs +representatives_of_hermitian_type(::Union{ZZLat, ZZGenus}, ::Union{ZZPolyRingElem, QQPolyRingElem}, ::Bool) +``` + +In the case of finite order isometries, when $\chi$ is cyclotomic, one can use +as a shortcut the following function instead: + +```@docs +representatives_of_hermitian_type(::Union{ZZGenus, ZZLat}, ::Int, ::Bool) +``` + +### Orders with two prime divisors As we will see later, the algorithms from [BH23](@cite) are specialized on the requirement for the input and regular users might not be able to choose between @@ -88,3 +106,4 @@ embeddings and their equivariant version. We use this basis to introduce the 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 812fd913f97e..fc96d4cc147b 100644 --- a/experimental/QuadFormAndIsom/docs/src/introduction.md +++ b/experimental/QuadFormAndIsom/docs/src/introduction.md @@ -25,7 +25,7 @@ of finite order with at most two prime divisors. The methods we resort to for this purpose are developed in the paper [BH23](@cite). We also provide some algorithms computing isomorphism classes of primitive -embeddings of even lattices following Nikulin's theory. More precisely, the +embeddings of integral lattices following Nikulin's theory. More precisely, the 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 @@ -33,12 +33,13 @@ in the case were the discriminant groups have a large number of subgroups. ## Status -This project has been slightly tested on simple and known examples. It is -currently being tested on a larger scale to test its reliability. Moreover, -there are still computational bottlenecks due to non-optimized algorithms. +Currently, the project features the following: -Among the possible improvements and extensions: -* Implement extra methods for lattices with isometries of infinite order; +- enumeration of conjugacy classes of isometries of finite order for even lattices (in the case of at most 2 prime divisors); +- enumeration of conjugacy classes of isometries with irreducible and reciprocal minimal polynomial for integral lattices (with maximal equation order); +- primitive embeddings/extensions for integral lattices; +- equivariant primitive extensions for integral lattices; +- miscellaneous operations on integral/rational quadratic form endowed with an isometry. ## Current applications of this project diff --git a/experimental/QuadFormAndIsom/docs/src/latwithisom.md b/experimental/QuadFormAndIsom/docs/src/latwithisom.md index 502158794e11..7fb9347224c8 100644 --- a/experimental/QuadFormAndIsom/docs/src/latwithisom.md +++ b/experimental/QuadFormAndIsom/docs/src/latwithisom.md @@ -15,7 +15,7 @@ ZZLatWithIsom ``` 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$ +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)$. @@ -115,8 +115,7 @@ rescale(::ZZLatWithIsom, ::RationalUnion) ## Type for finite order isometries Given a lattice with isometry $Lf := (L, f)$ where $f$ is of finite order $n$, -one can compute the *type* of $Lf$, which can be seen as an equivalent of the -*genus* used to classified single lattices. +one can compute the *type* of $Lf$. ```@docs type(::ZZLatWithIsom) @@ -183,6 +182,8 @@ project and it can be indirectly used through the general following method: image_centralizer_in_Oq(::ZZLatWithIsom) ``` +Note: hermitian Miranda-Morrison is only available for even lattices. + 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. diff --git a/experimental/QuadFormAndIsom/docs/src/primembed.md b/experimental/QuadFormAndIsom/docs/src/primembed.md index e14c4a00755d..3bf01561bdde 100644 --- a/experimental/QuadFormAndIsom/docs/src/primembed.md +++ b/experimental/QuadFormAndIsom/docs/src/primembed.md @@ -25,11 +25,13 @@ 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 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. +isomorphic embeddings or as isomorphic sublattices. Moreover, with enough care, +one can generalize the previous results for embeddings in odd lattices. -Such a method can be algorithmically implemented, however it tends to be slow -and inefficient in general for large rank or determinant. But, in the case -where the discriminant groups are (elementary) $p$-groups, the method can be +A general method to compute primitive embeddings between integral lattices +can be algorithmically implemented, however it tends to be slow and inefficient +in general for large rank or determinant. But, in the case where the +discriminant groups are (elementary) $p$-groups, the method can be made more efficient. We provide 4 kinds of output: @@ -48,16 +50,15 @@ first input: ```@docs primitive_embeddings(::ZZGenus, ::ZZLat) -primitive_embeddings(::TorQuadModule, ::Tuple{Int, Int}, -::ZZLat) +primitive_embeddings(::TorQuadModule, ::Tuple{Int, Int}, ::ZZLat) ``` 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 +Note: for the implementation of the algorithm, we construct a lattice $T$ which +is unique in its genus, such that $D_T$ and $D_M(-1)$ are isometric 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): @@ -91,13 +92,12 @@ equivariant_primitive_extensions(::Union{ZZLatWithIsom, ZZLat}, ::Union{ZZLatWit ## Admissible equivariant primitive extensions 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 +a triple of even 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)\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, ::IntegerUnion, ::IntegerUnion) ``` - diff --git a/experimental/QuadFormAndIsom/src/embeddings.jl b/experimental/QuadFormAndIsom/src/embeddings.jl index 98bd143fde62..2f6b81d8d3d8 100644 --- a/experimental/QuadFormAndIsom/src/embeddings.jl +++ b/experimental/QuadFormAndIsom/src/embeddings.jl @@ -390,8 +390,8 @@ function _fitting_isometries(OqfN::AutomorphismGroup{TorQuadModule}, push!(orb_and_rep, (p, orbit(m, p))) end end + reporb = QQMatrix[solve(basis_matrix(N), basis_matrix(N)*matrix(a[1]); side=:left) for a in orb_and_rep] end - reporb = QQMatrix[solve(basis_matrix(N), basis_matrix(N)*matrix(a[1]); side = :left) for a in orb_and_rep] return reporb end @@ -646,8 +646,9 @@ function _primitive_extensions_generic( if elM || elN || (ok && ek == 1) # We look for a glue kernel which is an elementary p-group - _, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), max(pM, pN, pk)) - subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(VMinqM, GM, k, fqM) + _p = max(pM, pN, pk) + _, VMinqM = _get_V(id_hom(qM), minimal_polynomial(identity_matrix(QQ, 1)), _p) + subsM = _subgroups_orbit_representatives_and_stabilizers_elementary(VMinqM, GM, k, _p, fqM) elseif prM || prN || ok # We look for a glue kernel which is a p-group _, VMinqM = primary_part(qM, max(pM, pN, pk)) @@ -847,45 +848,30 @@ end function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQuadModuleMap, G::AutomorphismGroup{TorQuadModule}, ord::IntegerUnion, + _p::IntegerUnion, f::Union{TorQuadModuleMap, AutomorphismGroupElem{TorQuadModule}} = id_hom(codomain(Vinq)), l::IntegerUnion = -1) res = Tuple{TorQuadModuleMap, AutomorphismGroup{TorQuadModule}}[] + p = ZZ(_p) + V = domain(Vinq) - # If V is trivial, then we ignore f and l, we just need to ensure that the - # order wanted is also 1 - if order(V) == 1 - ord != 1 && (return res) - push!(res, (Vinq, G)) + if ord > order(V) return res end q = codomain(Vinq) - p = elementary_divisors(V)[1] pq, pqtoq = primary_part(q, p) l = l < 0 ? valuation(order(pq), p) : l g = valuation(ord, p) - # some other trivial cases: if ord is 1, then l should be null (-1 by default) - # Otherwise, if ord == order(V), since V is preserved by f and contained the - # good subgroup of q, we just return V - if ord == 1 - l < valuation(order(pq), p) && (return res) - _, triv = sub(codomain(Vinq), TorQuadModuleElem[]) - push!(res, (triv, G)) - return res - elseif ord == order(V) - push!(res, (Vinq, G)) - return res - end - # In theory, V should contain H0 := p^l*pq where pq is the p-primary part of q all(a -> has_preimage_with_preimage(Vinq, (p^l)*pqtoq(a))[1], gens(pq)) || return res H0, H0inq = sub(q, elem_type(q)[q(lift((p^l)*a)) for a in gens(pq)]) @hassert :ZZLatWithIsom 1 is_invariant(f, H0inq) - # H0 should be contained in the group we want. So either H0 is the only one + # H0 should be contained in the groups we want. So either H0 is the only one # and we return it, or if order(H0) > ord, there are no subgroups as wanted if order(H0) >= ord order(H0) > ord && return res @@ -893,6 +879,15 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu return res end + # Now the groups we look for should strictly contain H0. + # If ord == order(V), then there is only V satisfying the given + # conditions, and V is stabilized by the all G + if ord == order(V) + push!(res, (Vinq, G)) + return res + end + + # Now the groups we look for are strictly contained between H0 and V H0inV = hom(H0, V, elem_type(V)[V(lift(a)) for a in gens(H0)]) @hassert :ZZLatWithIsom 1 is_injective(H0inV) @@ -908,7 +903,6 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu # We descend G to V for computing stabilizers later on GV, GtoGV = restrict_automorphism_group(G, Vinq; check = false) - satV, j = kernel(GtoGV) # Automorphisms in G preserved V and H0, since the construction of H0 is # natural. Therefore, the action of G descends to the quotient and we look for @@ -916,10 +910,11 @@ function _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq::TorQu # generators and putting them with H0 will give us invariant subgroups as # wanted) act_GV = dense_matrix_type(elem_type(base_ring(Qp)))[change_base_ring(base_ring(Qp), matrix(gg)) for gg in gens(GV)] - act_GV = dense_matrix_type(elem_type(base_ring(Qp)))[solve(VptoQp.matrix, g*VptoQp.matrix) for g in act_GV] + act_GV = dense_matrix_type(elem_type(base_ring(Qp)))[solve(VptoQp.matrix, g*VptoQp.matrix; side=:right) for g in act_GV] MGp = matrix_group(base_ring(Qp), dim(Qp), act_GV) GVtoMGp = hom(GV, MGp, MGp.(act_GV); check = false) GtoMGp = compose(GtoGV, GVtoMGp) + satV, _ = kernel(GtoMGp) g-ngens(snf(abelian_group(H0))[1]) >= dim(Qp) && return res @@ -983,7 +978,7 @@ function _classes_isomorphic_subgroups(q::TorQuadModule, 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) + sors = _subgroups_orbit_representatives_and_stabilizers_elementary(Vinq, O, ordH, p, f) else _, Vinq = primary_part(q, p) sors = _subgroups_orbit_representatives_and_stabilizers(Vinq, O, ordH, f) @@ -1028,7 +1023,7 @@ function _classes_isomorphic_subgroups(q::TorQuadModule, fqp = restrict_endomorphism(f, qpinq; check = false) if ordHp == p || (!isnothing(H) && is_elementary(T, p)) _, j = _get_V(id_hom(qp), minimal_polynomial(identity_matrix(QQ, 1)), p) - sors = _subgroups_orbit_representatives_and_stabilizers_elementary(j, Oqp, ordHp, fqp) + sors = _subgroups_orbit_representatives_and_stabilizers_elementary(j, Oqp, ordHp, p, fqp) else sors = _subgroups_orbit_representatives_and_stabilizers(id_hom(qp), Oqp, ordHp, fqp) end @@ -1306,13 +1301,8 @@ function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :su # In the non-even case, we need to consider several cases, i.e double odd, # even-odd or odd-even. # - # If M is even, then the complement can be even with discriminant form being - # qM(-1), or odd with discriminant form being bM(-1) - # - # If M is odd, it is more tricky: either the complement is odd with form bM(-1) - # or it is even with bM(-1) too. But in the latter case, their might be two - # possibilities for the quadratic form: we might need to add 1 to each - # diagonal entries of the gram matrix of the bilinear form bM before rescaling + # The complement can be even with discriminant form being + # qM(-1), or odd with discriminant form being bM(-1). # # Then for each possible form, we check which one defines a genus with the # given signature pair and then we do our extension routine. @@ -1355,9 +1345,6 @@ function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :su return (length(results) > 0), results end - if cs != :none && cs != :first - cs = :subsub - 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 @@ -1373,8 +1360,8 @@ function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :su end # 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) + # embedding such extensions in a big unimodular lattice (which we take + # unique in its genus) _, Vs = primitive_extensions(M, T; even, classification = cs) # GL is our big unimodular genus where we embed each of the V in Vs @@ -1387,9 +1374,60 @@ function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :su end # 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) - !okV && continue - classification == :none && return okV, results + resV = Tuple{ZZLat, ZZLat, ZZLat}[] + if rank(V) == rank(GL) + genus(V) != genus(GL) && continue + push!(resV, (V, V, orthogonal_submodule(V, V))) + continue + end + + # We need to classify the primitive embeddings of V in GL up to the actions + # of O(T) and O(M) (for sublattices; otherwise only up to O(T)) + # + # For this, we need the representation of the subgroup of isometries of V + # which preserves the primitive extension M\oplus T \subseteq V. This + # corresponds to the diagonal in \bar{O(T)}\times GM where GM is trivial + # for embedding classification, \bar{O(M)} otherwise. + # + # We use `_glue_stabilizers` which has been designed especially to compute + # such diagonal subgroup. + GV, _ = _glue_stabilizers(V, M2, T2) + qV = domain(GV) + qK = rescale(qV, -1) # Bilinear form of a complement of V in GL + + GKs = ZZGenus[] + posK = signature_tuple(GL)[1] - signature_tuple(V)[1] + negK = signature_tuple(GL)[3] - signature_tuple(V)[3] + if even + _G = try genus(qK, (posK, negK)) + catch + nothing + end + !isnothing(_G) && push!(GKs, _G) + else + _Ge = try genus(qK, (posK, negK); parity=2) + catch + nothing + end + !isnothing(_Ge) && push!(GKs, _Ge) + _Go = try genus(qK, (posK, negK); parity=1) + catch + nothing + end + !isnothing(_Go) && push!(GKs, _Go) + end + is_empty(GKs) && continue + unique!(GKs) + orths = reduce(vcat, Vector{ZZLat}[representatives(_GK) for _GK in GKs]) + for K in orths + GK, _ = image_in_Oq(K) + ok, pe = _primitive_extensions_generic(V, K, GV, GK, (:plain, :plain); even, exist_only=(classification == :none), first=(classification == :first), q=discriminant_group(GL)) + !ok && continue + classification == :none && true, results + !isempty(pe) && append!(resV, Tuple{ZZLat, ZZLat, ZZLat}[lattice.(t) for t in pe]) + end + isempty(resV) && 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)))) @@ -1405,7 +1443,7 @@ function primitive_embeddings(G::ZZGenus, M::ZZLat; classification::Symbol = :su 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. + # necessary information about the embedding. bM = solve(basis_matrix(L), basis_matrix(M3); side = :left) bN = solve(basis_matrix(L), basis_matrix(N); side = :left) L = integer_lattice(; gram = gram_matrix(L)) @@ -1714,10 +1752,10 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, # This is done by computing orbits and stabilizers of VA/lpqA (resp VB/lpqB) # seen as a F_p-vector space under the action of GA (resp. GB). Then we check which ones # are fA-stable (resp. fB-stable) - subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, fqA, ZZ(l)) + subsA = _subgroups_orbit_representatives_and_stabilizers_elementary(VAinqA, GA, p^g, p, fqA, ZZ(l)) is_empty(subsA) && return results - subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, fqB, ZZ(l)) + subsB = _subgroups_orbit_representatives_and_stabilizers_elementary(VBinqB, GB, p^g, p, fqB, ZZ(l)) is_empty(subsB) && return results # now, for each pair of anti-isometric potential kernels, we need to massage the gluing @@ -1787,7 +1825,7 @@ function admissible_equivariant_primitive_extensions(A::ZZLatWithIsom, stabSB, _ = intersect(center, imB) iso = isomorphism(PermGroup, center) - reps = double_cosets(codomain(iso), iso(stabSB)[1], iso(stabSAphi)[1]) + reps = double_cosets(codomain(iso), iso(stabSAphi)[1], iso(stabSB)[1]) # We iterate over all double cosets. Each representative, define a new # classe of admissible gluing and so, for each such representative we compute the @@ -2000,3 +2038,49 @@ function _glue_stabilizers(phi::TorQuadModuleMap, unique!(stab) return disc, stab end + +function _glue_stabilizers(L::ZZLatWithIsom, M::ZZLatWithIsom, N::ZZLatWithIsom; subM::Bool = true, subN::Bool = true) + qM, fqM = discriminant_group(M) + GM = subM ? image_centralizer_in_Oq(M)[1] : Oscar._orthogonal_group(qM, ZZMatrix[matrix(id_hom(qM))]; check=false) + + qN, fqN = discriminant_group(N) + GN = subN ? image_centralizer_in_Oq(N)[1] : Oscar._orthogonal_group(qN, ZZMatrix[matrix(id_hom(qN))]; check=false) + + phi, HMinqM, HNinqN = glue_map(lattice(L), lattice(M), lattice(N); 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(L) + OqL = orthogonal_group(qL) + + psi = hom(qL, disc, TorQuadModuleElem[disc(lift(x)) for x in gens(qL)]) + @hassert :ZZLatWithIsom 1 is_isometry(psi) + @hassert :ZZLatWithIsom 1 qL == disc + + ipsi = inv(psi) + stab = sub(OqL, elem_type(OqL)[OqL(compose(psi, compose(g, ipsi)); check=false) for g in _stab]) + @hassert :ZZLatWithIsom 1 fqL in stab[1] + return stab +end + +function _glue_stabilizers(L::ZZLat, M::ZZLat, N::ZZLat; subM::Bool = true, subN::Bool = true) + Lf = integer_lattice_with_isometry(L) + Mg = integer_lattice_with_isometry(M) + Nh = integer_lattice_with_isometry(N) + return _glue_stabilizers(Lf, Mg, Nh; subM, subN) +end diff --git a/experimental/QuadFormAndIsom/src/enumeration.jl b/experimental/QuadFormAndIsom/src/enumeration.jl index d459133cfde7..46cb33dd80eb 100644 --- a/experimental/QuadFormAndIsom/src/enumeration.jl +++ b/experimental/QuadFormAndIsom/src/enumeration.jl @@ -366,63 +366,64 @@ function _ideals_of_norm(E::Field, d::ZZRingElem) end end for I in Hecke.cartesian_product_iterator(primes) - I = prod(I) - if absolute_norm(I) == d - push!(ids, fractional_ideal(OE, I)) + if prod(absolute_norm.(I)) != d + continue end + I = prod(I) + @hassert :ZZLatWithIsom 1 absolute_norm(I) == d + push!(ids, fractional_ideal(OE, I)) end return ids end -# given a cyclotomic field (as cm extension) E/K, return all +# Given a degree 2 extension of number fields E/K, return all # the possible signatures dictionaries of any hermitian lattice over -# E/K of rank rk, whose trace lattice has signature (s1, s2). -# if `fix_root`, we do not consider permutations of a set of signatures since -# any permutation correspond to a change of a choice of a primitive root of -# unity. +# E/K of rank rk, and whose trace lattice has negative signature s2. +# In the cyclotomic case, if `fix_root = true`, we do not consider +# permutations of a set of signatures since any permutation correspond +# to a change of a choice of a primitive root of unity. -function _possible_signatures(s1::IntegerUnion, s2::IntegerUnion, E::Field, rk::IntegerUnion, fix_root::Bool = false) - @hassert :ZZLatWithIsom 1 E isa Hecke.RelSimpleNumField - ok, q = Hecke.is_cyclotomic_type(E) - @hassert :ZZLatWithIsom 1 ok - @hassert :ZZLatWithIsom 1 iseven(s2) - @hassert :ZZLatWithIsom 1 divides(2*(s1+s2), euler_phi(q))[1] - l = divexact(s2, 2) +function _possible_signatures(s2::IntegerUnion, E::Field, rk::IntegerUnion, fix_root::Bool = false) + lb = iseven(s2) ? 0 : 1 K = base_field(E) - inf = real_places(K) + inf = Hecke.place_type(K)[p for p in real_places(K) if length(extend(p, E)) == 1] + r = length(real_places(K)) - length(inf) s = length(inf) - signs = Dict{typeof(inf[1]), Int}[] - parts = Vector{Int}[] + signs = Dict{Hecke.place_type(K), Int}[] if !fix_root perm = AllPerms(s) end - for v in AllParts(l) - if any(i -> i > rk, v) - continue - end - if length(v) > s - continue - end - while length(v) != s - push!(v, 0) - end - push!(parts, copy(v)) - if !fix_root - for vv in perm - v2 = v[vv.d] - v2 in parts ? continue : push!(parts, v2) + for l in lb:2:min(s2, rk*r) + parts = Vector{Int}[] + l = divexact(s2-l, 2) + for v in AllParts(l) + if any(i -> i > rk, v) + continue + end + if length(v) > s + continue + end + while length(v) != s + push!(v, 0) + end + push!(parts, copy(v)) + if !fix_root + for vv in perm + v2 = v[vv.d] + v2 in parts ? continue : push!(parts, v2) + end end end - end - for v in parts - push!(signs, Dict(a => b for (a,b) in zip(inf, v))) + for v in parts + push!(signs, Dict(a => b for (a,b) in zip(inf, v))) + end end return signs end @doc raw""" representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1) - -> Vector{ZZLatWithIsom} + -> Vector{ZZLatWithIsom} 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$, @@ -452,115 +453,146 @@ function representatives_of_hermitian_type(Lf::ZZLatWithIsom, m::Int = 1, fix_ro @req m >= 1 "m must be a positive integer" @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" - rk = rank(Lf) - d = det(Lf) n = order_of_isometry(Lf) @req is_finite(n) "Isometry must be of finite order" - s1, _, s2 = signature_tuple(Lf) + reps = representatives_of_hermitian_type(genus(Lf), cyclotomic_polynomial(n*m), fix_root) + filter!(M -> is_of_same_type(M^m, Lf), reps) + return reps +end + +@doc raw""" + representatives_of_hermitian_type(G::ZZGenus, m::Int) + representatives_of_hermitian_type(L::ZZLat, m::Int) + -> Vector{ZZLatWithIsom} + +Given a non-empty genus of integer lattices $G$, return a list of +representatives of isomorphism classes of pairs $(M, g)$ consisting of a lattice +$M$ in $G$ and $g \in O(M)$ is an isometry of minimal polynomial $\Phi_m(X)$, +the $m-$th cyclotomic polynomial. + +If $m = 1,2$, this goes back to enumerate $G$ as a genus of integer lattices. + +One can also provide a representative $L$ of $G$ instead. +""" +representatives_of_hermitian_type(::Union{ZZGenus, ZZLat}, ::Int, ::Bool) + +representatives_of_hermitian_type(G::ZZGenus, m::Int, fix_root::Bool = false) = representatives_of_hermitian_type(G, cyclotomic_polynomial(m), fix_root) + +representatives_of_hermitian_type(L::ZZLat, m::Int, fix_root::Bool = false) = representatives_of_hermitian_type(genus(L), cyclotomic_polynomial(m), fix_root) + +@doc raw""" + representatives_of_hermitian_type(G::ZZGenus, chi::Union{ZZPolyRingElem, QQPolyRingElem}) + representatives_of_hermitian_type(L::ZZLat, chi::Union{ZZPolyRingElem, QQPolyRingElem}) + -> Vector{ZZLatWithIsom} + +Given a non-empty genus of integer lattices $G$ and a polynomial $chi$ irreducible +over $\mathbb Q$, such that the equation order of the associated number field is +maximal, return a list of representatives of isomorphism classes of pairs $(M, g)$ +consiting of a lattice $M$ in $G$ and $g \in O(M)$ is an isometry of minimal polynomial +$chi$. + +One can also provide a representative $L$ of $G$ instead. +""" +representatives_of_hermitian_type(::Union{ZZLat, ZZGenus}, ::Union{ZZPolyRingElem, QQPolyRingElem}, ::Bool) + +function representatives_of_hermitian_type(G::ZZGenus, chi::Union{ZZPolyRingElem, QQPolyRingElem}, fix_root::Bool = false) + @req is_irreducible(chi) "Polynomial must be irreducible" + @req is_integral(G) "For now G must be a genus symbol for integral lattices" + + rk = rank(G) + d = abs(det(G)) + s1, _, s2 = signature_tuple(G) reps = ZZLatWithIsom[] - nm = n*m + rank(G) == 0 && return ZZLatWithIsom[integer_lattice_with_isometry(integer_lattice(; gram=matrix(QQ, 0, 0, QQFieldElem[])))] - if nm < 3 + if degree(chi) == 1 + @hassert :ZZLatWithIsom 1 iszero(chi(1)*chi(-1)) @vprintln :ZZLatWithIsom 1 "Order smaller than 3" - f = (-1)^(nm+1)*identity_matrix(QQ, rk) - G = genus(Lf) + f = iszero(chi(1)) ? identity_matrix(QQ, rk) : -identity_matrix(QQ, rk) repre = representatives(G) @vprintln :ZZLatWithIsom 1 "$(length(repre)) representative(s)" while !is_empty(repre) LL = pop!(repre) - is_of_same_type(integer_lattice_with_isometry(LL, f^m; check = false), Lf) && push!(reps, integer_lattice_with_isometry(LL, f; check = false)) + push!(reps, integer_lattice_with_isometry(LL, f; ambient_representation=false, check=false)) end return reps end - !iseven(s2) && return reps + !iseven(degree(chi)) && return reps @vprintln :ZZLatWithIsom 1 "Order bigger than 3" - ok, rk = divides(rk, euler_phi(nm)) + ok, rk = divides(rk, degree(chi)) ok || return reps - E, b = cyclotomic_field_as_cm_extension(nm) + R = parent(chi) + list_cyc = Int[k for k in euler_phi_inv(degree(chi))] + j = findfirst(k -> chi == cyclotomic_polynomial(k, R), list_cyc) + if !isnothing(j) + E, b = cyclotomic_field_as_cm_extension(list_cyc[j]) + else + Etemp, btemp = number_field(chi; cached=false) + @req is_maximal(equation_order(Etemp)) "For infinite isometries, the equation order of the associated number field must be maximal" + K, a = number_field(minpoly(btemp + inv(btemp)), "a"; cached=false) + Kt, t = K["t"] + E, b = number_field(t^2-a*t+1, "b"; cached=false) + end + gene = Hecke.genus_herm_type(E)[] - Eabs, EabstoE = absolute_simple_field(E) - DE = EabstoE(different(maximal_order(Eabs))) + DEK = different(maximal_order(E)) + DK = different(base_ring(maximal_order(E))) + DE = DK*maximal_order(E)*DEK - @vprintln :ZZLatWithIsom 1 "We have the different" + @vprintln :ZZLatWithIsom 1 "We have the differents" ndE = d*inv(QQ(absolute_norm(DE)))^rk detE = _ideals_of_norm(E, ndE) - + isempty(detE) && return reps @vprintln :ZZLatWithIsom 1 "All possible ideal dets: $(length(detE))" - signatures = _possible_signatures(s1, s2, E, rk, fix_root) - + signatures = _possible_signatures(s2, E, rk, fix_root) + isempty(signatures) && return reps @vprintln :ZZLatWithIsom 1 "All possible signatures: $(length(signatures))" + for dd in detE, sign in signatures - append!(gene, hermitian_genera(E, rk, sign, dd; min_scale = inv(DE), max_scale = numerator(dd)*DE)) + append!(gene, hermitian_genera(E, rk, sign, dd; min_scale=inv(DE), max_scale=numerator(dd)*DE)) end unique!(gene) @vprintln :ZZLatWithIsom 1 "All possible genera: $(length(gene))" for g in gene - @vprintln :ZZLatWithIsom 1 "g = $g" - H = representative(g) - if !is_integral(DE*scale(H)) + if is_integral(G) && !is_integral(DE*scale(g)) continue end - if is_even(Lf) && !is_integral(different(fixed_ring(H))*norm(H)) + if is_even(G) && !is_integral(DK*norm(g)) continue end - @vprintln :ZZLatWithIsom 1 "$H" + @v_do :ZZLatWithIsom 3 Base.show(stdout, MIME"text/plain"(), g) + @vprintln :ZZLatWithIsom 1 "" + + H = representative(g) M, fM = trace_lattice_with_isometry(H) - det(M) == d || continue - MfM = integer_lattice_with_isometry(M, fM; check = false) + genus(M) != G && continue + + MfM = integer_lattice_with_isometry(M, fM; check=false) @hassert :ZZLatWithIsom 1 is_of_hermitian_type(MfM) - @hassert :ZZLatWithIsom 1 order_of_isometry(MfM) == nm - if is_even(M) != is_even(Lf) - continue - end - if !is_of_same_type(MfM^m, Lf) - continue - end + gr = genus_representatives(H) for HH in gr M, fM = trace_lattice_with_isometry(HH) - push!(reps, integer_lattice_with_isometry(M, fM; check = false)) + push!(reps, integer_lattice_with_isometry(M, fM; check=false)) end end return reps end -@doc raw""" - representatives_of_hermitian_type(G::ZZGenus, m::Int) -> Vector{ZZLatWithIsom} - representatives_of_hermitian_type(L::ZZLat, m::Int) -> Vector{ZZLatWithIsom} - -Given an non-empty genus of integer lattices $G$, return a list of -representatives of isomorphic classes of pairs $(L, f)$ consisting of a lattice -$L$ in $G$ and $f \in O(L)$ is an isometry of minimal polynomial $\Phi_m(X)$, -the $m-$th cyclotomic polynomial. - -If $m = 1,2$, this goes back to enumerate $G$ as a genus of integer lattices. - -One can also provide directly a representative $L$ of $G$ instead. -""" -representatives_of_hermitian_type(::Union{ZZGenus, ZZLat}, ::Int) - -function representatives_of_hermitian_type(G::ZZGenus, m::Int, fix_root::Bool = false) - Lf = integer_lattice_with_isometry(representative(G)) - return representatives_of_hermitian_type(Lf, m, fix_root) -end - -function representatives_of_hermitian_type(L::ZZLat, m::Int, fix_root::Bool = false) - Lf = integer_lattice_with_isometry(L) - return representatives_of_hermitian_type(Lf, m, fix_root) -end +representatives_of_hermitian_type(L::ZZLat, chi::Union{ZZPolyRingElem, QQPolyRingElem}, fix_root::Bool = false) = representatives_of_hermitian_type(genus(L), chi, fix_root) @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$ +Given an even 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)$. @@ -599,6 +631,7 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion; fix_root::Bool = false) rank(Lf) == 0 && return ZZLatWithIsom[Lf] + @req iseven(Lf) "Lattice must be even" @req is_prime(p) "p must be a prime number" @req is_of_hermitian_type(Lf) "Lf must be of hermitian type" @@ -616,11 +649,9 @@ function splitting_of_hermitian_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion; @vprintln :ZZLatWithIsom 1 "$(length(atp)) admissible triple(s)" while !is_empty(atp) A, B = pop!(atp) - LB = integer_lattice_with_isometry(representative(B)) - RB = representatives_of_hermitian_type(LB, p*q^e, fix_root) + RB = representatives_of_hermitian_type(B, p*q^e, fix_root) is_empty(RB) && continue - LA = integer_lattice_with_isometry(representative(A)) - RA = representatives_of_hermitian_type(LA, q^e) + RA = representatives_of_hermitian_type(A, q^e) is_empty(RA) && continue for L1 in RA, L2 in RB E = admissible_equivariant_primitive_extensions(L1, L2, Lf, p; check = false) @@ -634,7 +665,7 @@ 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 +Given an even 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)$. @@ -671,8 +702,9 @@ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion, b::Int = 0 return ZZLatWithIsom[] end + @req iseven(Lf) "Lattice must be even" @req is_prime(p) "p must be a prime number" - @req b in [0, 1] "b must be an integer equal to 0 or 1" + @req b == 0 || b == 1 "b must be an integer equal to 0 or 1" ord = order_of_isometry(Lf) @req ord isa Int "Order of isometry must be finite" @@ -685,7 +717,7 @@ function splitting_of_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion, b::Int = 0 reps = ZZLatWithIsom[] if e == 0 - reps = splitting_of_hermitian_prime_power(Lf, p; pA = p_inv, pB, fix_root) + reps = splitting_of_hermitian_prime_power(Lf, p; pA=p_inv, pB, fix_root) (b == 1) && filter!(M -> order_of_isometry(M) == p, reps) return reps end @@ -712,7 +744,7 @@ 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 an even 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 @@ -726,6 +758,8 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, _p::IntegerUnion fix_root::Bool = false) rank(Lf) == 0 && return ZZLatWithIsom[Lf] + @req iseven(Lf) "Lattice must be even" + n = order_of_isometry(Lf) @req is_finite(n) "Isometry must be of finite order" @@ -748,7 +782,7 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, _p::IntegerUnion end phi = minimal_polynomial(Lf) - chi = prod(cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e; init = zero(phi)) + chi = prod(cyclotomic_polynomial(p^d*q^i, parent(phi)) for i=0:e; init = one(phi)) @req is_divisible_by(chi, phi) "Minimal polynomial is not of the correct form" @@ -759,7 +793,7 @@ function splitting_of_pure_mixed_prime_power(Lf::ZZLatWithIsom, _p::IntegerUnion end A0 = kernel_lattice(Lf, p^d*q^e) - if pB >= 0 && signature_tuple(A0)[3] != pB + if pB >= 0 && signature_tuple(A0)[1] != pB return reps end bool, r = divides(phi, cyclotomic_polynomial(p^d*q^e, parent(phi))) @@ -781,7 +815,7 @@ 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 +Given an even 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)$. @@ -833,6 +867,7 @@ function splitting_of_mixed_prime_power(Lf::ZZLatWithIsom, p::IntegerUnion, b::I return ZZLatWithIsom[] end + @req iseven(Lf) "Lattice must be even" n = order_of_isometry(Lf) @req is_finite(n) "Isometry must be of finite order" @@ -879,10 +914,10 @@ 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 iseven(L) "Lattice must be 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)) + reps = representatives_of_hermitian_type(L, 1) return reps end pd = prime_divisors(order) diff --git a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl index 081022e284d7..74498c2e6902 100644 --- a/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl +++ b/experimental/QuadFormAndIsom/src/hermitian_miranda_morrison.jl @@ -313,16 +313,12 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # rank and then transport the generators along an appropriate map. qL, fqL = discriminant_group(Lf) OqL = orthogonal_group(qL) + if rank(Lf) != degree(Lf) - Lf2 = integer_lattice_with_isometry(integer_lattice(gram = gram_matrix(Lf)), isometry(Lf); ambient_representation = false, check = false) + Lf2 = integer_lattice_with_isometry(integer_lattice(; gram=gram_matrix(Lf)), isometry(Lf); ambient_representation=false, check=false) qL2, fqL2 = discriminant_group(Lf2) OqL2 = orthogonal_group(qL2) - ok, phi12 = is_isometric_with_isometry(qL, qL2) - @hassert :ZZLatWithIsom 1 ok - ok, g0 = is_conjugate_with_data(OqL, OqL(compose(phi12, compose(hom(fqL2), inv(phi12))); check = false), fqL) - @hassert :ZZLatWithIsom 1 ok - phi12 = compose(hom(OqL(g0)), phi12) - #@hassert :ZZLatWithIsom 1 matrix(compose(hom(fqL), phi12)) == matrix(compose(phi12, hom(fqL2))) + phi12 = hom(qL, qL2, identity_matrix(ZZ, ngens(qL))) @hassert :ZZLatWithIsom 1 is_isometry(phi12) else Lf2 = Lf @@ -339,6 +335,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) G, GinOqL = sub(OqL, gensG) @hassert :ZZLatWithIsom 1 fqL in G GtoG2 = hom(G, G2, gensG, gensG2; check = false) + @hassert :ZZLatWithIsom 1 GtoG2(fqL) == fqL2 # This is the associated hermitian O_E-lattice to (L, f): we want to make qL # (aka D_L) correspond to the quotient D^{-1}H^#/H by the trace construction, @@ -361,6 +358,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # hermitian world in which H lives. In particular, the trace lattice of H2 # with respect to res will be exactly the dual of L. res = get_attribute(Lf2, :transfer_data) + # We want only the prime ideal in O_K which divides the quotient H2/H. For # this, we collect all the primes dividing DEQ or for which H is not locally # unimodular. Then, we check for which prime ideals p, the local quotient @@ -396,15 +394,23 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) # avoid computing unnecessary crt. This will hold for the rest of the code, we # for those particular objects, the `dlog` maps take vectors, corresponding to # finite adeles. + list_ker = eltype(S)[] Fdata = Tuple{AbsSimpleNumFieldOrderIdeal, Int}[] - for p in S + for _i in 1:length(S) + p = S[_i] if !_is_special(H, p) push!(Fdata, (p, 0)) + if Fsharpdata[_i][2] == 0 + push!(list_ker, p) + end else lp = prime_decomposition(OE, p) P = lp[1][1] e = valuation(DEK, P) push!(Fdata, (p, e)) + if Fsharpdata[_i][2] == e + push!(list_ker, p) + end end end @@ -436,6 +442,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) Q, mQ = quo(FmodFsharp, I) SQ, SQtoQ = snf(Q) + oSQ = order(SQ) function dlog(x::Vector) @hassert :ZZLatWithIsom 1 length(x) == length(Fsharpdata) @@ -452,7 +459,7 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) for g in gensG2 ds = elem_type(E)[] for p in S - if !_is_special(H, p) + if isone(oSQ) || p in list_ker push!(ds, one(E)) else lp = prime_decomposition(OE, p) @@ -470,7 +477,6 @@ function _local_determinants_morphism(Lf::ZZLatWithIsom) GSQ, SQtoGSQ, _ = Oscar._isomorphic_gap_group(SQ) f2 = hom(G2, GSQ, gensG2, SQtoGSQ.(imgs); check = false) f = compose(GtoG2, f2) - @hassert :ZZLatWithIsom 1 isone(f(fqL)) return f, GinOqL # Needs the second map to map the kernel of f into OqL end @@ -528,16 +534,10 @@ Oscar.canonical_unit(x::AbsSimpleNumFieldOrderQuoRingElem) = one(parent(x)) # (fixed by res). The new map we obtain should be a local invertible map with # integer (for the given local field at p) entries which defines an isometry of # D^{-1}H^# modulo H. - function _transfer_discriminant_isometry(res::AbstractSpaceRes, g::AutomorphismGroupElem{TorQuadModule}, Bp::T, - P::Hecke.RelNumFieldOrderIdeal, - BHp::T) where T <: MatrixElem{Hecke.RelSimpleNumFieldElem{AbsSimpleNumFieldElem}} - E = base_ring(codomain(res)) - Eabs, EabstoE = absolute_simple_field(E) - Pabs = EabstoE\P - OEabs = order(Pabs) + pr::T) where T <: MatrixElem{Hecke.RelSimpleNumFieldElem{AbsSimpleNumFieldElem}} q = domain(g) @hassert :ZZLatWithIsom 1 ambient_space(cover(q)) === domain(res) @@ -545,60 +545,38 @@ function _transfer_discriminant_isometry(res::AbstractSpaceRes, # action of g via res. B2 = zero(Bp) for i in 1:nrows(Bp) - B2[i, :] = res(lift(g(q(res\(vec(collect(Bp[i, :]))))))) - end - - # Here BHp is the inverse of a local basis matrix of H at p. Since we look for - # F such that F*Bp == B2 mod inv(BHp), we then transform the problem into - # finding a matrix F such that F*newBp == newB2 mod O_p. Then to ensure that F - # has integral coefficients, we multiplying newBp and newB2 by a big enough - # integer d so that they are both integral (all their entries are in OEabs, - # after passing to an absolute simple extension), we keep track of the - # P-valuation i of d, and we then map everything in OEabs/Pabs^i. There, there - # should be a modular solution KQ such that KQ*BpQ == B2Q. We lift this matrix - # in OEabs and map it back to OE. - Bpabs = map_entries(a -> EabstoE\a, Bp*BHp) - B2abs = map_entries(a -> EabstoE\a, B2*BHp) - - # Here d is not necessarily well defined in O_E, but as it is implemented, - # each of the denominator(a, O_E) return the smallest positive integer d such - # that d*a lies in O_E, while `a` might be a non-integral element in E. - d = lcm(lcm([denominator(a, OEabs) for a in Bpabs]), lcm([denominator(a, OEabs) for a in B2abs])) - Bpabs = change_base_ring(OEabs, d*Bpabs) - B2abs = change_base_ring(OEabs, d*B2abs) - - # We would need to solve the equation modulo OE, but we multiplied by d, so we - # need to keep track of d. Note that with the precaution we took earlier, the - # Pabs-valuation of d is necessarily positive, so this quotient cannot be - # trivial. - Q, p = quo(OEabs, Pabs^(valuation(d, Pabs))) - Bpabs = map_entries(p, Bpabs) - B2abs = map_entries(p, B2abs) - - # Our local modular solution we have to lift - K = solve(Bpabs, B2abs; side = :left) - K = map_entries(a -> EabstoE(Eabs(p\a)), K) - - # If what we have done is correct then K*newBp == newB2 modulo O_p, so all - # entries in the difference K*newBp-newB2 must have non-negative P-valuation. - # Since it is the case, then K satisfies K*Bp == B2 mod H locally at p. - @hassert :ZZLatWithIsom 1 _scale_valuation((K*Bp-B2)*BHp, P) >= 0 + B2[i, :] = res(lift(g(q(res\(vec(collect(Bp[i, :])))))))*pr + end + # We should have a global exact solution. For now it does not seem to be slow + # If for larger (quite large) examples it happens to be slow, then one could + # modify this a bit and try to look for an inexact approximation modulo H. + ok, K = can_solve_with_solution(Bp, B2; side=:left) + @hassert :ZZLatWithIsom 1 ok return K end # the minimum P-valuation among all the non-zero entries of M function _scale_valuation(M::T, P::Hecke.RelNumFieldOrderIdeal) where T <: MatrixElem{Hecke.RelSimpleNumFieldElem{AbsSimpleNumFieldElem}} - @hassert :ZZLatWithIsom 1 Hecke.nf(order(P)) === base_ring(M) + OE = order(P) + E = Hecke.nf(OE) + @hassert :ZZLatWithIsom 1 base_ring(M) === E iszero(M) && return inf - return minimum(valuation(v, P) for v in collect(M) if !iszero(v)) + O = fractional_ideal(OE, one(E)) + to_sum = [ M[i, j] * O for j in 1:nrows(M) for i in 1:j ] + d = length(to_sum) + for i in 1:d + push!(to_sum, involution(E)(to_sum[i])) + end + s = sum(to_sum; init=fractional_ideal(maximal_order(E), zero(E))) + return valuation(s, P) end # the minimum P-valuation among all the non-zero diagonal entries of M function _norm_valuation(M::T, P::Hecke.RelNumFieldOrderIdeal) where T <: MatrixElem{Hecke.RelSimpleNumFieldElem{AbsSimpleNumFieldElem}} - @hassert :ZZLatWithIsom 1 Hecke.nf(order(P)) === base_ring(M) - iszero(diagonal(M)) && return inf - r = minimum(valuation(v, P) for v in diagonal(M) if !iszero(v)) - return r + E = Hecke.nf(order(P)) + @hassert :ZZLatWithIsom 1 base_ring(M) === E + iszero(M) && return inf + return Hecke._get_norm_valuation_from_gram_matrix(M, P) end # This is algorithm 8 of BH23: under the good assumptions, then we can do a @@ -610,21 +588,28 @@ end # We use this method iteratively to lift isometries (along a surjective map), by # looking at better representatives until we reach a good enough precision for # our purpose. -function _local_hermitian_lifting(G::T, F::T, rho::Hecke.RelSimpleNumFieldElem, l::Int, P::Hecke.RelNumFieldOrderIdeal, e::Int, a::Int; check = true) where T <: MatrixElem{Hecke.RelSimpleNumFieldElem{AbsSimpleNumFieldElem}} +function _local_hermitian_lifting(G::T, F::T, rho::Hecke.RelSimpleNumFieldElem, l::Int, P::Hecke.RelNumFieldOrderIdeal, P2::Hecke.RelNumFieldOrderIdeal, split::Bool, e::Int, a::Int; check = true) where T <: MatrixElem{Hecke.RelSimpleNumFieldElem{AbsSimpleNumFieldElem}} @hassert :ZZLatWithIsom 1 trace(rho) == 1 E = base_ring(G) + s = involution(E) # G here is a local gram matrix - @hassert :ZZLatWithIsom 1 G == map_entries(involution(E), transpose(G)) + @hassert :ZZLatWithIsom 1 G == map_entries(s, transpose(G)) @hassert :ZZLatWithIsom 1 base_ring(F) === E # R represents the defect, how far F is to be an isometry of G - R = G - F*G*map_entries(involution(E), transpose(F)) + R = G - F*G*map_entries(s, transpose(F)) # These are the necessary conditions for the input of algorithm 8 in BH23 if check @hassert :ZZLatWithIsom 1 _scale_valuation(inv(G), P) >= 1+a @hassert :ZZLatWithIsom 1 _norm_valuation(inv(G), P) + valuation(rho, P) >= 1+a @hassert :ZZLatWithIsom 1 _scale_valuation(R, P) >= l-a @hassert :ZZLatWithIsom 1 _norm_valuation(R, P) + valuation(rho,P) >= l-a + if split + @hassert :ZZLatWithIsom 1 _scale_valuation(inv(G), P2) >= 1+a + @hassert :ZZLatWithIsom 1 _norm_valuation(inv(G), P2) + valuation(rho, P2) >= 1+a + @hassert :ZZLatWithIsom 1 _scale_valuation(R, P2) >= l-a + @hassert :ZZLatWithIsom 1 _norm_valuation(R, P2) + valuation(rho, P2) >= l-a + end end # R is s-symmetric, where s is the canonical involution of E/K. We split R @@ -637,19 +622,24 @@ function _local_hermitian_lifting(G::T, F::T, rho::Hecke.RelSimpleNumFieldElem, end end - diag = R - U - map_entries(involution(E), transpose(U)) + diag = R - U - map_entries(s, transpose(U)) - # this newF is suppose to be a better lift than F, i.e. it is congruent to F + # this newF is supposed to be a better lift than F, i.e. it is congruent to F # modulo P^{l+1} and the corresponding defect R2 has a higher P-valuation (so # P-adic, we are close to have a proper isometry) - newF = F + (U + rho*diag)*map_entries(involution(E), inv(transpose(F)))*inv(G) + newF = F + (U + rho*diag)*map_entries(s, inv(transpose(F)))*inv(G) l2 = 2*l+1 if check - @hassert :ZZLatWithIsom 1 _scale_valuation(F-newF, P) >= l+1 - R2 = G-newF*G*map_entries(involution(E), transpose(newF)) + @hassert :ZZLatWithIsom 1 minimum(valuation(m, P) for m in collect(F-newF) if !iszero(m)) >= l+1 + R2 = G-newF*G*map_entries(s, transpose(newF)) @hassert :ZZLatWithIsom 1 _scale_valuation(R2, P) >= l2-a @hassert :ZZLatWithIsom 1 _norm_valuation(R2, P) + valuation(rho, P) >= l2-a + if split + @hassert :ZZLatWithIsom 1 minimum(valuation(m, P2) for m in collect(F-newF) if !iszero(m)) >= l+1 + @hassert :ZZLatWithIsom 1 _scale_valuation(R2, P2) >= l2-a + @hassert :ZZLatWithIsom 1 _norm_valuation(R2, P2) + valuation(rho, P2) >= l2-a + end end return newF, l2 @@ -671,27 +661,29 @@ end # function _approximate_isometry(H::HermLat, H2::HermLat, g::AutomorphismGroupElem{TorQuadModule}, P::Hecke.RelNumFieldOrderIdeal, e::Int, a::Int, k::Int, res::AbstractSpaceRes) E = base_field(H) + s = involution(E) + P2 = s(P) + split = P2 != P + # In the split case, we need to check valuation at both primes above p + P2.is_prime = 1 @hassert :ZZLatWithIsom 1 Hecke.nf(order(P)) === E ok, b = is_modular(H, minimum(P)) if ok && b == -a return identity_matrix(E, 1) end - BHp = local_basis_matrix(H, minimum(P); type = :submodule) - BHp_inv = inv(BHp) - Bps = _local_basis_modular_submodules(H2, minimum(P), a, res) - Bp = reduce(vcat, Bps) - Gp = Bp*gram_matrix(ambient_space(H))*map_entries(involution(E), transpose(Bp)) - Fp = block_diagonal_matrix(typeof(Gp)[_transfer_discriminant_isometry(res, g, Bps[i], P, BHp_inv) for i in 1:length(Bps)]) + Bp, pr = _local_basis_matrix_and_projection(H2, minimum(P), a, res) + Gp = Bp*gram_matrix(ambient_space(H))*map_entries(s, transpose(Bp)) + Fp = _transfer_discriminant_isometry(res, g, Bp, pr) # This is the local defect. By default, it should have scale P-valuations -a # and norm P-valuation e-1-a - Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) + Rp = Gp - Fp*Gp*map_entries(s, transpose(Fp)) rho = _find_rho(P, e) l = 0 - while _scale_valuation(Rp, P) < 2*k+a - Fp, l = _local_hermitian_lifting(Gp, Fp, rho, l, P, e, a) - Rp = Gp - Fp*Gp*map_entries(involution(E), transpose(Fp)) + while _scale_valuation(Rp, P) < 2*k+a && (!split || _scale_valuation(Rp, P2) < 2*k+a) + Fp, l = _local_hermitian_lifting(Gp, Fp, rho, l, P, P2, split, e, a) + Rp = Gp - Fp*Gp*map_entries(s, transpose(Fp)) end return Fp @@ -700,7 +692,7 @@ end # We use this function compute a local basis matrix of H at p, whose integral # span defines a sublattice of H. If H has a P^{-a}-modular block at p, we remove # the corresponding basis vector(s) from the output to only keep the Jordan -# blocks of H_p which are of P-valuation different from -a +# blocks of H_p which are of P-valuation different from -a. # # In our context of use, -a is actually the biggest scale valuation for # D^{-1}H^#. Since we take care earlier to discard the cases where D^{-1}H^# is @@ -708,27 +700,45 @@ end # the jordan decomposition of D^{-1}H^# at p. From that point, we massage a bit # the basis matrices of the other jordan blocks to obtain local basis matrices # which span sublattices of D^{-1}H^#. -function _local_basis_modular_submodules(H::HermLat, p::AbsSimpleNumFieldOrderIdeal, a::Int, res::AbstractSpaceRes) - L = restrict_scalars(H, res) - B, _ , exps = jordan_decomposition(H, p) - if exps[end] == -a - pop!(B) - pop!(exps) - end - subs = eltype(B)[] - for b in B - H2 = lattice_in_same_ambient_space(H, b) - if !is_sublattice(H, H2) - L2 = restrict_scalars(H2, res) - L2 = intersect(L, L2) - B2 = basis_matrix(L2) - gene = Vector{elem_type(base_field(H))}[res(vec(collect(B2[i, :]))) for i in 1:nrows(B2)] - H2 = lattice(ambient_space(H), gene) - b = local_basis_matrix(H2, p; type = :submodule) +# +# If Hv is locally U + R where U if a Jordan block of scale P^{-a}, the matrix +# pr in output is the projection onto R. We multiply our local basis for R by +# pr to remove all residue from U: we use this map also to compute approximation +# of local isometries later. +function _local_basis_matrix_and_projection(Hv::HermLat, p::AbsSimpleNumFieldOrderIdeal, a::Int, res::AbstractSpaceRes) + B, _ , exps = jordan_decomposition(Hv, p) + pr = identity_matrix(base_field(Hv), rank(Hv)) + # If the last Jordan block U is P^{-a}-modular, then this part while + # vanish in the quotient Hv/H and we want to get rid of it. However + # we need to remember a decomposition Hv_p = R+U: for this + # we define the projection map Hv_p \to R so later we can + # remove any trace of U in our elements (for computing the first + # approximation of our isometries) + if expsv[end] == -a + for i in 1:nrows(Bv[end]) + pr[nrows(pr)-i+1, ncols(pr)-i+1] = 0 end - push!(subs, b) - end - return subs + subsv = Bv[1:end-1] + else + subsv = Bv + end + + # Now that we have a good looking local basis at p for R, we massage it + # a bit to make sure that globally it spans a sublattice of Hv. + Bp = reduce(vcat, subsv) + H2v = lattice_in_same_ambient_space(Hv, Bp) + if !is_sublattice(Hv, H2v) + Lv = restrict_scalars(Hv, res) + L2 = restrict_scalars(H2v, res) + L2 = intersect(Lv, L2) + B2 = basis_matrix(L2) + gene = Vector{elem_type(base_field(Hv))}[res(vec(collect(B2[i, :]))) for i in 1:nrows(B2)] + H2v = lattice(ambient_space(Hv), gene) + @hassert :ZZLatWithIsom 1 is_sublattice(Hv, H2v) + Bp = local_basis_matrix(H2v, p; type = :submodule) + end + @hassert :ZZLatWithIsom 1 rank(Bp) == rank(Bp*pr) + return Bp*pr, pr end # We need a special rho for Algorithm 8 of BH23: we construct such an element @@ -747,12 +757,15 @@ function _find_rho(P::Hecke.RelNumFieldOrderIdeal, e::Int) lp = prime_decomposition(OE, minimum(P)) dya = is_dyadic(P) if !dya - length(lp) == 1 && return E(1//2) - return gen(E) + rho = E(1//2) + @hassert :ZZLatWithIsom 1 trace(rho) == 1 + return rho end lp = prime_decomposition(OE, minimum(P)) if lp[1][2] == 1 - return Hecke._special_unit(P, minimum(P)) + rho = Hecke._special_unit(P, minimum(P)) + @hassert :ZZLatWithIsom 1 trace(rho) == 1 + return rho end K = base_field(E) Eabs, EabstoE = absolute_simple_field(E) diff --git a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl index 1a3ef6f886a6..b7ad00678a9d 100644 --- a/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/lattices_with_isometry.jl @@ -405,7 +405,7 @@ julia> minimum(Lf) ``` """ function minimum(Lf::ZZLatWithIsom) - @req is_positive_definite(Lf) "Underlying lattice must be positive definite" + @req is_definite(Lf) "Underlying lattice must be definite" return minimum(lattice(Lf)) end @@ -1452,7 +1452,7 @@ 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 and the associated order is maximal. 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]$. @@ -1486,7 +1486,11 @@ true """ @attr function is_of_hermitian_type(Lf::ZZLatWithIsom) @req rank(Lf) > 0 "Underlying lattice must have positive rank" - return is_irreducible(minimal_polynomial(Lf)) + chi = minimal_polynomial(Lf) + !is_irreducible(chi) && return false + is_finite(order_of_isometry(Lf)) && return true + E = equation_order(number_field(chi; cached=false)[1]) + return is_maximal(E) end @doc raw""" @@ -1615,7 +1619,7 @@ function discriminant_group(Lf::ZZLatWithIsom) q = discriminant_group(L) f = hom(q, q, elem_type(q)[q(lift(t)*f) for t in gens(q)]) fq = gens(Oscar._orthogonal_group(q, ZZMatrix[matrix(f)]; check = false))[1] - return (q, fq) + return q, fq end @doc raw""" @@ -1726,6 +1730,7 @@ julia> order(G) disc = discriminant_representation(L, UL; check = false, ambient_representation = false) return image(disc) else + @req is_even(Lf) "Hermitian Miranda-Morrison currently available only for even lattices" # 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. @@ -1750,49 +1755,8 @@ julia> order(G) 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 + return _glue_stabilizers(Lf, M, N) end end @@ -1874,8 +1838,7 @@ function signatures(Lf::ZZLatWithIsom) n = order_of_isometry(Lf) C = CalciumField() eig = eigenvalues(QQBar, f) - j = findfirst(z -> findfirst(k -> isone(z^k), 1:n) == n, eig) - lambda = C(eig[j]) + lambda = C(eig[1]) Sq = Int[i for i in 1:div(n,2) if gcd(i,n) == 1] D = Dict{Integer, Tuple{Int, Int}}() fC = change_base_ring(C, f) @@ -2272,12 +2235,14 @@ true x = gen(Qx) t = Dict{Integer, Tuple}() for l in divs - Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) - if !(order_of_isometry(Hl) in [-1,1,2]) - Hl = hermitian_structure(lattice(Hl), isometry(Hl); check = false, ambient_representation = false) - end Al = kernel_lattice(Lf, x^l-1) - t[l] = (genus(Hl), genus(Al)) + _Hl = kernel_lattice(Lf, cyclotomic_polynomial(l)) + if !(order_of_isometry(_Hl) in [-1,1,2]) + Hl = hermitian_structure(lattice(_Hl), isometry(_Hl); check = false, ambient_representation = false) + t[l] = (genus(Hl), genus(Al)) + else + t[l] = (genus(_Hl), genus(Al)) + end end return t end @@ -2310,14 +2275,16 @@ function is_of_type(L::ZZLatWithIsom, t::Dict) divs = sort(collect(keys(t))) x = gen(Hecke.Globals.Qx) for l in divs - Hl = kernel_lattice(L, cyclotomic_polynomial(l)) - if !(order_of_isometry(Hl) in [-1, 1, 2]) - t[l][1] isa HermGenus || return false - Hl = hermitian_structure(lattice(Hl), isometry(Hl); check = false, ambient_representation = false, E = base_field(t[l][1])) - end - genus(Hl) == t[l][1] || return false Al = kernel_lattice(L, x^l-1) genus(Al) == t[l][2] || return false + _Hl = kernel_lattice(L, cyclotomic_polynomial(l)) + if !(order_of_isometry(_Hl) in [-1, 1, 2]) + t[l][1] isa HermGenus || return false + Hl = hermitian_structure(lattice(_Hl), isometry(_Hl); check = false, ambient_representation = false, E = base_field(t[l][1])) + genus(Hl) == t[l][1] || return false + else + genus(_Hl) == t[l][1] || return false + end end return true end diff --git a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl index 06388b9f3047..c53d4a7467f4 100644 --- a/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl +++ b/experimental/QuadFormAndIsom/src/spaces_with_isometry.jl @@ -739,8 +739,8 @@ end @doc raw""" rational_spinor_norm(Vf::QuadSpaceWithIsom; b::Int = -1) -> QQFieldElem -Given a rational quadratic space with isometry $(V, b, f)$, return the real -spinor norm of the extension of $f$ to $V\otimes \mathbb{Q}$. +Given a rational quadratic space with isometry $(V, b, f)$, return the rational +spinor norm of $f$. If $\Phi$ is the form on $V$, then the spinor norm is computed with respect to $b\Phi$. diff --git a/experimental/QuadFormAndIsom/test/runtests.jl b/experimental/QuadFormAndIsom/test/runtests.jl index 940b57243b1c..52c86a8cf4fa 100644 --- a/experimental/QuadFormAndIsom/test/runtests.jl +++ b/experimental/QuadFormAndIsom/test/runtests.jl @@ -256,9 +256,8 @@ end @test order(ONf) == order(image_centralizer_in_Oq(N)[1]) E6 = root_lattice(:E, 6) - @test length(enumerate_classes_of_lattices_with_isometry(E6, 10)) == 3 @test length(enumerate_classes_of_lattices_with_isometry(E6, 20)) == 0 - @test length(enumerate_classes_of_lattices_with_isometry(E6, 18)) == 1 + @test length(enumerate_classes_of_lattices_with_isometry(E6, 9)) == 1 @test length(enumerate_classes_of_lattices_with_isometry(genus(E6), 1)) == 1 @test length(admissible_triples(E6, 2; pA=2)) == 2 @@ -266,6 +265,16 @@ end @test length(admissible_triples(E6, 3; pA=2, pB = 4)) == 1 end +@testset "Enumeration of hermitian lattices with isometry" begin + # Infinite isometry: chi is a Salem polynomial + G = genus(torsion_quadratic_module(QQ[0;]), (9, 1)) + _, x = QQ["x"] + chi = x^(10)+x^9-x^7-x^6-x^5-x^4-x^3+x+1 + rht = @inferred representatives_of_hermitian_type(G, chi) + @test !isempty(rht) + @test all(N -> !is_finite(order_of_isometry(N)), rht) +end + @testset "Primitive extensions and embeddings" begin # Compute orbits of short vectors k = integer_lattice(; gram=matrix(QQ,1,1,[4]))