From 1a92bf0ba9bebf95747f736ff0ddfa8b949d3440 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 17 Jun 2025 08:52:53 +0200 Subject: [PATCH 1/2] Expand the common code for ideal implementations This includes the addition of a `DefaultIdealSet` struct type which can be used with zero overhead, and hopefully can simply be used as parent type for all ideal implementations (in Hecke, Oscar or wherever) down the road. Also semi-documented the interfaces to be implemented (the list is surely incomplete but better than nothing). --- src/AbstractAlgebra.jl | 4 ++ src/ConcreteTypes.jl | 10 ++- src/Ideal.jl | 137 ++++++++++++++++++++++++++++++++++++----- src/generic/Ideal.jl | 26 ++------ 4 files changed, 138 insertions(+), 39 deletions(-) diff --git a/src/AbstractAlgebra.jl b/src/AbstractAlgebra.jl index d59f18268e..7dafa5ba65 100644 --- a/src/AbstractAlgebra.jl +++ b/src/AbstractAlgebra.jl @@ -223,6 +223,10 @@ function check_parent(a, b, throw::Bool = true) return flag end +function check_base_ring(a, b) + base_ring(a) === base_ring(b) || error("base rings do not match") +end + include("algorithms/LaurentPoly.jl") include("algorithms/FinField.jl") include("algorithms/GenericFunctions.jl") diff --git a/src/ConcreteTypes.jl b/src/ConcreteTypes.jl index b1dbdc4740..0c16f5a7ca 100644 --- a/src/ConcreteTypes.jl +++ b/src/ConcreteTypes.jl @@ -1,9 +1,10 @@ ############################################################################### # -# Mat space +# Parent types # ############################################################################### +# parent type for matrices struct MatSpace{T <: NCRingElement} <: Module{T} base_ring::NCRing nrows::Int @@ -16,3 +17,10 @@ struct MatSpace{T <: NCRingElement} <: Module{T} return new{T}(R, r, c) end end + +# parent type for two-sided ideals +struct DefaultIdealSet{T <: NCRingElement} <: IdealSet{T} + base_ring::NCRing + + DefaultIdealSet(R::S) where {S <: NCRing} = new{elem_type(S)}(R) +end diff --git a/src/Ideal.jl b/src/Ideal.jl index e2f7f60a97..f7759b6262 100644 --- a/src/Ideal.jl +++ b/src/Ideal.jl @@ -1,14 +1,54 @@ ############################################################################### # -# Ideal constructor +# Ideal.jl : Generic functionality for two-sided ideals +# +# +# A the very least, an implementation of an Ideal subtype should provide the +# following methods: +# - ideal_type(::Type{MyRingType}) = MyIdealType +# - ideal(R::MyRingType, xs::Vector{MyRingElemType})::MyIdealType +# - base_ring(I::MyIdealType) +# - gen(I::MyIdealType, k::Int) +# - gens(I::MyIdealType) +# - ngens(I::MyIdealType) +# - Base.in(v::MyRingElemType, I::MyIdealType) +# - ... +# +# Many other functions are then automatically derived from these. +############################################################################### + +############################################################################### +# +# Type and parent functions +# +############################################################################### + +# fundamental interface, to be documented +ideal_type(x) = ideal_type(typeof(x)) +ideal_type(T::DataType) = throw(MethodError(ideal_type, (T,))) + +# +parent(I::Ideal) = DefaultIdealSet(base_ring(I)) + +parent_type(::Type{<:Ideal{T}}) where {T} = DefaultIdealSet{T} + +# +base_ring(S::DefaultIdealSet) = S.base_ring::base_ring_type(S) + +base_ring_type(::Type{<:IdealSet{T}}) where {T} = parent_type(T) + +elem_type(::Type{<:IdealSet{T}}) where {T} = ideal_type(parent_type(T)) + +############################################################################### +# +# Ideal constructors # ############################################################################### -# We assume that the function -# ideal(R::T, xs::Vector{U}) -# with U === elem_type(T) is implemented by anyone implementing ideals -# for AbstractAlgebra rings. -# The functions in this file extend the interface for `ideal`. +# All constructors ultimately delegate to a method +# ideal(R::T, xs::Vector{U}) where T <: NCRing +# and U === elem_type(T) +ideal(R::T, xs::Vector{S}) where {T <: NCRing, S <: NCRingElement} = ideal_type(T)(R, xs) # the following helper enables things like `ideal(R, [])` or `ideal(R, [1])` # the type check ensures we don't run into an infinite recursion @@ -21,6 +61,66 @@ function ideal(R::NCRing, x, y...; kw...) return ideal(R, elem_type(R)[R(z) for z in [x, y...]]; kw...) end +function ideal(x::NCRingElement; kw...) + return ideal(parent(x), x; kw...) +end + +function ideal(xs::AbstractVector{T}; kw...) where T<:NCRingElement + @req !is_empty(xs) "Empty collection, cannot determine parent ring. Try ideal(ring, xs) instead of ideal(xs)" + return ideal(parent(xs[1]), xs; kw...) +end + +############################################################################### +# +# Basic predicates +# +############################################################################### + +iszero(I::Ideal) = all(iszero, gens(I)) + +function is_subset(I::T, J::T) where {T <: Ideal} + I === J && return true + check_base_ring(I, J) + return all(x in J for x in gens(I)) +end + +############################################################################### +# +# Comparison +# +############################################################################### + +function Base.:(==)(I::T, J::T) where {T <: Ideal} + return is_subset(I, J) && is_subset(J, I) +end + +function Base.:hash(I::T, h::UInt) where {T <: Ideal} + h = hash(base_ring(I), h) + return h +end + +############################################################################### +# +# Binary operations +# +############################################################################### + +function Base.:+(I::T, J::T) where {T <: Ideal} + check_base_ring(I, J) + return ideal(base_ring(I), vcat(gens(I), gens(J))) +end + +function Base.:*(I::T, J::T) where {T <: Ideal} + check_base_ring(I, J) + return ideal(base_ring(I), [x*y for x in gens(I) for y in gens(J)]) +end + +############################################################################### +# +# Ad hoc binary operations +# +############################################################################### + function *(R::Ring, x::RingElement) return ideal(R, x) end @@ -29,19 +129,22 @@ function *(x::RingElement, R::Ring) return ideal(R, x) end -function ideal(x::NCRingElement; kw...) - return ideal(parent(x), x; kw...) +function *(I::Ideal{T}, p::T) where T <: RingElement + R = base_ring(I) + iszero(p) && return ideal(R, T[]) + return ideal(R, [v*p for v in gens(I)]) end -function ideal(xs::AbstractVector{T}; kw...) where T<:NCRingElement - @req !is_empty(xs) "Empty collection, cannot determine parent ring. Try ideal(ring, xs) instead of ideal(xs)" - return ideal(parent(xs[1]), xs; kw...) +function *(p::T, I::Ideal{T}) where T <: RingElement + return I*p end -iszero(I::Ideal) = all(iszero, gens(I)) - -base_ring_type(::Type{<:IdealSet{T}}) where T <: RingElement = parent_type(T) +function *(I::Ideal{T}, p::S) where {S <: RingElement, T <: RingElement} + R = base_ring(I) + iszero(p*one(R)) && return ideal(R, T[]) + return ideal(R, [v*p for v in gens(I)]) +end -# fundamental interface, to be documented -ideal_type(x) = ideal_type(typeof(x)) -ideal_type(T::DataType) = throw(MethodError(ideal_type, (T,))) +function *(p::S, I::Ideal{T}) where {S <: RingElement, T <: RingElement} + return I*p +end diff --git a/src/generic/Ideal.jl b/src/generic/Ideal.jl index bede8b7db1..2a7956af92 100644 --- a/src/generic/Ideal.jl +++ b/src/generic/Ideal.jl @@ -34,7 +34,11 @@ parent_type(::Type{Ideal{S}}) where S <: RingElement = IdealSet{S} Return a list of generators of the ideal `I` in reduced form and canonicalised. """ -gens(I::Ideal{T}) where T <: RingElement = I.gens +gens(I::Ideal) = I.gens + +number_of_generators(I::Ideal) = length(I.gens) + +gen(I::Ideal, i::Int) = I.gens[i] ############################################################################### # @@ -2204,26 +2208,6 @@ function intersect(I::Ideal{T}, J::Ideal{T}) where {U <: RingElement, T <: Abstr return Ideal(S, GInt) end -############################################################################### -# -# Binary operations -# -############################################################################### - -function +(I::Ideal{T}, J::Ideal{T}) where T <: RingElement - R = base_ring(I) - G1 = gens(I) - G2 = gens(J) - return Ideal(R, vcat(G1, G2)) -end - -function *(I::Ideal{T}, J::Ideal{T}) where T <: RingElement - R = base_ring(I) - G1 = gens(I) - G2 = gens(J) - return Ideal(R, [v*w for v in G1 for w in G2]) -end - ############################################################################### # # Ad hoc binary operations From e460676f0d7e115da1168e1d0000df96cd216c49 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Tue, 17 Jun 2025 12:35:30 +0200 Subject: [PATCH 2/2] similar... --- src/Ideal.jl | 29 +++++++++++++-------- src/generic/Ideal.jl | 60 +++----------------------------------------- 2 files changed, 23 insertions(+), 66 deletions(-) diff --git a/src/Ideal.jl b/src/Ideal.jl index f7759b6262..4cc9cd4540 100644 --- a/src/Ideal.jl +++ b/src/Ideal.jl @@ -70,6 +70,12 @@ function ideal(xs::AbstractVector{T}; kw...) where T<:NCRingElement return ideal(parent(xs[1]), xs; kw...) end +function Base.similar(I::T, xs::Vector) where {T <: Ideal} + R = base_ring(I) + @assert T === ideal_type(R) + return ideal(R, xs) +end + ############################################################################### # # Basic predicates @@ -78,10 +84,15 @@ end iszero(I::Ideal) = all(iszero, gens(I)) -function is_subset(I::T, J::T) where {T <: Ideal} +@doc raw""" + Base.issubset(I::T, J::T) where {T <: Ideal} + +Return `true` if the ideal `I` is a subset of the ideal `J`. +""" +function Base.issubset(I::T, J::T) where {T <: Ideal} I === J && return true check_base_ring(I, J) - return all(x in J for x in gens(I)) + return all(in(J), gens(I)) end ############################################################################### @@ -107,12 +118,12 @@ end function Base.:+(I::T, J::T) where {T <: Ideal} check_base_ring(I, J) - return ideal(base_ring(I), vcat(gens(I), gens(J))) + return similar(I, vcat(gens(I), gens(J))) end function Base.:*(I::T, J::T) where {T <: Ideal} check_base_ring(I, J) - return ideal(base_ring(I), [x*y for x in gens(I) for y in gens(J)]) + return similar(I, [x*y for x in gens(I) for y in gens(J)]) end ############################################################################### @@ -130,9 +141,8 @@ function *(x::RingElement, R::Ring) end function *(I::Ideal{T}, p::T) where T <: RingElement - R = base_ring(I) - iszero(p) && return ideal(R, T[]) - return ideal(R, [v*p for v in gens(I)]) + iszero(p) && return similar(I, T[]) + return similar(I, [v*p for v in gens(I)]) end function *(p::T, I::Ideal{T}) where T <: RingElement @@ -140,9 +150,8 @@ function *(p::T, I::Ideal{T}) where T <: RingElement end function *(I::Ideal{T}, p::S) where {S <: RingElement, T <: RingElement} - R = base_ring(I) - iszero(p*one(R)) && return ideal(R, T[]) - return ideal(R, [v*p for v in gens(I)]) + iszero(p*one(R)) && return similar(I, T[]) + return similar(I, [v*p for v in gens(I)]) end function *(p::S, I::Ideal{T}) where {S <: RingElement, T <: RingElement} diff --git a/src/generic/Ideal.jl b/src/generic/Ideal.jl index 2a7956af92..0363f06fc5 100644 --- a/src/generic/Ideal.jl +++ b/src/generic/Ideal.jl @@ -2098,17 +2098,7 @@ end ############################################################################### # -# Comparison -# -############################################################################### - -function ==(I::Ideal{T}, J::Ideal{T}) where T <: RingElement - return gens(I) == gens(J) -end - -############################################################################### -# -# Containment +# Membership # ############################################################################### @@ -2116,15 +2106,6 @@ function Base.in(v::T, I::Ideal{T}) where T <: RingElement return is_zero(normal_form(v, I)) end -@doc raw""" - Base.issubset(I::Ideal{T}, J::Ideal{T}) where T <: RingElement - -Return `true` if the ideal `I` is a subset of the ideal `J`. -""" -function Base.issubset(I::Ideal{T}, J::Ideal{T}) where T <: RingElement - return all(in(J), gens(I)) -end - ############################################################################### # # Intersection @@ -2208,41 +2189,6 @@ function intersect(I::Ideal{T}, J::Ideal{T}) where {U <: RingElement, T <: Abstr return Ideal(S, GInt) end -############################################################################### -# -# Ad hoc binary operations -# -############################################################################### - -function *(I::Ideal{T}, p::T) where T <: RingElement - R = base_ring(I) - G = gens(I) - if iszero(p) - return Ideal(R, T[]) - end - p = divexact(p, canonical_unit(p)) - return Ideal(R, [v*p for v in G]) -end - -function *(p::T, I::Ideal{T}) where T <: RingElement - return I*p -end - -function *(I::Ideal{T}, p::S) where {S <: RingElement, T <: RingElement} - R = base_ring(I) - G = gens(I) - if iszero(p*one(R)) - return Ideal(R, T[]) - end - V = [v*p for v in G] - V = [divexact(v, canonical_unit(v)) for v in V] - return Ideal(R, V) -end - -function *(p::S, I::Ideal{T}) where {S <: RingElement, T <: RingElement} - return I*p -end - ############################################################################### # # Ideal reduction in Euclidean domain @@ -2294,7 +2240,9 @@ function Ideal(R::Ring, v::T, vs::T...) where T <: RingElement end Ideal(R::Ring) = Ideal(R, elem_type(R)[]) -Ideal(R::Ring, V::Vector{Any}) = Ideal(R, elem_type(R)[R(v) for v in V]) +Ideal(R::Ring, V::Vector) = Ideal(R, elem_type(R)[R(v) for v in V]) + +Base.similar(I::Ideal, V::Vector) = Ideal(base_ring(I), V) ############################################################################### #