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
10 changes: 10 additions & 0 deletions ext/TestExt/Rings-conformance-tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ function test_NCRing_interface(R::AbstractAlgebra.NCRing; reps = 50)
@test iszero(characteristic(R) * one(R))
@test iszero(one(R) * characteristic(R))
catch
# could not compute characteristic, so verify that is_known
# reflects this
@test is_known(characteristic, R) == false
end
end

Expand Down Expand Up @@ -290,6 +293,13 @@ function test_Field_interface(R::AbstractAlgebra.Field; reps = 50)

test_Ring_interface(R, reps = reps)

# We implicitly assume all genuine fields (i.e. of type `Field`, not
# just rings that happen to be fields) know their characteristic. So
# test for that. We may relax this in the future if we have need for it.
# But for now if the next tests fail this usually means someone forgot
# to implement `characteristic` for their ring type properly.
@test is_known(characteristic, R) == true

@test iszero(R(characteristic(R)))
@test iszero(characteristic(R) * one(R))
@test iszero(one(R) * characteristic(R))
Expand Down
5 changes: 2 additions & 3 deletions src/AbsMSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ end
Return the characteristic of the base ring of the series `a`. If the
characteristic is not known, an exception is raised.
"""
function characteristic(a::MSeriesRing)
return characteristic(base_ring(a))
end
characteristic(a::MSeriesRing) = characteristic(base_ring(a))
is_known(::typeof(characteristic), R::MSeriesRing) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
10 changes: 2 additions & 8 deletions src/Fraction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,8 @@ function is_exact_type(a::Type{T}) where {S <: RingElement, T <: FracElem{S}}
return is_exact_type(S)
end

@doc raw"""
characteristic(R::FracField{T}) where T <: RingElem

Return the characteristic of the given field.
"""
function characteristic(R::FracField{T}) where T <: RingElem
return characteristic(base_ring(R))
end
characteristic(R::FracField) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::FracField) = is_known(characteristic, base_ring(R))

@doc raw"""
vars(a::FracElem{S}) where {S <: MPolyRingElem{<: RingElement}}
Expand Down
2 changes: 2 additions & 0 deletions src/FreeAssociativeAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ function is_exact_type(a::Type{S}) where {T <: RingElement, S <: FreeAssociative
end

characteristic(R::FreeAssociativeAlgebra) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::FreeAssociativeAlgebra) = is_known(characteristic, base_ring(R))

is_finite(R::FreeAssociativeAlgebra) = is_trivial(base_ring(R)) || (nvars(R) == 0 && is_finite(base_ring(R)))
is_known(::typeof(is_finite), R::FreeAssociativeAlgebra) = is_known(is_trivial, base_ring(R)) || (nvars(R) == 0 && is_known(is_finite, base_ring(R)))

###############################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/LaurentMPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
characteristic(R::LaurentMPolyRing) = characteristic(base_ring(R))

is_finite(R::LaurentMPolyRing) = is_trivial(base_ring(R)) || (nvars(R) == 0 && is_finite(base_ring(R)))
is_known(::typeof(is_finite), R::LaurentMPolyRing) = is_known(is_trivial, base_ring(R)) || (nvars(R) == 0 && is_known(is_finite, base_ring(R)))

###############################################################################
#
Expand Down
2 changes: 2 additions & 0 deletions src/LaurentPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
###############################################################################

characteristic(R::LaurentPolyRing) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::LaurentPolyRing) = is_known(characteristic, base_ring(R))

is_finite(R::LaurentPolyRing) = is_trivial(R)
is_known(::typeof(is_finite), R::LaurentPolyRing) = is_known(is_trivial, R)

###############################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/MPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ end
characteristic(R::MPolyRing) = characteristic(base_ring(R))

is_finite(R::MPolyRing) = is_trivial(base_ring(R)) || (nvars(R) == 0 && is_finite(base_ring(R)))
is_known(::typeof(is_finite), R::MPolyRing) = is_known(is_trivial, base_ring(R)) || (nvars(R) == 0 && is_known(is_finite, base_ring(R)))


###############################################################################
Expand Down
2 changes: 2 additions & 0 deletions src/MatRing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ function characteristic(a::MatRing)
iszero(nrows(a)) && return 1
return characteristic(base_ring(a))
end
is_known(::typeof(characteristic), R::MatRing) = is_known(characteristic, base_ring(R))

is_finite(R::MatRing) = iszero(nrows(a)) || is_finite(base_ring(R))
is_known(::typeof(is_finite), R::MatRing) = iszero(nrows(R)) || is_known(is_trivial, base_ring(R))

###############################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/Module.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function check_parent(M::FPModule{T}, N::FPModule{T}) where T <: RingElement
end

is_finite(M::FPModule{<:FinFieldElem}) = true
is_known(::typeof(is_finite), ::FPModule{<:FinFieldElem}) = true

function is_sub_with_data(M::FPModule{T}, N::FPModule{T}) where T <: RingElement
fl = is_submodule(N, M)
Expand Down
2 changes: 2 additions & 0 deletions src/NCPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ Return the number of variables of the polynomial ring, which is 1.
number_of_variables(a::NCPolyRing) = 1

characteristic(a::NCPolyRing) = characteristic(base_ring(a))
is_known(::typeof(characteristic), R::NCPolyRing) = is_known(characteristic, base_ring(R))

is_finite(a::NCPolyRing) = is_trivial(a)
is_known(::typeof(is_finite), R::NCPolyRing) = is_known(is_trivial, R)

###############################################################################
#
Expand Down
7 changes: 7 additions & 0 deletions src/NCRings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ function characteristic(R::NCRing)
error("Characteristic not known")
end

# All rings are supposed to implement characteristic if they can. If they
# can not, or only can do it with an expensive computation (e.g. a Groebner
# basis), then instead they should implement an `is_known` method indicating
# this. To enforce this via the ring conformance tests, we add this default
# method for `is_known(characteristic, ::NCRing)`
is_known(::typeof(characteristic), ::NCRing) = true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also demonstrated by PR #1995 which @thofma created while I worked on this PR: it adds new characteristic and is_finite methods. With the above is_known, we don't need to add another is_known method for RationalFunctionField.

If we agree on this approach I could add similar fallbacks for is_finite and is_trivial (and is_perfect?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I just realized that in the final is_known implementation the "default" is actually to throw an exception for is_known. We can of course leave it at that, and just add a test to the conformance test suite that invokes is_known(characteristic, R) on every ring R it is invoked for.

The downside then is that everyone implementing a ring has to add the "missing" is_known methods, even if they always return true. But perhaps that's the best approach anyway. I am really am open to either approach.

Copy link
Member Author

@fingolfin fingolfin Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to say, with either approach to changing the conformance tests, we would have caught the underlying issue for PR #1995


###############################################################################
#
# One and zero
Expand Down
2 changes: 2 additions & 0 deletions src/NumFields.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ canonical_unit(a::NumFieldElem) = a

characteristic(F::NumField) = 0

is_known(::typeof(characteristic), F::NumField) = true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be removed again if we go with the fallback approach I suggest. Otherwise we should keep it.


promote_rule(::Type{T}, ::Type{S}) where {S<:NumFieldElem,T<:Integer} = S

promote_rule(::Type{S}, ::Type{T}) where {S<:NumFieldElem,T<:Integer} = S
2 changes: 2 additions & 0 deletions src/Poly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ Return the number of variables of the polynomial ring, which is 1.
number_of_variables(a::PolyRing) = 1

characteristic(a::PolyRing) = characteristic(base_ring(a))
is_known(::typeof(characteristic), R::PolyRing) = is_known(characteristic, base_ring(R))

is_finite(a::PolyRing) = is_trivial(a)
is_known(::typeof(is_finite), R::PolyRing) = is_known(is_trivial, R)

Base.copy(a::PolyRingElem) = deepcopy(a)

Expand Down
1 change: 1 addition & 0 deletions src/Residue.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ deepcopy_internal(a::ResElem, dict::IdDict) =
function characteristic(a::ResidueRing{T}) where T <: Integer
return modulus(a)
end
is_known(::typeof(characteristic), R::ResidueRing{T}) where T <: Integer = true

###############################################################################
#
Expand Down
8 changes: 5 additions & 3 deletions src/ResidueField.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ end

Return the characteristic of the residue field.
"""
function characteristic(R::ResidueField)
return characteristic(base_ring(R))
end
characteristic(R::ResidueField) = characteristic(base_ring(R))
# FIXME: why is the above method correct in general??? Isn't it wrong if
# we e.g. start with ZZ[:x] and factor out ideal([x, 2]) ?
is_known(::typeof(characteristic), R::ResidueField) = is_known(characteristic, base_ring(R))

@doc raw"""
characteristic(r::ResidueField{T}) where T <: Integer
Expand All @@ -82,6 +83,7 @@ residue $r$ belongs to.
function characteristic(r::ResidueField{T}) where T <: Integer
return modulus(r)
end
is_known(::typeof(characteristic), R::ResidueField{T}) where T <: Integer = true

data(a::ResFieldElem) = a.data

Expand Down
10 changes: 8 additions & 2 deletions src/Rings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -211,20 +211,26 @@ of a single element, or equivalently if its characteristic is 1. Such
rings are also called zero rings.
"""
is_trivial(R::NCRing) = !is_domain_type(elem_type(R)) && iszero(one(R))
is_known(::typeof(is_trivial), R::NCRing) = is_domain_type(elem_type(R))

@doc raw"""
is_perfect(F::Field)

Test whether the field $F$ is perfect.
"""
is_perfect(F::Field) = characteristic(F) == 0 || F isa FinField ||
throw(NotImplementedError(:is_perfect, F))
is_perfect(F::Field) = characteristic(F) == 0 || throw(NotImplementedError(:is_perfect, F))
is_known(::typeof(is_perfect), F::Field) = is_known(characteristic, F) && characteristic(F) == 0

is_perfect(F::FinField) = true
is_known(::typeof(is_perfect), F::FinField) = true

is_finite(F::FinField) = true
is_known(::typeof(is_finite), F::FinField) = true

function is_finite(R::NCRing)
c = characteristic(R)
c == 0 && return false
c == 1 && return true
throw(NotImplementedError(:is_finite, R))
end
is_known(::typeof(is_finite), R::NCRing) = is_known(characteristic, R) && characteristic(R) <= 1
5 changes: 2 additions & 3 deletions src/generic/AbsSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ function deepcopy_internal(a::AbsSeries{T}, dict::IdDict) where T <: RingElement
return parent(a)(coeffs, length(a), precision(a))
end

function characteristic(a::AbsPowerSeriesRing{T}) where T <: RingElement
return characteristic(base_ring(a))
end
characteristic(R::AbsPowerSeriesRing) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::AbsPowerSeriesRing) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
3 changes: 2 additions & 1 deletion src/generic/FactoredFraction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ base_ring_type(::Type{FactoredFracField{T}}) where T <: NCRingElement = parent_t

base_ring(F::FactoredFracField{T}) where T <: RingElement = F.base_ring::parent_type(T)

characteristic(F::FactoredFracField{T}) where T <: RingElement = characteristic(base_ring(F))
characteristic(F::FactoredFracField) = characteristic(base_ring(F))
is_known(::typeof(characteristic), R::FactoredFracField) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/generic/FunctionField.jl
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ var(R::FunctionField) = R.S
Return the characteristic of the underlying rational function field.
"""
characteristic(R::FunctionField) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::FunctionField) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/generic/LaurentPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ symbols(R::LaurentPolyWrapRing) = symbols(R.polyring)
number_of_variables(R::LaurentPolyWrapRing) = number_of_variables(R.polyring)

characteristic(R::LaurentPolyWrapRing) = characteristic(R.polyring)
is_known(::typeof(characteristic), R::LaurentPolyWrapRing) = is_known(characteristic, base_ring(R))


###############################################################################
Expand Down
5 changes: 2 additions & 3 deletions src/generic/LaurentSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,8 @@ function renormalize!(z::LaurentSeriesElem)
return nothing
end

function characteristic(a::LaurentSeriesRing{T}) where T <: RingElement
return characteristic(base_ring(a))
end
characteristic(a::LaurentSeriesRing) = characteristic(base_ring(a))
is_known(::typeof(characteristic), R::LaurentSeriesRing) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/generic/PermGroups.jl
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ gen(G::SymmetricGroup, i::Int) = gens(G)[i]
number_of_generators(G::SymmetricGroup) = G.n == 1 ? 0 : G.n == 2 ? 1 : 2

is_finite(G::SymmetricGroup) = true
is_known(::typeof(is_finite), ::SymmetricGroup) = true

order(::Type{T}, G::SymmetricGroup) where {T} = convert(T, factorial(T(G.n)))

Expand Down
5 changes: 2 additions & 3 deletions src/generic/PuiseuxSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,8 @@ function deepcopy_internal(a::PuiseuxSeriesElem{T}, dict::IdDict) where {T <: Ri
return parent(a)(deepcopy_internal(a.data, dict), a.scale)
end

function characteristic(a::PuiseuxSeriesRing{T}) where T <: RingElement
return characteristic(base_ring(a))
end
characteristic(R::PuiseuxSeriesRing) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::PuiseuxSeriesRing) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
5 changes: 2 additions & 3 deletions src/generic/RationalFunctionField.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ function is_exact_type(a::Type{S}) where {T <: FieldElement, U <: Union{PolyRing
return is_exact_type(T)
end

function characteristic(R::RationalFunctionField)
return characteristic(base_ring(R))
end
characteristic(R::RationalFunctionField) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::RationalFunctionField) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
5 changes: 2 additions & 3 deletions src/generic/RelSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ function deepcopy_internal(a::RelSeries{T}, dict::IdDict) where T <: RingElement
return parent(a)(coeffs, pol_length(a), precision(a), valuation(a))
end

function characteristic(a::RelPowerSeriesRing{T}) where T <: RingElement
return characteristic(base_ring(a))
end
characteristic(a::RelPowerSeriesRing) = characteristic(base_ring(a))
is_known(::typeof(characteristic), R::RelPowerSeriesRing) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
5 changes: 2 additions & 3 deletions src/generic/Residue.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ function gens(R::Union{EuclideanRingResidueRing{T}, EuclideanRingResidueField{T}
return r
end

function characteristic(R::Union{EuclideanRingResidueRing{T}, EuclideanRingResidueField{T}}) where {T<:PolyRingElem}
return characteristic(base_ring(base_ring(R)))
end
characteristic(R::Union{EuclideanRingResidueRing, EuclideanRingResidueField}) = characteristic(base_ring(base_ring(R)))
is_known(::typeof(characteristic), R::Union{EuclideanRingResidueRing, EuclideanRingResidueField}) = is_known(characteristic, base_ring(R))

function size(R::Union{EuclideanRingResidueRing{T}, EuclideanRingResidueField{T}}) where {T<:PolyRingElem}
return size(base_ring(base_ring(R)))^degree(modulus(R))
Expand Down
5 changes: 2 additions & 3 deletions src/generic/SparsePoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,8 @@ function Base.deepcopy_internal(a::SparsePoly{T}, dict::IdDict) where {T <: Ring
return parent(a)(Rc, Re)
end

function characteristic(a::SparsePolyRing{T}) where T <: RingElement
return characteristic(base_ring(a))
end
characteristic(R::SparsePolyRing) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::SparsePolyRing) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
5 changes: 2 additions & 3 deletions src/generic/TotalFraction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ function is_exact_type(a::Type{T}) where {S <: RingElement, T <: TotFrac{S}}
return is_exact_type(S)
end

function characteristic(R::TotFracRing{T}) where T <: RingElem
return characteristic(base_ring(R))
end
characteristic(R::TotFracRing) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::TotFracRing) = is_known(characteristic, base_ring(R))

###############################################################################
#
Expand Down
1 change: 1 addition & 0 deletions src/generic/UnivPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ end
canonical_unit(p::UnivPoly) = canonical_unit(data(p))

characteristic(R::UniversalPolyRing) = characteristic(base_ring(R))
is_known(::typeof(characteristic), R::UniversalPolyRing) = is_known(characteristic, base_ring(R))

function Base.hash(p::UnivPoly, h::UInt)
b = 0xcf418d4529109236%UInt
Expand Down
5 changes: 2 additions & 3 deletions src/julia/GF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,8 @@ is_unit(a::GFElem) = a.d != 0

Return the characteristic of the given finite field.
"""
function characteristic(R::GFField)
return R.p
end
characteristic(R::GFField) = R.p
is_known(::typeof(characteristic), ::GFField) = true

@doc raw"""
order(R::GFField)
Expand Down
3 changes: 2 additions & 1 deletion src/julia/Integer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ is_zero_divisor(a::Integer) = is_zero(a)

canonical_unit(a::T) where T <: Integer = a < 0 ? T(-1) : T(1)

characteristic(::Integers{T}) where T <: Integer = 0
characteristic(::Integers) = 0
is_known(::typeof(characteristic), ::Integers) = true

###############################################################################
#
Expand Down
3 changes: 2 additions & 1 deletion src/julia/Rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ function denominator(a::Rational, canonicalise::Bool=true)
return Base.denominator(a) # all other types ignore canonicalise
end

characteristic(a::Rationals{T}) where T <: Integer = 0
characteristic(a::Rationals) = 0
is_known(::typeof(characteristic), ::Rationals) = true

###############################################################################
#
Expand Down
Loading