From 641f5204de0b69ca37b71480df4b50ad44cb4ec1 Mon Sep 17 00:00:00 2001 From: Claus Fieker Date: Thu, 21 Dec 2023 07:34:02 +0100 Subject: [PATCH] Updates for Galois group computation (#3123) --- experimental/GModule/Cohomology.jl | 2 +- experimental/GaloisGrp/src/Solve.jl | 6 +- experimental/GaloisGrp/test/runtests.jl | 20 ++- experimental/ModStd/ModStdQt.jl | 2 +- src/NumberTheory/GaloisGrp/GaloisGrp.jl | 210 +++++++++++++++++++----- src/NumberTheory/GaloisGrp/Qt.jl | 45 +++++ 6 files changed, 241 insertions(+), 44 deletions(-) diff --git a/experimental/GModule/Cohomology.jl b/experimental/GModule/Cohomology.jl index fbeb734d2437..a51532807d5e 100644 --- a/experimental/GModule/Cohomology.jl +++ b/experimental/GModule/Cohomology.jl @@ -1710,7 +1710,7 @@ function ==(a::Union{Generic.ModuleHomomorphism, Generic.ModuleIsomorphism}, b:: end function Oscar.id_hom(A::AbstractAlgebra.FPModule) - return Generic.ModuleIsomorphism(A, A, identity_matrix(base_ring(A), ngens(A))) + return Generic.ModuleHomomorphism(A, A, identity_matrix(base_ring(A), ngens(A))) end ########################################################### diff --git a/experimental/GaloisGrp/src/Solve.jl b/experimental/GaloisGrp/src/Solve.jl index 3a275cc28be0..c58f6a4e8571 100644 --- a/experimental/GaloisGrp/src/Solve.jl +++ b/experimental/GaloisGrp/src/Solve.jl @@ -444,7 +444,8 @@ function as_radical_extension(K::NumField, aut::Map, zeta::NumFieldElem; simplif p = parent(s) k, ma = absolute_simple_field(p) t = ma(evaluate(Hecke.reduce_mod_powers(preimage(ma, s), d))) - r = ma(root(preimage(ma, s//t), d))*r + rt = ma(root(preimage(ma, s//t), d)) + r *= inv(rt) s = t @hassert :SolveRadical 1 s == r^d end @@ -499,6 +500,7 @@ function Oscar.solve(f::ZZPolyRingElem; max_prec::Int=typemax(Int), show_radical #in a couple of places... scale = leading_coefficient(f) + @req is_squarefree(f) "Polynomial must be square-free" #switches check = true in hom and number_field on CHECK = get_assert_level(:SolveRadical) > 0 @@ -613,7 +615,7 @@ function Oscar.solve(f::ZZPolyRingElem; max_prec::Int=typemax(Int), show_radical h = hom(L, K, h_data..., check = CHECK) end else - @vtime :SolveRadical 2 Ra, hh = as_radical_extension(L, aut[i-length(pp)-1], zeta[findfirst(isequal(degree(L)), lp)]) + @vtime :SolveRadical 2 Ra, hh = as_radical_extension(L, aut[i-length(pp)-1], zeta[findfirst(isequal(degree(L)), lp)]; simplify) #hh: new -> old @vtime :SolveRadical 2 g = map_coefficients(h, parent(defining_polynomial(L))(preimage(hh, gen(L)))) diff --git a/experimental/GaloisGrp/test/runtests.jl b/experimental/GaloisGrp/test/runtests.jl index eada5b177b25..ba4a8f72d8d9 100644 --- a/experimental/GaloisGrp/test/runtests.jl +++ b/experimental/GaloisGrp/test/runtests.jl @@ -4,12 +4,30 @@ @test absolute_degree(K) == 12 @test length(r) == 3 + K, r = solve(x^3+3*x+5; simplify = true) + @test absolute_degree(K) == 12 + @test length(r) == 3 + + K, r = solve(x^3-3) + @test absolute_degree(K) == 6 + @test length(r) == 3 + @test all(x->x^3 == 3, r) + + K, r = solve(x^3-3; simplify = true) + @test absolute_degree(K) == 6 + @test length(r) == 3 + @test all(x->x^3 == 3, r) + Qt, t = rational_function_field(QQ, "t") Qtx, x = Qt["x"] F, a = function_field(x^6 + 108*t^2 + 108*t + 27) - subfields(F) + s = subfields(F) + @test length(s) == 4 G, = galois_group(F) @test is_isomorphic(G, symmetric_group(3)) + G,_, k = galois_group(F; overC = true) + @test is_isomorphic(G, cyclic_group(3)) + @test k isa AnticNumberField && degree(k) == 2 K, a = cyclotomic_field(3, "a", cached = false) G, C = galois_group(K) diff --git a/experimental/ModStd/ModStdQt.jl b/experimental/ModStd/ModStdQt.jl index 5ba7fc4231f5..a88d05ed3ab0 100644 --- a/experimental/ModStd/ModStdQt.jl +++ b/experimental/ModStd/ModStdQt.jl @@ -523,7 +523,7 @@ function ref_ff_rc!(M::MatElem{<:MPolyRingElem}) if iszero(M[k, j]) continue end -# g, a, b = gcd_with_cofactors(M[k, j], M[i, j]) + g, a, b = gcd_with_cofactors(M[k, j], M[i, j]) M[k, :] = b*M[k, :] - a * M[i, :] M[k, :] = divexact(M[k, :], content(M[k, :])) end diff --git a/src/NumberTheory/GaloisGrp/GaloisGrp.jl b/src/NumberTheory/GaloisGrp/GaloisGrp.jl index a4b4c896156a..cee93737b989 100644 --- a/src/NumberTheory/GaloisGrp/GaloisGrp.jl +++ b/src/NumberTheory/GaloisGrp/GaloisGrp.jl @@ -1946,15 +1946,25 @@ julia> roots(C, 2) (19^0 + O(19^2))*a + 11*19^0 + 19^1 + O(19^2) ``` """ -function galois_group(K::AnticNumberField, extra::Int = 5; useSubfields::Bool = true, pStart::Int = 2*degree(K), prime::Int = 0, algorithm::Symbol=:pAdic, field::Union{Nothing, AnticNumberField} = nothing) +function galois_group(K::AnticNumberField, extra::Int = 5; + useSubfields::Bool = true, + pStart::Int = 2*degree(K), + prime::Int = 0, + do_shape::Bool = true, + algorithm::Symbol=:pAdic, + field::Union{Nothing, AnticNumberField} = nothing) @assert algorithm in [:pAdic, :Complex, :Symbolic] + if do_shape + p, ct = find_prime(K.pol, pStart) + else + ct = Set{CycleType}() + @assert prime != 0 + end + if prime != 0 p = prime - ct = Set{CycleType}() - else - p, ct = find_prime(K.pol, pStart = pStart, prime = prime) end # TODO: otherwise, try to detect here if we are primitive or even 2-transitive @@ -2181,18 +2191,18 @@ end # rel. ext # ... function extension_field(f::ZZPolyRingElem, n::String = "_a"; cached::Bool = true, check::Bool = true) - return number_field(f, n, cached = cached, check = check) + return number_field(f, n; cached = cached, check = check) end function extension_field(f::QQPolyRingElem, n::String = "_a"; cached::Bool = true, check::Bool = true) - return number_field(f, n, cached = cached, check = check) + return number_field(f, n; cached = cached, check = check) end function extension_field(f::Generic.Poly{<:Generic.RationalFunctionFieldElem{T}}, n::String = "_a"; cached::Bool = true, check::Bool = true) where {T} - return function_field(f, n, cached = cached) + return function_field(f, n; cached = cached) end function extension_field(f::Generic.Poly{nf_elem}, n::String = "_a"; cached::Bool = true, check::Bool = true) - return number_field(f, n, cached = cached) + return number_field(f, n; cached = cached) end #Hecke.function_field(f::Generic.Poly{<:Generic.RationalFunctionFieldElem{T}}, n::String = "_a"; cached::Bool = true, check::Bool = true) where {T} = function_field(f, n, cached = cached) @@ -2632,6 +2642,7 @@ function Hecke.absolute_minpoly(a::Oscar.NfNSGenElem{QQFieldElem, QQMPolyRingEle end function blow_up(G::PermGroup, C::GaloisCtx, lf::Vector, con::PermGroupElem=one(G)) + if all(x->x[2] == 1, lf) return G, C end @@ -2673,6 +2684,25 @@ function galois_group(f::ZZPolyRingElem; pStart::Int = 2*degree(f), prime::Int = return galois_group(f(gen(Hecke.Globals.Qx)), pStart = pStart, prime = prime) end +@doc raw""" + are_disjoint(G::GaloisCtx, S::GaloisCtx) + +Tests if the splitting fields implicitly defined by G and S are +linearly disjoint, or equivalently, if the Galois group for the product +of the polynomials is the direct product of the groups. + +If the function returns true, the fields are provable disjoint, +false does not imply no-disjoint. +""" +function are_disjoint(G::GaloisCtx, S::GaloisCtx) + return false +end +function are_disjoint(G::GaloisCtx{T}, S::GaloisCtx{T}) where T <: Union{Hecke.qAdicRootCtx, ComplexRootCtx} + r = isone(gcd(order(G.G), order(S.G))) || + isone(gcd(ZZ(discriminant(G.f)), ZZ(discriminant(S.f)))) + return r +end + @doc raw""" galois_group(f::PolyRingElem{<:FieldElem}) @@ -2697,44 +2727,146 @@ function galois_group(f::PolyRingElem{<:FieldElem}; prime=0, pStart::Int = 2*deg p, ct = find_prime(g, pStart = pStart, prime = prime) C = [galois_group(extension_field(x, cached = false)[1], prime = p)[2] for x = lg] - G, emb, pro = inner_direct_product([x.G for x = C], morphisms = true) - - CC = GaloisCtx(g, p) - rr = roots(CC, 5, raw = true) #raw is necessary for non-monic case - #the scaling factor is the lead coeff - #thus not the same for all factors... - @assert length(Set(rr)) == length(rr) + #= Strategy: + sort factors into clusters where the splitting fields are disjoint. + There the resulting group is the direct product + + Within each cluster the group is a subdirect product + Maybe use Gap's SubDirectProducts for pairs? It is + supposed to be better than just doing everything + + Can I can MaximalSubDirectProducts? + + Plan: + - function to check if 2 GaloisCtx are disjoint. True if disjoiunt, + false if undecided + - use this to find the clusters + - deal with them + - form the big product + =# - d = map(frobenius, rr) - si = [findfirst(y->y==x, rr) for x = d] + g = Graph{Undirected}(length(C)) + for i=1:length(C) + for j=i+1:length(C) + if !are_disjoint(C[i], C[j]) + add_edge!(g, i, j) + end + end + end + cl = connected_components(g) + @vprint :GaloisGroup 1 "found $(length(cl)) connected components\n" + + res = Vector{Tuple{typeof(C[1]), PermGroupElem}}() + function setup(C::Vector{<:GaloisCtx}) + G, emb, pro = inner_direct_product([x.G for x = C], morphisms = true) + g = prod(x.f for x = C) + + CC = GaloisCtx(g, p) + rr = roots(CC, 5, raw = true) #raw is necessary for non-monic case + #the scaling factor is the lead coeff + #thus not the same for all factors... + @assert length(Set(rr)) == length(rr) + + d = map(frobenius, rr) + si = [findfirst(y->y==x, rr) for x = d] + + @vprint :GaloisGroup 1 "found Frobenius element: $si\n" + + k, mk = residue_field(parent(rr[1])) + rr = map(mk, rr) + po = Int[] + for GC = C + r = roots(GC, 5, raw = true) + K, mK = residue_field(parent(r[1])) + r = map(mK, r) + phi = Hecke.find_morphism(K, k) + po = vcat(po, [findfirst(x->x == phi(y), rr) for y = r]) + end - @vprint :GaloisGroup 1 "found Frobenius element: $si\n" + con = (symmetric_group(length(po))(po)) + G = G^con + CC.G = G #needed as a fallback if no descent is used + F = GroupFilter() + #= + function fi(x) + @show [pro[y](x^inv(con))[1] == C[y].G for y=1:length(C)] + return all(y->pro[y](x^inv(con))[1] == C[y].G, 1:length(C)) + end + push!(F, fi) + =# - k, mk = residue_field(parent(rr[1])) - rr = map(mk, rr) - po = Int[] - for GC = C - r = roots(GC, 5, raw = true) - K, mK = residue_field(parent(r[1])) - r = map(mK, r) - phi = Hecke.find_morphism(K, k) - po = vcat(po, [findfirst(x->x == phi(y), rr) for y = r]) + push!(F, x->!is_transitive(x), "subdirect case: group is transitive") + push!(F, x->all(y->pro[y](x^inv(con))[1] == C[y].G, 1:length(C)), "subdirect case: wrong projections") + return CC, G, F, G(si), con + end + for X = cl + @vprint :GaloisGroup 1 "dealing with factors $X ...\n" + if length(X) == 1 + push!(res, (C[X][1], one(C[X][1].G))) + else + function red(A::GaloisCtx, B::GaloisCtx) + CC, G, F, fr, con = setup([A, B]) + return descent(CC, G, F, fr, grp_id = x->(:-, :-))[2], con + end + local con + #= + G = C2 x C2 x C2 x C2 e.g. sqrt(2) sqrt(3) sqrt(5) sqrt(30) + (a,a) (a,a) with a: group combinations that have to be + a a testet + a a with b: groups that are not sub-direct enough + a a + a a + a a a + a a a + a a a a + a a a + a a a + + b + b + b + b + b b + b b + the idea is tp combine pairs in the hope that some descents can be + found on smaller groups (smaller support/ degree) + We'll have to test all possible subdirect subgroups, but this tries + smaller cases first + =# + @vprint :GaloisGroup 2 "entering combination tree..." + while length(X) > 1 + local p + d = map(x->degree(x.f), C[X]) + p = sortperm(d) + @vprint :GaloisGroup 2 "combining $(X[p[1:2]]) of degree $(degree(C[X[p[1]]].f)) and $(degree(C[X[p[2]]].f))\n" + Hecke.pushindent() + D, con = red(C[X[p[1]]], C[X[p[2]]]) + Hecke.popindent() + #delete the larger index as to not upset the smaller one + #then replace the Ctx at the smaller index + if p[1] < p[2] + deleteat!(X, p[2]) + C[X[p[1]]] = D + else + deleteat!(X, p[1]) + C[X[p[2]]] = D + end + end + push!(res, (C[X[1]], con)) + end end - con = (symmetric_group(length(po))(po)) - G = G^con - F = GroupFilter() -#= - function fi(x) - @show [pro[y](x^inv(con))[1] == C[y].G for y=1:length(C)] - return all(y->pro[y](x^inv(con))[1] == C[y].G, 1:length(C)) + @vprint :GaloisGroup 1 "combining clusters...\n" + if length(res) == 1 + CC = res[1][1] + G = CC.G + con = res[1][2] + else + CC, G, F, fr, con = setup([x[1] for x = res]) end - push!(F, fi) -=# + @vprint :GaloisGroup 1 "adding multiplicity...\n" - push!(F, x->all(y->pro[y](x^inv(con))[1] == C[y].G, 1:length(C)), "subdirect case: wrong projections") - G, C = descent(CC, G, F, G(si), grp_id = x->(:-,:-)) - return blow_up(G, C, lf, con) + return blow_up(G, CC, lf, con) end function Nemo.cyclotomic(n::Int, x::QQPolyRingElem) diff --git a/src/NumberTheory/GaloisGrp/Qt.jl b/src/NumberTheory/GaloisGrp/Qt.jl index b32ddd42f951..33736995e4c0 100644 --- a/src/NumberTheory/GaloisGrp/Qt.jl +++ b/src/NumberTheory/GaloisGrp/Qt.jl @@ -275,6 +275,14 @@ function galois_group(FF::Generic.FunctionField{QQFieldElem}; overC::Bool = fals F = GroupFilter() # no filter: need also intransitive groups # could restrict (possibly) to only those # cannot use short_cosets... + #TODO: the alg. closure of Q in the function + # field is normal, hence should be fixed + # by a normal group. If a decent is found + # for U, we can replace U by the core, + # the intersection of the conjugates + # (might be pointless: if U descents, then + # all conjugates also do, hence this is automatic) + # (it might save a cheap evaluation) descent(C, C.G, F, one(C.G), grp_id = x->order(x)) return C.G, C, fixed_field(S, C.G^pr) end @@ -283,6 +291,43 @@ function galois_group(FF::Generic.FunctionField{QQFieldElem}; overC::Bool = fals #if any fails, "t" was bad and I need to find a way of restarting end +#turn bivariate into univariate over rational function field +#trans decides which of the 2 becomes the rational transcendental +#the other is then the main polynomial variable +function to_uni(f::MPolyRingElem, trans::Int = 1) + @assert nvars(parent(f)) == 2 + k = base_ring(f) + Qs, s = rational_function_field(k, "s", cached = false) + Qst, t = polynomial_ring(Qs, "t", cached = false) + if trans == 1 + ev = [Qst(s), t] + else + ev = [t, Qst(s)] + end + return evaluate(f, ev) +end + +@doc raw""" + function_field(f::MPolyRingElem, trans::Int, s::VarName = :_a; cached::Bool = false, check::Bool = true) + +Let $f$ in $K[s, t]$ for trans == 1, return the function field $K(s)/f$ +otherwise $K(t)/f$. +""" +function Oscar.function_field(f::MPolyRingElem, trans::Int, s::VarName = :_a; cached::Bool = false, check::Bool = true) + return function_field(to_uni(f, trans), s; cached) +end + +@doc raw""" + galois_group(f::QQMPolyRingElem, trans::Int = 1) + +Let $f \in QQ[s,t]$, then this computes +the Galois group of $f$ as an element in $K(s)[t]$ if trans = 1 or +as $K(t)[s]$ if trans = 2. +""" +function galois_group(f::QQMPolyRingElem, trans::Int = 1) + return galois_group(to_uni(f, trans)) +end + """ subfields(FF:Generic.FunctionField{QQFieldElem})