Skip to content

Commit df116b4

Browse files
committed
Proof-of-concept: _implements trait
1 parent 7be7ddb commit df116b4

File tree

8 files changed

+116
-0
lines changed

8 files changed

+116
-0
lines changed

src/FreeAssociativeAlgebra.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ function is_unit(a::FreeAssociativeAlgebraElem{T}) where T
136136
end
137137
end
138138

139+
_implements(::Type{FreeAssociativeAlgebraElem{T}}, f::typeof(is_unit)) where T = is_domain_type(T) || _implements_directly(T, f)
140+
139141
###############################################################################
140142
#
141143
# Hashing

src/MPoly.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ function is_unit(f::T) where {T <: MPolyRingElem}
437437
return constant_term_is_unit # handles the case that there is no constant term
438438
end
439439

440+
_implements(::Type{MPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
441+
440442
function content(a::MPolyRingElem{T}) where T <: RingElement
441443
z = zero(coefficient_ring(a))
442444
for c in coefficients(a)
@@ -452,12 +454,16 @@ function is_nilpotent(f::T) where {T <: MPolyRingElem}
452454
return all(is_nilpotent, coefficients(f))
453455
end
454456

457+
_implements(::Type{MPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)
458+
455459

456460
function is_zero_divisor(x::MPolyRingElem{T}) where T <: RingElement
457461
is_domain_type(T) && return is_zero(x)
458462
return is_zero_divisor(content(x))
459463
end
460464

465+
_implements(::Type{MPolyRingElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)
466+
461467
function is_zero_divisor_with_annihilator(a::MPolyRingElem{T}) where T <: RingElement
462468
f, b = is_zero_divisor_with_annihilator(content(a))
463469
return f, parent(a)(b)

src/NCPoly.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,3 +785,36 @@ polynomial_ring_only(R::T, s::Symbol; cached::Bool=true) where T<:NCRing =
785785

786786
PolyRing(R::NCRing) = polynomial_ring_only(R, :x; cached=false)
787787

788+
789+
###############################################################################
790+
#
791+
# is_unit & is_nilpotent
792+
#
793+
###############################################################################
794+
795+
# ASSUMES structural interface is analogous to that for univariate polynomials
796+
797+
# This function handles both PolyRingElem & NCPolyRingElem
798+
function is_unit(f::T) where {T <: PolynomialElem}
799+
# constant coeff must itself be a unit
800+
is_unit(constant_coefficient(f)) || return false
801+
is_constant(f) && return true
802+
# Here deg(f) > 0; over an integral domain, non-constant polynomials are never units:
803+
is_domain_type(T) && return false
804+
for i in 1:degree(f) # we have already checked coeff(f,0)
805+
if !is_nilpotent(coeff(f, i))
806+
return false
807+
end
808+
end
809+
return true
810+
end
811+
812+
# This function handles both PolyRingElem & NCPolyRingElem
813+
function is_nilpotent(f::T) where {T <: PolynomialElem}
814+
is_domain_type(T) && return is_zero(f)
815+
return all(is_nilpotent, coefficients(f))
816+
end
817+
818+
_implements(::Type{PolynomialElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
819+
820+
_implements(::Type{PolynomialElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)

src/NCRings.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ function is_nilpotent(a::T) where {T <: NCRingElement}
175175
throw(NotImplementedError(:is_nilpotent, a))
176176
end
177177

178+
_implements(::Type{T}, f::typeof(is_nilpotent)) where {T <: NCRingElement} = is_domain_type(T) || _implements_directly(T, f)
179+
178180

179181
###############################################################################
180182
#

src/algorithms/GenericFunctions.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,8 @@ function is_zero_divisor(a::T) where T <: RingElement
429429
return is_zero(a) && !is_trivial(parent(a))
430430
end
431431

432+
_implements(::Type{T}, f::typeof(is_zero_divisor)) where {T} = is_domain_type(T) || _implements_directly(T, f)
433+
432434
@doc raw"""
433435
is_zero_divisor_with_annihilator(a::T) where T <: RingElement
434436
@@ -456,6 +458,8 @@ function factor(a)
456458
throw(NotImplementedError(:factor, a))
457459
end
458460

461+
_implements(::Type{T}, f::typeof(factor)) where {T} = _implements_directly(T, f)
462+
459463
@doc raw"""
460464
factor_squarefree(a::T) where T <: RingElement -> Fac{T}
461465
@@ -466,6 +470,8 @@ function factor_squarefree(a)
466470
throw(NotImplementedError(:factor_squarefree, a))
467471
end
468472

473+
_implements(::Type{T}, f::typeof(factor_squarefree)) where {T} = _implements_directly(T, f)
474+
469475
@doc raw"""
470476
is_irreducible(a::RingElement)
471477
@@ -479,6 +485,8 @@ function is_irreducible(a)
479485
return length(af) == 1 && all(isone, values(af.fac))
480486
end
481487

488+
_implements(::Type{T}, ::typeof(is_irreducible)) where {T} = _implements(T, is_unit) && _implements(T, factor)
489+
482490
@doc raw"""
483491
is_squarefree(a::RingElement)
484492
@@ -493,3 +501,4 @@ function is_squarefree(a)
493501
return all(isone, values(af.fac))
494502
end
495503

504+
_implements(::Type{T}, ::typeof(is_squarefree)) where {T} = _implements(T, is_unit) && _implements(T, factor_squarefree)

src/algorithms/LaurentPoly.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ function is_nilpotent(f::T) where {T <: LaurentPolyRingElem}
159159
return is_nilpotent(f.poly);
160160
end
161161

162+
_implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
163+
164+
_implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)
165+
162166

163167
###############################################################################
164168
#

src/fundamental_interface.jl

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,59 @@ function _number_of_direct_product_factors end
452452
Return the homomorphism from the domain `D` into the codomain `C` defined by the data.
453453
"""
454454
function hom end
455+
456+
###############################################################################
457+
#
458+
#
459+
#
460+
###############################################################################
461+
462+
# Calling `_implements(T, f)` checks whether a "sensible" method for the unary
463+
# function `f` is implemented for inputs of type `T`. The argument order is
464+
# meant to be similar to e.g. `isa`, and thus indicates `T implements f`.
465+
#
466+
# For example, `_implements(MyRingElem, is_unit)` should return true if
467+
# invoking `is_unit` on elements of type `MyRingElem` is supported.
468+
#
469+
# The generic fallback uses `hasmethod`. However, this may return `true` in
470+
# cases where it shouldn't, as we often provide generic methods for that rely
471+
# on other methods being implemented -- either for the same type, or for types
472+
# derived from it. For example the `is_nilpotent(::PolyElem{T})` method needs
473+
# `is_nilpotent(::T)` in order to work.
474+
#
475+
# To reflect this, additional `_implements` methods need to be provided.
476+
# We currently do this for at least the following functions:
477+
# - factor
478+
# - is_irreducible
479+
# - is_nilpotent
480+
# - is_squarefree
481+
# - is_unit
482+
# - is_zero_divisor
483+
#
484+
_implements(::Type{T}, f::Any) where {T} = hasmethod(f, Tuple{T})
485+
486+
# Alternatively, the first argument can be a concrete object. By default we
487+
# then redispatch to the type based version. But one may also choose to
488+
# implement custom methods for this: certain operations will only work for
489+
# *some* instances. E.g. for `Z/nZ` it may happen that for `n` a prime we can
490+
# perform a certain operation, but not if `n` is composite.
491+
#
492+
# In that case the recommendation is that `_implements` invoked on the type
493+
# returns `false`, but invoked on a concrete instance of a type, it may use
494+
# specifics of the instance to also return `true` if appropriate.
495+
function _implements(x::T, f::Any) where {T}
496+
@assert !(x isa Type) # paranoia
497+
return _implements(T, f)
498+
end
499+
500+
# helper for `_implements` which checks if `f` has a method explicitly for
501+
# a concrete type `T` (i.e. not a generic method that can be specialized to `T`
502+
# but really one that is implement for `T` and `T` only).
503+
function _implements_directly(::Type{T}, f::Any) where {T}
504+
isconcretetype(T) || return false # TODO: drop this?
505+
meth = methods(f, Tuple{T})
506+
# TODO: deal with type parameters: if `T` is `FreeAssociativeAlgebraElem{ZZRingElem}`
507+
# and `f` has a method for `FreeAssociativeAlgebraElem` then we should still consider
508+
# this a match.
509+
return any(m -> m.sig == Tuple{typeof(f), T}, meth)
510+
end

src/generic/LaurentMPoly.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ function is_nilpotent(f::T) where {T <: LaurentMPolyRingElem}
131131
return is_nilpotent(f.mpoly);
132132
end
133133

134+
_implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
135+
136+
_implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)
137+
134138

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

0 commit comments

Comments
 (0)