From 4678b4118ac8f60ca508869292ed2c7f0c7b8d7d Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 15 Jan 2025 01:18:07 +0100 Subject: [PATCH] Proof-of-concept: _implements trait --- src/FreeAssociativeAlgebra.jl | 2 ++ src/MPoly.jl | 6 +++++ src/NCPoly.jl | 4 +++ src/NCRings.jl | 2 ++ src/algorithms/GenericFunctions.jl | 9 +++++++ src/algorithms/LaurentPoly.jl | 4 +++ src/fundamental_interface.jl | 42 ++++++++++++++++++++++++++++++ src/generic/LaurentMPoly.jl | 4 +++ 8 files changed, 73 insertions(+) diff --git a/src/FreeAssociativeAlgebra.jl b/src/FreeAssociativeAlgebra.jl index 5b6e6c025d..b9f5d1b39e 100644 --- a/src/FreeAssociativeAlgebra.jl +++ b/src/FreeAssociativeAlgebra.jl @@ -135,6 +135,8 @@ function is_unit(a::FreeAssociativeAlgebraElem{T}) where T end end +_implements(::Type{FreeAssociativeAlgebraElem{T}}, f::typeof(is_unit)) where T = is_domain_type(T) || _implements_directly(T, f) + ############################################################################### # # Hashing diff --git a/src/MPoly.jl b/src/MPoly.jl index 17e7f6cd22..52a631e1bb 100644 --- a/src/MPoly.jl +++ b/src/MPoly.jl @@ -445,6 +445,8 @@ function is_unit(f::T) where {T <: MPolyRingElem} return constant_term_is_unit end +_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) @@ -459,12 +461,16 @@ function is_nilpotent(f::T) where {T <: MPolyRingElem} return all(is_nilpotent, coefficients(f)) end +_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 +_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) diff --git a/src/NCPoly.jl b/src/NCPoly.jl index 2f036ad43b..15a2f6e826 100644 --- a/src/NCPoly.jl +++ b/src/NCPoly.jl @@ -814,3 +814,7 @@ function is_nilpotent(f::T) where {T <: PolynomialElem} is_domain_type(T) && return is_zero(f) return all(is_nilpotent, coefficients(f)) end + +_implements(::Type{PolynomialElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent) + +_implements(::Type{PolynomialElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent) diff --git a/src/NCRings.jl b/src/NCRings.jl index 523f859d93..525932423a 100644 --- a/src/NCRings.jl +++ b/src/NCRings.jl @@ -175,6 +175,8 @@ function is_nilpotent(a::T) where {T <: NCRingElement} throw(NotImplementedError(:is_nilpotent, a)) end +_implements(::Type{T}, f::typeof(is_nilpotent)) where {T <: NCRingElement} = is_domain_type(T) || _implements_directly(T, f) + ############################################################################### # diff --git a/src/algorithms/GenericFunctions.jl b/src/algorithms/GenericFunctions.jl index 0b35339f18..ecc10842a6 100644 --- a/src/algorithms/GenericFunctions.jl +++ b/src/algorithms/GenericFunctions.jl @@ -429,6 +429,8 @@ function is_zero_divisor(a::T) where T <: RingElement return is_zero(a) && !is_zero(one(parent(a))) end +_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 @@ -456,6 +458,8 @@ function factor(a) throw(NotImplementedError(:factor, a)) end +_implements(::Type{T}, f::typeof(factor)) where {T} = _implements_directly(T, f) + @doc raw""" factor_squarefree(a::T) where T <: RingElement -> Fac{T} @@ -466,6 +470,8 @@ function factor_squarefree(a) throw(NotImplementedError(:factor_squarefree, a)) end +_implements(::Type{T}, f::typeof(factor_squarefree)) where {T} = _implements_directly(T, f) + @doc raw""" is_irreducible(a::RingElement) @@ -479,6 +485,8 @@ function is_irreducible(a) return length(af) == 1 && all(isone, values(af.fac)) end +_implements(::Type{T}, ::typeof(is_irreducible)) where {T} = _implements(T, is_unit) && _implements(T, factor) + @doc raw""" is_squarefree(a::RingElement) @@ -493,3 +501,4 @@ function is_squarefree(a) return all(isone, values(af.fac)) end +_implements(::Type{T}, ::typeof(is_squarefree)) where {T} = _implements(T, is_unit) && _implements(T, factor_squarefree) diff --git a/src/algorithms/LaurentPoly.jl b/src/algorithms/LaurentPoly.jl index ada0ca8822..95d93fa565 100644 --- a/src/algorithms/LaurentPoly.jl +++ b/src/algorithms/LaurentPoly.jl @@ -159,6 +159,10 @@ function is_nilpotent(f::T) where {T <: LaurentPolyRingElem} return is_nilpotent(f.poly); end +_implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent) + +_implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent) + ############################################################################### # diff --git a/src/fundamental_interface.jl b/src/fundamental_interface.jl index 407767e34e..17b213fb74 100644 --- a/src/fundamental_interface.jl +++ b/src/fundamental_interface.jl @@ -439,3 +439,45 @@ function _number_of_direct_product_factors end Return the homomorphism from the domain `D` into the codomain `C` defined by the data. """ function hom end + +############################################################################### +# +# +# +############################################################################### + +# 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}) + +# 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 diff --git a/src/generic/LaurentMPoly.jl b/src/generic/LaurentMPoly.jl index 89db476fc6..9f1ed57ed8 100644 --- a/src/generic/LaurentMPoly.jl +++ b/src/generic/LaurentMPoly.jl @@ -130,6 +130,10 @@ function is_nilpotent(f::T) where {T <: LaurentMPolyRingElem} return is_nilpotent(f.mpoly); end +_implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent) + +_implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent) + is_zero_divisor(p::LaurentMPolyWrap) = is_zero_divisor(p.mpoly)