From b4dce5918441341d0191e7129ada9290ff9900c8 Mon Sep 17 00:00:00 2001 From: thofma Date: Tue, 26 Jan 2021 21:15:05 +0100 Subject: [PATCH] More lattice tests (#139) --- src/Hecke.jl | 2 +- src/QuadForm/Herm/Genus.jl | 5 ++ src/QuadForm/Lattices.jl | 9 ++- src/QuadForm/Quad/ZLattices.jl | 41 +++++++++++ src/QuadForm/Torsion.jl | 116 +++++++++++++++++++++++++------- test/QuadForm.jl | 2 + test/QuadForm/Herm.jl | 4 ++ test/QuadForm/Herm/Genus.jl | 40 +++++++++++ test/QuadForm/Herm/Spaces.jl | 18 +++++ test/QuadForm/Quad/Spaces.jl | 7 ++ test/QuadForm/Quad/ZLattices.jl | 10 +++ test/QuadForm/Torsion.jl | 13 ++++ 12 files changed, 242 insertions(+), 25 deletions(-) create mode 100644 test/QuadForm/Herm.jl create mode 100644 test/QuadForm/Herm/Genus.jl create mode 100644 test/QuadForm/Herm/Spaces.jl create mode 100644 test/QuadForm/Torsion.jl diff --git a/src/Hecke.jl b/src/Hecke.jl index 9b339b221d..584504dd76 100644 --- a/src/Hecke.jl +++ b/src/Hecke.jl @@ -534,7 +534,7 @@ end function test_module(x, new::Bool = true) julia_exe = Base.julia_cmd() # On Windows, we also allow bla/blub" - x = _adjust_path + x = _adjust_path(x) if x == "all" test_file = joinpath(pkgdir, "test", "runtests.jl") else diff --git a/src/QuadForm/Herm/Genus.jl b/src/QuadForm/Herm/Genus.jl index 44f1cc40f3..7bbf57af25 100644 --- a/src/QuadForm/Herm/Genus.jl +++ b/src/QuadForm/Herm/Genus.jl @@ -522,6 +522,11 @@ function genus(::Type{HermLat}, E::S, p::T, data::Vector{Tuple{Int, Int, Int}}; z.isdyadic = isdyadic(p) z.isramified = isramified(maximal_order(E), p) @assert !(isramified(z) && isdyadic(z)) + + if type !== :det && type !== :disc + throw(error("type :$type must be :disc or :det")) + end + if !z.isramified || type === :det z.data = copy(data) else diff --git a/src/QuadForm/Lattices.jl b/src/QuadForm/Lattices.jl index f9c4633890..06c00d4e3d 100644 --- a/src/QuadForm/Lattices.jl +++ b/src/QuadForm/Lattices.jl @@ -7,7 +7,7 @@ export *, +, absolute_basis, absolute_basis_matrix, ambient_space, bad_primes, local_basis_matrix, norm, pseudo_matrix, quadratic_lattice, rank, rational_span, rescale, scale, volume, witt_invariant, lattice, Zlattice, automorphism_group_generators, automorphism_group_order, - isisometric, islocal_norm, normic_defect + isisometric, islocal_norm, normic_defect, issublattice, issublattice_with_relations export HermLat, QuadLat @@ -162,6 +162,13 @@ function degree(L::AbsLat) return dim(ambient_space(L)) end +@doc Markdown.doc""" + issublattice(L::AbsLat, M::AbsLat) -> Bool + +Returns whether $M$ is a sublattice of $L$. +""" +issublattice(L::AbsLat, M::AbsLat) + ################################################################################ # # Gram matrix diff --git a/src/QuadForm/Quad/ZLattices.jl b/src/QuadForm/Quad/ZLattices.jl index 42ad141e52..c64b97b9c1 100644 --- a/src/QuadForm/Quad/ZLattices.jl +++ b/src/QuadForm/Quad/ZLattices.jl @@ -340,6 +340,47 @@ function isisometric(L::ZLat, M::ZLat; ambient_representation::Bool = true) end end +################################################################################ +# +# Is sublattice? +# +################################################################################ + +function issublattice(M::ZLat, N::ZLat) + if ambient_space(M) != ambient_space(N) + return false + end + + hassol, _rels = can_solve_with_solution(basis_matrix(M), basis_matrix(N), side=:left) + + if !hassol || !isone(denominator(_rels)) + return false + end + + return true +end + +@doc Markdown.doc""" + issublattice_with_relations(M::ZLat, N::ZLat) -> Bool, fmpq_mat + +Returns whether $N$ is a sublattice of $N$. In this case, the second return +value is a matrix $B$ such that $B B_M = B_N$, where $B_M$ and $B_N$ are the +basis matrices of $M$ and $N$ respectively. +""" +function issublattice_with_relations(M::ZLat, N::ZLat) + if ambient_space(M) != ambient_space(N) + return false, basis_matrix(M) + end + + hassol, _rels = can_solve_with_solution(basis_matrix(M), basis_matrix(N), side=:left) + + if !hassol || !isone(denominator(_rels)) + return false, basis_matrix(M) + end + + return true, _rels + end + ################################################################################ # # Root lattice diff --git a/src/QuadForm/Torsion.jl b/src/QuadForm/Torsion.jl index 98ae890cdc..083cb0d873 100644 --- a/src/QuadForm/Torsion.jl +++ b/src/QuadForm/Torsion.jl @@ -1,3 +1,5 @@ +export discriminant_group + # Torsion QuadraticForm # # Example: @@ -42,6 +44,8 @@ mutable struct TorQuadMod TorQuadMod() = new() end +ngens(T::TorQuadMod) = length(gens(T)) + ################################################################################ # # Construction @@ -51,8 +55,8 @@ end # compute the torsion quadratic module M/N function torsion_quadratic_module(M::ZLat, N::ZLat; modulus = fmpq(0)) @req ambient_space(M) === ambient_space(N) "Lattices must have same ambient space" - hassol, _rels = can_solve_with_solution(basis_matrix(M), basis_matrix(N), side=:left) - @req isone(denominator(_rels)) && hassol "Second lattice must be a submodule of first lattice" + fl, _rels = issublattice_with_relations(M, N) + @req fl "Second lattice must be a sublattice of first lattice" rels = change_base_ring(FlintZZ, _rels) A = abelian_group(rels) S, mS = snf(A) @@ -183,19 +187,64 @@ mutable struct TorQuadModElem TorQuadModElem(T::TorQuadMod, a::GrpAbFinGenElem) = new(a, T) end -# TODO: Check the parents ... -(T::TorQuadMod)(a::GrpAbFinGenElem) = TorQuadModElem(T, a) +################################################################################ +# +# Creation +# +################################################################################ + +function (T::TorQuadMod)(a::GrpAbFinGenElem) + @req abelian_group(T) === parent(a) "Parents do not match" + return TorQuadModElem(T, a) +end + +# Coerces an element of the ambient space of cover(T) to T + +function (T::TorQuadMod)(v::Vector) + @req length(v) == dim(ambient_space(cover(T))) "Vector of wrong length" + vv = map(FlintQQ, v) + if eltype(vv) != fmpq + error("Cannot coerce elements to the rationals") + end + return T(vv::Vector{fmpq}) +end function (T::TorQuadMod)(v::Vector{fmpq}) + @req length(v) == dim(ambient_space(cover(T))) "Vector of wrong length" vv = change_base_ring(FlintZZ, matrix(FlintQQ, 1, length(v), v) * inv(basis_matrix(cover(T)))) return T(abelian_group(T)(vv * T.proj)) end +################################################################################ +# +# Printing +# +################################################################################ + +function Base.show(io::IO, a::TorQuadModElem) + v = a.a.coeff + print(io, "[") + for i in 1:length(v) + if i == length(v) + print(io, v[i]) + else + print(io, v[i], ", ") + end + end + print(io, "]") +end + +################################################################################ +# +# Generators +# +################################################################################ + function gens(T::TorQuadMod) if isdefined(T, :gens) - return T.gens + return T.gens::Vector{TorQuadModElem} else - _gens = [T(g) for g in gens(abelian_group(T))] + _gens = TorQuadModElem[T(g) for g in gens(abelian_group(T))] T.gens = _gens return _gens end @@ -204,7 +253,10 @@ end parent(a::TorQuadModElem) = a.parent # Check the parent -(A::GrpAbFinGen)(a::TorQuadModElem) = a.a +function (A::GrpAbFinGen)(a::TorQuadModElem) + @req A === abelian_group(parent(a)) "Parents do not match" + return a.a +end ################################################################################ # @@ -244,12 +296,30 @@ function lift(a::TorQuadModElem) return fmpq[z[1, i] for i in 1:ncols(z)] end -mutable struct TorQuadModMor - domain::TorQuadMod - codomain::TorQuadMod +################################################################################ +# +# Maps between torsion quadratic modules +# +################################################################################ + +mutable struct TorQuadModMor <: Map{TorQuadMod, TorQuadMod, HeckeMap, TorQuadModMor} + header::MapHeader{TorQuadMod, TorQuadMod} map_ab::GrpAbFinGenMap + + function TorQuadModMor(T::TorQuadMod, S::TorQuadMod, m::GrpAbFinGenMap) + z = new() + z.header = MapHeader(T, S) + z.map_ab = m + return z + end end +################################################################################ +# +# User constructors +# +################################################################################ + function hom(T::TorQuadMod, S::TorQuadMod, M::fmpz_mat) f = hom(abelian_group(T), abelian_group(S), M) return TorQuadModMor(T, S, map_ab) @@ -257,23 +327,24 @@ end function hom(T::TorQuadMod, S::TorQuadMod, img::Vector{TorQuadModElem}) _img = GrpAbFinGenElem[] + @req length(img) == ngens(T) "Wrong number of elements" for g in img + @req parent(g) === S "Elements have the wrong parent" push!(_img, abelian_group(S)(g)) end map_ab = hom(abelian_group(T), abelian_group(S), _img) return TorQuadModMor(T, S, map_ab) end -domain(f::TorQuadModMor) = f.domain - -codomain(f::TorQuadModMor) = f.codomain - -function (f::TorQuadModMor)(a::TorQuadModElem) +function image(f::TorQuadModMor, a::TorQuadModElem) A = abelian_group(domain(f)) return codomain(f)(f.map_ab(A(a))) end - +function preimage(f::TorQuadModMor, a::TorQuadModElem) + A = abelian_group(domain(f)) + return domain(f)(f.map_ab\(A(a))) +end ################################################################################ # @@ -283,22 +354,21 @@ end @doc Markdown.doc""" - submodule(T::TorQuadMod, generators::Vector{TorQuadModElem})-> TorQuadMod, Map + sub(T::TorQuadMod, generators::Vector{TorQuadModElem})-> TorQuadMod, Map Return the submodule of `T` defined by `generators` and the inclusion morphism. """ -function submodule(T::TorQuadMod, generators::Vector{TorQuadModElem}) +function sub(T::TorQuadMod, gens::Vector{TorQuadModElem}) V = ambient_space(T.cover) - generators = matrix(QQ, [lift(g) for g in generators]) - gens_new = [basis_matrix(T.rels); generators] - cover = lattice(V, gens_new, isbasis=false) + _gens = matrix(QQ, [lift(g) for g in gens]) + gens_new = [basis_matrix(T.rels); _gens] + cover = lattice(V, gens_new, isbasis = false) S = torsion_quadratic_module(cover, T.rels) - imgs = [T(lift(g)) for g in gens(S)] + imgs = [T(lift(g)) for g in Hecke.gens(S)] inclusion = hom(S, T, imgs) return S, inclusion end - function TorQuadMod(q::fmpq_mat) @req issquare(q) "Matrix must be a square matrix" @req issymmetric(q) "Matrix must be symmetric" diff --git a/test/QuadForm.jl b/test/QuadForm.jl index 8af88ca575..f26710c633 100644 --- a/test/QuadForm.jl +++ b/test/QuadForm.jl @@ -8,4 +8,6 @@ @time include("QuadForm/MassHerm.jl") @time include("QuadForm/Quad.jl") @time include("QuadForm/QuadBin.jl") + @time include("QuadForm/Herm.jl") + @time include("QuadForm/Torsion.jl") end diff --git a/test/QuadForm/Herm.jl b/test/QuadForm/Herm.jl new file mode 100644 index 0000000000..a7afd3b694 --- /dev/null +++ b/test/QuadForm/Herm.jl @@ -0,0 +1,4 @@ +@testset "Herm" begin + include("Herm/Spaces.jl") + include("Herm/Genus.jl") +end diff --git a/test/QuadForm/Herm/Genus.jl b/test/QuadForm/Herm/Genus.jl new file mode 100644 index 0000000000..774d84f0ff --- /dev/null +++ b/test/QuadForm/Herm/Genus.jl @@ -0,0 +1,40 @@ +@testset "Genus" begin + Qx, x = QQ["x"] + K, a = NumberField(x^2 - 2, "a") + OK = maximal_order(K) + Kt, t = K["t"] + + E1, b1 = NumberField(t^2 - a, "b1") # ramified at 2 + E2, b2 = NumberField(t^2 - 5, "b2") # unramified at 2 + + p = prime_decomposition(OK, 2)[1][1] + # ramified & dyadic + g = genus(HermLat, E1, p, [(0, 1, 1, 0), (2, 2, -1, 1)], type = :det) + @test sprint(show, "text/plain", g) isa String + @test sprint(show, g) isa String + g = genus(HermLat, E1, p, [(0, 1, 1, 0), (2, 2, -1, 1)], type = :disc) + @test sprint(show, "text/plain", g) isa String + @test sprint(show, g) isa String + @test_throws ErrorException genus(HermLat, E1, p, [(0, 1, 1, 1), (2, 2, -1, 0)], type = :det) + @test_throws ErrorException genus(HermLat, E1, p, [(0, 1, 1, 1), (2, 2, -1, 0)], type = :disc) + @test_throws ErrorException genus(HermLat, E1, p, [(0, 1, 1, 0), (2, 2, -1, 1)], type = :bla) + + # unramified & dyadic + g = genus(HermLat, E2, p, [(0, 1, 1), (2, 2, -1)], type = :det) + @test sprint(show, "text/plain", g) isa String + @test sprint(show, g) isa String + g = genus(HermLat, E2, p, [(0, 1, 1), (2, 2, -1)], type = :disc) + @test sprint(show, "text/plain", g) isa String + @test sprint(show, g) isa String + @test_throws ErrorException genus(HermLat, E2, p, [(0, 1, 1), (2, 2, -1)], type = :bla) + + # ramified & non-dyadic + p = prime_decomposition(OK, 5)[1][1] + g = genus(HermLat, E2, p, [(0, 1, 1), (2, 2, -1)], type = :det) + @test sprint(show, "text/plain", g) isa String + @test sprint(show, g) isa String + g = genus(HermLat, E2, p, [(0, 1, 1), (2, 2, -1)], type = :disc) + @test sprint(show, "text/plain", g) isa String + @test sprint(show, g) isa String + @test_throws ErrorException genus(HermLat, E2, p, [(0, 1, 1), (2, 2, -1)], type = :bla) +end diff --git a/test/QuadForm/Herm/Spaces.jl b/test/QuadForm/Herm/Spaces.jl new file mode 100644 index 0000000000..c48087aa4d --- /dev/null +++ b/test/QuadForm/Herm/Spaces.jl @@ -0,0 +1,18 @@ +@testset "Spaces" begin + Qx, x = PolynomialRing(FlintQQ, "x") + K, a = NumberField(x^2 - 2, "a1") + Kt, t = K["t"] + + E, b = NumberField(t^2 + 3) + + F = GF(3) + + Hecke.change_base_ring(::Hecke.NfRel, ::Hecke.gfp_mat) = error("asd") + @test_throws ErrorException hermitian_space(E, F[1 2; 2 1]) + + Hecke.change_base_ring(::Hecke.NfRel, x::Hecke.gfp_mat) = x + @test_throws ErrorException hermitian_space(E, F[1 2; 2 1]) + + V = @inferred hermitian_space(E, FlintQQ[1 2; 2 1]) + @test V isa Hecke.HermSpace +end diff --git a/test/QuadForm/Quad/Spaces.jl b/test/QuadForm/Quad/Spaces.jl index 9e73458bcc..598e358797 100644 --- a/test/QuadForm/Quad/Spaces.jl +++ b/test/QuadForm/Quad/Spaces.jl @@ -6,6 +6,13 @@ K2, a2 = NumberField(x^3 - 2, "a2") K1t, t = PolynomialRing(K1, "t") + F = GF(3) + + Hecke.change_base_ring(::FlintRationalField, ::Hecke.gfp_mat) = error("asd") + @test_throws ErrorException quadratic_space(FlintQQ, F[1 2; 2 1]) + + Hecke.change_base_ring(::FlintRationalField, x::Hecke.gfp_mat) = x + @test_throws ErrorException quadratic_space(FlintQQ, F[1 2; 2 1]) L, b = NumberField(t^2 + a1) diff --git a/test/QuadForm/Quad/ZLattices.jl b/test/QuadForm/Quad/ZLattices.jl index f12c9e7c85..2b56fa1f73 100644 --- a/test/QuadForm/Quad/ZLattices.jl +++ b/test/QuadForm/Quad/ZLattices.jl @@ -139,6 +139,16 @@ end @test (@inferred base_ring(Lr0)) isa FlintIntegerRing + @test !(@inferred issublattice(Lr2, Lr1)) + M = Zlattice(;gram = FlintQQ[2 2; 2 2]) + @test !(@inferred issublattice(Lr0, M)) + @test issublattice(Lr2, Lr0) + @test issublattice(Lr1, lattice(V, QQ[2 0;])) + + fl, rels = @inferred issublattice_with_relations(Lr1, lattice(V, QQ[2 0;])) + @test fl + @test rels == QQ[2;] + # lattices of rank 0 B = matrix(QQ, 0, 2, []) diff --git a/test/QuadForm/Torsion.jl b/test/QuadForm/Torsion.jl new file mode 100644 index 0000000000..1a86988a9a --- /dev/null +++ b/test/QuadForm/Torsion.jl @@ -0,0 +1,13 @@ +@testset "Torsion" begin + D4_gram = matrix(ZZ, [[2, 0, 0, -1], + [0, 2, 0, -1], + [0, 0, 2, -1], + [-1 ,-1 ,-1 ,2]]) + + L = Zlattice(gram = D4_gram) + T = discriminant_group(L) + @test order(T) == 4 + + TT, mTT = @inferred sub(T, [T([1, 1//2, 1//2, 1])]) + @test order(TT) == 2 +end