Skip to content

Commit 095c3ba

Browse files
authored
Negative exponents in SparsePolynomial (#411)
* negative exponents in SparsePolynomials * fix conversions to polynomial types * fix conversions * version bump to v3.1.2 * test conversions * test minimumexponent for SparsePolynomial * test show for negative exponents * firstindex instead of minimumexponent in LP * missing tests for PolynomialValues/Keys * fix testset * non-allocating == check
1 parent 8b1c45a commit 095c3ba

File tree

7 files changed

+135
-59
lines changed

7 files changed

+135
-59
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "Polynomials"
22
uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
33
license = "MIT"
44
author = "JuliaMath"
5-
version = "3.1.1"
5+
version = "3.1.2"
66

77
[deps]
88
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

src/common.jl

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,6 @@ Calculates the p-norm of the polynomial's coefficients
407407
"""
408408
function LinearAlgebra.norm(q::AbstractPolynomial, p::Real = 2)
409409
vs = values(q)
410-
isempty(vs) && return zero(eltype(q))
411410
return norm(vs, p) # if vs=() must be handled in special type
412411
end
413412

@@ -572,7 +571,7 @@ constantterm(p::AbstractPolynomial{T}) where {T} = p(zero(T))
572571
Return the degree of the polynomial, i.e. the highest exponent in the polynomial that
573572
has a nonzero coefficient. The degree of the zero polynomial is defined to be -1. The default method assumes the basis polynomial, `βₖ` has degree `k`.
574573
"""
575-
degree(p::AbstractPolynomial) = iszero(p) ? -1 : lastindex(p)
574+
degree(p::AbstractPolynomial) = iszero(coeffs(p)) ? -1 : length(coeffs(p)) - 1 + min(0, minimumexponent(p))
576575

577576

578577
"""
@@ -613,9 +612,15 @@ mapdomain(::P, x::AbstractArray) where {P <: AbstractPolynomial} = mapdomain(P,
613612

614613
#=
615614
indexing =#
615+
# minimumexponent(p) returns min(0, minimum(keys(p)))
616+
# For most polynomial types, this is statically known to be zero
617+
# For polynomials that support negative indices, minimumexponent(typeof(p))
618+
# should return typemin(Int)
619+
minimumexponent(p::AbstractPolynomial) = minimumexponent(typeof(p))
620+
minimumexponent(::Type{<:AbstractPolynomial}) = 0
616621
Base.firstindex(p::AbstractPolynomial) = 0
617-
Base.lastindex(p::AbstractPolynomial) = length(p) - 1
618-
Base.eachindex(p::AbstractPolynomial) = 0:length(p) - 1
622+
Base.lastindex(p::AbstractPolynomial) = length(p) - 1 + firstindex(p)
623+
Base.eachindex(p::AbstractPolynomial) = firstindex(p):lastindex(p)
619624
Base.broadcastable(p::AbstractPolynomial) = Ref(p)
620625

621626
# getindex
@@ -671,7 +676,7 @@ Base.iterate(p::AbstractPolynomial, state = firstindex(p)) = _iterate(p, state)
671676
struct PolynomialKeys{P} <: AbstractSet{Int}
672677
p::P
673678
end
674-
struct PolynomialValues{P, T} <: AbstractSet{T}
679+
struct PolynomialValues{P, T}
675680
p::P
676681

677682
PolynomialValues{P}(p::P) where {P} = new{P, eltype(p)}(p)
@@ -680,6 +685,7 @@ end
680685
Base.keys(p::AbstractPolynomial) = PolynomialKeys(p)
681686
Base.values(p::AbstractPolynomial) = PolynomialValues(p)
682687
Base.length(p::PolynomialValues) = length(p.p.coeffs)
688+
Base.eltype(p::PolynomialValues{<:Any,T}) where {T} = T
683689
Base.length(p::PolynomialKeys) = length(p.p.coeffs)
684690
Base.size(p::Union{PolynomialValues, PolynomialKeys}) = (length(p),)
685691
function Base.iterate(v::PolynomialKeys, state = firstindex(v.p))
@@ -1018,8 +1024,15 @@ Base.rem(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[2]
10181024
#=
10191025
Comparisons =#
10201026
Base.isequal(p1::P, p2::P) where {P <: AbstractPolynomial} = hash(p1) == hash(p2)
1021-
Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) =
1022-
check_same_variable(p1,p2) && (coeffs(p1) == coeffs(p2))
1027+
function Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial)
1028+
check_same_variable(p1,p2) || return false
1029+
iszero(p1) && iszero(p2) && return true
1030+
eachindex(p1) == eachindex(p2) || return false
1031+
# coeffs(p1) == coeffs(p2), but non-allocating
1032+
p1val = (p1[i] for i in eachindex(p1))
1033+
p2val = (p2[i] for i in eachindex(p2))
1034+
all(((a,b),) -> a == b, zip(p1val, p2val))
1035+
end
10231036
Base.:(==)(p::AbstractPolynomial, n::Number) = degree(p) <= 0 && constantterm(p) == n
10241037
Base.:(==)(n::Number, p::AbstractPolynomial) = p == n
10251038

src/polynomials/LaurentPolynomial.jl

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,6 @@ Base.promote_rule(::Type{P},::Type{Q}) where {T, X, P <: LaurentPolynomial{T,X},
126126
Base.promote_rule(::Type{Q},::Type{P}) where {T, X, P <: LaurentPolynomial{T,X}, S, Q <: StandardBasisPolynomial{S,X}} =
127127
LaurentPolynomial{promote_type(T, S),X}
128128

129-
function Base.convert(P::Type{<:Polynomial}, q::LaurentPolynomial)
130-
m,n = (extremadegreerange)(q)
131-
m < 0 && throw(ArgumentError("Can't convert a Laurent polynomial with m < 0"))
132-
P([q[i] for i in 0:n], indeterminate(q))
133-
end
134-
135129
# need to add p.m[], so abstract.jl method isn't sufficent
136130
# XXX unlike abstract.jl, this uses Y variable in conversion; no error
137131
# Used in DSP.jl
@@ -154,7 +148,7 @@ function Base.convert(::Type{P}, q::StandardBasisPolynomial{S}) where {P <:Laure
154148

155149
T = _eltype(P, q)
156150
X = indeterminate(P, q)
157-
(P){T,X}([q[i] for i in 0:degree(q)], 0)
151+
(P){T,X}([q[i] for i in eachindex(q)], firstindex(q))
158152
end
159153

160154
function Base.convert(::Type{P}, q::AbstractPolynomial) where {P <:LaurentPolynomial}
@@ -215,9 +209,10 @@ function Base.setindex!(p::LaurentPolynomial{T}, value::Number, idx::Int) where
215209

216210
end
217211

218-
Base.firstindex(p::LaurentPolynomial) = p.m[]
219-
Base.lastindex(p::LaurentPolynomial) = p.n[]
220-
Base.eachindex(p::LaurentPolynomial) = degreerange(p)
212+
minimumexponent(::Type{<:LaurentPolynomial}) = typemin(Int)
213+
minimumexponent(p::LaurentPolynomial) = p.m[]
214+
Base.firstindex(p::LaurentPolynomial) = minimumexponent(p)
215+
degree(p::LaurentPolynomial) = p.n[]
221216
degreerange(p::LaurentPolynomial) = firstindex(p):lastindex(p)
222217

223218
_convert(p::P, as) where {T,X,P <: LaurentPolynomial{T,X}} = (P)(as, firstindex(p), Var(X))

src/polynomials/SparsePolynomial.jl

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,9 @@ function SparsePolynomial{T,X}(coeffs::AbstractVector{S}) where {T, X, S}
7373
return SparsePolynomial{T,X}(p)
7474
end
7575

76-
77-
78-
79-
# conversion
80-
function Base.convert(P::Type{<:Polynomial}, q::SparsePolynomial)
81-
(P)(coeffs(q), indeterminate(q))
82-
end
83-
84-
function Base.convert(P::Type{<:SparsePolynomial}, q::StandardBasisPolynomial{T}) where {T}
85-
R = promote_type(eltype(P), T)
86-
(P){R,indeterminate(P,q)}(coeffs(q))
87-
end
76+
minimumexponent(::Type{<:SparsePolynomial}) = typemin(Int)
77+
minimumexponent(p::SparsePolynomial) = isempty(p.coeffs) ? 0 : min(0, minimum(keys(p.coeffs)))
78+
Base.firstindex(p::SparsePolynomial) = minimumexponent(p)
8879

8980
## changes to common
9081
degree(p::SparsePolynomial) = isempty(p.coeffs) ? -1 : maximum(keys(p.coeffs))
@@ -94,6 +85,8 @@ function isconstant(p::SparsePolynomial)
9485
return true
9586
end
9687

88+
Base.convert(::Type{T}, p::StandardBasisPolynomial) where {T<:SparsePolynomial} = T(Dict(pairs(p)))
89+
9790
function basis(P::Type{<:SparsePolynomial}, n::Int)
9891
T,X = eltype(P), indeterminate(P)
9992
SparsePolynomial{T,X}(Dict(n=>one(T)))
@@ -104,9 +97,10 @@ end
10497
function coeffs(p::SparsePolynomial{T}) where {T}
10598

10699
n = degree(p)
107-
cs = zeros(T, n+1)
100+
cs = zeros(T, length(p))
101+
keymin = firstindex(p)
108102
for (k,v) in p.coeffs
109-
cs[k+1]=v
103+
cs[k - keymin + 1] = v
110104
end
111105
cs
112106

@@ -141,7 +135,10 @@ function Base.iterate(v::PolynomialValues{SparsePolynomial{T,X}}, state...) wher
141135
return (y[1][2], y[2])
142136
end
143137

144-
Base.length(S::SparsePolynomial) = isempty(S.coeffs) ? 0 : maximum(keys(S.coeffs)) + 1
138+
Base.length(S::SparsePolynomial) = isempty(S.coeffs) ? 0 : begin
139+
minkey, maxkey = extrema(keys(S.coeffs))
140+
maxkey - min(0, minkey) + 1
141+
end
145142

146143
##
147144
## ----
@@ -198,6 +195,8 @@ function Base.:+(p1::P1, p2::P2) where {T,X, P1<:SparsePolynomial{T,X},
198195

199196
end
200197

198+
Base.:-(a::SparsePolynomial) = typeof(a)(Dict(k=>-v for (k,v) in a.coeffs))
199+
201200
## Multiplication
202201
function scalar_mult(p::P, c::S) where {T, X, P <: SparsePolynomial{T,X}, S<:Number}
203202

@@ -225,7 +224,6 @@ function scalar_mult(c::S, p::P) where {T, X, P <: SparsePolynomial{T,X}, S<:Num
225224
return q
226225
end
227226

228-
229227
function Base.:*(p::P, q::Q) where {T,X,P<:SparsePolynomial{T,X},
230228
S, Q<:SparsePolynomial{S,X}}
231229
R = promote_type(T,S)

src/polynomials/standard-basis.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolyno
4949
if isa(q, P)
5050
return q
5151
else
52+
minimumexponent(P) <= minimumexponent(q) ||
53+
throw(ArgumentError("a $P can not have a minimum exponent of $(minimumexponent(q))"))
5254
T = _eltype(P,q)
5355
X = indeterminate(P,q)
54-
return (P){T,X}([q[i] for i in 0:degree(q)])
56+
return (P){T,X}([q[i] for i in eachindex(q)])
5557
end
5658
end
5759

test/Poly.jl

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
## Tests for compatiability with the old form
2+
# Create a separate module so that include(file) doesn't import PolyCompat to Main
3+
module PolyTests
4+
5+
using LinearAlgebra
6+
using Test
7+
using SparseArrays
8+
using ..Polynomials
9+
using ..SpecialFunctions
10+
211
# test for not loaded
312
@test_throws UndefVarError Poly([1,2,3])
413
@test_throws UndefVarError poly([1,2,3])
@@ -29,7 +38,7 @@ p = Polynomial([1,2,3])
2938
@test_throws ErrorException polyder(p)
3039

3140

32-
41+
3342

3443

3544
## ---------
@@ -280,15 +289,15 @@ repr(p)
280289

281290
## changes to show
282291
p = Poly([1,2,3,1]) # leading coefficient of 1
283-
@test repr(p) == "Poly(1 + 2*x + 3*x^2 + x^3)"
292+
@test repr(p) == "$Poly(1 + 2*x + 3*x^2 + x^3)"
284293
p = Poly([1.0, 2.0, 3.0, 1.0])
285-
@test repr(p) == "Poly(1.0 + 2.0*x + 3.0*x^2 + 1.0*x^3)"
294+
@test repr(p) == "$Poly(1.0 + 2.0*x + 3.0*x^2 + 1.0*x^3)"
286295
p = Poly([1, im])
287-
@test repr(p) == "Poly(1 + im*x)"
296+
@test repr(p) == "$Poly(1 + im*x)"
288297
p = Poly([1+im, 1-im, -1+im, -1 - im])# minus signs
289-
@test repr(p) == "Poly(1 + im + (1 - im)x - (1 - im)x^2 - (1 + im)x^3)"
298+
@test repr(p) == "$Poly(1 + im + (1 - im)x - (1 - im)x^2 - (1 + im)x^3)"
290299
p = Poly([1.0, 0 + NaN*im, NaN, Inf, 0 - Inf*im]) # handle NaN or Inf appropriately
291-
@test repr(p) == "Poly(1.0 + NaN*im*x + NaN*x^2 + Inf*x^3 - Inf*im*x^4)"
300+
@test repr(p) == "$Poly(1.0 + NaN*im*x + NaN*x^2 + Inf*x^3 - Inf*im*x^4)"
292301

293302
p = Poly([1,2,3])
294303
@test repr("text/latex", p) == "\$1 + 2\\cdot x + 3\\cdot x^{2}\$"
@@ -497,3 +506,5 @@ end
497506
PQexpint = Pade(d, 30, 30)
498507
@test Float64(PQexpint(1.0)) exp(1) * (-γ - sum([(-1)^k / k / gamma(k + 1) for k = 1:20]))
499508
end
509+
510+
end # module

0 commit comments

Comments
 (0)