Skip to content

Commit ddd4340

Browse files
authored
Eltype fix; use T[] for 0-polynomial; speed up LaurentPolynomial operations (#487)
* T[] for zero polynomial * trim out use of one(T) * speed up Laurent;
1 parent 4df70ab commit ddd4340

15 files changed

+348
-180
lines changed

docs/src/reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ vander
7979

8080
## Plotting
8181

82-
Polynomials can be plotted directly using [Plots.jl](https://github.com/juliaplots/plots.jl).
82+
Polynomials can be plotted directly using [Plots.jl](https://github.com/juliaplots/plots.jl) or [Makie.jl](https://github.com/MakieOrg/Makie.jl).
8383

8484
```julia
8585
plot(::AbstractPolynomial; kwds...)

src/abstract.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ Some `T`s will not be successful
3030
* scalar mult: `c::T * p::Polynomial{T}` An ambiguity when `T <: AbstractPolynomial`
3131
* scalar mult: `p::Polynomial{T} * c::T` need not commute
3232
33-
* scalar add/sub: `p::Polynomial{T} + q::Polynomial{T}` should be defined
34-
* scalar sub: `p::Polynomial{T} - p::Polynomial{T}` generally needs `zeros(T,1)` defined for `zero(Polynomial{T})`
33+
* add/sub: `p::Polynomial{T} + q::Polynomial{T}` should be defined
34+
* sub: `p -p` sometimes needs `zero{T}` defined
3535
3636
* poly mult: `p::Polynomial{T} * q::Polynomial{T}` Needs "`T * T`" defined (e.g. `Base.promote_op(*, Vector{Int}, Vector{Int}))` needs to be something.)
3737
* poly powers: `p::Polynomial{T}^2` needs "`T^2`" defined
@@ -41,6 +41,7 @@ Some `T`s will not be successful
4141
4242
* evaluation: `p::Polynomial{T}(s::Number)`
4343
* evaluation `p::Polynomial{T}(c::T)` needs `T*T` defined
44+
* evaluation of a `0` polynomial requires `zero(T)` to be defined.
4445
4546
* derivatives: `derivative(p::Polynomial{T})`
4647
* integrals: `integrate(p::Polynomial{T})`

src/common.jl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ end
385385
function chop!(ps::Vector{T};
386386
rtol::Real = Base.rtoldefault(real(T)),
387387
atol::Real = 0,) where {T}
388-
388+
isempty(ps) && return ps
389389
tol = norm(ps) * rtol + atol
390390
for i = lastindex(ps):-1:1
391391
val = ps[i]
@@ -705,6 +705,7 @@ degreerange(p::AbstractPolynomial) = firstindex(p):lastindex(p)
705705
# getindex
706706
function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T}
707707
m,M = firstindex(p), lastindex(p)
708+
m > M && return zero(T)
708709
idx < m && throw(BoundsError(p, idx))
709710
idx > M && return zero(T)
710711
p.coeffs[idx - m + 1]
@@ -831,7 +832,7 @@ Returns a representation of 0 as the given polynomial.
831832
"""
832833
function Base.zero(::Type{P}) where {P<:AbstractPolynomial}
833834
T,X = eltype(P), indeterminate(P)
834-
(P){T,X}(zeros(T,1))
835+
(P){T,X}(T[])
835836
end
836837
Base.zero(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = zero((P){eltype(P),Symbol(var)}) #default 0⋅b₀
837838
Base.zero(p::P, var=indeterminate(p)) where {P <: AbstractPolynomial} = zero(P, var)
@@ -1031,7 +1032,7 @@ end
10311032

10321033
## -- multiplication
10331034

1034-
1035+
# Scalar multiplication; no assumption of commutivity
10351036
function scalar_mult(p::P, c::S) where {S, T, X, P<:AbstractPolynomial{T,X}}
10361037
result = coeffs(p) .* (c,)
10371038
(P){eltype(result), X}(result)
@@ -1044,11 +1045,11 @@ end
10441045

10451046
scalar_mult(p1::AbstractPolynomial, p2::AbstractPolynomial) = error("scalar_mult(::$(typeof(p1)), ::$(typeof(p2))) is not defined.") # avoid ambiguity, issue #435
10461047

1047-
function Base.:/(p::P, c::S) where {P <: AbstractPolynomial,S}
1048-
_convert(p, coeffs(p) ./ c)
1049-
end
1048+
# scalar div
1049+
Base.:/(p::P, c::S) where {P <: AbstractPolynomial,S} = scalar_div(p, c)
1050+
scalar_div(p::AbstractPolynomial, c) = scalar_mult(p, inv(c))
10501051

1051-
## polynomial p*q
1052+
## Polynomial p*q
10521053
## Polynomial multiplication formula depend on the particular basis used. The subtype must implement
10531054
function Base.:*(p1::P, p2::Q) where {T,X,P <: AbstractPolynomial{T,X},S,Y,Q <: AbstractPolynomial{S,Y}}
10541055
isconstant(p1) && return constantterm(p1) * p2
@@ -1060,6 +1061,7 @@ end
10601061

10611062
Base.:^(p::AbstractPolynomial, n::Integer) = Base.power_by_squaring(p, n)
10621063

1064+
10631065
function Base.divrem(num::P, den::O) where {P <: AbstractPolynomial,O <: AbstractPolynomial}
10641066
n, d = promote(num, den)
10651067
return divrem(n, d)

src/contrib.jl

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ using Base.Cartesian
55

66
# direct version (do not check if threshold is satisfied)
77
function fastconv(E::Array{T,N}, k::Array{T,N}) where {T,N}
8+
isempty(E) && return E
9+
isempty(k) && return k
810
retsize = ntuple(n -> size(E, n) + size(k, n) - 1, Val{N}())
911
ret = zeros(T, retsize)
1012
convn!(ret, E, k)
@@ -34,7 +36,6 @@ end
3436
module EvalPoly
3537
using LinearAlgebra
3638
function evalpoly(x::S, p::Tuple) where {S}
37-
p == () && return zero(S)
3839
if @generated
3940
N = length(p.parameters)
4041
ex = :(p[end]*_one(S))
@@ -51,14 +52,13 @@ evalpoly(x, p::AbstractVector) = _evalpoly(x, p)
5152

5253
# https://discourse.julialang.org/t/i-have-a-much-faster-version-of-evalpoly-why-is-it-faster/79899; improvement *and* closes #313
5354
function _evalpoly(x::S, p) where {S}
54-
i = lastindex(p)
5555

56-
@inbounds out = p[i]*_one(x)
56+
i = lastindex(p)
57+
@inbounds out = p[i] * _one(x)
5758
i -= 1
58-
5959
while i >= firstindex(p)
6060
@inbounds out = _muladd(out, x, p[i])
61-
i -= 1
61+
i -= 1
6262
end
6363

6464
return out
@@ -68,8 +68,8 @@ end
6868
function evalpoly(z::Complex, p::Tuple)
6969
if @generated
7070
N = length(p.parameters)
71-
a = :(p[end]*_one(z))
72-
b = :(p[end-1]*_one(z))
71+
a = :(p[end] .+ _zero(z)) # avoid one(x)
72+
b = :(p[end-1] .+ _zero(z))
7373
as = []
7474
for i in N-2:-1:1
7575
ai = Symbol("a", i)
@@ -124,6 +124,14 @@ _muladd(a, b::Matrix, c) = (a*I)*b + c*I
124124
_one(P::Type{<:Matrix}) = one(eltype(P))*I
125125
_one(x::Matrix) = one(eltype(x))*I
126126
_one(x) = one(x)
127+
128+
_zero(P::Type{<:Matrix}) = zero(eltype(P))*I
129+
function _zero(x::Matrix)
130+
m = LinearAlgebra.checksquare(x)
131+
zero(eltype(x)) * I(m)
132+
end
133+
_zero(x) = zero(x)
134+
127135
end
128136

129137
## get type of parametric composite type without type parameters

src/polynomials/ImmutablePolynomial.jl

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ end
6464

6565
## Various interfaces
6666
## Abstract Vector coefficients
67-
function ImmutablePolynomial{T,X, N}(coeffs::AbstractVector{T}) where {T,X, N}
67+
function ImmutablePolynomial{T, X, N}(coeffs::AbstractVector{T}) where {T,X, N}
6868
cs = NTuple{N,T}(coeffs[i] for i firstindex(coeffs):N)
6969
ImmutablePolynomial{T, X, N}(cs)
7070

@@ -188,44 +188,36 @@ function Base.:+(p1::P, p2::Q) where {T,X,N,P<:ImmutablePolynomial{T,X,N},
188188
end
189189

190190
## multiplication
191-
192191
function scalar_mult(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X,N, S <: Number}
193-
R = eltype(p[0] * c * 0)
194-
(N == 0 || iszero(c)) && return zero(ImmutablePolynomial{R,X})
195-
cs = p.coeffs .* c
196-
return ImmutablePolynomial(cs, X)
197-
end
192+
iszero(N) && return zero(ImmutablePolynomial{promote_type(T,S),X})
193+
iszero(c) && ImmutablePolynomial([p[0] .* c], X)
194+
return _polynomial(p, p.coeffs .* (c,))
195+
end
198196

199197
function scalar_mult(c::S, p::ImmutablePolynomial{T,X,N}) where {T, X,N, S <: Number}
200-
R = eltype(p[0] * c * 0)
201-
(N == 0 || iszero(c)) && return zero(ImmutablePolynomial{R,X})
202-
cs = p.coeffs .* c
203-
return ImmutablePolynomial(cs, X)
198+
iszero(N) && return zero(ImmutablePolynomial{promote_type(T,S),X})
199+
iszero(c) && ImmutablePolynomial([c .* p[0]], X)
200+
return _polynomial(p, (c,) .* p.coeffs)
204201
end
205202

206-
function Base.:/(p::ImmutablePolynomial{T,X,N}, c::S) where {T,X,N,S<:Number}
207-
R = eltype(one(T)/one(S))
203+
function _polynomial(p::ImmutablePolynomial{T,X,N}, cs) where {T, X, N}
204+
R = eltype(cs)
208205
P = ImmutablePolynomial{R,X}
209-
(N == 0 || isinf(c)) && return zero(P)
210-
cs = p.coeffs ./ c
211-
iszero(cs[end]) ? P(cs) : P{N}(cs) # more performant to specify when N is known
206+
iszero(cs[end]) ? P(cs) : P{N}(cs)
212207
end
213208

214-
215209
function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M}
216-
R = promote_type(T,S)
217-
P = ImmutablePolynomial{R,X}
218-
219-
(iszero(N) || iszero(M)) && return zero(P)
210+
(iszero(N) || iszero(M)) && return zero(ImmutablePolynomial{promote_type(T,S),X})
220211

221212
cs = (ImmutablePolynomial, p1.coeffs, p2.coeffs) #(p1.coeffs) ⊗ (p2.coeffs)
213+
R = eltype(cs)
214+
P = ImmutablePolynomial{R,X}
222215
iszero(cs[end]) ? P(cs) : P{N+M-1}(cs) # more performant to specify when N is known
223216

224217
end
225218

226219
Base.to_power_type(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = p
227220

228-
229221
## more performant versions borrowed from StaticArrays
230222
## https://github.com/JuliaArrays/StaticArrays.jl/blob/master/src/linalg.jl
231223
LinearAlgebra.norm(q::ImmutablePolynomial{T,X,0}) where {T,X} = zero(real(float(T)))

src/polynomials/LaurentPolynomial.jl

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export LaurentPolynomial
22

3+
34
"""
45
LaurentPolynomial{T,X}(coeffs::AbstractVector, [m::Integer = 0], [var = :x])
56
@@ -94,6 +95,11 @@ struct LaurentPolynomial{T, X} <: LaurentBasisPolynomial{T, X}
9495
new{T,X}(c, Ref(m′), Ref(n))
9596
end
9697
end
98+
# non copying version assumes trimmed coeffs
99+
function LaurentPolynomial{T,X}(::Val{false}, coeffs::AbstractVector{T},
100+
m::Integer=0) where {T, X}
101+
new{T,X}(coeffs, Ref(m), Ref(m + length(coeffs) - 1))
102+
end
97103
end
98104

99105
@register LaurentPolynomial
@@ -259,7 +265,7 @@ end
259265
function showterm(io::IO, ::Type{<:LaurentPolynomial}, pj::T, var, j, first::Bool, mimetype) where {T}
260266
if iszero(pj) return false end
261267
pj = printsign(io, pj, first, mimetype)
262-
if !(pj == one(T) && !(showone(T) || j == 0))
268+
if !(hasone(T) && pj == one(T) && !(showone(T) || j == 0))
263269
printcoefficient(io, pj, j, mimetype)
264270
end
265271
printproductsign(io, pj, j, mimetype)
@@ -413,57 +419,114 @@ function Base.:+(p::LaurentPolynomial{T,X}, c::S) where {T, X, S <: Number}
413419
end
414420

415421
##
416-
## Poly + and *
417-
##
418-
function Base.:+(p1::P, p2::P) where {T,X,P<:LaurentPolynomial{T,X}}
422+
## Poly +, - and *
423+
## uses some ideas from https://github.com/jmichel7/LaurentPolynomials.jl/blob/main/src/LaurentPolynomials.jl for speedups
424+
Base.:+(p1::LaurentPolynomial, p2::LaurentPolynomial) = add_sub(+, p1, p2)
425+
Base.:-(p1::LaurentPolynomial, p2::LaurentPolynomial) = add_sub(-, p1, p2)
419426

420-
isconstant(p1) && return constantterm(p1) + p2
421-
isconstant(p2) && return p1 + constantterm(p2)
427+
function add_sub(op, p1::P, p2::Q) where {T, X, P <: LaurentPolynomial{T,X},
428+
S, Y, Q <: LaurentPolynomial{S,Y}}
422429

430+
isconstant(p1) && return op(constantterm(p1), p2)
431+
isconstant(p2) && return op(p1, constantterm(p2))
432+
assert_same_variable(X, Y)
423433

424434
m1,n1 = (extrema degreerange)(p1)
425435
m2,n2 = (extrema degreerange)(p2)
426-
m,n = min(m1,m2), max(n1, n2)
436+
m, n = min(m1,m2), max(n1, n2)
427437

428-
as = zeros(T, length(m:n))
429-
for i in m:n
430-
as[1 + i-m] = p1[i] + p2[i]
431-
end
438+
R = typeof(p1.coeffs[1] + p2.coeffs[1]) # non-empty
439+
as = zeros(R, length(m:n))
440+
441+
d = m1 - m2
442+
d1, d2 = m1 > m2 ? (d,0) : (0, -d)
432443

433-
q = P(as, m)
434-
chop!(q)
444+
for (i, pᵢ) pairs(p1.coeffs)
445+
@inbounds as[d1 + i] = pᵢ
446+
end
447+
for (i, pᵢ) pairs(p2.coeffs)
448+
@inbounds as[d2 + i] = op(as[d2+i], pᵢ)
449+
end
435450

451+
m = _laurent_chop!(as, m)
452+
isempty(as) && return zero(LaurentPolynomial{R,X})
453+
q = LaurentPolynomial{R,X}(Val(false), as, m)
436454
return q
437455

438456
end
439457

440-
function Base.:*(p1::P, p2::P) where {T,X,P<:LaurentPolynomial{T,X}}
458+
function Base.:*(p1::P, p2::Q) where {T,X,P<:LaurentPolynomial{T,X},
459+
S,Y,Q<:LaurentPolynomial{S,Y}}
460+
461+
isconstant(p1) && return constantterm(p1) * p2
462+
isconstant(p2) && return p1 * constantterm(p2)
463+
assert_same_variable(X, Y)
441464

442465
m1,n1 = (extrema degreerange)(p1)
443466
m2,n2 = (extrema degreerange)(p2)
444467
m,n = m1 + m2, n1+n2
445468

446-
as = zeros(T, length(m:n))
447-
for i in eachindex(p1)
448-
p1ᵢ = p1[i]
449-
for j in eachindex(p2)
450-
as[1 + i+j - m] = muladd(p1ᵢ, p2[j], as[1 + i + j - m])
469+
R = promote_type(T,S)
470+
as = zeros(R, length(m:n))
471+
472+
for (i, p₁ᵢ) pairs(p1.coeffs)
473+
for (j, p₂ⱼ) pairs(p2.coeffs)
474+
@inbounds as[i+j-1] += p₁ᵢ * p₂ⱼ
451475
end
452476
end
453477

454-
p = P(as, m)
455-
chop!(p)
478+
m = _laurent_chop!(as, m)
479+
480+
isempty(as) && return zero(LaurentPolynomial{R,X})
481+
p = LaurentPolynomial{R,X}(Val(false), as, m)
456482

457483
return p
458484
end
459485

486+
function _laurent_chop!(as, m)
487+
while !isempty(as)
488+
if iszero(first(as))
489+
m += 1
490+
popfirst!(as)
491+
else
492+
break
493+
end
494+
end
495+
while !isempty(as)
496+
if iszero(last(as))
497+
pop!(as)
498+
else
499+
break
500+
end
501+
end
502+
m
503+
end
504+
460505
function scalar_mult(p::LaurentPolynomial{T,X}, c::Number) where {T,X}
461506
LaurentPolynomial(p.coeffs .* c, p.m[], Var(X))
462507
end
463508
function scalar_mult(c::Number, p::LaurentPolynomial{T,X}) where {T,X}
464509
LaurentPolynomial(c .* p.coeffs, p.m[], Var(X))
465510
end
466511

512+
513+
function integrate(p::P) where {T, X, P <: LaurentBasisPolynomial{T, X}}
514+
515+
R = typeof(constantterm(p) / 1)
516+
Q = (P){R,X}
517+
518+
hasnan(p) && return Q([NaN])
519+
iszero(p) && return zero(Q)
520+
521+
∫p = zero(Q)
522+
for (k, pₖ) pairs(p)
523+
iszero(pₖ) && continue
524+
k == -1 && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term"))
525+
∫p[k+1] = pₖ/(k+1)
526+
end
527+
∫p
528+
end
529+
467530
##
468531
## roots
469532
##

src/polynomials/Poly.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ end
116116

117117
function Polynomials.integrate(p::P, k::S) where {T, X, P <: Poly{T, X}, S<:Number}
118118

119-
R = eltype((one(T)+one(S))/1)
119+
R = eltype(one(T)/1 + one(S))
120120
Q = Poly{R,X}
121121
if hasnan(p) || isnan(k)
122122
return P([NaN]) # keep for Poly, not Q

0 commit comments

Comments
 (0)