From 657112d27c18f9395a2994fa74ab7d08ef8a36da Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 5 Feb 2021 09:49:26 -0500 Subject: [PATCH 01/38] WIP --- src/Polynomials.jl | 14 +++++++------- src/abstract.jl | 23 ++++++++++++----------- src/common.jl | 20 ++++++++++++++------ src/polynomials/Polynomial.jl | 29 +++++++++++++++-------------- src/polynomials/standard-basis.jl | 19 +++++++++---------- src/show.jl | 4 ++-- 6 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 1eb39934..f984d6c6 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -17,15 +17,15 @@ include("common.jl") # Polynomials include("polynomials/standard-basis.jl") include("polynomials/Polynomial.jl") -include("polynomials/ImmutablePolynomial.jl") -include("polynomials/SparsePolynomial.jl") -include("polynomials/LaurentPolynomial.jl") -include("polynomials/ngcd.jl") -include("polynomials/multroot.jl") +#include("polynomials/ImmutablePolynomial.jl") +#include("polynomials/SparsePolynomial.jl") +#include("polynomials/LaurentPolynomial.jl") +#include("polynomials/ngcd.jl") +#include("polynomials/multroot.jl") -include("polynomials/ChebyshevT.jl") +#include("polynomials/ChebyshevT.jl") # compat; opt-in with `using Polynomials.PolyCompat` -include("polynomials/Poly.jl") +#include("polynomials/Poly.jl") end # module diff --git a/src/abstract.jl b/src/abstract.jl index 4bb0a100..2d4fb066 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -3,15 +3,14 @@ export AbstractPolynomial const SymbolLike = Union{AbstractString,Char,Symbol} """ - AbstractPolynomial{T} + AbstractPolynomial{T, X} An abstract container for various polynomials. # Properties - `coeffs` - The coefficients of the polynomial -- `var` - The indeterminate of the polynomial """ -abstract type AbstractPolynomial{T} end +abstract type AbstractPolynomial{T, X} end # We want ⟒(P{α…,T}) = P{α…}; this default # works for most cases @@ -19,7 +18,7 @@ abstract type AbstractPolynomial{T} end # convert `as` into polynomial of type P based on instance, inheriting variable # (and for LaurentPolynomial the offset) -_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P)(as, p.var) +_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P)(as, var(P)) """ Polynomials.@register(name) @@ -42,20 +41,22 @@ macro register(name) poly = esc(name) quote Base.convert(::Type{P}, p::P) where {P<:$poly} = p - Base.convert(P::Type{<:$poly}, p::$poly{T}) where {T} = P(coeffs(p), p.var) - Base.promote(p::P, q::Q) where {T, P <:$poly{T}, Q <: $poly{T}} = p,q - Base.promote_rule(::Type{<:$poly{T}}, ::Type{<:$poly{S}}) where {T,S} = - $poly{promote_type(T, S)} - Base.promote_rule(::Type{<:$poly{T}}, ::Type{S}) where {T,S<:Number} = - $poly{promote_type(T, S)} + Base.convert(P::Type{<:$poly}, p::$poly{T}) where {T} = P(coeffs(p), var(p)) + Base.promote(p::P, q::Q) where {X, T, P <:$poly{T,X}, Q <: $poly{T,X}} = p,q + Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{<:$poly{S,X}}) where {T,S,X} = + $poly{promote_type(T, S,X)} + Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{S}) where {T,S<:Number,X} = + $poly{promote_type(T, S),X} $poly(coeffs::AbstractVector{T}, var::SymbolLike = :x) where {T} = - $poly{T}(coeffs, Symbol(var)) + $poly{T, Symbol(var)}(coeffs) $poly{T}(x::AbstractVector{S}, var::SymbolLike = :x) where {T,S<:Number} = $poly(T.(x), Symbol(var)) function $poly(coeffs::G, var::SymbolLike=:x) where {G} !Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable")) $poly(collect(coeffs), var) end + $poly{T,X}(n::S) where {X, T, S<:Number} = + n * one($poly{T}, X) $poly{T}(n::S, var::SymbolLike = :x) where {T, S<:Number} = n * one($poly{T}, Symbol(var)) $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S}, Symbol(var)) diff --git a/src/common.jl b/src/common.jl index ac3b512c..e9e1c425 100644 --- a/src/common.jl +++ b/src/common.jl @@ -275,7 +275,7 @@ end Check if either `p` or `q` is constant or if `p` and `q` share the same variable """ check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) = - (Polynomials.isconstant(p) || Polynomials.isconstant(q)) || p.var == q.var + (Polynomials.isconstant(p) || Polynomials.isconstant(q)) || var(p) == var(q) #= Linear Algebra =# @@ -508,10 +508,18 @@ Base.setindex!(p::AbstractPolynomial, values, ::Colon) = #= identity =# Base.copy(p::P) where {P <: AbstractPolynomial} = _convert(p, copy(coeffs(p))) -Base.hash(p::AbstractPolynomial, h::UInt) = hash(p.var, hash(coeffs(p), h)) +Base.hash(p::AbstractPolynomial, h::UInt) = hash(var(p), hash(coeffs(p), h)) #= zero, one, variable, basis =# + +var(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X +var(::Type{P}) where {P <: AbstractPolynomial} = :x +var(p::AbstractPolynomial{T, X}) where {T, X} = X +export var + + + """ zero(::Type{<:AbstractPolynomial}) zero(::AbstractPolynomial) @@ -519,7 +527,7 @@ zero, one, variable, basis =# Returns a representation of 0 as the given polynomial. """ Base.zero(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P)(zeros(eltype(P), 1), var) -Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, p.var) +Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, var(p)) """ one(::Type{<:AbstractPolynomial}) one(::AbstractPolynomial) @@ -527,7 +535,7 @@ Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, p.var) Returns a representation of 1 as the given polynomial. """ Base.one(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P)(ones(eltype(P),1), var) # assumes p₀ = 1 -Base.one(p::P) where {P <: AbstractPolynomial} = one(P, p.var) +Base.one(p::P) where {P <: AbstractPolynomial} = one(P, var(p)) Base.oneunit(::Type{P}, args...) where {P <: AbstractPolynomial} = one(P, args...) Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) @@ -536,7 +544,7 @@ Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) """ variable(var=:x) variable(::Type{<:AbstractPolynomial}, var=:x) - variable(p::AbstractPolynomial, var=p.var) + variable(p::AbstractPolynomial, var=var(p)) Return the monomial `x` in the indicated polynomial basis. If no type is give, will default to [`Polynomial`](@ref). Equivalent to `P(var)`. @@ -558,7 +566,7 @@ julia> roots((x - 3) * (x + 2)) ``` """ variable(::Type{P}, var::SymbolLike = :x) where {P <: AbstractPolynomial} = MethodError() -variable(p::AbstractPolynomial, var::SymbolLike = p.var) = variable(typeof(p), var) +variable(p::AbstractPolynomial, var::SymbolLike = var(p)) = variable(typeof(p), var) variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) # basis diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index d54b094d..f8656f33 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -1,7 +1,7 @@ export Polynomial """ - Polynomial{T<:Number}(coeffs::AbstractVector{T}, [var = :x]) + Polynomial{T<:Number, X}(coeffs::AbstractVector{T}, [var = :x]) Construct a polynomial from its coefficients `coeffs`, lowest order first, optionally in terms of the given variable `var` which may be a character, symbol, or a string. @@ -30,18 +30,17 @@ julia> one(Polynomial) Polynomial(1.0) ``` """ -struct Polynomial{T <: Number} <: StandardBasisPolynomial{T} +struct Polynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Vector{T} - var::Symbol - function Polynomial{T}(coeffs::AbstractVector{T}, var::Symbol) where {T <: Number} + function Polynomial{T, X}(coeffs::AbstractVector{T}) where {T <: Number, X} if Base.has_offset_axes(coeffs) @warn "ignoring the axis offset of the coefficient vector" end - length(coeffs) == 0 && return new{T}(zeros(T, 1), var) + length(coeffs) == 0 && return new{T,X}(zeros(T, 1)) c = OffsetArrays.no_offset_view(coeffs) # ensure 1-based indexing last_nz = findlast(!iszero, c) last = max(1, last_nz === nothing ? 0 : last_nz) - return new{T}(c[1:last], var) + return new{T, X}(c[1:last]) end end @@ -75,7 +74,7 @@ julia> p.(0:3) function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} n1, n2 = length(p1), length(p2) if n1 > 1 && n2 > 1 - p1.var != p2.var && error("Polynomials must have same variable") + var(p1) != var(p2) && error("Polynomials must have same variable") end R = promote_type(T,S) c = zeros(R, max(n1, n2)) @@ -91,15 +90,15 @@ function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} c[i] += p1.coeffs[i] end end - return Polynomial(c, p1.var) + return Polynomial{R, var(p1)}(c) elseif n1 <= 1 c .= p2.coeffs c[1] += p1[0] - return Polynomial(c, p2.var) + return Polynomial{R, var(p2)}(c) else c .= p1.coeffs c[1] += p2[0] - return Polynomial(c, p1.var) + return Polynomial{R, var(p1)}(c) end end @@ -108,17 +107,19 @@ function Base.:*(p1::Polynomial{T}, p2::Polynomial{S}) where {T,S} n, m = length(p1)-1, length(p2)-1 # not degree, so pNULL works if n > 0 && m > 0 - p1.var != p2.var && error("Polynomials must have same variable") + var(p1) != var(p2) && error("Polynomials must have same variable") R = promote_type(T, S) c = zeros(R, m + n + 1) for i in 0:n, j in 0:m @inbounds c[i + j + 1] += p1[i] * p2[j] end - return Polynomial(c, p1.var) + return Polynomial{R, var(p1)}(c) elseif n <= 0 - return Polynomial(p2.coeffs * p1[0], p2.var) + cs = p2.coeffs * p1[0] + return Polynomial{eltype(cs), var(p2)}(cs) else - return Polynomial(p1.coeffs * p2[0], p1.var) + cs = p1.coeffs * p2[0] + return Polynomial{eltype(cs), var(p1)}(cs) end end diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index d0098477..93ccf9c2 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -1,4 +1,4 @@ -abstract type StandardBasisPolynomial{T} <: AbstractPolynomial{T} end +abstract type StandardBasisPolynomial{T,X} <: AbstractPolynomial{T,X} end @@ -26,7 +26,7 @@ mapdomain(::Type{<:StandardBasisPolynomial}, x::AbstractArray) = x ## generic test if polynomial `p` is a constant isconstant(p::StandardBasisPolynomial) = degree(p) <= 0 -Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) = isa(q, P) ? q : P([q[i] for i in 0:degree(q)], q.var) +Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) = isa(q, P) ? q : P([q[i] for i in 0:degree(q)], var(q)) Base.values(p::StandardBasisPolynomial) = values(p.coeffs) @@ -63,10 +63,10 @@ function derivative(p::P, order::Integer = 1) where {T, P <: StandardBasisPolyno # Base.promote_op(*, Complex, Int) R = eltype(one(T)*1) order == 0 && return p - hasnan(p) && return ⟒(P){R}(R[NaN], p.var) - order > length(p) && return zero(⟒(P){R},p.var) + hasnan(p) && return ⟒(P){R}(R[NaN], var(p)) + order > length(p) && return zero(⟒(P){R},var(p)) d = degree(p) - d <= 0 && return zero(⟒(P){R},p.var) + d <= 0 && return zero(⟒(P){R},var(p)) n = d + 1 a2 = Vector{R}(undef, n - order) @inbounds for i in order:n - 1 @@ -95,7 +95,7 @@ end function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} check_same_variable(num, den) || error("Polynomials must have same variable") - var = num.var + var = var(num) n = degree(num) @@ -284,7 +284,7 @@ function roots(p::P; kwargs...) where {T, P <: StandardBasisPolynomial{T}} k == K && return zeros(R, k-1) # find eigenvalues of the companion matrix - comp = companion(⟒(P)(as[k:K], p.var)) + comp = companion(⟒(P)(as[k:K], var(p))) L = eigvals(comp; kwargs...) append!(L, zeros(eltype(L), k-1)) @@ -433,10 +433,9 @@ end A polynomial type produced through fitting a degree ``n`` or less polynomial to data ``(x_1,y_1),…,(x_N, y_N), N ≥ n+1``, This uses Arnoldi orthogonalization to avoid the exponentially ill-conditioned Vandermonde polynomial. See [`Polynomials.polyfitA`](@ref) for details. """ -struct ArnoldiFit{T, M<:AbstractArray{T,2}} <: AbstractPolynomial{T} +struct ArnoldiFit{T, M<:AbstractArray{T,2}, X} <: AbstractPolynomial{T,X} coeffs::Vector{T} H::M - var::Symbol end export ArnoldiFit @register ArnoldiFit @@ -448,7 +447,7 @@ Base.show(io::IO, mimetype::MIME"text/plain", p::ArnoldiFit) = print(io, "Arnold fit(::Type{ArnoldiFit}, x::AbstractVector{T}, y::AbstractVector{T}, deg::Int=length(x)-1; var=:x, kwargs...) where{T} = polyfitA(x, y, deg; var=var) -Base.convert(::Type{P}, p::ArnoldiFit) where {P <: AbstractPolynomial} = p(variable(P,p.var)) +Base.convert(::Type{P}, p::ArnoldiFit) where {P <: AbstractPolynomial} = p(variable(P,var(p))) diff --git a/src/show.jl b/src/show.jl index f8930139..5921eb2c 100644 --- a/src/show.jl +++ b/src/show.jl @@ -93,7 +93,7 @@ end ### """ - printpoly(io::IO, p::AbstractPolynomial, mimetype = MIME"text/plain"(); descending_powers=false, offset::Int=0, var=p.var, compact=false, mulsymbol="*") + printpoly(io::IO, p::AbstractPolynomial, mimetype = MIME"text/plain"(); descending_powers=false, offset::Int=0, var=var(p), compact=false, mulsymbol="*") Print a human-readable representation of the polynomial `p` to `io`. The MIME types "text/plain" (default), "text/latex", and "text/html" are supported. By @@ -133,7 +133,7 @@ julia> printpoly(stdout, map(x -> round(x, digits=12), p)) # more control on ro ``` """ function printpoly(io::IO, p::P, mimetype=MIME"text/plain"(); - descending_powers=false, offset::Int=0, var=p.var, + descending_powers=false, offset::Int=0, var=var(p), compact=false, mulsymbol="*") where {T,P<:AbstractPolynomial{T}} first = true printed_anything = false From 2f5080e0dbc336f57c882e71223e1c9a9b6b5883 Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 5 Feb 2021 21:34:09 -0500 Subject: [PATCH 02/38] WIP --- src/Polynomials.jl | 10 +- src/abstract.jl | 19 ++-- src/common.jl | 11 +- src/polynomials/ImmutablePolynomial.jl | 140 +++++++++++-------------- src/polynomials/LaurentPolynomial.jl | 128 +++++++++++----------- src/polynomials/SparsePolynomial.jl | 75 ++++++------- 6 files changed, 175 insertions(+), 208 deletions(-) diff --git a/src/Polynomials.jl b/src/Polynomials.jl index f984d6c6..a7b9b2d2 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -17,11 +17,11 @@ include("common.jl") # Polynomials include("polynomials/standard-basis.jl") include("polynomials/Polynomial.jl") -#include("polynomials/ImmutablePolynomial.jl") -#include("polynomials/SparsePolynomial.jl") -#include("polynomials/LaurentPolynomial.jl") -#include("polynomials/ngcd.jl") -#include("polynomials/multroot.jl") +include("polynomials/ImmutablePolynomial.jl") +include("polynomials/SparsePolynomial.jl") +include("polynomials/LaurentPolynomial.jl") +include("polynomials/ngcd.jl") +include("polynomials/multroot.jl") #include("polynomials/ChebyshevT.jl") diff --git a/src/abstract.jl b/src/abstract.jl index 2d4fb066..06a9e7fe 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -3,14 +3,14 @@ export AbstractPolynomial const SymbolLike = Union{AbstractString,Char,Symbol} """ - AbstractPolynomial{T, X} + AbstractPolynomial{T} An abstract container for various polynomials. # Properties - `coeffs` - The coefficients of the polynomial """ -abstract type AbstractPolynomial{T, X} end +abstract type AbstractPolynomial{T,X} end # We want ⟒(P{α…,T}) = P{α…}; this default # works for most cases @@ -18,7 +18,7 @@ abstract type AbstractPolynomial{T, X} end # convert `as` into polynomial of type P based on instance, inheriting variable # (and for LaurentPolynomial the offset) -_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P)(as, var(P)) +_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P){eltype(as), var(P)}(as) # ⟒(P)(as, var(P)) """ Polynomials.@register(name) @@ -50,17 +50,18 @@ macro register(name) $poly(coeffs::AbstractVector{T}, var::SymbolLike = :x) where {T} = $poly{T, Symbol(var)}(coeffs) $poly{T}(x::AbstractVector{S}, var::SymbolLike = :x) where {T,S<:Number} = - $poly(T.(x), Symbol(var)) + $poly{T,Symbol(var)}(T.(x)) function $poly(coeffs::G, var::SymbolLike=:x) where {G} !Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable")) - $poly(collect(coeffs), var) + cs = collect(coeffs) + $poly{eltype(cs), Symbol(var)}(cs) end - $poly{T,X}(n::S) where {X, T, S<:Number} = - n * one($poly{T}, X) + $poly{T,X}(n::S) where {T, X, S<:Number} = + n * one($poly{T, X}) $poly{T}(n::S, var::SymbolLike = :x) where {T, S<:Number} = n * one($poly{T}, Symbol(var)) - $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S}, Symbol(var)) - $poly{T}(var::SymbolLike=:x) where {T} = variable($poly{T}, Symbol(var)) + $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S, Symbol(var)}) + $poly{T}(var::SymbolLike=:x) where {T} = variable($poly{T, Symbol(var)}) $poly(var::SymbolLike=:x) = variable($poly, Symbol(var)) end end diff --git a/src/common.jl b/src/common.jl index e9e1c425..c71f7426 100644 --- a/src/common.jl +++ b/src/common.jl @@ -527,6 +527,7 @@ export var Returns a representation of 0 as the given polynomial. """ Base.zero(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P)(zeros(eltype(P), 1), var) +Base.zero(::Type{P}) where {T, X, P<:AbstractPolynomial{T,X}} = ⟒(P){T,X}(zeros(T,1)) Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, var(p)) """ one(::Type{<:AbstractPolynomial}) @@ -535,6 +536,7 @@ Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, var(p)) Returns a representation of 1 as the given polynomial. """ Base.one(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P)(ones(eltype(P),1), var) # assumes p₀ = 1 +Base.one(::Type{P}) where {T, X, P<:AbstractPolynomial{T,X}} = ⟒(P){T,X}(ones(T,1)) Base.one(p::P) where {P <: AbstractPolynomial} = one(P, var(p)) Base.oneunit(::Type{P}, args...) where {P <: AbstractPolynomial} = one(P, args...) @@ -567,6 +569,7 @@ julia> roots((x - 3) * (x + 2)) """ variable(::Type{P}, var::SymbolLike = :x) where {P <: AbstractPolynomial} = MethodError() variable(p::AbstractPolynomial, var::SymbolLike = var(p)) = variable(typeof(p), var) +variable(::Type{P}) where {T,X, P <: AbstractPolynomial{T,X}} = variable(P, X) variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) # basis @@ -577,8 +580,14 @@ variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) function basis(::Type{P}, k::Int, _var::SymbolLike=:x; var=_var) where {P <: AbstractPolynomial} zs = zeros(Int, k+1) zs[end] = 1 - ⟒(P){eltype(P)}(zs, var) + ⟒(P){eltype(P), _var}(zs) end +function basis(::Type{P}, k::Int) where {T, X, P<:AbstractPolynomial{T,X}} + zs = zeros(Int, k+1) + zs[end] = 1 + ⟒(P){eltype(P), X}(zs) +end + basis(p::P, k::Int, _var::SymbolLike=:x; var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) #= diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index 2da4d26b..b24541c8 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -1,7 +1,7 @@ export ImmutablePolynomial """ - ImmutablePolynomial{T<:Number, N}(coeffs::AbstractVector{T}, var=:x) + ImmutablePolynomial{T<:Number, X, N}(coeffs::AbstractVector{T}) Construct an immutable (static) polynomial from its coefficients `a₀, a₁, …, aₙ`, @@ -45,78 +45,58 @@ ImmutablePolynomial(1.0) This was modeled after https://github.com/tkoolen/StaticUnivariatePolynomials.jl by @tkoolen. """ -struct ImmutablePolynomial{T <: Number, N} <: StandardBasisPolynomial{T} +struct ImmutablePolynomial{T <: Number, X, N} <: StandardBasisPolynomial{T, X} coeffs::NTuple{N, T} - var::Symbol - - function ImmutablePolynomial{T,N}(coeffs::NTuple{N,T}, var::SymbolLike=:x) where {T <: Number,N} - N == 0 && return new{T,0}(coeffs, Symbol(var)) + function ImmutablePolynomial{T,X,N}(coeffs::NTuple{N,T}) where {T <: Number, X, N} + N == 0 && return new{T,X, 0}(coeffs) iszero(coeffs[end]) && throw(ArgumentError("Leading term must be non-zero")) - new{T,N}(coeffs, Symbol(var)) + new{T,X,N}(coeffs) end - - end @register ImmutablePolynomial ## Various interfaces -function ImmutablePolynomial{T,N}(coeffs::Tuple, var::SymbolLike=:x) where {T,N} - ImmutablePolynomial{T,N}(NTuple{N,T}(T.(coeffs)), var) +function ImmutablePolynomial{T,X}(coeffs::AbstractVector) where {T,X} + N = findlast(!iszero, coeffs) + ImmutablePolynomial{T, X, N}(NTuple{N,T}(coeffs[i] for i in 1:N)) end -function ImmutablePolynomial{T,N}(coeffs::AbstractVector{S}, var::SymbolLike=:x) where {T <: Number, N, S} - if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" - end - ImmutablePolynomial{T,N}(NTuple{N,T}(tuple(coeffs...)), var) + +function ImmutablePolynomial{T,X}(coeffs::Tuple) where {T,X} + N = findlast(!iszero, coeffs) + ImmutablePolynomial{T, X, N}(NTuple{N,T}(T(coeffs[i]) for i in 1:N)) end +#function ImmutablePolynomial{T,N}(coeffs::AbstractVector{S}, var::SymbolLike=:x) where {T <: Number, N, S#} +# if Base.has_offset_axes(coeffs) +# @warn "ignoring the axis offset of the coefficient vector" +# end +# ImmutablePolynomial{T,var, N}(NTuple{N,T}(tuple(coeffs...))) +#end + ## -- function ImmutablePolynomial{T}(coeffs::NTuple{M,S}, var::SymbolLike=:x) where {T, S<: Number, M} N = findlast(!iszero, coeffs) if N == nothing - return zero(ImmutablePolynomial{T}, var) + return zero(ImmutablePolynomial{T, Symbol(var)}) else cs = NTuple{N,T}(coeffs[i] for i in 1:N) - return ImmutablePolynomial{T,N}(cs, var) + return ImmutablePolynomial{T,Symbol(var),N}(cs) end end function ImmutablePolynomial{T}(coeffs::Tuple, var::SymbolLike=:x) where {T} - ImmutablePolynomial{T}(T.(coeffs), var) -end - -# entry point from abstract.jl; note T <: Number -function ImmutablePolynomial{T}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where {T <: Number} - if Base.has_offset_axes(coeffs) - @warn "ignoring the axes of the coefficient vector and treating it as a list" - end - M = length(coeffs) - ImmutablePolynomial{T}(NTuple{M,T}(tuple(coeffs...)), var) + ImmutablePolynomial{T}(T.(coeffs), Symbol(var)) end - ## -- function ImmutablePolynomial(coeffs::Tuple, var::SymbolLike=:x) cs = NTuple(promote(coeffs...)) T = eltype(cs) - ImmutablePolynomial{T}(cs, var) + ImmutablePolynomial{T, Symbol(var)}(cs) end -# Convenience; pass tuple to Polynomial -# Not documented, not sure this is a good idea as P(...)::P is not true... -# Deprecated -function Polynomial(coeffs::NTuple{N,T}, var::SymbolLike = :x) where{N,T} - Base.depwarn("Use of `Polynomial(NTuple, var)` is deprecated. Use the `ImmutablePolynomial` constructor", - :Polynomial) - ImmutablePolynomial(coeffs, var) -end -function Polynomial{T}(coeffs::NTuple{N,S}, var::SymbolLike = :x) where{N,T,S} - Base.depwarn("Use of `Polynomial(NTuple, var)` is deprecated. Use the `ImmutablePolynomial` constructor", - :Polynomial) - ImmutablePolynomial{N,T}(T.(coeffs), var) -end ## ## ---- @@ -124,29 +104,29 @@ end # overrides from common.jl due to coeffs being non mutable, N in type parameters Base.collect(p::P) where {P <: ImmutablePolynomial} = [pᵢ for pᵢ ∈ p] -Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p), p.var) +Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p), var(p)) ## defining these speeds things up function Base.zero(P::Type{<:ImmutablePolynomial}, var::SymbolLike=:x) R = eltype(P) - ImmutablePolynomial{R,0}(NTuple{0,R}(),var) + ImmutablePolynomial{R,Symbol(var),0}(NTuple{0,R}()) end function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=:x) R = eltype(P) - ImmutablePolynomial{R,1}(NTuple{1,R}(1),var) + ImmutablePolynomial{R,Symbol(var),1}(NTuple{1,R}(1)) end function variable(P::Type{<:ImmutablePolynomial}, var::SymbolLike=:x) R = eltype(P) - ImmutablePolynomial{R,2}(NTuple{2,R}((0,1)),var) + ImmutablePolynomial{R,Symbol(var),2}(NTuple{2,R}((0,1))) end # degree, isconstant -degree(p::ImmutablePolynomial{T,N}) where {T,N} = N - 1 # no trailing zeros -isconstant(p::ImmutablePolynomial{T,N}) where {T,N} = N <= 1 +degree(p::ImmutablePolynomial{T,X, N}) where {T,X,N} = N - 1 # no trailing zeros +isconstant(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = N <= 1 -function Base.getindex(p::ImmutablePolynomial{T,N}, idx::Int) where {T <: Number,N} +function Base.getindex(p::ImmutablePolynomial{T,X, N}, idx::Int) where {T <: Number,X, N} (idx < 0 || idx > N-1) && return zero(T) return p.coeffs[idx + 1] end @@ -174,10 +154,10 @@ function Base.chop(p::ImmutablePolynomial{T,N}; cs = coeffs(p) for i in N:-1:1 if !isapprox(cs[i], zero(T), rtol=rtol, atol=atol) - return ImmutablePolynomial{T,i}(cs[1:i], p.var) + return ImmutablePolynomial{T,i}(cs[1:i], var(p)) end end - zero(ImmutablePolynomial{T}, p.var) + zero(ImmutablePolynomial{T}, var(p)) end function Base.truncate(p::ImmutablePolynomial{T,N}; @@ -188,7 +168,7 @@ function Base.truncate(p::ImmutablePolynomial{T,N}; cs = coeffs(q) thresh = maximum(abs,cs) * rtol + atol cs′ = map(c->abs(c) <= thresh ? zero(T) : c, cs) - ImmutablePolynomial{T}(tuple(cs′...), p.var) + ImmutablePolynomial{T}(tuple(cs′...), var(p)) end # no in-place chop! and truncate! @@ -202,42 +182,42 @@ truncate!(p::ImmutablePolynomial; kwargs...) = truncate(p; kwargs...) (p::ImmutablePolynomial{T,N})(x::S) where {T,N,S} = evalpoly(x, p.coeffs) -function Base.:+(p1::ImmutablePolynomial{T,N}, p2::ImmutablePolynomial{S,M}) where {T,N,S,M} +function Base.:+(p1::ImmutablePolynomial{T,X, N}, p2::ImmutablePolynomial{S,Y, M}) where {T,X, N,S,Y,M} R = promote_type(S,T) - iszero(N) && return ImmutablePolynomial{R}(coeffs(p2), p2.var) - iszero(M) && return ImmutablePolynomial{R}(coeffs(p1), p1.var) + iszero(N) && return ImmutablePolynomial{R, var(p2)}(coeffs(p2)) + iszero(M) && return ImmutablePolynomial{R, var(p1)}(coeffs(p1)) - isconstant(p1) && p1.var != p2.var && return p2 + p1[0]*one(ImmutablePolynomial{R}, p2.var) - isconstant(p2) && p1.var != p2.var && return p1 + p2[0]*one(ImmutablePolynomial{R}, p1.var) + isconstant(p1) && X != Y && return p2 + p1[0]*one(ImmutablePolynomial{R, Y}) + isconstant(p2) && X != Y && return p1 + p2[0]*one(ImmutablePolynomial{R, X}) - p1.var != p2.var && error("Polynomials must have same variable") + X != Y && error("Polynomials must have same variable") if N == M cs = NTuple{N,R}(p1[i] + p2[i] for i in 0:N-1) - ImmutablePolynomial{R}(cs, p1.var) + ImmutablePolynomial{R,X,N}(cs) elseif N < M cs = (p2.coeffs) ⊕ (p1.coeffs) - ImmutablePolynomial{R,M}(cs, p1.var) + ImmutablePolynomial{R,X,M}(cs) else cs = (p1.coeffs) ⊕ (p2.coeffs) - ImmutablePolynomial{R,N}(cs, p1.var) + ImmutablePolynomial{R,X,N}(cs) end end # not type stable!!! -function Base.:*(p1::ImmutablePolynomial{T,N}, p2::ImmutablePolynomial{S,M}) where {T,N,S,M} +function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) where {T,X,N,S,Y,M} isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - p1.var != p2.var && error("Polynomials must have same variable") + X != Y && error("Polynomials must have same variable") R = promote_type(S,T) cs = (p1.coeffs) ⊗ (p2.coeffs) if !iszero(cs[end]) - return ImmutablePolynomial{R, N+M-1}(cs, p1.var) + return ImmutablePolynomial{R, X, N+M-1}(cs) else n = findlast(!iszero, cs) - return ImmutablePolynomial{R, n}(cs[1:n], p1.var) + return ImmutablePolynomial{R, X, n}(cs[1:n]) end end @@ -291,38 +271,38 @@ end end # scalar ops -function Base.:+(p::ImmutablePolynomial{T,N}, c::S) where {T, N, S<:Number} +function Base.:+(p::ImmutablePolynomial{T,X, N}, c::S) where {T, X, N, S<:Number} R = promote_type(T,S) - iszero(c) && return ImmutablePolynomial{R,N}(p.coeffs, p.var) - N == 0 && return ImmutablePolynomial{R,1}((c,), p.var) - N == 1 && return ImmutablePolynomial((p[0]+c,), p.var) + iszero(c) && return ImmutablePolynomial{R,X, N}(p.coeffs) + N == 0 && return ImmutablePolynomial{R,X,1}((c,)) + N == 1 && return ImmutablePolynomial((p[0]+c,), X) - q = ImmutablePolynomial{R,1}((c,), p.var) + q = ImmutablePolynomial{R,X, 1}((c,)) return p + q end -function Base.:*(p::ImmutablePolynomial{T,N}, c::S) where {T, N, S <: Number} +function Base.:*(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X,N, S <: Number} R = promote_type(T,S) - iszero(c) && return zero(ImmutablePolynomial{R}, p.var) - ImmutablePolynomial{R,N}(p.coeffs .* c, p.var) + iszero(c) && return zero(ImmutablePolynomial{R,X}) + ImmutablePolynomial{R,X,N}(p.coeffs .* c) end -function Base.:/(p::ImmutablePolynomial{T,N}, c::S) where {T,N,S <: Number} +function Base.:/(p::ImmutablePolynomial{T,X,N}, c::S) where {T,X,N,S <: Number} R = eltype(one(T)/one(S)) - isinf(c) && return zero(ImmutablePolynomial{R}, p.var) - ImmutablePolynomial{R,N}(p.coeffs ./ c, p.var) + isinf(c) && return zero(ImmutablePolynomial{R,X}) + ImmutablePolynomial{R,X,N}(p.coeffs ./ c) end -Base.:-(p::ImmutablePolynomial{T,N}) where {T,N} = ImmutablePolynomial{T,N}(.-p.coeffs, p.var) +Base.:-(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = ImmutablePolynomial{T,X,N}(.-p.coeffs) -Base.to_power_type(p::ImmutablePolynomial{T,N}) where {T,N} = p +Base.to_power_type(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = p ## more performant versions borrowed from StaticArrays ## https://github.com/JuliaArrays/StaticArrays.jl/blob/master/src/linalg.jl -LinearAlgebra.norm(q::ImmutablePolynomial{T,0}) where {T} = zero(real(float(T))) +LinearAlgebra.norm(q::ImmutablePolynomial{T,X,0}) where {T,X} = zero(real(float(T))) LinearAlgebra.norm(q::ImmutablePolynomial) = _norm(q.coeffs) LinearAlgebra.norm(q::ImmutablePolynomial, p::Real) = _norm(q.coeffs, p) diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index a5c72f66..c97fc3fc 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -1,7 +1,7 @@ export LaurentPolynomial """ - LaurentPolynomial(coeffs::AbstractVector, [m::Integer = 0], [var = :x]) + LaurentPolynomial{T,X}(coeffs::AbstractVector, [m::Integer = 0], [var = :x]) A [Laurent](https://en.wikipedia.org/wiki/Laurent_polynomial) polynomial is of the form `a_{m}x^m + ... + a_{n}x^n` where `m,n` are integers (not necessarily positive) with ` m <= n`. @@ -72,31 +72,32 @@ julia> x^degree(p) * p(x⁻¹) # reverses coefficients LaurentPolynomial(3.0 + 2.0*x + 1.0*x²) ``` """ -struct LaurentPolynomial{T <: Number} <: StandardBasisPolynomial{T} +struct LaurentPolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Vector{T} - var::Symbol m::Base.RefValue{Int} n::Base.RefValue{Int} - function LaurentPolynomial{T}(coeffs::AbstractVector{T}, - m::Int, - var::Symbol=:x) where {T <: Number} + function LaurentPolynomial{T,X}(coeffs::AbstractVector{T}, + m::Union{Int, Nothing}=nothing) where {T <: Number, X} - if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" - end - c = OffsetArrays.no_offset_view(coeffs) # ensure 1-based indexing - # trim zeros from front and back - lnz = findlast(!iszero, c) - fnz = findfirst(!iszero, c) - (lnz == nothing || length(c) == 0) && return new{T}(zeros(T,1), var, Ref(0), Ref(0)) - c = c[fnz:lnz] + fnz = findfirst(!iszero, coeffs) + fnz == nothing && return new{T,X}(zeros(T,1), Ref(0), Ref(0)) + lnz = findlast(!iszero, coeffs) - m = m + fnz - 1 - n = m + (lnz-fnz) + if Base.has_offset_axes(coeffs) + # if present, use axes + cs = coeffs[fnz:lnz] + return new{T,X}(cs, Ref(fnz), Ref(lnz)) + else + + c = coeffs[fnz:lnz] + + m′ = fnz - 1 + (m == nothing ? 0 : m) + n = m′ + (lnz-fnz) - (n-m+1 == length(c)) || throw(ArgumentError("Lengths do not match")) + (n- m′ + 1 == length(c)) || throw(ArgumentError("Lengths do not match")) - new{T}(c, var, Ref(m), Ref(n)) + new{T,X}(c, Ref(m′), Ref(n)) + end end end @@ -105,34 +106,18 @@ end ## constructors function LaurentPolynomial{T}(coeffs::AbstractVector{S}, m::Int, var::SymbolLike=:x) where { T <: Number, S <: Number} - LaurentPolynomial{T}(T.(coeffs), m, var) + LaurentPolynomial{T,Symbol(var)}(T.(coeffs), m) end function LaurentPolynomial{T}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where { T <: Number} - LaurentPolynomial{T}(coeffs, 0, var) + LaurentPolynomial{T, Symbol(var)}(coeffs, 0) end function LaurentPolynomial(coeffs::AbstractVector{T}, m::Int, var::SymbolLike=:x) where {T <: Number} - LaurentPolynomial{T}(coeffs, m, Symbol(var)) -end - -## Alternate with range specified -## Deprecate -function LaurentPolynomial{T}(coeffs::AbstractVector{S}, - rng::UnitRange{Int}, - var::Symbol=:x) where {T <: Number, S <: Number} - Base.depwarn("Using a range to indicate the offset is deprecated. Use just the lower value", - :LaurentPolynomial) - error("") - LaurentPolynomial{T}(T.(coeffs), first(rng), var) + LaurentPolynomial{T, Symbol(var)}(coeffs, m) end -function LaurentPolynomial(coeffs::AbstractVector{T}, rng::UnitRange, var::SymbolLike=:x) where {T <: Number} - Base.depwarn("Using a range to indicate the offset is deprecated. Use just the lower value", - :LaurentPolynomial) - LaurentPolynomial{T}(coeffs, rng, Symbol(var)) -end @@ -150,14 +135,14 @@ Base.promote_rule(::Type{Q},::Type{P}) where {T, P <: LaurentPolynomial{T}, S, Q function Base.convert(P::Type{<:Polynomial}, q::LaurentPolynomial) m,n = (extrema∘degreerange)(q) m < 0 && throw(ArgumentError("Can't convert a Laurent polynomial with m < 0")) - P([q[i] for i in 0:n], q.var) + P([q[i] for i in 0:n], var(q)) end -Base.convert(::Type{T}, p::LaurentPolynomial) where {T<:LaurentPolynomial} = T(p.coeffs, p.m[], p.var) +Base.convert(::Type{T}, p::LaurentPolynomial) where {T<:LaurentPolynomial} = T(p.coeffs, p.m[], var(p)) function Base.convert(::Type{P}, q::StandardBasisPolynomial{S}) where {T, P <:LaurentPolynomial{T},S} d = degree(q) - P([q[i] for i in 0:d], 0, q.var) + P([q[i] for i in 0:d], 0, var(q)) end ## @@ -172,10 +157,11 @@ function Base.range(p::LaurentPolynomial) p.m[]:p.n[] end -function Base.inv(p::LaurentPolynomial) +function Base.inv(p::LaurentPolynomial{T, X}) where {T, X} m,n = (extrema∘degreerange)(p) m != n && throw(ArgumentError("Only monomials can be inverted")) - LaurentPolynomial([1/p for p in p.coeffs], -m, p.var) + cs = [1/p for p in p.coeffs] + LaurentPolynomial{eltype(cs), X}(cs, -m) end ## @@ -183,14 +169,14 @@ end ## Base.:(==)(p1::LaurentPolynomial, p2::LaurentPolynomial) = check_same_variable(p1, p2) && (degreerange(p1) == degreerange(p2)) && (coeffs(p1) == coeffs(p2)) -Base.hash(p::LaurentPolynomial, h::UInt) = hash(p.var, hash(degreerange(p), hash(coeffs(p), h))) +Base.hash(p::LaurentPolynomial, h::UInt) = hash(var(p), hash(degreerange(p), hash(coeffs(p), h))) isconstant(p::LaurentPolynomial) = iszero(lastindex(p)) && iszero(firstindex(p)) -basis(P::Type{<:LaurentPolynomial{T}}, n::Int, var::SymbolLike=:x) where{T} = LaurentPolynomial(ones(T,1), n, var) -basis(P::Type{LaurentPolynomial}, n::Int, var::SymbolLike=:x) = LaurentPolynomial(ones(Float64, 1), n, var) +basis(P::Type{<:LaurentPolynomial{T}}, n::Int, var::SymbolLike=:x) where{T} = LaurentPolynomial{T,Symbol(var)}(ones(T,1), n) +basis(P::Type{LaurentPolynomial}, n::Int, var::SymbolLike=:x) = LaurentPolynomial{Float64, Symbol(var)}(ones(Float64, 1), n) -Base.zero(::Type{LaurentPolynomial{T}}, var=Symbollike=:x) where {T} = LaurentPolynomial{T}(zeros(T,1), 0, Symbol(var)) -Base.zero(::Type{LaurentPolynomial}, var=Symbollike=:x) = zero(LaurentPolynomial{Float64}, var) +Base.zero(::Type{LaurentPolynomial{T}}, var=Symbollike=:x) where {T} = LaurentPolynomial{T,Symbol(var)}(zeros(T,1), 0) +Base.zero(::Type{LaurentPolynomial}, var=Symbollike=:x) = zero(LaurentPolynomial{Float64, Symbol(var)}) Base.zero(p::P, var=Symbollike=:x) where {P <: LaurentPolynomial} = zero(P, var) @@ -228,7 +214,7 @@ Base.lastindex(p::LaurentPolynomial) = p.n[] Base.eachindex(p::LaurentPolynomial) = degreerange(p) degreerange(p::LaurentPolynomial) = firstindex(p):lastindex(p) -_convert(p::P, as) where {P <: LaurentPolynomial} = ⟒(P)(as, firstindex(p), p.var) +_convert(p::P, as) where {T,X,P <: LaurentPolynomial{T,X}} = ⟒(P)(as, firstindex(p), X) ## chop/truncation # trim from *both* ends @@ -363,7 +349,7 @@ function paraconj(p::LaurentPolynomial) cs = p.coeffs ds = adjoint.(cs) n = degree(p) - LaurentPolynomial(reverse(ds), -n, p.var) + LaurentPolynomial(reverse(ds), -n, var(p)) end """ @@ -412,7 +398,7 @@ function cconj(p::LaurentPolynomial) ps[i+1-m] *= -1 end end - LaurentPolynomial(ps, m, p.var) + LaurentPolynomial(ps, m, var(p)) end @@ -448,15 +434,19 @@ Base.:+(p::LaurentPolynomial{T}, c::S) where {T, S <: Number} = sum(promote(p,c) ## ## Poly + and * ## -function Base.:+(p1::P1, p2::P2) where {T,P1<:LaurentPolynomial{T}, S, P2<:LaurentPolynomial{S}} +function Base.:+(p1::P1, p2::P2) where {T,X,P1<:LaurentPolynomial{T,X}, S,Y, P2<:LaurentPolynomial{S,Y}} if isconstant(p1) - p1 = P1(p1.coeffs, firstindex(p1), p2.var) + i₁ = firstindex(p1) + p2[i₁] += p1[i₁] + return p2 elseif isconstant(p2) - p2 = P2(p2.coeffs, firstindex(p2), p1.var) + i₂ = firstindex(p2) + p1[i₂] += p2[i₂] + return p1 end - p1.var != p2.var && error("LaurentPolynomials must have same variable") + X != Y && error("LaurentPolynomials must have same variable") R = promote_type(T,S) @@ -469,19 +459,19 @@ function Base.:+(p1::P1, p2::P2) where {T,P1<:LaurentPolynomial{T}, S, P2<:Laure as[1 + i-m] = p1[i] + p2[i] end - q = LaurentPolynomial{R}(as, m, p1.var) + q = LaurentPolynomial{R,X}(as, m) chop!(q) return q end -function Base.:*(p1::LaurentPolynomial{T}, p2::LaurentPolynomial{S}) where {T,S} +function Base.:*(p1::LaurentPolynomial{T,X}, p2::LaurentPolynomial{S,Y}) where {T,X,S,Y} isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - p1.var != p2.var && error("LaurentPolynomials must have same variable") + X != Y && error("LaurentPolynomials must have same variable") R = promote_type(T,S) @@ -497,7 +487,7 @@ function Base.:*(p1::LaurentPolynomial{T}, p2::LaurentPolynomial{S}) where {T,S} end end - p = LaurentPolynomial(as, m, p1.var) + p = LaurentPolynomial{R,X}(as, m) chop!(p) return p @@ -529,7 +519,7 @@ julia> roots(a) 2.9999999999999982 ``` """ -function roots(p::P; kwargs...) where {T, P <: LaurentPolynomial{T}} +function roots(p::P; kwargs...) where {T, X, P <: LaurentPolynomial{T, X}} c = coeffs(p) r = degreerange(p) d = r[end] - min(0, r[1]) + 1 # Length of the coefficient vector, taking into consideration @@ -537,19 +527,19 @@ function roots(p::P; kwargs...) where {T, P <: LaurentPolynomial{T}} # (like p=3z^2). z = zeros(T, d) # Reserves space for the coefficient vector. z[max(0, r[1]) + 1:end] = c # Leaves the coeffs of the lower powers as zeros. - a = Polynomial(z, p.var) # The root is then the root of the numerator polynomial. + a = Polynomial{T,X}(z) # The root is then the root of the numerator polynomial. return roots(a; kwargs...) end ## ## d/dx, ∫ ## -function derivative(p::P, order::Integer = 1) where {T, P<:LaurentPolynomial{T}} +function derivative(p::P, order::Integer = 1) where {T, X, P<:LaurentPolynomial{T,X}} order < 0 && error("Order of derivative must be non-negative") order == 0 && return p - hasnan(p) && return ⟒(P)(T[NaN], 0, p.var) + hasnan(p) && return ⟒(P)(T[NaN], 0, X) m,n = (extrema ∘ degreerange)(p) m = m - order @@ -565,18 +555,18 @@ function derivative(p::P, order::Integer = 1) where {T, P<:LaurentPolynomial{T}} end end - chop!(LaurentPolynomial(as, m, p.var)) + chop!(LaurentPolynomial{T,X}(as, m)) end -function integrate(p::P, k::S) where {T, P<: LaurentPolynomial{T}, S<:Number} +function integrate(p::P, k::S) where {T, X, P<: LaurentPolynomial{T, X}, S<:Number} !iszero(p[-1]) && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term")) R = eltype((one(T)+one(S))/1) if hasnan(p) || isnan(k) - return P([NaN], 0, p.var) # not R(NaN)!! don't like XXX + return P([NaN], 0, X) # not R(NaN)!! don't like XXX end @@ -599,12 +589,12 @@ function integrate(p::P, k::S) where {T, P<: LaurentPolynomial{T}, S<:Number} as[1-m] = k - return ⟒(P)(as, m, p.var) + return ⟒(P){R,X}(as, m) end -function Base.gcd(p::LaurentPolynomial{T}, q::LaurentPolynomial{T}, args...; kwargs...) where {T} +function Base.gcd(p::LaurentPolynomial{T,X}, q::LaurentPolynomial{T,Y}, args...; kwargs...) where {T,X,Y} mp, Mp = (extrema ∘ degreerange)(p) mq, Mq = (extrema ∘ degreerange)(q) if mp < 0 || mq < 0 @@ -617,5 +607,5 @@ function Base.gcd(p::LaurentPolynomial{T}, q::LaurentPolynomial{T}, args...; kwa pp, qq = convert(Polynomial, p), convert(Polynomial, q) u = gcd(pp, qq, args..., kwargs...) - return LaurentPolynomial(coeffs(u), p.var) + return LaurentPolynomial(coeffs(u), X) end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 45705058..6b089fd5 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -1,7 +1,7 @@ export SparsePolynomial """ - SparsePolynomial(coeffs::Dict, [var = :x]) + SparsePolynomial{T, X}(coeffs::Dict, [var = :x]) Polynomials in the standard basis backed by a dictionary holding the non-zero coefficients. For polynomials of high degree, this might be @@ -39,21 +39,20 @@ julia> p(1) ``` """ -struct SparsePolynomial{T <: Number} <: StandardBasisPolynomial{T} +struct SparsePolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Dict{Int, T} - var::Symbol - function SparsePolynomial{T}(coeffs::AbstractDict{Int, T}, var::SymbolLike) where {T <: Number} + function SparsePolynomial{T, X}(coeffs::AbstractDict{Int, T}) where {T <: Number, X} c = Dict(coeffs) for (k,v) in coeffs iszero(v) && pop!(c, k) end - new{T}(c, Symbol(var)) + new{T, X}(c) end end @register SparsePolynomial -function SparsePolynomial{T}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where {T <: Number} +function SparsePolynomial{T,X}(coeffs::AbstractVector{T}) where {T <: Number, X} firstindex(coeffs) >= 0 || throw(ArgumentError("Use the `LaurentPolynomial` type for arrays with a negative first index")) if Base.has_offset_axes(coeffs) @@ -61,36 +60,23 @@ function SparsePolynomial{T}(coeffs::AbstractVector{T}, var::SymbolLike=:x) wher end c = OffsetArrays.no_offset_view(coeffs) # ensure 1-based indexing p = Dict{Int,T}(i - 1 => v for (i,v) in pairs(c)) - return SparsePolynomial{T}(p, var) + return SparsePolynomial{T,X}(p) end function SparsePolynomial(coeffs::AbstractDict{Int, T}, var::SymbolLike=:x) where {T <: Number} - SparsePolynomial{T}(coeffs, var) + SparsePolynomial{T, Symbol(var)}(coeffs) end -# Interface through `Polynomial`. As with ImmutablePolynomial, this may not be good idea... -# Deprecated -function Polynomial{T}(coeffs::Dict{Int,T}, var::SymbolLike = :x) where {T} - Base.depwarn("Use of `Polynomial(Dict, var)` is deprecated. Use the `SparsePolynomial` constructor", - :Polynomial) - SparsePolynomial{T}(coeffs, var) -end - -function Polynomial(coeffs::Dict{Int,T}, var::SymbolLike = :x) where {T} - Base.depwarn("Use of `Polynomial(Dict, var)` is deprecated. Use the `SparsePolynomial` constructor", - :Polynomial) - SparsePolynomial{T}(coeffs, var) -end # conversion function Base.convert(P::Type{<:Polynomial}, q::SparsePolynomial) - ⟒(P)(coeffs(q), q.var) + ⟒(P)(coeffs(q), var(q)) end function Base.convert(P::Type{<:SparsePolynomial}, q::StandardBasisPolynomial{T}) where {T} R = promote(eltype(P), T) - ⟒(P){R}(coeffs(q), q.var) + ⟒(P){R}(coeffs(q), var(q)) end ## changes to common @@ -101,8 +87,11 @@ function isconstant(p::SparsePolynomial) return true end -basis(P::Type{<:SparsePolynomial}, n::Int, var::SymbolLike=:x) = - SparsePolynomial(Dict(n=>one(eltype(one(P)))), var) +function basis(P::Type{<:SparsePolynomial}, n::Int, var::SymbolLike=:x) + T = eltype(P) + X = Symbol(var) + SparsePolynomial{T,X}(Dict(n=>one(T))) +end # return coeffs as a vector # use p.coeffs to get Dictionary @@ -196,17 +185,15 @@ end -function Base.:+(p1::SparsePolynomial{T}, p2::SparsePolynomial{S}) where {T, S} +function Base.:+(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T, X, S, Y} isconstant(p1) && return p2 + p1[0] isconstant(p2) && return p1 + p2[0] - p1.var != p2.var && error("SparsePolynomials must have same variable") + X != Y && error("SparsePolynomials must have same variable") R = promote_type(T,S) - P = SparsePolynomial - - p = zero(P{R}, p1.var) + p = zero(SparsePolynomial{R,X}) # this allocates in the union # for i in union(eachindex(p1), eachindex(p2)) @@ -228,12 +215,12 @@ function Base.:+(p1::SparsePolynomial{T}, p2::SparsePolynomial{S}) where {T, S} end -function Base.:+(p::SparsePolynomial{T}, c::S) where {T, S <: Number} +function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} R = promote_type(T,S) P = SparsePolynomial - q = zero(P{R}, p.var) + q = zero(P{R,X}) for k in eachindex(p) @inbounds q[k] = R(p[k]) end @@ -242,16 +229,16 @@ function Base.:+(p::SparsePolynomial{T}, c::S) where {T, S <: Number} return q end -function Base.:*(p1::SparsePolynomial{T}, p2::SparsePolynomial{S}) where {T,S} +function Base.:*(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T,X,S,Y} isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - p1.var != p2.var && error("SparsePolynomials must have same variable") + X != Y && error("SparsePolynomials must have same variable") R = promote_type(T,S) P = SparsePolynomial - p = zero(P{R}, p1.var) + p = zero(P{R, X}) for i in eachindex(p1) p1ᵢ = p1[i] for j in eachindex(p2) @@ -264,10 +251,10 @@ function Base.:*(p1::SparsePolynomial{T}, p2::SparsePolynomial{S}) where {T,S} end -function Base.:*(p::P, c::S) where {T, P <: SparsePolynomial{T}, S <: Number} +function Base.:*(p::P, c::S) where {T, X, P <: SparsePolynomial{T,X}, S <: Number} R = promote_type(T,S) - q = zero(⟒(P){R}, p.var) + q = zero(⟒(P){R,X}) for k in eachindex(p) q[k] = p[k] * c end @@ -277,19 +264,19 @@ end -function derivative(p::SparsePolynomial{T}, order::Integer = 1) where {T} +function derivative(p::SparsePolynomial{T,X}, order::Integer = 1) where {T,X} order < 0 && error("Order of derivative must be non-negative") order == 0 && return p R = eltype(one(T)*1) P = SparsePolynomial - hasnan(p) && return P(Dict(0 => R(NaN)), p.var) + hasnan(p) && return P{R,X}(Dict(0 => R(NaN))) n = degree(p) - order > n && return zero(P{R}, p.var) + order > n && return zero(P{R,X}) - dpn = zero(P{R}, p.var) + dpn = zero(P{R,X}) @inbounds for k in eachindex(p) dpn[k-order] = reduce(*, (k - order + 1):k, init = p[k]) end @@ -299,16 +286,16 @@ function derivative(p::SparsePolynomial{T}, order::Integer = 1) where {T} end -function integrate(p::SparsePolynomial{T}, k::S) where {T, S<:Number} +function integrate(p::SparsePolynomial{T,X}, k::S) where {T, X, S<:Number} R = eltype((one(T)+one(S))/1) P = SparsePolynomial if hasnan(p) || isnan(k) - return P(Dict(0 => NaN), p.var) # not R(NaN)!! don't like XXX + return P{R,X}(Dict(0 => R(NaN))) # not R(NaN)!! don't like XXX end - ∫p = P{R}(R(k), p.var) + ∫p = P{R,X}(R(k)) for k in eachindex(p) ∫p[k + 1] = p[k] / (k+1) end From 154052f87f675169aab3162eba790655822cfc19 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sat, 6 Feb 2021 21:13:27 -0500 Subject: [PATCH 03/38] WIP --- src/Polynomials.jl | 2 +- src/polynomials/ChebyshevT.jl | 53 ++++++++++++++------------ src/polynomials/ImmutablePolynomial.jl | 25 ++++++++---- src/polynomials/Poly.jl | 2 +- src/polynomials/Polynomial.jl | 7 ++-- src/polynomials/SparsePolynomial.jl | 12 +++--- test/StandardBasis.jl | 31 +++++++++------ test/runtests.jl | 2 +- 8 files changed, 75 insertions(+), 59 deletions(-) diff --git a/src/Polynomials.jl b/src/Polynomials.jl index a7b9b2d2..8b098d97 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -23,7 +23,7 @@ include("polynomials/LaurentPolynomial.jl") include("polynomials/ngcd.jl") include("polynomials/multroot.jl") -#include("polynomials/ChebyshevT.jl") +include("polynomials/ChebyshevT.jl") # compat; opt-in with `using Polynomials.PolyCompat` #include("polynomials/Poly.jl") diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 4e2de6cf..f694068a 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -1,7 +1,7 @@ export ChebyshevT """ - ChebyshevT{<:Number}(coeffs::AbstractVector, [var = :x]) + ChebyshevT{T<:Number, X}(coeffs::AbstractVector) Chebyshev polynomial of the first kind. @@ -27,18 +27,17 @@ julia> one(ChebyshevT) ChebyshevT(1.0⋅T_0(x)) ``` """ -struct ChebyshevT{T <: Number} <: AbstractPolynomial{T} +struct ChebyshevT{T <: Number, X} <: AbstractPolynomial{T, X} coeffs::Vector{T} - var::Symbol - function ChebyshevT{T}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where {T <: Number} - length(coeffs) == 0 && return new{T}(zeros(T, 1), var) + function ChebyshevT{T, X}(coeffs::AbstractVector{T}) where {T <: Number,X} + length(coeffs) == 0 && return new{T,X}(zeros(T, 1)) if Base.has_offset_axes(coeffs) @warn "ignoring the axis offset of the coefficient vector" end c = OffsetArrays.no_offset_view(coeffs) last_nz = findlast(!iszero, c) last = max(1, last_nz === nothing ? 0 : last_nz) - return new{T}(c[1:last], var) + return new{T,X}(c[1:last]) end end @@ -46,13 +45,13 @@ end function Base.convert(P::Type{<:Polynomial}, ch::ChebyshevT) if length(ch) < 3 - return P(ch.coeffs, ch.var) + return P(ch.coeffs, var(ch)) end - c0 = P(ch[end - 1], ch.var) - c1 = P(ch[end], ch.var) + c0 = P(ch[end - 1], var(ch)) + c1 = P(ch[end], var(ch)) @inbounds for i in degree(ch):-1:2 tmp = c0 - c0 = P(ch[i - 2], ch.var) - c1 + c0 = P(ch[i - 2], var(ch)) - c1 c1 = tmp + c1 * variable(P) * 2 end return c0 + c1 * variable(P) @@ -136,7 +135,7 @@ function integrate(p::ChebyshevT{T}, C::S) where {T,S <: Number} a2[i] -= p[i] / (2 * (i - 1)) end a2[1] += R(C) - ChebyshevT(a2)(0) - return ChebyshevT(a2, p.var) + return ChebyshevT(a2, var(p)) end @@ -144,7 +143,7 @@ function derivative(p::ChebyshevT{T}, order::Integer = 1) where {T} order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) R = eltype(one(T)/1) order == 0 && return convert(ChebyshevT{R}, p) - hasnan(p) && return ChebyshevT(R[NaN], p.var) + hasnan(p) && return ChebyshevT(R[NaN], var(p)) order > length(p) && return zero(ChebyshevT{R}) @@ -161,7 +160,7 @@ function derivative(p::ChebyshevT{T}, order::Integer = 1) where {T} end der[1] = q[1] - pp = ChebyshevT(der, p.var) + pp = ChebyshevT(der, var(p)) return order > 1 ? derivative(pp, order - 1) : pp end @@ -182,30 +181,34 @@ function companion(p::ChebyshevT{T}) where T return R.(comp) end -function Base.:+(p1::ChebyshevT, p2::ChebyshevT) - p1.var != p2.var && error("Polynomials must have same variable") +function Base.:+(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} + X′ = isconstant(p2) ? X : Y + (!isconstant(p1) && !isconstant(p2)) && X != Y && error("Polynomials must have same variable") n = max(length(p1), length(p2)) - c = [p1[i] + p2[i] for i = 0:n] - return ChebyshevT(c, p1.var) + R = promote_type(T,S) + c = R[p1[i] + p2[i] for i = 0:n] + return ChebyshevT{R,X′}(c) end -function Base.:*(p1::ChebyshevT{T}, p2::ChebyshevT{S}) where {T,S} - p1.var != p2.var && error("Polynomials must have same variable") +function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} + X′ = isconstant(p2) ? X : Y + (!isconstant(p1) && !isconstant(p2)) && X != Y && error("Polynomials must have same variable") z1 = _c_to_z(p1.coeffs) z2 = _c_to_z(p2.coeffs) prod = fastconv(z1, z2) - ret = ChebyshevT(_z_to_c(prod), p1.var) + cs = _z_to_c(prod) + ret = ChebyshevT{eltype(cs),X′}(cs) return truncate!(ret) end -function Base.divrem(num::ChebyshevT{T}, den::ChebyshevT{S}) where {T,S} - num.var != den.var && error("Polynomials must have same variable") +function Base.divrem(num::ChebyshevT{T,X}, den::ChebyshevT{S,Y}) where {T,X,S,Y} + X != Y && error("Polynomials must have same variable") n = length(num) - 1 m = length(den) - 1 R = typeof(one(T) / one(S)) - P = ChebyshevT{R} + P = ChebyshevT{R,X} if n < m return zero(P), convert(P, num) @@ -219,10 +222,10 @@ function Base.divrem(num::ChebyshevT{T}, den::ChebyshevT{S}) where {T,S} quo, rem = _z_division(znum, zden) q_coeff = _z_to_c(quo) r_coeff = _z_to_c(rem) - return P(q_coeff, num.var), P(r_coeff, num.var) + return P(q_coeff), P(r_coeff) end -function showterm(io::IO, ::Type{ChebyshevT{T}}, pj::T, var, j, first::Bool, mimetype) where {N, T} +function showterm(io::IO, ::Type{ChebyshevT{T,X}}, pj::T, var, j, first::Bool, mimetype) where {N, T,X} iszero(pj) && return false !first && print(io, " ") print(io, hasneg(T) && isneg(pj) ? "- " : (!first ? "+ " : "")) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index b24541c8..d60d29a5 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -57,9 +57,18 @@ end @register ImmutablePolynomial ## Various interfaces -function ImmutablePolynomial{T,X}(coeffs::AbstractVector) where {T,X} +function ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}) where {T,X,S} + R = promote_type(T,S) + + if Base.has_offset_axes(coeffs) + throw(ArgumentError("The `ImmutablePolynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) + + @warn "ignoring the axis offset of the coefficient vector" + end + #c = OffsetArrays.no_offset_view(coeffs) N = findlast(!iszero, coeffs) - ImmutablePolynomial{T, X, N}(NTuple{N,T}(coeffs[i] for i in 1:N)) + N == nothing && return zero(ImmutablePolynomial{R,X}) + ImmutablePolynomial{T, X, N}(NTuple{N,T}(cᵢ for cᵢ ∈ coeffs)) end @@ -107,18 +116,18 @@ Base.collect(p::P) where {P <: ImmutablePolynomial} = [pᵢ for pᵢ ∈ p] Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p), var(p)) ## defining these speeds things up -function Base.zero(P::Type{<:ImmutablePolynomial}, var::SymbolLike=:x) +function Base.zero(P::Type{<:ImmutablePolynomial}, var::SymbolLike=var(P)) R = eltype(P) ImmutablePolynomial{R,Symbol(var),0}(NTuple{0,R}()) end -function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=:x) +function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=var(P)) R = eltype(P) - ImmutablePolynomial{R,Symbol(var),1}(NTuple{1,R}(1)) + ImmutablePolynomial{R,Symbol(var),1}(NTuple{1,R}(one(R))) end -function variable(P::Type{<:ImmutablePolynomial}, var::SymbolLike=:x) +function variable(P::Type{<:ImmutablePolynomial}, var::SymbolLike=var(p)) R = eltype(P) - ImmutablePolynomial{R,Symbol(var),2}(NTuple{2,R}((0,1))) + ImmutablePolynomial{R,Symbol(var),2}(NTuple{2,R}((zero(R), one(R)))) end @@ -217,7 +226,7 @@ function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) return ImmutablePolynomial{R, X, N+M-1}(cs) else n = findlast(!iszero, cs) - return ImmutablePolynomial{R, X, n}(cs[1:n]) + return ImmutablePolynomial{R, X, n}(NTuple{n,R}(cs[i] for i ∈ 1:n)) end end diff --git a/src/polynomials/Poly.jl b/src/polynomials/Poly.jl index 137c599a..057c2baf 100644 --- a/src/polynomials/Poly.jl +++ b/src/polynomials/Poly.jl @@ -18,7 +18,7 @@ This type provides support for `poly`, `polyval`, `polyder`, and base. Call `using Polynomials.PolyCompat` to enable this module. """ -struct Poly{T <: Number} <: Polynomials.StandardBasisPolynomial{T} +struct Poly{T <: Number,X} <: Polynomials.StandardBasisPolynomial{T,X} coeffs::Vector{T} var::Symbol function Poly(a::AbstractVector{T}, var::Polynomials.SymbolLike = :x) where {T <: Number} diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index f8656f33..af7c87a1 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -34,13 +34,12 @@ struct Polynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Vector{T} function Polynomial{T, X}(coeffs::AbstractVector{T}) where {T <: Number, X} if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" + throw(ArgumentError("The `Polynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) end length(coeffs) == 0 && return new{T,X}(zeros(T, 1)) - c = OffsetArrays.no_offset_view(coeffs) # ensure 1-based indexing - last_nz = findlast(!iszero, c) + last_nz = findlast(!iszero, coeffs) last = max(1, last_nz === nothing ? 0 : last_nz) - return new{T, X}(c[1:last]) + return new{T, X}(coeffs[1:last]) end end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 6b089fd5..fd059c87 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -219,14 +219,12 @@ function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} R = promote_type(T,S) P = SparsePolynomial - - q = zero(P{R,X}) - for k in eachindex(p) - @inbounds q[k] = R(p[k]) - end - q[0] = q[0] + c - return q + D = Dict{Int, R}(kv for kv ∈ p.coeffs) + D[0] = get(D,0,zero(R)) + c + + return SparsePolynomial{R,X}(D) + end function Base.:*(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T,X,S,Y} diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 8cb0c1cf..537e409d 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -35,7 +35,7 @@ isimmutable(::Type{<:ImmutablePolynomial}) = true p = P(coeff) @test coeffs(p) ==ᵗ⁰ coeff @test degree(p) == length(coeff) - 1 - @test p.var == :x + @test Polynomials.var(p) == :x P == Polynomial && @test length(p) == length(coeff) P == Polynomial && @test size(p) == size(coeff) P == Polynomial && @test size(p, 1) == size(coeff, 1) @@ -129,8 +129,12 @@ Base.getindex(z::ZVector, I::Int) = parent(z)[I + z.offset] as = ones(3:4) bs = parent(as) + # LaurentPolynomial accepts OffsetArrays; others do not and throw an ArgumentError + @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) + for P in Ps - @test P(as) == P(bs) + P == LaurentPolynomial && continue + @test_throws ArgumentError P(as) @test P{eltype(as)}(as) == P{eltype(as)}(bs) end @@ -139,6 +143,7 @@ Base.getindex(z::ZVector, I::Int) = parent(z)[I + z.offset] c = ZVector(a) d = ZVector(b) for P in Ps + if P == LaurentPolynomial && continue @test P(a) == P(b) == P(c) == P(d) end @@ -460,18 +465,20 @@ end end @testset "Conversion" begin + + X = :x for P in Ps if !isimmutable(P) p = P([0,one(Float64)]) - @test P{Complex{Float64}} == typeof(p + 1im) - @test P{Complex{Float64}} == typeof(1im - p) - @test P{Complex{Float64}} == typeof(p * 1im) + @test P{Complex{Float64},X} == typeof(p + 1im) + @test P{Complex{Float64},X} == typeof(1im - p) + @test P{Complex{Float64},X} == typeof(p * 1im) else p = P([0,one(Float64)]) N=2 - @test P{Complex{Float64},N} == typeof(p + 1im) - @test P{Complex{Float64},N} == typeof(1im - p) - @test P{Complex{Float64},N} == typeof(p * 1im) + @test P{Complex{Float64},X,N} == typeof(p + 1im) + @test P{Complex{Float64},X,N} == typeof(1im - p) + @test P{Complex{Float64},X,N} == typeof(p * 1im) end end @@ -994,11 +1001,11 @@ end for P in Ps if !isimmutable(P) p = P{T2}(T1.(rand(1:3,3))) - @test typeof(p) == P{T2} + @test typeof(p) == P{T2, :x} else N = 3 p = P{T2}(T1.(rand(1:3,N))) - @test typeof(p) == P{T2,N} + @test typeof(p) == P{T2,:x, N} end end @@ -1010,14 +1017,14 @@ end if !isimmutable(P) for T in (Int32, Int64, BigInt) p₁ = P{T}(Float64.(rand(1:3,5))) - @test typeof(p₁) == P{T} # conversion works + @test typeof(p₁) == P{T,:x} # conversion works @test_throws InexactError P{T}(rand(5)) end else for T in (Int32, Int64, BigInt) N = 5 p₁ = P{T}(Float64.(rand(1:3,5))) - @test typeof(p₁) == P{T,5} # conversion works + @test typeof(p₁) == P{T,:x,5} # conversion works @test_throws InexactError P{T}(rand(5)) end end diff --git a/test/runtests.jl b/test/runtests.jl index 8df9fe87..c0880d7a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,4 +10,4 @@ using OffsetArrays @testset "Standard basis" begin include("StandardBasis.jl") end @testset "ChebyshevT" begin include("ChebyshevT.jl") end -@testset "Poly, Pade (compatability)" begin include("Poly.jl") end +#@testset "Poly, Pade (compatability)" begin include("Poly.jl") end From c52febd7aa2a0721577eddcbba29526afb3283c3 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 9 Feb 2021 13:08:42 -0500 Subject: [PATCH 04/38] move symbol into type --- src/Polynomials.jl | 2 +- src/abstract.jl | 7 +- src/common.jl | 37 ++++++----- src/pade.jl | 22 +++--- src/polynomials/ChebyshevT.jl | 24 ++++--- src/polynomials/ImmutablePolynomial.jl | 92 +++++++++++--------------- src/polynomials/LaurentPolynomial.jl | 69 +++++++++++-------- src/polynomials/Poly.jl | 23 ++++--- src/polynomials/Polynomial.jl | 24 ++++--- src/polynomials/SparsePolynomial.jl | 35 ++++++---- src/polynomials/ngcd.jl | 2 +- src/polynomials/standard-basis.jl | 31 +++++---- src/show.jl | 4 +- test/ChebyshevT.jl | 2 +- test/Poly.jl | 24 +++---- test/StandardBasis.jl | 52 ++++++++------- test/runtests.jl | 2 +- 17 files changed, 237 insertions(+), 215 deletions(-) diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 8b098d97..1eb39934 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -26,6 +26,6 @@ include("polynomials/multroot.jl") include("polynomials/ChebyshevT.jl") # compat; opt-in with `using Polynomials.PolyCompat` -#include("polynomials/Poly.jl") +include("polynomials/Poly.jl") end # module diff --git a/src/abstract.jl b/src/abstract.jl index 06a9e7fe..d65fdb59 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -18,7 +18,7 @@ abstract type AbstractPolynomial{T,X} end # convert `as` into polynomial of type P based on instance, inheriting variable # (and for LaurentPolynomial the offset) -_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P){eltype(as), var(P)}(as) # ⟒(P)(as, var(P)) +_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P)(as, indeterminate(P)) """ Polynomials.@register(name) @@ -41,10 +41,9 @@ macro register(name) poly = esc(name) quote Base.convert(::Type{P}, p::P) where {P<:$poly} = p - Base.convert(P::Type{<:$poly}, p::$poly{T}) where {T} = P(coeffs(p), var(p)) + Base.convert(P::Type{<:$poly}, p::$poly{T}) where {T} = constructorof(P){eltype(P), indeterminate(P,p)}(coeffs(p)) Base.promote(p::P, q::Q) where {X, T, P <:$poly{T,X}, Q <: $poly{T,X}} = p,q - Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{<:$poly{S,X}}) where {T,S,X} = - $poly{promote_type(T, S,X)} + Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{<:$poly{S,X}}) where {T,S,X} = $poly{promote_type(T, S),X} Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{S}) where {T,S<:Number,X} = $poly{promote_type(T, S),X} $poly(coeffs::AbstractVector{T}, var::SymbolLike = :x) where {T} = diff --git a/src/common.jl b/src/common.jl index c71f7426..bfed4d51 100644 --- a/src/common.jl +++ b/src/common.jl @@ -275,7 +275,7 @@ end Check if either `p` or `q` is constant or if `p` and `q` share the same variable """ check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) = - (Polynomials.isconstant(p) || Polynomials.isconstant(q)) || var(p) == var(q) + (Polynomials.isconstant(p) || Polynomials.isconstant(q)) || indeterminate(p) == indeterminate(q) #= Linear Algebra =# @@ -508,16 +508,20 @@ Base.setindex!(p::AbstractPolynomial, values, ::Colon) = #= identity =# Base.copy(p::P) where {P <: AbstractPolynomial} = _convert(p, copy(coeffs(p))) -Base.hash(p::AbstractPolynomial, h::UInt) = hash(var(p), hash(coeffs(p), h)) +Base.hash(p::AbstractPolynomial, h::UInt) = hash(indeterminate(p), hash(coeffs(p), h)) #= zero, one, variable, basis =# -var(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X -var(::Type{P}) where {P <: AbstractPolynomial} = :x -var(p::AbstractPolynomial{T, X}) where {T, X} = X -export var - +# get symbol of polynomial. (e.g. `:x` from 1x^2 + 2x^3... +_indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T, X}} = X +_indeterminate(::Type{P}) where {P <: AbstractPolynomial} = nothing +indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X +indeterminate(::Type{P}) where {P <: AbstractPolynomial} = :x +indeterminate(p::AbstractPolynomial{T, X}) where {T, X} = X +function indeterminate(PP::Type{P}, p::AbstractPolynomial) where {P <: AbstractPolynomial} + X = _indeterminate(PP) == nothing ? indeterminate(p) : _indeterminate(PP) +end """ @@ -528,16 +532,16 @@ Returns a representation of 0 as the given polynomial. """ Base.zero(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P)(zeros(eltype(P), 1), var) Base.zero(::Type{P}) where {T, X, P<:AbstractPolynomial{T,X}} = ⟒(P){T,X}(zeros(T,1)) -Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, var(p)) +Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, indeterminate(p)) """ one(::Type{<:AbstractPolynomial}) one(::AbstractPolynomial) Returns a representation of 1 as the given polynomial. """ -Base.one(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P)(ones(eltype(P),1), var) # assumes p₀ = 1 +Base.one(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P){eltype(P), Symbol(var)}(ones(eltype(P),1)) # assumes p₀ = 1 Base.one(::Type{P}) where {T, X, P<:AbstractPolynomial{T,X}} = ⟒(P){T,X}(ones(T,1)) -Base.one(p::P) where {P <: AbstractPolynomial} = one(P, var(p)) +Base.one(p::P) where {P <: AbstractPolynomial} = one(P, indeterminate(p)) Base.oneunit(::Type{P}, args...) where {P <: AbstractPolynomial} = one(P, args...) Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) @@ -546,7 +550,7 @@ Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) """ variable(var=:x) variable(::Type{<:AbstractPolynomial}, var=:x) - variable(p::AbstractPolynomial, var=var(p)) + variable(p::AbstractPolynomial, var=indeterminate(p)) Return the monomial `x` in the indicated polynomial basis. If no type is give, will default to [`Polynomial`](@ref). Equivalent to `P(var)`. @@ -568,7 +572,7 @@ julia> roots((x - 3) * (x + 2)) ``` """ variable(::Type{P}, var::SymbolLike = :x) where {P <: AbstractPolynomial} = MethodError() -variable(p::AbstractPolynomial, var::SymbolLike = var(p)) = variable(typeof(p), var) +variable(p::AbstractPolynomial, var::SymbolLike = indeterminate(p)) = variable(typeof(p), var) variable(::Type{P}) where {T,X, P <: AbstractPolynomial{T,X}} = variable(P, X) variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) @@ -687,12 +691,11 @@ Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) = Base.:(==)(p::AbstractPolynomial, n::Number) = degree(p) <= 0 && p[0] == n Base.:(==)(n::Number, p::AbstractPolynomial) = p == n -function Base.isapprox(p1::AbstractPolynomial{T}, - p2::AbstractPolynomial{S}; +function Base.isapprox(p1::AbstractPolynomial{T,X}, + p2::AbstractPolynomial{S,Y}; rtol::Real = (Base.rtoldefault(T, S, 0)), - atol::Real = 0,) where {T,S} - - p1, p2 = promote(p1, p2) + atol::Real = 0,) where {T,X,S,Y} +# p1, p2 = promote(p1, p2) check_same_variable(p1, p2) || error("p1 and p2 must have same var") # copy over from abstractarray.jl Δ = norm(p1-p2) diff --git a/src/pade.jl b/src/pade.jl index 19bfcb4e..c918d627 100644 --- a/src/pade.jl +++ b/src/pade.jl @@ -1,6 +1,8 @@ module PadeApproximation using ..Polynomials +indeterminate = Polynomials.indeterminate + using ..PolyCompat export Pade, padeval @@ -27,8 +29,8 @@ struct Pade{T <: Number,S <: Number} q::Union{Poly{S}, Polynomial{S}} var::Symbol function Pade{T,S}(p::Union{Poly{T}, Polynomial{T}}, q::Union{Poly{S}, Polynomial{S}}) where {T,S} - if p.var != q.var error("Polynomials must have same variable") end - new{T,S}(p, q, p.var) + if indeterminate(p) != indeterminate(q) error("Polynomials must have same variable") end + new{T,S}(p, q, indeterminate(p)) end end @@ -36,10 +38,10 @@ Pade(p::Polynomial{T}, q::Polynomial{S}) where {T <: Number,S <: Number} = Pade{ function Pade(c::Polynomial{T}, m::Integer, n::Integer) where {T} m + n < length(c) || error("m + n must be less than the length of the Polynomial") - rold = Polynomial([zeros(T, m + n + 1);one(T)], c.var) - rnew = Polynomial(c[0:m + n], c.var) - uold = Polynomial([one(T)], c.var) - vold = Polynomial([zero(T)], c.var) + rold = Polynomial([zeros(T, m + n + 1);one(T)], indeterminate(c)) + rnew = Polynomial(c[0:m + n], indeterminate(c)) + uold = Polynomial([one(T)], indeterminate(c)) + vold = Polynomial([zero(T)], indeterminate(c)) unew, vnew = vold, uold @inbounds for i = 1:n temp0, temp1, temp2 = rnew, unew, vnew @@ -61,10 +63,10 @@ Pade(p::Poly{T}, q::Poly{S}) where {T <: Number,S <: Number} = Pade{T,S}(p, q) function Pade(c::Poly{T}, m::Integer, n::Integer) where {T} m + n < length(c) || error("m + n must be less than the length of the polynomial") - rold = Poly([zeros(T, m + n + 1);one(T)], c.var) - rnew = Poly(c[0:m + n], c.var) - uold = Poly([one(T)], c.var) - vold = Poly([zero(T)], c.var) + rold = Poly([zeros(T, m + n + 1);one(T)], indeterminate(c)) + rnew = Poly(c[0:m + n], indeterminate(c)) + uold = Poly([one(T)], indeterminate(c)) + vold = Poly([zero(T)], indeterminate(c)) unew, vnew = vold, uold @inbounds for i = 1:n temp0, temp1, temp2 = rnew, unew, vnew diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index f694068a..039f3607 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -29,15 +29,17 @@ ChebyshevT(1.0⋅T_0(x)) """ struct ChebyshevT{T <: Number, X} <: AbstractPolynomial{T, X} coeffs::Vector{T} - function ChebyshevT{T, X}(coeffs::AbstractVector{T}) where {T <: Number,X} + function ChebyshevT{T, X}(coeffs::AbstractVector{S}) where {T <: Number,X, S} length(coeffs) == 0 && return new{T,X}(zeros(T, 1)) if Base.has_offset_axes(coeffs) + # throw(ArgumentError("The `ChebyshevT` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) + @warn "ignoring the axis offset of the coefficient vector" + coeffs = OffsetArrays.no_offset_view(coeffs) end - c = OffsetArrays.no_offset_view(coeffs) - last_nz = findlast(!iszero, c) + last_nz = findlast(!iszero, coeffs) last = max(1, last_nz === nothing ? 0 : last_nz) - return new{T,X}(c[1:last]) + return new{T,X}(convert(Vector{T}, coeffs[1:last])) end end @@ -45,13 +47,13 @@ end function Base.convert(P::Type{<:Polynomial}, ch::ChebyshevT) if length(ch) < 3 - return P(ch.coeffs, var(ch)) + return P(ch.coeffs, indeterminate(ch)) end - c0 = P(ch[end - 1], var(ch)) - c1 = P(ch[end], var(ch)) + c0 = P(ch[end - 1], indeterminate(ch)) + c1 = P(ch[end], indeterminate(ch)) @inbounds for i in degree(ch):-1:2 tmp = c0 - c0 = P(ch[i - 2], var(ch)) - c1 + c0 = P(ch[i - 2], indeterminate(ch)) - c1 c1 = tmp + c1 * variable(P) * 2 end return c0 + c1 * variable(P) @@ -135,7 +137,7 @@ function integrate(p::ChebyshevT{T}, C::S) where {T,S <: Number} a2[i] -= p[i] / (2 * (i - 1)) end a2[1] += R(C) - ChebyshevT(a2)(0) - return ChebyshevT(a2, var(p)) + return ChebyshevT(a2, indeterminate(p)) end @@ -143,7 +145,7 @@ function derivative(p::ChebyshevT{T}, order::Integer = 1) where {T} order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) R = eltype(one(T)/1) order == 0 && return convert(ChebyshevT{R}, p) - hasnan(p) && return ChebyshevT(R[NaN], var(p)) + hasnan(p) && return ChebyshevT(R[NaN], indeterminate(p)) order > length(p) && return zero(ChebyshevT{R}) @@ -160,7 +162,7 @@ function derivative(p::ChebyshevT{T}, order::Integer = 1) where {T} end der[1] = q[1] - pp = ChebyshevT(der, var(p)) + pp = ChebyshevT(der, indeterminate(p)) return order > 1 ? derivative(pp, order - 1) : pp end diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index d60d29a5..901dd952 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -57,13 +57,15 @@ end @register ImmutablePolynomial ## Various interfaces + +## Abstract Vector coefficients function ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}) where {T,X,S} R = promote_type(T,S) if Base.has_offset_axes(coeffs) - throw(ArgumentError("The `ImmutablePolynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) - + # throw(ArgumentError("The `ImmutablePolynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) @warn "ignoring the axis offset of the coefficient vector" + coeffs = OffsetArrays.no_offset_view(coeffs) end #c = OffsetArrays.no_offset_view(coeffs) N = findlast(!iszero, coeffs) @@ -71,34 +73,14 @@ function ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}) where {T,X,S} ImmutablePolynomial{T, X, N}(NTuple{N,T}(cᵢ for cᵢ ∈ coeffs)) end - +## -- Tuple arguments function ImmutablePolynomial{T,X}(coeffs::Tuple) where {T,X} N = findlast(!iszero, coeffs) - ImmutablePolynomial{T, X, N}(NTuple{N,T}(T(coeffs[i]) for i in 1:N)) + N == nothing && return zero(ImmutablePolynomial{T,X}) + ImmutablePolynomial{T,X,N}(NTuple{N,T}(coeffs[i] for i in 1:N)) end -#function ImmutablePolynomial{T,N}(coeffs::AbstractVector{S}, var::SymbolLike=:x) where {T <: Number, N, S#} -# if Base.has_offset_axes(coeffs) -# @warn "ignoring the axis offset of the coefficient vector" -# end -# ImmutablePolynomial{T,var, N}(NTuple{N,T}(tuple(coeffs...))) -#end - -## -- -function ImmutablePolynomial{T}(coeffs::NTuple{M,S}, var::SymbolLike=:x) where {T, S<: Number, M} - N = findlast(!iszero, coeffs) - if N == nothing - return zero(ImmutablePolynomial{T, Symbol(var)}) - else - cs = NTuple{N,T}(coeffs[i] for i in 1:N) - return ImmutablePolynomial{T,Symbol(var),N}(cs) - end -end - -function ImmutablePolynomial{T}(coeffs::Tuple, var::SymbolLike=:x) where {T} - ImmutablePolynomial{T}(T.(coeffs), Symbol(var)) -end -## -- +ImmutablePolynomial{T}(coeffs::Tuple, var::SymbolLike=:x) where {T} = ImmutablePolynomial{T,Symbol(var)}(coeffs) function ImmutablePolynomial(coeffs::Tuple, var::SymbolLike=:x) cs = NTuple(promote(coeffs...)) @@ -113,19 +95,19 @@ end # overrides from common.jl due to coeffs being non mutable, N in type parameters Base.collect(p::P) where {P <: ImmutablePolynomial} = [pᵢ for pᵢ ∈ p] -Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p), var(p)) +Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p)) ## defining these speeds things up -function Base.zero(P::Type{<:ImmutablePolynomial}, var::SymbolLike=var(P)) +function Base.zero(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) R = eltype(P) ImmutablePolynomial{R,Symbol(var),0}(NTuple{0,R}()) end -function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=var(P)) +function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) R = eltype(P) ImmutablePolynomial{R,Symbol(var),1}(NTuple{1,R}(one(R))) end -function variable(P::Type{<:ImmutablePolynomial}, var::SymbolLike=var(p)) +function variable(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) R = eltype(P) ImmutablePolynomial{R,Symbol(var),2}(NTuple{2,R}((zero(R), one(R)))) end @@ -157,27 +139,27 @@ for op in [:isequal, :(==)] end # in common.jl these call chop! and truncate! -function Base.chop(p::ImmutablePolynomial{T,N}; +function Base.chop(p::ImmutablePolynomial{T,X,N}; rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0) where {T,N} + atol::Real = 0) where {T,X,N} cs = coeffs(p) for i in N:-1:1 if !isapprox(cs[i], zero(T), rtol=rtol, atol=atol) - return ImmutablePolynomial{T,i}(cs[1:i], var(p)) + return ImmutablePolynomial{T,X,i}(cs[1:i]) end end - zero(ImmutablePolynomial{T}, var(p)) + zero(ImmutablePolynomial{T,X}) end -function Base.truncate(p::ImmutablePolynomial{T,N}; +function Base.truncate(p::ImmutablePolynomial{T,X,N}; rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0) where {T,N} + atol::Real = 0) where {T,X,N} q = chop(p, rtol=rtol, atol=atol) iszero(q) && return q cs = coeffs(q) thresh = maximum(abs,cs) * rtol + atol cs′ = map(c->abs(c) <= thresh ? zero(T) : c, cs) - ImmutablePolynomial{T}(tuple(cs′...), var(p)) + ImmutablePolynomial{T,X}(tuple(cs′...)) end # no in-place chop! and truncate! @@ -188,29 +170,30 @@ truncate!(p::ImmutablePolynomial; kwargs...) = truncate(p; kwargs...) ## -------------------- ## -(p::ImmutablePolynomial{T,N})(x::S) where {T,N,S} = evalpoly(x, p.coeffs) +(p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = evalpoly(x, p.coeffs) -function Base.:+(p1::ImmutablePolynomial{T,X, N}, p2::ImmutablePolynomial{S,Y, M}) where {T,X, N,S,Y,M} +function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) where {T,X,N,S,Y,M} R = promote_type(S,T) - iszero(N) && return ImmutablePolynomial{R, var(p2)}(coeffs(p2)) - iszero(M) && return ImmutablePolynomial{R, var(p1)}(coeffs(p1)) - - isconstant(p1) && X != Y && return p2 + p1[0]*one(ImmutablePolynomial{R, Y}) - isconstant(p2) && X != Y && return p1 + p2[0]*one(ImmutablePolynomial{R, X}) + iszero(N) && return ImmutablePolynomial{R,Y}(coeffs(p2)) + iszero(M) && return ImmutablePolynomial{R,X}(coeffs(p1)) - X != Y && error("Polynomials must have same variable") + if X != Y + isconstant(p1) && return ImmutablePolynomial{T,Y,1}(p1.coeffs) + p2 + isconstant(p2) && return p1 + ImmutablePolynomial{S,X,1}(p2.coeffs) + error("Polynomials must have same variable") + end if N == M cs = NTuple{N,R}(p1[i] + p2[i] for i in 0:N-1) - ImmutablePolynomial{R,X,N}(cs) + ImmutablePolynomial{R,X}(cs) elseif N < M cs = (p2.coeffs) ⊕ (p1.coeffs) - ImmutablePolynomial{R,X,M}(cs) + ImmutablePolynomial{R,X,M}(convert(NTuple{M,R}, cs)) else cs = (p1.coeffs) ⊕ (p2.coeffs) - ImmutablePolynomial{R,X,N}(cs) + ImmutablePolynomial{R,X,N}(convert(NTuple{N,R}, cs)) end end @@ -280,20 +263,19 @@ end end # scalar ops -function Base.:+(p::ImmutablePolynomial{T,X, N}, c::S) where {T, X, N, S<:Number} +function Base.:+(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X, N, S<:Number} R = promote_type(T,S) - iszero(c) && return ImmutablePolynomial{R,X, N}(p.coeffs) + iszero(c) && return ImmutablePolynomial{R,X,N}(convert(NTuple{N,R},p.coeffs)) N == 0 && return ImmutablePolynomial{R,X,1}((c,)) N == 1 && return ImmutablePolynomial((p[0]+c,), X) - - q = ImmutablePolynomial{R,X, 1}((c,)) - return p + q - + q = p + ImmutablePolynomial{S,X,1}((c,)) + return q + end function Base.:*(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X,N, S <: Number} - R = promote_type(T,S) + R = eltype(one(T)*one(S)) iszero(c) && return zero(ImmutablePolynomial{R,X}) ImmutablePolynomial{R,X,N}(p.coeffs .* c) end diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index c97fc3fc..a695cf22 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -76,26 +76,24 @@ struct LaurentPolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Vector{T} m::Base.RefValue{Int} n::Base.RefValue{Int} - function LaurentPolynomial{T,X}(coeffs::AbstractVector{T}, - m::Union{Int, Nothing}=nothing) where {T <: Number, X} + function LaurentPolynomial{T,X}(coeffs::AbstractVector{S}, + m::Union{Int, Nothing}=nothing) where {T <: Number, X, S} fnz = findfirst(!iszero, coeffs) fnz == nothing && return new{T,X}(zeros(T,1), Ref(0), Ref(0)) lnz = findlast(!iszero, coeffs) - if Base.has_offset_axes(coeffs) # if present, use axes - cs = coeffs[fnz:lnz] + cs = convert(Vector{T}, coeffs[fnz:lnz]) return new{T,X}(cs, Ref(fnz), Ref(lnz)) else - c = coeffs[fnz:lnz] + c = convert(Vector{T}, coeffs[fnz:lnz]) m′ = fnz - 1 + (m == nothing ? 0 : m) n = m′ + (lnz-fnz) - (n- m′ + 1 == length(c)) || throw(ArgumentError("Lengths do not match")) - + (n - m′ + 1 == length(c)) || throw(ArgumentError("Lengths do not match")) new{T,X}(c, Ref(m′), Ref(n)) end end @@ -126,23 +124,30 @@ end ## # LaurentPolynomial is a wider collection than other standard basis polynomials. -Base.promote_rule(::Type{P},::Type{Q}) where {T, P <: LaurentPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} = - LaurentPolynomial{promote_type(T, S)} +Base.promote_rule(::Type{P},::Type{Q}) where {T, X, P <: LaurentPolynomial{T,X}, S, Q <: StandardBasisPolynomial{S, X}} = LaurentPolynomial{promote_type(T, S), X} -Base.promote_rule(::Type{Q},::Type{P}) where {T, P <: LaurentPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} = - LaurentPolynomial{promote_type(T, S)} +Base.promote_rule(::Type{Q},::Type{P}) where {T, X, P <: LaurentPolynomial{T,X}, S, Q <: StandardBasisPolynomial{S,X}} = + LaurentPolynomial{promote_type(T, S),X} function Base.convert(P::Type{<:Polynomial}, q::LaurentPolynomial) m,n = (extrema∘degreerange)(q) m < 0 && throw(ArgumentError("Can't convert a Laurent polynomial with m < 0")) - P([q[i] for i in 0:n], var(q)) + P([q[i] for i in 0:n], indeterminate(q)) end -Base.convert(::Type{T}, p::LaurentPolynomial) where {T<:LaurentPolynomial} = T(p.coeffs, p.m[], var(p)) +# save variable if specified in P +Base.convert(::Type{P}, p::LaurentPolynomial) where {T, X, P<:LaurentPolynomial{T,X}} = P(p.coeffs, p.m[]) +function Base.convert(::Type{P}, p::LaurentPolynomial) where {P<:LaurentPolynomial} + T = eltype(P) + v′ = _indeterminate(P) + X = v′ == nothing ? indeterminate(p) : v′ + ⟒(P){T, X}(convert(Vector{T},p.coeffs), p.m[]) +end function Base.convert(::Type{P}, q::StandardBasisPolynomial{S}) where {T, P <:LaurentPolynomial{T},S} - d = degree(q) - P([q[i] for i in 0:d], 0, var(q)) + v′ = _indeterminate(P) + X = v′ == nothing ? indeterminate(q) : v′ + ⟒(P){T,X}([q[i] for i in 0:degree(q)], 0) end ## @@ -168,8 +173,8 @@ end ## changes to common.jl mostly as the range in the type is different ## Base.:(==)(p1::LaurentPolynomial, p2::LaurentPolynomial) = - check_same_variable(p1, p2) && (degreerange(p1) == degreerange(p2)) && (coeffs(p1) == coeffs(p2)) -Base.hash(p::LaurentPolynomial, h::UInt) = hash(var(p), hash(degreerange(p), hash(coeffs(p), h))) + check_same_variable(p1, p2) && (degreerange(chop!(p1)) == degreerange(chop!(p2))) && (coeffs(p1) == coeffs(p2)) +Base.hash(p::LaurentPolynomial, h::UInt) = hash(indeterminate(p), hash(degreerange(p), hash(coeffs(p), h))) isconstant(p::LaurentPolynomial) = iszero(lastindex(p)) && iszero(firstindex(p)) basis(P::Type{<:LaurentPolynomial{T}}, n::Int, var::SymbolLike=:x) where{T} = LaurentPolynomial{T,Symbol(var)}(ones(T,1), n) @@ -238,10 +243,10 @@ function chop!(p::LaurentPolynomial{T}; end end - cs = coeffs(p) + cs = copy(coeffs(p)) rng = m-m0+1:n-m0+1 resize!(p.coeffs, length(rng)) - p.coeffs[:] = coeffs(p)[rng] + p.coeffs[:] = cs[rng] isempty(p.coeffs) && push!(p.coeffs,zero(T)) p.m[], p.n[] = m, max(m,n) @@ -349,7 +354,7 @@ function paraconj(p::LaurentPolynomial) cs = p.coeffs ds = adjoint.(cs) n = degree(p) - LaurentPolynomial(reverse(ds), -n, var(p)) + LaurentPolynomial(reverse(ds), -n, indeterminate(p)) end """ @@ -398,7 +403,7 @@ function cconj(p::LaurentPolynomial) ps[i+1-m] *= -1 end end - LaurentPolynomial(ps, m, var(p)) + LaurentPolynomial(ps, m, indeterminate(p)) end @@ -436,19 +441,24 @@ Base.:+(p::LaurentPolynomial{T}, c::S) where {T, S <: Number} = sum(promote(p,c) ## function Base.:+(p1::P1, p2::P2) where {T,X,P1<:LaurentPolynomial{T,X}, S,Y, P2<:LaurentPolynomial{S,Y}} + R = promote_type(T,S) + if isconstant(p1) i₁ = firstindex(p1) - p2[i₁] += p1[i₁] - return p2 + q2 = LaurentPolynomial{R,Y}(p2.coeffs, p2.m[]) + q2[i₁] += p1[i₁] + chop!(q2) + return q2 elseif isconstant(p2) i₂ = firstindex(p2) - p1[i₂] += p2[i₂] - return p1 + q1 = LaurentPolynomial{R,X}(p1.coeffs, p1.m[]) + q1[i₂] += p2[i₂] + chop!(q1) + return q1 end X != Y && error("LaurentPolynomials must have same variable") - R = promote_type(T,S) m1,n1 = (extrema ∘ degreerange)(p1) m2,n2 = (extrema ∘ degreerange)(p2) @@ -564,9 +574,10 @@ function integrate(p::P, k::S) where {T, X, P<: LaurentPolynomial{T, X}, S<:Numb !iszero(p[-1]) && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term")) R = eltype((one(T)+one(S))/1) - + Q = ⟒(P){R, X} + if hasnan(p) || isnan(k) - return P([NaN], 0, X) # not R(NaN)!! don't like XXX + return P([NaN], 0) # not Q([NaN])!! don't like XXX end @@ -589,7 +600,7 @@ function integrate(p::P, k::S) where {T, X, P<: LaurentPolynomial{T, X}, S<:Numb as[1-m] = k - return ⟒(P){R,X}(as, m) + return Q(as, m) end diff --git a/src/polynomials/Poly.jl b/src/polynomials/Poly.jl index 057c2baf..e87710d3 100644 --- a/src/polynomials/Poly.jl +++ b/src/polynomials/Poly.jl @@ -1,6 +1,7 @@ module PolyCompat using ..Polynomials +indeterminate = Polynomials.indeterminate #= Compat support for old code. This will be opt-in by v1.0, through "using Polynomials.PolyCompat" @@ -20,25 +21,27 @@ base. Call `using Polynomials.PolyCompat` to enable this module. """ struct Poly{T <: Number,X} <: Polynomials.StandardBasisPolynomial{T,X} coeffs::Vector{T} - var::Symbol function Poly(a::AbstractVector{T}, var::Polynomials.SymbolLike = :x) where {T <: Number} # if a == [] we replace it with a = [0] + X = Symbol(var) if length(a) == 0 - return new{T}(zeros(T, 1), Symbol(var)) + return new{T,X}(zeros(T, 1)) else # determine the last nonzero element and truncate a accordingly last_nz = findlast(!iszero, a) a_last = max(1, last_nz === nothing ? 0 : last_nz) - new{T}(a[1:a_last], Symbol(var)) + new{T,X}(a[1:a_last]) end end end Polynomials.@register Poly -Base.convert(P::Type{<:Polynomial}, p::Poly{T}) where {T} = P(p.coeffs, p.var) +Poly{T,X}(coeffs::AbstractVector{S}) where {T,X,S} = Poly(convert(Vector{T},coeffs), X) -Base.eltype(P::Type{<:Poly}) = P +Base.convert(P::Type{<:Polynomial}, p::Poly{T}) where {T} = Polynomial{eltype(P),indeterminate(P,p)}(p.coeffs) +Base.convert(P::Type{<:Poly}, p::Poly{T,X}) where {T<:Number,X} = Polynomials.constructorof(P){_eltype(P),indeterminate(P,p)}(p.coeffs) +Base.eltype(P::Type{<:Poly{T,X}}) where {T, X} = P _eltype(::Type{<:Poly{T}}) where {T} = T _eltype(::Type{Poly}) = Float64 Base.zero(P::Type{<:Poly},var=:x) = Poly(zeros(_eltype(P),0), var) @@ -46,7 +49,7 @@ Base.one(P::Type{<:Poly},var=:x) = Poly(ones(_eltype(P),1), var) function Polynomials.basis(P::Type{<:Poly}, k::Int, _var::Polynomials.SymbolLike=:x; var=_var) zs = zeros(Int, k+1) zs[end] = 1 - P(zs, var) + Polynomials.constructorof(P){_eltype(P), Symbol(var)}(zs) end function (p::Poly{T})(x::S) where {T,S} @@ -61,14 +64,14 @@ end function Base.:+(p1::Poly, p2::Poly) - p1.var != p2.var && error("Polynomials must have same variable") + indeterminate(p1) != indeterminate(p2) && error("Polynomials must have same variable") n = max(length(p1), length(p2)) c = [p1[i] + p2[i] for i = 0:n-1] - return Poly(c, p1.var) + return Poly(c, indeterminate(p1)) end function Base.:*(p1::Poly{T}, p2::Poly{S}) where {T,S} - p1.var != p2.var && error("Polynomials must have same variable") + indeterminate(p1) != indeterminate(p2) && error("Polynomials must have same variable") n = length(p1) - 1 m = length(p2) - 1 R = promote_type(T, S) @@ -76,7 +79,7 @@ function Base.:*(p1::Poly{T}, p2::Poly{S}) where {T,S} for i in 0:n, j in 0:m c[i + j + 1] += p1[i] * p2[j] end - return Poly(c, p1.var) + return Poly(c, indeterminate(p1)) end diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index af7c87a1..c079099d 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -32,14 +32,16 @@ Polynomial(1.0) """ struct Polynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Vector{T} - function Polynomial{T, X}(coeffs::AbstractVector{T}) where {T <: Number, X} + function Polynomial{T, X}(coeffs::AbstractVector{S}) where {T <: Number, X, S} if Base.has_offset_axes(coeffs) - throw(ArgumentError("The `Polynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) + #throw(ArgumentError("The `Polynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) + @warn "ignoring the axis offset of the coefficient vector" + coeffs = OffsetArrays.no_offset_view(coeffs) end length(coeffs) == 0 && return new{T,X}(zeros(T, 1)) last_nz = findlast(!iszero, coeffs) last = max(1, last_nz === nothing ? 0 : last_nz) - return new{T, X}(coeffs[1:last]) + return new{T, X}(convert(Vector{T}, coeffs[1:last])) end end @@ -73,7 +75,7 @@ julia> p.(0:3) function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} n1, n2 = length(p1), length(p2) if n1 > 1 && n2 > 1 - var(p1) != var(p2) && error("Polynomials must have same variable") + indeterminate(p1) != indeterminate(p2) && error("Polynomials must have same variable") end R = promote_type(T,S) c = zeros(R, max(n1, n2)) @@ -89,15 +91,15 @@ function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} c[i] += p1.coeffs[i] end end - return Polynomial{R, var(p1)}(c) + return Polynomial{R, indeterminate(p1)}(c) elseif n1 <= 1 c .= p2.coeffs c[1] += p1[0] - return Polynomial{R, var(p2)}(c) + return Polynomial{R, indeterminate(p2)}(c) else c .= p1.coeffs c[1] += p2[0] - return Polynomial{R, var(p1)}(c) + return Polynomial{R, indeterminate(p1)}(c) end end @@ -106,19 +108,19 @@ function Base.:*(p1::Polynomial{T}, p2::Polynomial{S}) where {T,S} n, m = length(p1)-1, length(p2)-1 # not degree, so pNULL works if n > 0 && m > 0 - var(p1) != var(p2) && error("Polynomials must have same variable") + indeterminate(p1) != indeterminate(p2) && error("Polynomials must have same variable") R = promote_type(T, S) c = zeros(R, m + n + 1) for i in 0:n, j in 0:m @inbounds c[i + j + 1] += p1[i] * p2[j] end - return Polynomial{R, var(p1)}(c) + return Polynomial{R, indeterminate(p1)}(c) elseif n <= 0 cs = p2.coeffs * p1[0] - return Polynomial{eltype(cs), var(p2)}(cs) + return Polynomial{eltype(cs), indeterminate(p2)}(cs) else cs = p1.coeffs * p2[0] - return Polynomial{eltype(cs), var(p1)}(cs) + return Polynomial{eltype(cs), indeterminate(p1)}(cs) end end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index fd059c87..a36541d6 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -41,8 +41,8 @@ julia> p(1) """ struct SparsePolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Dict{Int, T} - function SparsePolynomial{T, X}(coeffs::AbstractDict{Int, T}) where {T <: Number, X} - c = Dict(coeffs) + function SparsePolynomial{T, X}(coeffs::AbstractDict{Int, S}) where {T <: Number, X, S} + c = Dict{Int, T}(coeffs) for (k,v) in coeffs iszero(v) && pop!(c, k) end @@ -52,31 +52,40 @@ end @register SparsePolynomial -function SparsePolynomial{T,X}(coeffs::AbstractVector{T}) where {T <: Number, X} +function SparsePolynomial{T}(coeffs::AbstractDict{Int, S}, var::SymbolLike=:x) where {T <: Number, S} + SparsePolynomial{T, Symbol(var)}(convert(Dict{Int,T}, coeffs)) +end + +function SparsePolynomial(coeffs::AbstractDict{Int, T}, var::SymbolLike=:x) where {T <: Number} + SparsePolynomial{T, Symbol(var)}(coeffs) +end + +function SparsePolynomial{T,X}(coeffs::AbstractVector{S}) where {T <: Number, X, S} firstindex(coeffs) >= 0 || throw(ArgumentError("Use the `LaurentPolynomial` type for arrays with a negative first index")) if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" + # throw(ArgumentError("The `SparsePolynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) + + @warn "ignoring the axis offset of the coefficient vector" + coeffs = OffsetArrays.no_offset_view(coeffs) end + c = OffsetArrays.no_offset_view(coeffs) # ensure 1-based indexing p = Dict{Int,T}(i - 1 => v for (i,v) in pairs(c)) return SparsePolynomial{T,X}(p) end -function SparsePolynomial(coeffs::AbstractDict{Int, T}, var::SymbolLike=:x) where {T <: Number} - SparsePolynomial{T, Symbol(var)}(coeffs) -end # conversion function Base.convert(P::Type{<:Polynomial}, q::SparsePolynomial) - ⟒(P)(coeffs(q), var(q)) + ⟒(P)(coeffs(q), indeterminate(q)) end function Base.convert(P::Type{<:SparsePolynomial}, q::StandardBasisPolynomial{T}) where {T} R = promote(eltype(P), T) - ⟒(P){R}(coeffs(q), var(q)) + ⟒(P){R}(coeffs(q), indeterminate(q)) end ## changes to common @@ -284,16 +293,16 @@ function derivative(p::SparsePolynomial{T,X}, order::Integer = 1) where {T,X} end -function integrate(p::SparsePolynomial{T,X}, k::S) where {T, X, S<:Number} +function integrate(p::P, k::S) where {T, X, P<:SparsePolynomial{T,X}, S<:Number} R = eltype((one(T)+one(S))/1) - P = SparsePolynomial + Q = SparsePolynomial{R,X} if hasnan(p) || isnan(k) - return P{R,X}(Dict(0 => R(NaN))) # not R(NaN)!! don't like XXX + return P(Dict(0 => NaN)) # not Q(NaN)!! don't like XXX end - ∫p = P{R,X}(R(k)) + ∫p = Q(R(k)) for k in eachindex(p) ∫p[k + 1] = p[k] / (k+1) end diff --git a/src/polynomials/ngcd.jl b/src/polynomials/ngcd.jl index decc91cd..09700d93 100644 --- a/src/polynomials/ngcd.jl +++ b/src/polynomials/ngcd.jl @@ -24,7 +24,7 @@ function ngcd(p::P, q::Q, args...;kwargs...) where {T, S, P<:StandardBasisPolyno ps, qs = [p′[i] for i in eachindex(p′)], [q′[i] for i in eachindex(q′)] # need vectors, want copy u,v,w,Θ,κ = NGCD.ngcd(ps, qs, args...; kwargs...) P′ = ⟒(typeof(p′)) - (u=P′(u, p.var), v=P′(v, p.var), w = P′(w, p.var), θ=Θ, κ=κ) + (u=P′(u, indeterminate(p)), v=P′(v, indeterminate(p)), w = P′(w, indeterminate(p)), θ=Θ, κ=κ) end diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 93ccf9c2..af253124 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -26,7 +26,7 @@ mapdomain(::Type{<:StandardBasisPolynomial}, x::AbstractArray) = x ## generic test if polynomial `p` is a constant isconstant(p::StandardBasisPolynomial) = degree(p) <= 0 -Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) = isa(q, P) ? q : P([q[i] for i in 0:degree(q)], var(q)) +Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) = isa(q, P) ? q : P([q[i] for i in 0:degree(q)], indeterminate(q)) Base.values(p::StandardBasisPolynomial) = values(p.coeffs) @@ -55,32 +55,35 @@ function Base.:+(p::P, c::S) where {T, P <: StandardBasisPolynomial{T}, S<:Numbe end -function derivative(p::P, order::Integer = 1) where {T, P <: StandardBasisPolynomial{T}} +function derivative(p::P, order::Integer = 1) where {T, X, P <: StandardBasisPolynomial{T, X}} order < 0 && error("Order of derivative must be non-negative") # we avoid usage like Base.promote_op(*, T, Int) here, say, as # Base.promote_op(*, Rational, Int) is Any, not Rational in analogy to # Base.promote_op(*, Complex, Int) R = eltype(one(T)*1) + Q = ⟒(P){R,X} + order == 0 && return p - hasnan(p) && return ⟒(P){R}(R[NaN], var(p)) - order > length(p) && return zero(⟒(P){R},var(p)) + hasnan(p) && return Q(R[NaN]) + order > length(p) && return zero(Q) d = degree(p) - d <= 0 && return zero(⟒(P){R},var(p)) + d <= 0 && return zero(Q) n = d + 1 a2 = Vector{R}(undef, n - order) @inbounds for i in order:n - 1 a2[i - order + 1] = reduce(*, (i - order + 1):i, init = p[i]) end - return _convert(p, a2) + Q(a2) end -function integrate(p::P, k::S) where {T, P <: StandardBasisPolynomial{T}, S<:Number} +function integrate(p::P, k::S) where {T, X, P <: StandardBasisPolynomial{T, X}, S<:Number} R = eltype((one(T)+one(S))/1) + Q = ⟒(P){R,X} if hasnan(p) || isnan(k) - return ⟒(P)([NaN]) + return ⟒(P){T,X}([NaN]) # XXX end n = length(p) a2 = Vector{R}(undef, n + 1) @@ -88,14 +91,14 @@ function integrate(p::P, k::S) where {T, P <: StandardBasisPolynomial{T}, S<:Num @inbounds for i in 1:n a2[i + 1] = p[i - 1] / i end - return _convert(p, a2) + return Q(a2) end function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} check_same_variable(num, den) || error("Polynomials must have same variable") - var = var(num) + X = indeterminate(num) n = degree(num) @@ -109,7 +112,7 @@ function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, deg = n - m + 1 if deg ≤ 0 - return zero(P, var), num + return zero(P), num end q_coeff = zeros(R, deg) @@ -284,7 +287,7 @@ function roots(p::P; kwargs...) where {T, P <: StandardBasisPolynomial{T}} k == K && return zeros(R, k-1) # find eigenvalues of the companion matrix - comp = companion(⟒(P)(as[k:K], var(p))) + comp = companion(⟒(P)(as[k:K], indeterminate(p))) L = eigvals(comp; kwargs...) append!(L, zeros(eltype(L), k-1)) @@ -410,7 +413,7 @@ function polyfitA(x, y, n=length(x)-1; var=:x) Q[:,k+1] .= q/H[k+1,k] end d = Q \ y - ArnoldiFit(d, H, var) + ArnoldiFit{eltype(d),typeof(H),Symbol(var)}(d, H) end function polyvalA(d, H::AbstractMatrix{S}, s::T) where {T, S} @@ -447,7 +450,7 @@ Base.show(io::IO, mimetype::MIME"text/plain", p::ArnoldiFit) = print(io, "Arnold fit(::Type{ArnoldiFit}, x::AbstractVector{T}, y::AbstractVector{T}, deg::Int=length(x)-1; var=:x, kwargs...) where{T} = polyfitA(x, y, deg; var=var) -Base.convert(::Type{P}, p::ArnoldiFit) where {P <: AbstractPolynomial} = p(variable(P,var(p))) +Base.convert(::Type{P}, p::ArnoldiFit) where {P <: AbstractPolynomial} = p(variable(P,indeterminate(p))) diff --git a/src/show.jl b/src/show.jl index 5921eb2c..d60e621b 100644 --- a/src/show.jl +++ b/src/show.jl @@ -93,7 +93,7 @@ end ### """ - printpoly(io::IO, p::AbstractPolynomial, mimetype = MIME"text/plain"(); descending_powers=false, offset::Int=0, var=var(p), compact=false, mulsymbol="*") + printpoly(io::IO, p::AbstractPolynomial, mimetype = MIME"text/plain"(); descending_powers=false, offset::Int=0, var=indeterminate(p), compact=false, mulsymbol="*") Print a human-readable representation of the polynomial `p` to `io`. The MIME types "text/plain" (default), "text/latex", and "text/html" are supported. By @@ -133,7 +133,7 @@ julia> printpoly(stdout, map(x -> round(x, digits=12), p)) # more control on ro ``` """ function printpoly(io::IO, p::P, mimetype=MIME"text/plain"(); - descending_powers=false, offset::Int=0, var=var(p), + descending_powers=false, offset::Int=0, var=indeterminate(p), compact=false, mulsymbol="*") where {T,P<:AbstractPolynomial{T}} first = true printed_anything = false diff --git a/test/ChebyshevT.jl b/test/ChebyshevT.jl index a12521bb..9acdae8e 100644 --- a/test/ChebyshevT.jl +++ b/test/ChebyshevT.jl @@ -8,7 +8,7 @@ @test p.coeffs == coeff @test coeffs(p) == coeff @test degree(p) == length(coeff) - 1 - @test p.var == :x + @test Polynomials.indeterminate(p) == :x @test length(p) == length(coeff) @test size(p) == size(coeff) @test size(p, 1) == size(coeff, 1) diff --git a/test/Poly.jl b/test/Poly.jl index 092135dd..fec8e46f 100644 --- a/test/Poly.jl +++ b/test/Poly.jl @@ -75,7 +75,7 @@ sprint(show, pNULL) @test pNULL^3 == pNULL @test pNULL*pNULL == pNULL -@test map(degree, [pNULL,p0,p1,p2,p3,p4,p5,pN,pR,p1000]) == [-1,-1,0,1,2,3,4,4,2,999] +@test map(degree, (pNULL,p0,p1,p2,p3,p4,p5,pN,pR,p1000)) == (-1,-1,0,1,2,3,4,4,2,999) @test polyval(poly(Int[]), 2.) == 1. @test polyval(pN, -.125) == 276.9609375 @@ -227,13 +227,13 @@ p1 = Poly([1, 2]) p2 = Poly([3, 1.]) p = [p1, p2] q = [3, p1] -@test isa(q,Vector{Poly{Int}}) +@test isa(q,Vector{Poly{Int,:x}}) psum = p .+ 3 pprod = p .* 3 pmin = p .- 3 -@test isa(psum, Vector{Poly{Float64}}) -@test isa(pprod,Vector{Poly{Float64}}) -@test isa(pmin, Vector{Poly{Float64}}) +@test isa(psum, Vector{Poly{Float64,:x}}) +@test isa(pprod,Vector{Poly{Float64,:x}}) +@test isa(pmin, Vector{Poly{Float64,:x}}) ## getindex with ranges #43 p1 = Poly([4,5,6]) @@ -344,9 +344,9 @@ pint = polyint(p, Complex(0.)) ## proper conversions in arithmetic with different element-types #94 p = Poly([0,one(Float64)]) -@test Poly{Complex{Float64}} == typeof(p+1im) -@test Poly{Complex{Float64}} == typeof(1im-p) -@test Poly{Complex{Float64}} == typeof(p*1im) +@test Poly{Complex{Float64},:x} == typeof(p+1im) +@test Poly{Complex{Float64},:x} == typeof(1im-p) +@test Poly{Complex{Float64},:x} == typeof(p*1im) ## comparison between `Number`s and `Poly`s p1s = Poly([1,2], :s) @@ -391,8 +391,8 @@ end # was to direct `collect{T}(p::Poly{T})` to `collect(Poly{T}, p)`. @test eltype(p1) == Int -@test eltype(collect(p1)) == Poly{Int} -@test eltype(collect(Poly{Float64}, p1)) == Poly{Float64} +@test eltype(collect(p1)) == Poly{Int,:x} +@test eltype(collect(Poly{Float64,:x}, p1)) == Poly{Float64,:x} @test_throws InexactError collect(Poly{Int}, Poly([1.2])) @test length(collect(p1)) == degree(p1)+1 @@ -450,8 +450,8 @@ end end @testset "`isintegral`" begin - x = Polynomial([1 // 1, Int8(2) + 0im, 3.0, Int16(4) + 0im]) - y = Polynomial([1 // 2, Int8(2) + 0im, 3.0, Int16(4) + 0im]) + x = Poly([1 // 1, Int8(2) + 0im, 3.0, Int16(4) + 0im]) + y = Poly([1 // 2, Int8(2) + 0im, 3.0, Int16(4) + 0im]) @test isintegral(x) === true @test isintegral(y) === false @test convert(Polynomial{Int}, x) == Polynomial([1, 2, 3, 4]) diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 537e409d..252fbf45 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -35,7 +35,7 @@ isimmutable(::Type{<:ImmutablePolynomial}) = true p = P(coeff) @test coeffs(p) ==ᵗ⁰ coeff @test degree(p) == length(coeff) - 1 - @test Polynomials.var(p) == :x + @test Polynomials.indeterminate(p) == :x P == Polynomial && @test length(p) == length(coeff) P == Polynomial && @test size(p) == size(coeff) P == Polynomial && @test size(p, 1) == size(coeff, 1) @@ -124,31 +124,37 @@ Base.getindex(z::ZVector, I::Int) = parent(z)[I + z.offset] @test Polynomials.isconstant(P(1)) @test !Polynomials.isconstant(variable(P)) end +end - @testset "OffsetVector" begin - as = ones(3:4) - bs = parent(as) - - # LaurentPolynomial accepts OffsetArrays; others do not and throw an ArgumentError - @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) - - for P in Ps - P == LaurentPolynomial && continue - @test_throws ArgumentError P(as) - @test P{eltype(as)}(as) == P{eltype(as)}(bs) - end +@testset "OffsetVector" begin + as = ones(3:4) + bs = parent(as) + + # LaurentPolynomial accepts OffsetArrays; others do not and throw an ArgumentError + @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) + + for P in Ps + P == LaurentPolynomial && continue + @test P(as) == P(bs) + @test P{eltype(as)}(as) == P{eltype(as)}(bs) +# P == LaurentPolynomial && continue # XXX move to this +# @test_throws ArgumentError P(as) +# @test P{eltype(as)}(as) == P{eltype(as)}(bs) + end - a = [1,1] - b = OffsetVector(a, axes(a)) - c = ZVector(a) - d = ZVector(b) - for P in Ps - if P == LaurentPolynomial && continue + a = [1,1] + b = OffsetVector(a, axes(a)) + c = ZVector(a) + d = ZVector(b) + for P in Ps + if P == LaurentPolynomial && continue @test P(a) == P(b) == P(c) == P(d) end - - @test ImmutablePolynomial{eltype(as), length(as)}(as) == ImmutablePolynomial(bs) + end + + @test ImmutablePolynomial{eltype(as)}(as) == ImmutablePolynomial(bs) + end @@ -639,8 +645,8 @@ end @test q isa Vector{typeof(p1)} @test p isa Vector{typeof(p2)} else - @test q isa Vector{P{eltype(p1)}} # ImmutablePolynomial{Int64,N} where {N}, different Ns - @test p isa Vector{P{eltype(p2)}} # ImmutablePolynomial{Int64,N} where {N}, different Ns + @test q isa Vector{P{eltype(p1),:x}} # ImmutablePolynomial{Int64,N} where {N}, different Ns + @test p isa Vector{P{eltype(p2),:x}} # ImmutablePolynomial{Int64,N} where {N}, different Ns end diff --git a/test/runtests.jl b/test/runtests.jl index c0880d7a..8df9fe87 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,4 +10,4 @@ using OffsetArrays @testset "Standard basis" begin include("StandardBasis.jl") end @testset "ChebyshevT" begin include("ChebyshevT.jl") end -#@testset "Poly, Pade (compatability)" begin include("Poly.jl") end +@testset "Poly, Pade (compatability)" begin include("Poly.jl") end From 409d49e349ac354d638eacc0643548a33844b911 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 9 Feb 2021 14:18:40 -0500 Subject: [PATCH 05/38] clean up --- src/abstract.jl | 2 +- src/polynomials/Polynomial.jl | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/abstract.jl b/src/abstract.jl index d65fdb59..57ad0256 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -18,7 +18,7 @@ abstract type AbstractPolynomial{T,X} end # convert `as` into polynomial of type P based on instance, inheriting variable # (and for LaurentPolynomial the offset) -_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P)(as, indeterminate(P)) +_convert(p::P, as) where {P <: AbstractPolynomial} = ⟒(P)(as, indeterminate(P)) """ Polynomials.@register(name) diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index c079099d..1ca031ae 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -71,6 +71,20 @@ julia> p.(0:3) (p::Polynomial{T})(x::S) where {T,S} = evalpoly(x, coeffs(p)) +# scalar _,* faster than standard-basis/common versions +function Base.:+(p::P, c::S) where {T, X, P <: Polynomial{T, X}, S<:Number} + R = promote_type(T, S) + as = convert(Vector{R}, copy(coeffs(p))) + as[1] += c + return Polynomial{R, X}(as) +end + +function Base.:*(p::P, c::S) where {T, X, P <: Polynomial{T,X} , S <: Number} + as = [aᵢ * c for aᵢ ∈ coeffs(p)] + Polynomial{promote_type(T,S),X}(as) +end + + function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} n1, n2 = length(p1), length(p2) From 2f91ef34b49bfc0524d04185e175066673fe0b1d Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 9 Feb 2021 18:59:40 -0500 Subject: [PATCH 06/38] change what iteration means for a polynomial --- src/common.jl | 87 +++++++++++++++++++++++++---- src/polynomials/Poly.jl | 14 +++++ src/polynomials/SparsePolynomial.jl | 14 +++++ src/polynomials/standard-basis.jl | 2 +- test/StandardBasis.jl | 39 +++++++++++-- 5 files changed, 139 insertions(+), 17 deletions(-) diff --git a/src/common.jl b/src/common.jl index bfed4d51..662dce78 100644 --- a/src/common.jl +++ b/src/common.jl @@ -286,6 +286,7 @@ Calculates the p-norm of the polynomial's coefficients """ function LinearAlgebra.norm(q::AbstractPolynomial, p::Real = 2) vs = values(q) + isempty(vs) && return zero(eltype(q)) return norm(vs, p) # if vs=() must be handled in special type end @@ -460,18 +461,6 @@ Base.firstindex(p::AbstractPolynomial) = 0 Base.lastindex(p::AbstractPolynomial) = length(p) - 1 Base.eachindex(p::AbstractPolynomial) = 0:length(p) - 1 Base.broadcastable(p::AbstractPolynomial) = Ref(p) -# like coeffs, though possibly only non-zero values (e.g. SparsePolynomial) -Base.values(p::AbstractPolynomial) = coeffs(p) - -# iteration -# iteration occurs over the basis polynomials -Base.iterate(p::AbstractPolynomial) = (p[0] * one(typeof(p)), 1) -function Base.iterate(p::AbstractPolynomial, state) - state <= length(p) - 1 ? (p[state] * basis(p, state), state + 1) : nothing -end - - -Base.collect(p::P) where {P <: AbstractPolynomial} = collect(P, p) # getindex function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T <: Number} @@ -505,6 +494,80 @@ Base.setindex!(p::AbstractPolynomial, value::Number, ::Colon) = Base.setindex!(p::AbstractPolynomial, values, ::Colon) = [setindex!(p, v, i) for (v, i) in zip(values, eachindex(p))] +#= +Iteration =# +## XXX breaking change in v2.0.0 +# +# we want to keep iteration close to iteration over the coefficients as a vector: +# `iterate` iterates over coefficients, includes 0s +# `collect(T, p)` yields coefficients of `p` converted to type `T` +# `keys(p)` an iterator spanning `firstindex` to `lastindex` which *may* skip 0 terms (SparsePolynomial) +# and *may* not be in order (SparsePolynomial) +# `values(p)` `pᵢ` for each `i` in `keys(p)` +# `pairs(p)`: `i => pᵢ` possibly skipping over values of `i` with `pᵢ == 0` (SparsePolynomial) +# and possibly non ordered (SparsePolynomial) +# `monomials(p)`: iterates over pᵢ ⋅ basis(p, i) i ∈ keys(p) +function Base.iterate(p::AbstractPolynomial, state=nothing) + i = firstindex(p) + if state == nothing + return (p[i], i) + else + j = lastindex(p) + if i <= state < j + return (p[state+1], state+1) + end + return nothing + end +end + +# pairs map i -> aᵢ *possibly* skipping over ai == 0 +# cf. abstractdict.jl +struct PolynomialKeys{P} + p::P +end +struct PolynomialValues{P} + p::P +end +Base.keys(p::AbstractPolynomial) = PolynomialKeys(p) +Base.values(p::AbstractPolynomial) = PolynomialValues(p) +Base.length(p::PolynomialValues) = length(p.p.coeffs) +Base.length(p::PolynomialKeys) = length(p.p.coeffs) +function Base.iterate(v::PolynomialKeys, state=nothing) + i = firstindex(v.p) + state==nothing && return (i, i) + j = lastindex(v.p) + i <= state < j && return (state+1, state+1) + return nothing +end + +function Base.iterate(v::PolynomialValues, state=nothing) + i = firstindex(v.p) + state==nothing && return (v.p[i], i) + j = lastindex(v.p) + i <= state < j && return (v.p[state+1], state+1) + return nothing +end + + +# iterate over monomials of the polynomial +struct Monomials{P} + p::P +end +""" + monomials(p::AbstractPolynomial) + +Returns an iterator over the terms, `pᵢ⋅basis(p,i)`, of the polynomial for each `i` in `keys(p)`. +""" +monomials(p) = Monomials(p) +function Base.iterate(v::Monomials, state...) + y = iterate(pairs(v.p), state...) + y == nothing && return nothing + kv, s = y + return (kv[2]*basis(v.p, kv[1]), s) +end +Base.length(v::Monomials) = length(keys(v.p)) + + #= identity =# Base.copy(p::P) where {P <: AbstractPolynomial} = _convert(p, copy(coeffs(p))) diff --git a/src/polynomials/Poly.jl b/src/polynomials/Poly.jl index e87710d3..0737a569 100644 --- a/src/polynomials/Poly.jl +++ b/src/polynomials/Poly.jl @@ -44,6 +44,20 @@ Base.convert(P::Type{<:Poly}, p::Poly{T,X}) where {T<:Number,X} = Polynomials.co Base.eltype(P::Type{<:Poly{T,X}}) where {T, X} = P _eltype(::Type{<:Poly{T}}) where {T} = T _eltype(::Type{Poly}) = Float64 + +# when interating over poly return monomials +function Base.iterate(p::Poly, state=nothing) + i = 0 + state == nothing && return (p[i]*one(p), i) + j = degree(p) + s = state + 1 + i <= state < j && return (p[s]*Polynomials.basis(p,s), s) + return nothing +end +Base.collect(p::Poly) = [pᵢ for pᵢ ∈ p] + + + Base.zero(P::Type{<:Poly},var=:x) = Poly(zeros(_eltype(P),0), var) Base.one(P::Type{<:Poly},var=:x) = Poly(ones(_eltype(P),1), var) function Polynomials.basis(P::Type{<:Poly}, k::Int, _var::Polynomials.SymbolLike=:x; var=_var) diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index a36541d6..90d3ae3b 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -135,6 +135,20 @@ Base.firstindex(p::SparsePolynomial) = sort(collect(keys(p.coeffs)), by=x->x[1]) Base.lastindex(p::SparsePolynomial) = sort(collect(keys(p.coeffs)), by=x->x[1])[end] Base.eachindex(p::SparsePolynomial) = sort(collect(keys(p.coeffs)), by=x->x[1]) +# pairs iterates only over non-zero +# inherits order for underlying dictionary +function Base.iterate(v::PolynomialKeys{SparsePolynomial{T,X}}, state...) where {T,X} + y = iterate(v.p.coeffs, state...) + y == nothing && return nothing + return (y[1][1], y[2]) +end +function Base.iterate(v::PolynomialValues{SparsePolynomial{T,X}}, state...) where {T,X} + y = iterate(v.p.coeffs, state...) + y == nothing && return nothing + return (y[1][2], y[2]) +end + + # only from tail function chop!(p::SparsePolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index af253124..fa4f45fa 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -28,7 +28,7 @@ isconstant(p::StandardBasisPolynomial) = degree(p) <= 0 Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) = isa(q, P) ? q : P([q[i] for i in 0:degree(q)], indeterminate(q)) -Base.values(p::StandardBasisPolynomial) = values(p.coeffs) +#Base.values(p::StandardBasisPolynomial) = values(p.coeffs) variable(::Type{P}, var::SymbolLike = :x) where {P <: StandardBasisPolynomial} = P([0, 1], var) diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 252fbf45..10efbb4c 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -780,15 +780,15 @@ end p1 = P([1,2,0,3]) for term in p1 - @test isa(term, P) + @test isa(term, eltype(p1)) end @test eltype(p1) == Int for P in Ps p1 = P([1,2,0,3]) - @test eltype(collect(p1)) <: P{Int} - @test eltype(collect(P{Float64}, p1)) <: P{Float64} - @test_throws InexactError collect(P{Int}, P([1.2])) + @test eltype(collect(p1)) <: Int + @test eltype(collect(Float64, p1)) <: Float64 + @test_throws InexactError collect(Int, P([1.2])) end p1 = P([1,2,0,3]) @@ -798,6 +798,37 @@ end end end +@testset "Iteration" begin + p, ip, lp, sp = ps = (Polynomial([1,2,0,4]), ImmutablePolynomial((1,2,0,4)), + LaurentPolynomial([1,2,0,4], -2), SparsePolynomial(Dict(0=>1, 1=>2, 3=>4))) + for pp ∈ ps + # iteration + @test all(collect(pp) .== coeffs(pp)) + + # keys, values, pairs + ks, vs, kvs = keys(pp), values(pp), pairs(pp) + if !isa(pp, SparsePolynomial) + @test first(ks) == firstindex(pp) + @test first(vs) == pp[first(ks)] + @test length(vs) == length(coeffs(pp)) + @test first(kvs) == (first(ks) => first(vs)) + else + @test first(sort(collect(ks))) == firstindex(pp) + @test length(vs) == length(pp.coeffs) + end + + ## monomials + if !isa(pp, SparsePolynomial) + i = firstindex(pp) + @test first(Polynomials.monomials(pp)) == pp[i] * Polynomials.basis(pp,i) + else + @test first(Polynomials.monomials(pp)) ∈ [pp[i] * Polynomials.basis(pp,i) for i ∈ keys(pp)] + end + end + +end + + @testset "Copying" begin for P in Ps pcpy1 = P([1,2,3,4,5], :y) From 5a84d9f6a124ce79232c0601e73ff1c12ff5c3be Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 9 Feb 2021 21:40:17 -0500 Subject: [PATCH 07/38] adjust integrate to return polynomial of same type, even if NaN --- src/polynomials/ChebyshevT.jl | 9 +++++---- src/polynomials/LaurentPolynomial.jl | 2 +- src/polynomials/Poly.jl | 16 ++++++++++++++++ src/polynomials/SparsePolynomial.jl | 6 +++--- src/polynomials/standard-basis.jl | 4 +++- test/StandardBasis.jl | 5 +++-- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 039f3607..0c4dd909 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -119,14 +119,15 @@ function vander(P::Type{<:ChebyshevT}, x::AbstractVector{T}, n::Integer) where { return A end -function integrate(p::ChebyshevT{T}, C::S) where {T,S <: Number} +function integrate(p::ChebyshevT{T,X}, C::S) where {T,X,S <: Number} R = promote_type(eltype(one(T) / 1), S) + Q = ChebyshevT{R,X} if hasnan(p) || isnan(C) - return ChebyshevT([NaN]) + return Q([NaN]) end n = length(p) if n == 1 - return ChebyshevT{R}([C, p[0]]) + return Q([C, p[0]]) end a2 = Vector{R}(undef, n + 1) a2[1] = zero(R) @@ -137,7 +138,7 @@ function integrate(p::ChebyshevT{T}, C::S) where {T,S <: Number} a2[i] -= p[i] / (2 * (i - 1)) end a2[1] += R(C) - ChebyshevT(a2)(0) - return ChebyshevT(a2, indeterminate(p)) + return Q(a2) end diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index a695cf22..acd8d618 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -577,7 +577,7 @@ function integrate(p::P, k::S) where {T, X, P<: LaurentPolynomial{T, X}, S<:Numb Q = ⟒(P){R, X} if hasnan(p) || isnan(k) - return P([NaN], 0) # not Q([NaN])!! don't like XXX + return Q([NaN],0) end diff --git a/src/polynomials/Poly.jl b/src/polynomials/Poly.jl index 0737a569..8cb9611e 100644 --- a/src/polynomials/Poly.jl +++ b/src/polynomials/Poly.jl @@ -114,6 +114,22 @@ function Base.getproperty(p::Poly, nm::Symbol) return getfield(p, nm) end +function Polynomials.integrate(p::P, k::S) where {T, X, P <: Poly{T, X}, S<:Number} + + R = eltype((one(T)+one(S))/1) + Q = Poly{R,X} + if hasnan(p) || isnan(k) + return P([NaN]) # keep for Poly, not Q + end + n = length(p) + a2 = Vector{R}(undef, n + 1) + a2[1] = k + @inbounds for i in 1:n + a2[i + 1] = p[i - 1] / i + end + return Q(a2) +end + polyint(p::Poly, C = 0) = integrate(p, C) polyint(p::Poly, a, b) = integrate(p, a, b) polyint(p::AbstractPolynomial, args...) = error("`polyint` is a legacy name for use with `Poly` objects only. Use `integrate(p,...)`.") diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 90d3ae3b..5dafa8b3 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -313,14 +313,14 @@ function integrate(p::P, k::S) where {T, X, P<:SparsePolynomial{T,X}, S<:Number} Q = SparsePolynomial{R,X} if hasnan(p) || isnan(k) - return P(Dict(0 => NaN)) # not Q(NaN)!! don't like XXX + return Q(Dict(0 => NaN)) end ∫p = Q(R(k)) for k in eachindex(p) ∫p[k + 1] = p[k] / (k+1) end - + return ∫p - + end diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index fa4f45fa..e4e663db 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -82,9 +82,11 @@ function integrate(p::P, k::S) where {T, X, P <: StandardBasisPolynomial{T, X}, R = eltype((one(T)+one(S))/1) Q = ⟒(P){R,X} + if hasnan(p) || isnan(k) - return ⟒(P){T,X}([NaN]) # XXX + return Q([NaN]) end + n = length(p) a2 = Vector{R}(undef, n + 1) a2[1] = k diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 10efbb4c..d803d714 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -627,8 +627,9 @@ end @test isequal(pder, P([NaN])) @test isequal(pint, P([NaN])) - pint = integrate(p, 0.0im) - @test isequal(pint, P([NaN])) + c = 0.0im + pint = integrate(p, c) + @test isequal(pint, P{promote_type(eltype(p), typeof(c)), :x}([NaN])) # Issue with overflow and polyder Issue #159 @test derivative(P(BigInt[0, 1])^100, 100) == P(factorial(big(100))) From a2fc2338869d4d509a12a60b3f0bcb7d81c1a720 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 11 Feb 2021 07:43:06 -0500 Subject: [PATCH 08/38] clean up offset vector tests --- test/StandardBasis.jl | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index d803d714..e01ae58f 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -130,16 +130,18 @@ end as = ones(3:4) bs = parent(as) - # LaurentPolynomial accepts OffsetArrays; others do not and throw an ArgumentError - @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) for P in Ps - P == LaurentPolynomial && continue - @test P(as) == P(bs) - @test P{eltype(as)}(as) == P{eltype(as)}(bs) -# P == LaurentPolynomial && continue # XXX move to this -# @test_throws ArgumentError P(as) -# @test P{eltype(as)}(as) == P{eltype(as)}(bs) + # LaurentPolynomial accepts OffsetArrays; others throw warning + if P == LaurentPolynomial + @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) + else + @test P(as) == P(bs) + @test P{eltype(as)}(as) == P{eltype(as)}(bs) + # (Or throw an error?) + # @test_throws ArgumentError P(as) + # @test P{eltype(as)}(as) == P{eltype(as)}(bs) + end end a = [1,1] @@ -152,9 +154,6 @@ end end end - - @test ImmutablePolynomial{eltype(as)}(as) == ImmutablePolynomial(bs) - end From 5a47053370aec918dbbd850890758cb189b73bb6 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sun, 14 Feb 2021 17:10:59 -0500 Subject: [PATCH 09/38] drop OffsetArrays dependency; clean up scalar operations for ImmutablePolynomial --- Project.toml | 6 +- src/Polynomials.jl | 1 - src/polynomials/ChebyshevT.jl | 13 ++-- src/polynomials/ImmutablePolynomial.jl | 18 +++--- src/polynomials/Polynomial.jl | 84 +++++++++++++------------- src/polynomials/SparsePolynomial.jl | 11 ++-- test/Project.toml | 7 +++ 7 files changed, 71 insertions(+), 69 deletions(-) create mode 100644 test/Project.toml diff --git a/Project.toml b/Project.toml index 59b6dfc4..c1cdfe42 100644 --- a/Project.toml +++ b/Project.toml @@ -7,20 +7,20 @@ version = "1.2.0" [deps] Intervals = "d8418881-c3e1-53bb-8760-2df7ec849ed5" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + [compat] Intervals = "0.5, 1.0, 1.3" -OffsetArrays = "1" RecipesBase = "0.7, 0.8, 1" julia = "1" [extras] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["LinearAlgebra", "SparseArrays", "SpecialFunctions", "Test"] +test = ["LinearAlgebra", "SparseArrays", "OffsetArrays", "SpecialFunctions", "Test"] diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 1eb39934..6f05ec20 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -3,7 +3,6 @@ module Polynomials # using GenericLinearAlgebra ## remove for now. cf: https://github.com/JuliaLinearAlgebra/GenericLinearAlgebra.jl/pull/71#issuecomment-743928205 using LinearAlgebra using Intervals -using OffsetArrays include("abstract.jl") include("show.jl") diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 0c4dd909..7687a57f 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -30,16 +30,15 @@ ChebyshevT(1.0⋅T_0(x)) struct ChebyshevT{T <: Number, X} <: AbstractPolynomial{T, X} coeffs::Vector{T} function ChebyshevT{T, X}(coeffs::AbstractVector{S}) where {T <: Number,X, S} - length(coeffs) == 0 && return new{T,X}(zeros(T, 1)) + if Base.has_offset_axes(coeffs) - # throw(ArgumentError("The `ChebyshevT` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) - @warn "ignoring the axis offset of the coefficient vector" - coeffs = OffsetArrays.no_offset_view(coeffs) end - last_nz = findlast(!iszero, coeffs) - last = max(1, last_nz === nothing ? 0 : last_nz) - return new{T,X}(convert(Vector{T}, coeffs[1:last])) + + N = findlast(!iszero, coeffs) + isnothing(N) && return new{T,X}(zeros(T,1)) + cs = T[coeffs[i] for i ∈ firstindex(coeffs):N] + new{T,X}(cs) end end diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index 901dd952..de90543e 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -63,20 +63,19 @@ function ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}) where {T,X,S} R = promote_type(T,S) if Base.has_offset_axes(coeffs) - # throw(ArgumentError("The `ImmutablePolynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) @warn "ignoring the axis offset of the coefficient vector" - coeffs = OffsetArrays.no_offset_view(coeffs) end - #c = OffsetArrays.no_offset_view(coeffs) N = findlast(!iszero, coeffs) - N == nothing && return zero(ImmutablePolynomial{R,X}) - ImmutablePolynomial{T, X, N}(NTuple{N,T}(cᵢ for cᵢ ∈ coeffs)) + isnothing(N) && return ImmutablePolynomial{R,X,0}(()) + N′ = N + 1 - firstindex(coeffs) + cs = NTuple{N′,T}(coeffs[i] for i ∈ firstindex(coeffs):N) + ImmutablePolynomial{T, X, N′}(cs) end ## -- Tuple arguments function ImmutablePolynomial{T,X}(coeffs::Tuple) where {T,X} N = findlast(!iszero, coeffs) - N == nothing && return zero(ImmutablePolynomial{T,X}) + isnothing(N) && return zero(ImmutablePolynomial{T,X}) ImmutablePolynomial{T,X,N}(NTuple{N,T}(coeffs[i] for i in 1:N)) end @@ -276,14 +275,15 @@ end function Base.:*(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X,N, S <: Number} R = eltype(one(T)*one(S)) - iszero(c) && return zero(ImmutablePolynomial{R,X}) + iszero(p[end]*c) && return ImmutablePolynomial{R,X}(p.coeffs .* c) ImmutablePolynomial{R,X,N}(p.coeffs .* c) end function Base.:/(p::ImmutablePolynomial{T,X,N}, c::S) where {T,X,N,S <: Number} R = eltype(one(T)/one(S)) - isinf(c) && return zero(ImmutablePolynomial{R,X}) - ImmutablePolynomial{R,X,N}(p.coeffs ./ c) + cs = p.coeffs ./ c + iszero(cs[end]) && return ImmutablePolynomial{R,X}(cs) + ImmutablePolynomial{R,X,N}(cs) end Base.:-(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = ImmutablePolynomial{T,X,N}(.-p.coeffs) diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 1ca031ae..0cb37a0a 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -34,14 +34,16 @@ struct Polynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} coeffs::Vector{T} function Polynomial{T, X}(coeffs::AbstractVector{S}) where {T <: Number, X, S} if Base.has_offset_axes(coeffs) - #throw(ArgumentError("The `Polynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) @warn "ignoring the axis offset of the coefficient vector" - coeffs = OffsetArrays.no_offset_view(coeffs) end - length(coeffs) == 0 && return new{T,X}(zeros(T, 1)) - last_nz = findlast(!iszero, coeffs) - last = max(1, last_nz === nothing ? 0 : last_nz) - return new{T, X}(convert(Vector{T}, coeffs[1:last])) + N = findlast(!iszero, coeffs) + isnothing(N) && return new{T,X}(zeros(T,1)) + cs = T[coeffs[i] for i ∈ firstindex(coeffs):N] + new{T,X}(cs) + end + # non-copying alternative assuming !iszero(coeffs[end]) + function Polynomial{T, X}(checked::Val{false}, coeffs::AbstractVector{T}) where {T <: Number, X} + new{T, X}(coeffs) end end @@ -73,68 +75,64 @@ julia> p.(0:3) # scalar _,* faster than standard-basis/common versions function Base.:+(p::P, c::S) where {T, X, P <: Polynomial{T, X}, S<:Number} - R = promote_type(T, S) - as = convert(Vector{R}, copy(coeffs(p))) - as[1] += c - return Polynomial{R, X}(as) + R = promote_type(T, S) + Q = Polynomial{R,X} + as = convert(Vector{R}, copy(coeffs(p))) + as[1] += c + iszero(as[end]) ? Q(as) : Q(Val(false), as) end function Base.:*(p::P, c::S) where {T, X, P <: Polynomial{T,X} , S <: Number} - as = [aᵢ * c for aᵢ ∈ coeffs(p)] - Polynomial{promote_type(T,S),X}(as) + R = promote_type(T,S) + Q = Polynomial{R, X} + as = R[aᵢ * c for aᵢ ∈ coeffs(p)] + iszero(as[end]) ? Q(as) : Q(Val(false), as) end - - function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} + isconstant(p1) && return p2 + p1[0] + isconstant(p2) && return p1 + p2[0] + X, Y = indeterminate(p1), indeterminate(p2) + X != Y && error("Polynomials must have same variable") n1, n2 = length(p1), length(p2) - if n1 > 1 && n2 > 1 - indeterminate(p1) != indeterminate(p2) && error("Polynomials must have same variable") - end R = promote_type(T,S) + c = zeros(R, max(n1, n2)) - if n1 > 1 && n2 > 1 - if n1 >= n2 - c .= p1.coeffs - for i = eachindex(p2.coeffs) + if n1 >= n2 + c .= p1.coeffs + for i in eachindex(p2.coeffs) c[i] += p2.coeffs[i] - end - else - c .= p2.coeffs - for i = eachindex(p1.coeffs) - c[i] += p1.coeffs[i] - end end - return Polynomial{R, indeterminate(p1)}(c) - elseif n1 <= 1 - c .= p2.coeffs - c[1] += p1[0] - return Polynomial{R, indeterminate(p2)}(c) - else - c .= p1.coeffs - c[1] += p2[0] - return Polynomial{R, indeterminate(p1)}(c) + else + c .= p2.coeffs + for i in eachindex(p1.coeffs) + c[i] += p1.coeffs[i] + end end + + Q = Polynomial{R,X} + return iszero(c[end]) ? Q(c) : Q(Val(false), c) + end function Base.:*(p1::Polynomial{T}, p2::Polynomial{S}) where {T,S} n, m = length(p1)-1, length(p2)-1 # not degree, so pNULL works + X, Y = indeterminate(p1), indeterminate(p2) + R = promote_type(T, S) if n > 0 && m > 0 - indeterminate(p1) != indeterminate(p2) && error("Polynomials must have same variable") - R = promote_type(T, S) + X != Y && error("Polynomials must have same variable") c = zeros(R, m + n + 1) for i in 0:n, j in 0:m @inbounds c[i + j + 1] += p1[i] * p2[j] end - return Polynomial{R, indeterminate(p1)}(c) + Q = Polynomial{R,X} + return iszero(c[end]) ? Q(c) : Q(Val(false), c) elseif n <= 0 - cs = p2.coeffs * p1[0] - return Polynomial{eltype(cs), indeterminate(p2)}(cs) + return Polynomial{R, Y}(p2.coeffs * p1[0]) else - cs = p1.coeffs * p2[0] - return Polynomial{eltype(cs), indeterminate(p1)}(cs) + return Polynomial{R, X}(p1.coeffs * p2[0]) end end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 5dafa8b3..52f807be 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -48,6 +48,9 @@ struct SparsePolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} end new{T, X}(c) end + function SparsePolynomial{T,X}(checked::Val{false}, coeffs::AbstractDict{Int, T}) where {T <: Number, X} + new{T,X}(convert(Dict{Int,S}, coeffs)) + end end @register SparsePolynomial @@ -61,17 +64,13 @@ function SparsePolynomial(coeffs::AbstractDict{Int, T}, var::SymbolLike=:x) wher end function SparsePolynomial{T,X}(coeffs::AbstractVector{S}) where {T <: Number, X, S} - firstindex(coeffs) >= 0 || throw(ArgumentError("Use the `LaurentPolynomial` type for arrays with a negative first index")) if Base.has_offset_axes(coeffs) - # throw(ArgumentError("The `SparsePolynomial` constructor does not accept `OffsetArrays`. Try `LaurentPolynomial`.")) - @warn "ignoring the axis offset of the coefficient vector" - coeffs = OffsetArrays.no_offset_view(coeffs) end - c = OffsetArrays.no_offset_view(coeffs) # ensure 1-based indexing - p = Dict{Int,T}(i - 1 => v for (i,v) in pairs(c)) + offset = firstindex(coeffs) + p = Dict{Int,T}(k - offset => v for (k,v) ∈ pairs(coeffs)) return SparsePolynomial{T,X}(p) end diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 00000000..461365c3 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,7 @@ +[deps] +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From 7a350ca093df639b08029c04ccf2ef22be65765a Mon Sep 17 00:00:00 2001 From: jverzani Date: Sun, 14 Feb 2021 17:55:43 -0500 Subject: [PATCH 10/38] cleanup --- Project.toml | 2 +- src/polynomials/ImmutablePolynomial.jl | 5 ++--- src/polynomials/Polynomial.jl | 4 ++-- src/polynomials/SparsePolynomial.jl | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index c1cdfe42..38791059 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Polynomials" uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" license = "MIT" author = "JuliaMath" -version = "1.2.0" +version = "2.0.0" [deps] Intervals = "d8418881-c3e1-53bb-8760-2df7ec849ed5" diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index de90543e..f4e8ec4f 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -162,8 +162,8 @@ function Base.truncate(p::ImmutablePolynomial{T,X,N}; end # no in-place chop! and truncate! -chop!(p::ImmutablePolynomial; kwargs...) = chop(p; kwargs...) -truncate!(p::ImmutablePolynomial; kwargs...) = truncate(p; kwargs...) +chop!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `chop!` for the `ImmutablePolynomial` type. Use `chop`?")) +truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate!` for the `ImmutablePolynomial` type. Use `trunctate`?")) ## ## -------------------- @@ -197,7 +197,6 @@ function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) end -# not type stable!!! function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) where {T,X,N,S,Y,M} isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 0cb37a0a..2b34d9bf 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -73,7 +73,7 @@ julia> p.(0:3) (p::Polynomial{T})(x::S) where {T,S} = evalpoly(x, coeffs(p)) -# scalar _,* faster than standard-basis/common versions +# scalar +,* faster than standard-basis/common versions function Base.:+(p::P, c::S) where {T, X, P <: Polynomial{T, X}, S<:Number} R = promote_type(T, S) Q = Polynomial{R,X} @@ -107,7 +107,7 @@ function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} c .= p2.coeffs for i in eachindex(p1.coeffs) c[i] += p1.coeffs[i] - end + end end Q = Polynomial{R,X} diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 52f807be..6e85d6b9 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -103,7 +103,7 @@ end # return coeffs as a vector # use p.coeffs to get Dictionary -function coeffs(p::SparsePolynomial{T}) where {T} +function coeffs(p::SparsePolynomial{T}) where {T} n = degree(p) cs = zeros(T, n+1) From 166522b232f43573819888dc2d2f1c04089afbd6 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sun, 14 Feb 2021 18:27:59 -0500 Subject: [PATCH 11/38] change error into ArgumentError --- src/common.jl | 2 +- src/polynomials/ChebyshevT.jl | 10 +++++----- src/polynomials/ImmutablePolynomial.jl | 4 ++-- src/polynomials/LaurentPolynomial.jl | 6 +++--- src/polynomials/Polynomial.jl | 4 ++-- src/polynomials/SparsePolynomial.jl | 6 +++--- src/polynomials/standard-basis.jl | 6 +++--- test/ChebyshevT.jl | 4 ++-- test/Poly.jl | 8 ++++---- test/StandardBasis.jl | 16 ++++++++-------- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/common.jl b/src/common.jl index 662dce78..2b6d21fd 100644 --- a/src/common.jl +++ b/src/common.jl @@ -759,7 +759,7 @@ function Base.isapprox(p1::AbstractPolynomial{T,X}, rtol::Real = (Base.rtoldefault(T, S, 0)), atol::Real = 0,) where {T,X,S,Y} # p1, p2 = promote(p1, p2) - check_same_variable(p1, p2) || error("p1 and p2 must have same var") + check_same_variable(p1, p2) || throw(ArgumentError("p1 and p2 must have same var")) # copy over from abstractarray.jl Δ = norm(p1-p2) if isfinite(Δ) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 7687a57f..5b8aac67 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -94,7 +94,7 @@ julia> c.(-1:0.5:1) ``` """ function (ch::ChebyshevT{T})(x::S) where {T,S} - x ∉ domain(ch) && error("$x outside of domain") + x ∉ domain(ch) && throw(ArgumentError("$x outside of domain")) R = promote_type(T, S) length(ch) == 0 && return zero(R) length(ch) == 1 && return R(ch[0]) @@ -169,7 +169,7 @@ end function companion(p::ChebyshevT{T}) where T d = length(p) - 1 - d < 1 && error("Series must have degree greater than 1") + d < 1 && throw(ArgumentError("Series must have degree greater than 1")) d == 1 && return diagm(0 => [-p[0] / p[1]]) R = eltype(one(T) / one(T)) @@ -185,7 +185,7 @@ end function Base.:+(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} X′ = isconstant(p2) ? X : Y - (!isconstant(p1) && !isconstant(p2)) && X != Y && error("Polynomials must have same variable") + (!isconstant(p1) && !isconstant(p2)) && X != Y && throw(ArgumentError("Polynomials must have same variable")) n = max(length(p1), length(p2)) R = promote_type(T,S) c = R[p1[i] + p2[i] for i = 0:n] @@ -195,7 +195,7 @@ end function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} X′ = isconstant(p2) ? X : Y - (!isconstant(p1) && !isconstant(p2)) && X != Y && error("Polynomials must have same variable") + (!isconstant(p1) && !isconstant(p2)) && X != Y && throw(ArgumentError("Polynomials must have same variable")) z1 = _c_to_z(p1.coeffs) z2 = _c_to_z(p2.coeffs) prod = fastconv(z1, z2) @@ -205,7 +205,7 @@ function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} end function Base.divrem(num::ChebyshevT{T,X}, den::ChebyshevT{S,Y}) where {T,X,S,Y} - X != Y && error("Polynomials must have same variable") + X != Y && throw(ArgumentError("Polynomials must have same variable")) n = length(num) - 1 m = length(den) - 1 diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index f4e8ec4f..7758d850 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -181,7 +181,7 @@ function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) if X != Y isconstant(p1) && return ImmutablePolynomial{T,Y,1}(p1.coeffs) + p2 isconstant(p2) && return p1 + ImmutablePolynomial{S,X,1}(p2.coeffs) - error("Polynomials must have same variable") + throw(ArgumentError("Polynomials must have same variable")) end if N == M @@ -200,7 +200,7 @@ end function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) where {T,X,N,S,Y,M} isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - X != Y && error("Polynomials must have same variable") + X != Y && throw(ArgumentError("Polynomials must have same variable")) R = promote_type(S,T) cs = (p1.coeffs) ⊗ (p2.coeffs) if !iszero(cs[end]) diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index acd8d618..766754bf 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -457,7 +457,7 @@ function Base.:+(p1::P1, p2::P2) where {T,X,P1<:LaurentPolynomial{T,X}, S,Y, P2< return q1 end - X != Y && error("LaurentPolynomials must have same variable") + X != Y && throw(ArgumentError("LaurentPolynomials must have same variable")) m1,n1 = (extrema ∘ degreerange)(p1) @@ -481,7 +481,7 @@ function Base.:*(p1::LaurentPolynomial{T,X}, p2::LaurentPolynomial{S,Y}) where { isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - X != Y && error("LaurentPolynomials must have same variable") + X != Y && throw(ArgumentError("LaurentPolynomials must have same variable")) R = promote_type(T,S) @@ -546,7 +546,7 @@ end ## function derivative(p::P, order::Integer = 1) where {T, X, P<:LaurentPolynomial{T,X}} - order < 0 && error("Order of derivative must be non-negative") + order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) order == 0 && return p hasnan(p) && return ⟒(P)(T[NaN], 0, X) diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 2b34d9bf..96324311 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -93,7 +93,7 @@ function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} isconstant(p1) && return p2 + p1[0] isconstant(p2) && return p1 + p2[0] X, Y = indeterminate(p1), indeterminate(p2) - X != Y && error("Polynomials must have same variable") + X != Y && throw(ArgumentError("Polynomials must have same variable")) n1, n2 = length(p1), length(p2) R = promote_type(T,S) @@ -122,7 +122,7 @@ function Base.:*(p1::Polynomial{T}, p2::Polynomial{S}) where {T,S} X, Y = indeterminate(p1), indeterminate(p2) R = promote_type(T, S) if n > 0 && m > 0 - X != Y && error("Polynomials must have same variable") + X != Y && throw(ArgumentError("Polynomials must have same variable")) c = zeros(R, m + n + 1) for i in 0:n, j in 0:m @inbounds c[i + j + 1] += p1[i] * p2[j] diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 6e85d6b9..a9977238 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -212,7 +212,7 @@ function Base.:+(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T, isconstant(p1) && return p2 + p1[0] isconstant(p2) && return p1 + p2[0] - X != Y && error("SparsePolynomials must have same variable") + X != Y && throw(ArgumentError("SparsePolynomials must have same variable")) R = promote_type(T,S) p = zero(SparsePolynomial{R,X}) @@ -253,7 +253,7 @@ function Base.:*(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T, isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - X != Y && error("SparsePolynomials must have same variable") + X != Y && throw(ArgumentError("SparsePolynomials must have same variable")) R = promote_type(T,S) P = SparsePolynomial @@ -286,7 +286,7 @@ end function derivative(p::SparsePolynomial{T,X}, order::Integer = 1) where {T,X} - order < 0 && error("Order of derivative must be non-negative") + order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) order == 0 && return p R = eltype(one(T)*1) diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index e4e663db..b3852707 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -56,7 +56,7 @@ end function derivative(p::P, order::Integer = 1) where {T, X, P <: StandardBasisPolynomial{T, X}} - order < 0 && error("Order of derivative must be non-negative") + order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) # we avoid usage like Base.promote_op(*, T, Int) here, say, as # Base.promote_op(*, Rational, Int) is Any, not Rational in analogy to @@ -99,7 +99,7 @@ end function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} - check_same_variable(num, den) || error("Polynomials must have same variable") + check_same_variable(num, den) || throw(ArgumentError("Polynomials must have same variable")) X = indeterminate(num) @@ -256,7 +256,7 @@ Base.gcd(::Val{:numerical}, p, q, args...; kwargs...) = ngcd(p,q, args...; kwarg function companion(p::P) where {T, P <: StandardBasisPolynomial{T}} d = length(p) - 1 - d < 1 && error("Series must have degree greater than 1") + d < 1 && throw(ArgumentError("Series must have degree greater than 1")) d == 1 && return diagm(0 => [-p[0] / p[1]]) diff --git a/test/ChebyshevT.jl b/test/ChebyshevT.jl index 9acdae8e..7e054f28 100644 --- a/test/ChebyshevT.jl +++ b/test/ChebyshevT.jl @@ -91,8 +91,8 @@ end @testset "Companion" begin c_null = ChebyshevT(Int[]) c_1 = ChebyshevT([1]) - @test_throws ErrorException companion(c_null) - @test_throws ErrorException companion(c_1) + @test_throws ArgumentError companion(c_null) + @test_throws ArgumentError companion(c_1) for i in 1:5 coef = vcat(zeros(i), 1) c = ChebyshevT(coef) diff --git a/test/Poly.jl b/test/Poly.jl index fec8e46f..d6a1b1ec 100644 --- a/test/Poly.jl +++ b/test/Poly.jl @@ -88,7 +88,7 @@ sprint(show, pNULL) @test polyder(pR) == Poly([-2//1,2//1]) @test polyder(p3) == Poly([2,2]) @test polyder(p1) == polyder(p0) == polyder(pNULL) == pNULL -@test_throws ErrorException polyder(pR, -1) +@test_throws ArgumentError polyder(pR, -1) @test polyint(pNULL,1) == p1 @test polyint(Poly(Rational[1,2,3])) == Poly(Rational[0, 1, 1, 1]) @test polyint(p2, 0, 2) == 4.0 @@ -148,8 +148,8 @@ pS3 = Poly([1, 2, 3, 4, 5], :s) @test_throws ErrorException pS1 + pX @test_throws ErrorException pS1 - pX @test_throws ErrorException pS1 * pX -@test_throws ErrorException pS1 ÷ pX -@test_throws ErrorException pS1 % pX +@test_throws ArgumentError pS1 ÷ pX +@test_throws ArgumentError pS1 % pX #Testing copying. pcpy1 = Poly([1,2,3,4,5], :y) @@ -357,7 +357,7 @@ p2s = Poly([1], :s) @test p1s ≠ p1x @test p1s ≠ p2s -@test_throws ErrorException p1s ≈ p1x +@test_throws ArgumentError p1s ≈ p1x @test p1s ≉ p2s @test p1s ≈ Poly([1,2.], :s) diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index e01ae58f..92596f6c 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -251,11 +251,11 @@ end @test pX != pS1 @test pS1 == pS2 @test pS1 == pS3 - @test_throws ErrorException pS1 + pX - @test_throws ErrorException pS1 - pX - @test_throws ErrorException pS1 * pX - @test_throws ErrorException pS1 ÷ pX - @test_throws ErrorException pS1 % pX + @test_throws ArgumentError pS1 + pX + @test_throws ArgumentError pS1 - pX + @test_throws ArgumentError pS1 * pX + @test_throws ArgumentError pS1 ÷ pX + @test_throws ArgumentError pS1 % pX # Testing copying. pcpy1 = P([1,2,3,4,5], :y) @@ -282,7 +282,7 @@ end @test p1s ≠ p1x @test p1s ≠ p2s - @test_throws ErrorException p1s ≈ p1x + @test_throws ArgumentError p1s ≈ p1x @test p1s ≉ p2s @test p1s ≈ P([1,2.], :s) @@ -320,7 +320,7 @@ end @test zero(P, :x) ≈ zero(P, :y) @test one(P, :x) ≈ one(P, :y) @test (variable(P, :x) ≈ variable(P, :x)) - @test_throws ErrorException variable(P, :x) ≈ variable(P, :y) + @test_throws ArgumentError variable(P, :x) ≈ variable(P, :y) end end @@ -600,7 +600,7 @@ end @test derivative(pR) == P([-2 // 1,2 // 1]) @test derivative(p3) == P([2,2]) @test derivative(p1) == derivative(p0) == derivative(pNULL) == pNULL - @test_throws ErrorException derivative(pR, -1) + @test_throws ArgumentError derivative(pR, -1) @test integrate(P([1,1,0,0]), 0, 2) == 4.0 @test derivative(integrate(pN)) == convert(P{Float64}, pN) From b398f036eb8247c9e3501f507660c082523272cf Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 15 Feb 2021 14:23:12 -0500 Subject: [PATCH 12/38] replace isnothing; standardize check for same variable --- src/common.jl | 4 ++++ src/polynomials/ChebyshevT.jl | 8 ++++---- src/polynomials/ImmutablePolynomial.jl | 6 +++--- src/polynomials/LaurentPolynomial.jl | 7 +++---- src/polynomials/Polynomial.jl | 9 +++++---- src/polynomials/SparsePolynomial.jl | 4 ++-- src/polynomials/ngcd.jl | 2 +- src/polynomials/standard-basis.jl | 2 +- 8 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/common.jl b/src/common.jl index 2b6d21fd..2fedb43a 100644 --- a/src/common.jl +++ b/src/common.jl @@ -277,6 +277,10 @@ Check if either `p` or `q` is constant or if `p` and `q` share the same variable check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) = (Polynomials.isconstant(p) || Polynomials.isconstant(q)) || indeterminate(p) == indeterminate(q) +function assert_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) + check_same_variable(p,q) || throw(ArgumentError("Polynomials have different indeterminates")) +end + #= Linear Algebra =# """ diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 5b8aac67..16f57459 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -36,7 +36,7 @@ struct ChebyshevT{T <: Number, X} <: AbstractPolynomial{T, X} end N = findlast(!iszero, coeffs) - isnothing(N) && return new{T,X}(zeros(T,1)) + N == nothing && return new{T,X}(zeros(T,1)) cs = T[coeffs[i] for i ∈ firstindex(coeffs):N] new{T,X}(cs) end @@ -185,7 +185,7 @@ end function Base.:+(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} X′ = isconstant(p2) ? X : Y - (!isconstant(p1) && !isconstant(p2)) && X != Y && throw(ArgumentError("Polynomials must have same variable")) + assert_same_variable(p1, p2) n = max(length(p1), length(p2)) R = promote_type(T,S) c = R[p1[i] + p2[i] for i = 0:n] @@ -195,7 +195,7 @@ end function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} X′ = isconstant(p2) ? X : Y - (!isconstant(p1) && !isconstant(p2)) && X != Y && throw(ArgumentError("Polynomials must have same variable")) + assert_same_variable(p1, p2) z1 = _c_to_z(p1.coeffs) z2 = _c_to_z(p2.coeffs) prod = fastconv(z1, z2) @@ -205,7 +205,7 @@ function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} end function Base.divrem(num::ChebyshevT{T,X}, den::ChebyshevT{S,Y}) where {T,X,S,Y} - X != Y && throw(ArgumentError("Polynomials must have same variable")) + assert_same_variable(num, den) n = length(num) - 1 m = length(den) - 1 diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index 7758d850..7c440a56 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -66,7 +66,7 @@ function ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}) where {T,X,S} @warn "ignoring the axis offset of the coefficient vector" end N = findlast(!iszero, coeffs) - isnothing(N) && return ImmutablePolynomial{R,X,0}(()) + N = nothing && return ImmutablePolynomial{R,X,0}(()) N′ = N + 1 - firstindex(coeffs) cs = NTuple{N′,T}(coeffs[i] for i ∈ firstindex(coeffs):N) ImmutablePolynomial{T, X, N′}(cs) @@ -75,7 +75,7 @@ end ## -- Tuple arguments function ImmutablePolynomial{T,X}(coeffs::Tuple) where {T,X} N = findlast(!iszero, coeffs) - isnothing(N) && return zero(ImmutablePolynomial{T,X}) + N == nothing && return zero(ImmutablePolynomial{T,X}) ImmutablePolynomial{T,X,N}(NTuple{N,T}(coeffs[i] for i in 1:N)) end @@ -200,7 +200,7 @@ end function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) where {T,X,N,S,Y,M} isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - X != Y && throw(ArgumentError("Polynomials must have same variable")) + assert_same_variable(p1, p2) R = promote_type(S,T) cs = (p1.coeffs) ⊗ (p2.coeffs) if !iszero(cs[end]) diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 766754bf..da146b68 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -457,8 +457,7 @@ function Base.:+(p1::P1, p2::P2) where {T,X,P1<:LaurentPolynomial{T,X}, S,Y, P2< return q1 end - X != Y && throw(ArgumentError("LaurentPolynomials must have same variable")) - + assert_same_variable(p1, p2) m1,n1 = (extrema ∘ degreerange)(p1) m2,n2 = (extrema ∘ degreerange)(p2) @@ -481,7 +480,7 @@ function Base.:*(p1::LaurentPolynomial{T,X}, p2::LaurentPolynomial{S,Y}) where { isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - X != Y && throw(ArgumentError("LaurentPolynomials must have same variable")) + assert_same_variable(p1, p2) R = promote_type(T,S) @@ -614,7 +613,7 @@ function Base.gcd(p::LaurentPolynomial{T,X}, q::LaurentPolynomial{T,Y}, args...; degree(p) == 0 && return iszero(p) ? q : one(q) degree(q) == 0 && return iszero(q) ? p : one(p) - check_same_variable(p,q) || throw(ArgumentError("p and q have different symbols")) + assert_same_variable(p,q) pp, qq = convert(Polynomial, p), convert(Polynomial, q) u = gcd(pp, qq, args..., kwargs...) diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 96324311..63467325 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -37,7 +37,7 @@ struct Polynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} @warn "ignoring the axis offset of the coefficient vector" end N = findlast(!iszero, coeffs) - isnothing(N) && return new{T,X}(zeros(T,1)) + N == nothing && return new{T,X}(zeros(T,1)) cs = T[coeffs[i] for i ∈ firstindex(coeffs):N] new{T,X}(cs) end @@ -92,8 +92,9 @@ end function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} isconstant(p1) && return p2 + p1[0] isconstant(p2) && return p1 + p2[0] - X, Y = indeterminate(p1), indeterminate(p2) - X != Y && throw(ArgumentError("Polynomials must have same variable")) + assert_same_variable(p1, p2) + X = indeterminate(p1) + n1, n2 = length(p1), length(p2) R = promote_type(T,S) @@ -122,7 +123,7 @@ function Base.:*(p1::Polynomial{T}, p2::Polynomial{S}) where {T,S} X, Y = indeterminate(p1), indeterminate(p2) R = promote_type(T, S) if n > 0 && m > 0 - X != Y && throw(ArgumentError("Polynomials must have same variable")) + assert_same_variable(p1, p2) c = zeros(R, m + n + 1) for i in 0:n, j in 0:m @inbounds c[i + j + 1] += p1[i] * p2[j] diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index a9977238..48b0614c 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -212,7 +212,7 @@ function Base.:+(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T, isconstant(p1) && return p2 + p1[0] isconstant(p2) && return p1 + p2[0] - X != Y && throw(ArgumentError("SparsePolynomials must have same variable")) + assert_same_variable(p1, p2) R = promote_type(T,S) p = zero(SparsePolynomial{R,X}) @@ -253,7 +253,7 @@ function Base.:*(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T, isconstant(p1) && return p2 * p1[0] isconstant(p2) && return p1 * p2[0] - X != Y && throw(ArgumentError("SparsePolynomials must have same variable")) + assert_same_variable(p1, p2) R = promote_type(T,S) P = SparsePolynomial diff --git a/src/polynomials/ngcd.jl b/src/polynomials/ngcd.jl index 09700d93..80a07600 100644 --- a/src/polynomials/ngcd.jl +++ b/src/polynomials/ngcd.jl @@ -13,7 +13,7 @@ function ngcd(p::P, q::Q, args...;kwargs...) where {T, S, P<:StandardBasisPolyno degree(p) == 0 && return (u=one(q), v=p, w=zero(q), θ=NaN, κ=NaN) degree(q) < 0 && return (u=one(q), v=p, w=zero(q), θ=NaN, κ=NaN) degree(q) == 0 && return (u=one(p), v=p, w=q, θ=NaN, κ=NaN) - check_same_variable(p,q) || throw(ArgumentError("Mis-matched variables")) + assert_same_variable(p,q) p′,q′ = promote(p,q) diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index b3852707..c7e326cf 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -99,7 +99,7 @@ end function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} - check_same_variable(num, den) || throw(ArgumentError("Polynomials must have same variable")) + assert_same_variable(num, den) X = indeterminate(num) From 1426b38e353886279d5494f1600088dedae0c2d9 Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 15 Feb 2021 14:49:27 -0500 Subject: [PATCH 13/38] registerN, bug --- src/abstract.jl | 63 ++++---------------------- src/polynomials/ImmutablePolynomial.jl | 2 +- 2 files changed, 9 insertions(+), 56 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index 57ad0256..a0e4fed8 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -3,7 +3,7 @@ export AbstractPolynomial const SymbolLike = Union{AbstractString,Char,Symbol} """ - AbstractPolynomial{T} + AbstractPolynomial{T,X} An abstract container for various polynomials. @@ -27,7 +27,7 @@ Given a polynomial with `name`, creates some common convenience constructors and # Example ```julia -struct MyPolynomial{T} <: AbstractPolynomial{T} end +struct MyPolynomial{T,X} <: AbstractPolynomial{T,X} end Polynomials.@register MyPolynomial ``` @@ -72,17 +72,17 @@ macro registerN(name, params...) quote Base.convert(::Type{P}, q::Q) where {$(αs...),T, P<:$poly{$(αs...),T}, Q <: $poly{$(αs...),T}} = q Base.convert(::Type{$poly{$(αs...)}}, q::Q) where {$(αs...),T, Q <: $poly{$(αs...),T}} = q - Base.promote(p::P, q::Q) where {$(αs...),T, P <:$poly{$(αs...),T}, Q <: $poly{$(αs...),T}} = p,q - Base.promote_rule(::Type{<:$poly{$(αs...),T}}, ::Type{<:$poly{$(αs...),S}}) where {$(αs...),T,S} = - $poly{$(αs...),promote_type(T, S)} - Base.promote_rule(::Type{<:$poly{$(αs...),T}}, ::Type{S}) where {$(αs...),T,S<:Number} = - $poly{$(αs...),promote_type(T,S)} + Base.promote(p::P, q::Q) where {$(αs...),T, X, P <:$poly{$(αs...),T,X}, Q <: $poly{$(αs...),T,X}} = p,q + Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{<:$poly{$(αs...),S,X}}) where {$(αs...),T,S,X} = + $poly{$(αs...),promote_type(T, S),X} + Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{S}) where {$(αs...),T,X,S<:Number} = + $poly{$(αs...),promote_type(T,S),X} function $poly{$(αs...),T}(x::AbstractVector{S}, var::SymbolLike = :x) where {$(αs...),T,S} $poly{$(αs...),T}(T.(x), Symbol(var)) end $poly{$(αs...)}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where {$(αs...),T} = - $poly{$(αs...),T}(coeffs, Symbol(var)) + $poly{$(αs...),T,Symbol(var)}(coeffs) $poly{$(αs...),T}(n::Number, var::SymbolLike = :x) where {$(αs...),T} = n*one($poly{$(αs...),T}, Symbol(var)) $poly{$(αs...)}(n::Number, var::SymbolLike = :x) where {$(αs...)} = n*one($poly{$(αs...)}, Symbol(var)) $poly{$(αs...),T}(var::SymbolLike=:x) where {$(αs...), T} = variable($poly{$(αs...),T}, Symbol(var)) @@ -90,50 +90,3 @@ macro registerN(name, params...) end end - -# deprecated. If desired, replace with @registerN type parameters... macro -# Macros to register POLY{α, T} and POLY{α, β, T} -macro register1(name) - @warn "@register1 is deprecated use @registerN" - poly = esc(name) - quote - Base.convert(::Type{P}, p::P) where {P<:$poly} = p - Base.promote(p::P, q::Q) where {α,T, P <:$poly{α,T}, Q <: $poly{α,T}} = p,q - Base.promote_rule(::Type{<:$poly{α,T}}, ::Type{<:$poly{α,S}}) where {α,T,S} = - $poly{α,promote_type(T, S)} - Base.promote_rule(::Type{<:$poly{α,T}}, ::Type{S}) where {α,T,S<:Number} = - $poly{α,promote_type(T,S)} - function $poly{α,T}(x::AbstractVector{S}, var::SymbolLike = :x) where {α,T,S} - $poly{α,T}(T.(x), Symbol(var)) - end - $poly{α}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where {α,T} = - $poly{α,T}(coeffs, Symbol(var)) - $poly{α,T}(n::Number, var::SymbolLike = :x) where {α,T} = n*one($poly{α,T}, Symbol(var)) - $poly{α}(n::Number, var::SymbolLike = :x) where {α} = n*one($poly{α}, Symbol(var)) - $poly{α,T}(var::SymbolLike=:x) where {α, T} = variable($poly{α,T}, Symbol(var)) - $poly{α}(var::SymbolLike=:x) where {α} = variable($poly{α}, Symbol(var)) - end -end - - -# Macro to register POLY{α, β, T} -macro register2(name) - @warn "@register2 is deprecated use @registerN" - poly = esc(name) - quote - Base.convert(::Type{P}, p::P) where {P<:$poly} = p - Base.promote(p::P, q::Q) where {α,β,T, P <:$poly{α,β,T}, Q <: $poly{α,β,T}} = p,q - Base.promote_rule(::Type{<:$poly{α,β,T}}, ::Type{<:$poly{α,β,S}}) where {α,β,T,S} = - $poly{α,β,promote_type(T, S)} - Base.promote_rule(::Type{<:$poly{α,β,T}}, ::Type{S}) where {α,β,T,S<:Number} = - $poly{α,β,promote_type(T, S)} - $poly{α,β}(coeffs::AbstractVector{T}, var::SymbolLike = :x) where {α,β,T} = - $poly{α,β,T}(coeffs, Symbol(var)) - $poly{α,β,T}(x::AbstractVector{S}, var::SymbolLike = :x) where {α,β,T,S<:Number} = $poly{α,β,T}(T.(x), var) - $poly{α,β,T}(n::Number, var::SymbolLike = :x) where {α,β,T} = n*one($poly{α,β,T}, Symbol(var)) - $poly{α,β}(n::Number, va::SymbolLiker = :x) where {α,β} = n*one($poly{α,β}, Symbol(var)) - $poly{α,β,T}(var::SymbolLike=:x) where {α,β, T} = variable($poly{α,β,T}, Symbol(var)) - $poly{α,β}(var::SymbolLike=:x) where {α,β} = variable($poly{α,β}, Symbol(var)) - end -end - diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index 7c440a56..4a9fa7d5 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -66,7 +66,7 @@ function ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}) where {T,X,S} @warn "ignoring the axis offset of the coefficient vector" end N = findlast(!iszero, coeffs) - N = nothing && return ImmutablePolynomial{R,X,0}(()) + N == nothing && return ImmutablePolynomial{R,X,0}(()) N′ = N + 1 - firstindex(coeffs) cs = NTuple{N′,T}(coeffs[i] for i ∈ firstindex(coeffs):N) ImmutablePolynomial{T, X, N′}(cs) From b3ca9c0ad25781f6c1c2af7ab557f62b69302c59 Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 15 Feb 2021 15:55:10 -0500 Subject: [PATCH 14/38] update documentation --- README.md | 4 ++- docs/src/index.md | 64 ++++++++++++++++++++++++++++++++--- src/polynomials/Polynomial.jl | 4 +++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 41908398..7783f12f 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,9 @@ Polynomial objects also have other methods: * [PolynomialRings](https://github.com/tkluck/PolynomialRings.jl) A library for arithmetic and algebra with multi-variable polynomials. -* [AbstractAlgebra.jl](https://github.com/wbhart/AbstractAlgebra.jl) and [Nemo.jl](https://github.com/wbhart/Nemo.jl) for generic polynomial rings, matrix spaces, fraction fields, residue rings, power series +* [AbstractAlgebra.jl](https://github.com/wbhart/AbstractAlgebra.jl), [Nemo.jl](https://github.com/wbhart/Nemo.jl) for generic polynomial rings, matrix spaces, fraction fields, residue rings, power series, [Hecke.jl](https://github.com/thofma/Hecke.jl) for algebraic number theory. + +* [CommutativeAlgebra](https://github.com/KlausC/CommutativeRings.jl) the start of a computer algebra system specialized to discrete calculations with support for polynomials. * [PolynomialRoots.jl](https://github.com/giordano/PolynomialRoots.jl) for a fast complex polynomial root finder. For larger degree problems, also [FastPolynomialRoots](https://github.com/andreasnoack/FastPolynomialRoots.jl) and [AMRVW](https://github.com/jverzani/AMRVW.jl). diff --git a/docs/src/index.md b/docs/src/index.md index 09952892..4171ea14 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -97,7 +97,7 @@ julia> q = Polynomial([1, 2, 3], :s) Polynomial(1 + 2*s + 3*s^2) julia> p + q -ERROR: Polynomials must have same variable +ERROR: ArgumentError: Polynomials have different indeterminates [...] ``` @@ -213,15 +213,68 @@ ChebyshevT(2.5⋅T_0(x) + 2.0⋅T_1(x) + 1.5⋅T_2(x)) ### Iteration -If its basis is implicit, then a polynomial may be seen as just a vector of coefficients. Vectors or 1-based, but, for convenience, polynomial types are 0-based, for purposes of indexing (e.g. `getindex`, `setindex!`, `eachindex`). Iteration over a polynomial steps through the basis vectors, e.g. `a_0`, `a_1*x`, ... +If its basis is implicit, then a polynomial may be seen as just a vector of coefficients. Vectors or 1-based, but, for convenience, polynomial types are 0-based, for purposes of indexing (e.g. `getindex`, `setindex!`, `eachindex`). Iteration over a polynomial steps through the underlying coefficients. ```jldoctest julia> as = [1,2,3,4,5]; p = Polynomial(as); julia> as[3], p[2], collect(p)[3] -(3, 3, Polynomial(3*x^2)) +(3, 3, 3) ``` + +The `pairs` iterator, iterates over the indices and coefficients, attempting to match how `pairs` applies to the underlying storage model: + +```jldoctest +julia> v = [1,2,0,4] +4-element Array{Int64,1}: + 1 + 2 + 0 + 4 + +julia> p,ip,sp,lp = Polynomial(v), ImmutablePolynomial(v), SparsePolynomial(v), LaurentPolynomial(v, -1); + +julia> collect(pairs(p)) +4-element Array{Pair{Int64,Int64},1}: + 0 => 1 + 1 => 2 + 2 => 0 + 3 => 4 + +julia> collect(pairs(ip)) == collect(pairs(p)) +true + +julia> collect(pairs(sp)) # unordered dictionary with only non-zero terms +3-element Array{Pair{Int64,Int64},1}: + 0 => 1 + 3 => 4 + 1 => 2 + +julia> collect(pairs(lp)) +4-element Array{Pair{Int64,Int64},1}: + -1 => 1 + 0 => 2 + 1 => 0 + 2 => 4 +``` + + +The unexported `monomials` iterator iterates over the terms (`p[i]*Polynomials.basis(p,i)`) of the polynomial: + +```jldoctest +julia> p = Polynomial([1,2,0,4], :u) +Polynomial(1 + 2*u + 4*u^3) + +julia> collect(Polynomials.monomials(p)) +4-element Array{Any,1}: + Polynomial(1) + Polynomial(2*x) + Polynomial(0) + Polynomial(4*x^3) +``` + + ## Related Packages * [StaticUnivariatePolynomials.jl](https://github.com/tkoolen/StaticUnivariatePolynomials.jl) Fixed-size univariate polynomials backed by a Tuple @@ -234,12 +287,15 @@ julia> as[3], p[2], collect(p)[3] * [PolynomialRings](https://github.com/tkluck/PolynomialRings.jl) A library for arithmetic and algebra with multi-variable polynomials. -* [AbstractAlgebra.jl](https://github.com/wbhart/AbstractAlgebra.jl) and [Nemo.jl](https://github.com/wbhart/Nemo.jl) for generic polynomial rings, matrix spaces, fraction fields, residue rings, power series +* [AbstractAlgebra.jl](https://github.com/wbhart/AbstractAlgebra.jl), [Nemo.jl](https://github.com/wbhart/Nemo.jl) for generic polynomial rings, matrix spaces, fraction fields, residue rings, power series, [Hecke.jl](https://github.com/thofma/Hecke.jl) for algebraic number theory. + +* [CommutativeAlgebra](https://github.com/KlausC/CommutativeRings.jl) the start of a computer algebra system specialized to discrete calculations with support for polynomials. * [PolynomialRoots.jl](https://github.com/giordano/PolynomialRoots.jl) for a fast complex polynomial root finder. For larger degree problems, also [FastPolynomialRoots](https://github.com/andreasnoack/FastPolynomialRoots.jl) and [AMRVW](https://github.com/jverzani/AMRVW.jl). + ## Contributing If you are interested in this project, feel free to open an issue or pull request! In general, any changes must be thoroughly tested, allow deprecation, and not deviate too far from the common interface. All PR's must have an updated project version, as well, to keep the continuous delivery cycle up-to-date. diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 63467325..5dc35cb0 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -20,6 +20,8 @@ polynomials of different variables causes an error except those involving a cons # Examples ```jldoctest +julia> using Polynomials + julia> Polynomial([1, 0, 3, 4]) Polynomial(1 + 3*x^2 + 4*x^3) @@ -56,6 +58,8 @@ Evaluate the polynomial using [Horner's Method](https://en.wikipedia.org/wiki/Ho # Examples ```jldoctest +julia> using Polynomials + julia> p = Polynomial([1, 0, 3]) Polynomial(1 + 3*x^2) From fc3014b9278b034846f13355be3fcb968864094e Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 17 Feb 2021 18:11:37 -0500 Subject: [PATCH 15/38] minor edits --- src/abstract.jl | 8 ++++++-- src/common.jl | 15 +++++++++------ src/polynomials/standard-basis.jl | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index a0e4fed8..cbf872de 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -62,6 +62,7 @@ macro register(name) $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S, Symbol(var)}) $poly{T}(var::SymbolLike=:x) where {T} = variable($poly{T, Symbol(var)}) $poly(var::SymbolLike=:x) = variable($poly, Symbol(var)) + _indeterminate(::Type{P}) where {T,X,P<:$poly{T,X}} = X end end @@ -79,14 +80,17 @@ macro registerN(name, params...) $poly{$(αs...),promote_type(T,S),X} function $poly{$(αs...),T}(x::AbstractVector{S}, var::SymbolLike = :x) where {$(αs...),T,S} - $poly{$(αs...),T}(T.(x), Symbol(var)) + $poly{$(αs...),T, Symbol(var)}(T.(x)) end $poly{$(αs...)}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where {$(αs...),T} = $poly{$(αs...),T,Symbol(var)}(coeffs) - $poly{$(αs...),T}(n::Number, var::SymbolLike = :x) where {$(αs...),T} = n*one($poly{$(αs...),T}, Symbol(var)) + + $poly{$(αs...),T,X}(n::Number) where {$(αs...),T,X} = n*one($poly{$(αs...),T,X}) + $poly{$(αs...),T}(n::Number, var::SymbolLike = :x) where {$(αs...),T} = n*one($poly{$(αs...),T,Symbol(var)}) $poly{$(αs...)}(n::Number, var::SymbolLike = :x) where {$(αs...)} = n*one($poly{$(αs...)}, Symbol(var)) $poly{$(αs...),T}(var::SymbolLike=:x) where {$(αs...), T} = variable($poly{$(αs...),T}, Symbol(var)) $poly{$(αs...)}(var::SymbolLike=:x) where {$(αs...)} = variable($poly{$(αs...)}, Symbol(var)) + _indeterminate(::Type{P}) where {$(αs...),T,X,P<:$poly{$(αs...),T,X}} = X end end diff --git a/src/common.jl b/src/common.jl index 2fedb43a..5c9e49b0 100644 --- a/src/common.jl +++ b/src/common.jl @@ -241,7 +241,7 @@ In-place version of [`chop`](@ref) function chop!(p::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0,) where {T} - isempty(values(p)) && return p + isempty(coeffs(p)) && return p tol = norm(p) * rtol + atol for i = lastindex(p):-1:0 val = p[i] @@ -581,11 +581,14 @@ Base.hash(p::AbstractPolynomial, h::UInt) = hash(indeterminate(p), hash(coeffs(p zero, one, variable, basis =# # get symbol of polynomial. (e.g. `:x` from 1x^2 + 2x^3... -_indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T, X}} = X +#_indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T, X}} = X _indeterminate(::Type{P}) where {P <: AbstractPolynomial} = nothing -indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X -indeterminate(::Type{P}) where {P <: AbstractPolynomial} = :x -indeterminate(p::AbstractPolynomial{T, X}) where {T, X} = X +_indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X +function indeterminate(::Type{P}) where {P <: AbstractPolynomial} + X = _indeterminate(P) + X == nothing ? :x : X +end +indeterminate(p::P) where {P <: AbstractPolynomial} = _indeterminate(P) function indeterminate(PP::Type{P}, p::AbstractPolynomial) where {P <: AbstractPolynomial} X = _indeterminate(PP) == nothing ? indeterminate(p) : _indeterminate(PP) end @@ -734,7 +737,7 @@ function Base.gcd(p1::AbstractPolynomial{T}, p2::AbstractPolynomial{T}; while !iszero(r₁) && iter ≤ itermax _, rtemp = divrem(r₀, r₁) r₀ = r₁ - r₁ = truncate(rtemp; atol=atol, rtol=rtol) + r₁ = truncate(rtemp; atol=atol, rtol=rtol) iter += 1 end return r₀ diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index c7e326cf..45b21f9e 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -30,7 +30,7 @@ Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) = i #Base.values(p::StandardBasisPolynomial) = values(p.coeffs) -variable(::Type{P}, var::SymbolLike = :x) where {P <: StandardBasisPolynomial} = P([0, 1], var) +variable(::Type{P}, var::SymbolLike = :x) where {P <: StandardBasisPolynomial} = ⟒(P){eltype(P),Symbol(var)}([0, 1]) function fromroots(P::Type{<:StandardBasisPolynomial}, r::AbstractVector{T}; var::SymbolLike = :x) where {T <: Number} n = length(r) From 206de82e15365785588c318949190e230eac4317 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 18 Feb 2021 14:16:57 -0500 Subject: [PATCH 16/38] cleanup, docfix --- docs/src/polynomials/chebyshev.md | 2 +- src/abstract.jl | 2 -- src/common.jl | 12 +++++++++- src/polynomials/ChebyshevT.jl | 33 +++++++++++++++------------- src/polynomials/LaurentPolynomial.jl | 2 +- src/polynomials/SparsePolynomial.jl | 4 ++-- src/polynomials/standard-basis.jl | 16 ++++++++++---- 7 files changed, 45 insertions(+), 26 deletions(-) diff --git a/docs/src/polynomials/chebyshev.md b/docs/src/polynomials/chebyshev.md index 38d79854..e711a9fb 100644 --- a/docs/src/polynomials/chebyshev.md +++ b/docs/src/polynomials/chebyshev.md @@ -31,7 +31,7 @@ ChebyshevT(1⋅T_0(x) + 3⋅T_2(x) + 4⋅T_3(x)) julia> p = convert(Polynomial, c) -Polynomial(-2 - 12*x + 6*x^2 + 16*x^3) +Polynomial(-2.0 - 12.0*x + 6.0*x^2 + 16.0*x^3) julia> convert(ChebyshevT, p) ChebyshevT(1.0⋅T_0(x) + 3.0⋅T_2(x) + 4.0⋅T_3(x)) diff --git a/src/abstract.jl b/src/abstract.jl index cbf872de..34cf48b3 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -62,7 +62,6 @@ macro register(name) $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S, Symbol(var)}) $poly{T}(var::SymbolLike=:x) where {T} = variable($poly{T, Symbol(var)}) $poly(var::SymbolLike=:x) = variable($poly, Symbol(var)) - _indeterminate(::Type{P}) where {T,X,P<:$poly{T,X}} = X end end @@ -90,7 +89,6 @@ macro registerN(name, params...) $poly{$(αs...)}(n::Number, var::SymbolLike = :x) where {$(αs...)} = n*one($poly{$(αs...)}, Symbol(var)) $poly{$(αs...),T}(var::SymbolLike=:x) where {$(αs...), T} = variable($poly{$(αs...),T}, Symbol(var)) $poly{$(αs...)}(var::SymbolLike=:x) where {$(αs...)} = variable($poly{$(αs...)}, Symbol(var)) - _indeterminate(::Type{P}) where {$(αs...),T,X,P<:$poly{$(αs...),T,X}} = X end end diff --git a/src/common.jl b/src/common.jl index 5c9e49b0..af47ba2a 100644 --- a/src/common.jl +++ b/src/common.jl @@ -331,7 +331,13 @@ Base.eltype(p::AbstractPolynomial{T}) where {T} = T # in analogy with polynomial as a Vector{T} with different operations defined. Base.eltype(::Type{<:AbstractPolynomial}) = Float64 Base.eltype(::Type{<:AbstractPolynomial{T}}) where {T} = T -#Base.eltype(::Type{P}) where {P <: AbstractPolynomial} = P # changed in v1.1.0 +_eltype(::Type{<:AbstractPolynomial}) = nothing +_eltype(::Type{<:AbstractPolynomial{T}}) where {T} = T +function _eltype(P::Type{<:AbstractPolynomial}, p::AbstractPolynomial) + T′ = _eltype(P) + T = T′ == nothing ? eltype(p) : T′ + T +end Base.iszero(p::AbstractPolynomial) = all(iszero, p) # See discussions in https://github.com/JuliaMath/Polynomials.jl/issues/258 @@ -592,6 +598,10 @@ indeterminate(p::P) where {P <: AbstractPolynomial} = _indeterminate(P) function indeterminate(PP::Type{P}, p::AbstractPolynomial) where {P <: AbstractPolynomial} X = _indeterminate(PP) == nothing ? indeterminate(p) : _indeterminate(PP) end +function indeterminate(PP::Type{P}, x::Symbol) where {P <: AbstractPolynomial} + X = _indeterminate(PP) == nothing ? x : _indeterminate(PP) +end + """ diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 16f57459..7fce22db 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -45,30 +45,33 @@ end @register ChebyshevT function Base.convert(P::Type{<:Polynomial}, ch::ChebyshevT) + T = eltype(P) + X = indeterminate(P,ch) + Q = ⟒(P){T,X} + if length(ch) < 3 - return P(ch.coeffs, indeterminate(ch)) + return Q(ch.coeffs) end - c0 = P(ch[end - 1], indeterminate(ch)) - c1 = P(ch[end], indeterminate(ch)) + + c0 = Q(ch[end - 1]) + c1 = Q(ch[end]) + x = variable(Q) @inbounds for i in degree(ch):-1:2 tmp = c0 - c0 = P(ch[i - 2], indeterminate(ch)) - c1 - c1 = tmp + c1 * variable(P) * 2 - end - return c0 + c1 * variable(P) -end - -function Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) - res = zero(C) - @inbounds for i in degree(p):-1:0 - res = variable(C) * res + p[i] + c0 = Q(ch[i - 2]) - c1 + c1 = tmp + c1 * x * 2 end - return res + return c0 + c1 * x end +Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) = p(variable(C)) domain(::Type{<:ChebyshevT}) = Interval(-1, 1) -variable(P::Type{<:ChebyshevT}, var::SymbolLike=:x ) = P([0,1], var) +function variable(P::Type{<:ChebyshevT}, var::SymbolLike) + X′ = _indeterminate(P) + X = X′ == nothing ? Symbol(var) : X′ + ⟒(P){eltype(P), X}([0, 1]) +end """ (::ChebyshevT)(x) diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index da146b68..5c1174b5 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -304,7 +304,7 @@ Examples julia> using Polynomials; julia> z = variable(LaurentPolynomial, :z) -LaurentPolynomial(z) +LaurentPolynomial(1.0*z) julia> p = LaurentPolynomial([im, 1+im, 2 + im], -1, :z) LaurentPolynomial(im*z⁻¹ + 1 + im + (2 + im)z) diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 48b0614c..b1b111fc 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -83,8 +83,8 @@ function Base.convert(P::Type{<:Polynomial}, q::SparsePolynomial) end function Base.convert(P::Type{<:SparsePolynomial}, q::StandardBasisPolynomial{T}) where {T} - R = promote(eltype(P), T) - ⟒(P){R}(coeffs(q), indeterminate(q)) + R = promote_type(eltype(P), T) + ⟒(P){R,indeterminate(P,q)}(coeffs(q)) end ## changes to common diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 45b21f9e..d33dbc90 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -26,11 +26,19 @@ mapdomain(::Type{<:StandardBasisPolynomial}, x::AbstractArray) = x ## generic test if polynomial `p` is a constant isconstant(p::StandardBasisPolynomial) = degree(p) <= 0 -Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) = isa(q, P) ? q : P([q[i] for i in 0:degree(q)], indeterminate(q)) - -#Base.values(p::StandardBasisPolynomial) = values(p.coeffs) +function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) + if isa(q, P) + return q + else + T = _eltype(P,q) + X = indeterminate(P,q) + return ⟒(P){T,X}([q[i] for i in 0:degree(q)]) + end +end -variable(::Type{P}, var::SymbolLike = :x) where {P <: StandardBasisPolynomial} = ⟒(P){eltype(P),Symbol(var)}([0, 1]) +function variable(::Type{P}, var::SymbolLike) where {P <: StandardBasisPolynomial} + ⟒(P){eltype(P), indeterminate(P,Symbol(var))}([0, 1]) +end function fromroots(P::Type{<:StandardBasisPolynomial}, r::AbstractVector{T}; var::SymbolLike = :x) where {T <: Number} n = length(r) From 5d5291879b6782b901dbdd736563e4b0ae5c7586 Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 19 Feb 2021 13:04:36 -0500 Subject: [PATCH 17/38] clean up integration --- src/abstract.jl | 9 +++++---- src/common.jl | 17 ++++++++++++++--- src/polynomials/ChebyshevT.jl | 10 +++++----- src/polynomials/ImmutablePolynomial.jl | 2 +- src/polynomials/LaurentPolynomial.jl | 20 +++++++++++--------- src/polynomials/SparsePolynomial.jl | 9 +++++---- src/polynomials/standard-basis.jl | 13 ++++--------- 7 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index 34cf48b3..67a74431 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -58,7 +58,7 @@ macro register(name) $poly{T,X}(n::S) where {T, X, S<:Number} = n * one($poly{T, X}) $poly{T}(n::S, var::SymbolLike = :x) where {T, S<:Number} = - n * one($poly{T}, Symbol(var)) + n * one($poly{T, Symbol(var)}) $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S, Symbol(var)}) $poly{T}(var::SymbolLike=:x) where {T} = variable($poly{T, Symbol(var)}) $poly(var::SymbolLike=:x) = variable($poly, Symbol(var)) @@ -86,9 +86,10 @@ macro registerN(name, params...) $poly{$(αs...),T,X}(n::Number) where {$(αs...),T,X} = n*one($poly{$(αs...),T,X}) $poly{$(αs...),T}(n::Number, var::SymbolLike = :x) where {$(αs...),T} = n*one($poly{$(αs...),T,Symbol(var)}) - $poly{$(αs...)}(n::Number, var::SymbolLike = :x) where {$(αs...)} = n*one($poly{$(αs...)}, Symbol(var)) - $poly{$(αs...),T}(var::SymbolLike=:x) where {$(αs...), T} = variable($poly{$(αs...),T}, Symbol(var)) - $poly{$(αs...)}(var::SymbolLike=:x) where {$(αs...)} = variable($poly{$(αs...)}, Symbol(var)) + $poly{$(αs...)}(n::S, var::SymbolLike = :x) where {$(αs...), S<:Number} = + n*one($poly{$(αs...),S,Symbol(var)}) + $poly{$(αs...),T}(var::SymbolLike=:x) where {$(αs...), T} = variable($poly{$(αs...),T,Symbol(var)}) + $poly{$(αs...)}(var::SymbolLike=:x) where {$(αs...)} = variable($poly{$(αs...)},Symbol(var)) end end diff --git a/src/common.jl b/src/common.jl index af47ba2a..317e594f 100644 --- a/src/common.jl +++ b/src/common.jl @@ -182,11 +182,22 @@ Calculate the psuedo-Vandermonde matrix of the given polynomial type with the gi vander(::Type{<:AbstractPolynomial}, x::AbstractVector, deg::Integer) """ - integrate(::AbstractPolynomial, C=0) + integrate(p::AbstractPolynomial) -Returns the indefinite integral of the polynomial with constant `C`. +Return an antiderivative for `p` """ -integrate(p::AbstractPolynomial, C::Number = 0) = integrate(p, C) +integrate(P::AbstractPolynomial) = throw(MethodError("`integrate` not implemented for polynomials of type $P")) + +""" + integrate(::AbstractPolynomial, C) + +Returns the indefinite integral of the polynomial with constant `C` when expressed in the standard basis. +""" +function integrate(p::P, C) where {P <: AbstractPolynomial} + ∫p = integrate(p) + isnan(C) && return ⟒(P){eltype(∫p+C), indeterminate(∫p)}([C]) + ∫p + (-∫p(0) + C) +end """ integrate(::AbstractPolynomial, a, b) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 7fce22db..b18219ea 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -121,15 +121,15 @@ function vander(P::Type{<:ChebyshevT}, x::AbstractVector{T}, n::Integer) where { return A end -function integrate(p::ChebyshevT{T,X}, C::S) where {T,X,S <: Number} - R = promote_type(eltype(one(T) / 1), S) +function integrate(p::ChebyshevT{T,X}) where {T,X} + R = eltype(one(T) / 1) Q = ChebyshevT{R,X} - if hasnan(p) || isnan(C) + if hasnan(p) return Q([NaN]) end n = length(p) if n == 1 - return Q([C, p[0]]) + return Q([zero(R), p[0]]) end a2 = Vector{R}(undef, n + 1) a2[1] = zero(R) @@ -139,7 +139,7 @@ function integrate(p::ChebyshevT{T,X}, C::S) where {T,X,S <: Number} a2[i + 2] = p[i] / (2 * (i + 1)) a2[i] -= p[i] / (2 * (i - 1)) end - a2[1] += R(C) - ChebyshevT(a2)(0) + return Q(a2) end diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index 4a9fa7d5..ed128b25 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -265,7 +265,7 @@ function Base.:+(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X, N, S<:Number} R = promote_type(T,S) iszero(c) && return ImmutablePolynomial{R,X,N}(convert(NTuple{N,R},p.coeffs)) - N == 0 && return ImmutablePolynomial{R,X,1}((c,)) + N == 0 && return ImmutablePolynomial{R,X,1}(NTuple{1,R}(c)) N == 1 && return ImmutablePolynomial((p[0]+c,), X) q = p + ImmutablePolynomial{S,X,1}((c,)) return q diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 5c1174b5..6b3c9761 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -433,8 +433,13 @@ end # scalar operattoinis -# standard-basis defn. assumes basis 1, x, x², ... -Base.:+(p::LaurentPolynomial{T}, c::S) where {T, S <: Number} = sum(promote(p,c)) +# needed as standard-basis defn. assumes basis 1, x, x², ... +function Base.:+(p::LaurentPolynomial{T,X}, c::S) where {T, X, S <: Number} + R = promote_type(T,S) + q = LaurentPolynomial{R,X}(p.coeffs, firstindex(p)) + q[0] += c + q +end ## ## Poly + and * @@ -569,13 +574,13 @@ function derivative(p::P, order::Integer = 1) where {T, X, P<:LaurentPolynomial{ end -function integrate(p::P, k::S) where {T, X, P<: LaurentPolynomial{T, X}, S<:Number} +function integrate(p::P) where {T, X, P<: LaurentPolynomial{T, X}} !iszero(p[-1]) && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term")) - R = eltype((one(T)+one(S))/1) + R = eltype(one(T)/1) Q = ⟒(P){R, X} - if hasnan(p) || isnan(k) + if hasnan(p) return Q([NaN],0) end @@ -597,13 +602,10 @@ function integrate(p::P, k::S) where {T, X, P<: LaurentPolynomial{T, X}, S<:Numb as[1 + k+1-m] = p[k]/(k+1) end - as[1-m] = k - return Q(as, m) end - function Base.gcd(p::LaurentPolynomial{T,X}, q::LaurentPolynomial{T,Y}, args...; kwargs...) where {T,X,Y} mp, Mp = (extrema ∘ degreerange)(p) mq, Mq = (extrema ∘ degreerange)(q) @@ -613,7 +615,7 @@ function Base.gcd(p::LaurentPolynomial{T,X}, q::LaurentPolynomial{T,Y}, args...; degree(p) == 0 && return iszero(p) ? q : one(q) degree(q) == 0 && return iszero(q) ? p : one(p) - assert_same_variable(p,q) + assert_same_variable(p,q) pp, qq = convert(Polynomial, p), convert(Polynomial, q) u = gcd(pp, qq, args..., kwargs...) diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index b1b111fc..7b5c1df3 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -306,16 +306,16 @@ function derivative(p::SparsePolynomial{T,X}, order::Integer = 1) where {T,X} end -function integrate(p::P, k::S) where {T, X, P<:SparsePolynomial{T,X}, S<:Number} +function integrate(p::P) where {T, X, P<:SparsePolynomial{T,X}} - R = eltype((one(T)+one(S))/1) + R = eltype(one(T)/1) Q = SparsePolynomial{R,X} - if hasnan(p) || isnan(k) + if hasnan(p) return Q(Dict(0 => NaN)) end - ∫p = Q(R(k)) + ∫p = zero(Q) for k in eachindex(p) ∫p[k + 1] = p[k] / (k+1) end @@ -323,3 +323,4 @@ function integrate(p::P, k::S) where {T, X, P<:SparsePolynomial{T,X}, S<:Number} return ∫p end + diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index d33dbc90..04093f2c 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -1,7 +1,5 @@ abstract type StandardBasisPolynomial{T,X} <: AbstractPolynomial{T,X} end - - function showterm(io::IO, ::Type{<:StandardBasisPolynomial}, pj::T, var, j, first::Bool, mimetype) where {T} if iszero(pj) return false end @@ -85,26 +83,23 @@ function derivative(p::P, order::Integer = 1) where {T, X, P <: StandardBasisPol Q(a2) end - -function integrate(p::P, k::S) where {T, X, P <: StandardBasisPolynomial{T, X}, S<:Number} - - R = eltype((one(T)+one(S))/1) +function integrate(p::P) where {T, X, P <: StandardBasisPolynomial{T, X}} + R = eltype(one(T)/1) Q = ⟒(P){R,X} - if hasnan(p) || isnan(k) + if hasnan(p) return Q([NaN]) end n = length(p) a2 = Vector{R}(undef, n + 1) - a2[1] = k + a2[1] = zero(R) @inbounds for i in 1:n a2[i + 1] = p[i - 1] / i end return Q(a2) end - function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} assert_same_variable(num, den) From a031b8e22793288e48281004146d152942c3b134 Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 19 Feb 2021 18:18:25 -0500 Subject: [PATCH 18/38] adjust doc string --- src/common.jl | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/common.jl b/src/common.jl index 317e594f..493c9107 100644 --- a/src/common.jl +++ b/src/common.jl @@ -70,19 +70,41 @@ fromroots(A::AbstractMatrix{T}; var::SymbolLike = :x) where {T <: Number} = fit(::Type{<:AbstractPolynomial}, x, y, deg=length(x)-1; [weights], var=:x) Fit the given data as a polynomial type with the given degree. Uses -linear least squares to minimize the norm of `V⋅c - y`, where `V` is -the Vandermonde matrix and `c` are the coefficients of the polynomial +linear least squares to minimize the norm `||y - V⋅β||^2`, where `V` is +the Vandermonde matrix and `β` are the coefficients of the polynomial fit. This will automatically scale your data to the [`domain`](@ref) of the polynomial type using [`mapdomain`](@ref). The default polynomial type is [`Polynomial`](@ref). -When weights are given, as either a `Number`, `Vector` or `Matrix`, -this will use weighted linear least squares. That is, the norm of -`W ⋅ (y - V ⋅ x)` is minimized. (As of now, the weights are specified -using their squares: for a number use `w^2`, for a vector `wᵢ^2`, and for a matrix - specify `W'*W`. This behavior may change in the future.) + +## Weights + +Weights may be assigned to the points by specifying a vector or matrix of weights. + +When specified as a vector, `[w₁,…,wₙ]`, the weights should be +non-negative as the minimization problem is `argmin_β Σᵢ wᵢ |yᵢ - Σⱼ +Vᵢⱼ βⱼ|² = argmin_β || √(W)⋅(y - V(x)β)||²`, where, `W` the digonal +matrix formed from `[w₁,…,wₙ]`, is used for the solution, `V` being +the Vandermonde matrix of `x` corresponding to the specified +degree. This parameterization of the weights is different from that of +`numpy.polyfit`, where the weights would be specified through +`[ω₁,ω₂,…,ωₙ] = [√w₁, √w₂,…,√wₙ]` +with the answer solving +`argminᵦ | (ωᵢ⋅yᵢ- ΣⱼVᵢⱼ(ω⋅x) βⱼ) |^2`. + +When specified as a matrix, `W`, the solution is through the normal +equations `(VᵀWV)β = (Vᵀy)`, again `V` being the Vandermonde matrix of +`x` corresponding to the specified degree. + +(In statistics, the vector case corresponds to weighted least squares, +where weights are typically given by `wᵢ = 1/σᵢ²`, the `σᵢ²` being the +variance of the measurement; the matrix specification follows that of +the generalized least squares estimator with `W = Σ⁻¹`, the inverse of +the variance-covariance matrix.) + +## large degree For fitting with a large degree, the Vandermonde matrix is exponentially ill-conditioned. The [`ArnoldiFit`](@ref) type introduces an Arnoldi orthogonalization that fixes this problem. @@ -136,7 +158,6 @@ end # Weighted linear least squares -# TODO: Breaking change for 2.0: use non-squared weights _wlstsq(vand, y, W::Number) = _wlstsq(vand, y, fill!(similar(y), W)) function _wlstsq(vand, y, w::AbstractVector) W = Diagonal(sqrt.(w)) From 93c15eab23d3eee94b99faa4a934c5e2d011dc71 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sat, 20 Feb 2021 12:07:09 -0500 Subject: [PATCH 19/38] WIP --- src/common.jl | 22 +++++++--- src/polynomials/ChebyshevT.jl | 30 +++++++++----- src/polynomials/ImmutablePolynomial.jl | 37 +++++------------ src/polynomials/LaurentPolynomial.jl | 36 +++-------------- src/polynomials/Polynomial.jl | 56 ++++++++++---------------- src/polynomials/SparsePolynomial.jl | 21 ++-------- src/polynomials/standard-basis.jl | 1 + 7 files changed, 79 insertions(+), 124 deletions(-) diff --git a/src/common.jl b/src/common.jl index 493c9107..61626975 100644 --- a/src/common.jl +++ b/src/common.jl @@ -217,7 +217,7 @@ Returns the indefinite integral of the polynomial with constant `C` when express function integrate(p::P, C) where {P <: AbstractPolynomial} ∫p = integrate(p) isnan(C) && return ⟒(P){eltype(∫p+C), indeterminate(∫p)}([C]) - ∫p + (-∫p(0) + C) + ∫p + (C - constantterm(∫p)) end """ @@ -457,7 +457,8 @@ Is the polynomial `p` a constant. """ isconstant(p::AbstractPolynomial) = degree(p) <= 0 - +# specialize this to p[0] when basis vector is 1 +constantterm(p::AbstractPolynomial{T}) where {T} = p(zero(T)) hasnan(p::AbstractPolynomial) = any(isnan, p) @@ -714,6 +715,13 @@ Base.:-(p::AbstractPolynomial, c::Number) = +(p, -c) Base.:-(c::Number, p::AbstractPolynomial) = +(-p, c) Base.:*(c::Number, p::AbstractPolynomial) = *(p, c) +# scalar operations +# no generic +, as polynomial addition falls back to this +#function Base.:+(p::P, n::Number) where {P <: AbstractPolynomial} +# p1, p2 = promote(p, n) +# return p1 + p2 +#end + function Base.:*(p::P, c::S) where {P <: AbstractPolynomial,S} _convert(p, coeffs(p) .* c) end @@ -724,17 +732,19 @@ end Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) -function Base.:+(p::P, n::Number) where {P <: AbstractPolynomial} - p1, p2 = promote(p, n) - return p1 + p2 -end function Base.:+(p1::P, p2::O) where {P <: AbstractPolynomial,O <: AbstractPolynomial} + isconstant(p1) && return constantterm(p1) + p2 + isconstant(p2) && return p1 + constantterm(p2) + check_same_variable(p1, p2) || throw(ArgumentError("polynomials have different indeterminates")) p1, p2 = promote(p1, p2) return p1 + p2 end function Base.:*(p1::P, p2::O) where {P <: AbstractPolynomial,O <: AbstractPolynomial} + isconstant(p1) && return constantterm(p1) * p2 + isconstant(p2) && return p1 * constantterm(p2) + check_same_variable(p1, p2) || throw(ArgumentError("polynomials have different indeterminates")) p1, p2 = promote(p1, p2) return p1 * p2 end diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index b18219ea..3125c1b0 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -109,6 +109,8 @@ function (ch::ChebyshevT{T})(x::S) where {T,S} return R(c0 + c1 * x) end +constantterm(p::ChebyshevT) = p[0] + function vander(P::Type{<:ChebyshevT}, x::AbstractVector{T}, n::Integer) where {T <: Number} A = Matrix{T}(undef, length(x), n + 1) A[:, 1] .= one(T) @@ -186,24 +188,32 @@ function companion(p::ChebyshevT{T}) where T return R.(comp) end -function Base.:+(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} - X′ = isconstant(p2) ? X : Y - assert_same_variable(p1, p2) +# scalar + +function Base.:+(p::ChebyshevT{T,X}, c::S) where {T,X, S<:Number} + R = promote_type(T,S) + cs = collect(R, values(p)) + cs[1] += c + ChebyshevT{T,X}(cs) +end +function Base.:+(p::P, c::T) where {T,X,P<:ChebyshevT{T,X}} + cs = collect(T, values(p)) + cs[1] += c + P(cs) +end + +function Base.:+(p1::ChebyshevT{T,X}, p2::ChebyshevT{T,X}) where {T,X} n = max(length(p1), length(p2)) - R = promote_type(T,S) - c = R[p1[i] + p2[i] for i = 0:n] - return ChebyshevT{R,X′}(c) + c = T[p1[i] + p2[i] for i = 0:n] + return ChebyshevT{T,X}(c) end -function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{S,Y}) where {T,X,S,Y} - X′ = isconstant(p2) ? X : Y - assert_same_variable(p1, p2) +function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{T,X}) where {T,X} z1 = _c_to_z(p1.coeffs) z2 = _c_to_z(p2.coeffs) prod = fastconv(z1, z2) cs = _z_to_c(prod) - ret = ChebyshevT{eltype(cs),X′}(cs) + ret = ChebyshevT(cs,X) return truncate!(ret) end diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index ed128b25..36a4a6cb 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -172,52 +172,37 @@ truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate! (p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = evalpoly(x, p.coeffs) -function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) where {T,X,N,S,Y,M} - - R = promote_type(S,T) - iszero(N) && return ImmutablePolynomial{R,Y}(coeffs(p2)) - iszero(M) && return ImmutablePolynomial{R,X}(coeffs(p1)) - - if X != Y - isconstant(p1) && return ImmutablePolynomial{T,Y,1}(p1.coeffs) + p2 - isconstant(p2) && return p1 + ImmutablePolynomial{S,X,1}(p2.coeffs) - throw(ArgumentError("Polynomials must have same variable")) - end +function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{T,X,M}) where {T,X,N,M} if N == M - cs = NTuple{N,R}(p1[i] + p2[i] for i in 0:N-1) - ImmutablePolynomial{R,X}(cs) + cs = NTuple{N,T}(p1[i] + p2[i] for i in 0:N-1) + ImmutablePolynomial{T,X}(cs) elseif N < M cs = (p2.coeffs) ⊕ (p1.coeffs) - ImmutablePolynomial{R,X,M}(convert(NTuple{M,R}, cs)) + ImmutablePolynomial{T,X,M}(convert(NTuple{M,T}, cs)) else cs = (p1.coeffs) ⊕ (p2.coeffs) - ImmutablePolynomial{R,X,N}(convert(NTuple{N,R}, cs)) + ImmutablePolynomial{T,X,N}(convert(NTuple{N,T}, cs)) end end -function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,Y,M}) where {T,X,N,S,Y,M} - isconstant(p1) && return p2 * p1[0] - isconstant(p2) && return p1 * p2[0] - assert_same_variable(p1, p2) - R = promote_type(S,T) +function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{T,X,M}) where {T,X,N,M} cs = (p1.coeffs) ⊗ (p2.coeffs) if !iszero(cs[end]) - return ImmutablePolynomial{R, X, N+M-1}(cs) + return ImmutablePolynomial{T, X, N+M-1}(cs) else n = findlast(!iszero, cs) - return ImmutablePolynomial{R, X, n}(NTuple{n,R}(cs[i] for i ∈ 1:n)) + return ImmutablePolynomial{T, X, n}(NTuple{n,T}(cs[i] for i ∈ 1:n)) end end + # Padded vector sum of two tuples assuming N > M # assume N > M. # As N ≠ M, we are assured of size of output (max(N,M)), so we generate the function @generated function ⊕(p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} - R = promote_type(T,S) - exprs = Any[nothing for i = 1:N] for i in 1:M exprs[i] = :(p1[$i] + p2[$i]) @@ -240,7 +225,6 @@ end ## convolution of two tuples @generated function ⊗(p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} P = M + N - 1 - R = promote_type(T,S) exprs = Any[nothing for i = 1 : P] for i in 1 : N for j in 1 : M @@ -267,7 +251,8 @@ function Base.:+(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X, N, S<:Number} iszero(c) && return ImmutablePolynomial{R,X,N}(convert(NTuple{N,R},p.coeffs)) N == 0 && return ImmutablePolynomial{R,X,1}(NTuple{1,R}(c)) N == 1 && return ImmutablePolynomial((p[0]+c,), X) - q = p + ImmutablePolynomial{S,X,1}((c,)) + cs = NTuple{N,R}(i == 1 ? p.coeffs[i] + c : p.coeffs[i] for i ∈ 1:N) + q = ImmutablePolynomial{R,X,N}(cs) return q end diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 6b3c9761..06489f30 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -444,56 +444,32 @@ end ## ## Poly + and * ## -function Base.:+(p1::P1, p2::P2) where {T,X,P1<:LaurentPolynomial{T,X}, S,Y, P2<:LaurentPolynomial{S,Y}} +function Base.:+(p1::P, p2::P) where {T,X,P<:LaurentPolynomial{T,X}} - R = promote_type(T,S) - - if isconstant(p1) - i₁ = firstindex(p1) - q2 = LaurentPolynomial{R,Y}(p2.coeffs, p2.m[]) - q2[i₁] += p1[i₁] - chop!(q2) - return q2 - elseif isconstant(p2) - i₂ = firstindex(p2) - q1 = LaurentPolynomial{R,X}(p1.coeffs, p1.m[]) - q1[i₂] += p2[i₂] - chop!(q1) - return q1 - end - - assert_same_variable(p1, p2) m1,n1 = (extrema ∘ degreerange)(p1) m2,n2 = (extrema ∘ degreerange)(p2) m,n = min(m1,m2), max(n1, n2) - as = zeros(R, length(m:n)) + as = zeros(T, length(m:n)) for i in m:n as[1 + i-m] = p1[i] + p2[i] end - q = LaurentPolynomial{R,X}(as, m) + q = P(as, m) chop!(q) return q end -function Base.:*(p1::LaurentPolynomial{T,X}, p2::LaurentPolynomial{S,Y}) where {T,X,S,Y} - - isconstant(p1) && return p2 * p1[0] - isconstant(p2) && return p1 * p2[0] - - assert_same_variable(p1, p2) - - R = promote_type(T,S) +function Base.:*(p1::P, p2::P) where {T,X,P<:LaurentPolynomial{T,X}} m1,n1 = (extrema ∘ degreerange)(p1) m2,n2 = (extrema ∘ degreerange)(p2) m,n = m1 + m2, n1+n2 - as = zeros(R, length(m:n)) + as = zeros(T, length(m:n)) for i in eachindex(p1) p1ᵢ = p1[i] for j in eachindex(p2) @@ -501,7 +477,7 @@ function Base.:*(p1::LaurentPolynomial{T,X}, p2::LaurentPolynomial{S,Y}) where { end end - p = LaurentPolynomial{R,X}(as, m) + p = P(as, m) chop!(p) return p diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 5dc35cb0..f8158f60 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -93,51 +93,37 @@ function Base.:*(p::P, c::S) where {T, X, P <: Polynomial{T,X} , S <: Number} iszero(as[end]) ? Q(as) : Q(Val(false), as) end -function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} - isconstant(p1) && return p2 + p1[0] - isconstant(p2) && return p1 + p2[0] - assert_same_variable(p1, p2) - X = indeterminate(p1) - +function Base.:+(p1::P, p2::P) where {T,X, P<:Polynomial{T,X}} n1, n2 = length(p1), length(p2) - R = promote_type(T,S) - - c = zeros(R, max(n1, n2)) - if n1 >= n2 - c .= p1.coeffs - for i in eachindex(p2.coeffs) - c[i] += p2.coeffs[i] + if n1 > n2 + cs = copy(p1.coeffs) + for (i,v) ∈ pairs(p2) + cs[i+1] += v end - else - c .= p2.coeffs - for i in eachindex(p1.coeffs) - c[i] += p1.coeffs[i] + pq = P(Val(false), cs) + elseif n1 < n2 + cs = copy(p2.coeffs) + for (i,v) ∈ pairs(p1) + cs[i+1] += v end + pq = P(Val(false),cs) + else + cs = [p1.coeffs[i] + p2.coeffs[i] for i ∈ 1:n1] + pq = iszero(cs[end]) ? P(cs) : P(Val(false), cs) end - Q = Polynomial{R,X} - return iszero(c[end]) ? Q(c) : Q(Val(false), c) + return pq end - -function Base.:*(p1::Polynomial{T}, p2::Polynomial{S}) where {T,S} +function Base.:*(p1::P, p2::P) where {T,X, P<:Polynomial{T,X}} n, m = length(p1)-1, length(p2)-1 # not degree, so pNULL works - X, Y = indeterminate(p1), indeterminate(p2) - R = promote_type(T, S) - if n > 0 && m > 0 - assert_same_variable(p1, p2) - c = zeros(R, m + n + 1) - for i in 0:n, j in 0:m - @inbounds c[i + j + 1] += p1[i] * p2[j] - end - Q = Polynomial{R,X} - return iszero(c[end]) ? Q(c) : Q(Val(false), c) - elseif n <= 0 - return Polynomial{R, Y}(p2.coeffs * p1[0]) - else - return Polynomial{R, X}(p1.coeffs * p2[0]) + c = zeros(T, m + n + 1) + for i in 0:n, j in 0:m + @inbounds c[i + j + 1] += p1[i] * p2[j] end + return iszero(c[end]) ? P(c) : P(Val(false), c) + end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 7b5c1df3..ff86e05a 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -207,15 +207,9 @@ end -function Base.:+(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T, X, S, Y} +function Base.:+(p1::P, p2::P) where {T, X, P<:SparsePolynomial{T,X}} - isconstant(p1) && return p2 + p1[0] - isconstant(p2) && return p1 + p2[0] - - assert_same_variable(p1, p2) - - R = promote_type(T,S) - p = zero(SparsePolynomial{R,X}) + p = zero(SparsePolynomial{T,X}) # this allocates in the union # for i in union(eachindex(p1), eachindex(p2)) @@ -249,16 +243,9 @@ function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} end -function Base.:*(p1::SparsePolynomial{T,X}, p2::SparsePolynomial{S,Y}) where {T,X,S,Y} - - isconstant(p1) && return p2 * p1[0] - isconstant(p2) && return p1 * p2[0] - assert_same_variable(p1, p2) - - R = promote_type(T,S) - P = SparsePolynomial +function Base.:*(p1::P, p2::P) where {T,X,P<:SparsePolynomial{T,X}} - p = zero(P{R, X}) + p = zero(P) for i in eachindex(p1) p1ᵢ = p1[i] for j in eachindex(p2) diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 04093f2c..c4c589a2 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -17,6 +17,7 @@ end # allows broadcast issue #209 evalpoly(x, p::StandardBasisPolynomial) = p(x) +constantterm(p::StandardBasisPolynomial) = p.coeffs[1] domain(::Type{<:StandardBasisPolynomial}) = Interval(-Inf, Inf) mapdomain(::Type{<:StandardBasisPolynomial}, x::AbstractArray) = x From 264189d6fa4761913178aa5db7e1f4b62c371c00 Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 22 Feb 2021 10:00:39 -0500 Subject: [PATCH 20/38] WIP --- src/common.jl | 101 ++++++++++++++++++------- src/polynomials/ChebyshevT.jl | 5 +- src/polynomials/ImmutablePolynomial.jl | 26 ++++--- src/polynomials/LaurentPolynomial.jl | 37 +++++---- src/polynomials/Polynomial.jl | 14 ++-- src/polynomials/SparsePolynomial.jl | 30 ++++---- src/polynomials/standard-basis.jl | 29 +++---- 7 files changed, 144 insertions(+), 98 deletions(-) diff --git a/src/common.jl b/src/common.jl index 61626975..3927eb01 100644 --- a/src/common.jl +++ b/src/common.jl @@ -313,6 +313,10 @@ function assert_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) check_same_variable(p,q) || throw(ArgumentError("Polynomials have different indeterminates")) end +function assert_same_variable(X::Symbol, Y::Symbol) + X == Y || throw(ArgumentError("Polynomials have different indeterminates")) +end + #= Linear Algebra =# """ @@ -441,6 +445,20 @@ Return the coefficient vector `[a_0, a_1, ..., a_n]` of a polynomial. """ coeffs(p::AbstractPolynomial) = p.coeffs + + +# specialize this to p[0] when basis vector is 1 +""" + constantterm(p::AbstractPolynomial) + +return `p(0)`, the constant term in the standard basis +""" +constantterm(p::AbstractPolynomial{T}) where {T} = p(zero(T)) + + +hasnan(p::AbstractPolynomial) = any(isnan, p) + + """ degree(::AbstractPolynomial) @@ -457,12 +475,6 @@ Is the polynomial `p` a constant. """ isconstant(p::AbstractPolynomial) = degree(p) <= 0 -# specialize this to p[0] when basis vector is 1 -constantterm(p::AbstractPolynomial{T}) where {T} = p(zero(T)) - - -hasnan(p::AbstractPolynomial) = any(isnan, p) - """ domain(::Type{<:AbstractPolynomial}) @@ -507,9 +519,10 @@ Base.broadcastable(p::AbstractPolynomial) = Ref(p) # getindex function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T <: Number} - idx < 0 && throw(BoundsError(p, idx)) - idx ≥ length(p) && return zero(T) - return coeffs(p)[idx + 1] + idx < firstindex(p) && throw(BoundsError(p, idx)) + idx ≥ lastindex(p) && return zero(T) + return p.coeffs[idx-firstindex(p)+1] +# return coeffs(p)[idx + 1] end Base.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) Base.getindex(p::AbstractPolynomial, indices) = [getindex(p, i) for i in indices] @@ -616,9 +629,6 @@ identity =# Base.copy(p::P) where {P <: AbstractPolynomial} = _convert(p, copy(coeffs(p))) Base.hash(p::AbstractPolynomial, h::UInt) = hash(indeterminate(p), hash(coeffs(p), h)) -#= -zero, one, variable, basis =# - # get symbol of polynomial. (e.g. `:x` from 1x^2 + 2x^3... #_indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T, X}} = X _indeterminate(::Type{P}) where {P <: AbstractPolynomial} = nothing @@ -635,6 +645,9 @@ function indeterminate(PP::Type{P}, x::Symbol) where {P <: AbstractPolynomial} X = _indeterminate(PP) == nothing ? x : _indeterminate(PP) end +#= +zero, one, variable, basis =# + """ @@ -716,39 +729,72 @@ Base.:-(c::Number, p::AbstractPolynomial) = +(-p, c) Base.:*(c::Number, p::AbstractPolynomial) = *(p, c) # scalar operations -# no generic +, as polynomial addition falls back to this +# no generic p+c, as polynomial addition falls back to scalar ops #function Base.:+(p::P, n::Number) where {P <: AbstractPolynomial} # p1, p2 = promote(p, n) # return p1 + p2 #end -function Base.:*(p::P, c::S) where {P <: AbstractPolynomial,S} - _convert(p, coeffs(p) .* c) + +Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) + + +## fallback addition +## These are pretty slow and not the most flexible, as a vector storage is assumed in p+q +## Subtypes will likely want to implement both: +## +(p::P,c::Number) and +(p::P, q::P) where {T,X,P<:SubtypePolynomial{T,X}} + +# scalar +Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = p + c * one(P) + +function Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} + R = promote_type(T,S) + q = convert(⟒(P){R,X}, p) + q + R(c) end -function Base.:/(p::P, c::S) where {P <: AbstractPolynomial,S} - _convert(p, coeffs(p) ./ c) +# polynomial +function Base.:+(p::P, q::Q) where {T,X,P <: AbstractPolynomial{T,X}, S,Y,Q <: AbstractPolynomial{S,Y}} + isconstant(p) && return constantterm(p) + q + isconstant(q) && return p + constantterm(q) + assert_same_variable(X,Y) + sum(promote(p,q)) end -Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) +## only works for types where storage is Vector: `[a₀, a₁, …, aₙ]`. +function Base.:+(p::P, q::P) where {T,X,P<:AbstractPolynomial{T,X}} + isa(p.coeffs, Vector{T}) || throw(MethodError("No default addition is defined")) + n1, n2 = length(p), length(q) + n1 < n2 && return q + p + cs = zeros(T, n1) + for i in 1:n2 + cs[i] = p.coeffs[i] + q.coeffs[i] + end + for i in (n2+1):n1 + cs[i] = p.coeffs[i] + end + return ⟒(P){T,X}(cs) +end +## scalar *, / +function Base.:*(p::P, c::S) where {P <: AbstractPolynomial,S} + _convert(p, coeffs(p) .* c) +end -function Base.:+(p1::P, p2::O) where {P <: AbstractPolynomial,O <: AbstractPolynomial} - isconstant(p1) && return constantterm(p1) + p2 - isconstant(p2) && return p1 + constantterm(p2) - check_same_variable(p1, p2) || throw(ArgumentError("polynomials have different indeterminates")) - p1, p2 = promote(p1, p2) - return p1 + p2 +function Base.:/(p::P, c::S) where {P <: AbstractPolynomial,S} + _convert(p, coeffs(p) ./ c) end -function Base.:*(p1::P, p2::O) where {P <: AbstractPolynomial,O <: AbstractPolynomial} +## polynomial p*q +function Base.:*(p1::P, p2::O) where {T,X,P <: AbstractPolynomial{T,X},S,Y,O <: AbstractPolynomial{S,Y}} isconstant(p1) && return constantterm(p1) * p2 isconstant(p2) && return p1 * constantterm(p2) - check_same_variable(p1, p2) || throw(ArgumentError("polynomials have different indeterminates")) + assert_same_variable(X, Y) p1, p2 = promote(p1, p2) return p1 * p2 end + Base.:^(p::AbstractPolynomial, n::Integer) = Base.power_by_squaring(p, n) function Base.divrem(num::P, den::O) where {P <: AbstractPolynomial,O <: AbstractPolynomial} @@ -817,8 +863,7 @@ function Base.isapprox(p1::AbstractPolynomial{T,X}, p2::AbstractPolynomial{S,Y}; rtol::Real = (Base.rtoldefault(T, S, 0)), atol::Real = 0,) where {T,X,S,Y} -# p1, p2 = promote(p1, p2) - check_same_variable(p1, p2) || throw(ArgumentError("p1 and p2 must have same var")) + assert_same_variable(p1, p2) # copy over from abstractarray.jl Δ = norm(p1-p2) if isfinite(Δ) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 3125c1b0..6bdd84fc 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -66,6 +66,7 @@ end Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) = p(variable(C)) + domain(::Type{<:ChebyshevT}) = Interval(-1, 1) function variable(P::Type{<:ChebyshevT}, var::SymbolLike) X′ = _indeterminate(P) @@ -109,7 +110,7 @@ function (ch::ChebyshevT{T})(x::S) where {T,S} return R(c0 + c1 * x) end -constantterm(p::ChebyshevT) = p[0] +constantterm(p::ChebyshevT) = p(0) function vander(P::Type{<:ChebyshevT}, x::AbstractVector{T}, n::Integer) where {T <: Number} A = Matrix{T}(undef, length(x), n + 1) @@ -193,7 +194,7 @@ function Base.:+(p::ChebyshevT{T,X}, c::S) where {T,X, S<:Number} R = promote_type(T,S) cs = collect(R, values(p)) cs[1] += c - ChebyshevT{T,X}(cs) + ChebyshevT{R,X}(cs) end function Base.:+(p::P, c::T) where {T,X,P<:ChebyshevT{T,X}} cs = collect(T, values(p)) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index 36a4a6cb..c3b887b4 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -97,10 +97,10 @@ Base.collect(p::P) where {P <: ImmutablePolynomial} = [pᵢ for pᵢ ∈ p] Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p)) ## defining these speeds things up -function Base.zero(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) - R = eltype(P) - ImmutablePolynomial{R,Symbol(var),0}(NTuple{0,R}()) -end +#function Base.zero(P::Type{<:ImmutablePolynomial})#, var::SymbolLike=indeterminate(P)) +# R = eltype(P) +# ImmutablePolynomial{R,Symbol(var),0}(NTuple{0,R}()) +#end function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) R = eltype(P) @@ -171,11 +171,10 @@ truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate! (p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = evalpoly(x, p.coeffs) - function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{T,X,M}) where {T,X,N,M} if N == M - cs = NTuple{N,T}(p1[i] + p2[i] for i in 0:N-1) + cs = (p1.coeffs) ⊕ (p2.coeffs) #NTuple{N,T}(p1[i] + p2[i] for i in 0:N-1) ImmutablePolynomial{T,X}(cs) elseif N < M cs = (p2.coeffs) ⊕ (p1.coeffs) @@ -187,13 +186,18 @@ function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{T,X,M}) end -function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{T,X,M}) where {T,X,N,M} +function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} + R = promote_type(T,S) + Q = ImmutablePolynomial{R,X} + + (iszero(N) || iszero(M)) && return zero(Q) + cs = (p1.coeffs) ⊗ (p2.coeffs) if !iszero(cs[end]) - return ImmutablePolynomial{T, X, N+M-1}(cs) + return Q{N+M-1}(cs) else n = findlast(!iszero, cs) - return ImmutablePolynomial{T, X, n}(NTuple{n,T}(cs[i] for i ∈ 1:n)) + return Q{n}(NTuple{n,T}(cs[i] for i ∈ 1:n)) end end @@ -251,8 +255,10 @@ function Base.:+(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X, N, S<:Number} iszero(c) && return ImmutablePolynomial{R,X,N}(convert(NTuple{N,R},p.coeffs)) N == 0 && return ImmutablePolynomial{R,X,1}(NTuple{1,R}(c)) N == 1 && return ImmutablePolynomial((p[0]+c,), X) - cs = NTuple{N,R}(i == 1 ? p.coeffs[i] + c : p.coeffs[i] for i ∈ 1:N) + + cs = convert(NTuple{N,R},p.coeffs) ⊕ NTuple{1,R}(c) q = ImmutablePolynomial{R,X,N}(cs) + return q end diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 06489f30..7550c909 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -153,14 +153,14 @@ end ## ## generic functions ## -function Base.extrema(p::LaurentPolynomial) - Base.depwarn("`extrema(::LaurentPolynomial)` is deprecated. Use `(firstindex(p), lastindex(p))`", :extrema) - (p.m[], p.n[]) -end -function Base.range(p::LaurentPolynomial) - Base.depwarn("`range(::LaurentPolynomial)` is deprecated. Use `firstindex(p):lastindex(p)`", :range) - p.m[]:p.n[] -end +# function Base.extrema(p::LaurentPolynomial) +# Base.depwarn("`extrema(::LaurentPolynomial)` is deprecated. Use `(firstindex(p), lastindex(p))`", :extrema) +# (p.m[], p.n[]) +# end +# function Base.range(p::LaurentPolynomial) +# Base.depwarn("`range(::LaurentPolynomial)` is deprecated. Use `firstindex(p):lastindex(p)`", :range) +# p.m[]:p.n[] +# end function Base.inv(p::LaurentPolynomial{T, X}) where {T, X} m,n = (extrema∘degreerange)(p) @@ -184,15 +184,20 @@ Base.zero(::Type{LaurentPolynomial{T}}, var=Symbollike=:x) where {T} = Laurent Base.zero(::Type{LaurentPolynomial}, var=Symbollike=:x) = zero(LaurentPolynomial{Float64, Symbol(var)}) Base.zero(p::P, var=Symbollike=:x) where {P <: LaurentPolynomial} = zero(P, var) - -# get/set index. Work with offset -function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T <: Number} - m,n = (extrema ∘ degreerange)(p) - i = idx - m + 1 - (i < 1 || i > (n-m+1)) && return zero(T) - p.coeffs[i] +function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T} + m,M = firstindex(p), lastindex(p) + m <= idx <= M || return zero(T) + p.coeffs[idx-m+1] end +# # get/set index. Work with offset +# function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T <: Number} +# m,n = (extrema ∘ degreerange)(p) +# i = idx - m + 1 +# (i < 1 || i > (n-m+1)) && return zero(T) +# p.coeffs[i] +# end + # extend if out of bounds function Base.setindex!(p::LaurentPolynomial{T}, value::Number, idx::Int) where {T} @@ -432,7 +437,7 @@ end -# scalar operattoinis +# scalar operations # needed as standard-basis defn. assumes basis 1, x, x², ... function Base.:+(p::LaurentPolynomial{T,X}, c::S) where {T, X, S <: Number} R = promote_type(T,S) diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index f8158f60..57cac85f 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -51,6 +51,7 @@ end @register Polynomial + """ (p::Polynomial)(x) @@ -76,8 +77,7 @@ julia> p.(0:3) """ (p::Polynomial{T})(x::S) where {T,S} = evalpoly(x, coeffs(p)) - -# scalar +,* faster than standard-basis/common versions +# scalar +,* faster than standard-basis/common versions as it avoids a copy function Base.:+(p::P, c::S) where {T, X, P <: Polynomial{T, X}, S<:Number} R = promote_type(T, S) Q = Polynomial{R,X} @@ -102,7 +102,7 @@ function Base.:+(p1::P, p2::P) where {T,X, P<:Polynomial{T,X}} end pq = P(Val(false), cs) elseif n1 < n2 - cs = copy(p2.coeffs) + cs = copy(p2.coeffs) # repeat; a bit faster than returning p2 + p1 for (i,v) ∈ pairs(p1) cs[i+1] += v end @@ -118,12 +118,8 @@ end function Base.:*(p1::P, p2::P) where {T,X, P<:Polynomial{T,X}} - n, m = length(p1)-1, length(p2)-1 # not degree, so pNULL works - c = zeros(T, m + n + 1) - for i in 0:n, j in 0:m - @inbounds c[i + j + 1] += p1[i] * p2[j] - end - + c = fastconv(p1.coeffs, p2.coeffs) return iszero(c[end]) ? P(c) : P(Val(false), c) end + diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index ff86e05a..24c8254b 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -49,7 +49,7 @@ struct SparsePolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} new{T, X}(c) end function SparsePolynomial{T,X}(checked::Val{false}, coeffs::AbstractDict{Int, T}) where {T <: Number, X} - new{T,X}(convert(Dict{Int,S}, coeffs)) + new{T,X}(copy(coeffs)) end end @@ -209,7 +209,7 @@ end function Base.:+(p1::P, p2::P) where {T, X, P<:SparsePolynomial{T,X}} - p = zero(SparsePolynomial{T,X}) + p = zero(P) # this allocates in the union # for i in union(eachindex(p1), eachindex(p2)) @@ -217,38 +217,42 @@ function Base.:+(p1::P, p2::P) where {T, X, P<:SparsePolynomial{T,X}} # end # this seems faster - for i in eachindex(p1) - p[i] = p1[i] + p2[i] + for i in keys(p1) #eachindex(p1) + @inbounds p[i] = p1[i] + p2[i] end - for i in eachindex(p2) + for i in keys(p2) #eachindex(p2) if iszero(p[i]) @inbounds p[i] = p1[i] + p2[i] end end - return p end + function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} R = promote_type(T,S) - P = SparsePolynomial + P = SparsePolynomial{R,X} - D = Dict{Int, R}(kv for kv ∈ p.coeffs) - D[0] = get(D,0,zero(R)) + c - - return SparsePolynomial{R,X}(D) + #D = Dict{Int, R}(kv for kv ∈ p.coeffs) + D = Dict{Int, R}() + for (k,v) ∈ pairs(p) + @inbounds D[k] = v + end + @inbounds D[0] = get(D,0,zero(R)) + c + iszero(D[0]) && pop!(D,0) + return P(Val(false),D) end function Base.:*(p1::P, p2::P) where {T,X,P<:SparsePolynomial{T,X}} p = zero(P) - for i in eachindex(p1) + for i in keys(p1) #eachindex(p1) p1ᵢ = p1[i] - for j in eachindex(p2) + for j in keys(p2) #eachindex(p2) @inbounds p[i+j] = muladd(p1ᵢ, p2[j], p[i+j]) end end diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index c4c589a2..8c5e16f1 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -17,14 +17,11 @@ end # allows broadcast issue #209 evalpoly(x, p::StandardBasisPolynomial) = p(x) -constantterm(p::StandardBasisPolynomial) = p.coeffs[1] +constantterm(p::StandardBasisPolynomial) = p[0] domain(::Type{<:StandardBasisPolynomial}) = Interval(-Inf, Inf) mapdomain(::Type{<:StandardBasisPolynomial}, x::AbstractArray) = x -## generic test if polynomial `p` is a constant -isconstant(p::StandardBasisPolynomial) = degree(p) <= 0 - function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) if isa(q, P) return q @@ -54,14 +51,6 @@ function fromroots(P::Type{<:StandardBasisPolynomial}, r::AbstractVector{T}; var end -function Base.:+(p::P, c::S) where {T, P <: StandardBasisPolynomial{T}, S<:Number} - R = promote_type(T,S) - as = R[c for c in coeffs(p)] - as[1] += c - _convert(p, as) -end - - function derivative(p::P, order::Integer = 1) where {T, X, P <: StandardBasisPolynomial{T, X}} order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) @@ -88,17 +77,17 @@ function integrate(p::P) where {T, X, P <: StandardBasisPolynomial{T, X}} R = eltype(one(T)/1) Q = ⟒(P){R,X} - if hasnan(p) - return Q([NaN]) - end + hasnan(p) && return Q([NaN]) + iszero(p) && return zero(Q) n = length(p) - a2 = Vector{R}(undef, n + 1) - a2[1] = zero(R) - @inbounds for i in 1:n - a2[i + 1] = p[i - 1] / i + as = Vector{R}(undef, n + 1) + as[1] = zero(R) + for (i, pᵢ) ∈ pairs(p) + i′ = i + 1 + @inbounds as[i′+1] = pᵢ/i′ end - return Q(a2) + return Q(as) end function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} From b9324b60dd66348cb802382dadbaed9e937c6447 Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 22 Feb 2021 14:52:03 -0500 Subject: [PATCH 21/38] WIP --- src/common.jl | 84 +++++++++++++++++++++----- src/polynomials/ImmutablePolynomial.jl | 23 ++++--- src/polynomials/Polynomial.jl | 32 +++++----- src/polynomials/SparsePolynomial.jl | 48 +++++++++------ 4 files changed, 129 insertions(+), 58 deletions(-) diff --git a/src/common.jl b/src/common.jl index 3927eb01..cc7ea470 100644 --- a/src/common.jl +++ b/src/common.jl @@ -520,7 +520,7 @@ Base.broadcastable(p::AbstractPolynomial) = Ref(p) # getindex function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T <: Number} idx < firstindex(p) && throw(BoundsError(p, idx)) - idx ≥ lastindex(p) && return zero(T) + idx > lastindex(p) && return zero(T) return p.coeffs[idx-firstindex(p)+1] # return coeffs(p)[idx + 1] end @@ -739,12 +739,12 @@ Base.:*(c::Number, p::AbstractPolynomial) = *(p, c) Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) -## fallback addition -## These are pretty slow and not the most flexible, as a vector storage is assumed in p+q +## addition +## Fall back addition. ## Subtypes will likely want to implement both: -## +(p::P,c::Number) and +(p::P, q::P) where {T,X,P<:SubtypePolynomial{T,X}} +## +(p::P,c::Number) and +(p::P, q::Q) where {T,S,X,P<:SubtypePolynomial{T,X},Q<:SubtypePolynomial{S,X}} -# scalar +# polynomial + scalar Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = p + c * one(P) function Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} @@ -753,29 +753,85 @@ function Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} q + R(c) end -# polynomial +# polynomial + polynomial function Base.:+(p::P, q::Q) where {T,X,P <: AbstractPolynomial{T,X}, S,Y,Q <: AbstractPolynomial{S,Y}} + isconstant(p) && return constantterm(p) + q isconstant(q) && return p + constantterm(q) assert_same_variable(X,Y) + sum(promote(p,q)) + end -## only works for types where storage is Vector: `[a₀, a₁, …, aₙ]`. +# Works when p,q of same type. For Immutable, must remove N,M bit; function Base.:+(p::P, q::P) where {T,X,P<:AbstractPolynomial{T,X}} - isa(p.coeffs, Vector{T}) || throw(MethodError("No default addition is defined")) + +# isconstant(p) && return constantterm(p) + q +# isconstant(q) && return p + constantterm(q) + + cs = degree(p) >= degree(q) ? ⊕(P, p.coeffs, q.coeffs) : ⊕(P, q.coeffs, p.coeffs) + return P(cs) +end + +# add assuming n1 >= n2 +function ⊕(P::Type{<:AbstractPolynomial}, p::Vector{T}, q::Vector{S}) where {T,S} n1, n2 = length(p), length(q) - n1 < n2 && return q + p - cs = zeros(T, n1) + R = promote_type(T,S) + + cs = collect(R,p) for i in 1:n2 - cs[i] = p.coeffs[i] + q.coeffs[i] + cs[i] += q[i] + end + return cs +end + +# Padded vector sum of two tuples assuming N > M +# assume N ≥ M. +@generated function ⊕(P::Type{<:AbstractPolynomial}, p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} + + exprs = Any[nothing for i = 1:N] + for i in 1:M + exprs[i] = :(p1[$i] + p2[$i]) + end + for i in M+1:N + exprs[i] =:(p1[$i]) end - for i in (n2+1):n1 - cs[i] = p.coeffs[i] + + return quote + Base.@_inline_meta + tuple($(exprs...)) + end + +end + +function ⊕(P::Type{<:AbstractPolynomial}, p1::Dict{Int,T}, p2::Dict{Int,S}) where {T,S} + + R = promote_type(T,S) + p = Dict{Int, R}() + + + # this allocates in the union +# for i in union(eachindex(p1), eachindex(p2)) +# p[i] = p1[i] + p2[i] +# end + + # this seems faster + for i in keys(p1) #eachindex(p1) + @inbounds p[i] = p1[i] + get(p2,i,zero(R)) + end + for i in keys(p2) #eachindex(p2) + if iszero(get(p,i,zero(R))) + @inbounds p[i] = get(p1,i,zero(R)) + p2[i] + end end - return ⟒(P){T,X}(cs) + + return p + end +## -- multiplication + ## scalar *, / function Base.:*(p::P, c::S) where {P <: AbstractPolynomial,S} _convert(p, coeffs(p) .* c) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index c3b887b4..72fc26ad 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -171,17 +171,26 @@ truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate! (p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = evalpoly(x, p.coeffs) -function Base.:+(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{T,X,M}) where {T,X,N,M} +function Base.:+(p1::P, p2::Q) where {T,X,N,P<:ImmutablePolynomial{T,X,N}, + S, M,Q<:ImmutablePolynomial{S,X,M}} + R = promote_type(T,S) if N == M - cs = (p1.coeffs) ⊕ (p2.coeffs) #NTuple{N,T}(p1[i] + p2[i] for i in 0:N-1) - ImmutablePolynomial{T,X}(cs) + cs = ⊕(P, p1.coeffs, p2.coeffs) + return ImmutablePolynomial{R,X}(R.(cs)) + #cs = (p1.coeffs) ⊕ (p2.coeffs) #NTuple{N,T}(p1[i] + p2[i] for i in 0:N-1) + #ImmutablePolynomial{T,X}(cs) elseif N < M - cs = (p2.coeffs) ⊕ (p1.coeffs) - ImmutablePolynomial{T,X,M}(convert(NTuple{M,T}, cs)) + cs = ⊕(P, p2.coeffs, p1.coeffs) + return ImmutablePolynomial{R,X,M}(R.(cs)) + + #cs = (p2.coeffs) ⊕ (p1.coeffs) + #ImmutablePolynomial{T,X,M}(convert(NTuple{M,T}, cs)) else - cs = (p1.coeffs) ⊕ (p2.coeffs) - ImmutablePolynomial{T,X,N}(convert(NTuple{N,T}, cs)) + cs = ⊕(P, p1.coeffs, p2.coeffs) + return ImmutablePolynomial{R,X,N}(R.(cs)) + #cs = (p1.coeffs) ⊕ (p2.coeffs) + #ImmutablePolynomial{T,X,N}(convert(NTuple{N,T}, cs)) end end diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 57cac85f..86d0266d 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -93,29 +93,25 @@ function Base.:*(p::P, c::S) where {T, X, P <: Polynomial{T,X} , S <: Number} iszero(as[end]) ? Q(as) : Q(Val(false), as) end -function Base.:+(p1::P, p2::P) where {T,X, P<:Polynomial{T,X}} +# implement, as not copying speeds up multiplication by a factor of 2 or so +# over the default +function Base.:+(p1::P1, p2::P2) where {T,X, P1<:Polynomial{T,X}, + S, P2<:Polynomial{S,X}} n1, n2 = length(p1), length(p2) - if n1 > n2 - cs = copy(p1.coeffs) - for (i,v) ∈ pairs(p2) - cs[i+1] += v - end - pq = P(Val(false), cs) - elseif n1 < n2 - cs = copy(p2.coeffs) # repeat; a bit faster than returning p2 + p1 - for (i,v) ∈ pairs(p1) - cs[i+1] += v - end - pq = P(Val(false),cs) + R = promote_type(T,S) + Q = Polynomial{R,X} + if n1 == n2 + cs = ⊕(P1, p1.coeffs, p2.coeffs) + return iszero(cs[end]) ? Q(cs) : Q(Val(false), cs) + elseif n1 > n2 + cs = ⊕(P1, p1.coeffs, p2.coeffs) else - cs = [p1.coeffs[i] + p2.coeffs[i] for i ∈ 1:n1] - pq = iszero(cs[end]) ? P(cs) : P(Val(false), cs) + cs = ⊕(P1, p2.coeffs, p1.coeffs) end - return pq - + Q(Val(false), cs) end - + function Base.:*(p1::P, p2::P) where {T,X, P<:Polynomial{T,X}} c = fastconv(p1.coeffs, p2.coeffs) diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 24c8254b..32c0de1e 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -49,7 +49,7 @@ struct SparsePolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} new{T, X}(c) end function SparsePolynomial{T,X}(checked::Val{false}, coeffs::AbstractDict{Int, T}) where {T <: Number, X} - new{T,X}(copy(coeffs)) + new{T,X}(coeffs) end end @@ -205,30 +205,40 @@ function Base.map(fn, p::P, args...) where {P <: SparsePolynomial} _convert(p, Dict(Pair.(ks, vs′))) end +# Implement over fallback. A bit faster for T != S +function Base.:+(p1::P1, p2::P2) where {T,X, P1<:SparsePolynomial{T,X}, + S, P2<:SparsePolynomial{S,X}} - -function Base.:+(p1::P, p2::P) where {T, X, P<:SparsePolynomial{T,X}} + R = promote_type(T,S) + Q = SparsePolynomial{R,X} + + d1, d2 = degree(p1), degree(p2) + cs = d1 > d2 ? ⊕(P1, p1.coeffs, p2.coeffs) : ⊕(P1, p2.coeffs, p1.coeffs) + return d1 != d2 ? Q(Val(false), cs) : Q(cs) +end - p = zero(P) +# function Base.:+(p1::P, p2::P) where {T, X, P<:SparsePolynomial{T,X}} - # this allocates in the union -# for i in union(eachindex(p1), eachindex(p2)) -# p[i] = p1[i] + p2[i] -# end +# p = zero(P) - # this seems faster - for i in keys(p1) #eachindex(p1) - @inbounds p[i] = p1[i] + p2[i] - end - for i in keys(p2) #eachindex(p2) - if iszero(p[i]) - @inbounds p[i] = p1[i] + p2[i] - end - end +# # this allocates in the union +# # for i in union(eachindex(p1), eachindex(p2)) +# # p[i] = p1[i] + p2[i] +# # end - return p +# # this seems faster +# for i in keys(p1) #eachindex(p1) +# @inbounds p[i] = p1[i] + p2[i] +# end +# for i in keys(p2) #eachindex(p2) +# if iszero(p[i]) +# @inbounds p[i] = p1[i] + p2[i] +# end +# end -end +# return p + +# end function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} From 5349be17dee2afce2af741b34a44953bc796b791 Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 22 Feb 2021 16:32:09 -0500 Subject: [PATCH 22/38] work on promotion for + and * operations --- src/common.jl | 39 +++---- src/polynomials/ImmutablePolynomial.jl | 136 ++++++++----------------- src/polynomials/LaurentPolynomial.jl | 3 + src/polynomials/Polynomial.jl | 6 +- src/polynomials/SparsePolynomial.jl | 74 +++++--------- src/polynomials/standard-basis.jl | 44 ++++++++ 6 files changed, 134 insertions(+), 168 deletions(-) diff --git a/src/common.jl b/src/common.jl index cc7ea470..3d28f41b 100644 --- a/src/common.jl +++ b/src/common.jl @@ -743,6 +743,7 @@ Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) ## Fall back addition. ## Subtypes will likely want to implement both: ## +(p::P,c::Number) and +(p::P, q::Q) where {T,S,X,P<:SubtypePolynomial{T,X},Q<:SubtypePolynomial{S,X}} +## though the default for poly+poly isn't terrible # polynomial + scalar Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = p + c * one(P) @@ -764,30 +765,31 @@ function Base.:+(p::P, q::Q) where {T,X,P <: AbstractPolynomial{T,X}, S,Y,Q <: A end -# Works when p,q of same type. For Immutable, must remove N,M bit; +# Works when p,q of same type. +# For Immutable, must remove N,M bit; +# for others, can widen to Type{T,X}, Type{S,X} to avoid a promotion function Base.:+(p::P, q::P) where {T,X,P<:AbstractPolynomial{T,X}} - -# isconstant(p) && return constantterm(p) + q -# isconstant(q) && return p + constantterm(q) - cs = degree(p) >= degree(q) ? ⊕(P, p.coeffs, q.coeffs) : ⊕(P, q.coeffs, p.coeffs) return P(cs) end -# add assuming n1 >= n2 -function ⊕(P::Type{<:AbstractPolynomial}, p::Vector{T}, q::Vector{S}) where {T,S} - n1, n2 = length(p), length(q) +# addition of polynomials is just vector space addition, so can be done regardless +# of basis, as long as the same. These ⊕ methods try to find a performant means to add +# to sets of coefficients based on the storage type. These assume n1 >= n2 +function ⊕(P::Type{<:AbstractPolynomial}, p1::Vector{T}, p2::Vector{S}) where {T,S} + + n1, n2 = length(p1), length(p2) R = promote_type(T,S) - cs = collect(R,p) + cs = collect(R,p1) for i in 1:n2 - cs[i] += q[i] + cs[i] += p2[i] end + return cs end -# Padded vector sum of two tuples assuming N > M -# assume N ≥ M. +# Padded vector sum of two tuples assuming N ≥ M @generated function ⊕(P::Type{<:AbstractPolynomial}, p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} exprs = Any[nothing for i = 1:N] @@ -805,6 +807,7 @@ end end +# addition when a dictionary is used for storage function ⊕(P::Type{<:AbstractPolynomial}, p1::Dict{Int,T}, p2::Dict{Int,S}) where {T,S} R = promote_type(T,S) @@ -816,16 +819,15 @@ function ⊕(P::Type{<:AbstractPolynomial}, p1::Dict{Int,T}, p2::Dict{Int,S}) wh # p[i] = p1[i] + p2[i] # end - # this seems faster - for i in keys(p1) #eachindex(p1) - @inbounds p[i] = p1[i] + get(p2,i,zero(R)) + for (i,pi) ∈ pairs(p1) + @inbounds p[i] = pi + get(p2, i, zero(R)) end - for i in keys(p2) #eachindex(p2) + for (i,pi) ∈ pairs(p2) if iszero(get(p,i,zero(R))) - @inbounds p[i] = get(p1,i,zero(R)) + p2[i] + @inbounds p[i] = get(p1, i, zero(R)) + pi end end - + return p end @@ -842,6 +844,7 @@ function Base.:/(p::P, c::S) where {P <: AbstractPolynomial,S} end ## polynomial p*q +## Polynomial multiplication formula depend on the particular basis used. The subtype must implement function Base.:*(p1::P, p2::O) where {T,X,P <: AbstractPolynomial{T,X},S,Y,O <: AbstractPolynomial{S,Y}} isconstant(p1) && return constantterm(p1) * p2 isconstant(p2) && return p1 * constantterm(p2) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index 72fc26ad..d39b78fa 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -96,12 +96,6 @@ Base.collect(p::P) where {P <: ImmutablePolynomial} = [pᵢ for pᵢ ∈ p] Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p)) -## defining these speeds things up -#function Base.zero(P::Type{<:ImmutablePolynomial})#, var::SymbolLike=indeterminate(P)) -# R = eltype(P) -# ImmutablePolynomial{R,Symbol(var),0}(NTuple{0,R}()) -#end - function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) R = eltype(P) ImmutablePolynomial{R,Symbol(var),1}(NTuple{1,R}(one(R))) @@ -171,122 +165,72 @@ truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate! (p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = evalpoly(x, p.coeffs) +## Addition +# scalar ops +function Base.:+(p::P, c::S) where {T, X, N, P <: ImmutablePolynomial{T,X,N}, S<:Number} + R = promote_type(T,S) + + iszero(c) && return ImmutablePolynomial{R,X,N}(convert(NTuple{N,R},p.coeffs)) + N == 0 && return ImmutablePolynomial{R,X,1}(NTuple{1,R}(c)) + N == 1 && return ImmutablePolynomial((p[0]+c,), X) + + cs = ⊕(P, convert(NTuple{N,R},p.coeffs), NTuple{1,R}(c)) + q = ImmutablePolynomial{R,X,N}(cs) + + return q + +end + +Base.:-(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = ImmutablePolynomial{T,X,N}(.-p.coeffs) + function Base.:+(p1::P, p2::Q) where {T,X,N,P<:ImmutablePolynomial{T,X,N}, S, M,Q<:ImmutablePolynomial{S,X,M}} R = promote_type(T,S) + P′ = ImmutablePolynomial{R,X} if N == M cs = ⊕(P, p1.coeffs, p2.coeffs) - return ImmutablePolynomial{R,X}(R.(cs)) - #cs = (p1.coeffs) ⊕ (p2.coeffs) #NTuple{N,T}(p1[i] + p2[i] for i in 0:N-1) - #ImmutablePolynomial{T,X}(cs) + return P′(R.(cs)) elseif N < M cs = ⊕(P, p2.coeffs, p1.coeffs) - return ImmutablePolynomial{R,X,M}(R.(cs)) - - #cs = (p2.coeffs) ⊕ (p1.coeffs) - #ImmutablePolynomial{T,X,M}(convert(NTuple{M,T}, cs)) + return P′{M}(R.(cs)) else cs = ⊕(P, p1.coeffs, p2.coeffs) - return ImmutablePolynomial{R,X,N}(R.(cs)) - #cs = (p1.coeffs) ⊕ (p2.coeffs) - #ImmutablePolynomial{T,X,N}(convert(NTuple{N,T}, cs)) + return P′{N}(R.(cs)) end end -function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} - R = promote_type(T,S) - Q = ImmutablePolynomial{R,X} +## multiplication - (iszero(N) || iszero(M)) && return zero(Q) - - cs = (p1.coeffs) ⊗ (p2.coeffs) - if !iszero(cs[end]) - return Q{N+M-1}(cs) - else - n = findlast(!iszero, cs) - return Q{n}(NTuple{n,T}(cs[i] for i ∈ 1:n)) - end +function Base.:*(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X,N, S <: Number} + R = eltype(one(T)*one(S)) + P = ImmutablePolynomial{R,X} + (N == 0 || iszero(c)) && return zero(P) + cs = p.coeffs .* c + iszero(cs[end]) ? P(cs) : P{N}(cs) # more performant to specify when N is known end - -# Padded vector sum of two tuples assuming N > M -# assume N > M. -# As N ≠ M, we are assured of size of output (max(N,M)), so we generate the function -@generated function ⊕(p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} - - exprs = Any[nothing for i = 1:N] - for i in 1:M - exprs[i] = :(p1[$i] + p2[$i]) - end - for i in M+1:N - exprs[i] =:(p1[$i]) - end - - return quote - Base.@_inline_meta - tuple($(exprs...)) - end - +function Base.:/(p::ImmutablePolynomial{T,X,N}, c::S) where {T,X,N,S <: Number} + R = eltype(one(T)/one(S)) + P = ImmutablePolynomial{R,X} + (N == 0 || isinf(c)) && return zero(P) + cs = p.coeffs ./ c + iszero(cs[end]) ? P(cs) : P{N}(cs) # more performant to specify when N is known end - -## Static size of product makes generated functions a good choice -## from https://github.com/tkoolen/StaticUnivariatePolynomials.jl/blob/master/src/monomial_basis.jl -## convolution of two tuples -@generated function ⊗(p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} - P = M + N - 1 - exprs = Any[nothing for i = 1 : P] - for i in 1 : N - for j in 1 : M - k = i + j - 1 - if exprs[k] === nothing - exprs[k] = :(p1[$i] * p2[$j]) - else - exprs[k] = :(muladd(p1[$i], p2[$j], $(exprs[k]))) - end - end - end - - return quote - Base.@_inline_meta - tuple($(exprs...)) - end - -end - -# scalar ops -function Base.:+(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X, N, S<:Number} +function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} R = promote_type(T,S) + P = ImmutablePolynomial{R,X} - iszero(c) && return ImmutablePolynomial{R,X,N}(convert(NTuple{N,R},p.coeffs)) - N == 0 && return ImmutablePolynomial{R,X,1}(NTuple{1,R}(c)) - N == 1 && return ImmutablePolynomial((p[0]+c,), X) - - cs = convert(NTuple{N,R},p.coeffs) ⊕ NTuple{1,R}(c) - q = ImmutablePolynomial{R,X,N}(cs) + (iszero(N) || iszero(M)) && return zero(P) - return q - -end + cs = ⊗(ImmutablePolynomial, p1.coeffs, p2.coeffs) #(p1.coeffs) ⊗ (p2.coeffs) + iszero(cs[end]) ? P(cs) : P{N+M-1}(cs) # more performant to specify when N is known -function Base.:*(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X,N, S <: Number} - R = eltype(one(T)*one(S)) - iszero(p[end]*c) && return ImmutablePolynomial{R,X}(p.coeffs .* c) - ImmutablePolynomial{R,X,N}(p.coeffs .* c) end -function Base.:/(p::ImmutablePolynomial{T,X,N}, c::S) where {T,X,N,S <: Number} - R = eltype(one(T)/one(S)) - cs = p.coeffs ./ c - iszero(cs[end]) && return ImmutablePolynomial{R,X}(cs) - ImmutablePolynomial{R,X,N}(cs) -end - -Base.:-(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = ImmutablePolynomial{T,X,N}(.-p.coeffs) - Base.to_power_type(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = p diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 7550c909..b31f442f 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -451,6 +451,9 @@ end ## function Base.:+(p1::P, p2::P) where {T,X,P<:LaurentPolynomial{T,X}} + isconstant(p1) && return constantterm(p1) + p2 + isconstant(p2) && return p1 + constantterm(p2) + m1,n1 = (extrema ∘ degreerange)(p1) m2,n2 = (extrema ∘ degreerange)(p2) diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 86d0266d..71c6ecb5 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -112,10 +112,8 @@ function Base.:+(p1::P1, p2::P2) where {T,X, P1<:Polynomial{T,X}, Q(Val(false), cs) end -function Base.:*(p1::P, p2::P) where {T,X, P<:Polynomial{T,X}} - - c = fastconv(p1.coeffs, p2.coeffs) +function Base.:*(p::P, q::P) where {T,X, P<:Polynomial{T,X}} + c = fastconv(p.coeffs, q.coeffs) return iszero(c[end]) ? P(c) : P(Val(false), c) - end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 32c0de1e..5ed2021b 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -205,42 +205,8 @@ function Base.map(fn, p::P, args...) where {P <: SparsePolynomial} _convert(p, Dict(Pair.(ks, vs′))) end -# Implement over fallback. A bit faster for T != S -function Base.:+(p1::P1, p2::P2) where {T,X, P1<:SparsePolynomial{T,X}, - S, P2<:SparsePolynomial{S,X}} - - R = promote_type(T,S) - Q = SparsePolynomial{R,X} - - d1, d2 = degree(p1), degree(p2) - cs = d1 > d2 ? ⊕(P1, p1.coeffs, p2.coeffs) : ⊕(P1, p2.coeffs, p1.coeffs) - return d1 != d2 ? Q(Val(false), cs) : Q(cs) -end - -# function Base.:+(p1::P, p2::P) where {T, X, P<:SparsePolynomial{T,X}} - -# p = zero(P) - -# # this allocates in the union -# # for i in union(eachindex(p1), eachindex(p2)) -# # p[i] = p1[i] + p2[i] -# # end - -# # this seems faster -# for i in keys(p1) #eachindex(p1) -# @inbounds p[i] = p1[i] + p2[i] -# end -# for i in keys(p2) #eachindex(p2) -# if iszero(p[i]) -# @inbounds p[i] = p1[i] + p2[i] -# end -# end - -# return p - -# end - +## Addition function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} R = promote_type(T,S) @@ -253,36 +219,44 @@ function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} end @inbounds D[0] = get(D,0,zero(R)) + c iszero(D[0]) && pop!(D,0) - return P(Val(false),D) + + return P(Val(false), D) end -function Base.:*(p1::P, p2::P) where {T,X,P<:SparsePolynomial{T,X}} - - p = zero(P) - for i in keys(p1) #eachindex(p1) - p1ᵢ = p1[i] - for j in keys(p2) #eachindex(p2) - @inbounds p[i+j] = muladd(p1ᵢ, p2[j], p[i+j]) - end - end +# Implement over fallback. A bit faster for T != S +function Base.:+(p1::P1, p2::P2) where {T,X, P1<:SparsePolynomial{T,X}, + S, P2<:SparsePolynomial{S,X}} + + R = promote_type(T,S) + Q = SparsePolynomial{R,X} - return p + d1, d2 = degree(p1), degree(p2) + cs = d1 > d2 ? ⊕(P1, p1.coeffs, p2.coeffs) : ⊕(P1, p2.coeffs, p1.coeffs) + + return d1 != d2 ? Q(Val(false), cs) : Q(cs) end - +## Multiplication function Base.:*(p::P, c::S) where {T, X, P <: SparsePolynomial{T,X}, S <: Number} R = promote_type(T,S) - q = zero(⟒(P){R,X}) - for k in eachindex(p) - q[k] = p[k] * c + Q = ⟒(P){R,X} + + q = zero(Q) + for (k,pₖ) ∈ pairs(p) + q[k] = pₖ * c end return q end +function Base.:*(p::P, q::Q) where {T,X,P<:SparsePolynomial{T,X}, + S, Q<:SparsePolynomial{S,X}} + R = promote_type(T,S) + SparsePolynomial{R,X}(⊗(P, p.coeffs, q.coeffs)) +end function derivative(p::SparsePolynomial{T,X}, order::Integer = 1) where {T,X} diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 8c5e16f1..bda61b42 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -36,6 +36,50 @@ function variable(::Type{P}, var::SymbolLike) where {P <: StandardBasisPolynomia ⟒(P){eltype(P), indeterminate(P,Symbol(var))}([0, 1]) end +## multiplication algorithms for computing p * q. +## put here, not with type defintion, in case reuse is possible +function ⊗(P::Type{<:StandardBasisPolynomial}, p::Vector{T}, q::Vector{S}) where {T,S} + R = promote_type(T,S) + fastconv(convert(Vector{R}, p), convert(Vector{R},q)) +end + +## Static size of product makes generated functions a good choice +## from https://github.com/tkoolen/StaticUnivariatePolynomials.jl/blob/master/src/monomial_basis.jl +## convolution of two tuples +@generated function ⊗(::Type{<:StandardBasisPolynomial}, p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} + P = M + N - 1 + exprs = Any[nothing for i = 1 : P] + for i in 1 : N + for j in 1 : M + k = i + j - 1 + if exprs[k] === nothing + exprs[k] = :(p1[$i] * p2[$j]) + else + exprs[k] = :(muladd(p1[$i], p2[$j], $(exprs[k]))) + end + end + end + + return quote + Base.@_inline_meta + tuple($(exprs...)) + end + +end + +function ⊗(P::Type{<:StandardBasisPolynomial}, p::Dict{Int,T}, q::Dict{Int,S}) where {T,S} + R = promote_type(T,S) + c = Dict{Int,R}() + for (i,pᵢ) ∈ pairs(p) + for (j,qⱼ) ∈ pairs(q) + cᵢⱼ = get(c, i+j, zero(R)) + @inbounds c[i+j] = muladd(pᵢ, qⱼ, cᵢⱼ) + end + end + c +end + +## --- function fromroots(P::Type{<:StandardBasisPolynomial}, r::AbstractVector{T}; var::SymbolLike = :x) where {T <: Number} n = length(r) c = zeros(T, n + 1) From c0d688c54126f2ccc13a50374fcf8e453fd3a8c8 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 23 Feb 2021 12:09:26 -0500 Subject: [PATCH 23/38] edits --- src/common.jl | 4 ++-- src/polynomials/SparsePolynomial.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common.jl b/src/common.jl index 3d28f41b..e43efa7b 100644 --- a/src/common.jl +++ b/src/common.jl @@ -749,18 +749,18 @@ Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = p + c * one(P) function Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} + R = promote_type(T,S) q = convert(⟒(P){R,X}, p) q + R(c) end -# polynomial + polynomial +# polynomial + polynomial when different types function Base.:+(p::P, q::Q) where {T,X,P <: AbstractPolynomial{T,X}, S,Y,Q <: AbstractPolynomial{S,Y}} isconstant(p) && return constantterm(p) + q isconstant(q) && return p + constantterm(q) assert_same_variable(X,Y) - sum(promote(p,q)) end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 5ed2021b..28a731c5 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -224,7 +224,7 @@ function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} end -# Implement over fallback. A bit faster for T != S +# Implement over fallback. A bit faster as it covers T != S function Base.:+(p1::P1, p2::P2) where {T,X, P1<:SparsePolynomial{T,X}, S, P2<:SparsePolynomial{S,X}} From 38110e09ec22718fe41b7441fd5e2d79a2d60b04 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 23 Feb 2021 12:17:26 -0500 Subject: [PATCH 24/38] laurent polynomial depwarns removed --- src/common.jl | 2 -- src/polynomials/LaurentPolynomial.jl | 8 -------- 2 files changed, 10 deletions(-) diff --git a/src/common.jl b/src/common.jl index e43efa7b..85a3d9e7 100644 --- a/src/common.jl +++ b/src/common.jl @@ -704,8 +704,6 @@ variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) # basis # var is a positional argument, not a keyword; can't deprecate so we do `_var; var=_var` -#@deprecate basis(p::P, k::Int; var=:x) where {P<:AbstractPolynomial} basis(p, k, var) -#@deprecate basis(::Type{P}, k::Int; var=:x) where {P <: AbstractPolynomial} basis(P, k,var) # return the kth basis polynomial for the given polynomial type, e.g. x^k for Polynomial{T} function basis(::Type{P}, k::Int, _var::SymbolLike=:x; var=_var) where {P <: AbstractPolynomial} zs = zeros(Int, k+1) diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index b31f442f..8e7a2288 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -153,14 +153,6 @@ end ## ## generic functions ## -# function Base.extrema(p::LaurentPolynomial) -# Base.depwarn("`extrema(::LaurentPolynomial)` is deprecated. Use `(firstindex(p), lastindex(p))`", :extrema) -# (p.m[], p.n[]) -# end -# function Base.range(p::LaurentPolynomial) -# Base.depwarn("`range(::LaurentPolynomial)` is deprecated. Use `firstindex(p):lastindex(p)`", :range) -# p.m[]:p.n[] -# end function Base.inv(p::LaurentPolynomial{T, X}) where {T, X} m,n = (extrema∘degreerange)(p) From dfe1d73343e869b063164e65b43aeb8de62498be Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 23 Feb 2021 12:20:33 -0500 Subject: [PATCH 25/38] run doctests --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index 4171ea14..eff84374 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -199,7 +199,7 @@ julia> derivative(p1) ChebyshevT(2.0⋅T_0(x) + 12.0⋅T_1(x)) julia> integrate(p2) -ChebyshevT(0.25⋅T_0(x) - 1.0⋅T_1(x) + 0.25⋅T_2(x) + 0.3333333333333333⋅T_3(x)) +ChebyshevT(- 1.0⋅T_1(x) + 0.25⋅T_2(x) + 0.3333333333333333⋅T_3(x)) julia> convert(Polynomial, p1) Polynomial(-2.0 + 2.0*x + 6.0*x^2) From dff66296baca5fd5a884467439d4f58d30dbddd0 Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 24 Feb 2021 09:15:07 -0500 Subject: [PATCH 26/38] WIP --- docs/src/extending.md | 72 ++++++++++++++++++++++++++++++- src/common.jl | 14 +++++- src/polynomials/standard-basis.jl | 7 +++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/docs/src/extending.md b/docs/src/extending.md index ee09e5e6..2d72733f 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -31,4 +31,74 @@ As always, if the default implementation does not work or there are more efficie | `divrem` | | Required for [`gcd`](@ref)| | `variable`| | Convenience to find monomial `x` in new basis| -Check out both the [`Polynomial`](@ref) and [`ChebyshevT`](@ref) for examples of this interface being extended. +Check out both the [`Polynomial`](@ref) and [`ChebyshevT`](@ref) for examples of this interface being extended. + +The following shows a minimal example where the polynomial aliases the vector defining the coefficients. +The constructor ensures that there are no trailing zeros. The method implemented below is the convenient call syntax. This example uses the standard basis. For other bases, many more methods may be necessary to define (again, refer to [`ChebyshevT`](@ref) for an example). + +```jldoctest +julia> using Polynomials + +julia> struct AliasPolynomial{T <: Number, X} <: Polynomials.StandardBasisPolynomial{T, X} + coeffs::Vector{T} + function AliasPolynomial{T, X}(coeffs::Vector{S}) where {T, X, S} + N = findlast(!iszero, coeffs) + N == nothing && return new{T,X}(zeros(T,1)) + resize!(coeffs, N) + new{T,X}(coeffs) + end + end + +julia> (p::CP{T})(x::S) where {T,S} = evalpoly(x, p.coeffs) + +julia> Polynomials.@register AliasPolynomial +AliasPolynomial + +julia> xs = [1,2,3,4]; + +julia> p = AliasPolynomial(xs) +AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) + +julia> q = AliasPolynomial(1.0, :y) +AliasPolynomial(1.0) + +julia> p + q +AliasPolynomial(2.0 + 2.0*x + 3.0*x^2 + 4.0*x^3) + +julia> p * p +AliasPolynomial(1 + 4*x + 10*x^2 + 20*x^3 + 25*x^4 + 24*x^5 + 16*x^6) + +julia> (derivative ∘ integrate)(p) == p +true +``` + +For the `Polynomial` type, the default is to copy the array, so updates that don't change the size of the array. For this type, it might seem reasonable, to avoid allcoations, to update the coefficients in place for scalar addition and scalar multiplication. For that, the broadcast syntax might be useful. By default, the abstract polynomial type is not "broadcastable." To make this type allow scalare multiplation through the `.*=` syntax, we could do: + +```jldoctest +julia> Base.broadcastable(p::AliasPolynomial) = p + +julia> Base.ndims(::Type{<:AliasPolynomial}) = 1 + +julia> Base.copyto!(p::AliasPolynomial, x) = (copyto!(p.coeffs, x); chop!(p)) +``` + +Then we might have: + +```jldoctest +julia> p +AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) + +julia> p .*= 2 +4-element Array{Int64,1}: + 2 + 4 + 6 + 8 + +julia> p +AliasPolynomial(2 + 4*x + 6*x^2 + 8*x^3) +``` + +Though a bit cumbersome how the display of `p .+= 2` is the coefficients and not the polynomial, we can see that `p` is updated. + +However, scalar addition is defined differently than diff --git a/src/common.jl b/src/common.jl index 85a3d9e7..b3a0cb0f 100644 --- a/src/common.jl +++ b/src/common.jl @@ -525,6 +525,17 @@ function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T <: Number} # return coeffs(p)[idx + 1] end Base.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) +## straight from array.jl +function getindex(A::AbstractPolynomial, I::UnitRange{Int}) + lI = length(I) + X = similar(A.coeffs, lI) + if lI > 0 + offset = -1 - firstindex(A) + unsafe_copyto!(X, 1, A.coeffs, first(I .- offset), lI) + end + return X +end +Base.getindex(p::AbstractPolynomial, indices::CartesianIndex) = first(indices.I) Base.getindex(p::AbstractPolynomial, indices) = [getindex(p, i) for i in indices] Base.getindex(p::AbstractPolynomial, ::Colon) = coeffs(p) @@ -588,6 +599,7 @@ Base.keys(p::AbstractPolynomial) = PolynomialKeys(p) Base.values(p::AbstractPolynomial) = PolynomialValues(p) Base.length(p::PolynomialValues) = length(p.p.coeffs) Base.length(p::PolynomialKeys) = length(p.p.coeffs) +Base.size(p::Union{PolynomialValues, PolynomialKeys}) = (length(p),) function Base.iterate(v::PolynomialKeys, state=nothing) i = firstindex(v.p) state==nothing && return (i, i) @@ -843,7 +855,7 @@ end ## polynomial p*q ## Polynomial multiplication formula depend on the particular basis used. The subtype must implement -function Base.:*(p1::P, p2::O) where {T,X,P <: AbstractPolynomial{T,X},S,Y,O <: AbstractPolynomial{S,Y}} +function Base.:*(p1::P, p2::Q) where {T,X,P <: AbstractPolynomial{T,X},S,Y,Q <: AbstractPolynomial{S,Y}} isconstant(p1) && return constantterm(p1) * p2 isconstant(p2) && return p1 * constantterm(p2) assert_same_variable(X, Y) diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index bda61b42..e4365a25 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -37,6 +37,13 @@ function variable(::Type{P}, var::SymbolLike) where {P <: StandardBasisPolynomia end ## multiplication algorithms for computing p * q. +## default multiplication between same type. +## subtypes might relax to match T,S to avoid one conversion +function Base.:*(p::P, q::P) where {T,X, P<:StandardBasisPolynomial{T,X}} + cs = ⊗(P, coeffs(p), coeffs(q)) + P(cs) +end + ## put here, not with type defintion, in case reuse is possible function ⊗(P::Type{<:StandardBasisPolynomial}, p::Vector{T}, q::Vector{S}) where {T,S} R = promote_type(T,S) From 65a7c923c44b3cb2eae17ad544207006ad4f971e Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 24 Feb 2021 09:43:11 -0500 Subject: [PATCH 27/38] fix basis symbol handling --- src/common.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/common.jl b/src/common.jl index 85a3d9e7..448183fe 100644 --- a/src/common.jl +++ b/src/common.jl @@ -705,17 +705,15 @@ variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) # basis # var is a positional argument, not a keyword; can't deprecate so we do `_var; var=_var` # return the kth basis polynomial for the given polynomial type, e.g. x^k for Polynomial{T} -function basis(::Type{P}, k::Int, _var::SymbolLike=:x; var=_var) where {P <: AbstractPolynomial} - zs = zeros(Int, k+1) - zs[end] = 1 - ⟒(P){eltype(P), _var}(zs) -end function basis(::Type{P}, k::Int) where {T, X, P<:AbstractPolynomial{T,X}} zs = zeros(Int, k+1) zs[end] = 1 ⟒(P){eltype(P), X}(zs) end - +function basis(::Type{P}, k::Int, _var::SymbolLike=:x; var=_var) where {P <: AbstractPolynomial} + T,X = eltype(P), Symbol(_var) + basis(⟒(P){T,X},k) +end basis(p::P, k::Int, _var::SymbolLike=:x; var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) #= From e57784dfefe59268cac4fb1d67856adb3db8542a Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 25 Feb 2021 07:47:55 -0500 Subject: [PATCH 28/38] WIP --- docs/src/extending.md | 62 ++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/docs/src/extending.md b/docs/src/extending.md index 2d72733f..fa41b855 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -25,16 +25,16 @@ As always, if the default implementation does not work or there are more efficie | `vander` | | Required for [`fit`](@ref) | | `companion` | | Required for [`roots`](@ref) | | `fromroots` | | By default, will form polynomials using `prod(variable(::P) - r)` for reach root `r`| -| `+(::P, ::P)` | | Addition of polynomials | -| `-(::P, ::P)` | | Subtraction of polynomials | | `*(::P, ::P)` | | Multiplication of polynomials | | `divrem` | | Required for [`gcd`](@ref)| | `variable`| | Convenience to find monomial `x` in new basis| Check out both the [`Polynomial`](@ref) and [`ChebyshevT`](@ref) for examples of this interface being extended. +## Example + The following shows a minimal example where the polynomial aliases the vector defining the coefficients. -The constructor ensures that there are no trailing zeros. The method implemented below is the convenient call syntax. This example uses the standard basis. For other bases, many more methods may be necessary to define (again, refer to [`ChebyshevT`](@ref) for an example). +The constructor ensures that there are no trailing zeros. The method implemented below is the convenient call syntax. This example subtypes `StandardBasisPolynomial`, not `AbstractPolynomial`, and consequently inherits the methods above. For other bases, more methods may be necessary to define (again, refer to [`ChebyshevT`](@ref) for an example). ```jldoctest julia> using Polynomials @@ -49,11 +49,15 @@ julia> struct AliasPolynomial{T <: Number, X} <: Polynomials.StandardBasisPolyno end end -julia> (p::CP{T})(x::S) where {T,S} = evalpoly(x, p.coeffs) +julia> (p::AliasPolynomial)(x) = evalpoly(x, p.coeffs) julia> Polynomials.@register AliasPolynomial AliasPolynomial +``` +To see this new polynomial type in action, we have: + +``` julia> xs = [1,2,3,4]; julia> p = AliasPolynomial(xs) @@ -70,35 +74,61 @@ AliasPolynomial(1 + 4*x + 10*x^2 + 20*x^3 + 25*x^4 + 24*x^5 + 16*x^6) julia> (derivative ∘ integrate)(p) == p true + +julia> p(3) +142 +``` + +For the `Polynomial` type, the default on operations is to copy the array. For this type, it might seem reasonable -- to avoid allocations -- to update the coefficients in place for scalar addition and scalar multiplication. + +Scalar addition, `p+c`, defaults to `p + c*one(p)`, or polynomial multiplication, which is not inplace without addition work. As such, we create a new method and an infix operator + +```jldoctest +julia> function sadd!(p::AliasPolynomial{T}, c::T) where {T} + p.coeffs[1] += c + p + end +sadd! (generic function with 1 method) + +julia> p::AliasPolynomial ⊕ c::Number = sadd!(p,c); ``` -For the `Polynomial` type, the default is to copy the array, so updates that don't change the size of the array. For this type, it might seem reasonable, to avoid allcoations, to update the coefficients in place for scalar addition and scalar multiplication. For that, the broadcast syntax might be useful. By default, the abstract polynomial type is not "broadcastable." To make this type allow scalare multiplation through the `.*=` syntax, we could do: +Then we have: ```jldoctest -julia> Base.broadcastable(p::AliasPolynomial) = p +julia> p +AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) + +julia> p ⊕ 2 # same result as p = p + 2, but makes no copies +AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) + +julia> p +AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) +``` + +The viewpoint that a polynomial represents a vector of coefficients leads to a desire to inherit vector operations when possible. Scalar multiplication is a vector operation, so it seems reasonable to override the broadcast machinery to implement an in place operation (e.g. `p .*= 2`). By default, the polynomial types are not broadcastable over their coefficients. We would need to make a change there and modify the `copyto!` function: + + +```jldoctest +julia> Base.broadcastable(p::AliasPolynomial) = p.coeffs julia> Base.ndims(::Type{<:AliasPolynomial}) = 1 julia> Base.copyto!(p::AliasPolynomial, x) = (copyto!(p.coeffs, x); chop!(p)) ``` +The last `chop!` call would ensure that there are no trailing zeros in the coefficient vector after multiplication, as multiplication by `0` is possible. + Then we might have: ```jldoctest julia> p -AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) +AliasPolynomial(2 + 4*x + 6*x^2 + 8*x^3) julia> p .*= 2 -4-element Array{Int64,1}: - 2 - 4 - 6 - 8 +AliasPolynomial(4 + 8*x + 12*x^2 + 16*x^3) julia> p -AliasPolynomial(2 + 4*x + 6*x^2 + 8*x^3) +AliasPolynomial(4 + 8*x + 12*x^2 + 16*x^3) ``` -Though a bit cumbersome how the display of `p .+= 2` is the coefficients and not the polynomial, we can see that `p` is updated. - -However, scalar addition is defined differently than From 7470f25297b3e7c681db5ed4b9ddc7da1f008399 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 25 Feb 2021 15:11:51 -0500 Subject: [PATCH 29/38] WIP --- docs/src/extending.md | 55 +++++++++++++++----------- src/abstract.jl | 2 + src/common.jl | 25 ++++++------ src/contrib.jl | 4 +- src/polynomials/ChebyshevT.jl | 3 +- src/polynomials/ImmutablePolynomial.jl | 2 +- src/polynomials/LaurentPolynomial.jl | 35 ++++++++-------- src/polynomials/Polynomial.jl | 48 +++++++++++----------- src/polynomials/SparsePolynomial.jl | 8 ++-- src/polynomials/standard-basis.jl | 12 +++--- 10 files changed, 107 insertions(+), 87 deletions(-) diff --git a/docs/src/extending.md b/docs/src/extending.md index fa41b855..9b0dc247 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -21,6 +21,7 @@ As always, if the default implementation does not work or there are more efficie | Constructor | x | | | Type function (`(::P)(x)`) | x | | | `convert(::Polynomial, ...)` | | Not required, but the library is built off the [`Polynomial`](@ref) type, so all operations are guaranteed to work with it. Also consider writing the inverse conversion method. | +| `Base.evalpoly(x, p::P)` | to evaluate the polynomial at `x` | | `domain` | x | Should return an [`AbstractInterval`](https://invenia.github.io/Intervals.jl/stable/#Intervals-1) | | `vander` | | Required for [`fit`](@ref) | | `companion` | | Required for [`roots`](@ref) | @@ -40,24 +41,19 @@ The constructor ensures that there are no trailing zeros. The method implemented julia> using Polynomials julia> struct AliasPolynomial{T <: Number, X} <: Polynomials.StandardBasisPolynomial{T, X} - coeffs::Vector{T} - function AliasPolynomial{T, X}(coeffs::Vector{S}) where {T, X, S} - N = findlast(!iszero, coeffs) - N == nothing && return new{T,X}(zeros(T,1)) - resize!(coeffs, N) - new{T,X}(coeffs) - end - end - -julia> (p::AliasPolynomial)(x) = evalpoly(x, p.coeffs) + coeffs::Vector{T} + function AliasPolynomial{T, X}(coeffs::Vector{S}) where {T, X, S} + p = new{T,X}(coeffs) + chop!(p) + end + end julia> Polynomials.@register AliasPolynomial -AliasPolynomial ``` To see this new polynomial type in action, we have: -``` +```jldoctest julia> xs = [1,2,3,4]; julia> p = AliasPolynomial(xs) @@ -85,10 +81,9 @@ Scalar addition, `p+c`, defaults to `p + c*one(p)`, or polynomial multiplication ```jldoctest julia> function sadd!(p::AliasPolynomial{T}, c::T) where {T} - p.coeffs[1] += c - p - end -sadd! (generic function with 1 method) + p.coeffs[1] += c + p + end; julia> p::AliasPolynomial ⊕ c::Number = sadd!(p,c); ``` @@ -99,7 +94,7 @@ Then we have: julia> p AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) -julia> p ⊕ 2 # same result as p = p + 2, but makes no copies +julia> p ⊕ 2 AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) julia> p @@ -110,11 +105,11 @@ The viewpoint that a polynomial represents a vector of coefficients leads to a ```jldoctest -julia> Base.broadcastable(p::AliasPolynomial) = p.coeffs +julia> Base.broadcastable(p::AliasPolynomial) = p.coeffs; julia> Base.ndims(::Type{<:AliasPolynomial}) = 1 -julia> Base.copyto!(p::AliasPolynomial, x) = (copyto!(p.coeffs, x); chop!(p)) +julia> Base.copyto!(p::AliasPolynomial, x) = (copyto!(p.coeffs, x); chop!(p)); ``` The last `chop!` call would ensure that there are no trailing zeros in the coefficient vector after multiplication, as multiplication by `0` is possible. @@ -123,12 +118,28 @@ Then we might have: ```jldoctest julia> p -AliasPolynomial(2 + 4*x + 6*x^2 + 8*x^3) +AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) julia> p .*= 2 -AliasPolynomial(4 + 8*x + 12*x^2 + 16*x^3) +AliasPolynomial(6 + 4*x + 6*x^2 + 8*x^3) julia> p -AliasPolynomial(4 + 8*x + 12*x^2 + 16*x^3) +AliasPolynomial(6 + 4*x + 6*x^2 + 8*x^3) + +julia> p ./= 2 +AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) +``` + +Trying to divide again would throw an error, as the result would not fit with the integer type of `p`. + +Now `p` is treated as the vector `p.coeffs`, as regards broadcasting, so some things may be surprising, for example this expression returns a vector, not a polynomial: + +```jldoctest +p .+ 2 +4-element Array{Int64,1}: + 5 + 4 + 5 + 6 ``` diff --git a/src/abstract.jl b/src/abstract.jl index 67a74431..5bfed88e 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -62,6 +62,7 @@ macro register(name) $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S, Symbol(var)}) $poly{T}(var::SymbolLike=:x) where {T} = variable($poly{T, Symbol(var)}) $poly(var::SymbolLike=:x) = variable($poly, Symbol(var)) + (p::$poly)(x) = evalpoly(x, p) end end @@ -90,6 +91,7 @@ macro registerN(name, params...) n*one($poly{$(αs...),S,Symbol(var)}) $poly{$(αs...),T}(var::SymbolLike=:x) where {$(αs...), T} = variable($poly{$(αs...),T,Symbol(var)}) $poly{$(αs...)}(var::SymbolLike=:x) where {$(αs...)} = variable($poly{$(αs...)},Symbol(var)) + (p::$poly)(x) = evalpoly(x, p) end end diff --git a/src/common.jl b/src/common.jl index b3a0cb0f..0fda0ed2 100644 --- a/src/common.jl +++ b/src/common.jl @@ -521,22 +521,25 @@ Base.broadcastable(p::AbstractPolynomial) = Ref(p) function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T <: Number} idx < firstindex(p) && throw(BoundsError(p, idx)) idx > lastindex(p) && return zero(T) - return p.coeffs[idx-firstindex(p)+1] + i = idx - firstindex(p) + 1 + return p.coeffs[i] # return coeffs(p)[idx + 1] end Base.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) ## straight from array.jl -function getindex(A::AbstractPolynomial, I::UnitRange{Int}) - lI = length(I) - X = similar(A.coeffs, lI) - if lI > 0 - offset = -1 - firstindex(A) - unsafe_copyto!(X, 1, A.coeffs, first(I .- offset), lI) - end - return X -end +# function Base.getindex(A::AbstractPolynomial, I::UnitRange{Int}) +# lI = length(I) +# (first(I) < firstindex(A) || lI > length(A.coeffs)) && +# throw(ArgumentError("indexing out of bounds not permitted with ranges")) +# X = similar(A.coeffs, lI) +# if lI > 0 +# offset = 1 - firstindex(A) +# unsafe_copyto!(X, 1, A.coeffs, first(I .+ offset), lI) +# end +# return X +# end Base.getindex(p::AbstractPolynomial, indices::CartesianIndex) = first(indices.I) -Base.getindex(p::AbstractPolynomial, indices) = [getindex(p, i) for i in indices] +Base.getindex(p::AbstractPolynomial, indices) = [p[i] for i in indices] Base.getindex(p::AbstractPolynomial, ::Colon) = coeffs(p) # setindex diff --git a/src/contrib.jl b/src/contrib.jl index 1a329e6f..a5d20b7c 100644 --- a/src/contrib.jl +++ b/src/contrib.jl @@ -32,6 +32,8 @@ end ## cf. https://github.com/JuliaLang/julia/pull/32753 ## Slight modification when `x` is a matrix ## Remove once dependencies for Julia 1.0.0 are dropped +module EvalPoly +using LinearAlgebra function evalpoly(x::S, p::Tuple) where {S} p == () && return zero(S) if @generated @@ -116,7 +118,7 @@ _muladd(a::Matrix, b, c) = a*(b*I) + c*I _one(P::Type{<:Matrix}) = one(eltype(P))*I _one(x::Matrix) = one(eltype(x))*I _one(x) = one(x) - +end ## get type of parametric composite type without type parameters ## this is needed when the underlying type changes, e.g. with integration ## where T=Int might become T=Float64 diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 6bdd84fc..b47a7893 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -97,7 +97,8 @@ julia> c.(-1:0.5:1) 5.0 ``` """ -function (ch::ChebyshevT{T})(x::S) where {T,S} +function Base.evalpoly(x::S, ch::ChebyshevT{T}) where {T,S} +#function (ch::ChebyshevT{T})(x::S) where {T,S} x ∉ domain(ch) && throw(ArgumentError("$x outside of domain")) R = promote_type(T, S) length(ch) == 0 && return zero(R) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index d39b78fa..fed181e2 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -163,7 +163,7 @@ truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate! ## -------------------- ## -(p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = evalpoly(x, p.coeffs) +#(p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = EvalPoly.evalpoly(x, p.coeffs) ## Addition # scalar ops diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 8e7a2288..dc297e2d 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -176,19 +176,19 @@ Base.zero(::Type{LaurentPolynomial{T}}, var=Symbollike=:x) where {T} = Laurent Base.zero(::Type{LaurentPolynomial}, var=Symbollike=:x) = zero(LaurentPolynomial{Float64, Symbol(var)}) Base.zero(p::P, var=Symbollike=:x) where {P <: LaurentPolynomial} = zero(P, var) -function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T} - m,M = firstindex(p), lastindex(p) - m <= idx <= M || return zero(T) - p.coeffs[idx-m+1] -end +# function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T} +# m,M = firstindex(p), lastindex(p) +# m <= idx <= M || return zero(T) +# p.coeffs[idx-m+1] +# end # # get/set index. Work with offset -# function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T <: Number} -# m,n = (extrema ∘ degreerange)(p) -# i = idx - m + 1 -# (i < 1 || i > (n-m+1)) && return zero(T) -# p.coeffs[i] -# end +function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T <: Number} + m,n = (extrema ∘ degreerange)(p) + i = idx - m + 1 + (i < 1 || i > (n-m+1)) && return zero(T) + p.coeffs[i] +end # extend if out of bounds function Base.setindex!(p::LaurentPolynomial{T}, value::Number, idx::Int) where {T} @@ -411,17 +411,18 @@ end # evaluation uses `evalpoly` -function (p::LaurentPolynomial{T})(x::S) where {T,S} +function Base.evalpoly(x::S, p::LaurentPolynomial{T}) where {T,S} +#function (p::LaurentPolynomial{T})(x::S) where {T,S} m,n = (extrema ∘ degreerange)(p) - m == n == 0 && return p[0] * _one(S) + m == n == 0 && return p[0] * EvalPoly._one(S) if m >= 0 - evalpoly(x, ntuple(i -> p[i-1], n+1)) # NTuple{n+1}(p[i] for i in 0:n) + EvalPoly.evalpoly(x, ntuple(i -> p[i-1], n+1)) # NTuple{n+1}(p[i] for i in 0:n) elseif n <= 0 - evalpoly(inv(x), ntuple(i -> p[-i+1], -m+1)) # NTuple{-m+1}(p[i] for i in 0:-1:m) + EvalPoly.evalpoly(inv(x), ntuple(i -> p[-i+1], -m+1)) # NTuple{-m+1}(p[i] for i in 0:-1:m) else # eval pl(x) = a_mx^m + ...+ a_0 at 1/x; pr(x) = a_0 + a_1x + ... + a_nx^n at x; subtract a_0 - l = evalpoly(inv(x), ntuple(i -> p[-i+1], -m+1)) # NTuple{-m+1}(p[i] for i in 0:-1:m) - r = evalpoly(x, ntuple(i -> p[i-1], n+1)) # NTuple{n+1}(p[i] for i in 0:n) + l = EvalPoly.evalpoly(inv(x), ntuple(i -> p[-i+1], -m+1)) # NTuple{-m+1}(p[i] for i in 0:-1:m) + r = EvalPoly.evalpoly(x, ntuple(i -> p[i-1], n+1)) # NTuple{n+1}(p[i] for i in 0:n) mid = p[0] l + r - mid end diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 71c6ecb5..9147772a 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -52,30 +52,30 @@ end @register Polynomial -""" - (p::Polynomial)(x) - -Evaluate the polynomial using [Horner's Method](https://en.wikipedia.org/wiki/Horner%27s_method), also known as synthetic division, as implemented in `evalpoly` of base `Julia`. - -# Examples -```jldoctest -julia> using Polynomials - -julia> p = Polynomial([1, 0, 3]) -Polynomial(1 + 3*x^2) - -julia> p(0) -1 - -julia> p.(0:3) -4-element Array{Int64,1}: - 1 - 4 - 13 - 28 -``` -""" -(p::Polynomial{T})(x::S) where {T,S} = evalpoly(x, coeffs(p)) +# """ +# (p::Polynomial)(x) + +# Evaluate the polynomial using [Horner's Method](https://en.wikipedia.org/wiki/Horner%27s_method), also known as synthetic division, as implemented in `evalpoly` of base `Julia`. + +# # Examples +# ```jldoctest +# julia> using Polynomials + +# julia> p = Polynomial([1, 0, 3]) +# Polynomial(1 + 3*x^2) + +# julia> p(0) +# 1 + +# julia> p.(0:3) +# 4-element Array{Int64,1}: +# 1 +# 4 +# 13 +# 28 +# ``` +# """ +#(p::Polynomial{T})(x::S) where {T,S} = EvalPoly.evalpoly(x, coeffs(p)) # scalar +,* faster than standard-basis/common versions as it avoids a copy function Base.:+(p::P, c::S) where {T, X, P <: Polynomial{T, X}, S<:Number} diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 28a731c5..f9f2c2f5 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -186,12 +186,12 @@ end ## ---- ## - -function (p::SparsePolynomial{T})(x::S) where {T,S} +function Base.evalpoly(x::S, p::SparsePolynomial{T}) where {T,S} +#function (p::SparsePolynomial{T})(x::S) where {T,S} - tot = zero(T) * _one(x) + tot = zero(T) * EvalPoly._one(x) for (k,v) in p.coeffs - tot = _muladd(x^k, v, tot) + tot = EvalPoly._muladd(x^k, v, tot) end return tot diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index e4365a25..693a03a6 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -16,7 +16,7 @@ function showterm(io::IO, ::Type{<:StandardBasisPolynomial}, pj::T, var, j, firs end # allows broadcast issue #209 -evalpoly(x, p::StandardBasisPolynomial) = p(x) +Base.evalpoly(x, p::StandardBasisPolynomial) = EvalPoly.evalpoly(x, p.coeffs) constantterm(p::StandardBasisPolynomial) = p[0] domain(::Type{<:StandardBasisPolynomial}) = Interval(-Inf, Inf) @@ -492,7 +492,7 @@ domain(::Type{<:ArnoldiFit}) = Interval(-Inf, Inf) Base.show(io::IO, mimetype::MIME"text/plain", p::ArnoldiFit) = print(io, "ArnoldiFit of degree $(length(p.coeffs)-1)") -(p::ArnoldiFit)(x) = polyvalA(p.coeffs, p.H, x) +Base.evalpoly(x, p::ArnoldiFit) = polyvalA(p.coeffs, p.H, x) fit(::Type{ArnoldiFit}, x::AbstractVector{T}, y::AbstractVector{T}, deg::Int=length(x)-1; var=:x, kwargs...) where{T} = polyfitA(x, y, deg; var=var) @@ -530,8 +530,8 @@ end @inline function compensated_horner(ps, x) n, T = length(ps), eltype(ps) aᵢ = ps[end] - sᵢ = aᵢ * _one(x) - c = zero(T) * _one(x) + sᵢ = aᵢ * EvalPoly._one(x) + c = zero(T) * EvalPoly._one(x) for i in n-1:-1:1 aᵢ = ps[i] pᵢ, πᵢ = two_product_fma(sᵢ, x) @@ -545,8 +545,8 @@ function compensated_horner(ps::Tuple, x::S) where {S} ps == () && return zero(S) if @generated n = length(ps.parameters) - sσᵢ =:(ps[end] * _one(x), zero(S)) - c = :(zero(S) * _one(x)) + sσᵢ =:(ps[end] * EvalPoly._one(x), zero(S)) + c = :(zero(S) * EvalPoly._one(x)) for i in n-1:-1:1 pπᵢ = :(two_product_fma($sσᵢ[1], x)) sσᵢ = :(two_sum($pπᵢ[1], ps[$i])) From 316a1ffcadbff596250d01fe4c449ed2a3ab3990 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 25 Feb 2021 15:51:35 -0500 Subject: [PATCH 30/38] adjust evaluation so that implemented evalpoly instead of call syntax is expected. --- docs/src/extending.md | 2 +- src/Polynomials.jl | 4 ++++ src/common.jl | 22 ++++--------------- src/polynomials/ChebyshevT.jl | 3 +-- src/polynomials/ImmutablePolynomial.jl | 2 -- src/polynomials/LaurentPolynomial.jl | 23 +++++++------------- src/polynomials/Polynomial.jl | 24 --------------------- src/polynomials/SparsePolynomial.jl | 5 ++--- src/polynomials/standard-basis.jl | 29 +++++++++++++++++++++++--- 9 files changed, 46 insertions(+), 68 deletions(-) diff --git a/docs/src/extending.md b/docs/src/extending.md index 9b0dc247..e8f28117 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -21,7 +21,7 @@ As always, if the default implementation does not work or there are more efficie | Constructor | x | | | Type function (`(::P)(x)`) | x | | | `convert(::Polynomial, ...)` | | Not required, but the library is built off the [`Polynomial`](@ref) type, so all operations are guaranteed to work with it. Also consider writing the inverse conversion method. | -| `Base.evalpoly(x, p::P)` | to evaluate the polynomial at `x` | +| `Polynomials.evalpoly(x, p::P)` | to evaluate the polynomial at `x` (`Base.evalpoly` okay post `v"1.4.0"`) | | `domain` | x | Should return an [`AbstractInterval`](https://invenia.github.io/Intervals.jl/stable/#Intervals-1) | | `vander` | | Required for [`fit`](@ref) | | `companion` | | Required for [`roots`](@ref) | diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 6f05ec20..634e2c51 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -4,6 +4,10 @@ module Polynomials using LinearAlgebra using Intervals +if VERSION >= v"1.4.0" + import Base: evalpoly +end + include("abstract.jl") include("show.jl") include("plots.jl") diff --git a/src/common.jl b/src/common.jl index 0fda0ed2..792fb7a0 100644 --- a/src/common.jl +++ b/src/common.jl @@ -519,26 +519,12 @@ Base.broadcastable(p::AbstractPolynomial) = Ref(p) # getindex function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T <: Number} - idx < firstindex(p) && throw(BoundsError(p, idx)) - idx > lastindex(p) && return zero(T) - i = idx - firstindex(p) + 1 - return p.coeffs[i] -# return coeffs(p)[idx + 1] + m,M = firstindex(p), lastindex(p) + idx < m && throw(BoundsError(p, idx)) + idx > M && return zero(T) + p.coeffs[idx - m + 1] end Base.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) -## straight from array.jl -# function Base.getindex(A::AbstractPolynomial, I::UnitRange{Int}) -# lI = length(I) -# (first(I) < firstindex(A) || lI > length(A.coeffs)) && -# throw(ArgumentError("indexing out of bounds not permitted with ranges")) -# X = similar(A.coeffs, lI) -# if lI > 0 -# offset = 1 - firstindex(A) -# unsafe_copyto!(X, 1, A.coeffs, first(I .+ offset), lI) -# end -# return X -# end -Base.getindex(p::AbstractPolynomial, indices::CartesianIndex) = first(indices.I) Base.getindex(p::AbstractPolynomial, indices) = [p[i] for i in indices] Base.getindex(p::AbstractPolynomial, ::Colon) = coeffs(p) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index b47a7893..7381167a 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -97,8 +97,7 @@ julia> c.(-1:0.5:1) 5.0 ``` """ -function Base.evalpoly(x::S, ch::ChebyshevT{T}) where {T,S} -#function (ch::ChebyshevT{T})(x::S) where {T,S} +function evalpoly(x::S, ch::ChebyshevT{T}) where {T,S} x ∉ domain(ch) && throw(ArgumentError("$x outside of domain")) R = promote_type(T, S) length(ch) == 0 && return zero(R) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index fed181e2..d542fc91 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -163,8 +163,6 @@ truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate! ## -------------------- ## -#(p::ImmutablePolynomial{T,X,N})(x::S) where {T,X,N,S} = EvalPoly.evalpoly(x, p.coeffs) - ## Addition # scalar ops function Base.:+(p::P, c::S) where {T, X, N, P <: ImmutablePolynomial{T,X,N}, S<:Number} diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index dc297e2d..7451e517 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -176,19 +176,13 @@ Base.zero(::Type{LaurentPolynomial{T}}, var=Symbollike=:x) where {T} = Laurent Base.zero(::Type{LaurentPolynomial}, var=Symbollike=:x) = zero(LaurentPolynomial{Float64, Symbol(var)}) Base.zero(p::P, var=Symbollike=:x) where {P <: LaurentPolynomial} = zero(P, var) -# function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T} -# m,M = firstindex(p), lastindex(p) -# m <= idx <= M || return zero(T) -# p.coeffs[idx-m+1] -# end - -# # get/set index. Work with offset -function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T <: Number} - m,n = (extrema ∘ degreerange)(p) - i = idx - m + 1 - (i < 1 || i > (n-m+1)) && return zero(T) - p.coeffs[i] -end +# like that in common, only return zero if idx < firstindex(p) +function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T} + m,M = firstindex(p), lastindex(p) + m <= idx <= M || return zero(T) + p.coeffs[idx-m+1] + end + # extend if out of bounds function Base.setindex!(p::LaurentPolynomial{T}, value::Number, idx::Int) where {T} @@ -411,8 +405,7 @@ end # evaluation uses `evalpoly` -function Base.evalpoly(x::S, p::LaurentPolynomial{T}) where {T,S} -#function (p::LaurentPolynomial{T})(x::S) where {T,S} +function evalpoly(x::S, p::LaurentPolynomial{T}) where {T,S} m,n = (extrema ∘ degreerange)(p) m == n == 0 && return p[0] * EvalPoly._one(S) if m >= 0 diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 9147772a..d980158f 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -52,30 +52,6 @@ end @register Polynomial -# """ -# (p::Polynomial)(x) - -# Evaluate the polynomial using [Horner's Method](https://en.wikipedia.org/wiki/Horner%27s_method), also known as synthetic division, as implemented in `evalpoly` of base `Julia`. - -# # Examples -# ```jldoctest -# julia> using Polynomials - -# julia> p = Polynomial([1, 0, 3]) -# Polynomial(1 + 3*x^2) - -# julia> p(0) -# 1 - -# julia> p.(0:3) -# 4-element Array{Int64,1}: -# 1 -# 4 -# 13 -# 28 -# ``` -# """ -#(p::Polynomial{T})(x::S) where {T,S} = EvalPoly.evalpoly(x, coeffs(p)) # scalar +,* faster than standard-basis/common versions as it avoids a copy function Base.:+(p::P, c::S) where {T, X, P <: Polynomial{T, X}, S<:Number} diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index f9f2c2f5..bed70a8b 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -186,9 +186,8 @@ end ## ---- ## -function Base.evalpoly(x::S, p::SparsePolynomial{T}) where {T,S} -#function (p::SparsePolynomial{T})(x::S) where {T,S} - +function evalpoly(x::S, p::SparsePolynomial{T}) where {T,S} + tot = zero(T) * EvalPoly._one(x) for (k,v) in p.coeffs tot = EvalPoly._muladd(x^k, v, tot) diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 693a03a6..c2fc093b 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -15,8 +15,31 @@ function showterm(io::IO, ::Type{<:StandardBasisPolynomial}, pj::T, var, j, firs return true end -# allows broadcast issue #209 -Base.evalpoly(x, p::StandardBasisPolynomial) = EvalPoly.evalpoly(x, p.coeffs) +""" + evalpoly(x, p::StandardBasisPolynomial) + p(x) + +Evaluate the polynomial using [Horner's Method](https://en.wikipedia.org/wiki/Horner%27s_method), also known as synthetic division, as implemented in `evalpoly` of base `Julia`. + +# Examples +```jldoctest +julia> using Polynomials + +julia> p = Polynomial([1, 0, 3]) +Polynomial(1 + 3*x^2) + +julia> p(0) +1 + +julia> p.(0:3) +4-element Array{Int64,1}: + 1 + 4 + 13 + 28 +``` +""" +evalpoly(x, p::StandardBasisPolynomial) = EvalPoly.evalpoly(x, p.coeffs) # allows broadcast issue #209 constantterm(p::StandardBasisPolynomial) = p[0] domain(::Type{<:StandardBasisPolynomial}) = Interval(-Inf, Inf) @@ -492,7 +515,7 @@ domain(::Type{<:ArnoldiFit}) = Interval(-Inf, Inf) Base.show(io::IO, mimetype::MIME"text/plain", p::ArnoldiFit) = print(io, "ArnoldiFit of degree $(length(p.coeffs)-1)") -Base.evalpoly(x, p::ArnoldiFit) = polyvalA(p.coeffs, p.H, x) +evalpoly(x, p::ArnoldiFit) = polyvalA(p.coeffs, p.H, x) fit(::Type{ArnoldiFit}, x::AbstractVector{T}, y::AbstractVector{T}, deg::Int=length(x)-1; var=:x, kwargs...) where{T} = polyfitA(x, y, deg; var=var) From 3cfb7f8196014aee404847cdc83d6e9248e9d043 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 25 Feb 2021 15:56:48 -0500 Subject: [PATCH 31/38] add example to extending; specialize evalpoly rather than call syntax --- docs/src/extending.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/src/extending.md b/docs/src/extending.md index e8f28117..9f90033b 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -37,7 +37,7 @@ Check out both the [`Polynomial`](@ref) and [`ChebyshevT`](@ref) for examples of The following shows a minimal example where the polynomial aliases the vector defining the coefficients. The constructor ensures that there are no trailing zeros. The method implemented below is the convenient call syntax. This example subtypes `StandardBasisPolynomial`, not `AbstractPolynomial`, and consequently inherits the methods above. For other bases, more methods may be necessary to define (again, refer to [`ChebyshevT`](@ref) for an example). -```jldoctest +```jldoctest AliasPolynomial julia> using Polynomials julia> struct AliasPolynomial{T <: Number, X} <: Polynomials.StandardBasisPolynomial{T, X} @@ -53,7 +53,7 @@ julia> Polynomials.@register AliasPolynomial To see this new polynomial type in action, we have: -```jldoctest +```jldoctest AliasPolynomial julia> xs = [1,2,3,4]; julia> p = AliasPolynomial(xs) @@ -79,18 +79,20 @@ For the `Polynomial` type, the default on operations is to copy the array. For t Scalar addition, `p+c`, defaults to `p + c*one(p)`, or polynomial multiplication, which is not inplace without addition work. As such, we create a new method and an infix operator -```jldoctest +```jldoctest AliasPolynomial julia> function sadd!(p::AliasPolynomial{T}, c::T) where {T} p.coeffs[1] += c p end; + julia> p::AliasPolynomial ⊕ c::Number = sadd!(p,c); + ``` Then we have: -```jldoctest +```jldoctest AliasPolynomial julia> p AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) @@ -104,19 +106,22 @@ AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) The viewpoint that a polynomial represents a vector of coefficients leads to a desire to inherit vector operations when possible. Scalar multiplication is a vector operation, so it seems reasonable to override the broadcast machinery to implement an in place operation (e.g. `p .*= 2`). By default, the polynomial types are not broadcastable over their coefficients. We would need to make a change there and modify the `copyto!` function: -```jldoctest +```jldoctest AliasPolynomial julia> Base.broadcastable(p::AliasPolynomial) = p.coeffs; + julia> Base.ndims(::Type{<:AliasPolynomial}) = 1 + julia> Base.copyto!(p::AliasPolynomial, x) = (copyto!(p.coeffs, x); chop!(p)); + ``` The last `chop!` call would ensure that there are no trailing zeros in the coefficient vector after multiplication, as multiplication by `0` is possible. Then we might have: -```jldoctest +```jldoctest AliasPolynomial julia> p AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) @@ -134,8 +139,8 @@ Trying to divide again would throw an error, as the result would not fit with th Now `p` is treated as the vector `p.coeffs`, as regards broadcasting, so some things may be surprising, for example this expression returns a vector, not a polynomial: -```jldoctest -p .+ 2 +```jldoctest AliasPolynomial +julia> p .+ 2 4-element Array{Int64,1}: 5 4 From 456bc09c9a7fa0aed98e09375150297fc83edff2 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 25 Feb 2021 20:18:41 -0500 Subject: [PATCH 32/38] refactor truncate! chop! --- src/.#common.jl | 1 + src/common.jl | 77 ++++++++++++++++++++++---- src/polynomials/ImmutablePolynomial.jl | 11 ++-- src/polynomials/LaurentPolynomial.jl | 21 +------ src/polynomials/SparsePolynomial.jl | 34 +----------- 5 files changed, 74 insertions(+), 70 deletions(-) create mode 120000 src/.#common.jl diff --git a/src/.#common.jl b/src/.#common.jl new file mode 120000 index 00000000..69a8b86c --- /dev/null +++ b/src/.#common.jl @@ -0,0 +1 @@ +jverzani@john-verzanis-macbook-pro.local.1015 \ No newline at end of file diff --git a/src/common.jl b/src/common.jl index a5ae750c..4e327158 100644 --- a/src/common.jl +++ b/src/common.jl @@ -246,12 +246,40 @@ In-place version of [`truncate`](@ref) function truncate!(p::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0,) where {T} - max_coeff = maximum(abs, coeffs(p)) - thresh = max_coeff * rtol + atol - map!(c->abs(c) <= thresh ? zero(T) : c, coeffs(p), coeffs(p)) + truncate!(p.coeffs, rtol=rtol, atol=atol) return chop!(p, rtol = rtol, atol = atol) end +function truncate!(ps::Vector{T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {T} + max_coeff = norm(ps, Inf) + thresh = max_coeff * rtol + atol + for (i,pᵢ) ∈ pairs(ps) + if abs(pᵢ) <= thresh + ps[i] = zero(T) + end + end + nothing +end + +function truncate!(ps::Dict{Int,T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {T} + + max_coeff = norm(values(ps), Inf) + thresh = max_coeff * rtol + atol + + for (k,val) in ps + if abs(val) <= thresh + pop!(ps,k) + end + end + nothing +end + +truncate!(ps::NTuple; kwargs...) = throw(ArgumentError("`truncate!` not defined.")) + """ truncate(::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) @@ -273,19 +301,46 @@ In-place version of [`chop`](@ref) function chop!(p::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0,) where {T} - isempty(coeffs(p)) && return p - tol = norm(p) * rtol + atol - for i = lastindex(p):-1:0 - val = p[i] + chop!(p.coeffs, rtol=rtol, atol=atol) + return p +end + +# chop! underlying storage type +function chop!(ps::Vector{T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {T} + + tol = norm(ps) * rtol + atol + for i = lastindex(ps):-1:1 + val = ps[i] if abs(val) > tol #!isapprox(val, zero(T); rtol = rtol, atol = atol) - resize!(p.coeffs, i + 1); - return p + resize!(ps, i); + return nothing end end - resize!(p.coeffs, 1) - return p + resize!(ps, 1) + return nothing end +function chop!(ps::Dict{Int,T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {T} + + tol = norm(values(ps)) * rtol + atol + + for k in sort(collect(keys(ps)), by=x->x[1], rev=true) + if abs(ps[k]) > tol + return nothing + end + pop!(ps, k) + end + + return nothing +end + +chop!(ps::NTuple; kwargs...) = throw(ArgumentError("chop! not defined")) + + """ chop(::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0)) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index d542fc91..d4a6a88a 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -134,10 +134,12 @@ end # in common.jl these call chop! and truncate! function Base.chop(p::ImmutablePolynomial{T,X,N}; rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0) where {T,X,N} + atol::Real = 0) where {T,X,N} + N == 0 && return p cs = coeffs(p) + thresh = maximum(abs, cs) * rtol + atol for i in N:-1:1 - if !isapprox(cs[i], zero(T), rtol=rtol, atol=atol) + if abs(cs[i]) > thresh return ImmutablePolynomial{T,X,i}(cs[1:i]) end end @@ -148,16 +150,13 @@ function Base.truncate(p::ImmutablePolynomial{T,X,N}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) where {T,X,N} q = chop(p, rtol=rtol, atol=atol) - iszero(q) && return q + iszero(q) && return q cs = coeffs(q) thresh = maximum(abs,cs) * rtol + atol cs′ = map(c->abs(c) <= thresh ? zero(T) : c, cs) ImmutablePolynomial{T,X}(tuple(cs′...)) end -# no in-place chop! and truncate! -chop!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `chop!` for the `ImmutablePolynomial` type. Use `chop`?")) -truncate!(p::ImmutablePolynomial; kwargs...) = throw(MethodError("No `truncate!` for the `ImmutablePolynomial` type. Use `trunctate`?")) ## ## -------------------- diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 7451e517..dd7f226f 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -212,7 +212,7 @@ degreerange(p::LaurentPolynomial) = firstindex(p):lastindex(p) _convert(p::P, as) where {T,X,P <: LaurentPolynomial{T,X}} = ⟒(P)(as, firstindex(p), X) -## chop/truncation +## chop! # trim from *both* ends function chop!(p::LaurentPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), @@ -245,25 +245,6 @@ function chop!(p::LaurentPolynomial{T}; end -function truncate!(p::LaurentPolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} - - max_coeff = maximum(abs, coeffs(p)) - thresh = max_coeff * rtol + atol - - for i in eachindex(p) - if abs(p[i]) <= thresh - p[i] = zero(T) - end - end - - chop!(p) - - return p - -end - # use unicode exponents. XXX modify printexponent to always use these? function showterm(io::IO, ::Type{<:LaurentPolynomial}, pj::T, var, j, first::Bool, mimetype) where {T} if iszero(pj) return false end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index bed70a8b..4128fe3c 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -141,6 +141,7 @@ function Base.iterate(v::PolynomialKeys{SparsePolynomial{T,X}}, state...) where y == nothing && return nothing return (y[1][1], y[2]) end + function Base.iterate(v::PolynomialValues{SparsePolynomial{T,X}}, state...) where {T,X} y = iterate(v.p.coeffs, state...) y == nothing && return nothing @@ -148,39 +149,6 @@ function Base.iterate(v::PolynomialValues{SparsePolynomial{T,X}}, state...) wher end -# only from tail -function chop!(p::SparsePolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} - - for k in sort(collect(keys(p.coeffs)), by=x->x[1], rev=true) - if isapprox(p[k], zero(T); rtol = rtol, atol = atol) - pop!(p.coeffs, k) - else - return p - end - end - - return p - -end - -function truncate!(p::SparsePolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} - - max_coeff = maximum(abs, coeffs(p)) - thresh = max_coeff * rtol + atol - - for (k,val) in p.coeffs - if abs(val) <= thresh - pop!(p.coeffs,k) - end - end - - return p - -end ## ## ---- From c5e155e5560702fe49c520423b5836620ce544ae Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 25 Feb 2021 20:18:59 -0500 Subject: [PATCH 33/38] oops --- src/.#common.jl | 1 - src/common.jl | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 120000 src/.#common.jl diff --git a/src/.#common.jl b/src/.#common.jl deleted file mode 120000 index 69a8b86c..00000000 --- a/src/.#common.jl +++ /dev/null @@ -1 +0,0 @@ -jverzani@john-verzanis-macbook-pro.local.1015 \ No newline at end of file diff --git a/src/common.jl b/src/common.jl index 4e327158..c03f7f63 100644 --- a/src/common.jl +++ b/src/common.jl @@ -250,6 +250,7 @@ function truncate!(p::AbstractPolynomial{T}; return chop!(p, rtol = rtol, atol = atol) end +## truncate! underlying storage type function truncate!(ps::Vector{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0,) where {T} From 1e8947643cbe3584f18118612da77845e68fd438 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 2 Mar 2021 12:03:30 -0500 Subject: [PATCH 34/38] move things into common (truncate, chop, zero, one,variable, basis) --- src/common.jl | 83 +++++++++++++++++--------- src/polynomials/ChebyshevT.jl | 13 ++-- src/polynomials/ImmutablePolynomial.jl | 45 +++----------- src/polynomials/LaurentPolynomial.jl | 21 +------ src/polynomials/Poly.jl | 11 ++-- src/polynomials/SparsePolynomial.jl | 5 -- src/polynomials/standard-basis.jl | 9 ++- test/StandardBasis.jl | 2 +- 8 files changed, 90 insertions(+), 99 deletions(-) diff --git a/src/common.jl b/src/common.jl index c03f7f63..e5271d5a 100644 --- a/src/common.jl +++ b/src/common.jl @@ -126,7 +126,7 @@ fit(P::Type{<:AbstractPolynomial}, var = :x,) = fit′(P, promote(collect(x), collect(y))..., deg; weights = weights, var = var) # avoid issue 214 -fit′(P::Type{<:AbstractPolynomial}, x, y, args...;kwargs...) = throw(MethodError("x and y do not produce abstract vectors")) +fit′(P::Type{<:AbstractPolynomial}, x, y, args...;kwargs...) = throw(ArgumentError("x and y do not produce abstract vectors")) fit′(P::Type{<:AbstractPolynomial}, x::AbstractVector{T}, y::AbstractVector{T}, @@ -207,7 +207,7 @@ vander(::Type{<:AbstractPolynomial}, x::AbstractVector, deg::Integer) Return an antiderivative for `p` """ -integrate(P::AbstractPolynomial) = throw(MethodError("`integrate` not implemented for polynomials of type $P")) +integrate(P::AbstractPolynomial) = throw(ArgumentError("`integrate` not implemented for polynomials of type $P")) """ integrate(::AbstractPolynomial, C) @@ -280,6 +280,14 @@ function truncate!(ps::Dict{Int,T}; end truncate!(ps::NTuple; kwargs...) = throw(ArgumentError("`truncate!` not defined.")) +Base.truncate(ps::NTuple{0}; kwargs...) = ps +function Base.truncate(ps::NTuple{N,T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {N,T} + thresh = norm(ps, Inf) * rtol + atol + return NTuple{N,T}(abs(pᵢ) <= thresh ? zero(T) : pᵢ for pᵢ ∈ values(ps)) +end + """ truncate(::AbstractPolynomial{T}; @@ -340,6 +348,19 @@ function chop!(ps::Dict{Int,T}; end chop!(ps::NTuple; kwargs...) = throw(ArgumentError("chop! not defined")) +Base.chop(ps::NTuple{0}; kwargs...) = ps +function Base.chop(ps::NTuple{N,T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {N,T} + thresh = norm(ps, Inf) * rtol + atol + for i in N:-1:1 + if abs(ps[i]) > thresh + return ps[1:i] + end + end + return NTuple{0,T}() +end + """ @@ -492,7 +513,18 @@ isintegral(p::AbstractPolynomial) = all(isinteger, p) Determine whether a polynomial is a monic polynomial, i.e., its leading coefficient is one. """ -ismonic(p::AbstractPolynomial) = isone(p[end]) +ismonic(p::AbstractPolynomial) = isone(convert(Polynomial, p)[end]) + +"`hasnan(p::AbstractPolynomial)` are any coefficients `NaN`" +hasnan(p::AbstractPolynomial) = any(isnan, p) + + +""" + isconstant(::AbstractPolynomial) + +Is the polynomial `p` a constant. +""" +isconstant(p::AbstractPolynomial) = degree(p) <= 0 """ coeffs(::AbstractPolynomial) @@ -512,7 +544,6 @@ return `p(0)`, the constant term in the standard basis constantterm(p::AbstractPolynomial{T}) where {T} = p(zero(T)) -hasnan(p::AbstractPolynomial) = any(isnan, p) """ @@ -524,13 +555,6 @@ has a nonzero coefficient. The degree of the zero polynomial is defined to be -1 degree(p::AbstractPolynomial) = iszero(p) ? -1 : lastindex(p) -""" - isconstant(::AbstractPolynomial) - -Is the polynomial `p` a constant. -""" -isconstant(p::AbstractPolynomial) = degree(p) <= 0 - """ domain(::Type{<:AbstractPolynomial}) @@ -566,6 +590,7 @@ function mapdomain(P::Type{<:AbstractPolynomial}, x::AbstractArray) return x_scaled end mapdomain(::P, x::AbstractArray) where {P <: AbstractPolynomial} = mapdomain(P, x) + #= indexing =# Base.firstindex(p::AbstractPolynomial) = 0 @@ -713,8 +738,11 @@ zero, one, variable, basis =# Returns a representation of 0 as the given polynomial. """ -Base.zero(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P)(zeros(eltype(P), 1), var) -Base.zero(::Type{P}) where {T, X, P<:AbstractPolynomial{T,X}} = ⟒(P){T,X}(zeros(T,1)) +function Base.zero(::Type{P}) where {P<:AbstractPolynomial} + T,X = eltype(P), indeterminate(P) + ⟒(P){T,X}(zeros(T,1)) +end +Base.zero(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = zero(⟒(P){eltype(P),Symbol(var)}) #default 0⋅b₀ Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, indeterminate(p)) """ one(::Type{<:AbstractPolynomial}) @@ -722,9 +750,9 @@ Base.zero(p::P) where {P <: AbstractPolynomial} = zero(P, indeterminate(p)) Returns a representation of 1 as the given polynomial. """ -Base.one(::Type{P}, var=:x) where {P <: AbstractPolynomial} = ⟒(P){eltype(P), Symbol(var)}(ones(eltype(P),1)) # assumes p₀ = 1 -Base.one(::Type{P}) where {T, X, P<:AbstractPolynomial{T,X}} = ⟒(P){T,X}(ones(T,1)) -Base.one(p::P) where {P <: AbstractPolynomial} = one(P, indeterminate(p)) +Base.one(::Type{P}) where {P<:AbstractPolynomial} = throw(ArgumentError("No default method defined")) # no default method +Base.one(::Type{P}, var) where {P <: AbstractPolynomial} = one(⟒(P){eltype(P), Symbol(var == nothing ? :x : var)}) +Base.one(p::P) where {P <: AbstractPolynomial} = one(P) Base.oneunit(::Type{P}, args...) where {P <: AbstractPolynomial} = one(P, args...) Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) @@ -754,22 +782,23 @@ julia> roots((x - 3) * (x + 2)) ``` """ -variable(::Type{P}, var::SymbolLike = :x) where {P <: AbstractPolynomial} = MethodError() +variable(::Type{P}) where {P <: AbstractPolynomial} = throw(ArgumentError("No default method defined")) # no default +variable(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = variable(⟒(P){eltype(P),Symbol(var)}) variable(p::AbstractPolynomial, var::SymbolLike = indeterminate(p)) = variable(typeof(p), var) -variable(::Type{P}) where {T,X, P <: AbstractPolynomial{T,X}} = variable(P, X) variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) # basis # var is a positional argument, not a keyword; can't deprecate so we do `_var; var=_var` # return the kth basis polynomial for the given polynomial type, e.g. x^k for Polynomial{T} -function basis(::Type{P}, k::Int) where {T, X, P<:AbstractPolynomial{T,X}} - zs = zeros(Int, k+1) - zs[end] = 1 - ⟒(P){eltype(P), X}(zs) +function basis(::Type{P}, k::Int) where {P<:AbstractPolynomial} + T,X = eltype(P), indeterminate(P) + zs = zeros(T, k+1) + zs[end] = one(T) + ⟒(P){eltype(P), indeterminate(P)}(zs) end -function basis(::Type{P}, k::Int, _var::SymbolLike=:x; var=_var) where {P <: AbstractPolynomial} - T,X = eltype(P), Symbol(_var) - basis(⟒(P){T,X},k) +function basis(::Type{P}, k::Int, _var::SymbolLike; var=_var) where {P <: AbstractPolynomial} + T,X = eltype(P), Symbol(var) + basis(⟒(P){T,X}, k) end basis(p::P, k::Int, _var::SymbolLike=:x; var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) @@ -793,7 +822,7 @@ Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) ## addition -## Fall back addition. +## Fall back addition is possible as vector addition with padding by 0s ## Subtypes will likely want to implement both: ## +(p::P,c::Number) and +(p::P, q::Q) where {T,S,X,P<:SubtypePolynomial{T,X},Q<:SubtypePolynomial{S,X}} ## though the default for poly+poly isn't terrible @@ -802,7 +831,6 @@ Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = p + c * one(P) function Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} - R = promote_type(T,S) q = convert(⟒(P){R,X}, p) q + R(c) @@ -810,7 +838,6 @@ end # polynomial + polynomial when different types function Base.:+(p::P, q::Q) where {T,X,P <: AbstractPolynomial{T,X}, S,Y,Q <: AbstractPolynomial{S,Y}} - isconstant(p) && return constantterm(p) + q isconstant(q) && return p + constantterm(q) assert_same_variable(X,Y) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 7381167a..103eaaf4 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -68,11 +68,15 @@ Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) = p(variable(C)) domain(::Type{<:ChebyshevT}) = Interval(-1, 1) -function variable(P::Type{<:ChebyshevT}, var::SymbolLike) - X′ = _indeterminate(P) - X = X′ == nothing ? Symbol(var) : X′ - ⟒(P){eltype(P), X}([0, 1]) +function Base.one(::Type{P}) where {P<:ChebyshevT} + T,X = eltype(P), indeterminate(P) + ChebyshevT{T,X}(ones(T,1)) end +function variable(::Type{P}) where {P<:ChebyshevT} + T,X = eltype(P), indeterminate(P) + ChebyshevT{T,X}([zero(T), one(T)]) +end +constantterm(p::ChebyshevT) = p(0) """ (::ChebyshevT)(x) @@ -110,7 +114,6 @@ function evalpoly(x::S, ch::ChebyshevT{T}) where {T,S} return R(c0 + c1 * x) end -constantterm(p::ChebyshevT) = p(0) function vander(P::Type{<:ChebyshevT}, x::AbstractVector{T}, n::Integer) where {T <: Number} A = Matrix{T}(undef, length(x), n + 1) diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl index d4a6a88a..af7a235e 100644 --- a/src/polynomials/ImmutablePolynomial.jl +++ b/src/polynomials/ImmutablePolynomial.jl @@ -92,29 +92,13 @@ end ## ---- ## # overrides from common.jl due to coeffs being non mutable, N in type parameters -Base.collect(p::P) where {P <: ImmutablePolynomial} = [pᵢ for pᵢ ∈ p] Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p)) -function Base.one(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) - R = eltype(P) - ImmutablePolynomial{R,Symbol(var),1}(NTuple{1,R}(one(R))) -end -function variable(P::Type{<:ImmutablePolynomial}, var::SymbolLike=indeterminate(P)) - R = eltype(P) - ImmutablePolynomial{R,Symbol(var),2}(NTuple{2,R}((zero(R), one(R)))) -end - - # degree, isconstant degree(p::ImmutablePolynomial{T,X, N}) where {T,X,N} = N - 1 # no trailing zeros isconstant(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = N <= 1 -function Base.getindex(p::ImmutablePolynomial{T,X, N}, idx::Int) where {T <: Number,X, N} - (idx < 0 || idx > N-1) && return zero(T) - return p.coeffs[idx + 1] -end - Base.setindex!(p::ImmutablePolynomial, val::Number, idx::Int) = throw(ArgumentError("ImmutablePolynomials are immutable")) for op in [:isequal, :(==)] @@ -132,29 +116,18 @@ for op in [:isequal, :(==)] end # in common.jl these call chop! and truncate! -function Base.chop(p::ImmutablePolynomial{T,X,N}; +function Base.chop(p::ImmutablePolynomial{T,X}; rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0) where {T,X,N} - N == 0 && return p - cs = coeffs(p) - thresh = maximum(abs, cs) * rtol + atol - for i in N:-1:1 - if abs(cs[i]) > thresh - return ImmutablePolynomial{T,X,i}(cs[1:i]) - end - end - zero(ImmutablePolynomial{T,X}) + atol::Real = 0) where {T,X} + ps = chop(p.coeffs; rtol=rtol, atol=atol) + return ImmutablePolynomial{T,X}(ps) end -function Base.truncate(p::ImmutablePolynomial{T,X,N}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0) where {T,X,N} - q = chop(p, rtol=rtol, atol=atol) - iszero(q) && return q - cs = coeffs(q) - thresh = maximum(abs,cs) * rtol + atol - cs′ = map(c->abs(c) <= thresh ? zero(T) : c, cs) - ImmutablePolynomial{T,X}(tuple(cs′...)) +function Base.truncate(p::ImmutablePolynomial{T,X}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0) where {T,X} + ps = truncate(p.coeffs; rtol=rtol, atol=atol) + ImmutablePolynomial{T,X}(ps) end diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index dd7f226f..08362d2e 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -169,12 +169,8 @@ Base.:(==)(p1::LaurentPolynomial, p2::LaurentPolynomial) = Base.hash(p::LaurentPolynomial, h::UInt) = hash(indeterminate(p), hash(degreerange(p), hash(coeffs(p), h))) isconstant(p::LaurentPolynomial) = iszero(lastindex(p)) && iszero(firstindex(p)) -basis(P::Type{<:LaurentPolynomial{T}}, n::Int, var::SymbolLike=:x) where{T} = LaurentPolynomial{T,Symbol(var)}(ones(T,1), n) -basis(P::Type{LaurentPolynomial}, n::Int, var::SymbolLike=:x) = LaurentPolynomial{Float64, Symbol(var)}(ones(Float64, 1), n) -Base.zero(::Type{LaurentPolynomial{T}}, var=Symbollike=:x) where {T} = LaurentPolynomial{T,Symbol(var)}(zeros(T,1), 0) -Base.zero(::Type{LaurentPolynomial}, var=Symbollike=:x) = zero(LaurentPolynomial{Float64, Symbol(var)}) -Base.zero(p::P, var=Symbollike=:x) where {P <: LaurentPolynomial} = zero(P, var) +basis(P::Type{<:LaurentPolynomial{T,X}}, n::Int) where {T,X} = LaurentPolynomial{T,X}(ones(T,1), n) # like that in common, only return zero if idx < firstindex(p) function Base.getindex(p::LaurentPolynomial{T}, idx::Int) where {T} @@ -387,19 +383,8 @@ end # evaluation uses `evalpoly` function evalpoly(x::S, p::LaurentPolynomial{T}) where {T,S} - m,n = (extrema ∘ degreerange)(p) - m == n == 0 && return p[0] * EvalPoly._one(S) - if m >= 0 - EvalPoly.evalpoly(x, ntuple(i -> p[i-1], n+1)) # NTuple{n+1}(p[i] for i in 0:n) - elseif n <= 0 - EvalPoly.evalpoly(inv(x), ntuple(i -> p[-i+1], -m+1)) # NTuple{-m+1}(p[i] for i in 0:-1:m) - else - # eval pl(x) = a_mx^m + ...+ a_0 at 1/x; pr(x) = a_0 + a_1x + ... + a_nx^n at x; subtract a_0 - l = EvalPoly.evalpoly(inv(x), ntuple(i -> p[-i+1], -m+1)) # NTuple{-m+1}(p[i] for i in 0:-1:m) - r = EvalPoly.evalpoly(x, ntuple(i -> p[i-1], n+1)) # NTuple{n+1}(p[i] for i in 0:n) - mid = p[0] - l + r - mid - end + xᵐ = (x/1)^firstindex(p) # make type stable + return EvalPoly.evalpoly(x, p.coeffs) * xᵐ end diff --git a/src/polynomials/Poly.jl b/src/polynomials/Poly.jl index 8cb9611e..37a2b2c9 100644 --- a/src/polynomials/Poly.jl +++ b/src/polynomials/Poly.jl @@ -56,10 +56,13 @@ function Base.iterate(p::Poly, state=nothing) end Base.collect(p::Poly) = [pᵢ for pᵢ ∈ p] - - -Base.zero(P::Type{<:Poly},var=:x) = Poly(zeros(_eltype(P),0), var) -Base.one(P::Type{<:Poly},var=:x) = Poly(ones(_eltype(P),1), var) +# need two here as `eltype(P)` is `_eltype(P)`. +Base.zero(::Type{P}) where {P <: Poly} = Poly{_eltype(P), Polynomials.indeterminate(P)}([0]) +Base.zero(::Type{P},var::Polynomials.SymbolLike) where {P <: Poly} = Poly(zeros(_eltype(P),1), var) +Base.one(::Type{P}) where {P <: Poly} = Poly{_eltype(P), Polynomials.indeterminate(P)}([1]) +Base.one(::Type{P},var::Polynomials.SymbolLike) where {P <: Poly} = Poly(ones(_eltype(P),1), var) +Polynomials.variable(::Type{P}) where {P <: Poly} = Poly{_eltype(P), Polynomials.indeterminate(P)}([0,1]) +Polynomials.variable(::Type{P},var::Polynomials.SymbolLike) where {P <: Poly} = Poly(_eltype(P)[0,1], var) function Polynomials.basis(P::Type{<:Poly}, k::Int, _var::Polynomials.SymbolLike=:x; var=_var) zs = zeros(Int, k+1) zs[end] = 1 diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index 4128fe3c..b8ac17bf 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -95,11 +95,6 @@ function isconstant(p::SparsePolynomial) return true end -function basis(P::Type{<:SparsePolynomial}, n::Int, var::SymbolLike=:x) - T = eltype(P) - X = Symbol(var) - SparsePolynomial{T,X}(Dict(n=>one(T))) -end # return coeffs as a vector # use p.coeffs to get Dictionary diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index c2fc093b..860bcff4 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -55,8 +55,13 @@ function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolyno end end -function variable(::Type{P}, var::SymbolLike) where {P <: StandardBasisPolynomial} - ⟒(P){eltype(P), indeterminate(P,Symbol(var))}([0, 1]) +function Base.one(::Type{P}) where {P<:StandardBasisPolynomial} + T,X = eltype(P), indeterminate(P) + ⟒(P){T,X}(ones(T,1)) +end +function variable(::Type{P}) where {P<:StandardBasisPolynomial} + T,X = eltype(P), indeterminate(P) + ⟒(P){T,X}([zero(T),one(T)]) end ## multiplication algorithms for computing p * q. diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 92596f6c..df736aac 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -353,7 +353,7 @@ end fit(P, xx, yy, 2) # issue #214 -- should error - @test_throws MethodError fit(Polynomial, rand(2,2), rand(2,2)) + @test_throws ArgumentError fit(Polynomial, rand(2,2), rand(2,2)) # issue #268 -- inexacterror @test fit(P, 1:4, 1:4, var=:x) ≈ variable(P{Float64}, :x) From 3450f5e84a586c6756ec4f8254757e5cfbd303d3 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 23 Mar 2021 13:45:00 -0400 Subject: [PATCH 35/38] close issue #316 --- src/abstract.jl | 8 ++++---- test/StandardBasis.jl | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/abstract.jl b/src/abstract.jl index 5bfed88e..1a91370a 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -56,9 +56,9 @@ macro register(name) $poly{eltype(cs), Symbol(var)}(cs) end $poly{T,X}(n::S) where {T, X, S<:Number} = - n * one($poly{T, X}) + T(n) * one($poly{T, X}) $poly{T}(n::S, var::SymbolLike = :x) where {T, S<:Number} = - n * one($poly{T, Symbol(var)}) + T(n) * one($poly{T, Symbol(var)}) $poly(n::S, var::SymbolLike = :x) where {S <: Number} = n * one($poly{S, Symbol(var)}) $poly{T}(var::SymbolLike=:x) where {T} = variable($poly{T, Symbol(var)}) $poly(var::SymbolLike=:x) = variable($poly, Symbol(var)) @@ -85,8 +85,8 @@ macro registerN(name, params...) $poly{$(αs...)}(coeffs::AbstractVector{T}, var::SymbolLike=:x) where {$(αs...),T} = $poly{$(αs...),T,Symbol(var)}(coeffs) - $poly{$(αs...),T,X}(n::Number) where {$(αs...),T,X} = n*one($poly{$(αs...),T,X}) - $poly{$(αs...),T}(n::Number, var::SymbolLike = :x) where {$(αs...),T} = n*one($poly{$(αs...),T,Symbol(var)}) + $poly{$(αs...),T,X}(n::Number) where {$(αs...),T,X} = T(n)*one($poly{$(αs...),T,X}) + $poly{$(αs...),T}(n::Number, var::SymbolLike = :x) where {$(αs...),T} = T(n)*one($poly{$(αs...),T,Symbol(var)}) $poly{$(αs...)}(n::S, var::SymbolLike = :x) where {$(αs...), S<:Number} = n*one($poly{$(αs...),S,Symbol(var)}) $poly{$(αs...),T}(var::SymbolLike=:x) where {$(αs...), T} = variable($poly{$(αs...),T,Symbol(var)}) diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index df736aac..93eb57f5 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -42,6 +42,12 @@ isimmutable(::Type{<:ImmutablePolynomial}) = true P == Polynomial && @test typeof(p).parameters[1] == eltype(coeff) @test eltype(p) == eltype(coeff) @test all([-200, -0.3, 1, 48.2] .∈ domain(p)) + + ## issue #316 + @test_throws InexactError P{Int,:x}([1+im, 1]) + @test_throws InexactError P{Int}([1+im, 1], :x) + @test_throws InexactError P{Int,:x}(1+im) + @test_throws InexactError P{Int}(1+im) end end From 9baddaa222a8d4894ff8c9aeac6b555774d951ce Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 23 Mar 2021 13:48:40 -0400 Subject: [PATCH 36/38] remove changes from a different branch --- docs/src/extending.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/extending.md b/docs/src/extending.md index 8571eba4..ac7fd581 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -25,7 +25,6 @@ As always, if the default implementation does not work or there are more efficie | `domain` | x | Should return an [`AbstractInterval`](https://invenia.github.io/Intervals.jl/stable/#Intervals-1) | | `vander` | | Required for [`fit`](@ref) | | `companion` | | Required for [`roots`](@ref) | -| `fromroots` | | By default, will form polynomials using `prod(variable(::P) - r)` for reach root `r`| | `*(::P, ::P)` | | Multiplication of polynomials | | `divrem` | | Required for [`gcd`](@ref)| | `one`| | Convenience to find constant in new basis | @@ -36,7 +35,7 @@ Check out both the [`Polynomial`](@ref) and [`ChebyshevT`](@ref) for examples of ## Example The following shows a minimal example where the polynomial aliases the vector defining the coefficients. -The constructor ensures that there are no trailing zeros. The method implemented below is the convenient call syntax. This example subtypes `StandardBasisPolynomial`, not `AbstractPolynomial`, and consequently inherits the methods above. For other bases, more methods may be necessary to define (again, refer to [`ChebyshevT`](@ref) for an example). +The constructor ensures that there are no trailing zeros. The `@register` call ensures a common interface. This example subtypes `StandardBasisPolynomial`, not `AbstractPolynomial`, and consequently inherits the methods above that otherwise would have been required. For other bases, more methods may be necessary to define (again, refer to [`ChebyshevT`](@ref) for an example). ```jldoctest AliasPolynomial julia> using Polynomials From 3cd3e77916a4ad7c3222115e98bb7377eac2c378 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 23 Mar 2021 14:02:50 -0400 Subject: [PATCH 37/38] fix issue with ngcd --- src/polynomials/ngcd.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/polynomials/ngcd.jl b/src/polynomials/ngcd.jl index 80a07600..d7bc7577 100644 --- a/src/polynomials/ngcd.jl +++ b/src/polynomials/ngcd.jl @@ -10,10 +10,10 @@ In the case `degree(p) ≫ degree(q)`, a heuristic is employed to first call on function ngcd(p::P, q::Q, args...;kwargs...) where {T, S, P<:StandardBasisPolynomial{T}, Q <: StandardBasisPolynomial{S}} degree(p) < 0 && return (u=q, v=p, w=one(q), θ=NaN, κ=NaN) - degree(p) == 0 && return (u=one(q), v=p, w=zero(q), θ=NaN, κ=NaN) + degree(p) == 0 && return (u=one(q), v=p, w=q, θ=NaN, κ=NaN) degree(q) < 0 && return (u=one(q), v=p, w=zero(q), θ=NaN, κ=NaN) degree(q) == 0 && return (u=one(p), v=p, w=q, θ=NaN, κ=NaN) - assert_same_variable(p,q) + assert_same_variable(p,q) p′,q′ = promote(p,q) @@ -264,8 +264,8 @@ end # return guess at smallest singular value and right sinuglar value, x # for an upper triangular matrix, V function smallest_singular_value(V::AbstractArray{T,2}, - atol=eps(T), - rtol=zero(T)) where {T} + atol=eps(real(T)), + rtol=zero(real(T))) where {T} R = UpperTriangular(V) k = size(R)[1]/2 From 62dea568fbb5d50b5b51663a78256fa6310ff49c Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 23 Mar 2021 14:03:15 -0400 Subject: [PATCH 38/38] version bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 38791059..e2726fe6 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Polynomials" uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" license = "MIT" author = "JuliaMath" -version = "2.0.0" +version = "2.0.1" [deps] Intervals = "d8418881-c3e1-53bb-8760-2df7ec849ed5"