From 2688cf6972be0c6577d2bf3f6848b7a5875830f4 Mon Sep 17 00:00:00 2001 From: Tommy Hofmann Date: Sat, 23 Nov 2024 09:12:11 +0100 Subject: [PATCH] feat: add iso_oscar_singular_* - use it in some places --- docs/Project.toml | 1 + src/Rings/Rings.jl | 1 + src/Rings/mpoly.jl | 11 +- src/Rings/oscar_singular.jl | 458 +++++++++++++++++++++++++++++++++++ test/Rings/oscar_singular.jl | 65 +++++ 5 files changed, 532 insertions(+), 4 deletions(-) create mode 100644 src/Rings/oscar_singular.jl create mode 100644 test/Rings/oscar_singular.jl diff --git a/docs/Project.toml b/docs/Project.toml index 6dad0075f817..a018332d471c 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -2,6 +2,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +Oscar = "f1435218-dba5-11e9-1e4d-f1a5fab5fc13" [compat] Documenter = "1.1" diff --git a/src/Rings/Rings.jl b/src/Rings/Rings.jl index 4c25c36d612b..faf464c711f9 100644 --- a/src/Rings/Rings.jl +++ b/src/Rings/Rings.jl @@ -9,6 +9,7 @@ include("groebner/groebner.jl") include("solving.jl") include("MPolyQuo.jl") include("FractionalIdeal.jl") +include("oscar_singular.jl") include("special_ideals.jl") diff --git a/src/Rings/mpoly.jl b/src/Rings/mpoly.jl index ce2543562705..33021ff8ae39 100644 --- a/src/Rings/mpoly.jl +++ b/src/Rings/mpoly.jl @@ -132,6 +132,7 @@ mutable struct BiPolyArray{S} Ox::NCRing #Oscar Poly Ring or Algebra O::Vector{S} Sx # Singular Poly Ring or Algebra, poss. with different ordering + f #= isomorphism Ox -> Sx =# S::Singular.sideal function BiPolyArray(O::Vector{T}) where {T <: NCRingElem} @@ -381,7 +382,8 @@ function singular_generators(B::IdealGens, monorder::MonomialOrdering=default_or # in case of quotient rings, monomial ordering is ignored so far in singular_poly_ring isa(B.gens.Ox, MPolyQuoRing) && return B.gens.S isdefined(B, :ord) && B.ord == monorder && monomial_ordering(B.Ox, Singular.ordering(base_ring(B.S))) == B.ord && return B.gens.S - SR = singular_poly_ring(B.Ox, monorder) + g = iso_oscar_singular_poly_ring(B.Ox, monorder) + SR = codomain(g) f = Singular.AlgebraHomomorphism(B.Sx, SR, gens(SR)) S = Singular.map_ideal(f, B.gens.S) if isdefined(B, :ord) && B.ord == monorder @@ -481,7 +483,6 @@ for T in [:MPolyRing, :(AbstractAlgebra.Generic.MPolyRing)] end end - #Note: Singular crashes if it gets Nemo.ZZ instead of Singular.ZZ ((Coeffs(17)) instead of (ZZ)) singular_coeff_ring(::ZZRing) = Singular.Integers() singular_coeff_ring(::QQField) = Singular.Rationals() @@ -746,8 +747,10 @@ end function singular_assure(I::IdealGens) if !isdefined(I.gens, :S) - I.gens.Sx = singular_poly_ring(I.Ox; keep_ordering = I.keep_ordering) - I.gens.S = Singular.Ideal(I.Sx, elem_type(I.Sx)[I.Sx(x) for x = I.O]) + g = iso_oscar_singular_poly_ring(I.Ox; keep_ordering = I.keep_ordering) + I.gens.Sx = codomain(g) + I.gens.f = g + I.gens.S = Singular.Ideal(I.gens.Sx, elem_type(I.gens.Sx)[g(x) for x = I.gens.O]) end if I.isGB && (!isdefined(I, :ord) || I.ord == monomial_ordering(I.gens.Ox, internal_ordering(I.gens.Sx))) I.gens.S.isGB = true diff --git a/src/Rings/oscar_singular.jl b/src/Rings/oscar_singular.jl new file mode 100644 index 000000000000..a7860998eb5c --- /dev/null +++ b/src/Rings/oscar_singular.jl @@ -0,0 +1,458 @@ +############################################################################## +# +# Conversion to and from Singular: in particular, some Rings are +# special as they exist natively in Singular and thus should be used +# +############################################################################## +# +# Needs convert(Target(Ring), elem) +# Ring(s.th.) +# +# Singular's polynomial rings are not recursive: +# 1. singular_poly_ring(R::Ring) tries to create a Singular.PolyRing (with +# elements of type Singular.spoly) isomorphic to R +# 2. singular_coeff_ring(R::Ring) tries to create a ring isomorphic to R that is +# acceptable to Singular.jl as 'coefficients' +# +# a real native Singular polynomial ring with Singular's native QQ as coefficients: +# singular_poly_ring(QQ[t]) => Singular.PolyRing{Singular.n_Q} +# +# Singular's native Fp(5): +# singular_coeff_ring(GF(5)) => Singular.N_ZpField +# +# Singular wrapper of the Oscar type QQPolyRingElem: +# singular_coeff_ring(QQ[t]) => Singular.N_Ring{QQPolyRingElem} +# +# even more wrappings of the immutable Oscar type FpFieldElem: +# singular_coeff_ring(GF(ZZRingElem(5))) => Singular.N_Field{Singular.FieldElemWrapper{FpField, FpFieldElem}} + +""" + iso_oscar_singular_coeff_ring(R::Ring) -> Map + +Return a ring isomorphism of `R` onto a native Singular ring +""" +iso_oscar_singular_coeff_ring + +""" + iso_oscar_singular_poly_ring(R::Ring, ...; kw...) -> Map + +Given a polynomial ring `R[x_1,...x_n]` return a ring isomorphism onto a native +Singular ring `S[x_1,...,x_n]` with `R` isomorphic to `S`. +""" +iso_oscar_singular_poly_ring + +abstract type OscarSingularCoefficientRingMap{S, T} <: Map{S, T, Any, Any} end + +domain(f::OscarSingularCoefficientRingMap) = f.R + +codomain(f::OscarSingularCoefficientRingMap) = f.S + +# fallback +function image(f::OscarSingularCoefficientRingMap, x) + parent(x) !== domain(f) && error("Element not in domain") + return codomain(f)(x) +end + +(f::OscarSingularCoefficientRingMap)(x) = image(f, x) + +function preimage(f::OscarSingularCoefficientRingMap, x) + parent(x) !== codomain(f) && error("Element not in codomain") + return domain(f)(x) +end + +# ZZ +struct OscarSingularCoefficientRingMapZZ <: OscarSingularCoefficientRingMap{ZZRing, Singular.Integers} + R::ZZRing # singleton, but keep for consistency + S::Singular.Integers +end + +iso_oscar_singular_coeff_ring(R::ZZRing) = OscarSingularCoefficientRingMapZZ(R, Singular.Integers()) + +# conversion is unambigous via parent object call + +# QQ +struct OscarSingularCoefficientRingMapQQ <: OscarSingularCoefficientRingMap{QQField, Singular.Rationals} + R::QQField # singleton, but keep for consistency + S::Singular.Rationals +end + +iso_oscar_singular_coeff_ring(R::QQField) = OscarSingularCoefficientRingMapQQ(R, Singular.Rationals()) + +# prime field, small characteristic +struct OscarSingularCoefficientRingMapfpField <: OscarSingularCoefficientRingMap{fpField, Singular.N_ZpField} + R::fpField + S::Singular.N_ZpField +end + +iso_oscar_singular_coeff_ring(R::fpField) = OscarSingularCoefficientRingMapfpField(R, Singular.Fp(Int(characteristic(R)))) + +# ZZ/nZZ, n small and big +struct OscarSingularCoefficientRingMapZZMod{U} <: OscarSingularCoefficientRingMap{U, Singular.N_ZpField} + R::U + S::Singular.N_ZnRing +end + +iso_oscar_singular_coeff_ring(R::U) where {U <: Union{zzModRing, ZZModRing}} = OscarSingularCoefficientRingMapZZMod{U}(R, Singular.residue_ring(Singular.Integers(), BigInt(modulus(R)))[1] +) + +# QQ(a) +struct OscarSingularCoefficientRingMapAbsSimpleNumField <: OscarSingularCoefficientRingMap{AbsSimpleNumField, Singular.N_ZpField} + R::AbsSimpleNumField + S::Singular.N_AlgExtField +end + +function iso_oscar_singular_coeff_ring(R::AbsSimpleNumField) + minpoly = defining_polynomial(R) + Qa = parent(minpoly) + a = gen(Qa) + SQa, (Sa,) = Singular.FunctionField(Singular.QQ, _variables_for_singular(symbols(Qa))) + Sminpoly = SQa(coeff(minpoly, 0)) + for i in 1:degree(minpoly) + Sminpoly += SQa(coeff(minpoly, i))*Sa^i + end + SK, _ = Singular.AlgebraicExtensionField(SQa, Sminpoly) + return OscarSingularCoefficientRingMapAbsSimpleNumField(R, SK) +end + +# It is nonsense, but the conversion resides in src/number/n_algExt.jl +# via parent object call overloading + +# GF(p, n), small p + +struct OscarSingularCoefficientRingMapfqPolyRepField <: OscarSingularCoefficientRingMap{fqPolyRepField, Singular.N_ZpField} + R::fqPolyRepField + S::Singular.N_AlgExtField +end + +function iso_oscar_singular_coeff_ring(F::fqPolyRepField) + # TODO: the Fp(Int(char)) can throw + minpoly = modulus(F) + Fa = parent(minpoly) + SFa, (Sa,) = Singular.FunctionField(Singular.Fp(Int(characteristic(F))), + _variables_for_singular(symbols(Fa))) + Sminpoly = SFa(coeff(minpoly, 0)) + for i in 1:degree(minpoly) + Sminpoly += SFa(coeff(minpoly, i))*Sa^i + end + SF, _ = Singular.AlgebraicExtensionField(SFa, Sminpoly) + return OscarSingularCoefficientRingMapfqPolyRepField(F, SF) +end + +# Finite field (FqField) +# +struct OscarSingularCoefficientRingMapFqField <: OscarSingularCoefficientRingMap{FqField, Singular.N_ZpField} + R::FqField + S#= unstable =# + iso::MapFromFunc{FqField, FqField} + + OscarSingularCoefficientRingMapFqField(R, S) = new(R, S) + OscarSingularCoefficientRingMapFqField(R, S, iso) = new(R, S, iso) +end + +function _absolute_field(F::FqField) + if is_absolute(F) + error("don't use me, you are already absolute") + end + Kx = parent(defining_polynomial(F)) + Fabs, QQtoFabs = Nemo._residue_field(defining_polynomial(F); absolute = true, check = false) + Fabsx, = polynomial_ring(Fabs, :x; cached = false) + return Fabs, MapFromFunc(Fabs, F, a -> F(preimage(QQtoFabs, a)), b -> begin a = Fabs(); Nemo.set!(a, b); a end) +end + +# Nonsense for FqField (aka fq_default from flint) +function iso_oscar_singular_coeff_ring(F::FqField) + # we are way beyond type stability, so just do what you want + + if !is_absolute(F) + Fabs, FabstoF = _absolute_field(F) + S = singular_coeff_ring(Fabs) + return OscarSingularCoefficientRingMapFqField(F, S, FabstoF) + end + + S = singular_coeff_ring(F) + + return OscarSingularCoefficientRingMapFqField(F, S) +end + +function image(f::OscarSingularCoefficientRingMapFqField, a::FqFieldElem) + parent(a) !== domain(f) && error("Element not in domain") + + if codomain(f) isa Singular.N_ZpField + return codomain(f)(lift(ZZ, a)) + end + + if codomain(f) isa Singular.N_Field + return codomain(f)(a) + end + + if isdefined(f, :iso) + b = _fq_field_to_n_algext(codomain(f), preimage(f.iso, a)) + else + b = _fq_field_to_n_algext(codomain(f), a) + end + @assert parent(b) == codomain(f) + return b +end + +function preimage(f::OscarSingularCoefficientRingMapFqField, a::Singular.n_FieldElem) + parent(a) !== codomain(f) && error("Element not in codomain") + return Singular.libSingular.julia(Singular.libSingular.cast_number_to_void(a.ptr))::FqFieldElem +end + +function preimage(f::OscarSingularCoefficientRingMapFqField, a::Singular.n_Zp) + parent(a) !== codomain(f) && error("Element not in codomain") + return domain(f)(Int(a)) +end + +function preimage(f::OscarSingularCoefficientRingMapFqField, a::Singular.n_algExt) + parent(a) !== codomain(f) && error("Element not in codomain") + + if isdefined(f, :iso) + b = image(f.iso, _n_algExt_to_fqfield(domain(f.iso), a)) + else + b = _n_algExt_to_fqfield(domain(f), a) + end + @assert parent(b) == domain(f) + return b +end + +function _fq_field_to_n_algext(SF, a::FqFieldElem) + F = parent(a) + SFa = gen(SF) + res = SF(lift(ZZ, coeff(a, 0))) + for i in 1:degree(F)-1 + res += SF(lift(ZZ, coeff(a, i)))*SFa^i + end + return res +end + +function _n_algExt_to_fqfield(K::FqField, a::Singular.n_algExt) + SK = parent(a) + SF = parent(Singular.modulus(SK)) + SFa = SF(a) + numSa = Singular.n_transExt_to_spoly(numerator(SFa)) + denSa = first(AbstractAlgebra.coefficients(Singular.n_transExt_to_spoly(denominator(SFa)))) + @assert isone(denSa) + res = zero(K) + Ka = gen(K) + for (c, e) in zip(AbstractAlgebra.coefficients(numSa), AbstractAlgebra.exponent_vectors(numSa)) + res += K(Int(c))*Ka^e[1] + end + return res +end + +# fraction field of polynomial rings over QQ and Fp +struct OscarSingularCoefficientRingMapFractionField{U, V, W, X} <: OscarSingularCoefficientRingMap{U, V} + R::U + S::V + g::W + Spoly::X #= we create univariate polynomials over S during the conversion =# +end + +function _map_oscar_singular_univariate(Rx, g, f::Singular.spoly) + @assert base_ring(Rx) === domain(g) + @assert ngens(parent(f)) == 1 + return Rx(preimage.(Ref(g), coefficients_of_univariate(f))) +end + +function iso_oscar_singular_coeff_ring(F::AbstractAlgebra.Generic.FracField{<:PolyRingElem{T}}) where {T <: Union{FqField, QQField}} + R = base_ring(F) + g = iso_oscar_singular_coeff_ring(base_ring(R)) + S, = Singular.FunctionField(codomain(g), [var(R)]) + Sx, = polynomial_ring(S, :x; cached = false) + return OscarSingularCoefficientRingMapFractionField(F, S, g, Sx) +end + +function preimage(f::OscarSingularCoefficientRingMapFractionField, a::Singular.n_transExt) + parent(a) !== codomain(f) && error("Element not in codomain") + F = domain(f) + R = base_ring(F) + n, d = Singular.n_transExt_to_spoly.([numerator(a), denominator(a)]; cached = false) + return F(_map_oscar_singular_univariate(R, f.g, n), _map_oscar_singular_univariate(R, f.g, d)) +end + +function image(f::OscarSingularCoefficientRingMapFractionField, a) + parent(a) !== domain(f) && error("Element not in domain") + F = base_ring(base_ring(domain(f))) # the F in domain(f) = F(X) + K = codomain(f) + @assert Singular.transcendence_degree(K) == 1 "wrong number of generators" + t, = Singular.transcendence_basis(K) + # It must be a transcendental extension of Q or Fp (other things are not supported) + n = map_coefficients(numerator(a); parent = f.Spoly) do x + if F isa FinField + K(lift(ZZ, x)) + else + K(f.g(x)) + end + end + d = map_coefficients(denominator(a)) do x + if F isa FinField + K(lift(ZZ, x)) + else + K(f.g(x)) + end + end + return divexact(n(t), d(t)) +end + +# rational function field +struct OscarSingularCoefficientRingMapRationalFunctionField{U, V, W} <: OscarSingularCoefficientRingMap{U, V} + R::U + S::V + g::W +end + +function iso_oscar_singular_coeff_ring(R::Generic.RationalFunctionField) + g = iso_oscar_singular_coeff_ring(R.fraction_field) + return OscarSingularCoefficientRingMapRationalFunctionField(R, codomain(g), g) +end + +function image(f::OscarSingularCoefficientRingMapRationalFunctionField, a) + parent(a) !== domain(f) && error("Element not in domain") + return f.g(a.d) +end + +function preimage(f::OscarSingularCoefficientRingMapRationalFunctionField, a) + parent(a) !== codomain(f) && error("Element not in codomain") + return domain(f)(preimage(f.g, a)) +end + +# catchall +struct OscarSingularCoefficientRingMapGeneric{U, V} <: OscarSingularCoefficientRingMap{U, V} + R::U + S::V +end + +function iso_oscar_singular_coeff_ring(F::AbstractAlgebra.Ring) + return OscarSingularCoefficientRingMapGeneric(F, Singular.CoefficientRing(F)) +end + +# image done by parent call overloading + +function preimage(f::OscarSingularCoefficientRingMapGeneric, a::Singular.n_unknown) + parent(a) !== codomain(f) && error("Element not in codomain") + b = Singular.libSingular.julia(Singular.libSingular.cast_number_to_void(a.ptr)) + return b::elem_type(domain(f)) +end + +# Singular polynomial ring + +struct OscarSingularPolyRingMap{U, V, W} <: Map{U, V, Any, Any} + R::U + S::V + f::W +end + +domain(f::OscarSingularPolyRingMap) = f.R + +codomain(f::OscarSingularPolyRingMap) = f.S + +(f::OscarSingularPolyRingMap)(x) = image(f, x) + +function iso_oscar_singular_poly_ring(Rx::MPolyRing; keep_ordering::Bool = false) + fcoeff = iso_oscar_singular_coeff_ring(base_ring(Rx)) + S = codomain(fcoeff) + if keep_ordering + Sx = Singular.polynomial_ring(S, + _variables_for_singular(symbols(Rx)), + ordering = internal_ordering(Rx), + cached = false)[1] + else + Sx = Singular.polynomial_ring(S, + _variables_for_singular(symbols(Rx)), + cached = false)[1] + end + return OscarSingularPolyRingMap(Rx, Sx, fcoeff) +end + +function iso_oscar_singular_poly_ring(Rx::MPolyRing, ord::Symbol) + fcoeff = iso_oscar_singular_coeff_ring(base_ring(Rx)) + S = codomain(fcoeff) + Sx = Singular.polynomial_ring(S, + _variables_for_singular(symbols(Rx)), + ordering = ord, + cached = false)[1] + return OscarSingularPolyRingMap(Rx, Sx, fcoeff) +end + +function iso_oscar_singular_poly_ring(Rx::MPolyRing, ord::Singular.sordering) + fcoeff = iso_oscar_singular_coeff_ring(base_ring(Rx)) + S = codomain(fcoeff) + Sx = Singular.polynomial_ring(S, + _variables_for_singular(symbols(Rx)), + ordering = ord, + cached = false)[1] + return OscarSingularPolyRingMap(Rx, Sx, fcoeff) +end + +function iso_oscar_singular_poly_ring(Rx::MPolyRing, ord::MonomialOrdering) + fcoeff = iso_oscar_singular_coeff_ring(base_ring(Rx)) + S = codomain(fcoeff) + Sx = Singular.polynomial_ring(S, + _variables_for_singular(symbols(Rx)), + ordering = singular(ord), + cached = false)[1] + return OscarSingularPolyRingMap(Rx, Sx, fcoeff) +end + +function image(f::OscarSingularPolyRingMap, a) + parent(a) !== domain(f) && error("Element not in domain") + g = MPolyBuildCtx(codomain(f)) + for (c, e) = zip(Nemo.coefficients(a), Nemo.exponent_vectors(a)) + push_term!(g, f.f(c), e) + end + return finish(g) +end + +function preimage(f::OscarSingularPolyRingMap, a; check = true) + check && (parent(a) === codomain(f) || error("Element not in codomain")) + g = MPolyBuildCtx(domain(f)) + for (c, e) = Base.Iterators.zip(AbstractAlgebra.coefficients(a), AbstractAlgebra.exponent_vectors(a)) + push_term!(g, preimage(f.f, c), e) + end + return finish(g) +end + +# Quotient rings + +struct OscarSingularPolyRingQuoMap{U, V, W} <: Map{U, V, Any, Any} + R::U + S::V + f::W #= isomorphism of the underlying "base rings" =# +end + +domain(f::OscarSingularPolyRingQuoMap) = f.R + +codomain(f::OscarSingularPolyRingQuoMap) = f.S + +function (f::OscarSingularPolyRingQuoMap)(a) + return image(f, a) +end + +function _iso_oscar_singular_poly_ring(R::MPolyQuoRing) + _groebner_basis(R) + Rorig = base_ring(R) + f = iso_oscar_singular_poly_ring(Rorig) + @assert base_ring(codomain(f)) === base_ring(R.SQR) + return OscarSingularPolyRingQuoMap(R, R.SQR, f) +end + +function image(f::OscarSingularPolyRingQuoMap, b::MPolyQuoRingElem) + @assert parent(b) === domain(f) + a = b.f + return image(f, a) +end + +# for some reason this is used +function image(f::OscarSingularPolyRingQuoMap, b::MPolyRingElem) + @assert parent(b) === base_ring(domain(f)) + return codomain(f)(b) +end + +function preimage(f::OscarSingularPolyRingQuoMap, a::Singular.spoly) + @assert parent(a) === codomain(f) + return MPolyQuoRingElem(preimage(f.f, a; check = false), domain(f)) +end + +iso_oscar_singular_poly_ring(Q::MPolyQuoRing; keep_ordering::Bool = false) = _iso_oscar_singular_poly_ring(Q) +iso_oscar_singular_poly_ring(Q::MPolyQuoRing, ordering::MonomialOrdering) = _iso_oscar_singular_poly_ring(Q) diff --git a/test/Rings/oscar_singular.jl b/test/Rings/oscar_singular.jl new file mode 100644 index 000000000000..b6a46e47ad1a --- /dev/null +++ b/test/Rings/oscar_singular.jl @@ -0,0 +1,65 @@ +@testset "Oscar-Singular conversion" begin + Qx, x = QQ[:x] + K, a = number_field(x^3 + 2) + S, = rational_function_field(K, "a") + R1, = residue_ring(ZZ, 2) + R2, = residue_ring(ZZ, ZZ(2)^100) + FFrel = let # relative finite field + _, x = GF(4)[:x] + finite_field(x^3 + x + 1)[1] + end + Krel = let + _, x = K[:x] + number_field(x^2 - 3)[1] + end + FFt, = rational_function_field(GF(2), :t); + QQt, = rational_function_field(QQ, :t); + + test_rings = (ZZ, QQ, Qx, + Nemo.Native.GF(2), GF(2), GF(2, 2), GF(next_prime(ZZ(2)^70)), + GF(next_prime(ZZ(2)^70), 2), FFrel, abelian_closure(QQ)[1], + K, Krel, FFt, QQt) + + for R in test_rings + f = Oscar.iso_oscar_singular_coeff_ring(R) + @test domain(f) === R + @test is_one(f(one(R))) + @test is_zero(f(zero(R))) + for i in 1:10 + a = R(rand(ZZ, -10:10)) + b = R(rand(ZZ, -10:10)) + @test f(a) + f(b) == f(a + b) + @test f(a) * f(b) == f(a * b) + @test preimage(f, f(a)) == a + end + + Rx, (x, y) = polynomial_ring(R, [:x, :y]) + g = Oscar.iso_oscar_singular_poly_ring(Rx) + @test domain(g) === Rx + for i in 1:10 + a = R(rand(ZZ, -10:10)) + b = R(rand(ZZ, -10:10)) + e = rand(1:10) + f = rand(1:10) + h = a + x^e + b * y^f + @test g(h)^2 == g(h^2) + @test preimage(g, g(h)) == h + end + + if R isa Field + Q, = quo(Rx, [x^2 - 1]) + x, y = gens(Q) + g = Oscar.iso_oscar_singular_poly_ring(Q) + @test domain(g) === Q + for i in 1:10 + a = R(rand(ZZ, -10:10)) + b = R(rand(ZZ, -10:10)) + e = rand(1:10) + f = rand(1:10) + h = a + x^e + b * y^f + @test g(h)^2 == g(h^2) + @test preimage(g, g(h)) == h + end + end + end +end