diff --git a/docs/src/ideal.md b/docs/src/ideal.md index 53889b6f15..36d3b00c35 100644 --- a/docs/src/ideal.md +++ b/docs/src/ideal.md @@ -85,7 +85,7 @@ AbstractAlgebra.Generic.Ideal{BigInt}(Integers, BigInt[1]) ### Basic functionality ```@docs -gens(::Generic.Ideal{T}) where T <: RingElement +gens(::Generic.Ideal) ``` **Examples** @@ -117,7 +117,7 @@ ideals. ### Containment ```@docs -is_subset(::Generic.Ideal{T}, ::Generic.Ideal{T}) where T <: RingElement +is_subset(I::T, J::T) where {T <: Generic.Ideal} ``` ```@docs diff --git a/docs/src/ideal_interface.md b/docs/src/ideal_interface.md index 2ce6b43d2e..c8da3a090c 100644 --- a/docs/src/ideal_interface.md +++ b/docs/src/ideal_interface.md @@ -1,12 +1,13 @@ ```@meta CurrentModule = AbstractAlgebra +CollapsedDocStrings = true DocTestSetup = AbstractAlgebra.doctestsetup() ``` # Ideal Interface AbstractAlgebra.jl generic code makes use of a standardised set of functions which it -expects to be implemented by anyone implementing ideals for AbstractAlgebra rings. +expects to be implemented by anyone implementing ideals for commutative rings. Here we document this interface. All libraries which want to make use of the generic capabilities of AbstractAlgebra.jl must supply all of the required functionality for their ideals. There are already many helper methods in AbstractAlgebra.jl for the methods mentioned below. @@ -16,19 +17,25 @@ provided for certain types of ideals e.g., for ideals of polynomial rings. If im these allow the generic code to provide additional functionality for those ideals, or in some cases, to select more efficient algorithms. -## Types and parents -New ideal types should come with the following type information: +## Types and parents +Below we describe this interface for a fictitious type `NewIdeal` representing +ideals over a base ring of type `NewRing`, with element type `NewRingElem`. To +make use of the functionality described on this page, `NewIdeal` must be a +subtype of `Ideal{NewRingElem}`. To inform the system about this relationship, +it is necessary to provide the following method: ```julia -ideal_type(::Type{NewRing}) = NewIdealType -base_ring_type(::Type{NewIdeal}) = NewRingType -parent_type(::Type{NewIdeal{T}}) = DefaultIdealSet{T} +ideal_type(::Type{NewRing}) = NewIdeal +```julia +The system automatically provides the following reverse method: +``` +base_ring_type(::Type{NewIdeal}) = NewRing ``` -However, new implementations of ideals needn't necessarily supply new types and could just extend -the existing functionality for new rings as AbstractAlgebra.jl provides a generic ideal type -based on Julia arrays which is implemented in `src/generic/Ideal.jl`. For information +For ideals of a Euclidean domain, it may also be possibly to opt into using the existing +functionality which is implemented in `src/generic/Ideal.jl`. In that case you would +essentially defined `const NewIdeal = Generic.Ideal{NewRingElem}`. For more information about implementing new rings, see the [Ring interface](@ref "Ring Interface"). ## Required functionality for ideals @@ -36,39 +43,32 @@ about implementing new rings, see the [Ring interface](@ref "Ring Interface"). In the following, we list all the functions that are required to be provided for ideals in AbstractAlgebra.jl or by external libraries wanting to use AbstractAlgebra.jl. -We give this interface for fictitious type `NewIdeal` and `Ring` or `NewRing` for the type of the base ring -object `R`, and `RingElem` for the type of the elements of the ring. -We assume that the function - +To facilitate construction of new ideals, implementations must provide a method with signature ```julia -ideal(R::Ring, xs::Vector{U}) +ideal(R::NewRing, xs::Vector{NewRingElem}) ``` +Here `xs` is a list of generators, and `NewRingElem === elem_type(NewRing)` holds. -with `U === elem_type(Ring)` and `xs` a list of generators, -is implemented by anyone implementing ideals for AbstractAlgebra rings. -Additionally, the following constructors are already implemented generically: - +With this in place, the following additional ideal constructors will automatically work via +generic implementations: ```julia -ideal(R::Ring, x::U) -ideal(xs::Vector{U}) = ideal(parent(xs[1]), xs) -ideal(x::U) = ideal(parent(x), x) -*(x::RingElem, R::Ring) -*(R::Ring, x::RingElem) +ideal(R::NewRing, x::RingElement...) = ideal(R, [x...]) +ideal(x::RingElement, y::RingElement...) = ideal(parent(x), x, y...) +ideal(xs::Vector{NewRingElem}) = ideal(parent(xs[1]), xs) +*(x::NewRingElem, R::NewRing) = ideal(R, x) +*(R::NewRing, x::NewRingElem) = ideal(R, x) ``` - -An implementation of an Ideal subtype should also provide the -following methods: - +In addition sums and products of ideals can be formed: ```julia -base_ring(I::NewIdeal) ++(I::T, J::T) where {T <: NewIdeal} +*(I::T, J::T) where {T <: NewIdeal} ``` + +An implementation of an `Ideal` subtype must also provide the following methods: ```julia +base_ring(I::NewIdeal) gen(I::NewIdeal, k::Int) -``` -```julia gens(I::NewIdeal) -``` -```julia ngens(I::NewIdeal) ``` @@ -84,24 +84,29 @@ functions. As these functions are optional, they do not need to exist. Julia wil already inform the user that the function has not been implemented if it is called but doesn't exist. +The following method have no generic implementation and only work when explicitly +implemented. ```julia -in(v::RingElem, I::NewIdeal) +in(v::NewRingElem, I::NewIdeal) +intersect(I::T, J::T) where {T <: NewIdeal} ``` + +If a method for `in` as above is provided, then the following automatically works: ```julia issubset(I::NewIdeal, J::NewIdeal) ``` + +If a method for `in` as above is provided (e.g. indirectly by providing method for `in`), +then the following automatically works: ```julia -iszero(I::NewIdeal) -``` -```julia -+(I::T, J::T) where {T <: NewIdeal} -``` -```julia -*(I::T, J::T) where {T <: NewIdeal} -``` -```julia -intersect(I::T, J::T) where {T <: NewIdeal} +==(I::T, J::T) where {T <: NewIdeal} ``` +Note that implementing `==` for a Julia type means that we have to provide +a matching `hash` method which preserves the invariant that `I == J` implies `hash(I) == hash(J)`. +We provide such a method but by necessity it is very conservative and hence does +not provide good hashing. You may wish to implement a better `hash` methods. + +The following method is implemented generically via the ideal generators. ```julia -==(I::T, J::T) where {T <: NewIdeal} +iszero(I::Ideal) = all(iszero, gens(I)) ``` diff --git a/src/Ideal.jl b/src/Ideal.jl index 74bf1e68a5..60b1276109 100644 --- a/src/Ideal.jl +++ b/src/Ideal.jl @@ -1,6 +1,6 @@ ############################################################################### # -# Generic functionality for ideals +# Default methods for ideals # ############################################################################### @@ -20,11 +20,31 @@ Return the type of ideals over the given ring `R`, respectively over rings of ty ideal_type(x) = ideal_type(typeof(x)) ideal_type(T::DataType) = throw(MethodError(ideal_type, (T,))) -# 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`. +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 +# +############################################################################### + +@doc raw""" + ideal(R::Ring, elms::AbstractVector{<:RingElement}) + ideal(R::Ring, elms...) + ideal(elms::AbstractVector{<:RingElement}) + ideal(elms...) + +Return the `R`-ideal generated by `elms`. + +If `R` is omitted then `elms` must be non-empty, otherwise an error is raised. +""" +ideal + +# All constructors ultimately delegate to a method +# ideal(R::T, xs::Vector{U}) where T <: NCRing +# and U === elem_type(T) # the following helper enables things like `ideal(R, [])` or `ideal(R, [1])` # the type check ensures we don't run into an infinite recursion @@ -33,10 +53,82 @@ function ideal(R::NCRing, xs::AbstractVector{T}; kw...) where T<:NCRingElement return ideal(R, elem_type(R)[R(x) for x in xs]; kw...) end -function ideal(R::NCRing, x, y...; kw...) - return ideal(R, elem_type(R)[R(z) for z in [x, y...]]; kw...) +function ideal(R::NCRing, x...; kw...) + return ideal(R, elem_type(R)[R(z) for z in x]; kw...) +end + +function ideal(x::T, y::T...; kw...) where T<:NCRingElement + return ideal(parent(x), x, y...; 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 +# +############################################################################### + +@doc raw""" + iszero(I::Ideal) + +Check if the ideal `I` is the zero ideal. +""" +iszero(I::Ideal) = all(iszero, gens(I)) + +@doc raw""" + Base.issubset(I::T, J::T) where {T <: Ideal} + +Return `true` if the ideal `I` is a subset of the ideal `J`. +An exception is thrown if the ideals are not defined over the same base ring. +""" +function Base.issubset(I::T, J::T) where {T <: Ideal} + I === J && return true + check_base_ring(I, J) + return all(in(J), 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 + +# The following method for I*J is only applicable to commutative ideals +function Base.:*(I::T, J::T) where {T <: Ideal{<:RingElement}} + 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 @@ -45,15 +137,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)) +function *(I::Ideal{<:RingElement}, p::RingElement) + R = base_ring(I) + iszero(p*one(R)) && return ideal(R, T[]) + return ideal(R, [v*p for v in gens(I)]) +end -base_ring_type(::Type{<:IdealSet{T}}) where T <: RingElement = parent_type(T) +function *(p::RingElement, I::Ideal{<:RingElement}) + return I*p +end diff --git a/src/generic/Ideal.jl b/src/generic/Ideal.jl index 2dd4b03db4..94ca83699e 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] function deepcopy_internal(I::Ideal, dict::IdDict) elms = [deepcopy_internal(x, dict) for x in I.gens] @@ -2099,18 +2103,7 @@ end ############################################################################### # -# Comparison -# -############################################################################### - -function ==(I::Ideal{T}, J::Ideal{T}) where T <: RingElement - check_base_ring(I, J) - return gens(I) == gens(J) -end - -############################################################################### -# -# Containment +# Membership # ############################################################################### @@ -2118,16 +2111,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 - check_base_ring(I, J) - return all(in(J), gens(I)) -end - ############################################################################### # # Intersection diff --git a/src/generic/imports.jl b/src/generic/imports.jl index c3b2b2579b..50105bf34a 100644 --- a/src/generic/imports.jl +++ b/src/generic/imports.jl @@ -142,6 +142,7 @@ import ..AbstractAlgebra: gen import ..AbstractAlgebra: gens import ..AbstractAlgebra: get_cached! import ..AbstractAlgebra: hom +import ..AbstractAlgebra: ideal import ..AbstractAlgebra: ideal_type import ..AbstractAlgebra: identity_matrix import ..AbstractAlgebra: image