diff --git a/.travis.yml b/.travis.yml index 418d41eb..0214f24f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ julia: - 1.1 - 1.2 - 1.3 + - 1.4 - nightly matrix: diff --git a/Project.toml b/Project.toml index a5241acb..28b610ac 100644 --- a/Project.toml +++ b/Project.toml @@ -10,8 +10,8 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" [compat] -Intervals = "0.5.0" -RecipesBase = "0.7, 0.8" +Intervals = "0.5.0, 1.0" +RecipesBase = "0.7, 0.8, 1.0" julia = "1" diff --git a/README.md b/README.md index 2b086250..3098c411 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ ERROR: Polynomials must have same variable. #### Integrals and Derivatives Integrate the polynomial `p` term by term, optionally adding constant -term `k`. The order of the resulting polynomial is one higher than the -order of `p`. +term `k`. The degree of the resulting polynomial is one higher than the +degree of `p`. ```julia julia> integrate(Polynomial([1, 0, -1])) @@ -107,8 +107,8 @@ julia> integrate(Polynomial([1, 0, -1]), 2) Polynomial(2.0 + x - 0.3333333333333333x^3) ``` -Differentiate the polynomial `p` term by term. The order of the -resulting polynomial is one lower than the order of `p`. +Differentiate the polynomial `p` term by term. The degree of the +resulting polynomial is one lower than the degree of `p`. ```julia julia> derivative(Polynomial([1, 3, -1])) @@ -119,7 +119,7 @@ Polynomial(3 - 2x) Return the roots (zeros) of `p`, with multiplicity. The number of -roots returned is equal to the order of `p`. By design, this is not type-stable, +roots returned is equal to the degree of `p`. By design, this is not type-stable, the returned roots may be real or complex. ```julia @@ -130,8 +130,8 @@ julia> roots(Polynomial([1, 0, -1])) julia> roots(Polynomial([1, 0, 1])) 2-element Array{Complex{Float64},1}: - 0.0+1.0im - 0.0-1.0im + 0.0 - 1.0im + 0.0 + 1.0im julia> roots(Polynomial([0, 0, 1])) 2-element Array{Float64,1}: @@ -141,16 +141,16 @@ julia> roots(Polynomial([0, 0, 1])) #### Fitting arbitrary data -Fit a polynomial (of order `deg`) to `x` and `y` using a least-squares approximation. +Fit a polynomial (of degree `deg`) to `x` and `y` using a least-squares approximation. ```julia julia> xs = 0:4; ys = @. exp(-xs) + sin(xs); -julia> fit(xs, ys) -Polynomial(1.0000000000000016 + 0.059334723072240664*x + 0.39589720602859824*x^2 - 0.2845598112184312*x^3 + 0.03867830809692903*x^4) +julia> fit(xs, ys) |> x -> round(x, digits=4) +Polynomial(1.0 + 0.0593*x + 0.3959*x^2 - 0.2846*x^3 + 0.0387*x^4) -julia> fit(ChebyshevT, xs, ys, deg=2) -ChebyshevT([0.541280671210034, -0.8990834124779993, -0.4237852336242923]) +julia> fit(ChebyshevT, xs, ys, deg=2) |> x -> round(x, digits=4) +ChebyshevT(0.5413⋅T_0(x) - 0.8991⋅T_1(x) - 0.4238⋅T_2(x)) ``` Visual example: @@ -166,15 +166,16 @@ Polynomial objects also have other methods: * `coeffs`: returns the entire coefficient vector -* `degree`: returns the polynomial degree, `length` is 1 plus the degree +* `degree`: returns the polynomial degree, `length` is number of stored coefficients -* `variable`: returns the polynomial symbol as a degree 1 polynomial +* `variable`: returns the polynomial symbol as polynomial in the underlying type * `norm`: find the `p`-norm of a polynomial -* `conj`: finds the conjugate of a polynomial over a complex fiel +* `conj`: finds the conjugate of a polynomial over a complex field + +* `truncate`: set to 0 all small terms in a polynomial; -* `truncate`: set to 0 all small terms in a polynomial; * `chop` chops off any small leading values that may arise due to floating point operations. * `gcd`: greatest common divisor of two polynomials. diff --git a/docs/src/extending.md b/docs/src/extending.md index 6eef893a..ee09e5e6 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -1,12 +1,15 @@ # Extending Polynomials -The [`AbstractPolynomial`](@ref) type was made to be extended via a rich interface. +The [`AbstractPolynomial`](@ref) type was made to be extended via a rich interface. ```@docs AbstractPolynomial ``` -To implement a new polynomial type, `P`, the following methods should be implemented. +A polynomial's coefficients are relative to some *basis*. The `Polynomial` type relates coefficients `[a0, a1, ..., an]`, say, to the polynomial `a0 + a1*x + a2*x^ + ... + an*x^n`, through the standard basis `1, x, x^2, ..., x^n`. New polynomial types typically represent the polynomial through a different basis. For example, `CheyshevT` uses a basis `T_0=1, T_1=x, T_2=2x^2-1, ..., T_n = 2xT_{n-1} - T_{n-2}`. For this type the coefficients `[a0,a1,...,an]` are associated with the polynomial `a0*T0 + a1*T_1 + ... + an*T_n`. + +To implement a new polynomial type, `P`, the following methods should +be implemented. !!! note Promotion rules will always coerce towards the [`Polynomial`](@ref) type, so not all methods have to be implemented if you provide a conversion function. @@ -26,5 +29,6 @@ As always, if the default implementation does not work or there are more efficie | `-(::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! +Check out both the [`Polynomial`](@ref) and [`ChebyshevT`](@ref) for examples of this interface being extended. diff --git a/docs/src/index.md b/docs/src/index.md index 20d413da..41bf91ba 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -104,8 +104,8 @@ ERROR: Polynomials must have same variable ### Integrals and Derivatives Integrate the polynomial `p` term by term, optionally adding constant -term `C`. The order of the resulting polynomial is one higher than the -order of `p`. +term `C`. The degree of the resulting polynomial is one higher than the +degree of `p`. ```jldoctest julia> integrate(Polynomial([1, 0, -1])) @@ -115,8 +115,8 @@ julia> integrate(Polynomial([1, 0, -1]), 2) Polynomial(2.0 + 1.0*x - 0.3333333333333333*x^3) ``` -Differentiate the polynomial `p` term by term. The order of the -resulting polynomial is one lower than the order of `p`. +Differentiate the polynomial `p` term by term. The degree of the +resulting polynomial is one lower than the degree of `p`. ```jldoctest julia> derivative(Polynomial([1, 3, -1])) @@ -137,8 +137,8 @@ julia> roots(Polynomial([1, 0, -1])) julia> roots(Polynomial([1, 0, 1])) 2-element Array{Complex{Float64},1}: - -0.0 + 1.0im 0.0 - 1.0im + 0.0 + 1.0im julia> roots(Polynomial([0, 0, 1])) 2-element Array{Float64,1}: @@ -148,13 +148,13 @@ julia> roots(Polynomial([0, 0, 1])) ### Fitting arbitrary data -Fit a polynomial (of order `deg`) to `x` and `y` using a least-squares approximation. +Fit a polynomial (of degree `deg`) to `x` and `y` using polynomial interpolation or a (weighted) least-squares approximation. ```@example using Plots, Polynomials xs = range(0, 10, length=10) ys = exp.(-xs) -f = fit(xs, ys) +f = fit(xs, ys) # fit(xs, ys, k) for fitting a kth degreee polynomial scatter(xs, ys, label="Data"); plot!(f, extrema(xs)..., label="Fit"); @@ -163,6 +163,47 @@ savefig("polyfit.svg"); nothing # hide ![](polyfit.svg) +### Other bases + +A polynomial, e.g. `a_0 + a_1 x + a_2 x^2 + ... + a_n x^n`, can be seen as a collection of coefficients, `[a_0, a_1, ..., a_n]`, relative to some polynomial basis. The most familiar basis being the standard one: `1`, `x`, `x^2`, ... Alternative bases are possible. The `ChebyshevT` polynomials are implemented, as an example. Instead of `Polynomial` or `Polynomial{T}`, `ChebyshevT` or `ChebyshevT{T}` constructors are used: + +```jldoctest +julia> p1 = ChebyshevT([1.0, 2.0, 3.0]) +ChebyshevT(1.0⋅T_0(x) + 2.0⋅T_1(x) + 3.0⋅T_2(x)) + +julia> p2 = ChebyshevT{Float64}([0, 1, 2]) +ChebyshevT(1.0⋅T_1(x) + 2.0⋅T_2(x)) + +julia> p1 + p2 +ChebyshevT(1.0⋅T_0(x) + 3.0⋅T_1(x) + 5.0⋅T_2(x)) + +julia> p1 * p2 +ChebyshevT(4.0⋅T_0(x) + 4.5⋅T_1(x) + 3.0⋅T_2(x) + 3.5⋅T_3(x) + 3.0⋅T_4(x)) + +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)) + +julia> convert(Polynomial, p1) +Polynomial(-2.0 + 2.0*x + 6.0*x^2) + +julia> convert(ChebyshevT, Polynomial([1.0, 2, 3])) +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`, ... + +```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)) +``` ## Related Packages @@ -170,7 +211,7 @@ savefig("polyfit.svg"); nothing # hide * [MultivariatePolynomials.jl](https://github.com/blegat/MultivariatePolynomials.jl) for multivariate polynomials and moments of commutative or non-commutative variables -* [Nemo.jl](https://github.com/wbhart/Nemo.jl) for generic polynomial rings, matrix spaces, fraction fields, residue rings, power series +* [AbstractAlgeebra.jl](https://github.com/wbhart/AbstractAlgebra.jl) for generic polynomial rings, matrix spaces, fraction fields, residue rings, power series. * [PolynomialRoots.jl](https://github.com/giordano/PolynomialRoots.jl) for a fast complex polynomial root finder diff --git a/docs/src/polynomials/chebyshev.md b/docs/src/polynomials/chebyshev.md index a2c4d428..4237d00a 100644 --- a/docs/src/polynomials/chebyshev.md +++ b/docs/src/polynomials/chebyshev.md @@ -6,6 +6,10 @@ DocTestSetup = quote end ``` + +The [Chebyshev polynomials](https://en.wikipedia.org/wiki/Chebyshev_polynomials) are two sequences of polynomials, `T_n` and `U_n`. The Chebyshev polynomials of the first kind, `T_n`, can be defined by the recurrence relation `T_0(x)=1`, `T_1(x)=x`, and `T_{n+1}(x) = 2xT_n{x}-T_{n-1}(x)`. The Chebyshev polynomioals of the second kind, `U_n(x)`, can be defined by `U_0(x)=1`, `U_1(x)=2x`, and `U_{n+1}(x) = 2xU_n(x) - U_{n-1}(x)`. Both `T_n` and `U_n` have degree `n`, and any polynomial of degree `n` may be uniquely written as a linear combination of the polynomials `T_0`, `T_1`, ..., `T_n` (similarly with `U`). + + ## First Kind ```@docs @@ -13,17 +17,23 @@ ChebyshevT ChebyshevT() ``` +The `ChebyshevT` type holds coefficients representing the polynomial `a_0 T_0 + a_1 T_1 + ... + a_n T_n`. + +For example, the basis polynomial `T_4` can be represented with `ChebyshevT([0,0,0,0,1])`. + + ### Conversion [`ChebyshevT`](@ref) can be converted to [`Polynomial`](@ref) and vice-versa. ```jldoctest julia> c = ChebyshevT([1, 0, 3, 4]) -ChebyshevT([1, 0, 3, 4]) +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) julia> convert(ChebyshevT, p) -ChebyshevT([1.0, 0.0, 3.0, 4.0]) +ChebyshevT(1.0⋅T_0(x) + 3.0⋅T_2(x) + 4.0⋅T_3(x)) ``` diff --git a/docs/src/reference.md b/docs/src/reference.md index cd5079e2..d3769b16 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -16,7 +16,6 @@ end ```@docs coeffs -order degree length size @@ -24,6 +23,7 @@ domain mapdomain chop chop! +round truncate truncate! ``` @@ -91,9 +91,10 @@ plot(::AbstractPolynomial, a, b; kwds...) will plot the polynomial within the range `[a, b]`. ### Example: The Polynomials.jl logo + ```@example using Plots, Polynomials -xs = range(-1, 1, length=100) +# T1, T2, T3, and T4: chebs = [ ChebyshevT([0, 1]), ChebyshevT([0, 0, 1]), @@ -101,9 +102,11 @@ chebs = [ ChebyshevT([0, 0, 0, 0, 1]), ] colors = ["#4063D8", "#389826", "#CB3C33", "#9558B2"] -plot() # hide -for (cheb, col) in zip(chebs, colors) - plot!(xs, cheb.(xs), c=col, lw=5, label="") +itr = zip(chebs, colors) +(cheb,col), state = iterate(itr) +p = plot(cheb, c=col, lw=5, legend=false, label="") +for (cheb, col) in Base.Iterators.rest(itr, state) + plot!(cheb, c=col, lw=5) end savefig("chebs.svg"); nothing # hide ``` diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 8db6f762..6fb7792f 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -8,16 +8,15 @@ include("show.jl") include("plots.jl") include("contrib.jl") +# Interface for all AbstractPolynomials +include("common.jl") + + # Polynomials include("polynomials/Polynomial.jl") include("polynomials/ChebyshevT.jl") -include("polynomials/ChebyshevU.jl") -include("polynomials/Poly.jl") # Deprecated -> Will be removed -include("pade.jl") -include("compat.jl") # Where we keep deprecations - -# Interface for all AbstractPolynomials -include("common.jl") +# to be deprecated, then removed +include("compat.jl") end # module diff --git a/src/abstract.jl b/src/abstract.jl index aed46039..eb63479b 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -35,7 +35,7 @@ macro register(name) poly = esc(name) quote Base.convert(::Type{P}, p::P) where {P<:$poly} = p - Base.convert(P::Type{<:$poly}, p::$poly) where {T} = P(p.coeffs, p.var) + Base.convert(P::Type{<:$poly}, p::$poly) where {T} = P(coeffs(p), p.var) 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} = diff --git a/src/common.jl b/src/common.jl index 4281fe4c..ec81f48c 100644 --- a/src/common.jl +++ b/src/common.jl @@ -31,12 +31,10 @@ julia> fromroots(r) Polynomial(6 - 5*x + x^2) ``` """ -function fromroots(P::Type{<:AbstractPolynomial}, - roots::AbstractVector; - var::SymbolLike = :x,) +function fromroots(P::Type{<:AbstractPolynomial}, roots::AbstractVector; var::SymbolLike = :x) x = variable(P, var) - p = [x - r for r in roots] - return truncate!(reduce(*, p)) + p = prod(x - r for r in roots) + return truncate!(p) end fromroots(r::AbstractVector{<:Number}; var::SymbolLike = :x) = fromroots(Polynomial, r, var = var) @@ -64,14 +62,13 @@ fromroots(A::AbstractMatrix{T}; var::SymbolLike = :x) where {T <: Number} = """ fit(x, y; [weights], deg=length(x) - 1, var=:x) fit(::Type{<:AbstractPolynomial}, x, y; [weights], deg=length(x)-1, var=:x) - Fit the given data as a polynomial type with the given degree. Uses linear least squares. When weights are given, as either a `Number`, `Vector` or `Matrix`, will use weighted linear least squares. The default polynomial type is [`Polynomial`](@ref). This will automatically scale your data to the [`domain`](@ref) of the polynomial type using [`mapdomain`](@ref) """ function fit(P::Type{<:AbstractPolynomial}, - x::AbstractVector{T}, - y::AbstractVector{T}; + x::AbstractVector{T}, + y::AbstractVector{T}, + deg::Integer = length(x) - 1; weights = nothing, - deg::Integer = length(x) - 1, var = :x,) where {T} x = mapdomain(P, x) vand = vander(P, x, deg) @@ -85,16 +82,16 @@ end fit(P::Type{<:AbstractPolynomial}, x, - y; + y, + deg::Integer = length(x) - 1; weights = nothing, - deg::Integer = length(x) - 1, - var = :x,) = fit(P, promote(collect(x), collect(y))...; weights = weights, deg = deg, var = var) + var = :x,) = fit(P, promote(collect(x), collect(y))..., deg; weights = weights, var = var) fit(x::AbstractVector, - y::AbstractVector; + y::AbstractVector, + deg::Integer = length(x) - 1; weights = nothing, - deg::Integer = length(x) - 1, - var = :x,) = fit(Polynomial, x, y; weights = weights, deg = deg, var = var) + var = :x,) = fit(Polynomial, x, y, deg; weights = weights, var = var) # Weighted linear least squares _wlstsq(vand, y, W::Number) = _wlstsq(vand, y, fill!(similar(y), W)) @@ -102,24 +99,20 @@ _wlstsq(vand, y, W::AbstractVector) = _wlstsq(vand, y, diagm(0 => W)) _wlstsq(vand, y, W::AbstractMatrix) = (vand' * W * vand) \ (vand' * W * y) """ - roots(::AbstractPolynomial) + roots(::AbstractPolynomial; kwargs...) + +Returns the roots of the given polynomial. This is calculated via the eigenvalues of the companion matrix. The `kwargs` are passed to the `LinearAlgeebra.eigvals` call. + +!!! note + + The [PolynomialRoots.jl](https://github.com/giordano/PolynomialRoots.jl) package provides an alternative that is a bit faster and abit more accurate; the [AMRVW.jl](https://github.com/jverzani/AMRVW.jl) package provides an alternative for high-degree polynomials. -Returns the roots of the given polynomial. This is calculated via the eigenvalues of the companion matrix. """ -function roots(p::AbstractPolynomial{T}) where {T <: Number} - d = length(p) - 1 - if d < 1 - return [] - end - d == 1 && return [-p[0] / p[1]] - - chopped_trimmed = truncate(p) - n_trail = length(p) - length(chopped_trimmed) - comp = companion(chopped_trimmed) - L = eigvals(rot180(comp)) - append!(L, zeros(eltype(L), n_trail)) - by = eltype(L) <: Complex ? norm : identity - return sort!(L, rev = true, by = by) +function roots(q::AbstractPolynomial{T}; kwargs...) where {T <: Number} + + p = convert(Polynomial{T}, q) + roots(p; kwargs...) + end """ @@ -167,7 +160,7 @@ Returns a polynomial that is the `order`th derivative of the given polynomial. ` derivative(::AbstractPolynomial, ::Int) """ - truncate!(::AbstractPolynomial{T}; + truncate!(::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) In-place version of [`truncate`](@ref) @@ -175,14 +168,14 @@ 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, p.coeffs) + max_coeff = maximum(abs, coeffs(p)) thresh = max_coeff * rtol + atol - map!(c->abs(c) <= thresh ? zero(T) : c, p.coeffs, p.coeffs) + map!(c->abs(c) <= thresh ? zero(T) : c, coeffs(p), coeffs(p)) return chop!(p, rtol = rtol, atol = atol) end """ - truncate(::AbstractPolynomial{T}; + truncate(::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) Rounds off coefficients close to zero, as determined by `rtol` and `atol`, and then chops any leading zeros. Returns a new polynomial. @@ -194,14 +187,15 @@ function Base.truncate(p::AbstractPolynomial{T}; end """ - chop!(::AbstractPolynomial{T}; + chop!(::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0)) In-place version of [`chop`](@ref) """ function chop!(p::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} + atol::Real = 0,) where {T} + degree(p) == -1 && return p for i = lastindex(p):-1:0 val = p[i] if !isapprox(val, zero(T); rtol = rtol, atol = atol) @@ -214,7 +208,7 @@ function chop!(p::AbstractPolynomial{T}; end """ - chop(::AbstractPolynomial{T}; + chop(::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0)) Removes any leading coefficients that are approximately 0 (using `rtol` and `atol`). Returns a polynomial whose degree will guaranteed to be equal to or less than the given polynomial's. @@ -225,12 +219,19 @@ function Base.chop(p::AbstractPolynomial{T}; chop!(deepcopy(p), rtol = rtol, atol = atol) end +""" + round(p::AbstractPolynomial, args...; kwargs) + +Applies `round` to the cofficients of `p` with the given arguments. Returns a new polynomial. +""" +Base.round(p::P, args...;kwargs...) where {P <: AbstractPolynomial} = P(round.(coeffs(p), args...; kwargs...), p.var) + """ variable(var=:x) variable(::Type{<:AbstractPolynomial}, var=:x) variable(p::AbstractPolynomial, var=p.var) -Return the indeterminate of a given polynomial. If no type is give, will default to [`Polynomial`](@ref) +Return the monomial `x` in the indicated polynomial basis. If no type is give, will default to [`Polynomial`](@ref). # Examples ```jldoctest @@ -251,7 +252,7 @@ variable(::Type{P}, var::SymbolLike = :x) where {P <: AbstractPolynomial} = P([0 variable(p::AbstractPolynomial, var::SymbolLike = p.var) = variable(typeof(p), var) variable(var::SymbolLike = :x) = variable(Polynomial{Int}) -#= +#= Linear Algebra =# """ norm(::AbstractPolynomial, p=2) @@ -269,7 +270,7 @@ LinearAlgebra.conj(p::P) where {P <: AbstractPolynomial} = P(conj(coeffs(p))) LinearAlgebra.transpose(p::AbstractPolynomial) = p LinearAlgebra.transpose!(p::AbstractPolynomial) = p -#= +#= Conversions =# Base.convert(::Type{P}, p::P) where {P <: AbstractPolynomial} = p Base.convert(P::Type{<:AbstractPolynomial}, x) = P(x) @@ -277,21 +278,21 @@ Base.promote_rule(::Type{<:AbstractPolynomial{T}}, ::Type{<:AbstractPolynomial{S}}, ) where {T,S} = Polynomial{promote_type(T, S)} -#= +#= Inspection =# """ length(::AbstractPolynomial) The length of the polynomial. """ -Base.length(p::AbstractPolynomial) = length(p.coeffs) +Base.length(p::AbstractPolynomial) = length(coeffs(p)) """ size(::AbstractPolynomial, [i]) Returns the size of the polynomials coefficients, along axis `i` if provided. """ -Base.size(p::AbstractPolynomial) = size(p.coeffs) -Base.size(p::AbstractPolynomial, i::Integer) = size(p.coeffs, i) +Base.size(p::AbstractPolynomial) = size(coeffs(p)) +Base.size(p::AbstractPolynomial, i::Integer) = size(coeffs(p), i) Base.eltype(p::AbstractPolynomial{T}) where {T} = T Base.eltype(::Type{P}) where {P <: AbstractPolynomial} = P function Base.iszero(p::AbstractPolynomial) @@ -316,13 +317,7 @@ has a nonzero coefficient. The degree of the zero polynomial is defined to be -1 """ degree(p::AbstractPolynomial) = iszero(p) ? -1 : length(p) - 1 -""" - order(::AbstractPolynomial) - -The order of the polynomial. This is the same as [`length`](@ref). -""" -order(p::AbstractPolynomial) = length(p) -hasnan(p::AbstractPolynomial) = any(isnan.(p.coeffs)) +hasnan(p::AbstractPolynomial) = any(isnan.(coeffs(p))) """ domain(::Type{<:AbstractPolynomial}) @@ -357,34 +352,59 @@ 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 Base.lastindex(p::AbstractPolynomial) = length(p) - 1 Base.eachindex(p::AbstractPolynomial) = 0:length(p) - 1 Base.broadcastable(p::AbstractPolynomial) = Ref(p) +# basis +# return the kth basis polynomial for the given polynomial type, e.g. x^k for Polynomial{T} +function basis(p::P, k::Int) where {P<:AbstractPolynomial} + basis(P, k) +end + +function basis(::Type{P}, k::Int; var=:x) where {P <: AbstractPolynomial} + zs = zeros(Int, k+1) + zs[end] = 1 + P(zs, var) +end + # iteration -Base.collect(p::P) where {P <: AbstractPolynomial} = collect(P, p) +# 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] * variable(p)^(state), state + 1) : nothing + state <= length(p) - 1 ? (p[state] * basis(p, state), state + 1) : nothing end + +Base.collect(p::P) where {P <: AbstractPolynomial} = collect(P, p) + +function Base.getproperty(p::AbstractPolynomial, nm::Symbol) + if nm == :a + Base.depwarn("AbstractPolynomial.a is deprecated, use AbstractPolynomial.coeffs or coeffs(AbstractPolynomial) instead.", + Symbol("Base.getproperty"), + ) + return getfield(p, :coeffs) + end + return getfield(p, nm) +end + + # 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 p.coeffs[idx + 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] -Base.getindex(p::AbstractPolynomial, ::Colon) = p.coeffs +Base.getindex(p::AbstractPolynomial, ::Colon) = coeffs(p) # setindex function Base.setindex!(p::AbstractPolynomial, value::Number, idx::Int) - n = length(p.coeffs) + n = length(coeffs(p)) if n ≤ idx resize!(p.coeffs, idx + 1) p.coeffs[n + 1:idx] .= 0 @@ -404,10 +424,10 @@ 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))] -#= +#= identity =# -Base.copy(p::P) where {P <: AbstractPolynomial} = P(copy(p.coeffs), p.var) -Base.hash(p::AbstractPolynomial, h::UInt) = hash(p.var, hash(p.coeffs, h)) +Base.copy(p::P) where {P <: AbstractPolynomial} = P(copy(coeffs(p)), p.var) +Base.hash(p::AbstractPolynomial, h::UInt) = hash(p.var, hash(coeffs(p), h)) """ zero(::Type{<:AbstractPolynomial}) zero(::AbstractPolynomial) @@ -425,9 +445,9 @@ Returns a representation of 1 as the given polynomial. Base.one(::Type{P}) where {P <: AbstractPolynomial} = P(ones(1)) Base.one(p::P) where {P <: AbstractPolynomial} = one(P) -#= +#= arithmetic =# -Base.:-(p::P) where {P <: AbstractPolynomial} = P(-p.coeffs, p.var) +Base.:-(p::P) where {P <: AbstractPolynomial} = P(-coeffs(p), p.var) Base.:+(c::Number, p::AbstractPolynomial) = +(p, c) Base.:-(p::AbstractPolynomial, c::Number) = +(p, -c) Base.:-(c::Number, p::AbstractPolynomial) = +(-p, c) @@ -435,11 +455,11 @@ Base.:*(c::Number, p::AbstractPolynomial) = *(p, c) function Base.:*(p::P, c::S) where {P <: AbstractPolynomial,S} T = promote_type(P, S) - return T(p.coeffs .* c, p.var) + return T(coeffs(p) .* c, p.var) end function Base.:/(p::P, c::S) where {T,P <: AbstractPolynomial{T},S} R = promote_type(P, eltype(one(T) / one(S))) - return R(p.coeffs ./ c, p.var) + return R(coeffs(p) ./ c, p.var) end Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) @@ -487,7 +507,7 @@ function Base.gcd(p1::AbstractPolynomial{T}, p2::AbstractPolynomial{S}) where {T while r₁ ≉ zero(r₁) && iter ≤ itermax # just to avoid unnecessary recursion _, rtemp = divrem(r₀, r₁) r₀ = r₁ - r₁ = truncate(rtemp) + r₁ = truncate(rtemp) iter += 1 end return r₀ @@ -503,12 +523,12 @@ Base.div(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[1] """ Base.rem(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[2] -#= +#= Comparisons =# Base.isequal(p1::P, p2::P) where {P <: AbstractPolynomial} = hash(p1) == hash(p2) Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) = - (p1.var == p2.var) && (p1.coeffs == p2.coeffs) -Base.:(==)(p::AbstractPolynomial, n::Number) = p.coeffs == [n] + (p1.var == p2.var) && (coeffs(p1) == coeffs(p2)) +Base.:(==)(p::AbstractPolynomial, n::Number) = coeffs(p) == [n] Base.:(==)(n::Number, p::AbstractPolynomial) = p == n function Base.isapprox(p1::AbstractPolynomial{T}, @@ -524,7 +544,7 @@ function Base.isapprox(p1::AbstractPolynomial{T}, if length(p1t) ≠ length(p2t) return false end - isapprox(p1t.coeffs, p2t.coeffs, rtol = rtol, atol = atol) + isapprox(coeffs(p1t), coeffs(p2t), rtol = rtol, atol = atol) end function Base.isapprox(p1::AbstractPolynomial{T}, @@ -535,7 +555,7 @@ function Base.isapprox(p1::AbstractPolynomial{T}, if length(p1t) != 1 return false end - isapprox(p1t.coeffs, [n], rtol = rtol, atol = atol) + isapprox(coeffs(p1t), [n], rtol = rtol, atol = atol) end Base.isapprox(n::S, diff --git a/src/compat.jl b/src/compat.jl index ef480eef..d1981e9a 100644 --- a/src/compat.jl +++ b/src/compat.jl @@ -1,22 +1,36 @@ -@deprecate poly(r, var = :x) fromroots(Polynomial, r; var = var) -@deprecate polyval(p::AbstractPolynomial, x::Number) p(x) -@deprecate polyval(p::AbstractPolynomial, x) p.(x) - -function Base.getproperty(p::AbstractPolynomial, nm::Symbol) - if nm == :a - Base.depwarn("AbstracPolynomial.a is deprecated, use AbstracPolynomial.coeffs or coeffs(AbstractPolynomial) instead.", - Symbol("Base.getproperty"), - ) - return getfield(p, :coeffs) - end - return getfield(p, nm) -end - -@deprecate polyint(p::AbstractPolynomial, C = 0) integrate(p, C) -@deprecate polyint(p::AbstractPolynomial, a, b) integrate(p, a, b) -@deprecate polyder(p::AbstractPolynomial, ord = 1) derivative(p, ord) -@deprecate polyfit(x, y, n = length(x) - 1) fit(Polynomial, x, y; deg = n) -@deprecate polyfit(x, y, sym::Symbol) fit(Polynomial, x, y; var = sym) - -@deprecate padeval(PQ::Pade, x::Number) PQ(x) -@deprecate padeval(PQ::Pade, x) PQ.(x) +## We have renamed the MATLAB/numpy type names to more Julian names +## How to keep the old names during a transition is the question. +## The plan: keep these to ensure underlying changes are not disruptive +## For now we ensure compatability by defining these for `Poly` objects such +## that they do not signal a deprecation (save polyfit)), +## but do for other `AbstractPolynomial` types. +## At v1.0, it is likely these will be removed. + +## Ensure compatability for now +@deprecate polyval(p::AbstractPolynomial, x::Number) p(x) +@deprecate polyval(p::AbstractPolynomial, x) p.(x) + + +@deprecate polyint(p::AbstractPolynomial, C = 0) integrate(p, C) +@deprecate polyint(p::AbstractPolynomial, a, b) integrate(p, a, b) + +@deprecate polyder(p::AbstractPolynomial, ord = 1) derivative(p, ord) + +@deprecate polyfit(x, y, n = length(x) - 1, sym=:x) fit(Poly, x, y, n; var = sym) +@deprecate polyfit(x, y, sym::Symbol) fit(Poly, x, y, var = sym) + + +include("polynomials/Poly.jl") +using .PolyCompat +export Poly +export poly, polyval, polyint, polyder, polyfit + + + + +## Pade +## Pade will likely be moved into a separate pacakge +include("pade.jl") +using .PadeApproximation +export Pade +export padeval diff --git a/src/pade.jl b/src/pade.jl index 36b0ba53..4ff9f8c0 100644 --- a/src/pade.jl +++ b/src/pade.jl @@ -1,3 +1,8 @@ +module PadeApproximation + +using ..Polynomials +export Pade, padeval + #= Pade approximation @@ -94,3 +99,12 @@ true ``` """ (PQ::Pade)(x) = PQ.p(x) / PQ.q(x) + + +## Compat +padeval(PQ::Pade, x::Number) = PQ(x) +padeval(PQ::Pade, x) = PQ.(x) + + + +end diff --git a/src/plots.jl b/src/plots.jl index 89f4b4b4..eebf1300 100644 --- a/src/plots.jl +++ b/src/plots.jl @@ -1,6 +1,19 @@ using RecipesBase function poly_interval(p::AbstractPolynomial) + + # use restricted domain, if finite + A,B = domain(p).first, domain(p).last + if !isinf(A) && !isinf(B) + if isopen(domain(p)) + Delta = (B-A)/100 + A += Delta + B -= Delta + end + return A:(B-A)/100:B + end + + # Find points of interest zero_pts = roots(p) crit_pts = roots(derivative(p, 1)) @@ -9,10 +22,12 @@ function poly_interval(p::AbstractPolynomial) # Choose a range that shows all interesting points with some margin min_x, max_x = length(pts) > 0 ? (pts[1], pts[end]) : (-1, 1) d = max(max_x - min_x, 1) - a = min_x - d / 2 - b = max_x + d / 2 + a = min_x - d / 5 + b = max_x + d / 5 + + Delta = b - a - return a:d / 50:b + return a:Delta/50:b end poly_label(p::AbstractPolynomial) = sprint(printpoly, p) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 4f06687e..691bcd55 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -126,27 +126,31 @@ function integrate(p::ChebyshevT{T}, C::S) where {T,S <: Number} return ChebyshevT(a2, p.var) end + function derivative(p::ChebyshevT{T}, order::Integer = 1) where {T} - order < 0 && error("Order of derivative must be non-negative") - order == 0 && return p - hasnan(p) && return ChebyshevT(T[NaN], p.var) - order > length(p) && return zero(ChebyshevT{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) + order > length(p) && return zero(ChebyshevT{R}) + + q = convert(ChebyshevT{R}, copy(p)) n = length(p) - der = Vector{T}(undef, n) - for i in 1:order - n -= 1 - resize!(der, n) - for j in n:-1:2 - der[j] = 2j * p[j] - p[j - 2] += j * p[j] / (j - 2) - end - if n > 1 - der[2] = 4p[2] - end - der[1] = p[1] + der = Vector{R}(undef, n) + + for j in n:-1:2 + der[j] = 2j * q[j] + q[j - 2] += j * q[j] / (j - 2) end - return ChebyshevT(der, p.var) + if n > 1 + der[2] = 4q[2] + end + der[1] = q[1] + + pp = ChebyshevT(der, p.var) + return order > 1 ? derivative(pp, order - 1) : pp + end function companion(p::ChebyshevT{T}) where T @@ -205,13 +209,16 @@ function Base.divrem(num::ChebyshevT{T}, den::ChebyshevT{S}) where {T,S} return P(q_coeff, num.var), P(r_coeff, num.var) end -function printpoly(io::IO, p::ChebyshevT{T}, mimetype = MIME"text/plain"(); descending_powers = false, offset::Int = 0) where {T} - chopped = chop(p) - print(io, coeffs(chopped)) - return nothing +function showterm(io::IO, ::Type{ChebyshevT{T}}, pj::T, var, j, first::Bool, mimetype) where {N, T} + iszero(pj) && return false + !first && print(io, " ") + print(io, hasneg(T) && isneg(pj) ? "- " : (!first ? "+ " : "")) + print(io, "$(abs(pj))⋅T_$j($var)") + return true end -#= + +#= zseries =# function _c_to_z(cs::AbstractVector{T}) where {T} @@ -255,7 +262,7 @@ function _z_division(z1::AbstractVector{T}, z2::AbstractVector{S}) where {T,S} i += 1 j -= 1 end - + r = z1[i] quo[i] = r tmp = r * z2 diff --git a/src/polynomials/ChebyshevU.jl b/src/polynomials/ChebyshevU.jl index aee3a275..17440606 100644 --- a/src/polynomials/ChebyshevU.jl +++ b/src/polynomials/ChebyshevU.jl @@ -1,4 +1,4 @@ struct ChebyshevU{T <: Number} <: AbstractPolynomial{T} coeffs::Vector{T} var::Symbol -end \ No newline at end of file +en diff --git a/src/polynomials/Poly.jl b/src/polynomials/Poly.jl index e204b8f0..bfb06d08 100644 --- a/src/polynomials/Poly.jl +++ b/src/polynomials/Poly.jl @@ -1,4 +1,9 @@ -#= +module PolyCompat + +using ..Polynomials +export poly, polyval, polyint, polyder #, polyfit + +#= This type is only here to provide stability while deprecating. This will eventually be removed in favor of `Polynomial` =# @@ -7,9 +12,9 @@ export Poly struct Poly{T <: Number} <: AbstractPolynomial{T} coeffs::Vector{T} var::Symbol - function Poly(a::AbstractVector{T}, var::SymbolLike = :x) where {T <: Number} + function Poly(a::AbstractVector{T}, var::Polynomials.SymbolLike = :x) where {T <: Number} # if a == [] we replace it with a = [0] - Base.depwarn("Poly is deprecated and will be removed in a future release. Please use Polynomial instead", :Poly) +## Base.depwarn("Poly is deprecated and will be removed in a future release. Please use Polynomial instead", :Poly) if length(a) == 0 return new{T}(zeros(T, 1), Symbol(var)) else @@ -21,24 +26,29 @@ struct Poly{T <: Number} <: AbstractPolynomial{T} end end -@register Poly +Polynomials.@register Poly + + + + Base.convert(P::Type{<:Polynomial}, p::Poly{T}) where {T} = P(p.coeffs, p.var) -domain(::Type{<:Poly}) = Interval(-Inf, Inf) -mapdomain(::Type{<:Poly}, x::AbstractArray) = x +Polynomials.domain(::Type{<:Poly}) = Polynomials.Interval(-Inf, Inf) +Polynomials.mapdomain(::Type{<:Poly}, x::AbstractArray) = x function (p::Poly{T})(x::S) where {T,S} - R = promote_type(T, S) - length(p) == 0 && return zero(R) - y = convert(R, p[end]) + oS = one(x) + length(p) == 0 && return zero(T) * oS + b = p[end] * oS @inbounds for i in (lastindex(p) - 1):-1:0 - y = p[i] + x * y + b = p[i]*oS .+ x * b end - return y + return b end -function fromroots(P::Type{<:Poly}, r::AbstractVector{T}; var::SymbolLike = :x) where {T <: Number} + +function Polynomials.fromroots(P::Type{<:Poly}, r::AbstractVector{T}; var::Polynomials.SymbolLike = :x) where {T <: Number} n = length(r) c = zeros(T, n + 1) c[1] = one(T) @@ -49,7 +59,7 @@ function fromroots(P::Type{<:Poly}, r::AbstractVector{T}; var::SymbolLike = :x) end -function vander(P::Type{<:Poly}, x::AbstractVector{T}, n::Integer) where {T <: Number} +function Polynomials.vander(P::Type{<:Poly}, x::AbstractVector{T}, n::Integer) where {T <: Number} A = Matrix{T}(undef, length(x), n + 1) A[:, 1] .= one(T) @inbounds for i in 1:n @@ -59,7 +69,7 @@ function vander(P::Type{<:Poly}, x::AbstractVector{T}, n::Integer) where {T <: N end -function integrate(p::Poly{T}, k::S) where {T,S <: Number} +function Polynomials.integrate(p::Poly{T}, k::S) where {T,S <: Number} R = promote_type(eltype(one(T) / 1), S) if hasnan(p) || isnan(k) return Poly([NaN]) @@ -74,7 +84,7 @@ function integrate(p::Poly{T}, k::S) where {T,S <: Number} end -function derivative(p::Poly{T}, order::Integer = 1) where {T} +function Polynomials.derivative(p::Poly{T}, order::Integer = 1) where {T} order < 0 && error("Order of derivative must be non-negative") order == 0 && return p hasnan(p) && return Poly(T[NaN], p.var) @@ -89,7 +99,7 @@ function derivative(p::Poly{T}, order::Integer = 1) where {T} end -function companion(p::Poly{T}) where T +function Polynomials.companion(p::Poly{T}) where T d = length(p) - 1 d < 1 && error("Series must have degree greater than 1") d == 1 && return diagm(0 => [-p[0] / p[1]]) @@ -129,7 +139,7 @@ function Base.divrem(num::Poly{T}, den::Poly{S}) where {T,S} R = typeof(one(T) / one(S)) P = Poly{R} deg = n - m + 1 - if deg ≤ 0 + if deg ≤ 0 return zero(P), convert(P, num) end q_coeff = zeros(R, deg) @@ -145,4 +155,28 @@ function Base.divrem(num::Poly{T}, den::Poly{S}) where {T,S} return P(q_coeff, num.var), P(r_coeff, num.var) end -showterm(io::IO, ::Type{Poly{T}}, pj::T, var, j, first::Bool, mimetype) where {T} = showterm(io, Polynomial{T}, pj, var, j, first, mimetype) +Polynomials.showterm(io::IO, ::Type{Poly{T}}, pj::T, var, j, first::Bool, mimetype) where {T} = showterm(io, Polynomial{T}, pj, var, j, first, mimetype) + + + +## Compat +poly(r, var = :x) = fromroots(Poly, r; var = var) + +Polynomials.polyval(p::Poly, x::Number) = p(x) +Polynomials.polyval(p::Poly, x) = p.(x) + +function Base.getproperty(p::Poly, nm::Symbol) + if nm == :a + return getfield(p, :coeffs) + end + return getfield(p, nm) +end + +Polynomials.polyint(p::Poly, C = 0) = integrate(p, C) +Polynomials.polyint(p::Poly, a, b) = integrate(p, a, b) +Polynomials.polyder(p::Poly, ord = 1) = derivative(p, ord) + +#polyfit(x, y, n = length(x) - 1, sym=:x) = fit(Poly, x, y, n; var = sym) +#polyfit(x, y, sym::Symbol) = fit(Poly, x, y, var = sym) + +end diff --git a/src/polynomials/Polynomial.jl b/src/polynomials/Polynomial.jl index 7e16d1de..ad288b04 100644 --- a/src/polynomials/Polynomial.jl +++ b/src/polynomials/Polynomial.jl @@ -64,15 +64,38 @@ julia> p.(0:3) ``` """ function (p::Polynomial{T})(x::S) where {T,S} - R = promote_type(T, S) - length(p) == 0 && return zero(R) - b = convert(R, p[end]) + oS = one(x) + length(p) == 0 && return zero(T) * oS + b = p[end] * oS @inbounds for i in (lastindex(p) - 1):-1:0 - b = p[i] + x * b + b = p[i]*oS .+ x * b # not muladd(x,b,p[i]), unless we want to add methods for matrices, ... end return b end +## From base/math.jl from Julia 1.4 +function (p::Polynomial{T})(z::S) where {T,S <: Complex} + d = degree(p) + d == -1 && zero(z) + d == 0 && return p[0] + N = d + 1 + a = p[end] + b = p[end-1] + + x = real(z) + y = imag(z) + r = 2x + s = muladd(x, x, y*y) + for i in d-2:-1:0 + ai = a + a = muladd(r, ai, b) + b = p[i] - s * ai + end + ai = a + muladd(ai, z, b) +end + + function fromroots(P::Type{<:Polynomial}, r::AbstractVector{T}; var::SymbolLike = :x) where {T <: Number} n = length(r) c = zeros(T, n + 1) @@ -108,12 +131,13 @@ end function derivative(p::Polynomial{T}, order::Integer = 1) where {T} order < 0 && error("Order of derivative must be non-negative") - order == 0 && return p - hasnan(p) && return Polynomial(T[NaN], p.var) - order > length(p) && return zero(Polynomial{T}) + R = eltype(one(T)/1) + order == 0 && return convert(Polynomial{R}, p) + hasnan(p) && return Polynomial(R[NaN], p.var) + order > length(p) && return zero(Polynomial{R}) n = length(p) - a2 = Vector{T}(undef, n - order) + 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 @@ -127,26 +151,64 @@ function companion(p::Polynomial{T}) where T R = eltype(one(T) / p.coeffs[end]) comp = diagm(-1 => ones(R, d - 1)) - monics = p.coeffs ./ p.coeffs[end] - comp[:, end] .= -monics[1:d] + ani = 1 / p[end] + for j in 0:(degree(p)-1) + comp[1,(d-j)] = -p[j] * ani # along top row has smaller residual than down column + end + # monics = p.coeffs ./ p.coeffs[end] + # comp[:, end] .= -monics[1:d] return comp end -function Base.:+(p1::Polynomial, p2::Polynomial) +function roots(p::Polynomial{T}; kwargs...) where {T} + d = length(p) - 1 + if d < 1 + return [] + end + d == 1 && return [-p[0] / p[1]] + + as = coeffs(p) + K = findlast(!iszero, as) + if K == nothing + return eltype(p[0]/p[0])[] + end + k = findfirst(!iszero, as) + + k == K && return zeros(eltype(p[0]/p[0]), k-1) + + comp = companion(typeof(p)(as[k:K], p.var)) + #L = eigvals(rot180(comp); kwargs...) + L = eigvals(comp; kwargs...) + append!(L, zeros(eltype(L), k-1)) + + L +end + +function Base.:+(p1::Polynomial{T}, p2::Polynomial{S}) where {T, S} + R = promote_type(T,S) p1.var != p2.var && error("Polynomials must have same variable") - n = max(length(p1), length(p2)) - c = [p1[i] + p2[i] for i = 0:n] + + n1, n2 = length(p1), length(p2) + c = [p1[i] + p2[i] for i = 0:max(n1, n2)] return Polynomial(c, p1.var) end + +function Base.:+(p::Polynomial{T}, c::S) where {T,S<:Number} + U = promote_type(T, S) + q = copy(p) + p2 = U == S ? q : convert(Polynomial{U}, q) + p2[0] += c + return p2 +end + function Base.:*(p1::Polynomial{T}, p2::Polynomial{S}) where {T,S} p1.var != p2.var && error("Polynomials must have same variable") - n = length(p1) - 1 - m = length(p2) - 1 + n,m = length(p1)-1, length(p2)-1 # not degree, so pNULL works R = promote_type(T, S) c = zeros(R, m + n + 1) for i in 0:n, j in 0:m - c[i + j + 1] += p1[i] * p2[j] + @inbounds c[i + j + 1] += p1[i] * p2[j] end return Polynomial(c, p1.var) end @@ -159,11 +221,11 @@ function Base.divrem(num::Polynomial{T}, den::Polynomial{S}) where {T,S} R = typeof(one(T) / one(S)) P = Polynomial{R} deg = n - m + 1 - if deg ≤ 0 + if deg ≤ 0 return zero(P), convert(P, num) end q_coeff = zeros(R, deg) - r_coeff = R.(num[0:n]) + r_coeff = R[ num[i-1] for i in 1:n+1 ] @inbounds for i in n:-1:m q = r_coeff[i + 1] / den[m] q_coeff[i - m + 1] = q @@ -176,9 +238,9 @@ function Base.divrem(num::Polynomial{T}, den::Polynomial{S}) where {T,S} end function showterm(io::IO, ::Type{Polynomial{T}}, pj::T, var, j, first::Bool, mimetype) where {T} - if pj == zero(T) return false end + if iszero(pj) return false end pj = printsign(io, pj, first, mimetype) - if !(pj == one(T) && !(showone(T) || j == 0)) + if !(pj == one(T) && !(showone(T) || j == 0)) printcoefficient(io, pj, j, mimetype) end printproductsign(io, pj, j, mimetype) diff --git a/test/ChebyshevT.jl b/test/ChebyshevT.jl index fcab154a..addbfa46 100644 --- a/test/ChebyshevT.jl +++ b/test/ChebyshevT.jl @@ -3,12 +3,11 @@ Float32[1, -4, 2], ComplexF64[1 - 1im, 2 + 3im], [3 // 4, -2 // 1, 1 // 1] -] +] p = ChebyshevT(coeff) @test p.coeffs == coeff @test coeffs(p) == coeff @test degree(p) == length(coeff) - 1 - @test order(p) == length(p) == length(coeff) @test p.var == :x @test length(p) == length(coeff) @test size(p) == size(coeff) @@ -32,7 +31,7 @@ end p = zero(ChebyshevT{Int}) @test p.coeffs == [0] - + p = one(ChebyshevT{Int}) @test p.coeffs == [1] @@ -46,17 +45,17 @@ end end @testset "Roots $i" for i in 1:5 - roots = cos.(range(-π, 0, length = 2i + 1)[2:2:end]) + roots = cos.(range(-π, stop=0, length = 2i + 1)[2:2:end]) target = ChebyshevT(vcat(zeros(i), 1)) res = fromroots(ChebyshevT, roots) .* 2^(i - 1) @test res == target end @testset "Roots" begin - r = [-1, 0, 1] + r = [-1, 1, 0] c = fromroots(ChebyshevT, r) @test c == ChebyshevT([0, -0.25, 0, 0.25]) - @test roots(c) ≈ sort(r, rev = true) + @test roots(c) ≈ r r = [1im, -1im] c = fromroots(ChebyshevT, r) @@ -162,7 +161,7 @@ end for k in [-3, 0, 2] @test integrate(c1, k) == ChebyshevT([k, 1]) end - + for i in 0:4 scl = i + 1 p = Polynomial(vcat(zeros(i), 1)) @@ -173,6 +172,12 @@ end @test res ≈ target @test derivative(cint) == cheb end + + for i in 1:10 + p = ChebyshevT{Float64}(rand(1:5, 6)) + @test degree(round(p - integrate(derivative(p)), digits=13)) <= 0 + @test degree(round(p - derivative(integrate(p)), digits=13)) <= 0 + end end @testset "z-series" for i in 0:5 diff --git a/test/Polynomial.jl b/test/Polynomial.jl index 8205c00a..730bda3b 100644 --- a/test/Polynomial.jl +++ b/test/Polynomial.jl @@ -5,12 +5,11 @@ using LinearAlgebra Float32[1, -4, 2], ComplexF64[1 - 1im, 2 + 3im], [3 // 4, -2 // 1, 1 // 1] -] +] p = Polynomial(coeff) @test p.coeffs == coeff @test coeffs(p) == coeff @test degree(p) == length(coeff) - 1 - @test order(p) == length(p) == length(coeff) @test p.var == :x @test length(p) == length(coeff) @test size(p) == size(coeff) @@ -45,7 +44,7 @@ end p = zero(Polynomial{Int}) @test p.coeffs == [0] - + p = one(Polynomial{Int}) @test p.coeffs == [1] @@ -168,14 +167,14 @@ end abs_error = abs.(y_fit .- ys) @test maximum(abs_error) <= 0.03 - p = fit(Polynomial, xs, ys, deg = 2) + p = fit(Polynomial, xs, ys, 2) y_fit = p.(xs) abs_error = abs.(y_fit .- ys) @test maximum(abs_error) <= 0.03 # Test weighted for W in [1, ones(size(xs)), diagm(0 => ones(size(xs)))] - p = fit(Polynomial, xs, ys, weights = W, deg = 2) + p = fit(Polynomial, xs, ys, 2, weights = W) @test p.(xs) ≈ y_fit end @@ -183,7 +182,7 @@ end # Getting error on passing Real arrays to polyfit #146 xx = Real[20.0, 30.0, 40.0] yy = Real[15.7696, 21.4851, 28.2463] - fit(xx, yy, deg = 2) + fit(xx, yy, 2) end @testset "Values" begin @@ -200,6 +199,12 @@ end @test isnan(p1(-Inf)) @test isnan(p1(0)) @test p2(-Inf) == -Inf + + # issue #189 + p = Polynomial([0,1,2,3]) + A = [0 1; 0 0]; + @test p(A) == A + 2A^2 + 3A^3 + end @testset "Conversion" begin @@ -217,11 +222,11 @@ end @testset "Roots" begin # From roots - r = [3, 2] + r = [2, 3] p = fromroots(Polynomial, r) @test fromroots(r) == Polynomial([6, -5, 1]) @test p == Polynomial([6, -5, 1]) - @test roots(p) ≈ r + @test sort(roots(p)) ≈ r @test roots(p0) == roots(p1) == roots(pNULL) == [] @test roots(Polynomial([0,1,0])) == [0.0] @@ -264,6 +269,13 @@ end @test integrate(Polynomial(rc)) == Polynomial{eltype(rc)}([0, 1, 1, 1]) @test integrate(Polynomial([1,1,0,0]), 0, 2) == 4.0 + for i in 1:10 + p = Polynomial{Float64}(rand(1:5, 6)) + @test degree(round(p - integrate(derivative(p)), digits=13)) <= 0 + @test degree(round(p - derivative(integrate(p)), digits=13)) <= 0 + end + + # Handling of `NaN`s p = Polynomial([NaN, 1, 5]) pder = derivative(p) @@ -317,6 +329,10 @@ end pchopped = chop(pchop) @test roots(pchop) == roots(pchopped) + # round + psmall = Polynomial(eps()*rand(1:10, 9)) + @test degree(round(psmall, digits=14)) == -1 + end @testset "Linear Algebra" begin @@ -364,7 +380,7 @@ end for term in p1 @test isa(term, Polynomial) end - + @test eltype(p1) == Int @test eltype(collect(p1)) == Polynomial{Int} @test eltype(collect(Polynomial{Float64}, p1)) == Polynomial{Float64} @@ -473,7 +489,7 @@ end @testset "Plotting" begin p = fromroots([-1, 1]) # x^2 - 1 - r = -2:0.04:2 + r = -1.4:0.055999999999999994:1.4 rec = apply_recipe(Dict{Symbol,Any}(), p) @test getfield(rec[1], 1) == Dict{Symbol,Any}(:label => "-1 + x^2") @test rec[1].args == (r, p.(r)) @@ -482,4 +498,10 @@ end rec = apply_recipe(Dict{Symbol,Any}(), p, -1, 1) @test getfield(rec[1], 1) == Dict{Symbol,Any}(:label => "-1 + x^2") @test rec[1].args == (r, p.(r)) + + p = ChebyshevT([1,1,1]) + rec = apply_recipe(Dict{Symbol,Any}(), p) + r = -1.0:0.02:1.0 # uses domain(p) + @test rec[1].args == (r, p.(r)) + end diff --git a/test/runtests.jl b/test/runtests.jl index 46825acc..442c84c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,5 +9,5 @@ import SparseArrays: sparse, nnz @testset "Polynomial" begin include("Polynomial.jl") end @testset "ChebyshevT" begin include("ChebyshevT.jl") end -@testset "Deprecations" begin include("deprecated.jl") end -@testset "Poly (deprecated)" begin include("Poly.jl") end +#@testset "Deprecations" begin include("deprecated.jl") end +@testset "Poly (compatability)" begin include("Poly.jl") end