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
42 changes: 39 additions & 3 deletions ext/TestExt/Rings-conformance-tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,62 @@ function test_NCRing_interface(R::AbstractAlgebra.NCRing; reps = 15)
end
end

@testset "Basic functions" begin
@testset "Basic properties" begin
@test iszero(R()) # R() is supposed to construct 0 ?
@test iszero(zero(R))
@test isone(one(R))
@test iszero(R(0))
@test isone(R(1))
@test isone(R(0)) || !is_unit(R(0))

@test is_unit(R(1))
if is_trivial(R)
@test isone(R(0))
@test iszero(R(1))
@test R(0) == R(1)
else
@test !is_unit(R(0))
for i in 1:reps
a = generate_element(R)::T
@test is_unit(a) == is_unit(a^2)
end
end

# test is_nilpotent if it is supported
if ConformanceTests._implements(R, is_nilpotent)
f = is_nilpotent(R(1))
@test is_nilpotent(R(0))
if is_trivial(R)
@test is_nilpotent(R(1))
else
@test !is_unit(R(0))
@test !is_nilpotent(R(1))
for i in 1:reps
a = generate_element(R)::T
@test !(is_unit(a) && is_nilpotent(a))
@test is_nilpotent(a) == is_nilpotent(a^2)
if is_domain_type(typeof(a))
@test is_nilpotent(a) == is_zero(a)
end
end
end
end
end

@testset "hash, deepcopy, equality, printing, parent" begin
for i in 1:reps
a = generate_element(R)::T
@test hash(a) isa UInt
A = deepcopy(a)
@test !ismutable(a) || a !== A
@test equality(a, A)
@test hash(a) == hash(A)
@test parent(a) === parent(A)
@test parent(a) === R
@test sprint(show, "text/plain", a) isa String
end
@test sprint(show, "text/plain", R) isa String
end

@testset "Basic arithmetic" begin
for i in 1:reps
a = generate_element(R)::T
b = generate_element(R)::T
Expand Down
57 changes: 57 additions & 0 deletions src/ConformanceTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,63 @@ It is supposed to be implemented in the src file of the respective type.
function generate_element end


###############################################################################
#
# `implements` trait
#
###############################################################################

# Calling `_implements(T, f)` checks whether a "sensible" method for the unary
# function `f` is implemented for inputs of type `T`. The argument order is
# meant to be similar to e.g. `isa`, and thus indicates `T implements f`.
#
# For example, `_implements(MyRingElem, is_unit)` should return true if
# invoking `is_unit` on elements of type `MyRingElem` is supported.
#
# The generic fallback uses `hasmethod`. However, this may return `true` in
# cases where it shouldn't, as we often provide generic methods for that rely
# on other methods being implemented -- either for the same type, or for types
# derived from it. For example the `is_nilpotent(::PolyElem{T})` method needs
# `is_nilpotent(::T)` in order to work.
#
# To reflect this, additional `_implements` methods need to be provided.
# We currently do this for at least the following functions:
# - factor
# - is_irreducible
# - is_nilpotent
# - is_squarefree
# - is_unit
# - is_zero_divisor
#
_implements(::Type{T}, f::Any) where {T} = hasmethod(f, Tuple{T})

# Alternatively, the first argument can be a concrete object. By default we
# then redispatch to the type based version. But one may also choose to
# implement custom methods for this: certain operations will only work for
# *some* instances. E.g. for `Z/nZ` it may happen that for `n` a prime we can
# perform a certain operation, but not if `n` is composite.
#
# In that case the recommendation is that `_implements` invoked on the type
# returns `false`, but invoked on a concrete instance of a type, it may use
# specifics of the instance to also return `true` if appropriate.
function _implements(x::T, f::Any) where {T}
@assert !(x isa Type) # paranoia
return _implements(T, f)
end

# helper for `_implements` which checks if `f` has a method explicitly for
# a concrete type `T` (i.e. not a generic method that can be specialized to `T`
# but really one that is implement for `T` and `T` only).
function _implements_directly(::Type{T}, f::Any) where {T}
isconcretetype(T) || return false # TODO: drop this?
meth = methods(f, Tuple{T})
# TODO: deal with type parameters: if `T` is `FreeAssociativeAlgebraElem{ZZRingElem}`
# and `f` has a method for `FreeAssociativeAlgebraElem` then we should still consider
# this a match.
return any(m -> m.sig == Tuple{typeof(f), T}, meth)
end


###############################################################################
#
# The following function stubs' actual implementations are in the folder `ext/TestExt/`.
Expand Down
2 changes: 2 additions & 0 deletions src/FreeAssociativeAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ function is_unit(a::FreeAssociativeAlgebraElem{T}) where T
end
end

ConformanceTests._implements(::Type{FreeAssociativeAlgebraElem{T}}, f::typeof(is_unit)) where T = is_domain_type(T) || _implements_directly(T, f)

###############################################################################
#
# Hashing
Expand Down
6 changes: 6 additions & 0 deletions src/MPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ function is_unit(f::T) where {T <: MPolyRingElem}
return constant_term_is_unit # handles the case that there is no constant term
end

ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

function content(a::MPolyRingElem{T}) where T <: RingElement
z = zero(coefficient_ring(a))
for c in coefficients(a)
Expand All @@ -459,12 +461,16 @@ function is_nilpotent(f::T) where {T <: MPolyRingElem}
return all(is_nilpotent, coefficients(f))
end

ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)


function is_zero_divisor(x::MPolyRingElem{T}) where T <: RingElement
is_domain_type(T) && return is_zero(x)
return is_zero_divisor(content(x))
end

ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)

function is_zero_divisor_with_annihilator(a::MPolyRingElem{T}) where T <: RingElement
f, b = is_zero_divisor_with_annihilator(content(a))
return f, parent(a)(b)
Expand Down
4 changes: 4 additions & 0 deletions src/MatRing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ is_unit(a::MatRingElem{T}) where T <: RingElement = is_unit(det(a))

is_unit(a::MatRingElem{T}) where T <: FieldElement = rank(a) == degree(a)

ConformanceTests._implements(::Type{MatRingElem{T}}, ::typeof(is_unit)) where {T <: RingElement} = _implements(T, is_unit)

# proof over a commutative ring: use adj(A)*A = det(A)*I = A*adj(A)
is_zero_divisor(a::MatRingElem{T}) where T <: RingElement = is_zero_divisor(det(a))

is_zero_divisor(a::MatRingElem{T}) where T <: FieldElement = rank(a) != degree(a)

ConformanceTests._implements(::Type{MatRingElem{T}}, ::typeof(is_zero_divisor)) where {T <: RingElement} = _implements(T, is_zero_divisor)

function is_zero_divisor_with_annihilator(a::MatRingElem{T}) where T <: RingElement
f, b = is_zero_divisor_with_annihilator(det(a))
throw(NotImplementedError(:adj, a)) #return f, b*adj(A)
Expand Down
4 changes: 2 additions & 2 deletions src/Matrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4142,13 +4142,13 @@ Return if `A` is nilpotent, i.e. if there exists a natural number $k$
such that $A^k = 0$. If `A` is not square an exception is raised.
"""
function is_nilpotent(A::MatrixElem{T}) where {T <: RingElement}
is_domain_type(T) || error("Only supported over integral domains")
!is_square(A) && error("Dimensions don't match in is_nilpotent")
is_zero(A) && return true
is_domain_type(T) || error("Only supported over integral domains")
is_zero(tr(A)) || return false
n = nrows(A)
A = deepcopy(A)
i = 1
is_zero(A) && return true
while i < n
i *= 2
A = mul!(A, A, A)
Expand Down
2 changes: 2 additions & 0 deletions src/NCRings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ function is_nilpotent(a::T) where {T <: NCRingElement}
throw(NotImplementedError(:is_nilpotent, a))
end

ConformanceTests._implements(::Type{T}, f::typeof(is_nilpotent)) where {T <: NCRingElement} = is_domain_type(T) || _implements_directly(T, f)


@doc raw"""
is_square(a::NCRingElement)
Expand Down
5 changes: 5 additions & 0 deletions src/Poly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ function is_unit(f::T) where {T <: PolyRingElem}
return true
end

ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

function is_nilpotent(f::T) where {T <: PolyRingElem}
# Makes essential use of the fact that sum of 2 nilpotents is nilpotent.
# This is true when the coeffs are commutative.
Expand All @@ -280,9 +282,12 @@ function is_nilpotent(f::T) where {T <: PolyRingElem}
return all(is_nilpotent, coefficients(f))
end

ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)

is_zero_divisor(a::PolynomialElem) = is_zero_divisor(content(a))

ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)

function is_zero_divisor_with_annihilator(a::PolyRingElem{T}) where T <: RingElement
f, b = is_zero_divisor_with_annihilator(content(a))
return f, parent(a)(b)
Expand Down
9 changes: 9 additions & 0 deletions src/algorithms/GenericFunctions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,8 @@ function is_zero_divisor(a::T) where T <: RingElement
return is_zero(a) && !is_trivial(parent(a))
end

ConformanceTests._implements(::Type{T}, f::typeof(is_zero_divisor)) where {T} = is_domain_type(T) || _implements_directly(T, f)

@doc raw"""
is_zero_divisor_with_annihilator(a::T) where T <: RingElement

Expand Down Expand Up @@ -456,6 +458,8 @@ function factor(a::RingElement)
throw(NotImplementedError(:factor, a))
end

ConformanceTests._implements(::Type{T}, f::typeof(factor)) where {T} = _implements_directly(T, f)

@doc raw"""
factor_squarefree(a::T) where T <: RingElement -> Fac{T}

Expand All @@ -466,6 +470,8 @@ function factor_squarefree(a::RingElement)
throw(NotImplementedError(:factor_squarefree, a))
end

ConformanceTests._implements(::Type{T}, f::typeof(factor_squarefree)) where {T} = _implements_directly(T, f)

@doc raw"""
is_irreducible(a::RingElement)

Expand All @@ -479,6 +485,8 @@ function is_irreducible(a::RingElement)
return length(af) == 1 && all(isone, values(af.fac))
end

ConformanceTests._implements(::Type{T}, ::typeof(is_irreducible)) where {T} = _implements(T, is_unit) && _implements(T, factor)

@doc raw"""
is_squarefree(a::RingElement)

Expand All @@ -493,3 +501,4 @@ function is_squarefree(a::RingElement)
return all(isone, values(af.fac))
end

ConformanceTests._implements(::Type{T}, ::typeof(is_squarefree)) where {T} = _implements(T, is_unit) && _implements(T, factor_squarefree)
4 changes: 4 additions & 0 deletions src/algorithms/LaurentPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ function is_nilpotent(f::T) where {T <: LaurentPolyRingElem}
return is_nilpotent(f.poly);
end

ConformanceTests._implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

ConformanceTests._implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)


###############################################################################
#
Expand Down
4 changes: 4 additions & 0 deletions src/generic/LaurentMPoly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,18 @@ function is_unit(f::T) where {T <: LaurentMPolyRingElem}
return unit_seen
end

ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)

function is_nilpotent(f::T) where {T <: LaurentMPolyRingElem}
return is_nilpotent(f.mpoly);
end

ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)

is_zero_divisor(p::LaurentMPolyWrap) = is_zero_divisor(p.mpoly)

ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)

function is_zero_divisor_with_annihilator(p::LaurentMPolyWrap)
f, b = is_zero_divisor_with_annihilator(p.mpoly)
return f, LaurentMPolyWrap(parent(p), b)
Expand Down
Loading