Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/src/ideal.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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
Expand Down
93 changes: 49 additions & 44 deletions docs/src/ideal_interface.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,59 +17,58 @@ 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

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)
```

Expand All @@ -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))
```
129 changes: 114 additions & 15 deletions src/Ideal.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
###############################################################################
#
# Generic functionality for ideals
# Default methods for ideals
#
###############################################################################

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
29 changes: 6 additions & 23 deletions src/generic/Ideal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -2099,35 +2103,14 @@ 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
#
###############################################################################

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
Expand Down
1 change: 1 addition & 0 deletions src/generic/imports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading