Skip to content

Commit d69de74

Browse files
committed
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).
1 parent 721bf21 commit d69de74

File tree

4 files changed

+181
-98
lines changed

4 files changed

+181
-98
lines changed

docs/src/ideal_interface.md

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
```@meta
22
CurrentModule = AbstractAlgebra
3+
CollapsedDocStrings = true
34
DocTestSetup = AbstractAlgebra.doctestsetup()
45
```
56

67
# Ideal Interface
78

89
AbstractAlgebra.jl generic code makes use of a standardised set of functions which it
9-
expects to be implemented by anyone implementing ideals for AbstractAlgebra rings.
10+
expects to be implemented by anyone implementing ideals for commutative rings.
1011
Here we document this interface. All libraries which want to make use of the generic
1112
capabilities of AbstractAlgebra.jl must supply all of the required functionality for their ideals.
1213
There are already many helper methods in AbstractAlgebra.jl for the methods mentioned below.
@@ -16,14 +17,17 @@ provided for certain types of ideals e.g., for ideals of polynomial rings. If im
1617
these allow the generic code to provide additional functionality for those ideals, or in
1718
some cases, to select more efficient algorithms.
1819

20+
1921
## Types and parents
2022

21-
New ideal types should come with the following type information:
23+
Below we describe this interface for a fictitious type `NewIdeal` which is a subtype of
24+
`Ideal`, representing ideals over a base ring of type `NewRing`, with element type
25+
`NewRingElem`. To inform the system about this relationship, it is necessary to provide the
26+
following methods:
2227

2328
```julia
24-
ideal_type(::Type{NewRing}) = NewIdealType
25-
base_ring_type(::Type{NewIdeal}) = NewRingType
26-
parent_type(::Type{NewIdeal{T}}) = DefaultIdealSet{T}
29+
ideal_type(::Type{NewRing}) = NewIdeal
30+
base_ring_type(::Type{NewIdeal}) = NewRing
2731
```
2832

2933
However, new implementations of ideals needn't necessarily supply new types and could just extend
@@ -36,39 +40,32 @@ about implementing new rings, see the [Ring interface](@ref "Ring Interface").
3640
In the following, we list all the functions that are required to be provided for ideals
3741
in AbstractAlgebra.jl or by external libraries wanting to use AbstractAlgebra.jl.
3842

39-
We give this interface for fictitious type `NewIdeal` and `Ring` or `NewRing` for the type of the base ring
40-
object `R`, and `RingElem` for the type of the elements of the ring.
41-
We assume that the function
42-
43+
To facilitate construction of new ideals, implementations must provide a method with signature
4344
```julia
44-
ideal(R::Ring, xs::Vector{U})
45+
ideal(R::NewRing, xs::Vector{NewRingElem})
4546
```
47+
Here `xs` is a list of generators, and `NewRingElem === elem_type(NewRing)` holds.
4648

47-
with `U === elem_type(Ring)` and `xs` a list of generators,
48-
is implemented by anyone implementing ideals for AbstractAlgebra rings.
49-
Additionally, the following constructors are already implemented generically:
50-
49+
With this in place, the following additional ideal constructors will automatically work via
50+
generic implementations:
5151
```julia
52-
ideal(R::Ring, x::U)
53-
ideal(xs::Vector{U}) = ideal(parent(xs[1]), xs)
54-
ideal(x::U) = ideal(parent(x), x)
55-
*(x::RingElem, R::Ring)
56-
*(R::Ring, x::RingElem)
52+
ideal(R::NewRing, x::RingElement...) = ideal(R, [x...])
53+
ideal(x::RingElement, y::RingElement...) = ideal(parent(x), x, y...)
54+
ideal(xs::Vector{NewRingElem}) = ideal(parent(xs[1]), xs)
55+
*(x::NewRingElem, R::NewRing) = ideal(R, x)
56+
*(R::NewRing, x::NewRingElem) = ideal(R, x)
5757
```
58-
59-
An implementation of an Ideal subtype should also provide the
60-
following methods:
61-
58+
In addition sums and products of ideals can be formed:
6259
```julia
63-
base_ring(I::NewIdeal)
60+
+(I::T, J::T) where {T <: NewIdeal}
61+
*(I::T, J::T) where {T <: NewIdeal}
6462
```
63+
64+
An implementation of an `Ideal` subtype must also provide the following methods:
6565
```julia
66+
base_ring(I::NewIdeal)
6667
gen(I::NewIdeal, k::Int)
67-
```
68-
```julia
6968
gens(I::NewIdeal)
70-
```
71-
```julia
7269
ngens(I::NewIdeal)
7370
```
7471

@@ -84,24 +81,29 @@ functions. As these functions are optional, they do not need to exist. Julia wil
8481
already inform the user that the function has not been implemented if it is called but
8582
doesn't exist.
8683

84+
The following method have no generic implementation and only work when explicitly
85+
implemented.
8786
```julia
88-
in(v::RingElem, I::NewIdeal)
87+
in(v::NewRingElem, I::NewIdeal)
88+
intersect(I::T, J::T) where {T <: NewIdeal}
8989
```
90+
91+
If a method for `in` as above is provided, then the following automatically works:
9092
```julia
9193
issubset(I::NewIdeal, J::NewIdeal)
9294
```
95+
96+
If a method for `in` as above is provided (e.g. indirectly by providing method for `in`),
97+
then the following automatically works:
9398
```julia
94-
iszero(I::NewIdeal)
95-
```
96-
```julia
97-
+(I::T, J::T) where {T <: NewIdeal}
98-
```
99-
```julia
100-
*(I::T, J::T) where {T <: NewIdeal}
101-
```
102-
```julia
103-
intersect(I::T, J::T) where {T <: NewIdeal}
99+
==(I::T, J::T) where {T <: NewIdeal}
104100
```
101+
Note that implementing `==` for a Julia type comes means that we have to pr
102+
a matching `hash` method which preserves the invariant that `I == J` implies `hash(I) == hash(J)`.
103+
We provide such a method but by necessity it is very conservative and hence does
104+
not provide good hashing. You may wish to implement a better `hash` methods.
105+
106+
The following method is implemented generically via the ideal generators.
105107
```julia
106-
==(I::T, J::T) where {T <: NewIdeal}
108+
iszero(I::Ideal) = all(iszero, gens(I))
107109
```

src/AbstractAlgebra.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ function check_parent(a, b, throw::Bool = true)
229229
return flag
230230
end
231231

232+
function check_base_ring(a, b)
233+
base_ring(a) === base_ring(b) || error("base rings do not match")
234+
end
235+
232236
include("algorithms/LaurentPoly.jl")
233237
include("algorithms/FinField.jl")
234238
include("algorithms/GenericFunctions.jl")

src/Ideal.jl

Lines changed: 129 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,51 @@
11
###############################################################################
22
#
3-
# Ideal constructor
3+
# Generic functionality for ideals
44
#
55
###############################################################################
66

7-
# We assume that the function
8-
# ideal(R::T, xs::Vector{U})
9-
# with U === elem_type(T) is implemented by anyone implementing ideals
10-
# for AbstractAlgebra rings.
11-
# The functions in this file extend the interface for `ideal`.
7+
###############################################################################
8+
#
9+
# Type and parent functions
10+
#
11+
###############################################################################
12+
13+
# fundamental interface
14+
@doc raw"""
15+
ideal_type(a)
16+
17+
Return the type of the base ring of the given element, element type, ring or ring type $a$.
18+
"""
19+
ideal_type(x) = ideal_type(typeof(x))
20+
ideal_type(x::Type{<:RingElement}) = ideal_type(parent_type(x))
21+
ideal_type(T::DataType) = throw(MethodError(ideal_type, (T,)))
22+
23+
base_ring_type(::Type{<:IdealSet{T}}) where {T} = parent_type(T)
24+
25+
elem_type(::Type{<:IdealSet{T}}) where {T} = ideal_type(parent_type(T))
26+
27+
###############################################################################
28+
#
29+
# Ideal constructors
30+
#
31+
###############################################################################
32+
33+
@doc raw"""
34+
ideal(R::Ring, elms::AbstractVector{<:RingElement})
35+
ideal(R::Ring, elms...)
36+
ideal(elms::AbstractVector{<:RingElement})
37+
ideal(elms...)
38+
39+
Return the `R`-ideal generated by `elms`.
40+
41+
If `R` is omitted then `elms` must be non-empty, otherwise an error is raised.
42+
"""
43+
ideal
44+
45+
# All constructors ultimately delegate to a method
46+
# ideal(R::T, xs::Vector{U}) where T <: NCRing
47+
# and U === elem_type(T)
48+
1249

1350
# the following helper enables things like `ideal(R, [])` or `ideal(R, [1])`
1451
# the type check ensures we don't run into an infinite recursion
@@ -21,6 +58,78 @@ function ideal(R::NCRing, x, y...; kw...)
2158
return ideal(R, elem_type(R)[R(z) for z in [x, y...]]; kw...)
2259
end
2360

61+
function ideal(x::T, y::T...; kw...) where T<:NCRingElement
62+
return ideal(parent(x), x, y...; kw...)
63+
end
64+
65+
function ideal(xs::AbstractVector{T}; kw...) where T<:NCRingElement
66+
@req !is_empty(xs) "Empty collection, cannot determine parent ring. Try ideal(ring, xs) instead of ideal(xs)"
67+
return ideal(parent(xs[1]), xs; kw...)
68+
end
69+
70+
###############################################################################
71+
#
72+
# Basic predicates
73+
#
74+
###############################################################################
75+
76+
@doc raw"""
77+
iszero(I::Ideal)
78+
79+
Check if the ideal `I` is the zero ideal.
80+
"""
81+
iszero(I::Ideal) = all(iszero, gens(I))
82+
83+
@doc raw"""
84+
Base.issubset(I::T, J::T) where {T <: Ideal}
85+
86+
Return `true` if the ideal `I` is a subset of the ideal `J`.
87+
An exception is thrown if the ideals are not defined over the same base ring.
88+
"""
89+
function Base.issubset(I::T, J::T) where {T <: Ideal}
90+
I === J && return true
91+
check_base_ring(I, J)
92+
return all(in(J), gens(I))
93+
end
94+
95+
###############################################################################
96+
#
97+
# Comparison
98+
#
99+
###############################################################################
100+
101+
function Base.:(==)(I::T, J::T) where {T <: Ideal}
102+
return is_subset(I, J) && is_subset(J, I)
103+
end
104+
105+
function Base.:hash(I::T, h::UInt) where {T <: Ideal}
106+
h = hash(base_ring(I), h)
107+
return h
108+
end
109+
110+
###############################################################################
111+
#
112+
# Binary operations
113+
#
114+
###############################################################################
115+
116+
function Base.:+(I::T, J::T) where {T <: Ideal}
117+
check_base_ring(I, J)
118+
return ideal(base_ring(I), vcat(gens(I), gens(J)))
119+
end
120+
121+
# The following method for I*J is only applicable to commutative ideals
122+
function Base.:*(I::T, J::T) where {T <: Ideal{<:RingElement}}
123+
check_base_ring(I, J)
124+
return ideal(base_ring(I), [x*y for x in gens(I) for y in gens(J)])
125+
end
126+
127+
###############################################################################
128+
#
129+
# Ad hoc binary operations
130+
#
131+
###############################################################################
132+
24133
function *(R::Ring, x::RingElement)
25134
return ideal(R, x)
26135
end
@@ -29,19 +138,22 @@ function *(x::RingElement, R::Ring)
29138
return ideal(R, x)
30139
end
31140

32-
function ideal(x::NCRingElement; kw...)
33-
return ideal(parent(x), x; kw...)
141+
function *(I::Ideal{T}, p::T) where T <: RingElement
142+
R = base_ring(I)
143+
iszero(p) && return ideal(R, T[])
144+
return ideal(R, [v*p for v in gens(I)])
34145
end
35146

36-
function ideal(xs::AbstractVector{T}; kw...) where T<:NCRingElement
37-
@req !is_empty(xs) "Empty collection, cannot determine parent ring. Try ideal(ring, xs) instead of ideal(xs)"
38-
return ideal(parent(xs[1]), xs; kw...)
147+
function *(p::T, I::Ideal{T}) where T <: RingElement
148+
return I*p
39149
end
40150

41-
iszero(I::Ideal) = all(iszero, gens(I))
42-
43-
base_ring_type(::Type{<:IdealSet{T}}) where T <: RingElement = parent_type(T)
151+
function *(I::Ideal{<:RingElement}, p::RingElement)
152+
R = base_ring(I)
153+
iszero(p*one(R)) && return ideal(R, T[])
154+
return ideal(R, [v*p for v in gens(I)])
155+
end
44156

45-
# fundamental interface, to be documented
46-
ideal_type(x) = ideal_type(typeof(x))
47-
ideal_type(T::DataType) = throw(MethodError(ideal_type, (T,)))
157+
function *(p::RingElement, I::Ideal{<:RingElement})
158+
return I*p
159+
end

src/generic/Ideal.jl

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ parent_type(::Type{Ideal{S}}) where S <: RingElement = IdealSet{S}
3434
3535
Return a list of generators of the ideal `I` in reduced form and canonicalised.
3636
"""
37-
gens(I::Ideal{T}) where T <: RingElement = I.gens
37+
gens(I::Ideal) = I.gens
38+
39+
number_of_generators(I::Ideal) = length(I.gens)
40+
41+
gen(I::Ideal, i::Int) = I.gens[i]
3842

3943
###############################################################################
4044
#
@@ -2094,33 +2098,14 @@ end
20942098

20952099
###############################################################################
20962100
#
2097-
# Comparison
2098-
#
2099-
###############################################################################
2100-
2101-
function ==(I::Ideal{T}, J::Ideal{T}) where T <: RingElement
2102-
return gens(I) == gens(J)
2103-
end
2104-
2105-
###############################################################################
2106-
#
2107-
# Containment
2101+
# Membership
21082102
#
21092103
###############################################################################
21102104

21112105
function Base.in(v::T, I::Ideal{T}) where T <: RingElement
21122106
return is_zero(normal_form(v, I))
21132107
end
21142108

2115-
@doc raw"""
2116-
Base.issubset(I::Ideal{T}, J::Ideal{T}) where T <: RingElement
2117-
2118-
Return `true` if the ideal `I` is a subset of the ideal `J`.
2119-
"""
2120-
function Base.issubset(I::Ideal{T}, J::Ideal{T}) where T <: RingElement
2121-
return all(in(J), gens(I))
2122-
end
2123-
21242109
###############################################################################
21252110
#
21262111
# Intersection
@@ -2204,26 +2189,6 @@ function intersect(I::Ideal{T}, J::Ideal{T}) where {U <: RingElement, T <: Abstr
22042189
return Ideal(S, GInt)
22052190
end
22062191

2207-
###############################################################################
2208-
#
2209-
# Binary operations
2210-
#
2211-
###############################################################################
2212-
2213-
function +(I::Ideal{T}, J::Ideal{T}) where T <: RingElement
2214-
R = base_ring(I)
2215-
G1 = gens(I)
2216-
G2 = gens(J)
2217-
return Ideal(R, vcat(G1, G2))
2218-
end
2219-
2220-
function *(I::Ideal{T}, J::Ideal{T}) where T <: RingElement
2221-
R = base_ring(I)
2222-
G1 = gens(I)
2223-
G2 = gens(J)
2224-
return Ideal(R, [v*w for v in G1 for w in G2])
2225-
end
2226-
22272192
###############################################################################
22282193
#
22292194
# Ad hoc binary operations

0 commit comments

Comments
 (0)