Skip to content

Commit 8947539

Browse files
authored
Add generic is_unit and is_nilpotent (#1933)
1 parent 4ae9859 commit 8947539

File tree

15 files changed

+593
-83
lines changed

15 files changed

+593
-83
lines changed

src/MPoly.jl

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -425,16 +425,22 @@ end
425425

426426
iszero(x::MPolyRingElem{T}) where T <: RingElement = length(x) == 0
427427

428-
function is_unit(a::MPolyRingElem{T}) where T <: RingElement
429-
if is_constant(a)
430-
return is_unit(leading_coefficient(a))
431-
elseif is_domain_type(elem_type(coefficient_ring(a)))
432-
return false
433-
elseif length(a) == 1
434-
return false
435-
else
436-
throw(NotImplementedError(:is_unit, a))
437-
end
428+
function is_unit(f::T) where {T <: MPolyRingElem}
429+
# for constant polynomials we delegate to the coefficient ring:
430+
is_constant(f) && return is_unit(constant_coefficient(f))
431+
# Here deg(f) > 0; over an integral domain, non-constant polynomials are never units:
432+
is_domain_type(T) && return false
433+
# A polynomial over a commutative ring is a unit iff its
434+
# constant term is a unit and all other coefficients are nilpotent:
435+
# see e.g. <https://kconrad.math.uconn.edu/blurbs/ringtheory/polynomial-properties.pdf> for a proof.
436+
for (c, expv) in zip(coefficients(f), exponent_vectors(f))
437+
if is_zero(expv)
438+
is_unit(c) || return false
439+
else
440+
is_nilpotent(c) || return false
441+
end
442+
end
443+
return true
438444
end
439445

440446
function content(a::MPolyRingElem{T}) where T <: RingElement
@@ -445,8 +451,16 @@ function content(a::MPolyRingElem{T}) where T <: RingElement
445451
return z
446452
end
447453

454+
455+
function is_nilpotent(f::T) where {T <: MPolyRingElem}
456+
is_domain_type(T) && return is_zero(f)
457+
return all(is_nilpotent, coefficients(f))
458+
end
459+
460+
448461
function is_zero_divisor(x::MPolyRingElem{T}) where T <: RingElement
449-
return is_zero_divisor(content(x))
462+
is_domain_type(T) && return is_zero(x)
463+
return is_zero_divisor(content(x))
450464
end
451465

452466
function is_zero_divisor_with_annihilator(a::MPolyRingElem{T}) where T <: RingElement

src/NCPoly.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,3 +783,34 @@ polynomial_ring_only(R::T, s::Symbol; cached::Bool=true) where T<:NCRing =
783783
# Simplified constructor
784784

785785
PolyRing(R::NCRing) = polynomial_ring_only(R, :x; cached=false)
786+
787+
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

src/NCRings.jl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ Base.literal_pow(::typeof(^), x::NCRingElem, ::Val{p}) where {p} = x^p
137137
###############################################################################
138138

139139
@doc raw"""
140-
is_unit(a::T) where {T <: NCRingElem}
140+
is_unit(a::T) where {T <: NCRingElement}
141141
142142
Return true if $a$ is invertible, else return false.
143143
@@ -155,6 +155,27 @@ julia> is_unit(ZZ(-1)), is_unit(ZZ(4))
155155
"""
156156
function is_unit end
157157

158+
@doc raw"""
159+
is_nilpotent(a::T) where {T <: NCRingElement}
160+
161+
Return true iff $a$ is nilpotent, i.e. a^k == 0 for some k.
162+
163+
# Examples
164+
```jldoctest
165+
julia> R, _ = residue_ring(ZZ,720);
166+
167+
julia> S, x = polynomial_ring(R, :x);
168+
169+
julia> is_nilpotent(30*x), is_nilpotent(30+90*x), is_nilpotent(S(15))
170+
(true, true, false)
171+
```
172+
"""
173+
function is_nilpotent(a::T) where {T <: NCRingElement}
174+
is_domain_type(T) && return is_zero(a)
175+
throw(NotImplementedError(:is_nilpotent, a))
176+
end
177+
178+
158179
###############################################################################
159180
#
160181
# Characteristic

src/Poly.jl

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,9 @@ function is_monic(a::PolynomialElem)
222222
return isone(leading_coefficient(a))
223223
end
224224

225-
function is_unit(a::PolynomialElem)
226-
if length(a) <= 1
227-
return is_unit(coeff(a, 0))
228-
elseif is_domain_type(elem_type(coefficient_ring(a)))
229-
return false
230-
elseif !is_unit(coeff(a, 0)) || is_unit(coeff(a, length(a) - 1))
231-
return false
232-
else
233-
throw(NotImplementedError(:is_unit, a))
234-
end
235-
end
225+
# function is_unit(...) see NCPoly.jl
226+
# function is_nilpotent(...) see NCPoly.jl
227+
236228

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

src/Residue.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ function is_unit(a::ResElem)
8585
return isone(g)
8686
end
8787

88+
function is_nilpotent(res::ResElem)
89+
m = modulus(res)
90+
r = data(res)
91+
while true
92+
g = gcd(r, m)
93+
(g == m) && return true
94+
is_one(g) && return false
95+
m = divexact(m, g)
96+
g = mod(g, m); r = g^2 # if computation domain is limited precision integer then g = mod(g,m) guarantees that g^2 will not overflow!
97+
end
98+
end
99+
100+
88101
# currently residue rings are only allowed over domains
89102
# otherwise this function would be more complicated
90103
is_zero_divisor(a::ResElem) = !is_unit(a)

src/algorithms/LaurentPoly.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,28 @@ function is_monomial_recursive(p::LaurentPolyRingElem)
138138
is_monomial_recursive(coeff(p, dr[]))
139139
end
140140

141-
function is_unit(p::LaurentPolyRingElem)
142-
dr = degrees_range(p)
143-
length(dr) == 1 || return false
144-
is_unit(coeff(p, dr[]))
141+
function is_unit(f::T) where {T <: LaurentPolyRingElem}
142+
# **NOTE** f.poly is not normalized so that the degree 0 coeff is non-zero
143+
is_trivial(parent(f)) && return true # coeffs in zero ring
144+
unit_seen = false
145+
for i in 0:degree(f.poly)
146+
if is_nilpotent(coeff(f.poly, i))
147+
continue
148+
end
149+
if unit_seen || !is_unit(coeff(f.poly, i))
150+
return false
151+
end
152+
unit_seen = true
153+
end
154+
return unit_seen
145155
end
146156

157+
158+
function is_nilpotent(f::T) where {T <: LaurentPolyRingElem}
159+
return is_nilpotent(f.poly);
160+
end
161+
162+
147163
###############################################################################
148164
#
149165
# Comparisons

src/generic/LaurentMPoly.jl

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,28 @@ function Base.inv(a::LaurentMPolyWrap)
109109
return LaurentMPolyWrap(parent(a), inv(ap), neg!(ad, ad))
110110
end
111111

112-
function is_unit(a::LaurentMPolyWrap)
113-
(ap, ad) = _normalize(a)
114-
if is_domain_type(elem_type(coefficient_ring(a))) || length(ap) <= 1
115-
return is_unit(ap)
116-
else
117-
throw(NotImplementedError(:is_unit, a))
112+
function is_unit(f::T) where {T <: LaurentMPolyRingElem}
113+
# **NOTE** f.mpoly is not normalized in any way
114+
is_trivial(parent(f)) && return true # coeffs in zero ring
115+
unit_seen = false
116+
for i in 1:length(f.mpoly)
117+
if is_nilpotent(coeff(f.mpoly, i))
118+
continue
118119
end
120+
if unit_seen || !is_unit(coeff(f.mpoly, i))
121+
return false
122+
end
123+
unit_seen = true
124+
end
125+
return unit_seen
119126
end
120127

128+
129+
function is_nilpotent(f::T) where {T <: LaurentMPolyRingElem}
130+
return is_nilpotent(f.mpoly);
131+
end
132+
133+
121134
is_zero_divisor(p::LaurentMPolyWrap) = is_zero_divisor(p.mpoly)
122135

123136
function is_zero_divisor_with_annihilator(p::LaurentMPolyWrap)

src/generic/LaurentPoly.jl

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,6 @@ function Base.inv(p::LaurentPolyWrap)
201201
return LaurentPolyWrap(parent(p), inv(g), -p.mindeg-v)
202202
end
203203

204-
function is_unit(p::LaurentPolyWrap)
205-
v, g = _remove_gen(p)
206-
if is_domain_type(elem_type(coefficient_ring(p))) || length(g) <= 1
207-
return is_unit(g)
208-
else
209-
throw(NotImplementedError(:is_unit, p))
210-
end
211-
end
212-
213204
is_zero_divisor(p::LaurentPolyWrap) = is_zero_divisor(p.poly)
214205

215206
function is_zero_divisor_with_annihilator(p::LaurentPolyWrap)

src/generic/imports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ import ..AbstractAlgebra: is_exact_type
149149
import ..AbstractAlgebra: is_finite
150150
import ..AbstractAlgebra: is_gen
151151
import ..AbstractAlgebra: is_monomial
152+
import ..AbstractAlgebra: is_nilpotent
152153
import ..AbstractAlgebra: is_power
153154
import ..AbstractAlgebra: is_square
154155
import ..AbstractAlgebra: is_square_with_sqrt

src/julia/Float.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ zero(::Floats{T}) where T <: AbstractFloat = T(0)
3838

3939
one(::Floats{T}) where T <: AbstractFloat = T(1)
4040

41-
is_unit(a::AbstractFloat) = a != 0
41+
is_unit(a::AbstractFloat) = !is_zero(a)
4242

4343
canonical_unit(a::AbstractFloat) = a
4444

0 commit comments

Comments
 (0)